diff options
Diffstat (limited to 'backend/pixma')
| -rw-r--r-- | backend/pixma/pixma.c | 2166 | ||||
| -rw-r--r-- | backend/pixma/pixma.h | 506 | ||||
| -rw-r--r-- | backend/pixma/pixma_bjnp.c | 2645 | ||||
| -rw-r--r-- | backend/pixma/pixma_bjnp.h | 201 | ||||
| -rw-r--r-- | backend/pixma/pixma_bjnp_private.h | 384 | ||||
| -rw-r--r-- | backend/pixma/pixma_common.c | 1187 | ||||
| -rw-r--r-- | backend/pixma/pixma_common.h | 232 | ||||
| -rw-r--r-- | backend/pixma/pixma_imageclass.c | 985 | ||||
| -rw-r--r-- | backend/pixma/pixma_io.h | 186 | ||||
| -rw-r--r-- | backend/pixma/pixma_io_sanei.c | 556 | ||||
| -rw-r--r-- | backend/pixma/pixma_mp150.c | 1817 | ||||
| -rw-r--r-- | backend/pixma/pixma_mp730.c | 846 | ||||
| -rw-r--r-- | backend/pixma/pixma_mp750.c | 972 | ||||
| -rw-r--r-- | backend/pixma/pixma_mp800.c | 2434 | ||||
| -rw-r--r-- | backend/pixma/pixma_rename.h | 105 | ||||
| -rw-r--r-- | backend/pixma/pixma_sane_options.c | 362 | ||||
| -rw-r--r-- | backend/pixma/pixma_sane_options.h | 51 | ||||
| -rwxr-xr-x | backend/pixma/scripts/pixma_gen_options.py | 389 | 
18 files changed, 16024 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" diff --git a/backend/pixma/pixma.h b/backend/pixma/pixma.h new file mode 100644 index 0000000..c2df3cc --- /dev/null +++ b/backend/pixma/pixma.h @@ -0,0 +1,506 @@ +/* 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. + */ +#ifndef PIXMA_H +#define PIXMA_H + +#include "../include/sane/sane.h" + + +/*! + * \mainpage Scanner driver for Canon PIXMA MP series + * \section example Sample code for application + * \code + *    pixma_set_debug_level(level); + *    pixma_init(); + *    nscanners = pixma_find_scanners(); + *    devnr = choose_scanner(nscanners); + *    scanner = pixma_open(devnr); + *    setup_param(param); + *    pixma_check_scan_param(scanner, param); + *    do { + *        if (I_need_events && + *            (ev = pixma_wait_event(scanner, timeout)) > 0) { + *            handle_event(ev); + *        } + *        pixma_scan(scanner, param); + *        while ((count = pixma_read_image(scanner, buf, len)) > 0) { + *            write(buf, count); + *            if (error_occured_in_write) { + *                pixma_cancel(scanner); + *            } + *        } + *    } while (!enough); + *    pixma_close(scanner); + *    pixma_cleanup(); + * \endcode + * + * <b>Note:</b> pixma_cancel() can be called asynchronously to + * interrupt pixma_read_image(). It does not cancel the operation + * immediately. pixma_read_image() <em>must</em> be called until it + * returns zero or an error (probably \c PIXMA_ECANCELED). + * + * \section reference Reference + * - \subpage API + * - \subpage IO + * - \subpage subdriver + * - \subpage debug + */ + +/*! + * \defgroup API The driver API + * \brief The driver API. + * + * The return value of functions that returns \c int has the following + * meaning if not otherwise specified: + *    - >= 0  if succeeded + *    - < 0 if failed + */ + +#ifdef HAVE_STDINT_H +# include <stdint.h>		/* available in ISO C99 */ +#else +# include <sys/types.h> +typedef uint8_t uint8_t; +typedef uint16_t uint16_t; +typedef uint32_t uint32_t; +#endif /* HAVE_STDINT_H */ + +#ifdef HAVE_INTTYPES_H +# include <inttypes.h>          /* available in ISO C99 */ +#endif /* HAVE_INTTYPES_H */ + +/** \addtogroup API + *  @{ */ +/** Don't forget to update the backend version in the SANE Backend specification + *  file: doc/descriptions/pixma.desc !!! + */ +/** \name Version of the driver */ +/**@{*/ +#define PIXMA_VERSION_MAJOR 0 +#define PIXMA_VERSION_MINOR 27 +#define PIXMA_VERSION_BUILD 0 +/**@}*/ + +/** \name Error codes */ +/**@{*/ +#define PIXMA_EIO               -1 +#define PIXMA_ENODEV            -2 +#define PIXMA_EACCES            -3 +#define PIXMA_ENOMEM            -4 +#define PIXMA_EINVAL            -5 +#define PIXMA_EBUSY             -6 +#define PIXMA_ECANCELED         -7 +#define PIXMA_ENOTSUP           -8 +#define PIXMA_ETIMEDOUT         -9 +#define PIXMA_EPROTO            -10 +#define PIXMA_EPAPER_JAMMED     -11 +#define PIXMA_ECOVER_OPEN       -12 +#define PIXMA_ENO_PAPER         -13 +#define PIXMA_EOF               -14 +/**@}*/ + +/** \name Capabilities for using with pixma_config_t::cap */ +/**@{*/ +#define PIXMA_CAP_EASY_RGB     (1 << 0) +#define PIXMA_CAP_GRAY         (1 << 1) +#define PIXMA_CAP_ADF          (1 << 2) +#define PIXMA_CAP_48BIT        (1 << 3) +#define PIXMA_CAP_GAMMA_TABLE  (1 << 4) +#define PIXMA_CAP_EVENTS       (1 << 5) +#define PIXMA_CAP_TPU          (1 << 6) +#define PIXMA_CAP_ADFDUP       ((1 << 7) | PIXMA_CAP_ADF) +#define PIXMA_CAP_CIS          (0) +#define PIXMA_CAP_CCD          (1 << 8) +#define PIXMA_CAP_LINEART      (1 << 9) +#define PIXMA_CAP_NEGATIVE     (1 << 10) +#define PIXMA_CAP_TPUIR        ((1 << 11) | PIXMA_CAP_TPU) +#define PIXMA_CAP_ADF_WAIT     (1 << 12) +#define PIXMA_CAP_ADF_JPEG     (1 << 13) +#define PIXMA_CAP_EXPERIMENT   (1 << 31) +/**@}*/ + +/** \name Button events and related information returned by pixma_wait_event() */ +/**@{*/ +#define PIXMA_EV_NONE          0 +#define PIXMA_EV_ACTION_MASK   (0xffffff) +#define PIXMA_EV_BUTTON1       (1 << 24) +#define PIXMA_EV_BUTTON2       (2 << 24) +#define PIXMA_EV_TARGET_MASK   (0xff) +#define PIXMA_EV_ORIGINAL_MASK (0xff00) +#define PIXMA_EV_DPI_MASK      (0xff0000) + +#define GET_EV_TARGET(x) (x & PIXMA_EV_TARGET_MASK) +#define GET_EV_ORIGINAL(x) ( (x & PIXMA_EV_ORIGINAL_MASK) >> 8 ) +#define GET_EV_DPI(x) ( (x & PIXMA_EV_DPI_MASK) >> 16 ) + +/**@}*/ +/** @} end of API group */ + +#define PIXMA_CONFIG_FILE "pixma.conf" +#define MAX_CONF_DEVICES 15 + +struct pixma_t; +struct pixma_scan_ops_t; +struct pixma_scan_param_t; +struct pixma_config_t; +struct pixma_cmdbuf_t; +struct pixma_imagebuf_t; +struct pixma_device_status_t; + +typedef struct pixma_t pixma_t; +typedef struct pixma_scan_ops_t pixma_scan_ops_t; +typedef struct pixma_scan_param_t pixma_scan_param_t; +typedef struct pixma_config_t pixma_config_t; +typedef struct pixma_cmdbuf_t pixma_cmdbuf_t; +typedef struct pixma_imagebuf_t pixma_imagebuf_t; +typedef struct pixma_device_status_t pixma_device_status_t; + + +/** \addtogroup API + *  @{ */ +/** String index constants */ +typedef enum pixma_string_index_t +{ +  PIXMA_STRING_MODEL, +  PIXMA_STRING_ID, +  PIXMA_STRING_LAST +} pixma_string_index_t; + +/** Paper sources */ +typedef enum pixma_paper_source_t +{ +  PIXMA_SOURCE_FLATBED, +  PIXMA_SOURCE_ADF, +  PIXMA_SOURCE_TPU, +  PIXMA_SOURCE_ADFDUP		/* duplex */ +} pixma_paper_source_t; + +/** Scan modes */ +typedef enum pixma_scan_mode_t +{ +  /* standard scan modes */ +  PIXMA_SCAN_MODE_COLOR, +  PIXMA_SCAN_MODE_GRAY, +  /* TPU scan modes for negatives */ +  PIXMA_SCAN_MODE_NEGATIVE_COLOR, +  PIXMA_SCAN_MODE_NEGATIVE_GRAY, +  /* extended scan modes for 48 bit flatbed scanners */ +  PIXMA_SCAN_MODE_COLOR_48, +  PIXMA_SCAN_MODE_GRAY_16, +  /* 1 bit lineart scan mode */ +  PIXMA_SCAN_MODE_LINEART, +  /* TPUIR scan mode */ +  PIXMA_SCAN_MODE_TPUIR +} pixma_scan_mode_t; + +typedef enum pixma_hardware_status_t +{ +  PIXMA_HARDWARE_OK, +  PIXMA_HARDWARE_ERROR +} pixma_hardware_status_t; + +typedef enum pixma_lamp_status_t +{ +  PIXMA_LAMP_OK, +  PIXMA_LAMP_WARMING_UP, +  PIXMA_LAMP_OFF, +  PIXMA_LAMP_ERROR +} pixma_lamp_status_t; + +typedef enum pixma_adf_status_t +{ +  PIXMA_ADF_OK, +  PIXMA_ADF_NO_PAPER, +  PIXMA_ADF_JAMMED, +  PIXMA_ADF_COVER_OPEN, +  PIXMA_ADF_ERROR +} pixma_adf_status_t; + +typedef enum pixma_calibration_status_t +{ +  PIXMA_CALIBRATION_OK, +  PIXMA_CALIBRATION_IN_PROGRESS, +  PIXMA_CALIBRATION_OFF, +  PIXMA_CALIBRATION_ERROR +} pixma_calibration_status_t; + +/** Device status. */ +struct pixma_device_status_t +{ +  pixma_hardware_status_t hardware; +  pixma_lamp_status_t lamp; +  pixma_adf_status_t adf; +  pixma_calibration_status_t cal; +}; + +/** Scan parameters. */ +struct pixma_scan_param_t +{ +    /** Size in bytes of one image line (row). +     *  line_size >= depth / 8 * channels * w <br> +     *  This field will be set by pixma_check_scan_param(). */ +  uint64_t line_size; + +    /** Size in bytes of the whole image. +     *  image_size = line_size * h <br> +     *  This field will be set by pixma_check_scan_param(). */ +  uint64_t image_size; + +    /** Channels per pixel. 1 = grayscale and lineart, 3 = color */ +  unsigned channels; + +    /** Bits per channels. +     *   1 =  1 bit B/W lineart (flatbed) +     *   8 =  8 bit grayscale, +     *       24 bit color (both flatbed) +     *  16 = 16 bit grayscale (TPU, flatbed not implemeted), +     *       48 bit color (TPU, flatbed not implemented) */ +  unsigned depth; + +  /*@{ */ +    /** Resolution. Valid values are 75,150,300,600,1200... */ +  unsigned xdpi, ydpi; +  /*@} */ + +  /*! \name Scan area in pixels +   * (0,0) = top left; positive x extends to the right; positive y to the +   *  bottom; in pixels. +   * xs is the offset in x direction of the selected scan range relative +   * to the range read from the scanner and wx the width in x direction +   * of the scan line read from scanner. */ +  /*@{ */ +  unsigned x, y, w, h,   xs, wx; +  /*@} */ + +  /** Flag indicating whether the offset correction for TPU scans +   *  was already performed (to avoid repeated corrections). +   *  Currently only used in pixma_mp800.c sub-driver */ +  unsigned tpu_offset_added; + +  /* Flag indicating if data from scanner will be in JPEG format */ +  unsigned mode_jpeg; + +  /** Flag indicating whether a software-lineart scan is in progress +   *  0 = other scan +   *  1 = software-lineart scan */ +  unsigned software_lineart; + +  /** Threshold for software-lineart scans */ +  unsigned threshold; + +  /** lineart threshold curve for dynamic rasterization */ +  unsigned threshold_curve; + +  /* look up table used in dynamic rasterization */ +  unsigned char lineart_lut[256]; + +    /** Gamma table. 4096 entries, 12 bit => 8 bit. If \c NULL, default gamma +     *  specified by subdriver will be used. */ +  const uint8_t *gamma_table; + +    /** \see #pixma_paper_source_t */ +  pixma_paper_source_t source; + +  /** \see #pixma_scan_mode_t */ +  pixma_scan_mode_t mode; + +    /** The current page # in the same ADF scan session, 0 in non ADF */ +  unsigned adf_pageid; + +  /** adf-wait */ +  unsigned adf_wait; +  unsigned frontend_cancel; +}; + +/** PIXMA model information */ +struct pixma_config_t +{ +  /* If you change this structure, don't forget to update the device list in +   * subdrivers. */ +  const char *name;	   /**< Model name. */ +  const char *model;   /**< Short model */ +  uint16_t vid;		     /**< USB Vendor ID */ +  uint16_t pid;		     /**< USB Product ID */ +  unsigned iface;	     /**< USB Interface number */ +  const pixma_scan_ops_t *ops;	  /**< Subdriver ops */ +  unsigned min_xdpi;         /**< Minimum horizontal resolution[DPI] */ +  unsigned xdpi;	     /**< Maximum horizontal resolution[DPI] */ +  unsigned ydpi;	     /**< Maximum vertical resolution[DPI] */ +  unsigned adftpu_min_dpi;    /**< Maximum horizontal resolution[DPI] for adf/tpu +                                 *  only needed if ADF/TPU has another min. dpi value than 75 dpi */ +  unsigned adftpu_max_dpi;    /**< Maximum vertical resolution[DPI] for adf/tpu +                                 *  only needed if ADF/TPU has another max. dpi value than xdpi */ +  unsigned tpuir_min_dpi;       /**< Minimum resolution[DPI] for tpu-ir +                                   *  only needed if TPU-IR has another min. dpi value than 75 dpi */ +  unsigned tpuir_max_dpi;       /**< Maximum resolution[DPI] for tpu-ir +                                   *  only needed if TPU-IR has another max. dpi value than xdpi */ +  unsigned width;	     /**< Maximum width of scannable area in pixels at 75DPI */ +  unsigned height;	   /**< Maximum height of scannable area in pixels at 75DPI */ +  unsigned cap;		     /**< Capability bitfield \see PIXMA_CAP_* */ +}; + + +/* Defined in pixma_common.c */ + +/** Initialize the driver. It must be called before any other functions + *  except pixma_set_debug_level(). */ +int pixma_init (void); + +/** Free resources allocated by the driver. */ +void pixma_cleanup (void); + +/** Set the debug level. + *  \param[in] level the debug level + *    - 0 No debug output at all + *    - 1 Only errors and warning + *    - 2 General information + *    - 3 Debugging messages + *    - 10 USB traffic dump */ +void pixma_set_debug_level (int level); + +/** Find scanners. The device number used in pixma_open(), + *  pixma_get_device_model(), pixma_get_device_id() and + *  pixma_get_device_config() must be less than the value returned by the last + *  call of this function. + * + *  \return The number of scanners found currently. The return value is + *  guaranteed to be valid until the next call to pixma_find_scanners(). */ +int pixma_find_scanners (const char **conf_devices, SANE_Bool local_only); + +/** Return the model name of the device \a devnr. */ +const char *pixma_get_device_model (unsigned devnr); + +/** Return the unique ID of the device \a devnr. */ +const char *pixma_get_device_id (unsigned devnr); + +/** Return the device configuration of the device \a devnr. */ +const struct pixma_config_t *pixma_get_device_config (unsigned devnr); + +/** Open a connection to the scanner \a devnr. + *  \param[in] devnr The scanner number + *  \param[out] handle The device handle + *  \see pixma_find_scanners() */ +int pixma_open (unsigned devnr, pixma_t ** handle); + +/** Close the connection to the scanner. The scanning process is aborted + *  if necessary before the function returns. */ +void pixma_close (pixma_t * s); + +/** Initiate an image acquisition process. You must keep \a sp valid until the + *  image acquisition process has finished. */ +int pixma_scan (pixma_t *, pixma_scan_param_t * sp); + +/** Read a block of image data. It blocks until there is at least one byte + *  available or an error occurs. + * + *  \param[out] buf Pointer to the buffer + *  \param[in] len Size of the buffer + * + *  \retval count Number of bytes written to the buffer or error. Possible + *  return value: + *     - count = 0 for end of image + *     - count = \a len + *     - 0 < count < \a len if and only if it is the last block. + *     - count < 0 for error  */ +int pixma_read_image (pixma_t *, void *buf, unsigned len); + +#if 0 +/** Read a block of image data and write to \a fd. + *  \param[in] fd output file descriptor + *  \see pixma_read_image() */ +int pixma_read_image_write (pixma_t *, int fd); +#endif + +/** Cancel the scanning process. No effect if no scanning process is in + *  progress. It can be called asynchronously e.g. within a signal + *  handle. pixma_cancel() doesn't abort the operation immediately.  It + *  guarantees that the current call or, at the latest, the next call to + *  pixma_read_image() will return zero or an error (probably PIXMA_ECANCELED). */ +void pixma_cancel (pixma_t *); + +/** Check the scan parameters. This function can change your parameters to + *  match the device capability, e.g. adjust width and height to the available + *  area. + *  \return PIXMA_EINVAL for invalid parameters. */ +int pixma_check_scan_param (pixma_t *, pixma_scan_param_t *); + +/** Wait until a scanner button is pressed or it times out. It should not be + *  called during image acquisition is in progress. + *  \param[in] timeout in milliseconds, less than 0 means forever + *  \return + *   - \c PIXMA_EV_NONE if it timed out. + *   - non-zero value indicates which button was pressed. + *  \see PIXMA_EV_* + */ +uint32_t pixma_wait_event (pixma_t *, int timeout); + +/** Activate connection to scanner */ +int pixma_activate_connection (pixma_t *); + +/** De-activate connection to scanner */ + +int pixma_deactivate_connection (pixma_t *); + + +/** Enable or disable background tasks. Currently, the only one task + *  is submitting interrupt URB in background. + *  \param[in] enabled if not zero, enable background task. + *  \see pixma_set_interrupt_mode() */ +int pixma_enable_background (pixma_t *, int enabled); + +/** Read the current device status. + *  \param[out] status the current device status + *  \return 0 if succeeded. Otherwise, failed. + */ +int pixma_get_device_status (pixma_t *, pixma_device_status_t * status); + +const char *pixma_get_string (pixma_t *, pixma_string_index_t); +const pixma_config_t *pixma_get_config (pixma_t *); +void pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n); +const char *pixma_strerror (int error); + +/** @} end of API group */ + +#endif diff --git a/backend/pixma/pixma_bjnp.c b/backend/pixma/pixma_bjnp.c new file mode 100644 index 0000000..34ba918 --- /dev/null +++ b/backend/pixma/pixma_bjnp.c @@ -0,0 +1,2645 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2008  2012 by Louis Lagendijk + +   This file is part of the SANE package. + +   SANE 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. + +   SANE 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 sane; see the file COPYING.  If not, write to the Free +   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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. +*/ + +#undef BACKEND_NAME +#define BACKEND_NAME bjnp + +#include  "../include/sane/config.h" +#include  "../include/sane/sane.h" + +/* + * Standard types etc + */ +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <unistd.h> +#include <stdio.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* + * networking stuff + */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <net/if.h> +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#include <errno.h> +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "pixma_bjnp_private.h" +#include "pixma_bjnp.h" +/* #include "pixma_rename.h" */ +#include "pixma.h" +#include "pixma_common.h" + +#ifndef SSIZE_MAX +# define SSIZE_MAX      LONG_MAX +#endif + +/* static data */ +static bjnp_device_t device[BJNP_NO_DEVICES]; +static int bjnp_no_devices = 0; + +/* + * Private functions + */ + +static const struct pixma_config_t *lookup_scanner(const char *makemodel, +                                                   const struct pixma_config_t *const pixma_devices[]) +{ +  int i; +  const struct pixma_config_t *cfg; +  char *match; + +  for (i = 0; pixma_devices[i]; i++) +    { +      /* loop through the device classes (mp150, mp730 etc) */ +      for (cfg = pixma_devices[i]; cfg->name; cfg++) +        { +          /* loop through devices in class */ +          PDBG( bjnp_dbg( LOG_DEBUG3, "lookup_scanner: Checking for %s in %s\n", makemodel, cfg->model)); +          if ((match = strcasestr (makemodel, cfg->model)) != NULL) +            { +              /* possible match found, make sure it is not a partial match */ +              /* MP600 and MP600R are different models! */ +              /* some models contain ranges, so check for a '-' too */ + +              if ((match[strlen(cfg->model)] == ' ') || +                  (match[strlen(cfg->model)] == '\0') || +                  (match[strlen(cfg->model)] == '-')) +                { +                  PDBG( bjnp_dbg (LOG_DEBUG, "lookup_scanner: Scanner model found: Name %s(%s) matches %s\n", cfg->model, cfg->name, makemodel)); +                  return cfg; +                } +            } +       } +    } +  PDBG( bjnp_dbg (LOG_DEBUG, "lookup_scanner: Scanner model %s not found, giving up!\n", makemodel)); +  return NULL; +} + +static void +u8tohex (char *string, const uint8_t *value, int len ) +{ +  int i; +  int x; +  const char hdigit[16] = +    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', +    'e', 'f' +  }; +  for (i = 0; i < len; i++) +    { +      x = value[i]; +      string[ 2 * i ] = hdigit[(x >> 4) & 0xf]; +      string[ 2 * i + 1] = hdigit[x & 0xf]; +    } +  string[2 * len ] = '\0'; +} + +static void +u32tohex (uint32_t x, char *str) +{ +  uint8_t uint8[4]; +  uint8[0] = (uint8_t)(x >> 24); +  uint8[1] = (uint8_t)(x >> 16); +  uint8[2] = (uint8_t)(x >> 8); +  uint8[3] = (uint8_t)x ; +  u8tohex(str, uint8, 4); +} + +static void +bjnp_hexdump (int level, const void *d_, unsigned len) +{ +  const uint8_t *d = (const uint8_t *) (d_); +  unsigned ofs, c, plen; +  char line[100];               /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */ + +  if (level > DBG_LEVEL) +    return; +  if (level == DBG_LEVEL) +    /* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */ +    plen = (len > 64) ? 32: len; +  else +    plen = len; +  ofs = 0; +  while (ofs < plen) +    { +      char *p; +      line[0] = ' '; +      u32tohex (ofs, line + 1); +      line[9] = ':'; +      p = line + 10; +      for (c = 0; c != 16 && (ofs + c) < plen; c++) +        { +          u8tohex (p, d + ofs + c, 1); +          p[2] = ' '; +          p += 3; +          if (c == 7) +            { +              p[0] = ' '; +              p++; +            } +        } +      p[0] = '\0'; +      bjnp_dbg (level, "%s\n", line); +      ofs += c; +    } +  if (len > plen) +    bjnp_dbg(level, "......\n"); +} + +static int sa_is_equal( const bjnp_sockaddr_t * sa1, const bjnp_sockaddr_t * sa2) +{ +  if ((sa1 == NULL) || (sa2 == NULL) ) +    return 0; + +  if (sa1->addr.sa_family == sa2-> addr.sa_family) +    { +      if( sa1 -> addr.sa_family == AF_INET) +        { +          if ( (sa1->ipv4.sin_port == sa2->ipv4.sin_port) && +               (sa1->ipv4.sin_addr.s_addr == sa2->ipv4.sin_addr.s_addr)) +            { +            return 1; +            } +        } +#ifdef ENABLE_IPV6 +      else if (sa1 -> addr.sa_family == AF_INET6 ) +        { +          if ( (sa1-> ipv6.sin6_port == sa2->ipv6.sin6_port) && +              (memcmp(&(sa1->ipv6.sin6_addr), &(sa2->ipv6.sin6_addr), sizeof(struct in6_addr)) == 0)) +            { +              return 1; +            } +        } +#endif +    } +    return 0; +} + +static int +sa_size( const bjnp_sockaddr_t *sa) +{ +  switch (sa -> addr.sa_family) +    { +      case AF_INET: +        return (sizeof(struct sockaddr_in) ); +#ifdef ENABLE_IPV6 +      case AF_INET6: +        return (sizeof(struct sockaddr_in6) ); +#endif +      default: +        /* should not occur */ +        return sizeof( bjnp_sockaddr_t ); +    } +} + +static int +get_protocol_family( const bjnp_sockaddr_t *sa) +{ +  switch (sa -> addr.sa_family) +    { +      case AF_INET: +        return PF_INET; +        break; +#ifdef ENABLE_IPV6 +      case AF_INET6: +        return PF_INET6; +        break; +#endif +      default: +        /* should not occur */ +        return -1; +    } +} + +static void +get_address_info ( const bjnp_sockaddr_t *addr, char * addr_string, int *port) +{ +  char tmp_addr[BJNP_HOST_MAX]; +  if ( addr->addr.sa_family == AF_INET) +    { +      inet_ntop( AF_INET, &(addr -> ipv4.sin_addr.s_addr), addr_string, BJNP_HOST_MAX); +      *port = ntohs (addr->ipv4.sin_port); +    } +#ifdef ENABLE_IPV6 +  else if (addr->addr.sa_family == AF_INET6) +    { +      inet_ntop( AF_INET6, addr -> ipv6.sin6_addr.s6_addr, tmp_addr, sizeof(tmp_addr) ); + +      if (IN6_IS_ADDR_LINKLOCAL( &(addr -> ipv6.sin6_addr) ) ) +          sprintf(addr_string, "[%s%%%d]", tmp_addr, addr -> ipv6.sin6_scope_id); + +      *port = ntohs (addr->ipv6.sin6_port); +    } +#endif +  else +    { +      /* unknown address family, should not occur */ +      strcpy(addr_string, "Unknown address family"); +      *port = 0; +    } +} + +static int +parse_IEEE1284_to_model (char *scanner_id, char *model) +{ +/* + * parses the  IEEE1284  ID of the scanner to retrieve make and model + * of the scanner + * Returns: 0 = not found + *          1 = found, model is set + */ + +  char s[BJNP_IEEE1284_MAX]; +  char *tok; +  char * model_str; + +  strncpy (s, scanner_id, BJNP_IEEE1284_MAX); +  s[BJNP_IEEE1284_MAX - 1] = '\0'; +  model[0] = '\0'; + +  tok = strtok (s, ";"); +  while (tok != NULL) +    { +      /* MDL contains make and model */ + +      if (strncmp (tok, "MDL:", strlen("MDL:")) == 0) +	{ +	  model_str = tok + strlen("MDL:"); +	  strncpy (model, model_str, BJNP_MODEL_MAX); +	  model[BJNP_MODEL_MAX -1] = '\0'; +	  return 1; +	} +      tok = strtok (NULL, ";"); +    } +  return 0; +} + +static int +charTo2byte (char *d, const char *s, int len) +{ +  /* +   * copy ASCII string to UTF-16 unicode string +   * len is length of destination buffer +   * Returns: number of characters copied +   */ + +  int done = 0; +  int copied = 0; +  int i; + +  len = len / 2; +  for (i = 0; i < len; i++) +    { +      d[2 * i] = '\0'; +      if (s[i] == '\0') +	{ +	  done = 1; +	} +      if (done == 0) +	{ +	  d[2 * i + 1] = s[i]; +	  copied++; +	} +      else +	d[2 * i + 1] = '\0'; +    } +  return copied; +} + +static bjnp_protocol_defs_t *get_protocol_by_method( char *method) +{ +  int i = 0; +  while ( bjnp_protocol_defs[i].method_string != NULL) +    { +      if (strcmp(method, bjnp_protocol_defs[i].method_string) == 0) +        { +          return &bjnp_protocol_defs[i]; +        } +      i++; +    } +  return NULL; +} + +static bjnp_protocol_defs_t *get_protocol_by_proto_string( char *proto_string) +{ +  int i = 0; +  while ( bjnp_protocol_defs[i].proto_string != NULL) +    { +      if (strncmp(proto_string, bjnp_protocol_defs[i].proto_string, 4) == 0) +        { +          return &bjnp_protocol_defs[i]; +        } +      i++; +    } +  return NULL; +} + +static char * +getusername (void) +{ +  static char noname[] = "sane_pixma"; +  struct passwd *pwdent; + +#ifdef HAVE_PWD_H +  if (((pwdent = getpwuid (geteuid ())) != NULL) && (pwdent->pw_name != NULL)) +    return pwdent->pw_name; +#endif +  return noname; +} + + +static char * +determine_scanner_serial (const char *hostname, const char * mac_address, char *serial) +{ +  char *dot; +  char copy[BJNP_HOST_MAX]; + +  /* determine a "serial number" for the scanner */ +  /* if available we use the hostname or ipv4 address of the printer */ +  /* if we only have a literal ipv6 address, we use the mac-address */ + +  strcpy(copy, hostname); +  if (strlen (copy) >= SERIAL_MAX) +    { +      /* make the string fit into the serial */ +      /* if this is a FQDN, not an ip-address, remove domain part of the name */ +      if ((dot = strchr (copy, '.')) != NULL) +        { +          *dot = '\0'; +        } +    } +  /* check if name is still to long. If so use the mac-address */ +  if (strlen(copy) >= SERIAL_MAX) +    { +      strcpy(copy, mac_address); +    } +  strcpy( serial, copy ); +  return serial; +} + +static int +bjnp_open_tcp (int devno) +{ +  int sock; +  int val; +  bjnp_sockaddr_t *addr = device[devno].addr; +  char host[BJNP_HOST_MAX]; +  int port; +  int connect_timeout = BJNP_TIMEOUT_TCP_CONNECT; + +  get_address_info( addr, host, &port); +  PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_open_tcp: Setting up a TCP socket, dest: %s  port %d\n", +		   host, port ) ); + +  if ((sock = socket (get_protocol_family( addr ) , SOCK_STREAM, 0)) < 0) +    { +      PDBG (bjnp_dbg (LOG_CRIT, "bjnp_open_tcp: ERROR - Can not create socket: %s\n", +		       strerror (errno))); +      return -1; +    } + +  val = 1; +  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val)); + +#if 0 +  val = 1; +  setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, &val, sizeof (val)); + +  val = 1; +#endif + +  /* +   * Using TCP_NODELAY improves responsiveness, especially on systems +   * with a slow loopback interface... +   */ + +  val = 1; +  setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof (val)); + +/* + * Close this socket when starting another process... + */ + +  fcntl (sock, F_SETFD, FD_CLOEXEC); + +  while (connect_timeout > 0) +    { +      if (connect +          (sock, &(addr->addr), sa_size(device[devno].addr)) == 0) +	    { +              device[devno].tcp_socket = sock; +              return 0; +	    } +      PDBG (bjnp_dbg( LOG_INFO, "bjnp_open_tcp: INFO - Can not yet connect over TCP to scanner: %s, retrying\n", +                      strerror(errno))); +      usleep(BJNP_TCP_CONNECT_INTERVAL * BJNP_USLEEP_MS); +      connect_timeout = connect_timeout - BJNP_TCP_CONNECT_INTERVAL; +    } +  PDBG (bjnp_dbg +        (LOG_CRIT, "bjnp_open_tcp: ERROR - Can not connect to scanner, giving up!")); +  return -1; +} + +static int +split_uri (const char *devname, char *method, char *host, char *port, +	   char *args) +{ +  char copy[1024]; +  char *start; +  char next; +  int i; + +  strncpy (copy, devname, 1024); +  copy[1023] = '\0'; +  start = copy; + +/* + * retrieve method + */ +  i = 0; +  while ((start[i] != '\0') && (start[i] != ':')) +    { +      i++; +    } + +  if (((strncmp (start + i, "://", 3) != 0)) || (i > BJNP_METHOD_MAX -1 )) +    { +      PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find method in %s (offset %d)\n", +		       devname, i)); +      return -1; +    } + +  start[i] = '\0'; +  strcpy (method, start); +  start = start + i + 3; + +/* + * retrieve host + */ + +  if (start[0] == '[') +    { +      /* literal IPv6 address */ + +      char *end_of_address = strchr(start, ']'); + +      if ( ( end_of_address == NULL) || +           ( (end_of_address[1] != ':') && (end_of_address[1] != '/' ) &&  (end_of_address[1] != '\0' )) || +           ( (end_of_address - start) >= BJNP_HOST_MAX ) ) +        { +          PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find hostname or address in %s\n", devname)); +          return -1; +        } +      next = end_of_address[1]; +      *end_of_address = '\0'; +      strcpy(host, start + 1); +      start = end_of_address + 2; +    } +  else +    { +      i = 0; +      while ((start[i] != '\0') && (start[i] != '/') && (start[i] != ':')) +        { +          i++; +        } +      next = start[i]; +      start[i] = '\0'; +      if ((i == 0) || (i >= BJNP_HOST_MAX ) ) +        { +          PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find hostname or address in %s\n", devname)); +          return -1; +        } +      strcpy (host, start); +      start = start + i +1; +    } + + +/* + * retrieve port number + */ + +  if (next != ':') +    strcpy(port, ""); +  else +    { +      char *end_of_port = strchr(start, '/'); +      if (end_of_port == NULL) +        { +          next = '\0'; +        } +      else +        { +          next = *end_of_port; +          *end_of_port = '\0'; +        } +      if ((strlen(start) == 0) || (strlen(start) >= BJNP_PORT_MAX ) ) +        { +          PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find port in %s (have \"%s\")\n", devname, start)); +          return -1; +        } +      strcpy(port, start); +      start = end_of_port + 1; +    } + +/* + * Retrieve arguments + */ + +  if (next == '/') +    { +    i = strlen(start); +    if ( i >= BJNP_ARGS_MAX) +      { +        PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Argument string too long in %s\n", devname)); +      } +    strcpy (args, start); +    } +  else +    strcpy (args, ""); +  return 0; +} + + + +static void +set_cmd_from_string (char* protocol_string, struct BJNP_command *cmd, char cmd_code, int payload_len) +{ +  /* +   * Set command buffer with command code, session_id and length of payload +   * Returns: sequence number of command +   */ + +  memcpy (cmd->BJNP_id, protocol_string, sizeof (cmd->BJNP_id)); +  cmd->dev_type = BJNP_CMD_SCAN; +  cmd->cmd_code = cmd_code; +  cmd->unknown1 = htons (0); + +  /* device not yet opened, use 0 for serial and session) */ +  cmd->seq_no = htons (0); +  cmd->session_id = htons (0); +  cmd->payload_len = htonl (payload_len); +} + +static void +set_cmd_for_dev (int devno, struct BJNP_command *cmd, char cmd_code, int payload_len) +{ +  /* +   * Set command buffer with command code, session_id and length of payload +   * Returns: sequence number of command +   */ + +  memcpy(cmd->BJNP_id, device[devno].protocol_string, sizeof (cmd->BJNP_id)); +  cmd->dev_type = BJNP_CMD_SCAN; +  cmd->cmd_code = cmd_code; +  cmd->unknown1 = htons (0); +  cmd->seq_no = htons (++(device[devno].serial)); +  cmd->session_id = (cmd_code == CMD_UDP_POLL ) ? 0 : htons (device[devno].session_id); +  device[devno].last_cmd = cmd_code; +  cmd->payload_len = htonl (payload_len); +} + +static int +bjnp_setup_udp_socket ( const int dev_no ) +{ +  /* +   * Setup a udp socket for the given device +   * Returns the socket or -1 in case of error +   */ + +  int sockfd; +  char addr_string[256]; +  int port; +  bjnp_sockaddr_t * addr = device[dev_no].addr; + +  get_address_info( addr, addr_string, &port); + +  PDBG (bjnp_dbg (LOG_DEBUG, "setup_udp_socket: Setting up a UDP socket, dest: %s  port %d\n", +		   addr_string, port ) ); + +  if ((sockfd = socket (get_protocol_family( addr ), SOCK_DGRAM, IPPROTO_UDP)) == -1) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, "setup_udp_socket: ERROR - can not open socket - %s\n", +	     strerror (errno))); +      return -1; +    } + +  if (connect +      (sockfd, &(device[dev_no].addr->addr), sa_size(device[dev_no].addr) )!= 0) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, "setup_udp_socket: ERROR - connect failed- %s\n", +	     strerror (errno))); +      close(sockfd); +      return -1; +    } +  return sockfd; +} + +static int +udp_command (const int dev_no, char *command, int cmd_len, char *response, +	     int resp_len) +{ +  /* +   * send udp command to given device and recieve the response` +   * returns: the legth of the response or -1 +   */ +  int sockfd; +  struct timeval timeout; +  int result; +  int try, attempt; +  int numbytes; +  fd_set fdset; +  struct BJNP_command *resp = (struct BJNP_command *) response; +  struct BJNP_command *cmd = (struct BJNP_command *) command; + +  if ( (sockfd = bjnp_setup_udp_socket(dev_no) ) == -1 ) +    { +      PDBG (bjnp_dbg( LOG_CRIT, "udp_command: ERROR - Can not setup socket\n") ); +      return -1; +    } + +  for (try = 0; try < BJNP_UDP_RETRY_MAX; try++) +    { +      if ((numbytes = send (sockfd, command, cmd_len, 0)) != cmd_len) +	{ +	  PDBG (bjnp_dbg +		(LOG_NOTICE, "udp_command: ERROR - Sent %d bytes, expected %d\n", +		 numbytes, cmd_len)); +	  continue; +	} + +      attempt = 0; + +      /* wait for data to be received, ignore signals being received */ +      /* skip late udp responses (they have an incorrect sequence number */ +      do +	{ +	  FD_ZERO (&fdset); +	  FD_SET (sockfd, &fdset); + +	  timeout.tv_sec = device[dev_no].bjnp_ip_timeout /1000; +	  timeout.tv_usec = device[dev_no].bjnp_ip_timeout %1000; +	} +      while (((result = +	       select (sockfd + 1, &fdset, NULL, NULL, &timeout)) <= 0) +	     && (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS) +             && resp-> seq_no != cmd->seq_no); + +      if (result <= 0) +	{ +	  PDBG (bjnp_dbg +		(LOG_NOTICE, "udp_command: ERROR - select failed: %s\n", +		 result == 0 ? "timed out" : strerror (errno))); +	  continue; +	} + +      if ((numbytes = recv (sockfd, response, resp_len, 0)) == -1) +	{ +	  PDBG (bjnp_dbg +		(LOG_NOTICE, "udp_command: ERROR - recv failed: %s", +		 strerror (errno))); +	  continue; +	} +      close(sockfd); +      return numbytes; +    } + +  /* no response even after retry */ + +  close(sockfd); +  PDBG (bjnp_dbg +        (LOG_CRIT, "udp_command: ERROR - no data received (timeout = %d)\n", device[dev_no].bjnp_ip_timeout ) ); +  return -1; +} + +static int +get_scanner_id (const int dev_no, char *model) +{ +  /* +   * get scanner identity +   * Sets model (make and model) +   * Return 0 on success, -1 in case of errors +   */ + +  struct BJNP_command cmd; +  struct IDENTITY *id; +  char scanner_id[BJNP_IEEE1284_MAX]; +  int resp_len; +  char resp_buf[BJNP_RESP_MAX]; +  int id_len; + +  /* set defaults */ + +  strcpy (model, "Unidentified scanner"); + +  set_cmd_for_dev (dev_no, &cmd, CMD_UDP_GET_ID, 0); + +  PDBG (bjnp_dbg (LOG_DEBUG2, "get_scanner_id: Get scanner identity\n")); +  PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &cmd, +		       sizeof (struct BJNP_command))); + +  if ( ( resp_len =  udp_command (dev_no, (char *) &cmd, sizeof (struct BJNP_command), +		 resp_buf, BJNP_RESP_MAX) ) < (int)sizeof(struct BJNP_command) ) +    { +      PDBG (bjnp_dbg (LOG_DEBUG, "get_scanner_id: ERROR - Failed to retrieve scanner identity:\n")); +      return -1; +    } +  PDBG (bjnp_dbg (LOG_DEBUG2, "get_scanner_id: scanner identity:\n")); +  PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len)); + +  id = (struct IDENTITY *) resp_buf; + +  if (device[dev_no].protocol == PROTOCOL_BJNP) +    { +      id_len = MIN(ntohl( id-> cmd.payload_len ) - sizeof(id-> payload.bjnp.id_len), BJNP_IEEE1284_MAX); +      strncpy(scanner_id, id->payload.bjnp.id, id_len); +      scanner_id[id_len] = '\0'; +    } +  else +    { +      id_len = MIN(ntohl( id-> cmd.payload_len ), BJNP_IEEE1284_MAX); +      strncpy(scanner_id, id->payload.mfnp.id, id_len); +      scanner_id[id_len] = '\0'; +    } +  PDBG (bjnp_dbg (LOG_INFO, "get_scanner_id: Scanner identity string = %s - length = %d\n", scanner_id, id_len)); + +  /* get make&model from IEEE1284 id  */ + +  if (model != NULL) +  { +    parse_IEEE1284_to_model (scanner_id, model); +    PDBG (bjnp_dbg (LOG_INFO, "get_scanner_id: Scanner model = %s\n", model)); +  } +  return 0; +} + +static int +get_scanner_name(const bjnp_sockaddr_t *scanner_sa, char *host) +{ +  /* +   * Parse identify command responses to ip-address +   * and hostname. Return qulity of the address +   */ + +  struct addrinfo *results; +  struct addrinfo *result; +  char ip_address[BJNP_HOST_MAX]; +  int port; +  int error; +  int match = 0; +  int level; +  char service[64]; + +#ifdef ENABLE_IPV6 +  if ( ( scanner_sa -> addr.sa_family == AF_INET6 ) && +       ( IN6_IS_ADDR_LINKLOCAL( &(scanner_sa -> ipv6.sin6_addr ) ) ) ) +    level = BJNP_ADDRESS_IS_LINK_LOCAL; +  else +#endif +    level = BJNP_ADDRESS_IS_GLOBAL; + +  get_address_info( scanner_sa, ip_address, &port ); + +  /* do reverse name lookup, if hostname can not be found return ip-address */ + +  if( (error = getnameinfo( &(scanner_sa -> addr) , sa_size( scanner_sa), +                  host, BJNP_HOST_MAX , NULL, 0, NI_NAMEREQD) ) != 0 ) +    { +      PDBG (bjnp_dbg(LOG_INFO, "get_scanner_name: Name for %s not found : %s\n", +                      ip_address, gai_strerror(error) ) ); +      strcpy(host, ip_address); +      return level; +    } +  else +    { +      sprintf(service, "%d", port); +      /* some buggy routers return rubbish if reverse lookup fails, so +       * we do a forward lookup on the received name to see if the result matches */ + +      if (getaddrinfo(host , service, NULL, &results) == 0) +        { +          result = results; + +          while (result != NULL) +            { +               if(sa_is_equal( scanner_sa, (bjnp_sockaddr_t *)result-> ai_addr)) +                 { +                     /* found match, good */ +                     PDBG (bjnp_dbg (LOG_INFO, +                              "get_scanner_name: Forward lookup for %s succeeded, using as hostname\n", host)); +                    match = 1; +                    level = BJNP_ADDRESS_HAS_FQDN; +                    break; +                 } +              result = result-> ai_next; +            } +          freeaddrinfo(results); + +          if (match != 1) +            { +              PDBG (bjnp_dbg (LOG_INFO, +                 "get_scanner_name: Forward lookup for %s succeeded, IP-address does not match, using IP-address %s instead\n", +                 host, ip_address)); +              strcpy (host, ip_address); +            } +         } +       else +         { +           /* forward lookup failed, use ip-address */ +           PDBG ( bjnp_dbg (LOG_INFO, "get_scanner_name: Forward lookup of %s failed, using IP-address", ip_address)); +           strcpy (host, ip_address); +         } +    } +  return level; +} + +static int +get_port_from_sa(const bjnp_sockaddr_t scanner_sa) +{ +#ifdef ENABLE_IPV6 +  if ( scanner_sa.addr.sa_family == AF_INET6 ) +    { +      return ntohs(scanner_sa.ipv6.sin6_port); +    } +  else +#endif +  if ( scanner_sa.addr.sa_family == AF_INET ) +    { +      return ntohs(scanner_sa.ipv4.sin_port); +    } +  return -1; +} + +static int create_broadcast_socket( const bjnp_sockaddr_t * local_addr ) +{ +  int sockfd = -1; +  int broadcast = 1; +  int ipv6_v6only = 1; + + + if ((sockfd = socket (local_addr-> addr.sa_family, SOCK_DGRAM, 0)) == -1) +    { +      PDBG (bjnp_dbg +            (LOG_CRIT, "create_broadcast_socket: ERROR - can not open socket - %s", +             strerror (errno))); +      return -1; +    } + +  /* Set broadcast flag on socket */ + +  if (setsockopt +      (sockfd, SOL_SOCKET, SO_BROADCAST, (const char *) &broadcast, +       sizeof (broadcast)) != 0) +    { +      PDBG (bjnp_dbg +            (LOG_CRIT, +             "create_broadcast_socket: ERROR - setting socket option SO_BROADCAST failed - %s", +             strerror (errno))); +      close (sockfd); +      return -1; +    }; + +  /* For an IPv6 socket, bind to v6 only so a V6 socket can co-exist with a v4 socket */ +  if ( (local_addr -> addr.sa_family == AF_INET6) && ( setsockopt +      (sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &ipv6_v6only, +       sizeof (ipv6_v6only)) != 0) ) +    { +      PDBG (bjnp_dbg +            (LOG_CRIT, +             "create_broadcast_socket: ERROR - setting socket option IPV6_V6ONLY failed - %s", +             strerror (errno))); +      close (sockfd); +      return -1; +    }; + +  if (bind +      (sockfd, &(local_addr->addr), +       (socklen_t) sa_size( local_addr)) != 0) +    { +      PDBG (bjnp_dbg +            (LOG_CRIT, +             "create_broadcast_socket: ERROR - bind socket to local address failed - %s\n", +             strerror (errno))); +      close (sockfd); +      return -1; +    } +  return sockfd; +} + +static int +prepare_socket(const char *if_name, const bjnp_sockaddr_t *local_sa, +               const bjnp_sockaddr_t *broadcast_sa, bjnp_sockaddr_t * dest_sa) +{ +  /* +   * Prepare a socket for broadcast or multicast +   * Input: +   * if_name: the name of the interface +   * local_sa: local address to use +   * broadcast_sa: broadcast address to use, if NULL we use all hosts +   * dest_sa: (write) where to return destination address of broadcast +   * retuns: open socket or -1 +   */ + +  int socket = -1; +  bjnp_sockaddr_t local_sa_copy; + +  if ( local_sa == NULL ) +    { +      PDBG (bjnp_dbg (LOG_DEBUG, +                       "prepare_socket: %s is not a valid IPv4 interface, skipping...\n", +                       if_name)); +      return -1; +    } + +  memset( &local_sa_copy, 0, sizeof(local_sa_copy) ); +  memcpy( &local_sa_copy, local_sa, sa_size(local_sa) ); + +  switch( local_sa_copy.addr.sa_family ) +    { +      case AF_INET: +        { +          local_sa_copy.ipv4.sin_port = htons(BJNP_PORT_SCAN); + +          if (local_sa_copy.ipv4.sin_addr.s_addr == htonl (INADDR_LOOPBACK) ) +            { +              /* not a valid interface */ + +              PDBG (bjnp_dbg (LOG_DEBUG, +                               "prepare_socket: %s is not a valid IPv4 interface, skipping...\n", +	                       if_name)); +              return -1; +            } + + +          /* send broadcasts to the broadcast address of the interface */ + +          memcpy(dest_sa, broadcast_sa, sa_size(dest_sa) ); + +	  /* we fill port when we send the broadcast */ +          dest_sa -> ipv4.sin_port = htons(0); + +          if ( (socket = create_broadcast_socket( &local_sa_copy) ) != -1) +            { +               PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: %s is IPv4 capable, sending broadcast, socket = %d\n", +                      if_name, socket)); +            } +          else +            { +              PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: ERROR - %s is IPv4 capable, but failed to create a socket.\n", +                    if_name)); +              return -1; +            } +        } +        break; +#ifdef ENABLE_IPV6 +      case AF_INET6: +        { +          local_sa_copy.ipv6.sin6_port = htons(BJNP_PORT_SCAN); + +          if (IN6_IS_ADDR_LOOPBACK( &(local_sa_copy.ipv6.sin6_addr) ) ) +            { +              /* not a valid interface */ + +              PDBG (bjnp_dbg (LOG_DEBUG, +                               "prepare_socket: %s is not a valid IPv6 interface, skipping...\n", +	                       if_name)); +              return -1; +            } +          else +            { +              dest_sa -> ipv6.sin6_family = AF_INET6; + +              /* We fill port when we send the broadcast */ +              dest_sa -> ipv6.sin6_port = htons(0); + +              inet_pton(AF_INET6, "ff02::1", dest_sa -> ipv6.sin6_addr.s6_addr); +              if ( (socket = create_broadcast_socket( &local_sa_copy ) ) != -1) +                { +                   PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: %s is IPv6 capable, sending broadcast, socket = %d\n", +                          if_name, socket)); +                } +              else +                { +                  PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: ERROR - %s is IPv6 capable, but failed to create a socket.\n", +                        if_name)); +                  return -1; +                } +            } +          } +          break; +#endif + +      default: +        socket = -1; +    } +  return socket; +} + +static int +bjnp_send_broadcast (int sockfd, const bjnp_sockaddr_t * broadcast_addr, int port, +                     struct BJNP_command cmd, int size) +{ +  int num_bytes; +  bjnp_sockaddr_t dest_addr; + +  /* set address to send packet to broadcast address of interface, */ +  /* with port set to the destination port */ + +  memcpy(&dest_addr,  broadcast_addr, sizeof(dest_addr)); +  if( dest_addr.addr.sa_family == AF_INET) +    { +      dest_addr.ipv4.sin_port = htons(port); +    } +#ifdef ENABLE_IPV6 +  if( dest_addr.addr.sa_family == AF_INET6) +    { +      dest_addr.ipv6.sin6_port = htons(port); +    } +#endif + +  if ((num_bytes = sendto (sockfd, &cmd, size, 0, +			  &(dest_addr.addr), +			  sa_size( broadcast_addr)) ) != size) +    { +      PDBG (bjnp_dbg (LOG_INFO, +		       "bjnp_send_broadcast: Socket: %d: ERROR - sent only %x = %d bytes of packet, error = %s\n", +		       sockfd, num_bytes, num_bytes, strerror (errno))); +      /* not allowed, skip this interface */ + +      return -1; +    } +  return sockfd; +} + +static void +bjnp_finish_job (int devno) +{ +/* + * Signal end of scanjob to scanner + */ + +  char resp_buf[BJNP_RESP_MAX]; +  int resp_len; +  struct BJNP_command cmd; + +  set_cmd_for_dev (devno, &cmd, CMD_UDP_CLOSE, 0); + +  PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_finish_job: Finish scanjob\n")); +  PDBG (bjnp_hexdump +	(LOG_DEBUG2, (char *) &cmd, sizeof (struct BJNP_command))); +  resp_len = +    udp_command (devno, (char *) &cmd, sizeof (struct BJNP_command), resp_buf, +		 BJNP_RESP_MAX); + +  if (resp_len != sizeof (struct BJNP_command)) +    { +      PDBG (bjnp_dbg +	    (LOG_INFO, +	     "bjnp_finish_job: ERROR - Received %d characters on close scanjob command, expected %d\n", +	     resp_len, (int) sizeof (struct BJNP_command))); +      return; +    } +  PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_finish_job: Finish scanjob response\n")); +  PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len)); + +} + +#ifdef PIXMA_BJNP_USE_STATUS +static int +bjnp_poll_scanner (int devno, char type,char *hostname, char *user, SANE_Byte *status, int size) +{ +/* + * send details of user to the scanner + */ + +  char cmd_buf[BJNP_CMD_MAX]; +  char resp_buf[BJNP_RESP_MAX]; +  int resp_len; +  int len = 0;			/* payload length */ +  int buf_len;			/* length of the whole command  buffer */ +  struct POLL_DETAILS *poll; +  struct POLL_RESPONSE *response; +  char user_host[256]; +  time_t t; +  int user_host_len; + +  poll = (struct POLL_DETAILS *) cmd_buf; +  memset( poll, 0, sizeof( struct POLL_DETAILS)); +  memset( &resp_buf, 0, sizeof( resp_buf) ); + + +  /* create payload */ +  poll->type = htons(type); + +  user_host_len =  sizeof( poll -> extensions.type2.user_host); +  snprintf(user_host, (user_host_len /2) ,"%s  %s", user, hostname); +  user_host[ user_host_len /2 + 1] = '\0'; + +  switch( type) { +    case 0: +      len = 80; +      break; +    case 1: +      charTo2byte(poll->extensions.type1.user_host, user_host, user_host_len); +      len = 80; +      break; +    case 2: +      poll->extensions.type2.dialog = htonl(device[devno].dialog); +      charTo2byte(poll->extensions.type2.user_host, user_host, user_host_len); +      poll->extensions.type2.unknown_1 = htonl(0x14); +      poll->extensions.type2.unknown_2 = htonl(0x10); +      t = time (NULL); +      strftime (poll->extensions.type2.ascii_date, +                sizeof (poll->extensions.type2.ascii_date), +               "%Y%m%d%H%M%S", localtime (&t)); +      len = 116; +      break; +    case 5: +      poll->extensions.type5.dialog = htonl(device[devno].dialog); +      charTo2byte(poll->extensions.type5.user_host, user_host, user_host_len); +      poll->extensions.type5.unknown_1 = htonl(0x14); +      poll->extensions.type5.key = htonl(device[devno].status_key); +      len = 100; +      break; +    default: +      PDBG (bjnp_dbg (LOG_INFO, "bjnp_poll_scanner: unknown packet type: %d\n", type)); +      return -1; +  }; +  /* we can only now set the header as we now know the length of the payload */ +  set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_POLL, +	   len); + +  buf_len = len + sizeof(struct BJNP_command); +  PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_poll_scanner: Poll details (type %d)\n", type)); +  PDBG (bjnp_hexdump (LOG_DEBUG2, cmd_buf, +		       buf_len)); + +  resp_len = udp_command (devno, cmd_buf, buf_len,  resp_buf, BJNP_RESP_MAX); + +  if (resp_len > 0) +    { +      PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_poll_scanner: Poll details response:\n")); +      PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len)); +      response = (struct POLL_RESPONSE *) resp_buf; + +      device[devno].dialog = ntohl( response -> dialog ); + +      if ( response -> result[3] == 1 ) +        { +          return BJNP_RESTART_POLL; +        } +      if ( (response -> result[2] & 0x80) != 0) +        { +          memcpy( status, response->status, size); +          PDBG( bjnp_dbg(LOG_INFO, "bjnp_poll_scanner: received button status!\n")); +	  PDBG (bjnp_hexdump( LOG_DEBUG2, status, size )); +	  device[devno].status_key = ntohl( response -> key ); +          return  size; +        } +    } +  return 0; +} +#endif + +static void +bjnp_send_job_details (int devno, char *hostname, char *user, char *title) +{ +/* + * send details of scanjob to scanner + */ + +  char cmd_buf[BJNP_CMD_MAX]; +  char resp_buf[BJNP_RESP_MAX]; +  int resp_len; +  struct JOB_DETAILS *job; +  struct BJNP_command *resp; + +  /* send job details command */ + +  set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_JOB_DETAILS, +	   sizeof (*job) - sizeof (struct BJNP_command)); + +  /* create payload */ + +  job = (struct JOB_DETAILS *) (cmd_buf); +  charTo2byte (job->unknown, "", sizeof (job->unknown)); +  charTo2byte (job->hostname, hostname, sizeof (job->hostname)); +  charTo2byte (job->username, user, sizeof (job->username)); +  charTo2byte (job->jobtitle, title, sizeof (job->jobtitle)); + +  PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_send_job_details: Job details\n")); +  PDBG (bjnp_hexdump (LOG_DEBUG2, cmd_buf, +		       (sizeof (struct BJNP_command) + sizeof (*job)))); + +  resp_len = udp_command (devno, cmd_buf, +			  sizeof (struct JOB_DETAILS), resp_buf, +			  BJNP_RESP_MAX); + +  if (resp_len > 0) +    { +      PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_send_job_details: Job details response:\n")); +      PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len)); +      resp = (struct BJNP_command *) resp_buf; +      device[devno].session_id = ntohs (resp->session_id); +    } +} + +static int +bjnp_get_scanner_mac_address ( int devno, char *mac_address ) +{ +/* + * send discover to scanner + */ + +  char cmd_buf[BJNP_CMD_MAX]; +  char resp_buf[BJNP_RESP_MAX]; +  int resp_len; +  struct DISCOVER_RESPONSE *resp = (struct DISCOVER_RESPONSE * )&resp_buf;; + +  /* send job details command */ + +  set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_DISCOVER, 0); +  resp_len = udp_command (devno, cmd_buf, +			  sizeof (struct BJNP_command), resp_buf, +			  BJNP_RESP_MAX); + +  if (resp_len > 0) +    { +      PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_get_scanner_mac_address: Discover response:\n")); +      PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len)); +      u8tohex( mac_address, resp -> mac_addr, sizeof( resp -> mac_addr ) ); +      return 0; +    } +  return -1; +} + +static int +bjnp_write (int devno, const SANE_Byte * buf, size_t count) +{ +/* + * This function writes TCP data to the scanner. + * Returns: number of bytes written to the scanner + */ +  int sent_bytes; +  int terrno; +  struct SCAN_BUF bjnp_buf; + +  if (device[devno].scanner_data_left) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, "bjnp_write: ERROR - scanner data left = 0x%lx = %ld\n", +	     (unsigned long) device[devno].scanner_data_left, +	     (unsigned long) device[devno].scanner_data_left)); +    } +  /* set BJNP command header */ + +  set_cmd_for_dev (devno, (struct BJNP_command *) &bjnp_buf, CMD_TCP_SEND, count); +  memcpy (bjnp_buf.scan_data, buf, count); +  PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_write: sending 0x%lx = %ld bytes\n", +		   (unsigned long) count, (unsigned long) count); +	PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &bjnp_buf, +			     sizeof (struct BJNP_command) + count))); + +  if ((sent_bytes = +       send (device[devno].tcp_socket, &bjnp_buf, +	     sizeof (struct BJNP_command) + count, 0)) < +      (ssize_t) (sizeof (struct BJNP_command) + count)) +    { +      /* return result from write */ +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, "bjnp_write: ERROR - Could not send data!\n")); +      errno = terrno; +      return sent_bytes; +    } +  /* correct nr of bytes sent for length of command */ + +  else if (sent_bytes != (int) (sizeof (struct BJNP_command) + count)) +    { +      errno = EIO; +      return -1; +    } +  return count; +} + +static int +bjnp_send_read_request (int devno) +{ +/* + * This function reads responses from the scanner. + * Returns: 0 on success, else -1 + * + */ +  int sent_bytes; +  int terrno; +  struct BJNP_command bjnp_buf; + +  if (device[devno].scanner_data_left) +    PDBG (bjnp_dbg +	  (LOG_CRIT, +	   "bjnp_send_read_request: ERROR - scanner data left = 0x%lx = %ld\n", +	   (unsigned long) device[devno].scanner_data_left, +	   (unsigned long) device[devno].scanner_data_left)); + +  /* set BJNP command header */ + +  set_cmd_for_dev (devno, (struct BJNP_command *) &bjnp_buf, CMD_TCP_REQ, 0); + +  PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_send_read_req sending command\n")); +  PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &bjnp_buf, +		       sizeof (struct BJNP_command))); + +  if ((sent_bytes = +       send (device[devno].tcp_socket, &bjnp_buf, sizeof (struct BJNP_command), +	     0)) < 0) +    { +      /* return result from write */ +      terrno = errno; +      PDBG (bjnp_dbg +	    (LOG_CRIT, "bjnp_send_read_request: ERROR - Could not send data!\n")); +      errno = terrno; +      return -1; +    } +  return 0; +} + +static SANE_Status +bjnp_recv_header (int devno, size_t *payload_size ) +{ +/* + * This function receives the response header to bjnp commands. + * devno device number + * size: return value for data size returned by scanner + * Returns: + * SANE_STATUS_IO_ERROR when any IO error occurs + * SANE_STATUS_GOOD in case no errors were encountered + */ +  struct BJNP_command resp_buf; +  fd_set input; +  struct timeval timeout; +  int recv_bytes; +  int terrno; +  int result; +  int fd; +  int attempt; + +  PDBG (bjnp_dbg +	(LOG_DEBUG, "bjnp_recv_header: receiving response header\n") ); +  fd = device[devno].tcp_socket; + +  *payload_size = 0; +  attempt = 0; +  do +    { +      /* wait for data to be received, ignore signals being received */ +      FD_ZERO (&input); +      FD_SET (fd, &input); + +      timeout.tv_sec = device[devno].bjnp_ip_timeout /1000; +      timeout.tv_usec = device[devno].bjnp_ip_timeout %1000; +    } +  while ( ( (result = select (fd + 1, &input, NULL, NULL, &timeout)) <= 0) && +	 (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS)); + +  if (result < 0) +    { +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, +		       "bjnp_recv_header: ERROR - could not read response header (select): %s!\n", +		       strerror (terrno))); +      errno = terrno; +      return SANE_STATUS_IO_ERROR; +    } +  else if (result == 0) +    { +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, +		"bjnp_recv_header: ERROR - could not read response header (select timed out after %d ms)!\n", +		device[devno].bjnp_ip_timeout ) ); +      errno = terrno; +      return SANE_STATUS_IO_ERROR; +    } + +  /* get response header */ + +  if ((recv_bytes = +       recv (fd, (char *) &resp_buf, +	     sizeof (struct BJNP_command), +	     0)) != sizeof (struct BJNP_command)) +    { +      terrno = errno; +      if (recv_bytes == 0) +        { +          PDBG (bjnp_dbg (LOG_CRIT, +          		"bjnp_recv_header: ERROR - (recv) Scanner closed the TCP-connection!\n")); +        } else { +          PDBG (bjnp_dbg (LOG_CRIT, +	      	       "bjnp_recv_header: ERROR - (recv) could not read response header, received %d bytes!\n", +		       recv_bytes)); +          PDBG (bjnp_dbg +	  		(LOG_CRIT, "bjnp_recv_header: ERROR - (recv) error: %s!\n", +	     		strerror (terrno))); +        } +      errno = terrno; +      return SANE_STATUS_IO_ERROR; +    } + +  if (resp_buf.cmd_code != device[devno].last_cmd) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, +	     "bjnp_recv_header: ERROR - Received response has cmd code %d, expected %d\n", +	     resp_buf.cmd_code, device[devno].last_cmd)); +      return SANE_STATUS_IO_ERROR; +    } + +  if (ntohs (resp_buf.seq_no) != (uint16_t) device[devno].serial) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, +	     "bjnp_recv_header: ERROR - Received response has serial %d, expected %d\n", +	     (int) ntohs (resp_buf.seq_no), (int) device[devno].serial)); +      return SANE_STATUS_IO_ERROR; +    } + +  /* got response header back, retrieve length of payload */ + + +  *payload_size = ntohl (resp_buf.payload_len); +  PDBG (bjnp_dbg +	(LOG_DEBUG, "bjnp_recv_header: TCP response header(payload data = %ld bytes):\n", +	 *payload_size) ); +  PDBG (bjnp_hexdump +	(LOG_DEBUG2, (char *) &resp_buf, sizeof (struct BJNP_command))); +  return SANE_STATUS_GOOD; +} + +static int +bjnp_init_device_structure(int dn, bjnp_sockaddr_t *sa, bjnp_protocol_defs_t *protocol_defs, int ip_timeout) +{ +  /* initialize device structure */ + +  char name[BJNP_HOST_MAX]; + +  device[dn].open = 0; +#ifdef PIXMA_BJNP_USE_STATUS +  device[dn].polling_status = BJNP_POLL_STOPPED; +  device[dn].dialog = 0; +  device[dn].status_key = 0; +#endif +  device[dn].protocol = protocol_defs->protocol_version; +  device[dn].protocol_string = protocol_defs->proto_string; +  device[dn].tcp_socket = -1; + +  device[dn].addr = (bjnp_sockaddr_t *) malloc(sizeof ( bjnp_sockaddr_t) ); +  memset( device[dn].addr, 0, sizeof( bjnp_sockaddr_t ) ); +  memcpy(device[dn].addr, sa, sa_size((bjnp_sockaddr_t *)sa) ); +  device[dn].address_level = get_scanner_name(sa, name); +  device[dn].session_id = 0; +  device[dn].serial = -1; +  device[dn].bjnp_ip_timeout = ip_timeout; +  device[dn].bjnp_scanner_timeout = 1000; +  device[dn].scanner_data_left = 0; +  device[dn].last_cmd = 0; +  device[dn].blocksize = BJNP_BLOCKSIZE_START; +  device[dn].last_block = 0; +  /* fill mac_address */ + +  if (bjnp_get_scanner_mac_address(dn, device[dn].mac_address) != 0 ) +    { +      PDBG (bjnp_dbg +            (LOG_CRIT, "bjnp_init_device_structure: Cannot read mac address, skipping this scanner\n"  ) ); +            device[dn].open = 0; +      return -1; +    } +  device[dn].open = 1; +  return 0; +} + +static void +bjnp_free_device_structure( int dn) +{ +  if (device[dn].addr != NULL) +    { +    free (device[dn].addr ); +    device[dn].addr = NULL; +    } +  device[dn].open = 0; +} + +static SANE_Status +bjnp_recv_data (int devno, SANE_Byte * buffer, size_t start_pos, size_t * len) +{ +/* + * This function receives the payload data. + * NOTE: len may not exceed SSIZE_MAX (as that is max for recv) + *       len will be restricted to SSIZE_MAX to be sure + * Returns: number of bytes of payload received from device + */ + +  fd_set input; +  struct timeval timeout; +  ssize_t recv_bytes; +  int terrno; +  int result; +  int fd; +  int attempt; + +  PDBG (bjnp_dbg +	(LOG_DEBUG, "bjnp_recv_data: read response payload (0x%lx bytes max), buffer: 0x%lx, start_pos: 0x%lx\n", +	 (long) *len, (long) buffer, (long) start_pos)); + + +  if (*len == 0) +    { +      /* nothing to do */ +      PDBG (bjnp_dbg +  	    (LOG_DEBUG, "bjnp_recv_data: Nothing to do (%ld bytes requested)\n", +	     (long) *len)); +      return SANE_STATUS_GOOD; +    } +  else if ( *len > SSIZE_MAX ) +    { +      PDBG (bjnp_dbg +    	    (LOG_DEBUG, "bjnp_recv_data: WARNING - requested block size (%ld) exceeds maximum, setting to maximum %ld\n", +	     (long)*len, SSIZE_MAX)); +      *len = SSIZE_MAX; +    } + +  fd = device[devno].tcp_socket; +  attempt = 0; +  do +    { +      /* wait for data to be received, retry on a signal being received */ +      FD_ZERO (&input); +      FD_SET (fd, &input); +      timeout.tv_sec = device[devno].bjnp_ip_timeout /1000; +      timeout.tv_usec = device[devno].bjnp_ip_timeout %1000; +    } +  while (((result = select (fd + 1, &input, NULL, NULL, &timeout)) <= 0) && +	 (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS)); + +  if (result < 0) +    { +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, +		       "bjnp_recv_data: ERROR - could not read response payload (select failed): %s!\n", +		       strerror (errno))); +      errno = terrno; +      *len = 0; +      return SANE_STATUS_IO_ERROR; +    } +  else if (result == 0) +    { +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, +		"bjnp_recv_data: ERROR - could not read response payload (select timed out after %d ms)!\n", +		device[devno].bjnp_ip_timeout) ); +      errno = terrno; +      *len = 0; +      return SANE_STATUS_IO_ERROR; +    } + +  if ((recv_bytes = recv (fd, buffer + start_pos, *len, 0)) < 0) +    { +      terrno = errno; +      PDBG (bjnp_dbg (LOG_CRIT, +		       "bjnp_recv_data: ERROR - could not read response payload (%ld + %ld = %ld) (recv): %s!\n", +		       (long) buffer, (long) start_pos, (long) buffer + start_pos, strerror (errno))); +      errno = terrno; +      *len = 0; +      return SANE_STATUS_IO_ERROR; +    } +  PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_recv_data: Received TCP response payload (%ld bytes):\n", +		   (unsigned long) recv_bytes)); +  PDBG (bjnp_hexdump (LOG_DEBUG2, buffer, recv_bytes)); + +  *len = recv_bytes; +  return SANE_STATUS_GOOD; +} + +static BJNP_Status +bjnp_allocate_device (SANE_String_Const devname, +                      SANE_Int * dn, char *resulting_host) +{ +  char method[BJNP_METHOD_MAX]; +  char host[BJNP_HOST_MAX]; +  char port[BJNP_PORT_MAX] = ""; +  char args[BJNP_ARGS_MAX]; +  bjnp_protocol_defs_t *protocol_defs; +  struct addrinfo *res, *cur; +  struct addrinfo hints; +  int result; +  int i; +  int ip_timeout = BJNP_TIMEOUT_DEFAULT; + +  PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_allocate_device(%s) %d\n", devname, bjnp_no_devices)); + +  if (split_uri (devname, method, host, port, args) != 0) +    { +      return BJNP_STATUS_INVAL; +    } + +  if (strlen (args) > 0) +    { +      /* get device specific ip timeout if any */ + +      if (strncmp(args, "timeout=", strlen("timeout=")) == 0) +        { +          ip_timeout = atoi(args + strlen("timeout=")); +        } else { +		PDBG (bjnp_dbg +	    		(LOG_CRIT, +				"bjnp_allocate_device: ERROR - Unrecognized argument: %s\n", +	     			devname)); + +      return BJNP_STATUS_INVAL; +        } +    } +  if ( (protocol_defs = get_protocol_by_method(method)) == NULL) +    { +      PDBG (bjnp_dbg +		(LOG_CRIT, "bjnp_allocate_device: ERROR - URI %s contains invalid method: %s\n", +		 devname, method)); +      return BJNP_STATUS_INVAL; +    } + +  if (strlen(port) == 0) +    { +      sprintf( port, "%d", protocol_defs->default_port ); +    } + +  hints.ai_flags = 0; +#ifdef ENABLE_IPV6 +  hints.ai_family = AF_UNSPEC; +#else +  hints.ai_family = AF_INET; +#endif +  hints.ai_socktype = SOCK_DGRAM; +  hints.ai_protocol = 0; +  hints.ai_addrlen = 0; +  hints.ai_addr = NULL; +  hints.ai_canonname = NULL; +  hints.ai_next = NULL; + +  result = getaddrinfo (host, port, &hints, &res ); +  if (result != 0 ) +    { +      PDBG (bjnp_dbg (LOG_CRIT, "bjnp_allocate_device: ERROR - Cannot resolve host: %s port %s\n", host, port)); +      return SANE_STATUS_INVAL; +    } + +  /* Check if a device number is already allocated to any of the scanner's addresses */ + +  cur = res; +  while( cur != NULL) +    { +      /* create a new device structure for this address */ + +      if (bjnp_no_devices == BJNP_NO_DEVICES) +        { +          PDBG (bjnp_dbg +    	    (LOG_CRIT, +    	     "bjnp_allocate_device: WARNING - Too many devices, ran out of device structures, cannot add %s\n", +    	     devname)); +          freeaddrinfo(res); +          return BJNP_STATUS_INVAL; +        } +      if (bjnp_init_device_structure( bjnp_no_devices, (bjnp_sockaddr_t *)cur -> ai_addr, +                                      protocol_defs, ip_timeout) != 0) +        { +          /* giving up on this address, try next one if any */ +          cur = cur->ai_next; +          continue; +        } +      for (i = 0; i < bjnp_no_devices; i++) +        { + +          /* Check if found the scanner before, if so we use the best address +	   * but still make sure the scanner is listed only once. +	   * We check for matching addresses as wel as matching mac_addresses as +           * an IPv6 host can have multiple adresses */ + +          if ( strcmp( device[i].mac_address, device[bjnp_no_devices].mac_address ) == 0 ) +            { +              if ( device[i].address_level < device[bjnp_no_devices].address_level ) +                { +                  /* use the new address instead as it is better */ +                  free (device[i].addr); +                  device[i].addr = device[bjnp_no_devices].addr; +                  device[bjnp_no_devices].addr = NULL; +                  device[i].address_level = device[bjnp_no_devices].address_level; +                } + +	      /* Leave timeout values unchanged, as they were probably specified by the user */ + +              freeaddrinfo(res); +              *dn = i; +              bjnp_free_device_structure( bjnp_no_devices); +              return BJNP_STATUS_ALREADY_ALLOCATED; +            } +        } +      cur = cur->ai_next; +    } +  freeaddrinfo(res); + +  if (device[bjnp_no_devices].open == 0) +    { +      PDBG (bjnp_dbg(LOG_NOTICE, "bjnp_allocate_device: Cannot access scanner, skipping!")); +      return BJNP_STATUS_INVAL; +    } + +  PDBG (bjnp_dbg (LOG_INFO, "bjnp_allocate_device: Scanner not yet in our list, added it: %s:%s\n", host, port)); + +  /* Commit new device structure */ + +  *dn = bjnp_no_devices; +  bjnp_no_devices++; + +  /* return hostname if required */ + +  if (resulting_host != NULL) +    { +      strcpy (resulting_host, host); +    } + +  return BJNP_STATUS_GOOD; +} + +static void add_scanner(SANE_Int *dev_no, +                        const char *uri, +			SANE_Status (*attach_bjnp) +			              (SANE_String_Const devname, +			               SANE_String_Const serial, +			               const struct pixma_config_t *cfg), +                       const struct pixma_config_t *const pixma_devices[]) + +{ +  char scanner_host[BJNP_HOST_MAX]; +  char serial[BJNP_SERIAL_MAX]; +  char makemodel[BJNP_MODEL_MAX]; +  const struct pixma_config_t *cfg = NULL; + +  /* Allocate device structure for scanner */ +  switch (bjnp_allocate_device (uri, dev_no, scanner_host)) +    { +      case BJNP_STATUS_GOOD: +        if (get_scanner_id (*dev_no, makemodel) != 0) +          { +            PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: ERROR - Cannot read scanner make & model: %s\n", +                             uri)); +          } +        else +          { +            /* +             * fetch scanner configuration +             */ +            if ((cfg = lookup_scanner(makemodel, pixma_devices)) == (struct pixma_config_t *)NULL) +              { +                 PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: Scanner %s is not supported, model is unknown! Please report upstream\n", makemodel)); +                 break; +              } + +            /* +             * inform caller of found scanner +             */ + +             determine_scanner_serial (scanner_host, device[*dev_no].mac_address, serial); + +             switch (attach_bjnp (uri, serial, cfg)) +             { +               case SANE_STATUS_GOOD: +                 PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: New scanner added: %s, serial %s, mac address: %s.\n", +	                         uri, serial, device[*dev_no].mac_address)); +                 break; +               default: +                 PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: unexpected error (out of memory?), adding %s\n", makemodel)); +            } +          } + +        break; +      case BJNP_STATUS_ALREADY_ALLOCATED: +        PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: Scanner at %s was added before, good!\n", +	                 uri)); +        break; + +      case BJNP_STATUS_INVAL: +        PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: Scanner at %s can not be added\n", +	                 uri)); +        break; +    } +} + +int add_timeout_to_uri(char *uri, int timeout, int max_len) +{ +  char method[BJNP_METHOD_MAX]; +  char host[BJNP_HOST_MAX]; +  char port_str[BJNP_PORT_MAX]; +  char args[BJNP_HOST_MAX]; +  int port; +  bjnp_protocol_defs_t *proto_struct; + +  if (split_uri(uri, method, host, port_str, args ) != 0) +    { +      return -1; +    } + +  port = atoi(port_str); + +  if (port == 0) +    { +      proto_struct = get_protocol_by_method(method); +      if (proto_struct == NULL) +        { +          PDBG (bjnp_dbg (LOG_NOTICE, "uri: %s: Method %s cannot be recognized\n", uri,  method)); +        } +      else +        { +          port = proto_struct-> default_port; +        } +    } + +  /* add timeout value only if missing in URI */ + +  if (strstr(args, "timeout=") == NULL) +    { +      sprintf(args, "timeout=%d", timeout); +    } + +  snprintf(uri, max_len -1, "%s://%s:%d/%s", method,host, port, args); +  uri[max_len - 1] = '\0'; +  return 0; +} + +/** Public functions **/ + +/** Initialize sanei_bjnp. + * + * Call this before any other sanei_bjnp function. + */ +extern void +sanei_bjnp_init (void) +{ +  DBG_INIT(); +  bjnp_no_devices = 0; +} + +/** + * Find devices that implement the bjnp protocol + * + * The function attach is called for every device which has been found. + * + * @param attach attach function + * + * @return SANE_STATUS_GOOD - on success (even if no scanner was found) + */ +extern SANE_Status +sanei_bjnp_find_devices (const char **conf_devices, +			 SANE_Status (*attach_bjnp) +			     (SANE_String_Const devname, +			      SANE_String_Const serial, +			      const struct pixma_config_t *cfg), +			 const struct pixma_config_t *const pixma_devices[]) +{ +  int numbytes = 0; +  struct BJNP_command cmd; +  unsigned char resp_buf[2048]; +  struct DISCOVER_RESPONSE *disc_resp = ( struct DISCOVER_RESPONSE *) & resp_buf; +  int socket_fd[BJNP_SOCK_MAX]; +  int no_sockets; +  int i; +  int j; +  int attempt; +  int last_socketfd = 0; +  fd_set fdset; +  fd_set active_fdset; +  struct timeval timeout; +  char scanner_host[HOST_NAME_MAX]; +  char uri[HOST_NAME_MAX + 32]; +  int dev_no; +  int port; +  int auto_detect = 1; +  int timeout_default = BJNP_TIMEOUT_DEFAULT; +  bjnp_sockaddr_t broadcast_addr[BJNP_SOCK_MAX]; +  bjnp_sockaddr_t scanner_sa; +  socklen_t socklen; +  bjnp_protocol_defs_t *protocol_defs; + +  memset( broadcast_addr, 0, sizeof( broadcast_addr) ); +  memset( &scanner_sa, 0 ,sizeof( scanner_sa ) ); +  PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_find_devices, pixma backend version: %d.%d.%d\n", +	PIXMA_VERSION_MAJOR, PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD)); +  bjnp_no_devices = 0; + +  for (i=0; i < BJNP_SOCK_MAX; i++) +    { +      socket_fd[i] = -1; +    } + +  /* parse config file */ + +  if (conf_devices[0] != NULL) +    { +      if (strcmp(conf_devices[0], "networking=no") == 0) +        { +          /* networking=no may only occur on the first non-commented line */ + +          PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Networked scanner detection is disabled in configuration file.\n" ) ); +          return SANE_STATUS_GOOD; +        } +      /* parse configuration file */ + +      for (i = 0; conf_devices[i] != NULL; i++) +        { +          if (strncmp(conf_devices[i], "bjnp-timeout=", strlen("bjnp-timeout="))== 0) +            { +	      timeout_default = atoi(conf_devices[i] + strlen("bjnp-timeout=") ); +	      PDBG ( bjnp_dbg (LOG_DEBUG, "Set new default timeout value: %d ms.", timeout_default)); +	      continue; +	    } +	  else if (strncmp(conf_devices[i], "auto_detection=no", strlen("auto_detection=no"))== 0) +            { +              auto_detect = 0; +	      PDBG ( bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: auto detection of scanners disabled")); +	      continue; +	    } +	  else +            { +              PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Adding scanner from pixma.conf: %s\n", conf_devices[i])); +              memcpy(uri, conf_devices[i], sizeof(uri)); +              add_timeout_to_uri(uri, timeout_default, sizeof(uri)); +              add_scanner(&dev_no, uri, attach_bjnp, pixma_devices); +	    } +        } +      PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Added all specified scanners.\n")); +    } +  else +    { +      PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Configuration file is empty, No devices specified.\n" ) ); +    } + +  if (auto_detect == 0) +    { +      return SANE_STATUS_GOOD; +    } +  /* +   * Send UDP DISCOVER to discover scanners and return the list of scanners found +   */ + +  PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Start auto-detection.\n" ) ); +  FD_ZERO (&fdset); + +  no_sockets = 0; +#ifdef HAVE_IFADDRS_H +  { +    struct ifaddrs *interfaces = NULL; +    struct ifaddrs *interface; +    getifaddrs (&interfaces); + +    /* create a socket for each suitable interface */ + +    interface = interfaces; +    while ((no_sockets < BJNP_SOCK_MAX) && (interface != NULL)) +      { +        if ( ! (interface -> ifa_flags & IFF_POINTOPOINT) && +            ( (socket_fd[no_sockets] = +                      prepare_socket( interface -> ifa_name, +                                      (bjnp_sockaddr_t *) interface -> ifa_addr, +                                      (bjnp_sockaddr_t *) interface -> ifa_broadaddr, +                                      &broadcast_addr[no_sockets] ) ) != -1 ) ) +          { +            /* track highest used socket for later use in select */ +            if (socket_fd[no_sockets] > last_socketfd) +              { +                last_socketfd = socket_fd[no_sockets]; +              } +            FD_SET (socket_fd[no_sockets], &fdset); +            no_sockets++; +          } +        interface = interface->ifa_next; +      } +    freeifaddrs (interfaces); +  } +#else +  /* we have no easy way to find interfaces with their broadcast addresses. */ +  /* use global broadcast and all-hosts instead */ +  { +    bjnp_sockaddr_t local; +    bjnp_sockaddr_t bc_addr; + +    memset( &local, 0, sizeof( local) ); +    local.ipv4.sin_family = AF_INET; +    local.ipv4.sin_addr.s_addr = htonl (INADDR_ANY); + +    bc_addr.ipv4.sin_family = AF_INET; +    bc_addr.ipv4.sin_port = htons(0); +    bc_addr.ipv4.sin_addr.s_addr = htonl (INADDR_BROADCAST); + +    socket_fd[no_sockets] = prepare_socket( "any_interface", +                                   &local, +                                   &bc_addr, +                                   &broadcast_addr[no_sockets] ); +    if (socket_fd[no_sockets] >= 0) +      { +        FD_SET (socket_fd[no_sockets], &fdset); +        if (socket_fd[no_sockets] > last_socketfd) +          { +            last_socketfd = socket_fd[no_sockets]; +          } +        no_sockets++; +      } +#ifdef ENABLE_IPV6 +    local.ipv6.sin6_family = AF_INET6; +    local.ipv6.sin6_addr = in6addr_any; + +    socket_fd[no_sockets] = prepare_socket( "any_interface", +                                   &local, +                                   NULL, +                                   &broadcast_addr[no_sockets] ); +    if (socket_fd[no_sockets] >= 0) +      { +        FD_SET (socket_fd[no_sockets], &fdset); +        if (socket_fd[no_sockets] > last_socketfd) +          { +            last_socketfd = socket_fd[no_sockets]; +          } +        no_sockets++; +      } +#endif +  } +#endif + +  /* send BJNP_MAX_BROADCAST_ATTEMPTS broadcasts on each prepared socket */ +  for (attempt = 0; attempt < BJNP_MAX_BROADCAST_ATTEMPTS; attempt++) +    { +      for ( i=0; i < no_sockets; i++) +        { +	  j = 0; +          while(bjnp_protocol_defs[j].protocol_version != PROTOCOL_NONE) +	    { +	      set_cmd_from_string (bjnp_protocol_defs[j].proto_string, &cmd, CMD_UDP_DISCOVER, 0); +              bjnp_send_broadcast ( socket_fd[i], &broadcast_addr[i], +                                    bjnp_protocol_defs[j].default_port, cmd, sizeof (cmd)); +	      j++; +	    } +	} +      /* wait for some time between broadcast packets */ +      usleep (BJNP_BROADCAST_INTERVAL * BJNP_USLEEP_MS); +    } + +  /* wait for a UDP response */ + +  timeout.tv_sec = 0; +  timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * BJNP_USLEEP_MS; + + +  active_fdset = fdset; + +  while (select (last_socketfd + 1, &active_fdset, NULL, NULL, &timeout) > 0) +    { +      PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Select returned, time left %d.%d....\n", +		       (int) timeout.tv_sec, (int) timeout.tv_usec)); +      for (i = 0; i < no_sockets; i++) +	{ +	  if (FD_ISSET (socket_fd[i], &active_fdset)) +	    { +              socklen =  sizeof(scanner_sa); +	      if ((numbytes = +		   recvfrom (socket_fd[i], resp_buf, sizeof (resp_buf), 0, +                             &(scanner_sa.addr), &socklen ) ) == -1) +		{ +		  PDBG (bjnp_dbg +			(LOG_INFO, "sanei_find_devices: no data received")); +		  break; +		} +	      else +		{ +		  PDBG (bjnp_dbg (LOG_DEBUG2, "sanei_find_devices: Discover response:\n")); +		  PDBG (bjnp_hexdump (LOG_DEBUG2, &resp_buf, numbytes)); + +		  /* check if something sensible is returned */ +		  protocol_defs = get_protocol_by_proto_string(disc_resp-> response.BJNP_id); +		  if ( (numbytes < (int)sizeof (struct BJNP_command)) || +		       (protocol_defs == NULL)) +		    { +		      /* not a valid response, assume not a scanner  */ + +                      char bjnp_id[5]; +                      strncpy(bjnp_id,  disc_resp-> response.BJNP_id, 4); +                      bjnp_id[4] = '\0'; +                      PDBG (bjnp_dbg (LOG_INFO, +                        "sanei_find_devices: Invalid discover response! Length = %d, Id = %s\n", +                        numbytes, bjnp_id ) ); +		      break; +		    } +                  if ( !(disc_resp -> response.dev_type & 0x80) ) +                    { +                      /* not a response, a command from somebody else or */ +                      /* a discover command that we generated */ +                      break; +                    } +		}; + +	      port = get_port_from_sa(scanner_sa); +	      /* scanner found, get IP-address or hostname */ +              get_scanner_name( &scanner_sa, scanner_host); + +	      /* construct URI */ +	      sprintf (uri, "%s://%s:%d/timeout=%d", protocol_defs->method_string, scanner_host, +		           port, timeout_default); + +              add_scanner( &dev_no, uri, attach_bjnp, pixma_devices); + +	    } +	} +      active_fdset = fdset; +      timeout.tv_sec = 0; +      timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * BJNP_USLEEP_MS; +    } +  PDBG (bjnp_dbg (LOG_DEBUG, "sanei_find_devices: scanner discovery finished...\n")); + +  for (i = 0; i < no_sockets; i++) +    close (socket_fd[i]); + +  return SANE_STATUS_GOOD; +} + +/** Open a BJNP device. + * + * The device is opened by its name devname and the device number is + * returned in dn on success. + * + * Device names consist of an URI + * Where: + * type = bjnp + * hostname = resolvable name or IP-address + * port = 8612 for a scanner + * An example could look like this: bjnp://host.domain:8612 + * + * @param devname name of the device to open + * @param dn device number + * + * @return + * - SANE_STATUS_GOOD - on success + * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to + *   permissions + * - SANE_STATUS_INVAL - on every other error + */ + +extern SANE_Status +sanei_bjnp_open (SANE_String_Const devname, SANE_Int * dn) +{ +  int result; + +  PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_open(%s, %d):\n", devname, *dn)); + +  result = bjnp_allocate_device (devname, dn, NULL); +  if ( (result != BJNP_STATUS_GOOD) && (result != BJNP_STATUS_ALREADY_ALLOCATED ) ) { +    return SANE_STATUS_INVAL; +  } +  return SANE_STATUS_GOOD; +} + +/** Close a BJNP device. + * + * @param dn device number + */ + +void +sanei_bjnp_close (SANE_Int dn) +{ +  PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_close(%d):\n", dn)); + +  device[dn].open = 0; +  sanei_bjnp_deactivate(dn); +} + +/** Activate BJNP device connection + * + * @param dn device number + */ + +SANE_Status +sanei_bjnp_activate (SANE_Int dn) +{ +  char hostname[256]; +  char pid_str[64]; + +  PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_activate (%d)\n", dn)); +  gethostname (hostname, 256); +  hostname[255] = '\0'; +  sprintf (pid_str, "Process ID = %d", getpid ()); + +  bjnp_send_job_details (dn, hostname, getusername (), pid_str); + +  if (bjnp_open_tcp (dn) != 0) +    { +      return SANE_STATUS_INVAL; +    } + +  return SANE_STATUS_GOOD; +} + +/** Deactivate BJNP device connection + * + * @paran dn device number + */ + +SANE_Status +sanei_bjnp_deactivate (SANE_Int dn) +{ +  PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_deactivate (%d)\n", dn)); +  if ( device[dn].tcp_socket != -1) +    { +      bjnp_finish_job (dn); +      close (device[dn].tcp_socket); +      device[dn].tcp_socket = -1; +    } +  return SANE_STATUS_GOOD; +} + +/** Set the timeout for interrupt reads. + *  we do not use it for bulk reads! + * @param timeout the new timeout in ms + */ +extern void +sanei_bjnp_set_timeout (SANE_Int devno, SANE_Int timeout) +{ +  PDBG (bjnp_dbg (LOG_INFO, "bjnp_set_timeout to %d\n", +        timeout)); + +  device[devno].bjnp_scanner_timeout = timeout; +} + +/** Initiate a bulk transfer read. + * + * Read up to size bytes from the device to buffer. After the read, size + * contains the number of bytes actually read. + * + * @param dn device number + * @param buffer buffer to store read data in + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_EOF - if zero bytes have been read + * - SANE_STATUS_IO_ERROR - if an error occured during the read + * - SANE_STATUS_INVAL - on every other error + * + */ + +extern SANE_Status +sanei_bjnp_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size) +{ +  SANE_Status result; +  SANE_Status error; +  size_t recvd; +  size_t read_size; +  size_t read_size_max; +  size_t requested; + +  PDBG (bjnp_dbg +	(LOG_INFO, "bjnp_read_bulk(dn=%d, bufferptr=%lx, 0x%lx = %ld)\n", dn, +	 (long) buffer, (unsigned long) *size, (unsigned long) *size)); + +  recvd = 0; +  requested = *size; + +  PDBG (bjnp_dbg +	(LOG_DEBUG, "bjnp_read_bulk: 0x%lx = %ld bytes available at start\n", +	 (unsigned long) device[dn].scanner_data_left, +	 (unsigned long) device[dn].scanner_data_left ) ); + +  while ( (recvd < requested) && !( device[dn].last_block && (device[dn].scanner_data_left == 0)) ) +    { +      PDBG (bjnp_dbg +	    (LOG_DEBUG, +	     "bjnp_read_bulk: Already received 0x%lx = %ld bytes, backend requested 0x%lx = %ld bytes\n", +	     (unsigned long) recvd, (unsigned long) recvd, +	     (unsigned long) requested, (unsigned long)requested )); + +      /* Check first if there is data in flight from the scanner */ + +      if (device[dn].scanner_data_left == 0) +        { +	  /* There is no data in flight from the scanner, send new read request */ + +          PDBG (bjnp_dbg (LOG_DEBUG, +                          "bjnp_read_bulk: No (more) scanner data available, requesting more( blocksize = %ld = %lx\n", +                          (long int) device[dn].blocksize, (long int) device[dn].blocksize )); + +          if ((error = bjnp_send_read_request (dn)) != SANE_STATUS_GOOD) +            { +              *size = recvd; +              return SANE_STATUS_IO_ERROR; +            } +          if ( ( error = bjnp_recv_header (dn, &(device[dn].scanner_data_left) )  ) != SANE_STATUS_GOOD) +            { +              *size = recvd; +              return SANE_STATUS_IO_ERROR; +            } +          /* correct blocksize if applicable */ + +          device[dn].blocksize = MAX (device[dn].blocksize, device[dn].scanner_data_left); + +          if ( device[dn].scanner_data_left < device[dn].blocksize) +            { +              /* the scanner will not react at all to a read request, when no more data is available */ +              /* we now determine end of data by comparing the payload size to the maximun blocksize */ +              /* this block is shorter than blocksize, so after this block we are done */ + +              device[dn].last_block = 1; +            } +        } + +      PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: In flight: 0x%lx = %ld bytes available\n", +		 (unsigned long) device[dn].scanner_data_left, +		 (unsigned long) device[dn].scanner_data_left)); + +       /* read as many bytes as needed and available */ + +      read_size_max = MIN( device[dn].scanner_data_left, (requested - recvd) ); +      read_size = read_size_max; + +      PDBG (bjnp_dbg +	    (LOG_DEBUG, +	     "bjnp_read_bulk: Try to read 0x%lx = %ld (of max 0x%lx = %ld) bytes\n", +	     (unsigned long) read_size_max, +	     (unsigned long) read_size_max, +	     (unsigned long) device[dn].scanner_data_left, +	     (unsigned long) device[dn].scanner_data_left) ); + +      result = bjnp_recv_data (dn, buffer , recvd, &read_size); +      if (result != SANE_STATUS_GOOD) +	{ +	  *size = recvd; +	  return SANE_STATUS_IO_ERROR; +	} +      PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: Expected at most %ld bytes, received this time: %ld\n", +            read_size_max, read_size) ); + +      device[dn].scanner_data_left = device[dn].scanner_data_left - read_size; +      recvd = recvd + read_size; +    } + +  PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: %s: Returning %ld bytes, backend expexts %ld\n", +        (recvd == *size)? "OK": "NOTICE",recvd, *size ) ); +  *size = recvd; +  if ( *size == 0 ) +    return SANE_STATUS_EOF; +  return SANE_STATUS_GOOD; +} + +/** Initiate a bulk transfer write. + * + * Write up to size bytes from buffer to the device. After the write size + * contains the number of bytes actually written. + * + * @param dn device number + * @param buffer buffer to write to device + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_IO_ERROR - if an error occured during the write + * - SANE_STATUS_INVAL - on every other error + */ + +extern SANE_Status +sanei_bjnp_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size) +{ +  ssize_t sent; +  size_t recvd; +  uint32_t buf; +  size_t payload_size; + +  /* Write received data to scanner */ + +  sent = bjnp_write (dn, buffer, *size); +  if (sent < 0) +    return SANE_STATUS_IO_ERROR; +  if (sent != (int) *size) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Sent only %ld bytes to scanner, expected %ld!!\n", +	     (unsigned long) sent, (unsigned long) *size)); +      return SANE_STATUS_IO_ERROR; +    } + +  if (bjnp_recv_header (dn, &payload_size) != SANE_STATUS_GOOD) +    { +      PDBG (bjnp_dbg (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Could not read response to command!\n")); +      return SANE_STATUS_IO_ERROR; +    } + +  if (payload_size != 4) +    { +      PDBG (bjnp_dbg (LOG_CRIT, +		       "sanei_bjnp_write_bulk: ERROR - Scanner length of write confirmation = 0x%lx bytes = %ld, expected %d!!\n", +		       (unsigned long) payload_size, +		       (unsigned long) payload_size, 4)); +      return SANE_STATUS_IO_ERROR; +    } +  recvd = payload_size; +  if ((bjnp_recv_data (dn, (unsigned char *) &buf, 0, &recvd) != +       SANE_STATUS_GOOD) || (recvd != payload_size)) +    { +      PDBG (bjnp_dbg (LOG_CRIT, +		       "sanei_bjnp_write_bulk: ERROR - Could not read length of data confirmed by device\n")); +      return SANE_STATUS_IO_ERROR; +    } +  recvd = ntohl (buf); +  if (recvd != *size) +    { +      PDBG (bjnp_dbg +	    (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Scanner confirmed %ld bytes, expected %ld!!\n", +	     (unsigned long) recvd, (unsigned long) *size)); +      return SANE_STATUS_IO_ERROR; +    } +  /* we can expect data from the scanner */ + +  device[dn].last_block = 0; + +  return SANE_STATUS_GOOD; +} + +/** Initiate a interrupt transfer read. + * + * Read up to size bytes from the interrupt endpoint from the device to + * buffer. After the read, size contains the number of bytes actually read. + * + * @param dn device number + * @param buffer buffer to store read data in + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_EOF - if zero bytes have been read + * - SANE_STATUS_IO_ERROR - if an error occured during the read + * - SANE_STATUS_INVAL - on every other error + * + */ + +extern SANE_Status +sanei_bjnp_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size) +{ +#ifndef PIXMA_BJNP_USE_STATUS +  PDBG (bjnp_dbg +	(LOG_INFO, "bjnp_read_int(%d, bufferptr, 0x%lx = %ld):\n", dn, +	 (unsigned long) *size, (unsigned long) *size)); + +  memset (buffer, 0, *size); +  sleep (1); +  return SANE_STATUS_IO_ERROR; +#else + +  char hostname[256]; +  int resp_len; +  int timeout; +  int interval; + +  PDBG (bjnp_dbg +	(LOG_INFO, "bjnp_read_int(%d, bufferptr, 0x%lx = %ld):\n", dn, +	 (unsigned long) *size, (unsigned long) *size)); + +  memset (buffer, 0, *size); + +  gethostname (hostname, 32); +  hostname[32] = '\0'; + + +  switch (device[dn].polling_status) +    { +    case BJNP_POLL_STOPPED: + +      /* establish dialog */ + +      if ( (bjnp_poll_scanner (dn, 0, hostname, getusername (), buffer, *size ) != 0) || +           (bjnp_poll_scanner (dn, 1, hostname, getusername (), buffer, *size ) != 0) ) +        { +	  PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: WARNING - Failed to setup read_intr dialog with device!\n")); +          device[dn].dialog = 0; +          device[dn].status_key = 0; +          return SANE_STATUS_IO_ERROR; +        } +      device[dn].polling_status = BJNP_POLL_STARTED; + +      /* fall through */ +    case BJNP_POLL_STARTED: +      /* we use only seonds (rounded up) accuracy between poll attempts */ +      timeout = device[dn].bjnp_scanner_timeout /1000 + 1; +      if (device[dn].bjnp_scanner_timeout %1000 > 0) +        { +	  timeout++; + +	} +      interval = 1; +      do +        { +          if ( (resp_len = bjnp_poll_scanner (dn, 2, hostname, getusername (), buffer, *size ) ) < 0 ) +            { +              PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: Poll failed, Restarting polling dialog!\n")); +              device[dn].polling_status = BJNP_POLL_STOPPED; +              *size = 0; +              return SANE_STATUS_EOF; +            } +          *size = (size_t) resp_len; +          if ( resp_len > 0 ) +            { +              device[dn].polling_status = BJNP_POLL_STATUS_RECEIVED; +              return SANE_STATUS_GOOD; +            } +          timeout = timeout - interval; +	  if (timeout <= 0) +	    return SANE_STATUS_EOF; +          sleep(interval); +        } while ( timeout > 0 ) ; +      break; +    case BJNP_POLL_STATUS_RECEIVED: +       if ( (resp_len = bjnp_poll_scanner (dn, 5, hostname, getusername (), buffer, *size ) ) < 0 ) +        { +          PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: Restarting polling dialog!\n")); +          device[dn].polling_status = BJNP_POLL_STOPPED; +          *size = 0; +          break; +        } +    } +  return SANE_STATUS_EOF; +#endif +} diff --git a/backend/pixma/pixma_bjnp.h b/backend/pixma/pixma_bjnp.h new file mode 100644 index 0000000..79e084e --- /dev/null +++ b/backend/pixma/pixma_bjnp.h @@ -0,0 +1,201 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2008 by Louis Lagendijk +   based on sane_usb.h: +   Copyright (C) 2003, 2005 Rene Rebe (sanei_read_int,sanei_set_timeout) +   Copyright (C) 2001, 2002 Henning Meier-Geinitz + +   This file is part of the SANE package. + +   SANE 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. + +   SANE 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 sane; see the file COPYING.  If not, write to the Free +   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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. +*/ +/** @file sanei_bjnp.h + * This file provides a generic BJNP interface. + */ + +#ifndef sanei_bjnp_h +#define sanei_bjnp_h + +#include "../include/sane/config.h" +#include "../include/sane/sane.h" +#include "pixma.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h>		/* for size_t */ +#endif + +/** Initialize sanei_bjnp. + * + * Call this before any other sanei_bjnp function. + */ +extern void sanei_bjnp_init (void); + +/** Find scanners responding to a BJNP broadcast. + * + * The function sanei_bjnp_attach is called for every device which has + * been found. + * Serial is the address of the scanner in human readable form of max + * SERIAL_MAX characters + * @param conf_devices list of pre-configures device URI's to attach + * @param attach attach function + * @param pixma_devices device informatio needed by attach function + * + * @return SANE_STATUS_GOOD - on success (even if no scanner was found) + */ + +#define SERIAL_MAX 16 + +extern SANE_Status +sanei_bjnp_find_devices (const char **conf_devices, +                         SANE_Status (*attach_bjnp) +			     (SANE_String_Const devname, +                               SANE_String_Const serial, +			       const struct pixma_config_t *cfg), +                         const struct pixma_config_t *const pixma_devices[]); + +/** Open a BJNP device. + * + * The device is opened by its name devname and the device number is + * returned in dn on success. + * + * Device names consist of an URI + * Where: + * method = bjnp + * hostname = resolvable name or IP-address + * port = 8612 for a bjnp scanner, 8610 for a mfnp device + * An example could look like this: bjnp://host.domain:8612 + * + * @param devname name of the device to open + * @param dn device number + * + * @return + * - SANE_STATUS_GOOD - on success + * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to + *   permissions + * - SANE_STATUS_INVAL - on every other error + */ +extern SANE_Status sanei_bjnp_open (SANE_String_Const devname, SANE_Int * dn); + +/** Close a BJNP device. + * + * @param dn device number + */ + +extern void sanei_bjnp_close (SANE_Int dn); + +/** Activate a BJNP device connection + * + *  @param dn device number + */ + +extern SANE_Status sanei_bjnp_activate (SANE_Int dn); + +/** De-activate a BJNP device connection + * + * @param dn device number + */ + +extern SANE_Status sanei_bjnp_deactivate (SANE_Int dn); + +/** Set the libbjnp timeout for bulk and interrupt reads. + * + * @param devno device number + * @param timeout the new timeout in ms + */ +extern void sanei_bjnp_set_timeout (SANE_Int devno, SANE_Int timeout); + +/** Check if sanei_bjnp_set_timeout() is available. + */ +#define HAVE_SANEI_BJNP_SET_TIMEOUT + +/** Initiate a bulk transfer read. + * + * Read up to size bytes from the device to buffer. After the read, size + * contains the number of bytes actually read. + * + * @param dn device number + * @param buffer buffer to store read data in + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_EOF - if zero bytes have been read + * - SANE_STATUS_IO_ERROR - if an error occured during the read + * - SANE_STATUS_INVAL - on every other error + * + */ +extern SANE_Status +sanei_bjnp_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size); + +/** Initiate a bulk transfer write. + * + * Write up to size bytes from buffer to the device. After the write size + * contains the number of bytes actually written. + * + * @param dn device number + * @param buffer buffer to write to device + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_IO_ERROR - if an error occured during the write + * - SANE_STATUS_INVAL - on every other error + */ +extern SANE_Status +sanei_bjnp_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size); + +/** Initiate a interrupt transfer read. + * + * Read up to size bytes from the interrupt endpoint from the device to + * buffer. After the read, size contains the number of bytes actually read. + * + * @param dn device number + * @param buffer buffer to store read data in + * @param size size of the data + * + * @return + * - SANE_STATUS_GOOD - on succes + * - SANE_STATUS_EOF - if zero bytes have been read + * - SANE_STATUS_IO_ERROR - if an error occured during the read + * - SANE_STATUS_INVAL - on every other error + * + */ + +extern SANE_Status +sanei_bjnp_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size); + +/*------------------------------------------------------*/ +#endif /* sanei_bjnp_h */ diff --git a/backend/pixma/pixma_bjnp_private.h b/backend/pixma/pixma_bjnp_private.h new file mode 100644 index 0000000..edfb330 --- /dev/null +++ b/backend/pixma/pixma_bjnp_private.h @@ -0,0 +1,384 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2008 by Louis Lagendijk + +   This file is part of the SANE package. + +   Data structures and definitions for +   bjnp backend for the Common UNIX Printing System (CUPS). + +   These coded instructions, statements, and computer programs are the +   property of Louis Lagendijk and are protected by Federal copyright +   law.  Distribution and use rights are outlined in the file "LICENSE.txt" +   "LICENSE" which should have been included with this file.  If this +   file is missing or damaged, see the license at "http://www.cups.org/". + +   This file is subject to the Apple OS-Developed Software exception. + +   SANE 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. + +   SANE 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 sane; see the file COPYING.  If not, write to the Free +   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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. +*/ + +/* + *  BJNP definitions + */ + +/* selection of options */ +/* This works now, disable when it gives you problems */ +#define PIXMA_BJNP_USE_STATUS 1 + +/* sizes */ + +#define BJNP_PRINTBUF_MAX 1400		/* size of printbuffer */ +#define BJNP_CMD_MAX 2048		/* size of BJNP response buffer */ +#define BJNP_RESP_MAX 2048		/* size of BJNP response buffer */ +#define BJNP_SOCK_MAX 256		/* maximum number of open sockets */ +#define BJNP_MODEL_MAX 64		/* max allowed size for make&model */ +#define BJNP_STATUS_MAX 256		/* max size for status string */ +#define BJNP_IEEE1284_MAX 1024		/* max. allowed size of IEEE1284 id */ +#define BJNP_METHOD_MAX 16		/* max length of method */ +#define BJNP_HOST_MAX 128       	/* max length of hostname or address */ +#define BJNP_PORT_MAX 64		/* max length of port string */ +#define BJNP_ARGS_MAX 128		/* max length of argument string */ +#define BJNP_SERIAL_MAX 16		/* maximum length of serial number */ +#define BJNP_NO_DEVICES 16		/* max number of open devices */ +#define BJNP_SCAN_BUF_MAX 65536		/* size of scanner data intermediate buffer */ +#define BJNP_BLOCKSIZE_START 512	/* startsize for last block detection */ + +/* timers */ +#define BJNP_BROADCAST_INTERVAL 10 	/* ms between broadcasts */ +#define BJNP_BC_RESPONSE_TIMEOUT 500  	/* waiting time for broadc. responses */ +#define BJNP_TIMEOUT_DEFAULT 10000	/* minimum tiemout value for network operations */ +#define BJNP_TIMEOUT_TCP_CONNECT 2000   /* timeout for tcp connect attempts in ms */ +#define BJNP_USLEEP_MS 1000          	/* sleep for 1 msec */ +#define BJNP_TCP_CONNECT_INTERVAL 100   /* TCP retry interval in ms */ + +/* retries */ +#define BJNP_MAX_SELECT_ATTEMPTS 3   	/* max nr of retries on select (EINTR) */ +#define BJNP_MAX_BROADCAST_ATTEMPTS 2	/* number of broadcast packets to be sent */ +#define BJNP_UDP_RETRY_MAX 3		/* max nt of retries on a udp command */ + +#define bjnp_dbg DBG +#include "../include/sane/sanei_debug.h" + +/* loglevel definitions */ + +#define LOG_CRIT 0 +#define LOG_NOTICE 1 +#define LOG_INFO 2 +#define LOG_DEBUG 3 +#define LOG_DEBUG2 4 +#define LOG_DEBUG3 5 + +#define BJNP_RESTART_POLL -1 + +/*************************************/ +/* BJNP protocol related definitions */ +/*************************************/ + +/* port numbers */ +typedef enum bjnp_port_e +{ +  MFNP_PORT_SCAN = 8610, +  BJNP_PORT_PRINT = 8611, +  BJNP_PORT_SCAN = 8612, +  BJNP_PORT_3 = 8613, +  BJNP_PORT_4 = 8614 +} bjnp_port_t; + +typedef enum +{ +  PROTOCOL_BJNP = 0, +  PROTOCOL_MFNP = 1, +  PROTOCOL_NONE =2 +} bjnp_protocol_t; + +typedef struct +{ +  bjnp_protocol_t protocol_version; +  int default_port; +  char * proto_string; +  char * method_string; +} bjnp_protocol_defs_t; + +bjnp_protocol_defs_t bjnp_protocol_defs[] = +{ +  {PROTOCOL_BJNP, BJNP_PORT_SCAN,"BJNP", "bjnp"}, +  {PROTOCOL_MFNP, MFNP_PORT_SCAN,"MFNP", "mfnp"}, +  {PROTOCOL_NONE, -1, NULL, NULL} +}; + +/* commands */ +typedef enum bjnp_cmd_e +{ +  CMD_UDP_DISCOVER = 0x01,	/* discover if service type is listening at this port */ +  CMD_UDP_START_SCAN = 0x02,	/* start scan pressed, sent from scanner to 224.0.0.1 */ +  CMD_UDP_JOB_DETAILS = 0x10,	/* send print/ scanner job owner details */ +  CMD_UDP_CLOSE = 0x11,		/* request connection closure */ +  CMD_UDP_GET_STATUS = 0x20,	/* get printer status  */ +  CMD_TCP_REQ = 0x20,		/* read data from device */ +  CMD_TCP_SEND = 0x21,		/* send data to device */ +  CMD_UDP_GET_ID = 0x30,	/* get printer identity */ +  CMD_UDP_POLL = 0x32		/* poll scanner for button status */ +} bjnp_cmd_t; + +/* command type */ + +typedef enum uint8_t +{ +  BJNP_CMD_PRINT = 0x1,		/* printer command */ +  BJNP_CMD_SCAN = 0x2,		/* scanner command */ +  BJNP_RES_PRINT = 0x81,	/* printer response */ +  BJNP_RES_SCAN = 0x82		/* scanner response */ +} bjnp_cmd_type_t; + +/***************************/ +/* BJNP protocol structure */ +/***************************/ + +/* The common protocol header */ + +struct  __attribute__ ((__packed__)) BJNP_command +{ +  char BJNP_id[4];		/* string: BJNP */ +  uint8_t dev_type;		/* 1 = printer, 2 = scanner */ +                                /* responses have MSB set */ +  uint8_t cmd_code;		/* command code/response code */ +  int16_t unknown1;		/* unknown, always 0? */ +  int16_t seq_no;		/* sequence number */ +  uint16_t session_id;		/* session id for printing */ +  uint32_t payload_len;		/* length of command buffer */ +}; + +/* Layout of the init response buffer */ + +struct  __attribute__ ((__packed__)) DISCOVER_RESPONSE +{ +  struct BJNP_command response;	/* reponse header */ +  char unknown1[4];		/* 00 01 08 00 */ +  char mac_len;			/* length of mac address */ +  char addr_len;		/* length of address field */ +  unsigned char mac_addr[6];	/* printers mac address */ +  union  { +    struct __attribute__ ((__packed__)) { +       unsigned char ipv4_addr[4]; +     } ipv4; +     struct  __attribute__ ((__packed__)) { +       unsigned char ipv6_addr_1[16]; +       unsigned char ipv6_addr_2[16]; +     } ipv6; +  } addresses; +}; + +/* layout of payload for the JOB_DETAILS command */ + +struct  __attribute__ ((__packed__)) JOB_DETAILS +{ +  struct BJNP_command cmd;	/* command header */ +  char unknown[8];		/* don't know what these are for */ +  char hostname[64];		/* hostname of sender */ +  char username[64];		/* username */ +  char jobtitle[256];		/* job title */ +}; + +/* layout of the poll command, not everything is complete */ + +struct  __attribute__ ((__packed__)) POLL_DETAILS +{ +  struct BJNP_command cmd;      /* command header */ +  uint16_t type;                /* 0, 1, 2 or 5 */ +                                /* 05 = reset status */ +  union { +    struct  __attribute__ ((__packed__)) { +	char empty0[78];	/* type 0 has only 0 */ +      } type0;			/* length = 80 */ + +    struct __attribute__ ((__packed__)) { +      char empty1[6];		/* 0 */ +      char user_host[64];       /* unicode user <space> <space> hostname */ +      uint64_t emtpy2;		/* 0 */ +    } type1;			/* length = 80 */ + +    struct __attribute__ ((__packed__)) { +      uint16_t empty_1;         /* 00 00 */ +      uint32_t dialog;          /* constant dialog id, from previous response */ +      char user_host[64];       /* unicode user <space> <space> hostname */ +      uint32_t unknown_1;	/* 00 00 00 14 */ +      uint32_t empty_2[5];      /* only 0 */ +      uint32_t unknown_2;	/* 00 00 00 10 */ +      char ascii_date[16];      /* YYYYMMDDHHMMSS  only for type 2 */ +    } type2;			/* length = 116 */ + +    struct __attribute__ ((__packed__)) { +      uint16_t empty_1;         /* 00 00 */ +      uint32_t dialog;          /* constant dialog id, from previous response */ +      char user_host[64];       /* unicode user <space> <space> hostname */ +      uint32_t unknown_1;	/* 00 00 00 14 */ +      uint32_t key;		/* copied from key field in status msg */ +      uint32_t unknown_3[5];    /* only 0 */ +    } type5;			/* length = 100 */ + +  } extensions; +}; + +/* the poll response layout */ + +struct  __attribute__ ((__packed__)) POLL_RESPONSE +{ +  struct BJNP_command cmd;	/* command header */ + +  unsigned char result[4];	/* unknown stuff, result[2] = 80 -> status is available*/ +                                /* result[8] is dialog, size? */ +  uint32_t dialog;		/* to be returned in next request */ +  uint32_t unknown_2;		/* returns the 00 00 00 14 from unknown_2 in request */ +  uint32_t key;			/* to be returned in type 5 status reset */ +  unsigned char status[20];	/* interrupt status */ +}; + +/* Layout of ID and status responses */ + +struct  __attribute__ ((__packed__)) IDENTITY +{ +  struct BJNP_command cmd; +  union  __attribute__ ((__packed__)) +    { +      struct __attribute__ ((__packed__)) payload_s +        { +          uint16_t id_len;		/* length of identity */ +          char id[BJNP_IEEE1284_MAX];	/* identity */ +        } bjnp; +      struct __attribute__ ((__packed__)) mfnp +        { +          char id[BJNP_IEEE1284_MAX]; +         } mfnp; +    } payload; +}; + + +/* response to TCP print command */ + +struct  __attribute__ ((__packed__)) SCAN_BUF +{ +  struct BJNP_command cmd; +  char scan_data[65536]; +}; + +/**************************/ +/* Local enum definitions */ +/**************************/ + +typedef enum bjnp_paper_status_e +{ +  BJNP_PAPER_UNKNOWN = -1, +  BJNP_PAPER_OK = 0, +  BJNP_PAPER_OUT = 1 +} bjnp_paper_status_t; + +typedef enum +{ +  BJNP_STATUS_GOOD, +  BJNP_STATUS_INVAL, +  BJNP_STATUS_ALREADY_ALLOCATED +} BJNP_Status; + +/* button polling */ + +typedef enum +{ +  BJNP_POLL_STOPPED = 0, +  BJNP_POLL_STARTED = 1, +  BJNP_POLL_STATUS_RECEIVED = 2 +} BJNP_polling_status_e; + +typedef union +{ +  struct sockaddr_storage storage; +  struct sockaddr addr; +  struct sockaddr_in ipv4; +  struct sockaddr_in6 ipv6; +} bjnp_sockaddr_t; + +typedef enum +{ +  BJNP_ADDRESS_IS_LINK_LOCAL = 0, +  BJNP_ADDRESS_IS_GLOBAL = 1, +  BJNP_ADDRESS_HAS_FQDN = 2 +} bjnp_address_type_t; + + +/* + * Device information for opened devices + */ + +typedef struct device_s +{ +  int open;			/* connection to scanner is opened */ + +  /* protocol version */ +  int protocol; +  char *protocol_string; + +  /* sockets */ + +  int tcp_socket;		/* open tcp socket for communcation to scannner */ +  int16_t serial;		/* sequence number of command */ + +  /* communication state */ + +  int session_id;		/* session id used in bjnp protocol for TCP packets */ +  int last_cmd;			/* last command sent */ + +  /* TCP bulk read state information */ + +  size_t blocksize;		/* size of (TCP) blocks returned by the scanner */ +  size_t scanner_data_left;	/* TCP data left from last read request */ +  char last_block;		/* last TCP read command was shorter than blocksize */ + +  /* device information */ +  char mac_address[BJNP_HOST_MAX]; + 		 		/* mac-address, used as device serial no */ +  bjnp_sockaddr_t * addr;	/* ip-address of the scanner */ +  int address_level;		/* link local, public or has a FQDN */ +  int bjnp_scanner_timeout;	/* timeout (msec) for next poll command */ +  int bjnp_ip_timeout;		/* device specific min timeout for the IP-protocol */ + +#ifdef PIXMA_BJNP_USE_STATUS +  /* polling state information */ + +  char polling_status;		/* status polling ongoing */ +  uint32_t dialog;		/* poll dialog */ +  uint32_t status_key;		/* key of last received status message */ +#endif +} bjnp_device_t; diff --git a/backend/pixma/pixma_common.c b/backend/pixma/pixma_common.c new file mode 100644 index 0000000..7b7ecec --- /dev/null +++ b/backend/pixma/pixma_common.c @@ -0,0 +1,1187 @@ +/* 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h>		/* pow(C90) */ + +#include <sys/time.h>		/* gettimeofday(4.3BSD) */ +#include <unistd.h>		/* usleep */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + +#include "../include/sane/sanei_usb.h" +#include "../include/sane/sane.h" + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +extern const pixma_config_t pixma_mp150_devices[]; +extern const pixma_config_t pixma_mp750_devices[]; +extern const pixma_config_t pixma_mp730_devices[]; +extern const pixma_config_t pixma_mp800_devices[]; +extern const pixma_config_t pixma_iclass_devices[]; + +static const pixma_config_t *const pixma_devices[] = { +  pixma_mp150_devices, +  pixma_mp750_devices, +  pixma_mp730_devices, +  pixma_mp800_devices, +  pixma_iclass_devices, +  NULL +}; + +static pixma_t *first_pixma = NULL; +static time_t tstart_sec = 0; +static uint32_t tstart_usec = 0; +static int debug_level = 1; + + +#ifndef NDEBUG + +static void +u8tohex (uint8_t x, char *str) +{ +  static const char hdigit[16] = +    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', +    'e', 'f' +    }; +  str[0] = hdigit[(x >> 4) & 0xf]; +  str[1] = hdigit[x & 0xf]; +  str[2] = '\0'; +} + +static void +u32tohex (uint32_t x, char *str) +{ +  u8tohex (x >> 24, str); +  u8tohex (x >> 16, str + 2); +  u8tohex (x >> 8, str + 4); +  u8tohex (x, str + 6); +} + +void +pixma_hexdump (int level, const void *d_, unsigned len) +{ +  const uint8_t *d = (const uint8_t *) (d_); +  unsigned ofs, c, plen; +  char line[100];		/* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */ + +  if (level > debug_level) +    return; +  if (level == debug_level) +    /* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */ +    plen = (len > 64) ? 32: len; +  else +    plen = len; +  ofs = 0; +  while (ofs < plen) +    { +      char *p; +      line[0] = ' '; +      u32tohex (ofs, line + 1); +      line[9] = ':'; +      p = line + 10; +      for (c = 0; c != 16 && (ofs + c) < plen; c++) +        { +          u8tohex (d[ofs + c], p); +          p[2] = ' '; +          p += 3; +          if (c == 7) +            { +              p[0] = ' '; +              p++; +            } +        } +      p[0] = '\0'; +      pixma_dbg (level, "%s\n", line); +      ofs += c; +    } +  if (len > plen) +    pixma_dbg(level, "......\n"); +} + +static void +time2str (char *buf, unsigned size) +{ +  time_t sec; +  uint32_t usec; + +  pixma_get_time (&sec, &usec); +  sec -= tstart_sec; +  if (usec >= tstart_usec) +    { +      usec -= tstart_usec; +    } +  else +    { +      usec = 1000000 + usec - tstart_usec; +      sec--; +    } +  snprintf (buf, size, "%lu.%03u", (unsigned long) sec, +	    (unsigned) (usec / 1000)); +} + +void +pixma_dump (int level, const char *type, const void *data, int len, +	    int size, int max) +{ +  int actual_len, print_len; +  char buf[20]; + +  if (level > debug_level) +    return; +  if (debug_level >= 20) +    max = -1;			/* dump every bytes */ + +  time2str (buf, sizeof (buf)); +  pixma_dbg (level, "%s T=%s len=%d\n", type, buf, len); + +  actual_len = (size >= 0) ? size : len; +  print_len = (max >= 0 && max < actual_len) ? max : actual_len; +  if (print_len >= 0) +    { +      pixma_hexdump (level, data, print_len); +      if (print_len < actual_len) +	pixma_dbg (level, " ...\n"); +    } +  if (len < 0) +    pixma_dbg (level, "  ERROR: %s\n", pixma_strerror (len)); +  pixma_dbg (level, "\n"); +} + + +#endif /* NDEBUG */ + +/* NOTE: non-reentrant */ +const char * +pixma_strerror (int error) +{ +  static char buf[50]; + +  /* TODO: more human friendly messages */ +  switch (error) +    { +    case PIXMA_EIO: +      return "EIO"; +    case PIXMA_ENODEV: +      return "ENODEV"; +    case PIXMA_EACCES: +      return "EACCES"; +    case PIXMA_ENOMEM: +      return "ENOMEM"; +    case PIXMA_EINVAL: +      return "EINVAL"; +    case PIXMA_EBUSY: +      return "EBUSY"; +    case PIXMA_ECANCELED: +      return "ECANCELED"; +    case PIXMA_ENOTSUP: +      return "ENOTSUP"; +    case PIXMA_ETIMEDOUT: +      return "ETIMEDOUT"; +    case PIXMA_EPROTO: +      return "EPROTO"; +    case PIXMA_EPAPER_JAMMED: +      return "EPAPER_JAMMED"; +    case PIXMA_ECOVER_OPEN: +      return "ECOVER_OPEN"; +    case PIXMA_ENO_PAPER: +      return "ENO_PAPER"; +    case PIXMA_EOF: +      return "EEOF"; +    } +  snprintf (buf, sizeof (buf), "EUNKNOWN:%d", error); +  return buf; +} + +void +pixma_set_debug_level (int level) +{ +  debug_level = level; +} + +void +pixma_set_be16 (uint16_t x, uint8_t * buf) +{ +  buf[0] = x >> 8; +  buf[1] = x; +} + +void +pixma_set_be32 (uint32_t x, uint8_t * buf) +{ +  buf[0] = x >> 24; +  buf[1] = x >> 16; +  buf[2] = x >> 8; +  buf[3] = x; +} + +uint16_t +pixma_get_be16 (const uint8_t * buf) +{ +  return ((uint16_t) buf[0] << 8) | buf[1]; +} + +uint32_t +pixma_get_be32 (const uint8_t * buf) +{ +  return ((uint32_t) buf[0] << 24) + ((uint32_t) buf[1] << 16) + +    ((uint32_t) buf[2] << 8) + buf[3]; +} + +uint8_t +pixma_sum_bytes (const void *data, unsigned len) +{ +  const uint8_t *d = (const uint8_t *) data; +  unsigned i, sum = 0; +  for (i = 0; i != len; i++) +    sum += d[i]; +  return sum; +} + +void +pixma_sleep (unsigned long usec) +{ +  usleep (usec); +} + +void +pixma_get_time (time_t * sec, uint32_t * usec) +{ +  struct timeval tv; +  gettimeofday (&tv, NULL); +  if (sec) +    *sec = tv.tv_sec; +  if (usec) +    *usec = tv.tv_usec; +} + +/* convert 24/48 bit RGB to 8/16 bit ir + * + * Formular: g = R + *           drop G + B + * + * sptr: source color scale buffer + * gptr: destination gray scale buffer + * c == 3: 24 bit RGB -> 8 bit ir + * c == 6: 48 bit RGB -> 16 bit ir + */ +uint8_t * +pixma_r_to_ir (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c) +{ +  unsigned i; + +  /* PDBG (pixma_dbg (4, "*pixma_rgb_to_ir*****\n")); */ + +  for (i = 0; i < w; i++) +    { +      *gptr++ = *sptr++; +      if (c == 6) *gptr++ = *sptr++;            /* 48 bit RGB: high byte */ +      sptr += (c == 6) ? 4 : 2;                 /* drop G + B */ +    } +  return gptr; +} + +/* convert 24/48 bit RGB to 8/16 bit grayscale + * + * Formular: g = (R + G + B) / 3 + * + * sptr: source color scale buffer + * gptr: destination gray scale buffer + * c == 3: 24 bit RGB -> 8 bit gray + * c == 6: 48 bit RGB -> 16 bit gray + */ +uint8_t * +pixma_rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c) +{ +  unsigned i, j, g; + +  /* PDBG (pixma_dbg (4, "*pixma_rgb_to_gray*****\n")); */ + +  for (i = 0; i < w; i++) +    { +      for (j = 0, g = 0; j < 3; j++) +        { +          g += *sptr++; +          if (c == 6) g += (*sptr++ << 8);      /* 48 bit RGB: high byte */ +        } + +      g /= 3;                                   /* 8 or 16 bit gray */ +      *gptr++ = g; +      if (c == 6) *gptr++ = (g >> 8);           /* 16 bit gray: high byte */ +    } +  return gptr; +} + +/** + * This code was taken from the genesys backend + * uses threshold and threshold_curve to control software binarization + * @param sp    device set up for the scan + * @param dst   pointer where to store result + * @param src   pointer to raw data + * @param width width of the processed line + * @param c     1 for 1-channel single-byte data, + *              3 for 3-channel single-byte data, + *              6 for double-byte data + * */ +uint8_t * +pixma_binarize_line(pixma_scan_param_t * sp, uint8_t * dst, uint8_t * src, unsigned width, unsigned c) +{ +  unsigned j, x, windowX, sum = 0; +  unsigned threshold; +  unsigned offset, addCol; +  int dropCol, offsetX; +  unsigned char mask; +  uint8_t min, max; + +  /* PDBG (pixma_dbg (4, "*pixma_binarize_line***** src = %u, dst = %u, width = %u, c = %u, threshold = %u, threshold_curve = %u *****\n", +                      src, dst, width, c, sp->threshold, sp->threshold_curve)); */ + +  /* 16 bit grayscale not supported */ +  if (c == 6) +    { +      PDBG (pixma_dbg (1, "*pixma_binarize_line***** Error: 16 bit grayscale not supported\n")); +      return dst; +    } + +  /* first, color convert to grayscale */ +    if (c != 1) +      pixma_rgb_to_gray(dst, src, width, c); + +  /* second, normalize line */ +    min = 255; +    max = 0; +    for (x = 0; x < width; x++) +      { +        if (src[x] > max) +          { +            max = src[x]; +          } +        if (src[x] < min) +          { +            min = src[x]; +          } +      } + +    /* safeguard against dark or white areas */ +    if(min>80) +        min=0; +    if(max<80) +        max=255; +    for (x = 0; x < width; x++) +      { +        src[x] = ((src[x] - min) * 255) / (max - min); +      } + +  /* third, create sliding window, prefill the sliding sum */ +    /* ~1mm works best, but the window needs to have odd # of pixels */ +    windowX = (6 * sp->xdpi) / 150; +    if (!(windowX % 2)) +      windowX++; + +    /* to avoid conflicts with *dst start with offset */ +    offsetX = 1 + (windowX / 2) / 8; +    for (j = offsetX; j <= windowX; j++) +      sum += src[j]; +    /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** windowX = %u, startX = %u, sum = %u\n", +                     windowX, startX, sum)); */ + +  /* fourth, walk the input buffer, output bits */ +    for (j = 0; j < width; j++) +      { +        /* output image location */ +        offset = j % 8; +        mask = 0x80 >> offset; +        threshold = sp->threshold; + +        /* move sum/update threshold only if there is a curve */ +        if (sp->threshold_curve) +          { +            addCol = j + windowX / 2; +            dropCol = addCol - windowX; + +            if (dropCol >= offsetX && addCol < width) +              { +                sum += src[addCol]; +                sum -= (sum < src[dropCol] ? sum : src[dropCol]);       /* no negative sum */ +              } +            threshold = sp->lineart_lut[sum / windowX]; +            /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** addCol = %u, dropCol = %d, sum = %u, windowX = %u, lut-element = %d, threshold = %u\n", +                             addCol, dropCol, sum, windowX, sum/windowX, threshold)); */ +          } + +        /* lookup threshold */ +        if (src[j] > threshold) +            *dst &= ~mask;      /* white */ +        else +            *dst |= mask;       /* black */ + +        if (offset == 7) +            dst++; +      } + +  /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** ready: src = %u, dst = %u *****\n", src, dst)); */ + +  return dst; +} + +/** +   This code was taken from the genesys backend +   Function to build a lookup table (LUT), often +   used by scanners to implement brightness/contrast/gamma +   or by backends to speed binarization/thresholding + +   offset and slope inputs are -127 to +127 + +   slope rotates line around central input/output val, +   0 makes horizontal line + +       pos           zero          neg +       .       x     .             .  x +       .      x      .             .   x +   out .     x       .xxxxxxxxxxx  .    x +       .    x        .             .     x +       ....x.......  ............  .......x.... +            in            in            in + +   offset moves line vertically, and clamps to output range +   0 keeps the line crossing the center of the table + +       high           low +       .   xxxxxxxx   . +       . x            . +   out x              .          x +       .              .        x +       ............   xxxxxxxx.... +            in             in + +   out_min/max provide bounds on output values, +   useful when building thresholding lut. +   0 and 255 are good defaults otherwise. +  * */ +static SANE_Status +load_lut (unsigned char * lut, +  int in_bits, int out_bits, +  int out_min, int out_max, +  int slope, int offset) +{ +  int i, j; +  double shift, rise; +  int max_in_val = (1 << in_bits) - 1; +  int max_out_val = (1 << out_bits) - 1; +  unsigned char * lut_p = lut; + +  /* PDBG (pixma_dbg (4, "*load_lut***** start %d %d *****\n", slope, offset)); */ + +  /* slope is converted to rise per unit run: +   * first [-127,127] to [-1,1] +   * then multiply by PI/2 to convert to radians +   * then take the tangent (T.O.A) +   * then multiply by the normal linear slope +   * because the table may not be square, i.e. 1024x256*/ +  rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val; + +  /* line must stay vertically centered, so figure +   * out vertical offset at central input value */ +  shift = (double)max_out_val/2 - (rise*max_in_val/2); + +  /* convert the user offset setting to scale of output +   * first [-127,127] to [-1,1] +   * then to [-max_out_val/2,max_out_val/2]*/ +  shift += (double)offset / 127 * max_out_val / 2; + +  for(i=0;i<=max_in_val;i++){ +    j = rise*i + shift; + +    if(j<out_min){ +      j=out_min; +    } +    else if(j>out_max){ +      j=out_max; +    } + +    *lut_p=j; +    lut_p++; +  } + +  /* PDBG (pixma_dbg (4, "*load_lut***** finish *****\n")); */ +  /* PDBG (pixma_hexdump (4, lut, max_in_val+1)); */ + +  return SANE_STATUS_GOOD; +} + +int +pixma_map_status_errno (unsigned status) +{ +  switch (status) +    { +    case PIXMA_STATUS_OK: +      return 0; +    case PIXMA_STATUS_FAILED: +      return PIXMA_ECANCELED; +    case PIXMA_STATUS_BUSY: +      return PIXMA_EBUSY; +    default: +      return PIXMA_EPROTO; +    } +} + +int +pixma_check_result (pixma_cmdbuf_t * cb) +{ +  const uint8_t *r = cb->buf; +  unsigned header_len = cb->res_header_len; +  unsigned expected_reslen = cb->expected_reslen; +  int error; +  unsigned len; + +  if (cb->reslen < 0) +    return cb->reslen; + +  len = (unsigned) cb->reslen; +  if (len >= header_len) +    { +      error = pixma_map_status_errno (pixma_get_be16 (r)); +      if (expected_reslen != 0) +        { +          if (len == expected_reslen) +            { +              if (pixma_sum_bytes (r + header_len, len - header_len) != 0) +                 error = PIXMA_EPROTO; +            } +          else +            { +              /* This case will happen when a command cannot be completely +                 executed, e.g. because you press the cancel button. The +                 device will return only a header with PIXMA_STATUS_FAILED. */ +              if (len != header_len) +                 error = PIXMA_EPROTO; +            } +        } +    } +  else +    error = PIXMA_EPROTO; + +#ifndef NDEBUG +  if (error == PIXMA_EPROTO) +    { +      pixma_dbg (1, "WARNING: result len=%d expected %d\n", +		 len, cb->expected_reslen); +      pixma_hexdump (1, r, MIN (len, 64)); +    } +#endif +  return error; +} + +int +pixma_cmd_transaction (pixma_t * s, const void *cmd, unsigned cmdlen, +		       void *data, unsigned expected_len) +{ +  int error, tmo; + +  error = pixma_write (s->io, cmd, cmdlen); +  if (error != (int) cmdlen) +    { +      if (error >= 0) +        { +          /* Write timeout is too low? */ +          PDBG (pixma_dbg +          (1, "ERROR: incomplete write, %u out of %u written\n", +           (unsigned) error, cmdlen)); +          error = PIXMA_ETIMEDOUT; +        } +      return error; +    } + +  /* When you send the start_session command while the scanner optic is +     going back to the home position after the last scan session has been +     cancelled, you won't get the response before it arrives home. This takes +     about 5 seconds. If the last session was succeeded, the scanner will +     immediatly answer with PIXMA_STATUS_BUSY. + +     Is 8 seconds timeout enough? This affects ALL commands that use +     pixma_cmd_transaction(). Default value set in pixma_open(). */ +  tmo = s->rec_tmo; +  do +    { +      error = pixma_read (s->io, data, expected_len); +      if (error == PIXMA_ETIMEDOUT) +      { +        PDBG (pixma_dbg (2, "No response yet. Timed out in %d sec.\n", tmo)); + +#ifndef HAVE_SANEI_USB_SET_TIMEOUT +        /* 1s timeout +           Only needed, if sanei_usb_set_timeout() isn't available. +           pixma_read() has an internal timeout of 1 sec. */ +        pixma_sleep (1000000); +#endif +      } +    } +  while (error == PIXMA_ETIMEDOUT && --tmo != 0); +  if (error < 0) +    { +      PDBG (pixma_dbg (1, "WARNING: Error in response phase. cmd:%02x%02x\n", +		       ((const uint8_t *) cmd)[0], +		       ((const uint8_t *) cmd)[1])); +      PDBG (pixma_dbg (1,"  If the scanner hangs, reset it and/or unplug the " +	                       "USB cable.\n")); +    } +  return error;			/* length of the result packet or error */ +} + +uint8_t * +pixma_newcmd (pixma_cmdbuf_t * cb, unsigned cmd, +	      unsigned dataout, unsigned datain) +{ +  unsigned cmdlen = cb->cmd_header_len + dataout; +  unsigned reslen = cb->res_header_len + datain; + +  if (cmdlen > cb->size || reslen > cb->size) +    return NULL; +  memset (cb->buf, 0, cmdlen); +  cb->cmdlen = cmdlen; +  cb->expected_reslen = reslen; +  pixma_set_be16 (cmd, cb->buf); +  pixma_set_be16 (dataout + datain, cb->buf + cb->cmd_len_field_ofs); +  if (dataout != 0) +    return cb->buf + cb->cmd_header_len; +  else +    return cb->buf + cb->res_header_len; +} + +int +pixma_exec (pixma_t * s, pixma_cmdbuf_t * cb) +{ +  if (cb->cmdlen > cb->cmd_header_len) +    pixma_fill_checksum (cb->buf + cb->cmd_header_len, +			 cb->buf + cb->cmdlen - 1); +  cb->reslen = +    pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf, +			   cb->expected_reslen); +  return pixma_check_result (cb); +} + +int +pixma_exec_short_cmd (pixma_t * s, pixma_cmdbuf_t * cb, unsigned cmd) +{ +  pixma_newcmd (cb, cmd, 0, 0); +  return pixma_exec (s, cb); +} + +int +pixma_check_dpi (unsigned dpi, unsigned max) +{ +  /* valid dpi = 75 * 2^n */ +  unsigned temp = dpi / 75; +  if (dpi > max || dpi < 75 || 75 * temp != dpi || (temp & (temp - 1)) != 0) +    return PIXMA_EINVAL; +  return 0; +} + + +int +pixma_init (void) +{ +  PDBG (pixma_dbg (2, "pixma version %d.%d.%d\n", PIXMA_VERSION_MAJOR, +		   PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD)); +  PASSERT (first_pixma == NULL); +  if (tstart_sec == 0) +    pixma_get_time (&tstart_sec, &tstart_usec); +  return pixma_io_init (); +} + +void +pixma_cleanup (void) +{ +  while (first_pixma) +    pixma_close (first_pixma); +  pixma_io_cleanup (); +} + +int +pixma_open (unsigned devnr, pixma_t ** handle) +{ +  int error; +  pixma_t *s; +  const pixma_config_t *cfg; + +  *handle = NULL; +  cfg = pixma_get_device_config (devnr); +  if (!cfg) +    return PIXMA_EINVAL;	/* invalid devnr */ +  PDBG (pixma_dbg (2, "pixma_open(): %s\n", cfg->name)); + +  s = (pixma_t *) calloc (1, sizeof (s[0])); +  if (!s) +    return PIXMA_ENOMEM; +  s->next = first_pixma; +  first_pixma = s; + +  s->cfg = cfg; +  s->rec_tmo = 8;               /* set receive timeout to 8 seconds */ +  error = pixma_connect (devnr, &s->io); +  if (error < 0) +    { +      PDBG (pixma_dbg +	    (2, "pixma_connect() failed %s\n", pixma_strerror (error))); +      goto rollback; +    } +  strncpy (s->id, pixma_get_device_id (devnr), sizeof (s->id) - 1); +  s->ops = s->cfg->ops; +  s->scanning = 0; +  error = s->ops->open (s); +  if (error < 0) +    goto rollback; +  error = pixma_deactivate (s->io); +  if (error < 0) +    goto rollback; +  *handle = s; +  return 0; + +rollback: +  PDBG (pixma_dbg (2, "pixma_open() failed %s\n", pixma_strerror (error))); +  pixma_close (s); +  return error; +} + +void +pixma_close (pixma_t * s) +{ +  pixma_t **p; + +  if (!s) +    return; +  for (p = &first_pixma; *p && *p != s; p = &((*p)->next)) +    { +    } +  PASSERT (*p); +  if (!(*p)) +    return; +  PDBG (pixma_dbg (2, "pixma_close(): %s\n", s->cfg->name)); +  if (s->io) +    { +      if (s->scanning) +	{ +	  PDBG (pixma_dbg (3, "pixma_close(): scanning in progress, call" +			   " finish_scan()\n")); +	  s->ops->finish_scan (s); +	} +      s->ops->close (s); +      pixma_disconnect (s->io); +    } +  *p = s->next; +  free (s); +} + +int +pixma_scan (pixma_t * s, pixma_scan_param_t * sp) +{ +  int error; + +  error = pixma_check_scan_param (s, sp); +  if (error < 0) +    return error; + +  if (sp->mode == PIXMA_SCAN_MODE_LINEART) +    { +      load_lut(sp->lineart_lut, 8, 8, 50, 205, +               sp->threshold_curve, sp->threshold-127); +    } + +#ifndef NDEBUG +  pixma_dbg (3, "\n"); +  pixma_dbg (3, "pixma_scan(): start\n"); +  pixma_dbg (3, "  line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n", +	     sp->line_size, sp->image_size, sp->channels, sp->depth); +  pixma_dbg (3, "  dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n", +	     sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h); +  pixma_dbg (3, "  gamma_table=%p source=%d\n", sp->gamma_table, sp->source); +  pixma_dbg (3, "  threshold=%d threshold_curve=%d\n", sp->threshold, sp->threshold_curve); +  pixma_dbg (3, "  adf-wait=%d\n", sp->adf_wait); +  pixma_dbg (3, "  ADF page count: %d\n", sp->adf_pageid); +#endif + +  s->param = sp; +  s->cancel = 0; +  s->cur_image_size = 0; +  s->imagebuf.wptr = NULL; +  s->imagebuf.wend = NULL; +  s->imagebuf.rptr = NULL; +  s->imagebuf.rend = NULL; +  s->underrun = 0; +  error = s->ops->scan (s); +  if (error >= 0) +    { +      s->scanning = 1; +    } +  else +    { +      PDBG (pixma_dbg +	    (3, "pixma_scan() failed %s\n", pixma_strerror (error))); +    } + +  return error; +} + +static uint8_t * +fill_pixels (pixma_t * s, uint8_t * ptr, uint8_t * end, uint8_t value) +{ +  if (s->cur_image_size < s->param->image_size) +    { +      long n = s->param->image_size - s->cur_image_size; +      if (n > (end - ptr)) +	n = end - ptr; +      memset (ptr, value, n); +      s->cur_image_size += n; +      ptr += n; +    } +  return ptr; +} + +int +pixma_read_image (pixma_t * s, void *buf, unsigned len) +{ +  int result; +  pixma_imagebuf_t ib; + +  if (!s->scanning) +    return 0; +  if (s->cancel) +    { +      result = PIXMA_ECANCELED; +      goto cancel; +    } + +  ib = s->imagebuf;		/* get rptr and rend */ +  ib.wptr = (uint8_t *) buf; +  ib.wend = ib.wptr + len; + +  if (s->underrun) +    { +      if (s->cur_image_size < s->param->image_size) +        { +          ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff); +        } +      else +        { +          PDBG (pixma_dbg +          (3, "pixma_read_image(): completed (underrun detected)\n")); +          s->scanning = 0; +        } +      return ib.wptr - (uint8_t *) buf; +    } + +  while (ib.wptr != ib.wend) +    { +      if (ib.rptr == ib.rend) +        { +          ib.rptr = ib.rend = NULL; +          result = s->ops->fill_buffer (s, &ib); +          if (result < 0) +            goto cancel; +          if (result == 0) +            {			/* end of image? */ +              s->ops->finish_scan (s); +              if ((s->cur_image_size != s->param->image_size) && !s->param->mode_jpeg) +                { +                  pixma_dbg (1, "WARNING:image size mismatches\n"); +                  pixma_dbg (1, +                       "    %"PRIu64" expected (%d lines) but %"PRIu64" received (%"PRIu64" lines)\n", +                       s->param->image_size, s->param->h, +                       s->cur_image_size, +                       s->cur_image_size / s->param->line_size); +                  if ((s->cur_image_size % s->param->line_size) != 0) +                    { +                      pixma_dbg (1, +                     "BUG:received data not multiple of line_size\n"); +                    } +                } +              if ((s->cur_image_size < s->param->image_size) && !s->param->mode_jpeg) +                { +                  s->underrun = 1; +                  ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff); +                } +              else +                { +                  PDBG (pixma_dbg (3, "pixma_read_image():completed\n")); +                  s->scanning = 0; +                } +              break; +            } +          s->cur_image_size += result; + +          PASSERT (s->cur_image_size <= s->param->image_size); +        } +      if (ib.rptr) +        { +          unsigned count = MIN (ib.rend - ib.rptr, ib.wend - ib.wptr); +          memcpy (ib.wptr, ib.rptr, count); +          ib.rptr += count; +          ib.wptr += count; +        } +    } +  s->imagebuf = ib;		/* store rptr and rend */ +  return ib.wptr - (uint8_t *) buf; + +cancel: +  s->ops->finish_scan (s); +  s->scanning = 0; +  if (result == PIXMA_ECANCELED) +    { +      PDBG (pixma_dbg (3, "pixma_read_image(): cancelled by %sware\n", +		       (s->cancel) ? "soft" : "hard")); +    } +  else +    { +      PDBG (pixma_dbg (3, "pixma_read_image() failed %s\n", +		       pixma_strerror (result))); +    } +  return result; +} + +void +pixma_cancel (pixma_t * s) +{ +  s->cancel = 1; +} + +int +pixma_enable_background (pixma_t * s, int enabled) +{ +  return pixma_set_interrupt_mode (s->io, enabled); +} + +int +pixma_activate_connection(pixma_t * s) +{ +  return pixma_activate (s->io); +} + +int +pixma_deactivate_connection(pixma_t * s) +{ +  return pixma_deactivate (s->io); +} + +uint32_t +pixma_wait_event (pixma_t * s, int timeout /*ms */ ) +{ +  unsigned events; + +  if (s->events == PIXMA_EV_NONE && s->ops->wait_event) +    s->ops->wait_event (s, timeout); +  events = s->events; +  s->events = PIXMA_EV_NONE; +  return events; +} + +#define CLAMP2(x,w,min,max,dpi) do {		\ +    unsigned m = (max) * (dpi) / 75;		\ +    x = MIN(x, m - min);			\ +    w = MIN(w, m - x);				\ +    if (w < min)  w = min;			\ +} while(0) + +int +pixma_check_scan_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  unsigned cfg_xdpi; + +  if (!(sp->channels == 3 || +	(sp->channels == 1 && (s->cfg->cap & PIXMA_CAP_GRAY) != 0))) +    return PIXMA_EINVAL; + +  /* flatbed: use s->cfg->xdpi +   * TPU/ADF: use s->cfg->adftpu_max_dpi, if configured with dpi value */ +  cfg_xdpi = ((sp->source == PIXMA_SOURCE_FLATBED +               || s->cfg->adftpu_max_dpi == 0) ? s->cfg->xdpi +                                               : s->cfg->adftpu_max_dpi); + +  if (pixma_check_dpi (sp->xdpi, cfg_xdpi) < 0 || +      pixma_check_dpi (sp->ydpi, s->cfg->ydpi) < 0) +    return PIXMA_EINVAL; + +  /* xdpi must be equal to ydpi except that +     xdpi = max_xdpi and ydpi = max_ydpi. */ +  if (!(sp->xdpi == sp->ydpi || +	(sp->xdpi == cfg_xdpi && sp->ydpi == s->cfg->ydpi))) +    return PIXMA_EINVAL; + +  if (s->ops->check_param (s, sp) < 0) +    return PIXMA_EINVAL; + +  /* FIXME: I assume the same minimum width and height for every model. +   * new scanners need minimum 16 px height +   * minimum image size: 16 px x 16 px */ +  CLAMP2 (sp->x, sp->w, 16, s->cfg->width, sp->xdpi); +  CLAMP2 (sp->y, sp->h, 16, s->cfg->height, sp->ydpi); + +  switch (sp->source) +    { +    case PIXMA_SOURCE_FLATBED: +      break; + +    case PIXMA_SOURCE_TPU: +      if ((s->cfg->cap & PIXMA_CAP_TPU) != PIXMA_CAP_TPU) +        { +          sp->source = PIXMA_SOURCE_FLATBED; +          PDBG (pixma_dbg +          (1, "WARNING: TPU unsupported, fallback to flatbed.\n")); +        } +      break; + +    case PIXMA_SOURCE_ADF: +      if ((s->cfg->cap & PIXMA_CAP_ADF) != PIXMA_CAP_ADF) +        { +          sp->source = PIXMA_SOURCE_FLATBED; +          PDBG (pixma_dbg +          (1, "WARNING: ADF unsupported, fallback to flatbed.\n")); +        } +      break; + +    case PIXMA_SOURCE_ADFDUP: +      if ((s->cfg->cap & PIXMA_CAP_ADFDUP) != PIXMA_CAP_ADFDUP) +        { +          if (s->cfg->cap & PIXMA_CAP_ADF) +            { +              sp->source = PIXMA_SOURCE_ADF; +            } +          else +            { +              sp->source = PIXMA_SOURCE_FLATBED; +            } +          PDBG (pixma_dbg +          (1, "WARNING: ADF duplex unsupported, fallback to %d.\n", +           sp->source)); +        } +      break; +    } + +  if (sp->depth == 0) +    sp->depth = 8; +  if ((sp->depth % 8) != 0 && sp->depth != 1) +    return PIXMA_EINVAL; + +  sp->line_size = 0; + +  if (s->ops->check_param (s, sp) < 0) +    return PIXMA_EINVAL; + +  if (sp->line_size == 0) +    sp->line_size = sp->depth / 8 * sp->channels * sp->w; +  sp->image_size = sp->line_size * sp->h; + +  /* image_size for software lineart is counted in bits */ +  if (sp->software_lineart == 1) +    sp->image_size /= 8; +  return 0; +} + +const char * +pixma_get_string (pixma_t * s, pixma_string_index_t i) +{ +  switch (i) +    { +    case PIXMA_STRING_MODEL: +      return s->cfg->name; +    case PIXMA_STRING_ID: +      return s->id; +    case PIXMA_STRING_LAST: +      return NULL; +    } +  return NULL; +} + +const pixma_config_t * +pixma_get_config (pixma_t * s) +{ +  return s->cfg; +} + +void +pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n) +{ +  int i; +  double r_gamma = 1.0 / gamma; +  double out_scale = 255.0; +  double in_scale = 1.0 / (n - 1); + +  for (i = 0; (unsigned) i != n; i++) +    { +      table[i] = (int) (out_scale * pow (i * in_scale, r_gamma) + 0.5); +    } +} + +int +pixma_find_scanners (const char **conf_devices, SANE_Bool local_only) +{ +  return pixma_collect_devices (conf_devices, pixma_devices, local_only); +} + +const char * +pixma_get_device_model (unsigned devnr) +{ +  const pixma_config_t *cfg = pixma_get_device_config (devnr); +  return (cfg) ? cfg->name : NULL; +} + + +int +pixma_get_device_status (pixma_t * s, pixma_device_status_t * status) +{ +  if (!status) +    return PIXMA_EINVAL; +  memset (status, 0, sizeof (*status)); +  return s->ops->get_status (s, status); +} diff --git a/backend/pixma/pixma_common.h b/backend/pixma/pixma_common.h new file mode 100644 index 0000000..c0ed4ba --- /dev/null +++ b/backend/pixma/pixma_common.h @@ -0,0 +1,232 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> +   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. + */ +#ifndef PIXMA_COMMON_H +#define PIXMA_COMMON_H + + +#include <time.h>		/* time_t */ +#include "pixma.h" + + +/*! \defgroup subdriver Subdriver Interface + *  \brief Subdriver interface. */ + +/*! \defgroup debug Debug utilities + *  \brief Debug utilities. */ + +#ifdef NDEBUG +# define PDBG(x)     do {} while(0) +# define PASSERT(x)  do {} while(0) +#else +# define PDBG(x) x +# define PASSERT(x) do {				\ +    if (!(x))						\ +	pixma_dbg(1, "ASSERT failed:%s:%d: "	\ +		     #x "\n", __FILE__, __LINE__);	\ +  } while(0) +#endif + + +#define PIXMA_STATUS_OK       0x0606 +#define PIXMA_STATUS_FAILED   0x1515 +#define PIXMA_STATUS_BUSY     0x1414 + +#define PIXMA_MAX_ID_LEN 30 + +/* These may have been defined elsewhere */ +#ifndef MIN +#define MIN(x,y) (((x) < (y)) ? (x):(y)) +#endif +#ifndef MAX +#define MAX(x,y) (((x) < (y)) ? (y):(x)) +#endif +#define ALIGN_SUP(x,n) (((x) + (n) - 1) / (n) * (n)) +#define ALIGN_INF(x,n) (((x) / (n)) * (n)) + +struct pixma_io_t; + +struct pixma_limits_t +{ +  unsigned xdpi, ydpi; +  unsigned width, height; +}; + +struct pixma_cmdbuf_t +{ +  unsigned cmd_header_len, res_header_len, cmd_len_field_ofs; +  unsigned expected_reslen, cmdlen; +  int reslen; +  unsigned size; +  uint8_t *buf; +}; + +struct pixma_imagebuf_t +{ +  uint8_t *wptr, *wend; +  const uint8_t *rptr, *rend; +}; + +struct pixma_t +{ +  pixma_t *next; +  struct pixma_io_t *io; +  const pixma_scan_ops_t *ops; +  pixma_scan_param_t *param; +  const pixma_config_t *cfg; +  char id[PIXMA_MAX_ID_LEN + 1]; +  int cancel;			/* NOTE: It can be set in a signal handler. */ +  uint32_t events; +  void *subdriver;		/* can be used by model driver. */ +  int rec_tmo;                  /* receive timeout [s] */ + +  /* private */ +  uint64_t cur_image_size; +  pixma_imagebuf_t imagebuf; +  unsigned scanning:1; +  unsigned underrun:1; +}; + +/** \addtogroup subdriver + * @{ */ +/** Scan operations for subdriver. */ +struct pixma_scan_ops_t +{ +    /** Allocate a data structure for the subdriver. It is called after the +     *  core driver connected to the scanner. The subdriver should reset the +     *  scanner to a known state in this function. */ +  int (*open) (pixma_t *); + +    /** Free resources allocated by the subdriver. Don't forget to send abort +     *  command to the scanner if it is scanning. */ +  void (*close) (pixma_t *); + +    /** Setup the scanner for scan parameters defined in \a s->param. */ +  int (*scan) (pixma_t * s); + +    /** Fill a buffer with image data. The subdriver has two choices: +     * -# Fill the buffer pointed by ib->wptr directly and leave +     *    ib->rptr and ib->rend untouched. The length of the buffer is +     *    ib->wend - ib->wptr. It must update ib->wptr accordingly. +     * -# Update ib->rptr and ib->rend to point to the beginning and +     *    the end of the internal buffer resp. The length of the buffer +     *    is ib->rend - ib->rptr. This function is called again if +     *    and only if pixma_read_image() has copied the whole buffer. +     * +     * The subdriver must wait until there is at least one byte to read or +     * return 0 for the end of image. */ +  int (*fill_buffer) (pixma_t *, pixma_imagebuf_t * ib); + +    /** Cancel the scan operation if necessary and free resources allocated in +     *  scan(). */ +  void (*finish_scan) (pixma_t *); + +    /** [Optional] Wait for a user's event, e.g. button event. \a timeout is +     *  in milliseconds. If an event occured before it's timed out, flags in +     *  \a s->events should be set accordingly. +     *  \see PIXMA_EV_* */ +  void (*wait_event) (pixma_t * s, int timeout); + +    /** Check the scan parameters. The parameters can be adjusted if they are +     *  out of range, e.g. width > max_width. */ +  int (*check_param) (pixma_t *, pixma_scan_param_t *); + +    /** Read the device status. \see pixma_get_device_status() */ +  int (*get_status) (pixma_t *, pixma_device_status_t *); +}; + + +/** \name Funtions for read and write big-endian integer values */ +/**@{*/ +void pixma_set_be16 (uint16_t x, uint8_t * buf); +void pixma_set_be32 (uint32_t x, uint8_t * buf); +uint16_t pixma_get_be16 (const uint8_t * buf); +uint32_t pixma_get_be32 (const uint8_t * buf); +/**@}*/ + +/** \name Utility functions */ +/**@{*/ +uint8_t pixma_sum_bytes (const void *data, unsigned len); +int pixma_check_dpi (unsigned dpi, unsigned max); +void pixma_sleep (unsigned long usec); +void pixma_get_time (time_t * sec, uint32_t * usec); +uint8_t * pixma_r_to_ir (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c); +uint8_t * pixma_rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c); +uint8_t * pixma_binarize_line(pixma_scan_param_t *, uint8_t * dst, uint8_t * src, unsigned width, unsigned c); +/**@}*/ + +/** \name Command related functions */ +/**@{*/ +int pixma_cmd_transaction (pixma_t *, const void *cmd, unsigned cmdlen, +			   void *data, unsigned expected_len); +int pixma_check_result (pixma_cmdbuf_t *); +uint8_t *pixma_newcmd (pixma_cmdbuf_t *, unsigned cmd, +		       unsigned dataout, unsigned datain); +int pixma_exec (pixma_t *, pixma_cmdbuf_t *); +int pixma_exec_short_cmd (pixma_t *, pixma_cmdbuf_t *, unsigned cmd); +int pixma_map_status_errno (unsigned status); +/**@}*/ + +#define pixma_fill_checksum(start, end) do {		\ +    *(end) = -pixma_sum_bytes(start, (end)-(start));	\ +} while(0) + +/** @} end of group subdriver */ + +/** \addtogroup debug + *  @{ */ +void pixma_set_debug_level (int level); +#ifndef NDEBUG +void pixma_hexdump (int level, const void *d_, unsigned len); + +/* len:   length of data or error code. +   size:  if >= 0, force to print 'size' bytes. +   max:   maximum number of bytes to print(-1 means no limit). */ +void pixma_dump (int level, const char *type, const void *data, int len, +		 int size, int max); +#  define DEBUG_DECLARE_ONLY +#  include "../include/sane/sanei_debug.h" +#endif /* NDEBUG */ +/** @} end of group debug */ + +#endif diff --git a/backend/pixma/pixma_imageclass.c b/backend/pixma/pixma_imageclass.c new file mode 100644 index 0000000..ce0c37d --- /dev/null +++ b/backend/pixma/pixma_imageclass.c @@ -0,0 +1,985 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> +   Copyright (C) 2007-2009 Nicolas Martin, <nicols-guest at alioth dot debian dot org> +   Copyright (C) 2008 Dennis Lou, dlou 99 at yahoo dot com + +   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. + */ + +/* + * imageCLASS backend based on pixma_mp730.c + */ + +#include "../include/sane/config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +#define IMAGE_BLOCK_SIZE (0x80000) +#define MAX_CHUNK_SIZE   (0x1000) +#define MIN_CHUNK_SIZE   (0x0200) +#define CMDBUF_SIZE 512 + +#define MF4100_PID 0x26a3 +#define MF4600_PID 0x26b0 +#define MF4010_PID 0x26b4 +#define MF4200_PID 0x26b5 +#define MF4360_PID 0x26ec +#define D480_PID   0x26ed +#define MF4320_PID 0x26ee +#define D420_PID   0x26ef +#define MF3200_PID 0x2684 +#define MF6500_PID 0x2686 +/* generation 2 scanners (>=0x2707) */ +#define MF8300_PID 0x2708 +#define MF4500_PID 0x2736 +#define MF4410_PID 0x2737 +#define D550_PID   0x2738 +#define MF3010_PID 0x2759 +#define MF4570_PID 0x275a +#define MF4800_PID 0x2773 +#define MF4700_PID 0x2774 +#define MF8200_PID 0x2779 +/* the following are all untested */ +#define MF5630_PID 0x264e +#define MF5650_PID 0x264f +#define MF8100_PID 0x2659 +#define MF5880_PID 0x26f9 +#define MF6680_PID 0x26fa +#define MF8030_PID 0x2707 +#define IR1133_PID 0x2742 +#define MF5900_PID 0x2743 +#define D530_PID   0x2775 +#define MF8500_PID 0x277a +#define MF6100_PID 0x278e +#define MF820_PID  0x27a6 +#define MF220_PID  0x27a8 +#define MF210_PID  0x27a9 +#define MF620_PID  0x27b4 +#define MF410_PID  0x27c0 +#define MF510_PID  0x27c2 +#define MF230_PID  0x27d1 +#define MF240_PID  0x27d2 +#define MF630_PID  0x27e1 +#define MF634_PID  0x27e2 +#define MF730_PID  0x27e4 +#define MF731_PID  0x27e5 +#define D570_PID   0x27e8 +#define MF110_PID  0x27ed +#define MF520_PID  0x27f0 +#define MF420_PID  0x27f1 +#define MF260_PID  0x27f4 +#define MF740_PID  0x27fb +#define MF743_PID  0x27fc +#define MF640_PID  0x27fe +#define MF645_PID  0x27fd + + +enum iclass_state_t +{ +  state_idle, +  state_warmup,			/* MF4200 always warm/calibrated; others? */ +  state_scanning, +  state_finished +}; + +enum iclass_cmd_t +{ +  cmd_start_session = 0xdb20, +  cmd_select_source = 0xdd20, +  cmd_scan_param = 0xde20, +  cmd_status = 0xf320, +  cmd_abort_session = 0xef20, +  cmd_read_image  = 0xd420, +  cmd_read_image2 = 0xd460,     /* New multifunctionals, such as MF4410 */ +  cmd_error_info = 0xff20, + +  cmd_activate = 0xcf60 +}; + +typedef struct iclass_t +{ +  enum iclass_state_t state; +  pixma_cmdbuf_t cb; +  unsigned raw_width; +  uint8_t current_status[12]; + +  uint8_t *buf, *blkptr, *lineptr; +  unsigned buf_len, blk_len; + +  unsigned last_block; + +  uint8_t generation;           /* New multifunctionals are (generation == 2) */ + +  uint8_t adf_state;            /* handle adf scanning */ +} iclass_t; + + +static int is_scanning_from_adf (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADF +          || s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static int is_scanning_from_adfdup (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static void iclass_finish_scan (pixma_t * s); + +/* checksumming is sometimes different than pixmas */ +static int +iclass_exec (pixma_t * s, pixma_cmdbuf_t * cb, char invcksum) +{ +  if (cb->cmdlen > cb->cmd_header_len) +    pixma_fill_checksum (cb->buf + cb->cmd_header_len, +			 cb->buf + cb->cmdlen - 2); +  cb->buf[cb->cmdlen - 1] = invcksum ? -cb->buf[cb->cmdlen - 2] : 0; +  cb->reslen = +    pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf, +			   cb->expected_reslen); +  return pixma_check_result (cb); +} + +static int +has_paper (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  return ((mf->current_status[1] & 0x0f) == 0           /* allow 0x10 as ADF paper OK */ +          || mf->current_status[1] == 81);              /* allow 0x51 as ADF paper OK */ +} + +static int +abort_session (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mf->cb, cmd_abort_session); +} + +static int +query_status (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mf->cb, cmd_status, 0, 12); +  error = pixma_exec (s, &mf->cb); +  if (error >= 0) +    { +      memcpy (mf->current_status, data, 12); +      /*DBG (3, "Current status: paper=0x%02x cal=%u lamp=%u\n", +	   data[1], data[8], data[7]);*/ +      PDBG (pixma_dbg (3, "Current status: paper=0x%02x cal=%u lamp=%u\n", +		       data[1], data[8], data[7])); +    } +  return error; +} + +static int +activate (pixma_t * s, uint8_t x) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mf->cb, cmd_activate, 10, 0); +  data[0] = 1; +  data[3] = x; +  switch (s->cfg->pid) +    { +    case MF4200_PID: +    case MF4600_PID: +    case MF6500_PID: +    case D480_PID: +    case D420_PID: +    case MF4360_PID: +    case MF4100_PID: +    case MF8300_PID: +      return iclass_exec (s, &mf->cb, 1); +      break; +    default: +      return pixma_exec (s, &mf->cb); +    } +} + +static int +start_session (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mf->cb, cmd_start_session); +} + +static int +select_source (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mf->cb, cmd_select_source, 10, 0); +  data[0] = (is_scanning_from_adf(s)) ? 2 : 1; +  /* special settings for MF6100 */ +  data[5] = is_scanning_from_adfdup(s) ? 3 : ((s->cfg->pid == MF6100_PID && s->param->source == PIXMA_SOURCE_ADF) ? 1 : 0); +  switch (s->cfg->pid) +    { +    case MF4200_PID: +    case MF4600_PID: +    case MF6500_PID: +    case D480_PID: +    case D420_PID: +    case MF4360_PID: +    case MF4100_PID: +    case MF8300_PID: +      return iclass_exec (s, &mf->cb, 0); +      break; +    default: +      return pixma_exec (s, &mf->cb); +    } +} + +static int +send_scan_param (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *data; + +  data = pixma_newcmd (&mf->cb, cmd_scan_param, 0x2e, 0); +  pixma_set_be16 (s->param->xdpi | 0x1000, data + 0x04); +  pixma_set_be16 (s->param->ydpi | 0x1000, data + 0x06); +  pixma_set_be32 (s->param->x, data + 0x08); +  pixma_set_be32 (s->param->y, data + 0x0c); +  pixma_set_be32 (mf->raw_width, data + 0x10); +  pixma_set_be32 (s->param->h, data + 0x14); +  data[0x18] = (s->param->channels == 1) ? 0x04 : 0x08; +  data[0x19] = s->param->channels * ((s->param->depth == 1) ? 8 : s->param->depth);	/* bits per pixel */ +  data[0x1f] = 0x7f; +  data[0x20] = 0xff; +  data[0x23] = 0x81; +  switch (s->cfg->pid) +    { +    case MF4200_PID: +    case MF4600_PID: +    case MF6500_PID: +    case D480_PID: +    case D420_PID: +    case MF4360_PID: +    case MF4100_PID: +    case MF8300_PID: +      return iclass_exec (s, &mf->cb, 0); +      break; +    default: +      return pixma_exec (s, &mf->cb); +    } +} + +static int +request_image_block (pixma_t * s, unsigned flag, uint8_t * info, +		     unsigned * size, uint8_t * data, unsigned * datalen) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  int error; +  unsigned expected_len; +  const int hlen = 2 + 6; + +  memset (mf->cb.buf, 0, 11); +  /* generation 2 scanners use cmd_read_image2. +   * MF6100, ... are exceptions */ +  pixma_set_be16 (((mf->generation >= 2 +                    && s->cfg->pid != MF6100_PID) ? cmd_read_image2 : cmd_read_image), mf->cb.buf); +  mf->cb.buf[8] = flag; +  mf->cb.buf[10] = 0x06; +  expected_len = (mf->generation >= 2 || +                  s->cfg->pid == MF4600_PID || +                  s->cfg->pid == MF6500_PID || +                  s->cfg->pid == MF8030_PID) ? 512 : hlen; +  mf->cb.reslen = pixma_cmd_transaction (s, mf->cb.buf, 11, mf->cb.buf, expected_len); +  if (mf->cb.reslen >= hlen) +    { +      *info = mf->cb.buf[2]; +      *size = pixma_get_be16 (mf->cb.buf + 6);    /* 16bit size */ +      error = 0; + +      if (mf->generation >= 2 || +          s->cfg->pid == MF4600_PID || +          s->cfg->pid == MF6500_PID || +          s->cfg->pid == MF8030_PID) +        {                                         /* 32bit size */ +          *datalen = mf->cb.reslen - hlen; +          *size = (*datalen + hlen == 512) ? pixma_get_be32 (mf->cb.buf + 4) - *datalen : *size; +          memcpy (data, mf->cb.buf + hlen, *datalen); +        } +     PDBG (pixma_dbg (11, "*request_image_block***** size = %u *****\n", *size)); +    } +  else +    { +       error = PIXMA_EPROTO; +    } +  return error; +} + +static int +read_image_block (pixma_t * s, uint8_t * data, unsigned size) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; +  int error; +  unsigned maxchunksize, chunksize, count = 0; + +  maxchunksize = MAX_CHUNK_SIZE * ((mf->generation >= 2 || +                                    s->cfg->pid == MF4600_PID || +                                    s->cfg->pid == MF6500_PID || +                                    s->cfg->pid == MF8030_PID) ? 4 : 1); +  while (size) +    { +      if (size >= maxchunksize) +      	chunksize = maxchunksize; +      else if (size < MIN_CHUNK_SIZE) +      	chunksize = size; +      else +      	chunksize = size - (size % MIN_CHUNK_SIZE); +      error = pixma_read (s->io, data, chunksize); +      if (error < 0) +      	return count; +      count += error; +      data += error; +      size -= error; +    } +  return count; +} + +static int +read_error_info (pixma_t * s, void *buf, unsigned size) +{ +  unsigned len = 16; +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mf->cb, cmd_error_info, 0, len); +  switch (s->cfg->pid) +    { +    case MF4200_PID: +    case MF4600_PID: +    case MF6500_PID: +    case D480_PID: +    case D420_PID: +    case MF4360_PID: +    case MF4100_PID: +    case MF8300_PID: +      error = iclass_exec (s, &mf->cb, 0); +      break; +    default: +      error = pixma_exec (s, &mf->cb); +    } +  if (error < 0) +    return error; +  if (buf && len < size) +    { +      size = len; +      /* NOTE: I've absolutely no idea what the returned data mean. */ +      memcpy (buf, data, size); +      error = len; +    } +  return error; +} + +static int +handle_interrupt (pixma_t * s, int timeout) +{ +  uint8_t buf[16]; +  int len; + +  len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout); +  if (len == PIXMA_ETIMEDOUT) +    return 0; +  if (len < 0) +    return len; +  if (len != 16) +    { +      PDBG (pixma_dbg +	    (1, "WARNING:unexpected interrupt packet length %d\n", len)); +      return PIXMA_EPROTO; +    } +  if (buf[12] & 0x40) +    query_status (s); +  if (buf[15] & 1) +    s->events = PIXMA_EV_BUTTON1; +  return 1; +} + +static int +step1 (pixma_t * s) +{ +  int error; +  int rec_tmo; +  iclass_t *mf = (iclass_t *) s->subdriver; + +  /* don't wait full timeout for 1st command */ +  rec_tmo = s->rec_tmo;         /* save globel timeout */ +  s->rec_tmo = 2;               /* set timeout to 2 seconds */ +  error = query_status (s); +  s->rec_tmo = rec_tmo;         /* restore global timeout */ +  if (error < 0) +  { +    PDBG (pixma_dbg (1, "WARNING: Resend first USB command after timeout!\n")); +    error = query_status (s); +  } +  if (error < 0) +    return error; + +  /* wait for inserted paper */ +  if (s->param->adf_wait != 0 && is_scanning_from_adf(s)) +  { +    int tmo = s->param->adf_wait; + +    while (!has_paper (s) && --tmo >= 0 && !s->param->frontend_cancel) +    { +      if ((error = query_status (s)) < 0) +        return error; +      pixma_sleep (1000000); +      PDBG (pixma_dbg(2, "No paper in ADF. Timed out in %d sec.\n", tmo)); +    } +    /* canceled from frontend */ +    if (s->param->frontend_cancel) +    { +      return PIXMA_ECANCELED; +    } +  } +  /* no paper inserted +   * => abort session */ +  if (is_scanning_from_adf(s) && !has_paper (s)) +  { +    return PIXMA_ENO_PAPER; +  } +  /* activate only seen for generation 1 scanners */ +  if (mf->generation == 1) +    { +      if (error >= 0) +        error = activate (s, 0); +      if (error >= 0) +        error = activate (s, 4); +    } +  return error; +} + +/* line in=rrr... ggg... bbb... line out=rgbrgbrgb...  */ +static void +pack_rgb (const uint8_t * src, unsigned nlines, unsigned w, uint8_t * dst) +{ +  unsigned w2, stride; + +  w2 = 2 * w; +  stride = 3 * w; +  for (; nlines != 0; nlines--) +    { +      unsigned x; +      for (x = 0; x != w; x++) +        { +          *dst++ = src[x + 0]; +          *dst++ = src[x + w]; +          *dst++ = src[x + w2]; +        } +      src += stride; +    } +} + +static int +iclass_open (pixma_t * s) +{ +  iclass_t *mf; +  uint8_t *buf; + +  mf = (iclass_t *) calloc (1, sizeof (*mf)); +  if (!mf) +    return PIXMA_ENOMEM; + +  buf = (uint8_t *) malloc (CMDBUF_SIZE); +  if (!buf) +    { +      free (mf); +      return PIXMA_ENOMEM; +    } + +  s->subdriver = mf; +  mf->state = state_idle; + +  mf->cb.buf = buf; +  mf->cb.size = CMDBUF_SIZE; +  mf->cb.res_header_len = 2; +  mf->cb.cmd_header_len = 10; +  mf->cb.cmd_len_field_ofs = 7; + +  /* adf scanning */ +  mf->adf_state = state_idle; + +  /* set generation = 2 for new multifunctionals */ +  mf->generation = (s->cfg->pid >= MF8030_PID) ? 2 : 1; +  PDBG (pixma_dbg (3, "*iclass_open***** This is a generation %d scanner.  *****\n", mf->generation)); + +  PDBG (pixma_dbg (3, "Trying to clear the interrupt buffer...\n")); +  if (handle_interrupt (s, 200) == 0) +    { +      PDBG (pixma_dbg (3, "  no packets in buffer\n")); +    } +  return 0; +} + +static void +iclass_close (pixma_t * s) +{ +  iclass_t *mf = (iclass_t *) s->subdriver; + +  iclass_finish_scan (s); +  free (mf->cb.buf); +  free (mf->buf); +  free (mf); +  s->subdriver = NULL; +} + +static int +iclass_check_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  UNUSED (s); + +  /* PDBG (pixma_dbg (4, "*iclass_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, line_size=%" PRIu64 " , h=%u*****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->line_size, sp->h)); */ + +  sp->depth = 8; +  sp->software_lineart = 0; +  if (sp->mode == PIXMA_SCAN_MODE_LINEART) +  { +    sp->software_lineart = 1; +    sp->channels = 1; +    sp->depth = 1; +  } + +  if (sp->software_lineart == 1) +  { +    unsigned w_max; + +    /* for software lineart line_size and w must be a multiple of 8 */ +    sp->line_size = ALIGN_SUP (sp->w, 8) * sp->channels; +    sp->w = ALIGN_SUP (sp->w, 8); + +    /* do not exceed the scanner capability */ +    w_max = s->cfg->width * s->cfg->xdpi / 75; +    w_max -= w_max % 32; +    if (sp->w > w_max) +      sp->w = w_max; +  } +  else +    sp->line_size = ALIGN_SUP (sp->w, 32) * sp->channels; + +  /* Some exceptions here for particular devices */ +  /* Those devices can scan up to Legal 14" with ADF, but A4 11.7" in flatbed */ +  /* PIXMA_CAP_ADF also works for PIXMA_CAP_ADFDUP */ +  if ((s->cfg->cap & PIXMA_CAP_ADF) && sp->source == PIXMA_SOURCE_FLATBED) +    sp->h = MIN (sp->h, 877 * sp->xdpi / 75); + +  /* PDBG (pixma_dbg (4, "*iclass_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, line_size=%" PRIu64 " , h=%u*****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->line_size, sp->h)); */ + +  return 0; +} + +static int +iclass_scan (pixma_t * s) +{ +  int error, n; +  iclass_t *mf = (iclass_t *) s->subdriver; +  uint8_t *buf, ignore; +  unsigned buf_len, ignore2; + +  if (mf->state != state_idle) +    return PIXMA_EBUSY; + +  /* clear interrupt packets buffer */ +  while (handle_interrupt (s, 0) > 0) +    { +    } + +  mf->raw_width = ALIGN_SUP (s->param->w, 32); +  PDBG (pixma_dbg (3, "raw_width = %u\n", mf->raw_width)); + +  n = IMAGE_BLOCK_SIZE / s->param->line_size + 1; +  buf_len = (n + 1) * s->param->line_size + IMAGE_BLOCK_SIZE; +  if (buf_len > mf->buf_len) +    { +      buf = (uint8_t *) realloc (mf->buf, buf_len); +      if (!buf) +	return PIXMA_ENOMEM; +      mf->buf = buf; +      mf->buf_len = buf_len; +    } +  mf->lineptr = mf->buf; +  mf->blkptr = mf->buf + n * s->param->line_size; +  mf->blk_len = 0; + +  error = step1 (s); +  if (error >= 0 +      && (s->param->adf_pageid == 0 || mf->generation == 1 || mf->adf_state == state_idle)) +    { /* single sheet or first sheet from ADF */ +      PDBG (pixma_dbg (3, "*iclass_scan***** start scanning *****\n")); +      error = start_session (s); +      if (error >= 0) +        mf->state = state_scanning; +      if (error >= 0) +        error = select_source (s); +    } +  else if (error >= 0) +    { /* next sheet from ADF */ +      PDBG (pixma_dbg (3, "*iclass_scan***** scan next sheet from ADF  *****\n")); +      mf->state = state_scanning; +    } +  if (error >= 0) +    error = send_scan_param (s); +  if (error >= 0) +    error = request_image_block (s, 0, &ignore, &ignore2, &ignore, &ignore2); +  if (error < 0) +    { +      iclass_finish_scan (s); +      return error; +    } +  mf->last_block = 0; + +  /* ADF scanning active */ +  if (is_scanning_from_adf (s)) +    mf->adf_state = state_scanning; +  return 0; +} + + +static int +iclass_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib) +{ +  int error, n; +  iclass_t *mf = (iclass_t *) s->subdriver; +  unsigned block_size, lines_size, lineart_lines_size, first_block_size; +  uint8_t info; + +/* + * 1. send a block request cmd (d4 20 00... 04 00 06) + * 2. examine the response for block size and/or end-of-scan flag + * 3. read the block one chunk at a time + * 4. repeat until have enough to process >=1 lines + */ +  do +    { +      do +        { +          if (s->cancel) +            return PIXMA_ECANCELED; +          if (mf->last_block) +            { +              /* end of image */ +              mf->state = state_finished; +              return 0; +            } + +          first_block_size = 0; +          error = request_image_block (s, 4, &info, &block_size, +                          mf->blkptr + mf->blk_len, &first_block_size); +          /* add current block to remainder of previous */ +          mf->blk_len += first_block_size; +          if (error < 0) +            { +              /* NOTE: seen in traffic logs but don't know the meaning. */ +              read_error_info (s, NULL, 0); +              if (error == PIXMA_ECANCELED) +                return error; +            } + +          /* info: 0x28 = end; 0x38 = end + ADF empty */ +          mf->last_block = info & 0x38; +          if ((info & ~0x38) != 0) +            { +              PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n")); +              PDBG (pixma_hexdump (1, &info, 1)); +            } + +          if (block_size == 0) +            { +              /* no image data at this moment. */ +              /*pixma_sleep(100000); *//* FIXME: too short, too long? */ +              handle_interrupt (s, 100); +            } +        } +      while (block_size == 0 && first_block_size == 0); + +      error = read_image_block (s, mf->blkptr + mf->blk_len, block_size); +      block_size = error; +      if (error < 0) +        return error; + +      /* add current block to remainder of previous */ +      mf->blk_len += block_size; +      /* n = number of full lines (rows) we have in the buffer. */ +      n = mf->blk_len / ((s->param->mode == PIXMA_SCAN_MODE_LINEART) ? mf->raw_width : s->param->line_size); +      if (n != 0) +        { +          /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** Processing with n=%d, w=%i, line_size=%" PRIu64 ", raw_width=%u ***** \n", +                           n, s->param->w, s->param->line_size, mf->raw_width)); */ +          /* PDBG (pixma_dbg (4, "*iclass_fill_buffer*****                 scan_mode=%d, lineptr=%" PRIu64 ", blkptr=%" PRIu64 " \n", +                           s->param->mode, (uint64_t)mf->lineptr, (uint64_t)mf->blkptr)); */ + +          /* gray to lineart convert +           * mf->lineptr         : image line +           * mf->blkptr          : scanned image block as grayscale +           * s->param->w         : image width +           * s->param->line_size : scanned image width */ +          if (s->param->mode == PIXMA_SCAN_MODE_LINEART) +          { +            int i; +            uint8_t *sptr, *dptr; + +            /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** Processing lineart *****\n")); */ + +            /* process ALL lines */ +            sptr = mf->blkptr; +            dptr = mf->lineptr; +            for (i = 0; i < n; i++, sptr += mf->raw_width) +              dptr = pixma_binarize_line (s->param, dptr, sptr, s->param->line_size, 1); +          } +          else if (s->param->channels != 1 && +                  mf->generation == 1 && +	          s->cfg->pid != MF4600_PID && +	          s->cfg->pid != MF6500_PID && +	          s->cfg->pid != MF8030_PID) +            { +              /* color and not MF46xx or MF65xx */ +              pack_rgb (mf->blkptr, n, mf->raw_width, mf->lineptr); +            } +          else +            { +              /* grayscale */ +              memcpy (mf->lineptr, mf->blkptr, n * s->param->line_size); +            } +          /* cull remainder and shift left */ +          lineart_lines_size = n * s->param->line_size / 8; +          lines_size = n * ((s->param->mode == PIXMA_SCAN_MODE_LINEART) ? mf->raw_width : s->param->line_size); +          mf->blk_len -= lines_size; +          memcpy (mf->blkptr, mf->blkptr + lines_size, mf->blk_len); +        } +    } +  while (n == 0); + +  /* output full lines, keep partial lines for next block +   * ib->rptr : start of image buffer +   * ib->rend : end of image buffer */ +  ib->rptr = mf->lineptr; +  ib->rend = mf->lineptr + (s->param->mode == PIXMA_SCAN_MODE_LINEART ? lineart_lines_size : lines_size); +  /* PDBG (pixma_dbg (4, "*iclass_fill_buffer*****                 rptr=%" PRIu64 ", rend=%" PRIu64 ", diff=%ld \n", +                   (uint64_t)ib->rptr, (uint64_t)ib->rend, ib->rend - ib->rptr)); */ +  return ib->rend - ib->rptr; +} + +static void +iclass_finish_scan (pixma_t * s) +{ +  int error; +  iclass_t *mf = (iclass_t *) s->subdriver; + +  switch (mf->state) +    { +      /* fall through */ +    case state_warmup: +    case state_scanning: +      error = abort_session (s); +      if (error < 0) +	PDBG (pixma_dbg +	      (1, "WARNING:abort_session() failed %s\n", +	       pixma_strerror (error))); +      /* fall through */ +    case state_finished: +      query_status (s); +      query_status (s); +      if (mf->generation == 1) +        { /* activate only seen for generation 1 scanners */ +          activate (s, 0); +          query_status (s); +        } +      /* generation = 1: +       * 0x28 = last block (no multi page scan) +       * generation >= 2: +       * 0x38 = last block and ADF empty (generation >= 2) +       * 0x28 = last block and Paper in ADF (multi page scan) +       * some generation 2 scanners don't use 0x38 for ADF empty => check status */ +      if (mf->last_block==0x38                                  /* generation 2 scanner ADF empty */ +          || (mf->generation == 1 && mf->last_block == 0x28)    /* generation 1 scanner last block */ +          || (mf->generation >= 2 && !has_paper(s)))            /* check status: no paper in ADF */ +	{ +          /* ADFDUP scan: wait for 8sec to throw last page out of ADF feeder */ +          if (is_scanning_from_adfdup(s)) +          { +            PDBG (pixma_dbg (4, "*iclass_finish_scan***** sleep for 8s  *****\n")); +            pixma_sleep(8000000);       /* sleep for 8s */ +            query_status (s); +          } +          PDBG (pixma_dbg (3, "*iclass_finish_scan***** abort session  *****\n")); +	  abort_session (s); +	  mf->adf_state = state_idle; +	  mf->last_block = 0; +	} +      else +        PDBG (pixma_dbg (3, "*iclass_finish_scan***** wait for next page from ADF  *****\n")); + +      mf->state = state_idle; +      /* fall through */ +    case state_idle: +      break; +    } +} + +static void +iclass_wait_event (pixma_t * s, int timeout) +{ +  /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for +   * instance. */ +  while (s->events == 0 && handle_interrupt (s, timeout) > 0) +    { +    } +} + +static int +iclass_get_status (pixma_t * s, pixma_device_status_t * status) +{ +  int error; + +  error = query_status (s); +  if (error < 0) +    return error; +  status->hardware = PIXMA_HARDWARE_OK; +  status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER; +  return 0; +} + + +static const pixma_scan_ops_t pixma_iclass_ops = { +  iclass_open, +  iclass_close, +  iclass_scan, +  iclass_fill_buffer, +  iclass_finish_scan, +  iclass_wait_event, +  iclass_check_param, +  iclass_get_status +}; + +#define DEV(name, model, pid, dpi, adftpu_max_dpi, w, h, cap) {     \ +            name,                     /* name */		\ +            model,                    /* model */		\ +            0x04a9, pid,              /* vid pid */	\ +            1,                        /* iface */		\ +            &pixma_iclass_ops,        /* ops */		\ +            0,                        /* min_xdpi not used in this subdriver */ \ +            dpi, dpi,                 /* xdpi, ydpi */	\ +            0,                        /* adftpu_min_dpi not used in this subdriver */ \ +            adftpu_max_dpi,           /* adftpu_max_dpi */ \ +            0, 0,                     /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */   \ +            w, h,                     /* width, height */	\ +            PIXMA_CAP_LINEART|        /* all scanners have software lineart */ \ +            PIXMA_CAP_ADF_WAIT|       /* adf wait for all ADF and ADFDUP scanners */ \ +            PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap             \ +} +const pixma_config_t pixma_iclass_devices[] = { +  DEV ("Canon imageCLASS MF4270", "MF4270", MF4200_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF4150", "MF4100", MF4100_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF4690", "MF4690", MF4600_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS D420", "D420", D420_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageCLASS D480", "D480", D480_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageCLASS MF4360", "MF4360", MF4360_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageCLASS MF4320", "MF4320", MF4320_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF4010", "MF4010", MF4010_PID, 600, 0, 640, 877, 0), +  DEV ("Canon imageCLASS MF3240", "MF3240", MF3200_PID, 600, 0, 640, 877, 0), +  DEV ("Canon imageClass MF6500", "MF6500", MF6500_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF4410", "MF4410", MF4410_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS D550", "D550", D550_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF4500 Series", "MF4500", MF4500_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF3010", "MF3010", MF3010_PID, 600, 0, 640, 877, 0), +  DEV ("Canon i-SENSYS MF4700 Series", "MF4700", MF4700_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF4800 Series", "MF4800", MF4800_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF4570dw", "MF4570dw", MF4570_PID, 600, 0, 640, 877, 0), +  DEV ("Canon i-SENSYS MF8200C Series", "MF8200C", MF8200_PID, 600, 300, 640, 1050, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF8300 Series", "MF8300", MF8300_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS D530", "D530", D530_PID, 600, 0, 640, 877, 0), +  /* FIXME: the following capabilities all need updating/verifying */ +  DEV ("Canon imageCLASS MF5630", "MF5630", MF5630_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon laserBase MF5650", "MF5650", MF5650_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageCLASS MF8170c", "MF8170c", MF8100_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon imageClass MF8030", "MF8030", MF8030_PID, 600, 0, 640, 877, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF5880dn", "MF5880", MF5880_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF6680dn", "MF6680", MF6680_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageRUNNER 1133", "iR1133", IR1133_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP),                  /* max. w = 216mm */ +  DEV ("Canon i-SENSYS MF5900 Series", "MF5900", MF5900_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF8500C Series", "MF8500C", MF8500_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF6100 Series", "MF6100", MF6100_PID, 600, 300, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageClass MF810/820", "MF810/820", MF820_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF220 Series", "MF220", MF220_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),              /* max. w = 216mm */ +  DEV ("Canon i-SENSYS MF210 Series", "MF210", MF210_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF),                 /* max. w = 216mm */ +  DEV ("Canon i-SENSYS MF620 Series", "MF620", MF620_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF), +  DEV ("Canon i-SENSYS MF410 Series", "MF410", MF410_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),              /* max. w = 216mm */ +  DEV ("Canon i-SENSYS MF510 Series", "MF510", MF510_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF230 Series", "MF230", MF230_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF),                 /* max. w = 216mm */ +  DEV ("Canon i-SENSYS MF240 Series", "MF240", MF240_PID, 600, 300, 634, 1050, PIXMA_CAP_ADF),               /* max. w = 215mm, */ +                                                                                                             /* TODO: fix black stripes for 216mm @ 600dpi */ +  DEV ("Canon i-SENSYS MF630 Series", "MF630", MF630_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF730 Series", "MF730", MF730_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF731C", "MF731", MF731_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF633C/MF635C", "MF633C/635C", MF630_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP),              /* max. w = 216mm */ +  DEV ("Canon imageCLASS MF634C", "MF632C/634C", MF634_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon imageCLASS MF733C", "MF731C/733C", MF731_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),            /* however, we need this for ethernet/wifi */ +  DEV ("Canon imageCLASS D570", "D570", D570_PID, 600, 0, 640, 877, 0), +  DEV ("Canon i-SENSYS MF110 Series", "MF110", MF110_PID, 600, 0, 640, 1050, 0), +  DEV ("Canon i-SENSYS MF520 Series", "MF520", MF520_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF420 Series", "MF420", MF420_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF260 Series", "MF260", MF260_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF740 Series", "MF740", MF740_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF741C/743C", "MF741C/743C", MF743_PID, 600, 300, 640, 1050, PIXMA_CAP_ADFDUP),       /* ADFDUP restricted to 300dpi */ +  DEV ("Canon i-SENSYS MF640 Series", "MF642C/643C/644C", MF640_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP), +  DEV ("Canon i-SENSYS MF645C", "MF645C", MF645_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP),                    /* max. w = 216mm */ +  DEV (NULL, NULL, 0, 0, 0, 0, 0, 0) +}; diff --git a/backend/pixma/pixma_io.h b/backend/pixma/pixma_io.h new file mode 100644 index 0000000..715bab5 --- /dev/null +++ b/backend/pixma/pixma_io.h @@ -0,0 +1,186 @@ +/* SANE - Scanner Access Now Easy. + +   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. + */ +#ifndef PIXMA_IO_H +#define PIXMA_IO_H + +/* TODO: move to pixma_common.h, to reduce the number of files */ + +/*! + * \defgroup IO IO interface + * \brief The IO interface. + * + * Return value of functions that return \c int if not otherwise specified: + *  - >=    if succeeded + *  - < 0   if failed (e.g. \c PIXMA_ETIMEDOUT) + * . + * @{ + */ + +/** Timeout for pixma_read() in milliseconds */ +#define PIXMA_BULKIN_TIMEOUT  1000 +/** Timeout for pixma_write() in milliseconds */ +#define PIXMA_BULKOUT_TIMEOUT 1000 + + +struct pixma_io_t; +struct pixma_config_t; + +/** IO handle */ +typedef struct pixma_io_t pixma_io_t; + + +/** Initialize IO module. It must be called before any other functions in this + *  module. + *  \return 0 on success or + *   - \c PIXMA_ENOMEM + *   - \c PIXMA_EACCES + *   - \c PIXMA_EIO */ +int pixma_io_init (void); + +/** Shutdown all connections and free resources allocated in this module. */ +void pixma_io_cleanup (void); + +/** Find devices currently connected to the computer. + *  \c devnr passed to functions + *  - pixma_get_device_config() + *  - pixma_get_device_id() + *  - pixma_connect() + *  . + *  should be less than the number of devices returned by this function. + *  \param[in] pixma_devices A \c NULL terminated array of pointers to + *             array of pixma_config_t which is terminated by setting + *             pixma_config_t::name to \c NULL. + *  \return Number of devices found */ +unsigned pixma_collect_devices (const char ** conf_devices, +                                const struct pixma_config_t *const +				pixma_devices[], SANE_Bool local_only); + +/** Get device configuration. */ +const struct pixma_config_t *pixma_get_device_config (unsigned devnr); + +/** Get a unique ID of the device \a devnr. */ +const char *pixma_get_device_id (unsigned devnr); + +/** Connect to the device and claim the scanner interface. + *  \param[in] devnr + *  \param[out] handle + *  \return 0 on success or + *   - \c PIXMA_ENODEV the device is gone from the system. + *   - \c PIXMA_EINVAL \a devnr is invalid. + *   - \c PIXMA_EBUSY + *   - \c PIXMA_EACCES + *   - \c PIXMA_ENOMEM + *   - \c PIXMA_EIO */ +int pixma_connect (unsigned devnr, pixma_io_t ** handle); + +/** Release the scanner interface and disconnect from the device. */ +void pixma_disconnect (pixma_io_t *); + +/** Activate connection to scanner */ +int pixma_activate (pixma_io_t *); + +/** De-activate connection to scanner */ +int pixma_deactivate (pixma_io_t *); + +/** Reset the USB interface. \warning Use with care! */ +int pixma_reset_device (pixma_io_t *); + +/** Write data to the device. This function may not be interrupted by signals. + *  It will return iff + *   - \a len bytes have been successfully written or + *   - an error (inclusive timeout) occured. + *  . + *  \note Calling pixma_write(io, buf, n1) and pixma(io, buf+n1, n2) may + *        not be the same as pixma_write(io, buf, n1+n2) if n1 is not + *        multiple of the maximum packet size of the endpoint. + *  \param[in] cmd Data + *  \param[in] len Length of data + *  \return Number of bytes successfully written (always = \a len) or + *   - \c PIXMA_ETIMEDOUT + *   - \c PIXMA_EIO + *   - \c PIXMA_ENOMEM + *  \see #PIXMA_BULKOUT_TIMEOUT */ +int pixma_write (pixma_io_t *, const void *cmd, unsigned len); + +/** Read data from the device. This function may not be interrupted by signals. + *  It will return iff + *   - \a size bytes have been successfully read, + *   - a short packet has been read or + *   - an error (inclusive timeout) occured. + *  . + *  \param[out] buf + *  \param[in]  size of the buffer + *  \return Number of bytes successfully read. A return value of zero means that + *       a zero length USB packet was received. Or + *   - \c PIXMA_ETIMEDOUT + *   - \c PIXMA_EIO + *   - \c PIXMA_ENOMEM + *  \see #PIXMA_BULKIN_TIMEOUT */ +int pixma_read (pixma_io_t *, void *buf, unsigned size); + +/** Wait for an interrupt. This function can be interrupted by signals. + *  \a size should be less than or equal to the maximum packet size. + *  \param[out] buf + *  \param[in]  size of the buffer + *  \param[in]  timeout in milliseconds; if < 0, wait forever. + *  \return Number of bytes successfully read or + *   - \c PIXMA_ETIMEDOUT + *   - \c PIXMA_EIO + *   - \c PIXMA_ENOMEM + *   - \c PIXMA_ECANCELED if it was interrupted by a signal. */ +int pixma_wait_interrupt (pixma_io_t *, void *buf, unsigned size, +			  int timeout); + +/** Enable or disable background interrupt monitoring. + *  Background mode is enabled by default. + *  \param[in] background if not zero, a URB is submitted in background + *             for interrupt endpoint. + *  \return 0 on success or + *   - \c PIXMA_ENOTSUP + *   - \c PIXMA_EIO + *   - \c PIXMA_ENOMEM */ +int pixma_set_interrupt_mode (pixma_io_t *, int background); + +/** @} end of IO group */ + +#endif diff --git a/backend/pixma/pixma_io_sanei.c b/backend/pixma/pixma_io_sanei.c new file mode 100644 index 0000000..c30b404 --- /dev/null +++ b/backend/pixma/pixma_io_sanei.c @@ -0,0 +1,556 @@ +/* SANE - Scanner Access Now Easy. + * For limitations, see function sanei_usb_get_vendor_product(). + +   Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> +   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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h>		/* INT_MAX */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" +#include "pixma_bjnp.h" + +#include "../include/sane/sanei_usb.h" +#include "../include/sane/sane.h" + + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +/* MAC OS X does not support timeouts in darwin/libusb interrupt reads + * This is a very basic turnaround for MAC OS X + * Button scan will not work with this wrapper */ +#ifdef __APPLE__ +# define sanei_usb_read_int sanei_usb_read_bulk +#endif + + +struct pixma_io_t +{ +  pixma_io_t *next; +  int interface; +  SANE_Int dev; +}; + +typedef struct scanner_info_t +{ +  struct scanner_info_t *next; +  char *devname; +  int interface; +  const pixma_config_t *cfg; +  char serial[PIXMA_MAX_ID_LEN + 1];	/* "xxxxyyyy_zzzzzzz..." +					   x = vid, y = pid, z = serial */ +} scanner_info_t; + +#define INT_USB 0 +#define INT_BJNP 1 + +static scanner_info_t *first_scanner = NULL; +static pixma_io_t *first_io = NULL; +static unsigned nscanners; + + +static scanner_info_t * +get_scanner_info (unsigned devnr) +{ +  scanner_info_t *si; +  for (si = first_scanner; si && devnr != 0; --devnr, si = si->next) +    { +    } +  return si; +} + +static SANE_Status +attach (SANE_String_Const devname) +{ +  scanner_info_t *si; + +  si = (scanner_info_t *) calloc (1, sizeof (*si)); +  if (!si) +    return SANE_STATUS_NO_MEM; +  si->devname = strdup (devname); +  if (!si->devname) +    return SANE_STATUS_NO_MEM; +  si -> interface = INT_USB; +  si->next = first_scanner; +  first_scanner = si; +  nscanners++; +  return SANE_STATUS_GOOD; +} + + +static SANE_Status +attach_bjnp (SANE_String_Const devname, +             SANE_String_Const serial, +             const struct pixma_config_t *cfg) +{ +  scanner_info_t *si; + +  si = (scanner_info_t *) calloc (1, sizeof (*si)); +  if (!si) +    return SANE_STATUS_NO_MEM; +  si->devname = strdup (devname); +  if (!si->devname) +    return SANE_STATUS_NO_MEM; + +  si->cfg = cfg; +  sprintf(si->serial, "%s_%s", cfg->model, serial); +  si -> interface = INT_BJNP; +  si->next = first_scanner; +  first_scanner = si; +  nscanners++; +  return SANE_STATUS_GOOD; +} + +static void +clear_scanner_list (void) +{ +  scanner_info_t *si = first_scanner; +  while (si) +    { +      scanner_info_t *temp = si; +      free (si->devname); +      si = si->next; +      free (temp); +    } +  nscanners = 0; +  first_scanner = NULL; +} + +static SANE_Status +get_descriptor (SANE_Int dn, SANE_Int type, SANE_Int descidx, +		SANE_Int index, SANE_Int length, SANE_Byte * data) +{ +  return sanei_usb_control_msg (dn, 0x80, USB_REQ_GET_DESCRIPTOR, +				((type & 0xff) << 8) | (descidx & 0xff), +				index, length, data); +} + +static SANE_Status +get_string_descriptor (SANE_Int dn, SANE_Int index, SANE_Int lang, +		       SANE_Int length, SANE_Byte * data) +{ +  return get_descriptor (dn, USB_DT_STRING, index, lang, length, data); +} + +static void +u16tohex (uint16_t x, char *str) +{ +  static const char hdigit[16] = +    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', +    'E', 'F' +    }; +  str[0] = hdigit[(x >> 12) & 0xf]; +  str[1] = hdigit[(x >> 8) & 0xf]; +  str[2] = hdigit[(x >> 4) & 0xf]; +  str[3] = hdigit[x & 0xf]; +  str[4] = '\0'; +} + +static void +read_serial_number (scanner_info_t * si) +{ +  uint8_t unicode[2 * (PIXMA_MAX_ID_LEN - 9) + 2]; +  uint8_t ddesc[18]; +  int iSerialNumber; +  SANE_Int usb; +  char *serial = si->serial; + +  u16tohex (si->cfg->vid, serial); +  u16tohex (si->cfg->pid, serial + 4); + +  if (SANE_STATUS_GOOD != sanei_usb_open (si->devname, &usb)) +    return; +  if (get_descriptor (usb, USB_DT_DEVICE, 0, 0, 18, ddesc) +      != SANE_STATUS_GOOD) +    goto done; +  iSerialNumber = ddesc[16]; +  if (iSerialNumber != 0) +    { +      int i, len; +      SANE_Status status; + +      /*int iSerialNumber = ddesc[16];*/ +      /* Read the first language code. Assumed that there is at least one. */ +      if (get_string_descriptor (usb, 0, 0, 4, unicode) != SANE_STATUS_GOOD) +        goto done; +      /* Read the serial number string. */ +      status = get_string_descriptor (usb, iSerialNumber, +                                      unicode[3] * 256 + unicode[2], +                                      sizeof (unicode), unicode); +      if (status != SANE_STATUS_GOOD) +        goto done; +      /* Assumed charset: Latin1 */ +      len = unicode[0]; +      if (len > (int) sizeof (unicode)) +        { +          len = sizeof (unicode); +          PDBG (pixma_dbg (1, "WARNING:Truncated serial number\n")); +        } +            serial[8] = '_'; +            for (i = 2; i < len; i += 2) +        { +          serial[9 + i / 2 - 1] = unicode[i]; +        } +      serial[9 + i / 2 - 1] = '\0'; +    } +  else +    { +      PDBG (pixma_dbg (1, "WARNING:No serial number\n")); +    } +done: +  sanei_usb_close (usb); +} + +static int +map_error (SANE_Status ss) +{ +  switch (ss) +    { +    case SANE_STATUS_GOOD: +      return 0; +    case SANE_STATUS_UNSUPPORTED: +      return PIXMA_ENODEV; +    case SANE_STATUS_DEVICE_BUSY: +      return PIXMA_EBUSY; +    case SANE_STATUS_INVAL: +      return PIXMA_EINVAL; +    case SANE_STATUS_IO_ERROR: +      return PIXMA_EIO; +    case SANE_STATUS_NO_MEM: +      return PIXMA_ENOMEM; +    case SANE_STATUS_ACCESS_DENIED: +      return PIXMA_EACCES; +    case SANE_STATUS_CANCELLED: +      return PIXMA_ECANCELED; +    case SANE_STATUS_JAMMED: +       return PIXMA_EPAPER_JAMMED; +    case SANE_STATUS_COVER_OPEN: +       return PIXMA_ECOVER_OPEN; +    case SANE_STATUS_NO_DOCS: +       return PIXMA_ENO_PAPER; +    case SANE_STATUS_EOF: +       return PIXMA_EOF; +#ifdef SANE_STATUS_HW_LOCKED +    case SANE_STATUS_HW_LOCKED:       /* unused by pixma */ +#endif +#ifdef SANE_STATUS_WARMING_UP +    case SANE_STATUS_WARMING_UP:      /* unused by pixma */ +#endif +      break; +    } +  PDBG (pixma_dbg (1, "BUG:Unmapped SANE Status code %d\n", ss)); +  return PIXMA_EIO;		/* should not happen */ +} + + +int +pixma_io_init (void) +{ +  sanei_usb_init (); +  sanei_bjnp_init(); +  nscanners = 0; +  return 0; +} + +void +pixma_io_cleanup (void) +{ +  while (first_io) +    pixma_disconnect (first_io); +  clear_scanner_list (); +} + +unsigned +pixma_collect_devices (const char **conf_devices, +                       const struct pixma_config_t *const pixma_devices[], SANE_Bool local_only) +{ +  unsigned i, j; +  struct scanner_info_t *si; +  const struct pixma_config_t *cfg; + +  clear_scanner_list (); +  j = 0; +  for (i = 0; pixma_devices[i]; i++) +    { +      for (cfg = pixma_devices[i]; cfg->name; cfg++) +        { +          sanei_usb_find_devices (cfg->vid, cfg->pid, attach); +          si = first_scanner; +          while (j < nscanners) +            { +              PDBG (pixma_dbg (3, "pixma_collect_devices() found %s at %s\n", +                   cfg->name, si->devname)); +              si->cfg = cfg; +              read_serial_number (si); +              si = si->next; +              j++; +            } +        } +    } +  if (! local_only) +    sanei_bjnp_find_devices(conf_devices, attach_bjnp, pixma_devices); + +  si = first_scanner; +  while (j < nscanners) +    { +      PDBG (pixma_dbg (3, "pixma_collect_devices() found %s at %s\n", +               si->cfg->name, si->devname)); +      si = si->next; +      j++; + +    } +  return nscanners; +} + +const pixma_config_t * +pixma_get_device_config (unsigned devnr) +{ +  const scanner_info_t *si = get_scanner_info (devnr); +  return (si) ? si->cfg : NULL; +} + +const char * +pixma_get_device_id (unsigned devnr) +{ +  const scanner_info_t *si = get_scanner_info (devnr); +  return (si) ? si->serial : NULL; +} + +int +pixma_connect (unsigned devnr, pixma_io_t ** handle) +{ +  pixma_io_t *io; +  SANE_Int dev; +  const scanner_info_t *si; +  int error; + +  *handle = NULL; +  si = get_scanner_info (devnr); +  if (!si) +    return PIXMA_EINVAL; +  if (si-> interface == INT_BJNP) +    error = map_error (sanei_bjnp_open (si->devname, &dev)); +  else +    error = map_error (sanei_usb_open (si->devname, &dev)); + +  if (error < 0) +    return error; +  io = (pixma_io_t *) calloc (1, sizeof (*io)); +  if (!io) +    { +      if (si -> interface == INT_BJNP) +        sanei_bjnp_close (dev); +      else +        sanei_usb_close (dev); +      return PIXMA_ENOMEM; +    } +  io->next = first_io; +  first_io = io; +  io->dev = dev; +  io->interface = si->interface; +  *handle = io; +  return 0; +} + + +void +pixma_disconnect (pixma_io_t * io) +{ +  pixma_io_t **p; + +  if (!io) +    return; +  for (p = &first_io; *p && *p != io; p = &((*p)->next)) +    { +    } +  PASSERT (*p); +  if (!(*p)) +    return; +  if (io-> interface == INT_BJNP) +    sanei_bjnp_close (io->dev); +  else +    sanei_usb_close (io->dev); +  *p = io->next; +  free (io); +} + +int pixma_activate (pixma_io_t * io) +{ +  int error; +  if (io->interface == INT_BJNP) +    { +      error = map_error(sanei_bjnp_activate (io->dev)); +    } +  else +    /* noop for USB interface */ +    error = 0; +  return error; +} + +int pixma_deactivate (pixma_io_t * io) +{ +  int error; +  if (io->interface == INT_BJNP) +    { +      error = map_error(sanei_bjnp_deactivate (io->dev)); +    } +  else +    /* noop for USB interface */ +    error = 0; +  return error; + +} + +int +pixma_reset_device (pixma_io_t * io) +{ +  UNUSED (io); +  return PIXMA_ENOTSUP; +} + +int +pixma_write (pixma_io_t * io, const void *cmd, unsigned len) +{ +  size_t count = len; +  int error; + +  if (io->interface == INT_BJNP) +    { +    sanei_bjnp_set_timeout (io->dev, PIXMA_BULKOUT_TIMEOUT); +    error = map_error (sanei_bjnp_write_bulk (io->dev, cmd, &count)); +    } +  else +    { +#ifdef HAVE_SANEI_USB_SET_TIMEOUT +    sanei_usb_set_timeout (PIXMA_BULKOUT_TIMEOUT); +#endif +    error = map_error (sanei_usb_write_bulk (io->dev, cmd, &count)); +    } +  if (error == PIXMA_EIO) +    error = PIXMA_ETIMEDOUT;	/* FIXME: SANE doesn't have ETIMEDOUT!! */ +  if (count != len) +    { +      PDBG (pixma_dbg (1, "WARNING:pixma_write(): count(%u) != len(%u)\n", +		       (unsigned) count, len)); +      error = PIXMA_EIO; +    } +  if (error >= 0) +    error = count; +  PDBG (pixma_dump (10, "OUT ", cmd, error, len, 128)); +  return error; +} + +int +pixma_read (pixma_io_t * io, void *buf, unsigned size) +{ +  size_t count = size; +  int error; + +  if (io-> interface == INT_BJNP) +    { +    sanei_bjnp_set_timeout (io->dev, PIXMA_BULKIN_TIMEOUT); +    error = map_error (sanei_bjnp_read_bulk (io->dev, buf, &count)); +    } +  else +    { +#ifdef HAVE_SANEI_USB_SET_TIMEOUT +      sanei_usb_set_timeout (PIXMA_BULKIN_TIMEOUT); +#endif +      error = map_error (sanei_usb_read_bulk (io->dev, buf, &count)); +    } + +  if (error == PIXMA_EIO) +    error = PIXMA_ETIMEDOUT;	/* FIXME: SANE doesn't have ETIMEDOUT!! */ +  if (error >= 0) +    error = count; +  PDBG (pixma_dump (10, "IN  ", buf, error, -1, 128)); +  return error; +} + +int +pixma_wait_interrupt (pixma_io_t * io, void *buf, unsigned size, int timeout) +{ +  size_t count = size; +  int error; + +  /* FIXME: What is the meaning of "timeout" in sanei_usb? */ +  if (timeout < 0) +    timeout = INT_MAX; +  else if (timeout < 100) +    timeout = 100; +  if (io-> interface == INT_BJNP) +    { +      sanei_bjnp_set_timeout (io->dev, timeout); +      error = map_error (sanei_bjnp_read_int (io->dev, buf, &count)); +    } +  else +    { +#ifdef HAVE_SANEI_USB_SET_TIMEOUT +      sanei_usb_set_timeout (timeout); +#endif +      error = map_error (sanei_usb_read_int (io->dev, buf, &count)); +    } +  if (error == PIXMA_EIO || +      (io->interface == INT_BJNP && error == PIXMA_EOF))     /* EOF is a bjnp timeout error! */ +    error = PIXMA_ETIMEDOUT;	/* FIXME: SANE doesn't have ETIMEDOUT!! */ +  if (error == 0) +    error = count; +  if (error != PIXMA_ETIMEDOUT) +    PDBG (pixma_dump (10, "INTR", buf, error, -1, -1)); +  return error; +} + +int +pixma_set_interrupt_mode (pixma_io_t * s, int background) +{ +  UNUSED (s); +  return (background) ? PIXMA_ENOTSUP : 0; +} diff --git a/backend/pixma/pixma_mp150.c b/backend/pixma/pixma_mp150.c new file mode 100644 index 0000000..3973702 --- /dev/null +++ b/backend/pixma/pixma_mp150.c @@ -0,0 +1,1817 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> +   Copyright (C) 2007-2009 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. + */ +/* test cases +   1. short USB packet (must be no -ETIMEDOUT) +   2. cancel using button on the printer (look for abort command) +   3. start scan while busy (status 0x1414) +   4. cancel using ctrl-c (must send abort command) + */ + +#include "../include/sane/config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h>		/* localtime(C90) */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + +/* Some macro code to enhance readability */ +#define RET_IF_ERR(x) do {	\ +    if ((error = (x)) < 0)	\ +      return error;		\ +  } while(0) + +#define WAIT_INTERRUPT(x) do {			\ +    error = handle_interrupt (s, x);		\ +    if (s->cancel)				\ +      return PIXMA_ECANCELED;			\ +    if (error != PIXMA_ECANCELED && error < 0)	\ +      return error;				\ +  } while(0) + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +/* Size of the command buffer should be multiple of wMaxPacketLength and +   greater than 4096+24. +   4096 = size of gamma table. 24 = header + checksum */ +#define IMAGE_BLOCK_SIZE (512*1024) +#define CMDBUF_SIZE (4096 + 24) +#define DEFAULT_GAMMA 2.0	/***** Gamma different from 1.0 is potentially impacting color profile generation *****/ +#define UNKNOWN_PID 0xffff + + +#define CANON_VID 0x04a9 + +/* Generation 1 */ +#define MP150_PID 0x1709 +#define MP170_PID 0x170a +#define MP450_PID 0x170b +#define MP500_PID 0x170c +#define MP530_PID 0x1712 + +/* Generation 2 */ +#define MP160_PID 0x1714 +#define MP180_PID 0x1715 +#define MP460_PID 0x1716 +#define MP510_PID 0x1717 +#define MP600_PID 0x1718 +#define MP600R_PID 0x1719 + +#define MP140_PID 0x172b + +/* Generation 3 */ +/* PIXMA 2007 vintage */ +#define MX7600_PID 0x171c +#define MP210_PID 0x1721 +#define MP220_PID 0x1722 +#define MP470_PID 0x1723 +#define MP520_PID 0x1724 +#define MP610_PID 0x1725 +#define MX300_PID 0x1727 +#define MX310_PID 0x1728 +#define MX700_PID 0x1729 +#define MX850_PID 0x172c + +/* PIXMA 2008 vintage */ +#define MP630_PID 0x172e +#define MP620_PID 0x172f +#define MP540_PID 0x1730 +#define MP480_PID 0x1731 +#define MP240_PID 0x1732 +#define MP260_PID 0x1733 +#define MP190_PID 0x1734 + +/* PIXMA 2009 vintage */ +#define MX860_PID 0x1735 +#define MX320_PID 0x1736    /* untested */ +#define MX330_PID 0x1737 + +/* Generation 4 */ +#define MP250_PID 0x173a +#define MP270_PID 0x173b +#define MP490_PID 0x173c +#define MP550_PID 0x173d +#define MP560_PID 0x173e +#define MP640_PID 0x173f + +/* PIXMA 2010 vintage */ +#define MX340_PID 0x1741 +#define MX350_PID 0x1742 +#define MX870_PID 0x1743 + +/* 2010 new devices (untested) */ +#define MP280_PID 0x1746 +#define MP495_PID 0x1747 +#define MG5100_PID 0x1748 +#define MG5200_PID 0x1749 +#define MG6100_PID 0x174a + +/* PIXMA 2011 vintage */ +#define MX360_PID 0x174d +#define MX410_PID 0x174e +#define MX420_PID 0x174f +#define MX880_PID 0x1750 + +/* Generation 5 */ +/* 2011 new devices (untested) */ +#define MG2100_PID 0x1751 +#define MG3100_PID 0x1752 +#define MG4100_PID 0x1753 +#define MG5300_PID 0x1754 +#define MG6200_PID 0x1755 +#define MP493_PID 0x1757 +#define E500_PID 0x1758 + +/* 2012 new devices (untested) */ +#define MX370_PID 0x1759 +#define MX430_PID 0x175B +#define MX510_PID 0x175C +#define MX710_PID 0x175D +#define MX890_PID 0x175E +#define E600_PID 0x175A +#define MG4200_PID 0x1763 + +/* 2013 new devices */ +#define MP230_PID 0x175F +#define MG6300_PID 0x1765 + +/* 2013 new devices (untested) */ +#define MG2200_PID 0x1760 +#define E510_PID 0x1761 +#define MG3200_PID 0x1762 +#define MG5400_PID 0x1764 +#define MX390_PID 0x1766 +#define E610_PID 0x1767 +#define MX450_PID 0x1768 +#define MX520_PID 0x1769 +#define MX720_PID 0x176a +#define MX920_PID 0x176b +#define MG2400_PID 0x176c +#define MG2500_PID 0x176d +#define MG3500_PID 0x176e +#define MG6500_PID 0x176f +#define MG6400_PID 0x1770 +#define MG5500_PID 0x1771 +#define MG7100_PID 0x1772 + +/* 2014 new devices (untested) */ +#define MX470_PID 0x1774 +#define MX530_PID 0x1775 +#define MB5000_PID 0x1776 +#define MB5300_PID 0x1777 +#define MB2000_PID 0x1778 +#define MB2300_PID 0x1779 +#define E400_PID 0x177a +#define E560_PID 0x177b +#define MG7500_PID 0x177c +#define MG6600_PID 0x177e +#define MG5600_PID 0x177f +#define MG2900_PID 0x1780 +#define E460_PID 0x1788 + +/* 2015 new devices (untested) */ +#define MX490_PID 0x1787 +#define E480_PID 0x1789 +#define MG3600_PID 0x178a +#define MG7700_PID 0x178b +#define MG6900_PID 0x178c +#define MG6800_PID 0x178d +#define MG5700_PID 0x178e + +/* 2016 new devices (untested) */ +#define MB2700_PID 0x1792 +#define MB2100_PID 0x1793 +#define G3000_PID 0x1794 +#define G2000_PID 0x1795 +#define TS9000_PID 0x179f +#define TS8000_PID 0x1800 +#define TS6000_PID 0x1801 +#define TS5000_PID 0x1802 +#define MG3000_PID 0x180b +#define E470_PID 0x180c +#define E410_PID 0x181e + +/* 2017 new devices (untested) */ +#define G4000_PID 0x181d +#define TS6100_PID 0x1822 +#define TS5100_PID 0x1825 +#define TS3100_PID 0x1827 +#define E3100_PID 0x1828 + +/* 2018 new devices (untested) */ +#define MB5400_PID 0x178f +#define MB5100_PID 0x1790 +#define TS9100_PID 0x1820 +#define TR8500_PID 0x1823 +#define TR7500_PID 0x1824 +#define TS9500_PID 0x185c +#define LIDE400_PID 0x1912  /* tested */ +#define LIDE300_PID 0x1913  /* tested */ + +/* 2019 new devices (untested) */ +#define TS8100_PID 0x1821 +#define G2010_PID 0x183a +#define G3010_PID 0x183b +#define G4010_PID 0x183d +#define TS9180_PID 0x183e +#define TS8180_PID 0x183f +#define TS6180_PID 0x1840 +#define TR8580_PID 0x1841 +#define TS8130_PID 0x1842 +#define TS6130_PID 0x1843 +#define TR8530_PID 0x1844 +#define TR7530_PID 0x1845 +#define XK50_PID 0x1846 +#define XK70_PID 0x1847 +#define TR4500_PID 0x1854 +#define E4200_PID 0x1855 +#define TS6200_PID 0x1856 +#define TS6280_PID 0x1857 +#define TS6230_PID 0x1858 +#define TS8200_PID 0x1859 +#define TS8280_PID 0x185a +#define TS8230_PID 0x185b +#define TS9580_PID 0x185d +#define TR9530_PID 0x185e +#define G6000_PID 0x1865 +#define G6080_PID 0x1866 +#define XK80_PID 0x1873 +#define TS5300_PID 0x188b +#define TS5380_PID 0x188c +#define TS6300_PID 0x188d +#define TS6380_PID 0x188e +#define TS7330_PID 0x188f +#define TS8300_PID 0x1890 +#define TS8380_PID 0x1891 +#define TS8330_PID 0x1892 +#define XK60_PID   0x1893 +#define TS6330_PID 0x1894 +#define TS3300_PID 0x18a2 +#define E3300_PID  0x18a3 + +/* Generation 4 XML messages that encapsulates the Pixma protocol messages */ +#define XML_START_1   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\ +<ivec:contents><ivec:operation>StartJob</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +<ivec:bidi>1</ivec:bidi></ivec:param_set></ivec:contents></cmd>" + +#define XML_START_2   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\" xmlns:vcn=\"http://www.canon.com/ns/cmd/2008/07/canon/\">\ +<ivec:contents><ivec:operation>VendorCmd</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +<vcn:ijoperation>ModeShift</vcn:ijoperation><vcn:ijmode>1</vcn:ijmode>\ +</ivec:param_set></ivec:contents></cmd>" + +#define XML_END   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\ +<ivec:contents><ivec:operation>EndJob</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +</ivec:param_set></ivec:contents></cmd>" + +#define XML_OK   "<ivec:response>OK</ivec:response>" + +enum mp150_state_t +{ +  state_idle, +  state_warmup, +  state_scanning, +  state_transfering, +  state_finished +}; + +enum mp150_cmd_t +{ +  cmd_start_session = 0xdb20, +  cmd_select_source = 0xdd20, +  cmd_gamma = 0xee20, +  cmd_scan_param = 0xde20, +  cmd_status = 0xf320, +  cmd_abort_session = 0xef20, +  cmd_time = 0xeb80, +  cmd_read_image = 0xd420, +  cmd_error_info = 0xff20, + +  cmd_scan_param_3 = 0xd820, +  cmd_scan_start_3 = 0xd920, +  cmd_status_3 = 0xda20, +}; + +typedef struct mp150_t +{ +  enum mp150_state_t state; +  pixma_cmdbuf_t cb; +  uint8_t *imgbuf; +  uint8_t current_status[16]; +  unsigned last_block; +  uint8_t generation; +  /* for Generation 3 shift */ +  uint8_t *linebuf; +  uint8_t *data_left_ofs; +  unsigned data_left_len; +  uint8_t adf_state;            /* handle adf scanning */ +  unsigned scale;               /* Scale factor for lower resolutions, the +                                 * scanner doesn't support. We scale down the +                                 * image after scanning minimum possible +                                 * resolution. +                                 */ + +} mp150_t; + +/* +  STAT:  0x0606 = ok, +         0x1515 = failed (PIXMA_ECANCELED), +	 0x1414 = busy (PIXMA_EBUSY) + +  Transaction scheme +    1. command_header/data | result_header +    2. command_header      | result_header/data +    3. command_header      | result_header/image_data + +  - data has checksum in the last byte. +  - image_data has no checksum. +  - data and image_data begins in the same USB packet as +    command_header or result_header. + +  command format #1: +   u16be      cmd +   u8[6]      0 +   u8[4]      0 +   u32be      PLEN parameter length +   u8[PLEN-1] parameter +   u8         parameter check sum +  result: +   u16be      STAT +   u8         0 +   u8         0 or 0x21 if STAT == 0x1414 +   u8[4]      0 + +  command format #2: +   u16be      cmd +   u8[6]      0 +   u8[4]      0 +   u32be      RLEN result length +  result: +   u16be      STAT +   u8[6]      0 +   u8[RLEN-1] result +   u8         result check sum + +  command format #3: (only used by read_image_block) +   u16be      0xd420 +   u8[6]      0 +   u8[4]      0 +   u32be      max. block size + 8 +  result: +   u16be      STAT +   u8[6]      0 +   u8         block info bitfield: 0x8 = end of scan, 0x10 = no more paper, 0x20 = no more data +   u8[3]      0 +   u32be      ILEN image data size +   u8[ILEN]   image data + */ + +static void mp150_finish_scan (pixma_t * s); + +static int +is_scanning_from_adf (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADF +	  || s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static int +is_scanning_from_adfdup (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static int +is_scanning_jpeg (pixma_t *s) +{ +  return s->param->mode_jpeg; +} + +static int +send_xml_dialog (pixma_t * s, const char * xml_message) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  int datalen; + +  datalen = pixma_cmd_transaction (s, xml_message, strlen (xml_message), +                                   mp->cb.buf, 1024); +  if (datalen < 0) +    return datalen; + +  mp->cb.buf[datalen] = 0; + +  PDBG (pixma_dbg (10, "XML message sent to scanner:\n%s\n", xml_message)); +  PDBG (pixma_dbg (10, "XML response back from scanner:\n%s\n", mp->cb.buf)); + +  return (strcasestr ((const char *) mp->cb.buf, XML_OK) != NULL); +} + +static int +start_session (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  pixma_newcmd (&mp->cb, cmd_start_session, 0, 0); +  mp->cb.buf[3] = 0x00; +  return pixma_exec (s, &mp->cb); +} + +static int +start_scan_3 (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  pixma_newcmd (&mp->cb, cmd_scan_start_3, 0, 0); +  mp->cb.buf[3] = 0x00; +  return pixma_exec (s, &mp->cb); +} + +static int +is_calibrated (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  if (mp->generation >= 3) +    { +      return ((mp->current_status[0] & 0x01) == 1 || (mp->current_status[0] & 0x02) == 2); +    } +  if (mp->generation == 1) +    { +      return (mp->current_status[8] == 1); +    } +  else +    { +      return (mp->current_status[9] == 1); +    } +} + +static int +has_paper (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  if (is_scanning_from_adfdup (s)) +    return (mp->current_status[1] == 0 || mp->current_status[2] == 0); +  else +    return (mp->current_status[1] == 0); +} + +static void +drain_bulk_in (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0); +} + +static int +abort_session (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  mp->adf_state = state_idle;           /* reset adf scanning */ +  return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session); +} + +static int +select_source (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  uint8_t *data; + +  data = pixma_newcmd (&mp->cb, cmd_select_source, 12, 0); +  data[5] = ((mp->generation == 2) ? 1 : 0); +  switch (s->param->source) +    { +      case PIXMA_SOURCE_FLATBED: +        data[0] = 1; +        data[1] = 1; +        break; + +      case PIXMA_SOURCE_ADF: +        data[0] = 2; +        data[5] = 1; +        data[6] = 1; +        break; + +      case PIXMA_SOURCE_ADFDUP: +        data[0] = 2; +        data[5] = 3; +        data[6] = 3; +        break; + +      default: +        return PIXMA_EPROTO; +    } +  return pixma_exec (s, &mp->cb); +} + +static int +send_gamma_table (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  const uint8_t *lut = s->param->gamma_table; +  uint8_t *data; + +  if (mp->generation == 1) +    { +      data = pixma_newcmd (&mp->cb, cmd_gamma, 4096 + 8, 0); +      data[0] = (s->param->channels == 3) ? 0x10 : 0x01; +      pixma_set_be16 (0x1004, data + 2); +      if (lut) +	      memcpy (data + 4, lut, 4096); +      else +        pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 4096); +    } +  else +    { +      /* FIXME: Gamma table for 2nd generation: 1024 * uint16_le */ +      data = pixma_newcmd (&mp->cb, cmd_gamma, 2048 + 8, 0); +      data[0] = 0x10; +      pixma_set_be16 (0x0804, data + 2); +      if (lut) +        { +          int i; +          for (i = 0; i < 1024; i++) +            { +              int j = (i << 2) + (i >> 8); +              data[4 + 2 * i + 0] = lut[j]; +              data[4 + 2 * i + 1] = lut[j]; +            } +        } +      else +        { +          int i; +          pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 2048); +          for (i = 0; i < 1024; i++) +            { +              int j = (i << 1) + (i >> 9); +              data[4 + 2 * i + 0] = data[4 + j]; +              data[4 + 2 * i + 1] = data[4 + j]; +            } +        } +    } +  return pixma_exec (s, &mp->cb); +} + +static unsigned +calc_raw_width (const mp150_t * mp, const pixma_scan_param_t * param) +{ +  unsigned raw_width; +  /* NOTE: Actually, we can send arbitary width to MP150. Lines returned +     are always padded to multiple of 4 or 12 pixels. Is this valid for +     other models, too? */ +  if (mp->generation >= 2) +    { +      raw_width = ALIGN_SUP ((param->w * mp->scale) + param->xs, 32); +      /* PDBG (pixma_dbg (4, "*calc_raw_width***** width %i extended by %i and rounded to %i *****\n", param->w, param->xs, raw_width)); */ +    } +  else if (param->channels == 1) +    { +      raw_width = ALIGN_SUP (param->w + param->xs, 12); +    } +  else +    { +      raw_width = ALIGN_SUP (param->w + param->xs, 4); +    } +  return raw_width; +} + +static unsigned +get_cis_line_size (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  /*PDBG (pixma_dbg (4, "%s: line_size=%ld, w=%d, wx=%d, scale=%d\n", +                   __func__, s->param->line_size, s->param->w, s->param->wx, mp->scale));*/ + +  return (s->param->wx ? s->param->line_size / s->param->w * s->param->wx +                       : s->param->line_size) * mp->scale; +} + +static int +send_scan_param (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  uint8_t *data; +  unsigned xdpi = s->param->xdpi * mp->scale; +  unsigned ydpi = s->param->xdpi * mp->scale; +  unsigned x = s->param->x * mp->scale; +  unsigned xs = s->param->xs; +  unsigned y = s->param->y * mp->scale; +  unsigned wx = calc_raw_width (mp, s->param); +  unsigned h = MIN (s->param->h, s->cfg->height * s->param->ydpi / 75) * mp->scale; + +  if (mp->generation <= 2) +    { +      PDBG (pixma_dbg (4, "*send_scan_param gen. 1-2 ***** Setting: xdpi=%hi ydpi=%hi  x=%i y=%i  wx=%i ***** \n", +                           xdpi, ydpi, x-xs, y, wx)); +      data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x30, 0); +      pixma_set_be16 (xdpi | 0x8000, data + 0x04); +      pixma_set_be16 (ydpi | 0x8000, data + 0x06); +      pixma_set_be32 (x, data + 0x08); +      if (mp->generation == 2) +        pixma_set_be32 (x - s->param->xs, data + 0x08); +      pixma_set_be32 (y, data + 0x0c); +      pixma_set_be32 (wx, data + 0x10); +      pixma_set_be32 (h, data + 0x14); +      data[0x18] = (s->param->channels != 1) ? 0x08 : 0x04; +      data[0x19] = ((s->param->software_lineart) ? 8 : s->param->depth) +                    * s->param->channels;   /* bits per pixel */ +      data[0x1a] = 0; +      data[0x20] = 0xff; +      data[0x23] = 0x81; +      data[0x26] = 0x02; +      data[0x27] = 0x01; +    } +  else +    { +      PDBG (pixma_dbg (4, "*send_scan_param gen. 3+ ***** Setting: xdpi=%hi ydpi=%hi x=%i xs=%i y=%i  wx=%i h=%i ***** \n", +                           xdpi, ydpi, x, xs, y, wx, h)); +      data = pixma_newcmd (&mp->cb, cmd_scan_param_3, 0x38, 0); +      data[0x00] = (is_scanning_from_adf (s)) ? 0x02 : 0x01; +      data[0x01] = 0x01; +      data[0x02] = 0x01; +      if (is_scanning_from_adfdup (s)) +        { +          data[0x02] = 0x03; +          data[0x03] = 0x03; +        } +      if (is_scanning_jpeg (s)) +        { +          data[0x03] = 0x01; +        } +      else +        { +          data[0x05] = 0x01;	/* This one also seen at 0. Don't know yet what's used for */ +        } +      pixma_set_be16 (xdpi | 0x8000, data + 0x08); +      pixma_set_be16 (ydpi | 0x8000, data + 0x0a); +      pixma_set_be32 (x - xs, data + 0x0c); +      pixma_set_be32 (y, data + 0x10); +      pixma_set_be32 (wx, data + 0x14); +      pixma_set_be32 (h, data + 0x18); +      data[0x1c] = (s->param->channels != 1) ? 0x08 : 0x04; + +      data[0x1d] = ((s->param->software_lineart) ? 8 : s->param->depth) +                    * s->param->channels;   /* bits per pixel */ + +      data[0x1f] = 0x01;        /* This one also seen at 0. Don't know yet what's used for */ +      data[0x20] = 0xff; +      if (is_scanning_jpeg (s)) +        { +          data[0x21] = 0x83; +        } +      else +        { +          data[0x21] = 0x81; +        } +      data[0x23] = 0x02; +      data[0x24] = 0x01; + +      switch (s->cfg->pid) +        { +	case MG5300_PID: +	  /* unknown values (perhaps counter) for MG5300 series---values must be 0x30-0x39: decimal 0-9 */ +	  data[0x26] = 0x32; /* using example values from a real scan here */ +	  data[0x27] = 0x31; +	  data[0x28] = 0x34; +	  data[0x29] = 0x35; +	  break; + +	default: +	  break; +	} + +      data[0x30] = 0x01; +    } +  return pixma_exec (s, &mp->cb); +} + +static int +query_status_3 (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  uint8_t *data; +  int error, status_len; + +  status_len = 8; +  data = pixma_newcmd (&mp->cb, cmd_status_3, 0, status_len); +  RET_IF_ERR (pixma_exec (s, &mp->cb)); +  memcpy (mp->current_status, data, status_len); +  return error; +} + +static int +query_status (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  uint8_t *data; +  int error, status_len; + +  status_len = (mp->generation == 1) ? 12 : 16; +  data = pixma_newcmd (&mp->cb, cmd_status, 0, status_len); +  RET_IF_ERR (pixma_exec (s, &mp->cb)); +  memcpy (mp->current_status, data, status_len); +  PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u busy=%u\n", +		       data[1], data[8], data[7], data[9])); +  return error; +} + +#if 0 +static int +send_time (pixma_t * s) +{ +  /* Why does a scanner need a time? */ +  time_t now; +  struct tm *t; +  uint8_t *data; +  mp150_t *mp = (mp150_t *) s->subdriver; + +  data = pixma_newcmd (&mp->cb, cmd_time, 20, 0); +  pixma_get_time (&now, NULL); +  t = localtime (&now); +  strftime ((char *) data, 16, "%y/%m/%d %H:%M", t); +  PDBG (pixma_dbg (3, "Sending time: '%s'\n", (char *) data)); +  return pixma_exec (s, &mp->cb); +} +#endif + +/* TODO: Simplify this function. Read the whole data packet in one shot. */ +static int +read_image_block (pixma_t * s, uint8_t * header, uint8_t * data) +{ +  uint8_t cmd[16]; +  mp150_t *mp = (mp150_t *) s->subdriver; +  const int hlen = 8 + 8; +  int error, datalen; + +  memset (cmd, 0, sizeof (cmd)); +  pixma_set_be16 (cmd_read_image, cmd); +  if ((mp->last_block & 0x20) == 0) +    pixma_set_be32 ((IMAGE_BLOCK_SIZE / 65536) * 65536 + 8, cmd + 0xc); +  else +    pixma_set_be32 (32 + 8, cmd + 0xc); + +  mp->state = state_transfering; +  mp->cb.reslen = +    pixma_cmd_transaction (s, cmd, sizeof (cmd), mp->cb.buf, 512); +  datalen = mp->cb.reslen; +  if (datalen < 0) +    return datalen; + +  memcpy (header, mp->cb.buf, hlen); + +  if (datalen >= hlen) +    { +      datalen -= hlen; +      memcpy (data, mp->cb.buf + hlen, datalen); +      data += datalen; +      if (mp->cb.reslen == 512) +        { +          error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen); +          RET_IF_ERR (error); +          datalen += error; +        } +    } + +  mp->state = state_scanning; +  mp->cb.expected_reslen = 0; +  RET_IF_ERR (pixma_check_result (&mp->cb)); +  if (mp->cb.reslen < hlen) +    return PIXMA_EPROTO; +  return datalen; +} + +static int +read_error_info (pixma_t * s, void *buf, unsigned size) +{ +  unsigned len = 16; +  mp150_t *mp = (mp150_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len); +  RET_IF_ERR (pixma_exec (s, &mp->cb)); +  if (buf && len < size) +    { +      size = len; +      /* NOTE: I've absolutely no idea what the returned data mean. */ +      memcpy (buf, data, size); +      error = len; +    } +  return error; +} + +/* +handle_interrupt() waits until it receives an interrupt packet or times out. +It calls send_time() and query_status() if necessary. Therefore, make sure +that handle_interrupt() is only called from a safe context for send_time() +and query_status(). + +   Returns: +   0     timed out +   1     an interrupt packet received +   PIXMA_ECANCELED interrupted by signal +   <0    error +*/ +static int +handle_interrupt (pixma_t * s, int timeout) +{ +  uint8_t buf[64]; +  int len; + +  len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout); +  if (len == PIXMA_ETIMEDOUT) +    return 0; +  if (len < 0) +    return len; +  if (len%16)           /* len must be a multiple of 16 bytes */ +    { +      PDBG (pixma_dbg +	    (1, "WARNING:unexpected interrupt packet length %d\n", len)); +      return PIXMA_EPROTO; +    } + +  /* s->event = 0x0brroott +   * b:  button +   * oo: original +   * tt: target +   * rr: scan resolution +   * poll event with 'scanimage -A' */ +  if (s->cfg->pid == MG5300_PID +      || s->cfg->pid == MG5400_PID +      || s->cfg->pid == MG6200_PID +      || s->cfg->pid == MG6300_PID +      || s->cfg->pid == MX520_PID +      || s->cfg->pid == MX720_PID +      || s->cfg->pid == MX920_PID +      || s->cfg->pid == MB2300_PID +      || s->cfg->pid == MB5000_PID +      || s->cfg->pid == MB5400_PID) +  /* button no. in buf[7] +   * size in buf[10] 01=A4; 02=Letter; 08=10x15; 09=13x18; 0b=auto +   * format in buf[11] 01=JPEG; 02=TIFF; 03=PDF; 04=Kompakt-PDF +   * dpi in buf[12] 01=75; 02=150; 03=300; 04=600 +   * target = format; original = size; scan-resolution = dpi */ +  { +    if (buf[7] & 1) +      s->events = PIXMA_EV_BUTTON1 | buf[11] | buf[10]<<8 | buf[12]<<16;    /* color scan */ +    if (buf[7] & 2) +      s->events = PIXMA_EV_BUTTON2 | buf[11] | buf[10]<<8 | buf[12]<<16;    /* b/w scan */ +  } +  else if (s->cfg->pid == LIDE300_PID +           || s->cfg->pid == LIDE400_PID) +  /* unknown value in buf[4] +   * target in buf[0x13] +   * always set button-1 */ +  { +    if (buf[0x13]) +      s->events = PIXMA_EV_BUTTON1 | buf[0x13]; +  } +  else +  /* button no. in buf[0] +   * original in buf[0] +   * target in buf[1] */ +  { +    /* More than one event can be reported at the same time. */ +    if (buf[3] & 1) +      /* FIXME: This function makes trouble with a lot of scanners +      send_time (s); +       */ +      PDBG (pixma_dbg (1, "WARNING:send_time() disabled!\n")); +    if (buf[9] & 2) +      query_status (s); +    if (buf[0] & 2) +      s->events = PIXMA_EV_BUTTON2 | buf[1] | ((buf[0] & 0xf0) << 4);	/* b/w scan */ +    if (buf[0] & 1) +      s->events = PIXMA_EV_BUTTON1 | buf[1] | ((buf[0] & 0xf0) << 4);	/* color scan */ +  } +  return 1; +} + +static int +wait_until_ready (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  int error, tmo = 120;         /* some scanners need a long timeout */ + +  RET_IF_ERR ((mp->generation >= 3) ? query_status_3 (s) +                                    : query_status (s)); +  while (!is_calibrated (s)) +    { +      WAIT_INTERRUPT (1000); +      if (mp->generation >= 3) +        RET_IF_ERR (query_status_3 (s)); +      else if (s->cfg->pid == MP600_PID || +               s->cfg->pid == MP600R_PID) +        RET_IF_ERR (query_status (s)); +      if (--tmo == 0) +        { +          PDBG (pixma_dbg (1, "WARNING:Timed out in wait_until_ready()\n")); +          PDBG (query_status (s)); +          return PIXMA_ETIMEDOUT; +        } +    } +  return 0; +} + +static void +reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, unsigned n, +                unsigned m, unsigned w, unsigned line_size) +{ +  unsigned i; + +  for (i = 0; i < w; i++) +    { +      memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c); +    } +  memcpy (sptr, linebuf, line_size); +} + +/* the scanned image must be shrinked by factor "scale" + * the image can be formatted as rgb (c=3) or gray (c=1) + * we need to crop the left side (xs) + * we ignore more pixels inside scanned line (wx), behind needed line (w) + * + * example (scale=2): + * line | pixel[0] | pixel[1] | ... | pixel[w-1] + * --------- + *  0   |  rgbrgb  |  rgbrgb  | ... |  rgbrgb + * wx*c |  rgbrgb  |  rgbrgb  | ... |  rgbrgb + */ +uint8_t * +shrink_image (uint8_t * dptr, uint8_t * sptr, unsigned xs, unsigned w, +              unsigned wx, unsigned scale, unsigned c) +{ +  unsigned i, ic; +  uint16_t pixel; +  uint8_t *dst = dptr;  /* don't change dptr */ +  uint8_t *src = sptr;  /* don't change sptr */ + +  /*PDBG (pixma_dbg (4, "%s: w=%d, wx=%d, c=%d, scale=%d\n", +                   __func__, w, wx, c, scale)); +  PDBG (pixma_dbg (4, "\tdptr=%ld, sptr=%ld\n", +                   dptr, sptr));*/ + +  /* crop left side */ +  src += c * xs; + +  /* process line */ +  for (i = 0; i < w; i++) +  { +    /* process rgb or gray pixel */ +    for (ic = 0; ic < c; ic++) +    { +#if 0 +      dst[ic] = src[ic]; +#else +      pixel = 0; + +      /* sum shrink pixels */ +      for (unsigned m = 0; m < scale; m++)    /* get pixels from shrinked lines */ +      { +        for (unsigned n = 0; n < scale; n++)  /* get pixels from same line */ +        { +          pixel += src[ic + c * n + wx * c * m]; +        } +      } +      dst[ic] = pixel / (scale * scale); +#endif +    } + +    /* jump over shrinked data */ +    src += c * scale; +    /* next pixel */ +    dst += c; +  } + +  return dst; +} + +/* This function deals with Generation >= 3 high dpi images. + * Each complete line in mp->imgbuf is processed for reordering pixels above + * 600 dpi for Generation >= 3. */ +static unsigned +post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; +  unsigned c, lines, line_size, n, m, cw, cx; +  uint8_t *sptr, *dptr, *gptr, *cptr; + +  if (s->param->mode_jpeg) +    { +      /* No post-processing, send raw JPEG data to main */ +      ib->rptr = mp->imgbuf; +      ib->rend = mp->data_left_ofs; +      return 0;    /* # of non processed bytes */ +    } + +  /* process image sizes */ +  c = s->param->channels +      * ((s->param->software_lineart) ? 8 : s->param->depth) / 8;   /* color channels count */ +  cw = c * s->param->w;                                             /* image width */ +  cx = c * s->param->xs;                                            /* x-offset */ + +  /* special image format parameters +   * n: no. of sub-images +   * m: sub-image width +   */ +  if (mp->generation >= 3) +    n = s->param->xdpi / 600; +  else +    n = s->param->xdpi / 2400; +  if (s->cfg->pid == MP600_PID || s->cfg->pid == MP600R_PID) +    n = s->param->xdpi / 1200; +  m = (n > 0) ? s->param->wx / n : 1; + +  /* Initialize pointers */ +  sptr = dptr = gptr = cptr = mp->imgbuf; + +  /* walk through complete received lines */ +  line_size = get_cis_line_size (s); +  lines = (mp->data_left_ofs - mp->imgbuf) / line_size; +  if (lines > 0) +    { +      unsigned i; + +      /*PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, wx=%i, line_size=%u, cx=%u, cw=%u ***** \n", +                       c, n, m, s->param->wx, line_size, cx, cw));*/ +      /*PDBG (pixma_dbg (4, "*post_process_image_data***** lines = %i ***** \n", lines));*/ + +      for (i = 0; i < lines; i++, sptr += line_size) +        { +          /*PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, w=%i, line_size=%u ***** \n", +                           c, n, m, s->param->wx, line_size));*/ +          /*PDBG (pixma_dbg (4, "*post_process_image_data***** Pointers: sptr=%lx, dptr=%lx, linebuf=%lx ***** \n", +                           sptr, dptr, mp->linebuf));*/ + +          /* special image format for *most* devices at high dpi. +           * MP220, MX360 and generation 5 scanners are exceptions */ +          if (n > 1 +              && s->cfg->pid != MP220_PID +              && s->cfg->pid != MP490_PID +              && s->cfg->pid != MX360_PID +              && (mp->generation < 5 +                  /* generation 5 scanners *with* special image format */ +                  || s->cfg->pid == MG2200_PID +                  || s->cfg->pid == MG3200_PID +                  || s->cfg->pid == MG4200_PID +                  || s->cfg->pid == MG5600_PID +                  || s->cfg->pid == MG5700_PID +                  || s->cfg->pid == MG6200_PID +                  || s->cfg->pid == MP230_PID +                  || s->cfg->pid == MX470_PID +                  || s->cfg->pid == MX510_PID +                  || s->cfg->pid == MX520_PID)) +              reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx, line_size); + + +          /* scale image */ +          if (mp->scale > 1) +          { +            /* Crop line inside shrink_image() */ +            shrink_image(cptr, sptr, s->param->xs, s->param->w, s->param->wx, mp->scale, c); +          } +          else +          { +            /* Crop line to selected borders */ +            memmove(cptr, sptr + cx, cw); +          } + +          /* Color / Gray to Lineart convert */ +          if (s->param->software_lineart) +              cptr = gptr = pixma_binarize_line (s->param, gptr, cptr, s->param->w, c); +          else +              cptr += cw; +        } +    } +  ib->rptr = mp->imgbuf; +  ib->rend = cptr; +  return mp->data_left_ofs - sptr;    /* # of non processed bytes */ +} + +static int +mp150_open (pixma_t * s) +{ +  mp150_t *mp; +  uint8_t *buf; + +  mp = (mp150_t *) calloc (1, sizeof (*mp)); +  if (!mp) +    return PIXMA_ENOMEM; + +  buf = (uint8_t *) malloc (CMDBUF_SIZE + IMAGE_BLOCK_SIZE); +  if (!buf) +    { +      free (mp); +      return PIXMA_ENOMEM; +    } + +  s->subdriver = mp; +  mp->state = state_idle; + +  mp->cb.buf = buf; +  mp->cb.size = CMDBUF_SIZE; +  mp->cb.res_header_len = 8; +  mp->cb.cmd_header_len = 16; +  mp->cb.cmd_len_field_ofs = 14; + +  mp->imgbuf = buf + CMDBUF_SIZE; + +  /* General rules for setting Pixma protocol generation # */ +  mp->generation = (s->cfg->pid >= MP160_PID) ? 2 : 1; + +  if (s->cfg->pid >= MX7600_PID) +    mp->generation = 3; + +  if (s->cfg->pid >= MP250_PID) +    mp->generation = 4; + +  if (s->cfg->pid >= MG2100_PID)        /* this scanners generation doesn't need */ +    mp->generation = 5;                 /* special image conversion @ high dpi */ + +  /* And exceptions to be added here */ +  if (s->cfg->pid == MP140_PID) +    mp->generation = 2; + +  PDBG (pixma_dbg (3, "*mp150_open***** This is a generation %d scanner.  *****\n", mp->generation)); + +  /* adf scanning */ +  mp->adf_state = state_idle; + +  if (mp->generation < 4) +    { +      query_status (s); +      handle_interrupt (s, 200); +    } +  return 0; +} + +static void +mp150_close (pixma_t * s) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  mp150_finish_scan (s); +  free (mp->cb.buf); +  free (mp); +  s->subdriver = NULL; +} + +static int +mp150_check_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  mp150_t *mp = (mp150_t *) s->subdriver; + +  /* PDBG (pixma_dbg (4, "*mp150_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */ + +  /* MP150 only supports 8 bit per channel in color and grayscale mode */ +  if (sp->depth != 1) +    { +      sp->software_lineart = 0; +      sp->depth = 8; +    } +  else +    { +      /* software lineart */ +      sp->software_lineart = 1; +      sp->depth = 1; +      sp->channels = 1; +    } + +  /* for software lineart w must be a multiple of 8 */ +  if (sp->software_lineart == 1 && sp->w % 8) +    { +      unsigned w_max; + +      sp->w += 8 - (sp->w % 8); + +      /* do not exceed the scanner capability */ +      w_max = s->cfg->width * s->cfg->xdpi / 75; +      w_max -= w_max % 8; +      if (sp->w > w_max) +        sp->w = w_max; +    } + +  if (mp->generation >= 2) +    { +      /* mod 32 and expansion of the X scan limits */ +      /*PDBG (pixma_dbg (4, "*mp150_check_param***** ----- Initially: x=%i, y=%i, w=%i, h=%i *****\n", sp->x, sp->y, sp->w, sp->h));*/ +      sp->xs = (sp->x * mp->scale) % 32; +    } +  else +      sp->xs = 0; +  /*PDBG (pixma_dbg (4, "*mp150_check_param***** Selected origin, origin shift: %i, %i *****\n", sp->x, sp->xs));*/ +  sp->wx = calc_raw_width (mp, sp); +  sp->line_size = sp->w * sp->channels * (((sp->software_lineart) ? 8 : sp->depth) / 8);              /* bytes per line per color after cropping */ +  /*PDBG (pixma_dbg (4, "*mp150_check_param***** Final scan width and line-size: %i, %li *****\n", sp->wx, sp->line_size));*/ + +  /* Some exceptions here for particular devices */ +  /* Those devices can scan up to legal 14" with ADF, but A4 11.7" in flatbed */ +  /* PIXMA_CAP_ADF also works for PIXMA_CAP_ADFDUP */ +  if ((s->cfg->cap & PIXMA_CAP_ADF) && sp->source == PIXMA_SOURCE_FLATBED) +    sp->h = MIN (sp->h, 877 * sp->xdpi / 75); + +  if (sp->source == PIXMA_SOURCE_ADF || sp->source == PIXMA_SOURCE_ADFDUP) +    { +      uint8_t k = 1; + +  /* ADF/ADF duplex mode: max scan res is 600 dpi, at least for generation 4+ */ +      if (mp->generation >= 4) +        k = sp->xdpi / MIN (sp->xdpi, 600); +      sp->x /= k; +      sp->xs /= k; +      sp->y /= k; +      sp->w /= k; +      sp->wx /= k; +      sp->h /= k; +      sp->xdpi /= k; +      sp->ydpi = sp->xdpi; +    } + +  sp->mode_jpeg = (s->cfg->cap & PIXMA_CAP_ADF_JPEG) && +                      (sp->source == PIXMA_SOURCE_ADF || +                       sp->source == PIXMA_SOURCE_ADFDUP); + +  mp->scale = 1; +  if (s->cfg->min_xdpi && sp->xdpi < s->cfg->min_xdpi) +  { +    mp->scale = s->cfg->min_xdpi / sp->xdpi; +  } +  /*PDBG (pixma_dbg (4, "*mp150_check_param***** xdpi=%u, min_xdpi=%u, scale=%u *****\n", +                   sp->xdpi, s->cfg->min_xdpi, mp->scale));*/ + +  /*PDBG (pixma_dbg (4, "*mp150_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx));*/ +  return 0; +} + +static int +mp150_scan (pixma_t * s) +{ +  int error = 0, tmo; +  mp150_t *mp = (mp150_t *) s->subdriver; + +  if (mp->state != state_idle) +    return PIXMA_EBUSY; + +  /* no paper inserted after first adf page => abort session */ +  if (s->param->adf_pageid && is_scanning_from_adf(s) && mp->adf_state == state_idle) +  { +    return PIXMA_ENO_PAPER; +  } + +  /* Generation 4+: send XML dialog */ +  /* adf: first page or idle */ +  if (mp->generation >= 4 && mp->adf_state == state_idle) +    { +      if (!send_xml_dialog (s, XML_START_1)) +        return PIXMA_EPROTO; +      if (!send_xml_dialog (s, XML_START_2)) +        return PIXMA_EPROTO; +    } + +  /* clear interrupt packets buffer */ +  while (handle_interrupt (s, 0) > 0) +    { +    } + +  /* FIXME: Duplex ADF: check paper status only before odd pages (1,3,5,...). */ +  if (is_scanning_from_adf (s)) +    { +      if ((error = query_status (s)) < 0) +        return error; + +      /* wait for inserted paper +       * timeout: 10 sec */ +      tmo = 10; +      while (!has_paper (s) && --tmo >= 0) +        { +          if ((error = query_status (s)) < 0) +            return error; +          WAIT_INTERRUPT (1000); +          PDBG (pixma_dbg +            (2, "No paper in ADF. Timed out in %d sec.\n", tmo)); +        } + +      /* no paper inserted +       * => abort session */ +      if (!has_paper (s)) +      { +        PDBG (pixma_dbg (4, "*mp150_scan***** no paper in ADF *****\n")); +        error = abort_session (s); +        if (error < 0) +          return error; + +        /* Generation 4+: send XML dialog */ +        /* adf: first page or idle */ +        if (mp->generation >= 4 && mp->adf_state == state_idle) +        { +          if (!send_xml_dialog (s, XML_END)) +            return PIXMA_EPROTO; +        } + +        return PIXMA_ENO_PAPER; +      } +    } + +  tmo = 10; +  /* adf: first page or idle */ +  if (mp->generation <= 2 || mp->adf_state == state_idle) +    { /* single sheet or first sheet from ADF */ +      PDBG (pixma_dbg (4, "*mp150_scan***** start scanning *****\n")); +      error = start_session (s); +      while (error == PIXMA_EBUSY && --tmo >= 0) +        { +          if (s->cancel) +            { +              error = PIXMA_ECANCELED; +              break; +            } +          PDBG (pixma_dbg +          (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1)); +          pixma_sleep (1000000); +          error = start_session (s); +        } +      if (error == PIXMA_EBUSY || error == PIXMA_ETIMEDOUT) +        { +          /* The scanner maybe hangs. We try to empty output buffer of the +           * scanner and issue the cancel command. */ +          PDBG (pixma_dbg (2, "Scanner hangs? Sending abort_session command.\n")); +          drain_bulk_in (s); +          abort_session (s); +          pixma_sleep (500000); +          error = start_session (s); +        } +      if ((error >= 0) || (mp->generation >= 3)) +        mp->state = state_warmup; +      if ((error >= 0) && (mp->generation <= 2)) +        error = select_source (s); +      if ((error >= 0) && !is_scanning_jpeg (s)) +        { +          int i; + +          for (i = (mp->generation >= 3) ? 3 : 1 ; i > 0 && error >= 0; i--) +            error = send_gamma_table (s); +        } +    } +  else   /* ADF pageid != 0 and gen3 or above */ +  { /* next sheet from ADF */ +    PDBG (pixma_dbg (4, "*mp150_scan***** scan next sheet from ADF  *****\n")); +    pixma_sleep (1000000); +  } +  if ((error >= 0) || (mp->generation >= 3)) +    mp->state = state_warmup; +  if (error >= 0) +    error = send_scan_param (s); +  if ((error >= 0) && (mp->generation >= 3)) +    error = start_scan_3 (s); +  if (error < 0) +    { +      mp->last_block = 0x38;   /* Force abort session if ADF scan */ +      mp150_finish_scan (s); +      return error; +    } + +  /* ADF scanning active */ +  if (is_scanning_from_adf (s)) +    mp->adf_state = state_scanning; +  return 0; +} + +static int +mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib) +{ +  int error; +  mp150_t *mp = (mp150_t *) s->subdriver; +  unsigned block_size, bytes_received, proc_buf_size, line_size; +  uint8_t header[16]; + +  if (mp->state == state_warmup) +    { +      RET_IF_ERR (wait_until_ready (s)); +      pixma_sleep (1000000);	/* No need to sleep, actually, but Window's driver +				 * sleep 1.5 sec. */ +      mp->state = state_scanning; +      mp->last_block = 0; + +      line_size = get_cis_line_size (s); +      proc_buf_size = 2 * line_size; +      mp->cb.buf = realloc (mp->cb.buf, +             CMDBUF_SIZE + IMAGE_BLOCK_SIZE + proc_buf_size); +      if (!mp->cb.buf) +        return PIXMA_ENOMEM; +      mp->linebuf = mp->cb.buf + CMDBUF_SIZE; +      mp->imgbuf = mp->data_left_ofs = mp->linebuf + line_size; +      mp->data_left_len = 0; +    } + +  do +    { +      if (s->cancel) +      { +        PDBG (pixma_dbg (4, "*mp150_fill_buffer***** s->cancel  *****\n")); +        return PIXMA_ECANCELED; +      } +      if ((mp->last_block & 0x28) == 0x28) +        {  /* end of image */ +           PDBG (pixma_dbg (4, "*mp150_fill_buffer***** end of image  *****\n")); +           mp->state = state_finished; +           return 0; +        } +      /*PDBG (pixma_dbg (4, "*mp150_fill_buffer***** moving %u bytes into buffer *****\n", mp->data_left_len));*/ +      memmove (mp->imgbuf, mp->data_left_ofs, mp->data_left_len); +      error = read_image_block (s, header, mp->imgbuf + mp->data_left_len); +      if (error < 0) +        { +          PDBG (pixma_dbg (4, "*mp150_fill_buffer***** scanner error (%d): end scan  *****\n", error)); +          mp->last_block = 0x38;        /* end scan in mp150_finish_scan() */ +          if (error == PIXMA_ECANCELED) +            { +               /* NOTE: I see this in traffic logs but I don't know its meaning. */ +               read_error_info (s, NULL, 0); +            } +          return error; +        } + +      bytes_received = error; +      /*PDBG (pixma_dbg (4, "*mp150_fill_buffer***** %u bytes received by read_image_block *****\n", bytes_received));*/ +      block_size = pixma_get_be32 (header + 12); +      mp->last_block = header[8] & 0x38; +      if ((header[8] & ~0x38) != 0) +        { +          PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n")); +          PDBG (pixma_hexdump (1, header, 16)); +        } +      PASSERT (bytes_received == block_size); + +      if (block_size == 0) +        {     /* no image data at this moment. */ +          pixma_sleep (10000); +        } +      /* Post-process the image data */ +      mp->data_left_ofs = mp->imgbuf + mp->data_left_len + bytes_received; +      mp->data_left_len = post_process_image_data (s, ib); +      mp->data_left_ofs -= mp->data_left_len; +    } +  while (ib->rend == ib->rptr); + +  return ib->rend - ib->rptr; +} + +static void +mp150_finish_scan (pixma_t * s) +{ +  int error; +  mp150_t *mp = (mp150_t *) s->subdriver; + +  switch (mp->state) +    { +    case state_transfering: +      drain_bulk_in (s); +      /* fall through */ +    case state_scanning: +    case state_warmup: +    case state_finished: +      /* FIXME: to process several pages ADF scan, must not send +       * abort_session and start_session between pages (last_block=0x28) */ +      if (mp->generation <= 2 || !is_scanning_from_adf (s) || mp->last_block == 0x38) +        { +          PDBG (pixma_dbg (4, "*mp150_finish_scan***** abort session  *****\n")); +          error = abort_session (s);  /* FIXME: it probably doesn't work in duplex mode! */ +          if (error < 0) +            PDBG (pixma_dbg (1, "WARNING:abort_session() failed %d\n", error)); + +          /* Generation 4+: send XML end of scan dialog */ +          if (mp->generation >= 4) +            { +              if (!send_xml_dialog (s, XML_END)) +                PDBG (pixma_dbg (1, "WARNING:XML_END dialog failed \n")); +            } +        } +      else +        PDBG (pixma_dbg (4, "*mp150_finish_scan***** wait for next page from ADF  *****\n")); + +        mp->state = state_idle; +      /* fall through */ +    case state_idle: +      break; +    } +} + +static void +mp150_wait_event (pixma_t * s, int timeout) +{ +  /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for +   * instance. */ +  while (s->events == 0 && handle_interrupt (s, timeout) > 0) +    { +    } +} + +static int +mp150_get_status (pixma_t * s, pixma_device_status_t * status) +{ +  int error; + +  RET_IF_ERR (query_status (s)); +  status->hardware = PIXMA_HARDWARE_OK; +  status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER; +  status->cal = +    (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF; +  return 0; +} + +static const pixma_scan_ops_t pixma_mp150_ops = { +  mp150_open, +  mp150_close, +  mp150_scan, +  mp150_fill_buffer, +  mp150_finish_scan, +  mp150_wait_event, +  mp150_check_param, +  mp150_get_status +}; + +#define DEVICE(name, model, pid, min_dpi, dpi, adftpu_min_dpi, adftpu_max_dpi, w, h, cap) { \ +        name,              /* name */               \ +        model,             /* model */              \ +        CANON_VID, pid,    /* vid pid */            \ +        0,                 /* iface */              \ +        &pixma_mp150_ops,  /* ops */                \ +        min_dpi,           /* min_xdpi */           \ +        dpi, 2*(dpi),      /* xdpi, ydpi */         \ +        adftpu_min_dpi, adftpu_max_dpi,         /* adftpu_min_dpi, adftpu_max_dpi */ \ +        0, 0,              /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */  \ +        w, h,              /* width, height */      \ +        PIXMA_CAP_EASY_RGB|                         \ +        PIXMA_CAP_GRAY|    /* CIS with native grayscale */ \ +        PIXMA_CAP_LINEART| /* all scanners with software lineart */ \ +        PIXMA_CAP_GAMMA_TABLE|PIXMA_CAP_EVENTS|cap  \ +} + +#define END_OF_DEVICE_LIST DEVICE(NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0) + +const pixma_config_t pixma_mp150_devices[] = { +  /* Generation 1: CIS */ +  DEVICE ("Canon PIXMA MP150", "MP150", MP150_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP170", "MP170", MP170_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP450", "MP450", MP450_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP500", "MP500", MP500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP530", "MP530", MP530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), + +  /* Generation 2: CIS */ +  DEVICE ("Canon PIXMA MP140", "MP140", MP140_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP160", "MP160", MP160_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP180", "MP180", MP180_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP460", "MP460", MP460_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP510", "MP510", MP510_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP600", "MP600", MP600_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP600R", "MP600R", MP600R_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Generation 3: CIS */ +  DEVICE ("Canon PIXMA MP210", "MP210", MP210_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP220", "MP220", MP220_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP470", "MP470", MP470_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP520", "MP520", MP520_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP610", "MP610", MP610_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  DEVICE ("Canon PIXMA MX300", "MX300", MX300_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MX310", "MX310", MX310_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX700", "MX700", MX700_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX850", "MX850", MX850_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA MX7600", "MX7600", MX7600_PID, 0, 4800, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), + +  DEVICE ("Canon PIXMA MP630", "MP630", MP630_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP620", "MP620", MP620_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP540", "MP540", MP540_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP480", "MP480", MP480_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP240", "MP240", MP240_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP260", "MP260", MP260_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP190", "MP190", MP190_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* PIXMA 2009 vintage */ +  DEVICE ("Canon PIXMA MX320", "MX320", MX320_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX330", "MX330", MX330_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX860", "MX860", MX860_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +/* width and height adjusted to flatbed size 21.8 x 30.2 cm^2 respective + * Not sure if anything's going wrong here, leaving as is +  DEVICE ("Canon PIXMA MX860", "MX860", MX860_PID, 0, 2400, 0, 0, 638, 880, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),*/ + +  /* PIXMA 2010 vintage */ +  DEVICE ("Canon PIXMA MX340", "MX340", MX340_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX350", "MX350", MX350_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX870", "MX870", MX870_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), + +  /* PIXMA 2011 vintage */ +  DEVICE ("Canon PIXMA MX360", "MX360", MX360_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX410", "MX410", MX410_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX420", "MX420", MX420_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX880 Series", "MX880", MX880_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), + +  /* Generation 4: CIS */ +  DEVICE ("Canon PIXMA MP640", "MP640", MP640_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP560", "MP560", MP560_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP550", "MP550", MP550_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP490", "MP490", MP490_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP250", "MP250", MP250_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP270", "MP270", MP270_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2010) Generation 4 CIS */ +  DEVICE ("Canon PIXMA MP280",  "MP280",  MP280_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), /* TODO: 1200dpi doesn't work yet */ +  DEVICE ("Canon PIXMA MP495",  "MP495",  MP495_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5100", "MG5100", MG5100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5200", "MG5200", MG5200_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6100", "MG6100", MG6100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2011) Generation 5 CIS */ +  DEVICE ("Canon PIXMA MG2100", "MG2100", MG2100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG3100", "MG3100", MG3100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG4100", "MG4100", MG4100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5300", "MG5300", MG5300_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6200", "MG6200", MG6200_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MP493",  "MP493",  MP493_PID, 0,  1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E500",   "E500",   E500_PID, 0,   1200, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2012) Generation 5 CIS */ +  DEVICE ("Canon PIXMA MX370 Series", "MX370", MX370_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX430 Series", "MX430", MX430_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX510 Series", "MX510", MX510_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX710 Series", "MX710", MX710_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA MX890 Series", "MX890", MX890_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA E600 Series",  "E600",  E600_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MG4200", "MG4200", MG4200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2013) Generation 5 CIS */ +  DEVICE ("Canon PIXMA E510",  "E510",  E510_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E610",  "E610",  E610_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MP230", "MP230", MP230_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG2200 Series", "MG2200", MG2200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG3200 Series", "MG3200", MG3200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5400 Series", "MG5400", MG5400_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6300 Series", "MG6300", MG6300_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MX390 Series", "MX390", MX390_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX450 Series", "MX450", MX450_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX520 Series", "MX520", MX520_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX720 Series", "MX720", MX720_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA MX920 Series", "MX920", MX920_PID, 0, 2400, 0, 600, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA MG2400 Series", "MG2400", MG2400_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG2500 Series", "MG2500", MG2500_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG3500 Series", "MG3500", MG3500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5500 Series", "MG5500", MG5500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6400 Series", "MG6400", MG6400_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6500 Series", "MG6500", MG6500_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG7100 Series", "MG7100", MG7100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2014) Generation 5 CIS */ +  DEVICE ("Canon PIXMA MX470 Series", "MX470", MX470_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MX530 Series", "MX530", MX530_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon MAXIFY MB5000 Series", "MB5000", MB5000_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon MAXIFY MB5300 Series", "MB5300", MB5300_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon MAXIFY MB2000 Series", "MB2000", MB2000_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon MAXIFY MB2100 Series", "MB2100", MB2100_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon MAXIFY MB2300 Series", "MB2300", MB2300_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon MAXIFY MB2700 Series", "MB2700", MB2700_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon PIXMA E400",  "E400",  E400_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E560",  "E560",  E560_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG7500 Series", "MG7500", MG7500_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6600 Series", "MG6600", MG6600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5600 Series", "MG5600", MG5600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG2900 Series", "MG2900", MG2900_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E460 Series",  "E460",  E460_PID, 0,  600, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2015) Generation 5 CIS */ +  DEVICE ("Canon PIXMA MX490 Series", "MX490", MX490_PID, 0, 600, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA E480 Series",  "E480",  E480_PID, 0, 600, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MG3600 Series", "MG3600", MG3600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG7700 Series", "MG7700", MG7700_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6900 Series", "MG6900", MG6900_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG6800 Series", "MG6800", MG6800_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG5700 Series", "MG5700", MG5700_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2016) Generation 5 CIS */ +  DEVICE ("Canon PIXMA G3000", "G3000", G3000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA G2000", "G2000", G2000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS9000 Series", "TS9000", TS9000_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8000 Series", "TS8000", TS8000_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6000 Series", "TS6000", TS6000_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS5000 Series", "TS5000", TS5000_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA MG3000 Series", "MG3000", MG3000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E470 Series", "E470", E470_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E410 Series", "E410", E410_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2017) Generation 5 CIS */ +  DEVICE ("Canon PIXMA G4000", "G4000", G4000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6100 Series", "TS6100", TS6100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS5100 Series", "TS5100", TS5100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS3100 Series", "TS3100", TS3100_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E3100 Series", "E3100", E3100_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2018) Generation 5 CIS */ +  DEVICE ("Canon MAXIFY MB5400 Series", "MB5400", MB5400_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP | PIXMA_CAP_ADF_JPEG), +  DEVICE ("Canon MAXIFY MB5100 Series", "MB5100", MB5100_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP), +  DEVICE ("Canon PIXMA TS9100 Series", "TS9100", TS9100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TR8500 Series", "TR8500", TR8500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TR7500 Series", "TR7500", TR7500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TS9500 Series", "TS9500", TS9500_PID, 0, 1200, 0, 600, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("CanoScan LiDE 400", "LIDE400", LIDE400_PID, 300, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("CanoScan LiDE 300", "LIDE300", LIDE300_PID, 300, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  /* Latest devices (2019) Generation 5 CIS */ +  DEVICE ("Canon PIXMA TS8100 Series", "TS8100", TS8100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA G2010 Series", "G2010", G2010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA G3010 Series", "G3010", G3010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA G4010 Series", "G4010", G4010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TS9180 Series", "TS9180", TS9180_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8180 Series", "TS8180", TS8180_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6180 Series", "TS6180", TS6180_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TR8580 Series", "TR8580", TR8580_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TS8130 Series", "TS8130", TS8130_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6130 Series", "TS6130", TS6130_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TR8530 Series", "TR8530", TR8530_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TR7530 Series", "TR7530", TR7530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXUS XK50 Series", "XK50", XK50_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXUS XK70 Series", "XK70", XK70_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TR4500 Series", "TR4500", TR4500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA E4200 Series", "E4200", E4200_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TS6200 Series", "TS6200", TS6200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6280 Series", "TS6280", TS6280_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6230 Series", "TS6230", TS6230_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8200 Series", "TS8200", TS8200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8280 Series", "TS8280", TS8280_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8230 Series", "TS8230", TS8230_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS9580 Series", "TS9580", TS9580_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA TR9530 Series", "TR9530", TR9530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA G6000 Series", "G6000", G6000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA G6080 Series", "G6080", G6080_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXUS XK80 Series", "XK80", XK80_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS5300 Series", "TS5300", TS5300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS5380 Series", "TS5380", TS5380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6300 Series", "TS6300", TS6300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6380 Series", "TS6380", TS6380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS7330 Series", "TS7330", TS7330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8380 Series", "TS8380", TS8380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS8330 Series", "TS8330", TS8330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA XK60 Series", "XK60", XK60_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS6330 Series", "TS6330", TS6330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA TS3300 Series", "TS3300", TS3300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS), +  DEVICE ("Canon PIXMA E3300 Series", "E3300", E3300_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), + +  END_OF_DEVICE_LIST +}; diff --git a/backend/pixma/pixma_mp730.c b/backend/pixma/pixma_mp730.c new file mode 100644 index 0000000..93d518b --- /dev/null +++ b/backend/pixma/pixma_mp730.c @@ -0,0 +1,846 @@ +/* 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h>		/* localtime(C90) */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +#define IMAGE_BLOCK_SIZE (0xc000) +#define CMDBUF_SIZE 512 + +#define MP10_PID 0x261f + +#define MP730_PID 0x262f +#define MP700_PID 0x2630 + +#define MP5_PID 0x2635          /* untested */ + +#define MP360_PID 0x263c +#define MP370_PID 0x263d +#define MP390_PID 0x263e +#define MP375R_PID 0x263f       /* untested */ + +#define MP740_PID 0x264c	/* Untested */ +#define MP710_PID 0x264d + +#define MF5730_PID 0x265d	/* Untested */ +#define MF5750_PID 0x265e	/* Untested */ +#define MF5770_PID 0x265f +#define MF3110_PID 0x2660 + +#define IR1020_PID 0x26e6 + +enum mp730_state_t +{ +  state_idle, +  state_warmup, +  state_scanning, +  state_transfering, +  state_finished +}; + +enum mp730_cmd_t +{ +  cmd_start_session = 0xdb20, +  cmd_select_source = 0xdd20, +  cmd_gamma         = 0xee20, +  cmd_scan_param    = 0xde20, +  cmd_status        = 0xf320, +  cmd_abort_session = 0xef20, +  cmd_time          = 0xeb80, +  cmd_read_image    = 0xd420, +  cmd_error_info    = 0xff20, + +  cmd_activate      = 0xcf60, +  cmd_calibrate     = 0xe920 +}; + +typedef struct mp730_t +{ +  enum mp730_state_t state; +  pixma_cmdbuf_t cb; +  unsigned raw_width; +  uint8_t current_status[12]; + +  uint8_t *buf, *imgbuf, *lbuf; +  unsigned imgbuf_len; + +  unsigned last_block:1; +} mp730_t; + + +static void mp730_finish_scan (pixma_t * s); + +static int +has_paper (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  return (mp->current_status[1] == 0); +} + +static void +drain_bulk_in (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0); +} + +static int +abort_session (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session); +} + +static int +query_status (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_status, 0, 12); +  error = pixma_exec (s, &mp->cb); +  if (error >= 0) +    { +      memcpy (mp->current_status, data, 12); +      PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u\n", +		       data[1], data[8], data[7])); +    } +  return error; +} + +static int +activate (pixma_t * s, uint8_t x) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mp->cb, cmd_activate, 10, 0); +  data[0] = 1; +  data[3] = x; +  return pixma_exec (s, &mp->cb); +} + +static int +start_session (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_start_session); +} + +static int +select_source (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mp->cb, cmd_select_source, 10, 0); +  switch (s->param->source) +    { +    case PIXMA_SOURCE_ADF: +      data[0] = 2; +      break; + +    case PIXMA_SOURCE_ADFDUP: +      data[0] = 2; +      data[5] = 3; +      break; + +    default: +      data[0] = 1; +      break; +    } +  return pixma_exec (s, &mp->cb); +} + +static int +send_scan_param (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *data; + +  data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x2e, 0); +  pixma_set_be16 (s->param->xdpi | 0x1000, data + 0x04); +  pixma_set_be16 (s->param->ydpi | 0x1000, data + 0x06); +  pixma_set_be32 (s->param->x, data + 0x08); +  pixma_set_be32 (s->param->y, data + 0x0c); +  pixma_set_be32 (mp->raw_width, data + 0x10); +  pixma_set_be32 (s->param->h, data + 0x14); + +  if (s->param->channels == 1) +    { +      if (s->param->depth == 1) +        data[0x18] = 0x01; +      else +        data[0x18] = 0x04; +    } +  else +    data[0x18] = 0x08; + +  data[0x19] = s->param->channels * s->param->depth;  /* bits per pixel, for lineart should be 0x01 */ +  data[0x1e] = (s->param->depth == 1) ? 0x80 : 0x00;  /* modify for lineart: 0x80 NEW */ +  data[0x1f] = (s->param->depth == 1) ? 0x80 : 0x7f;  /* modify for lineart: 0x80 */ +  data[0x20] = (s->param->depth == 1) ? 0x01 : 0xff;  /* modify for lineart: 0x01 */ +  data[0x23] = 0x81; + +  return pixma_exec (s, &mp->cb); +} + +static int +calibrate (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_calibrate); +} + +static int +read_image_block (pixma_t * s, uint8_t * header, uint8_t * data) +{ +  static const uint8_t cmd[10] =	/* 0xd420 cmd */ +  { 0xd4, 0x20, 0, 0, 0, 0, 0, IMAGE_BLOCK_SIZE / 256, 4, 0 }; +  mp730_t *mp = (mp730_t *) s->subdriver; +  const int hlen = 2 + 4; +  int error, datalen; + +  mp->state = state_transfering; +  mp->cb.reslen = +    pixma_cmd_transaction (s, cmd, sizeof (cmd), mp->cb.buf, 512); +  datalen = mp->cb.reslen; +  if (datalen < 0) +    return datalen; + +  memcpy (header, mp->cb.buf, hlen); +  if (datalen >= hlen) +    { +      datalen -= hlen; +      memcpy (data, mp->cb.buf + hlen, datalen); +      data += datalen; +      if (mp->cb.reslen == 512) +	{ +	  error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen); +	  if (error < 0) +	    return error; +	  datalen += error; +	} +    } + +  mp->state = state_scanning; +  mp->cb.expected_reslen = 0; +  error = pixma_check_result (&mp->cb); +  if (error < 0) +    return error; +  if (mp->cb.reslen < hlen) +    return PIXMA_EPROTO; +  return datalen; +} + +static int +send_time (pixma_t * s) +{ +  /* Why does a scanner need a time? */ +  time_t now; +  struct tm *t; +  uint8_t *data; +  mp730_t *mp = (mp730_t *) s->subdriver; + +  data = pixma_newcmd (&mp->cb, cmd_time, 20, 0); +  pixma_get_time (&now, NULL); +  t = localtime (&now); +  strftime ((char *) data, 16, "%y/%m/%d %H:%M", t); +  PDBG (pixma_dbg (3, "Sending time: '%s'\n", (char *) data)); +  return pixma_exec (s, &mp->cb); +} + +static int +handle_interrupt (pixma_t * s, int timeout) +{ +  uint8_t buf[16]; +  int len; + +  len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout); +  if (len == PIXMA_ETIMEDOUT) +    return 0; +  if (len < 0) +    return len; +  switch (s->cfg->pid) +    { +    case MP360_PID: +    case MP370_PID: +    case MP375R_PID: +    case MP390_PID: +    case MF5730_PID: +    case MF5750_PID: +    case MF5770_PID: +    case MF3110_PID: +    case IR1020_PID: +      if (len != 16) +	{ +	  PDBG (pixma_dbg +		(1, "WARNING:unexpected interrupt packet length %d\n", len)); +	  return PIXMA_EPROTO; +	} +      if (buf[12] & 0x40) +	query_status (s); +      if (buf[10] & 0x40) +	send_time (s); +      /* FIXME: following is unverified! */ +      if (buf[15] & 1) +	s->events = PIXMA_EV_BUTTON2;	/* b/w scan */ +      if (buf[15] & 2) +	s->events = PIXMA_EV_BUTTON1;	/* color scan */ +      break; + +    case MP5_PID: +    case MP10_PID: +    case MP700_PID: +    case MP730_PID: +    case MP710_PID: +    case MP740_PID: +      if (len != 8) +	{ +	  PDBG (pixma_dbg +		(1, "WARNING:unexpected interrupt packet length %d\n", len)); +	  return PIXMA_EPROTO; +	} +      if (buf[7] & 0x10) +	s->events = PIXMA_EV_BUTTON1; +      if (buf[5] & 8) +	send_time (s); +      break; + +    default: +      PDBG (pixma_dbg (1, "WARNING:unknown interrupt, please report!\n")); +      PDBG (pixma_hexdump (1, buf, len)); +    } +  return 1; +} + +static int +has_ccd_sensor (pixma_t * s) +{ +  return (s->cfg->pid == MP360_PID || +          s->cfg->pid == MP370_PID || +          s->cfg->pid == MP375R_PID || +          s->cfg->pid == MP390_PID || +          s->cfg->pid == MF5730_PID || +          s->cfg->pid == MF5750_PID || +          s->cfg->pid == MF5770_PID); +} + +static int +read_error_info (pixma_t * s, void *buf, unsigned size) +{ +  unsigned len = 16; +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len); +  error = pixma_exec (s, &mp->cb); +  if (error < 0) +    return error; +  if (buf && len < size) +    { +      size = len; +      /* NOTE: I've absolutely no idea what the returned data mean. */ +      memcpy (buf, data, size); +      error = len; +    } +  return error; +} + +static int +step1 (pixma_t * s) +{ +  int error; + +  error = query_status (s); +  if (error < 0) +    return error; +  if ((s->param->source == PIXMA_SOURCE_ADF +       || s->param->source == PIXMA_SOURCE_ADFDUP) +      && !has_paper (s)) +    return PIXMA_ENO_PAPER; +  if (has_ccd_sensor (s)) +    { +      switch (s->cfg->pid) +        { +          case MF5730_PID: +          case MF5750_PID: +          case MF5770_PID: +          /* MF57x0: Wait 10 sec before starting for 1st page only */ +            if (s->param->adf_pageid == 0) +	      { +                int tmo = 10;  /* like Windows driver, 10 sec CCD calibration ? */ +                while (--tmo >= 0) +                  { +                    error = handle_interrupt (s, 1000);		\ +                    if (s->cancel)				\ +                      return PIXMA_ECANCELED;			\ +                    if (error != PIXMA_ECANCELED && error < 0)	\ +                      return error; +                    PDBG (pixma_dbg (2, "CCD Calibration ends in %d sec.\n", tmo)); +                  } +              } +            break; + +          default: +            break; +        } + +      activate (s, 0); +      error = calibrate (s); + +      switch (s->cfg->pid) +        { +          case MF5730_PID: +          case MF5750_PID: +          case MF5770_PID: +          /* MF57x0: calibration returns PIXMA_STATUS_FAILED */ +            if (error == PIXMA_ECANCELED) +              error = read_error_info (s, NULL, 0); +            break; + +          default: +            break; +        } + +      // ignore result from calibrate() +      // don't interrupt @ PIXMA_STATUS_BUSY +      error = 0; +    } +  if (error >= 0) +    error = activate (s, 0); +  if (error >= 0) +    error = activate (s, 4); +  return error; +} + +static void +pack_rgb (const uint8_t * src, unsigned nlines, unsigned w, uint8_t * dst) +{ +  unsigned w2, stride; + +  w2 = 2 * w; +  stride = 3 * w; +  for (; nlines != 0; nlines--) +    { +      unsigned x; +      for (x = 0; x != w; x++) +	{ +	  *dst++ = src[x + 0]; +	  *dst++ = src[x + w]; +	  *dst++ = src[x + w2]; +	} +      src += stride; +    } +} + +static int +mp730_open (pixma_t * s) +{ +  mp730_t *mp; +  uint8_t *buf; + +  mp = (mp730_t *) calloc (1, sizeof (*mp)); +  if (!mp) +    return PIXMA_ENOMEM; + +  buf = (uint8_t *) malloc (CMDBUF_SIZE); +  if (!buf) +    { +      free (mp); +      return PIXMA_ENOMEM; +    } + +  s->subdriver = mp; +  mp->state = state_idle; + +  mp->cb.buf = buf; +  mp->cb.size = CMDBUF_SIZE; +  mp->cb.res_header_len = 2; +  mp->cb.cmd_header_len = 10; +  mp->cb.cmd_len_field_ofs = 7; + +  PDBG (pixma_dbg (3, "Trying to clear the interrupt buffer...\n")); +  if (handle_interrupt (s, 200) == 0) +    { +      PDBG (pixma_dbg (3, "  no packets in buffer\n")); +    } +  return 0; +} + +static void +mp730_close (pixma_t * s) +{ +  mp730_t *mp = (mp730_t *) s->subdriver; + +  mp730_finish_scan (s); +  free (mp->cb.buf); +  free (mp->buf); +  free (mp); +  s->subdriver = NULL; +} + +static unsigned +calc_raw_width (pixma_t * s, const pixma_scan_param_t * sp) +{ +  unsigned raw_width; +  /* FIXME: Does MP730 need the alignment? */ +  /*  TODO test: MP710/740 */ +  if (sp->channels == 1) +    { +      if (sp->depth == 8)   /* grayscale  */ +        { +          if (s->cfg->pid == MP5_PID   || +              s->cfg->pid == MP10_PID  || +              s->cfg->pid == MP700_PID || +              s->cfg->pid == MP730_PID || +              s->cfg->pid == MP360_PID || +              s->cfg->pid == MP370_PID || +              s->cfg->pid == MP375R_PID || +              s->cfg->pid == MP390_PID || +	      s->cfg->pid == IR1020_PID) +            raw_width = ALIGN_SUP (sp->w, 4); +          else +            raw_width = ALIGN_SUP (sp->w, 12); +        } +      else   /* depth = 1 : LINEART */ +        raw_width = ALIGN_SUP (sp->w, 16); +    } +  else +    raw_width = ALIGN_SUP (sp->w, 4); +  return raw_width; +} + +static int +mp730_check_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  uint8_t k = 1; + +  /* check if channels is 3, or if depth is 1 then channels also 1 else set depth to 8 */ +  if ((sp->channels==3) || !(sp->channels==1 && sp->depth==1)) +    { +      sp->depth=8; +    } +  /* for MP5, MP10, MP360/370, MP700/730 in grayscale & lineart modes, max scan res is 600 dpi */ +  if (s->cfg->pid == MP5_PID   || +      s->cfg->pid == MP10_PID  || +      s->cfg->pid == MP700_PID || +      s->cfg->pid == MP730_PID || +      s->cfg->pid == MP360_PID || +      s->cfg->pid == MP370_PID || +      s->cfg->pid == MP375R_PID || +      s->cfg->pid == MP390_PID) +    { +      if (sp->channels == 1) +          k = sp->xdpi / MIN (sp->xdpi, 600); +    } + +  sp->x /= k; +  sp->y /= k; +  sp->h /= k; +  sp->xdpi /= k; +  sp->ydpi = sp->xdpi; + +  sp->w = calc_raw_width (s, sp); +  sp->w /= k; +  sp->line_size = (calc_raw_width (s, sp) * sp->channels * sp->depth) / 8; + +  return 0; +} + +static int +mp730_scan (pixma_t * s) +{ +  int error, n; +  mp730_t *mp = (mp730_t *) s->subdriver; +  uint8_t *buf; + +  if (mp->state != state_idle) +    return PIXMA_EBUSY; + +  /* clear interrupt packets buffer */ +  while (handle_interrupt (s, 0) > 0) +    { +    } + +  mp->raw_width = calc_raw_width (s, s->param); +  PDBG (pixma_dbg (3, "raw_width = %u\n", mp->raw_width)); + +  n = IMAGE_BLOCK_SIZE / s->param->line_size + 1; +  buf = (uint8_t *) malloc ((n + 1) * s->param->line_size + IMAGE_BLOCK_SIZE); +  if (!buf) +    return PIXMA_ENOMEM; +  mp->buf = buf; +  mp->lbuf = buf; +  mp->imgbuf = buf + n * s->param->line_size; +  mp->imgbuf_len = 0; + +  error = step1 (s); +  if (error >= 0) +    error = start_session (s); +  if (error >= 0) +    mp->state = state_scanning; +  if (error >= 0) +    error = select_source (s); +  if (error >= 0) +    error = send_scan_param (s); +  if (error < 0) +    { +      mp730_finish_scan (s); +      return error; +    } +  mp->last_block = 0; +  return 0; +} + +static int +mp730_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib) +{ +  int error, n; +  mp730_t *mp = (mp730_t *) s->subdriver; +  unsigned block_size, bytes_received; +  uint8_t header[16]; + +  do +    { +      do +	{ +	  if (s->cancel) +	    return PIXMA_ECANCELED; +	  if (mp->last_block)           /* end of image */ +	      return 0; + +	  error = read_image_block (s, header, mp->imgbuf + mp->imgbuf_len); +	  if (error < 0) +	    return error; + +	  bytes_received = error; +	  block_size = pixma_get_be16 (header + 4); +	  mp->last_block = ((header[2] & 0x28) == 0x28); +	  if (mp->last_block) +	    {    /* end of image */ +	      mp->state = state_finished; +	    } +	  if ((header[2] & ~0x38) != 0) +	    { +	      PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n")); +	      PDBG (pixma_hexdump (1, header, 16)); +	    } +	  PASSERT (bytes_received == block_size); + +	  if (block_size == 0) +	    { +	      /* no image data at this moment. */ +	      /*pixma_sleep(100000); *//* FIXME: too short, too long? */ +	      handle_interrupt (s, 100); +            } +	} +      while (block_size == 0); + +      /* TODO: simplify! */ +      mp->imgbuf_len += bytes_received; +      n = mp->imgbuf_len / s->param->line_size; +      /* n = number of full lines (rows) we have in the buffer. */ +      if (n != 0) +	{ +	  if (s->param->channels != 1    && +	      s->cfg->pid != MF5730_PID  && +	      s->cfg->pid != MF5750_PID  && +	      s->cfg->pid != MF5770_PID  && +	      s->cfg->pid != MF3110_PID  && +	      s->cfg->pid != IR1020_PID) +	    { +	      /* color, and not an MF57x0 nor MF3110 */ +	      pack_rgb (mp->imgbuf, n, mp->raw_width, mp->lbuf); +	    } +	  else +             /* grayscale/lineart or MF57x0 or MF3110 */ +             memcpy (mp->lbuf, mp->imgbuf, n * s->param->line_size); + +	  block_size = n * s->param->line_size; +	  mp->imgbuf_len -= block_size; +	  memcpy (mp->imgbuf, mp->imgbuf + block_size, mp->imgbuf_len); +	} +    } +  while (n == 0); + +  ib->rptr = mp->lbuf; +  ib->rend = mp->lbuf + block_size; +  return ib->rend - ib->rptr; +} + +static void +mp730_finish_scan (pixma_t * s) +{ +  int error, aborted = 0; +  mp730_t *mp = (mp730_t *) s->subdriver; + +  switch (mp->state) +    { +    case state_transfering: +      drain_bulk_in (s); +      /* fall through */ +    case state_scanning: +    case state_warmup: +      aborted = 1; +      error = abort_session (s); +      if (error < 0) +	PDBG (pixma_dbg +	      (1, "WARNING:abort_session() failed %s\n", +	       pixma_strerror (error))); +      /* fall through */ +    case state_finished: +      query_status (s); +      query_status (s); +      activate (s, 0); + +      // MF57x0 devices don't require abort_session() after the last page +      if (!aborted && +          (s->param->source == PIXMA_SOURCE_ADF || +           s->param->source == PIXMA_SOURCE_ADFDUP) && +           has_paper (s) && +           (s->cfg->pid == MF5730_PID || +            s->cfg->pid == MF5750_PID || +            s->cfg->pid == MF5770_PID || +            s->cfg->pid == IR1020_PID)) +      { +        error = abort_session (s); +        if (error < 0) +          PDBG (pixma_dbg +                (1, "WARNING:abort_session() failed %s\n", +                 pixma_strerror (error))); +      } + +      mp->buf = mp->lbuf = mp->imgbuf = NULL; +      mp->state = state_idle; +      /* fall through */ +    case state_idle: +      break; +    } +} + +static void +mp730_wait_event (pixma_t * s, int timeout) +{ +  /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for +   * instance. */ +  while (s->events == 0 && handle_interrupt (s, timeout) > 0) +    { +    } +} + +static int +mp730_get_status (pixma_t * s, pixma_device_status_t * status) +{ +  int error; + +  error = query_status (s); +  if (error < 0) +    return error; +  status->hardware = PIXMA_HARDWARE_OK; +  status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER; +  return 0; +} + + +static const pixma_scan_ops_t pixma_mp730_ops = { +  mp730_open, +  mp730_close, +  mp730_scan, +  mp730_fill_buffer, +  mp730_finish_scan, +  mp730_wait_event, +  mp730_check_param, +  mp730_get_status +}; + +/* TODO: implement adftpu_min_dpi & adftpu_max_dpi for grayscale & lineart */ +#define DEVICE(name, model, pid, dpi, w, h, cap) {           \ +              name,              /* name */           \ +              model,             /* model */		      \ +              0x04a9, pid,       /* vid pid */	      \ +              1,                 /* iface */		      \ +              &pixma_mp730_ops,  /* ops */            \ +              0,                 /* min_xdpi not used in this subdriver */ \ +              dpi, dpi,          /* xdpi, ydpi */	    \ +              0, 0,              /* adftpu_min_dpi & adftpu_max_dpi not used in this subdriver */ \ +              0, 0,              /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */  \ +              w, h,              /* width, height */	\ +        PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap           \ +} +const pixma_config_t pixma_mp730_devices[] = { +/* TODO: check area limits */ +  DEVICE ("PIXUS MP5/SmartBase MPC190/imageCLASS MPC190","MP5", MP5_PID, 600, 636, 868, PIXMA_CAP_LINEART),/* color scan can do 600x1200 */ +  DEVICE ("PIXUS MP10/SmartBase MPC200/imageCLASS MPC200","MP10", MP10_PID, 600, 636, 868, PIXMA_CAP_LINEART),/* color scan can do 600x1200 */ +  DEVICE ("PIXMA MP360", "MP360", MP360_PID, 1200, 636, 868, PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP370", "MP370", MP370_PID, 1200, 636, 868, PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP375R", "MP375R", MP375R_PID, 1200, 636, 868, PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP390", "MP390", MP390_PID, 1200, 636, 868, PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP700", "MP700", MP700_PID, 1200, 638, 877 /*1035 */ , PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP710", "MP710", MP710_PID, 1200, 637, 868, PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP730", "MP730", MP730_PID, 1200, 637, 868, PIXMA_CAP_ADF | PIXMA_CAP_LINEART), +  DEVICE ("PIXMA MP740", "MP740", MP740_PID, 1200, 637, 868, PIXMA_CAP_ADF | PIXMA_CAP_LINEART), + +  DEVICE ("Canon imageCLASS MF5730", "MF5730", MF5730_PID, 1200, 636, 868, PIXMA_CAP_ADF), +  DEVICE ("Canon imageCLASS MF5750", "MF5750", MF5750_PID, 1200, 636, 868, PIXMA_CAP_ADF), +  DEVICE ("Canon imageCLASS MF5770", "MF5770", MF5770_PID, 1200, 636, 868, PIXMA_CAP_ADF), +  DEVICE ("Canon imageCLASS MF3110", "MF3110", MF3110_PID, 600, 636, 868, 0), + +  DEVICE ("Canon iR 1020/1024/1025", "iR1020", IR1020_PID, 600, 636, 868, PIXMA_CAP_ADFDUP), + +  DEVICE (NULL, NULL, 0, 0, 0, 0, 0) +}; diff --git a/backend/pixma/pixma_mp750.c b/backend/pixma/pixma_mp750.c new file mode 100644 index 0000000..7f00023 --- /dev/null +++ b/backend/pixma/pixma_mp750.c @@ -0,0 +1,972 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> +   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. + */ + +/**************************************************************************** + * Credits should go to Martin Schewe (http://pixma.schewe.com) who analysed + * the protocol of MP750. + ****************************************************************************/ + +#include "../include/sane/config.h" + +#include <stdlib.h> +#include <string.h> + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + +/* TODO: remove lines marked with SIM. They are inserted so that I can test +   the subdriver with the simulator. WY */ + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +#define IMAGE_BLOCK_SIZE 0xc000 +#define CMDBUF_SIZE 512 +#define HAS_PAPER(s) (s[1] == 0) +#define IS_WARMING_UP(s) (s[7] != 3) +#define IS_CALIBRATED(s) (s[8] == 0xf) + +#define MP750_PID 0x1706 +#define MP760_PID 0x1708 +#define MP780_PID 0x1707 + + +enum mp750_state_t +{ +  state_idle, +  state_warmup, +  state_scanning, +  state_transfering, +  state_finished +}; + +enum mp750_cmd_t +{ +  cmd_start_session = 0xdb20, +  cmd_select_source = 0xdd20, +  cmd_scan_param = 0xde20, +  cmd_status = 0xf320, +  cmd_abort_session = 0xef20, +  cmd_time = 0xeb80, +  cmd_read_image = 0xd420, + +  cmd_activate = 0xcf60, +  cmd_calibrate = 0xe920, +  cmd_error_info = 0xff20 +}; + +typedef struct mp750_t +{ +  enum mp750_state_t state; +  pixma_cmdbuf_t cb; +  unsigned raw_width, raw_height; +  uint8_t current_status[12]; + +  uint8_t *buf, *rawimg, *img; +  /* make new buffer for rgb_to_gray to act on */ +  uint8_t *imgcol; +  unsigned line_size; /* need in 2 functions */ + +  unsigned rawimg_left, imgbuf_len, last_block_size, imgbuf_ofs; +  int shifted_bytes; +  int stripe_shift; /* for 2400dpi */ +  unsigned last_block; + +  unsigned monochrome:1; +  unsigned needs_abort:1; +} mp750_t; + + + +static void mp750_finish_scan (pixma_t * s); +static void check_status (pixma_t * s); + +static int +has_paper (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return HAS_PAPER (mp->current_status); +} + +static int +is_warming_up (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return IS_WARMING_UP (mp->current_status); +} + +static int +is_calibrated (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return IS_CALIBRATED (mp->current_status); +} + +static void +drain_bulk_in (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  while (pixma_read (s->io, mp->buf, IMAGE_BLOCK_SIZE) >= 0); +} + +static int +abort_session (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session); +} + +static int +query_status (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_status, 0, 12); +  error = pixma_exec (s, &mp->cb); +  if (error >= 0) +    { +      memcpy (mp->current_status, data, 12); +      PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u\n", +		       data[1], data[8], data[7])); +    } +  return error; +} + +static int +activate (pixma_t * s, uint8_t x) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mp->cb, cmd_activate, 10, 0); +  data[0] = 1; +  data[3] = x; +  return pixma_exec (s, &mp->cb); +} + +static int +activate_cs (pixma_t * s, uint8_t x) +{ +   /*SIM*/ check_status (s); +  return activate (s, x); +} + +static int +start_session (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_start_session); +} + +static int +select_source (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  uint8_t *data = pixma_newcmd (&mp->cb, cmd_select_source, 10, 0); +  data[0] = (s->param->source == PIXMA_SOURCE_ADF) ? 2 : 1; +  data[1] = 1; +  return pixma_exec (s, &mp->cb); +} + +static int +has_ccd_sensor (pixma_t * s) +{ +  return ((s->cfg->cap & PIXMA_CAP_CCD) != 0); +} + +static int +is_ccd_grayscale (pixma_t * s) +{ +  return (has_ccd_sensor (s) && (s->param->channels == 1)); +} + +/* CCD sensors don't have a Grayscale mode, but use color mode instead */ +static unsigned +get_cis_ccd_line_size (pixma_t * s) +{ +  return (s->param->wx ? s->param->line_size / s->param->w * s->param->wx +	  : s->param->line_size) * ((is_ccd_grayscale (s)) ? 3 : 1); +} + +static int +send_scan_param (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  uint8_t *data; + +  data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x2e, 0); +  pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x04); +  pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x06); +  pixma_set_be32 (s->param->x, data + 0x08); +  pixma_set_be32 (s->param->y, data + 0x0c); +  pixma_set_be32 (mp->raw_width, data + 0x10); +  pixma_set_be32 (mp->raw_height, data + 0x14); +  data[0x18] = 8;		/* 8 = color, 4 = grayscale(?) */ +  /* GH: No, there is no grayscale for CCD devices, Windows shows same  */ +  data[0x19] = s->param->depth * ((is_ccd_grayscale (s)) ? 3 : s->param->channels);	/* bits per pixel */ +  data[0x20] = 0xff; +  data[0x23] = 0x81; +  data[0x26] = 0x02; +  data[0x27] = 0x01; +  data[0x29] = mp->monochrome ? 0 : 1; + +  return pixma_exec (s, &mp->cb); +} + +static int +calibrate (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_calibrate); +} + +static int +calibrate_cs (pixma_t * s) +{ +   /*SIM*/ check_status (s); +  return calibrate (s); +} + +static int +request_image_block_ex (pixma_t * s, unsigned *size, uint8_t * info, +			unsigned flag) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  int error; + +  memset (mp->cb.buf, 0, 10); +  pixma_set_be16 (cmd_read_image, mp->cb.buf); +  mp->cb.buf[7] = *size >> 8; +  mp->cb.buf[8] = 4 | flag; +  mp->cb.reslen = pixma_cmd_transaction (s, mp->cb.buf, 10, mp->cb.buf, 6); +  mp->cb.expected_reslen = 0; +  error = pixma_check_result (&mp->cb); +  if (error >= 0) +    { +      if (mp->cb.reslen == 6) +        { +          *info = mp->cb.buf[2]; +          *size = pixma_get_be16 (mp->cb.buf + 4); +        } +      else +        { +          error = PIXMA_EPROTO; +        } +    } +  return error; +} + +static int +request_image_block (pixma_t * s, unsigned *size, uint8_t * info) +{ +  return request_image_block_ex (s, size, info, 0); +} + +static int +request_image_block2 (pixma_t * s, uint8_t * info) +{ +  unsigned temp = 0; +  return request_image_block_ex (s, &temp, info, 0x20); +} + +static int +read_image_block (pixma_t * s, uint8_t * data) +{ +  int count, temp; + +  count = pixma_read (s->io, data, IMAGE_BLOCK_SIZE); +  if (count < 0) +    return count; +  if (count == IMAGE_BLOCK_SIZE) +    { +      int error = pixma_read (s->io, &temp, 0); +      if (error < 0) +        { +          PDBG (pixma_dbg +          (1, "WARNING: reading zero-length packet failed %d\n", error)); +        } +    } +  return count; +} + +static int +read_error_info (pixma_t * s, void *buf, unsigned size) +{ +  unsigned len = 16; +  mp750_t *mp = (mp750_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len); +  error = pixma_exec (s, &mp->cb); +  if (error >= 0 && buf) +    { +      if (len < size) +        size = len; +      /* NOTE: I've absolutely no idea what the returned data mean. */ +      memcpy (buf, data, size); +      error = len; +    } +  return error; +} + +static int +send_time (pixma_t * s) +{ +  /* TODO: implement send_time() */ +  UNUSED (s); +  PDBG (pixma_dbg (3, "send_time() is not yet implemented.\n")); +  return 0; +} + +static int +handle_interrupt (pixma_t * s, int timeout) +{ +  int error; +  uint8_t intr[16]; + +  error = pixma_wait_interrupt (s->io, intr, sizeof (intr), timeout); +  if (error == PIXMA_ETIMEDOUT) +    return 0; +  if (error < 0) +    return error; +  if (error != 16) +    { +      PDBG (pixma_dbg (1, "WARNING: unexpected interrupt packet length %d\n", +		       error)); +      return PIXMA_EPROTO; +    } + +  if (intr[10] & 0x40) +    send_time (s); +  if (intr[12] & 0x40) +    query_status (s); +  if (intr[15] & 1) +    s->events = PIXMA_EV_BUTTON2;	/* b/w scan */ +  if (intr[15] & 2) +    s->events = PIXMA_EV_BUTTON1;	/* color scan */ +  return 1; +} + +static void +check_status (pixma_t * s) +{ +  while (handle_interrupt (s, 0) > 0); +} + +static int +step1 (pixma_t * s) +{ +  int error, tmo; + +  error = activate (s, 0); +  if (error < 0) +    return error; +  error = query_status (s); +  if (error < 0) +    return error; +  if (s->param->source == PIXMA_SOURCE_ADF && !has_paper (s)) +    return PIXMA_ENO_PAPER; +  error = activate_cs (s, 0); +   /*SIM*/ if (error < 0) +    return error; +  error = activate_cs (s, 0x20); +  if (error < 0) +    return error; + +  tmo = 60; +  error = calibrate_cs (s); +  while (error == PIXMA_EBUSY && --tmo >= 0) +    { +      if (s->cancel) +	return PIXMA_ECANCELED; +      PDBG (pixma_dbg +	    (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1)); +      pixma_sleep (1000000); +      error = calibrate_cs (s); +    } +  return error; +} + +static void +shift_rgb (const uint8_t * src, unsigned pixels, +	   int sr, int sg, int sb, int stripe_shift, +	   int line_size, uint8_t * dst) +{ +  unsigned st; + +  for (; pixels != 0; pixels--) +    { +      st = (pixels % 2 == 0) ? -2 * stripe_shift * line_size : 0; +      *(dst++ + sr + st) = *src++; +      *(dst++ + sg + st) = *src++; +      *(dst++ + sb + st) = *src++; +    } +} + +static uint8_t * +rgb_to_gray (uint8_t * gptr, const uint8_t * cptr, unsigned pixels, unsigned c) +{ +  unsigned i, j, g; + +  /* gptr: destination gray scale buffer */ +  /* cptr: source color scale buffer */ +  /* c: 3 for 3-channel single-byte data, 6 for double-byte data */ + +  for (i=0; i < pixels; i++) +    { +      for (j = 0, g = 0; j < 3; j++) +        { +          g += *cptr++; +	  if (c == 6) g += (*cptr++ << 8); +        } +      g /= 3; +      *gptr++ = g; +      if (c == 6) *gptr++ = (g >> 8); +    } +  return gptr; +} + +static int +calc_component_shifting (pixma_t * s) +{ +  switch (s->cfg->pid) +    { +    case MP760_PID: +      switch (s->param->ydpi) +	{ +	case 300: +	  return 3; +	case 600: +	  return 6; +	default: +	  return s->param->ydpi / 75; +	} +      /* never reached */ +      break; + +    case MP750_PID: +    case MP780_PID: +    default: +      return 2 * s->param->ydpi / 75; +    } +} + +static void +workaround_first_command (pixma_t * s) +{ +  /* FIXME: Send a dummy command because the device doesn't response to the +     first command that is sent directly after the USB interface has been +     set up. Why? USB isn't set up properly? */ +  uint8_t cmd[10]; +  int error; + +  if (s->cfg->pid == MP750_PID) +    return;			/* MP750 doesn't have this problem(?) */ + +  PDBG (pixma_dbg +	(1, +	 "Work-around for the problem: device doesn't response to the first command.\n")); +  memset (cmd, 0, sizeof (cmd)); +  pixma_set_be16 (cmd_calibrate, cmd); +  error = pixma_write (s->io, cmd, 10); +  if (error != 10) +    { +      if (error < 0) +	{ +	  PDBG (pixma_dbg +		(1, "  Sending a dummy command failed: %s\n", +		 pixma_strerror (error))); +	} +      else +	{ +	  PDBG (pixma_dbg +		(1, "  Sending a dummy command failed: count = %d\n", error)); +	} +      return; +    } +  error = pixma_read (s->io, cmd, sizeof (cmd)); +  if (error >= 0) +    { +      PDBG (pixma_dbg +	    (1, "  Got %d bytes response from a dummy command.\n", error)); +    } +  else +    { +      PDBG (pixma_dbg +	    (1, "  Reading response of a dummy command failed: %s\n", +	     pixma_strerror (error))); +    } +} + +static int +mp750_open (pixma_t * s) +{ +  mp750_t *mp; +  uint8_t *buf; + +  mp = (mp750_t *) calloc (1, sizeof (*mp)); +  if (!mp) +    return PIXMA_ENOMEM; + +  buf = (uint8_t *) malloc (CMDBUF_SIZE); +  if (!buf) +    { +      free (mp); +      return PIXMA_ENOMEM; +    } + +  s->subdriver = mp; +  mp->state = state_idle; + +  /* ofs:   0   1    2  3  4  5  6  7  8  9 +     cmd: cmd1 cmd2 00 00 00 00 00 00 00 00 +     data length-^^^^^    => cmd_len_field_ofs +     |--------- cmd_header_len --------| + +     res: res1 res2 +     |---------| res_header_len +   */ +  mp->cb.buf = buf; +  mp->cb.size = CMDBUF_SIZE; +  mp->cb.res_header_len = 2; +  mp->cb.cmd_header_len = 10; +  mp->cb.cmd_len_field_ofs = 7; + +  handle_interrupt (s, 200); +  workaround_first_command (s); +  return 0; +} + +static void +mp750_close (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; + +  mp750_finish_scan (s); +  free (mp->cb.buf); +  free (mp); +  s->subdriver = NULL; +} + +static int +mp750_check_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  unsigned raw_width; + +  UNUSED (s); + +  sp->depth = 8;		/* FIXME: Does MP750 supports other depth? */ + +  /* GH: my implementation */ +  /*   if ((sp->channels == 3) || (is_ccd_grayscale (s))) +    raw_width = ALIGN_SUP (sp->w, 4); +  else +  raw_width = ALIGN_SUP (sp->w, 12);*/ + +  /* the above code gives segmentation fault?!? why... it seems to work in the mp750_scan function */ +  raw_width = ALIGN_SUP (sp->w, 4); + +  /*sp->line_size = raw_width * sp->channels;*/ +  sp->line_size = raw_width * sp->channels * (sp->depth / 8);  /* no cropping? */ +  return 0; +} + +static int +mp750_scan (pixma_t * s) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  int error; +  uint8_t *buf; +  unsigned size, dpi, spare; + +  dpi = s->param->ydpi; +  /* add a stripe shift for 2400dpi */ +  mp->stripe_shift = (dpi == 2400) ? 4 : 0; + +  if (mp->state != state_idle) +    return PIXMA_EBUSY; + +  /* clear interrupt packets buffer */ +  while (handle_interrupt (s, 0) > 0) +    { +    } + +  /*  if (s->param->channels == 1) +    mp->raw_width = ALIGN_SUP (s->param->w, 12); +  else +    mp->raw_width = ALIGN_SUP (s->param->w, 4);*/ + +  /* change to use CCD grayscale mode --- why does this give segmentation error at runtime in mp750_check_param? */ +  if ((s->param->channels == 3) || (is_ccd_grayscale (s))) +    mp->raw_width = ALIGN_SUP (s->param->w, 4); +  else +    mp->raw_width = ALIGN_SUP (s->param->w, 12); +  /* not sure about MP750, but there is no need for aligning at 12 for the MP760/770, MP780/790 since always use CCD color mode */ + +  /* modify for stripe shift */ +  spare = 2 * calc_component_shifting (s) + 2 * mp->stripe_shift; /* FIXME: or maybe (2*... + 1)? */ +  mp->raw_height = s->param->h + spare; +  PDBG (pixma_dbg (3, "raw_width=%u raw_height=%u dpi=%u\n", +		   mp->raw_width, mp->raw_height, dpi)); + +  /* PDBG (pixma_dbg (4, "line_size=%"PRIu64"\n",s->param->line_size)); */ + +  mp->line_size = get_cis_ccd_line_size (s); /* scanner hardware line_size multiplied by 3 for CCD grayscale */ + +  size = 8 + 2 * IMAGE_BLOCK_SIZE + spare * mp->line_size; +  buf = (uint8_t *) malloc (size); +  if (!buf) +    return PIXMA_ENOMEM; +  mp->buf = buf; +  mp->rawimg = buf; +  mp->imgbuf_ofs = spare * mp->line_size; +  mp->imgcol = mp->rawimg + IMAGE_BLOCK_SIZE + 8; /* added to make rgb->gray */ +  mp->img = mp->rawimg + IMAGE_BLOCK_SIZE + 8; +  mp->imgbuf_len = IMAGE_BLOCK_SIZE + mp->imgbuf_ofs; +  mp->rawimg_left = 0; +  mp->last_block_size = 0; +  mp->shifted_bytes = -(int) mp->imgbuf_ofs; + +  error = step1 (s); +  if (error >= 0) +    error = start_session (s); +  if (error >= 0) +    mp->state = state_warmup; +  if (error >= 0) +    error = select_source (s); +  if (error >= 0) +    error = send_scan_param (s); +  if (error < 0) +    { +      mp750_finish_scan (s); +      return error; +    } +  return 0; +} + + +static int +mp750_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib) +{ +  mp750_t *mp = (mp750_t *) s->subdriver; +  int error; +  uint8_t info; +  unsigned block_size, bytes_received, n; +  int shift[3], base_shift; +  int c; + +  c = ((is_ccd_grayscale (s)) ? 3 : s->param->channels) * s->param->depth / 8; /* single-byte or double-byte data */ + +  if (mp->state == state_warmup) +    { +      int tmo = 60; + +      query_status (s); +      check_status (s); + /*SIM*/ while (!is_calibrated (s) && --tmo >= 0) +        { +          if (s->cancel) +            return PIXMA_ECANCELED; +          if (handle_interrupt (s, 1000) > 0) +            { +              block_size = 0; +              error = request_image_block (s, &block_size, &info); +               /*SIM*/ if (error < 0) +          return error; +            } +        } +      if (tmo < 0) +        { +          PDBG (pixma_dbg (1, "WARNING: Timed out waiting for calibration\n")); +          return PIXMA_ETIMEDOUT; +        } +      pixma_sleep (100000); +      query_status (s); +      if (is_warming_up (s) || !is_calibrated (s)) +        { +          PDBG (pixma_dbg (1, "WARNING: Wrong status: wup=%d cal=%d\n", +               is_warming_up (s), is_calibrated (s))); +          return PIXMA_EPROTO; +        } +      block_size = 0; +      request_image_block (s, &block_size, &info); +       /*SIM*/ mp->state = state_scanning; +      mp->last_block = 0; +    } + +  /* TODO: Move to other place, values are constant. */ +  base_shift = calc_component_shifting (s) * mp->line_size; +  if (s->param->source == PIXMA_SOURCE_ADF) +    { +      shift[0] = 0; +      shift[1] = -base_shift; +      shift[2] = -2 * base_shift; +    } +  else +    { +      shift[0] = -2 * base_shift; +      shift[1] = -base_shift; +      shift[2] = 0; +    } + +  do +    { +      if (mp->last_block_size > 0) +        { +          block_size = mp->imgbuf_len - mp->last_block_size; +          memcpy (mp->img, mp->img + mp->last_block_size, block_size); +        } + +      do +        { +          if (s->cancel) +            return PIXMA_ECANCELED; +          if (mp->last_block) +            { +              /* end of image */ +              info = mp->last_block; +              if (info != 0x38) +                { +                  query_status (s); +                   /*SIM*/ while ((info & 0x28) != 0x28) +                    { +                      pixma_sleep (10000); +                      error = request_image_block2 (s, &info); +                      if (s->cancel) +                        return PIXMA_ECANCELED;	/* FIXME: Is it safe to cancel here? */ +                      if (error < 0) +                        return error; +                    } +                } +              mp->needs_abort = (info != 0x38); +              mp->last_block = info; +              mp->state = state_finished; +              return 0; +            } + +          check_status (s); +           /*SIM*/ while (handle_interrupt (s, 1) > 0); +           /*SIM*/ block_size = IMAGE_BLOCK_SIZE; +          error = request_image_block (s, &block_size, &info); +          if (error < 0) +            { +              if (error == PIXMA_ECANCELED) +                read_error_info (s, NULL, 0); +              return error; +            } +          mp->last_block = info; +          if ((info & ~0x38) != 0) +            { +              PDBG (pixma_dbg (1, "WARNING: Unknown info byte %x\n", info)); +            } +          if (block_size == 0) +            { +              /* no image data at this moment. */ +              pixma_sleep (10000); +            } +        } +      while (block_size == 0); + +      error = read_image_block (s, mp->rawimg + mp->rawimg_left); +      if (error < 0) +	{ +	  mp->state = state_transfering; +	  return error; +	} +      bytes_received = error; +      PASSERT (bytes_received == block_size); + +      /* TODO: simplify! */ +      mp->rawimg_left += bytes_received; +      n = mp->rawimg_left / 3; +      /* n = number of pixels in the buffer? */ + +      /* Color to Grayscale converion for CCD sensor */ +      if (is_ccd_grayscale (s)) { +	shift_rgb (mp->rawimg, n, shift[0], shift[1], shift[2], mp->stripe_shift, mp->line_size, +		   mp->imgcol + mp->imgbuf_ofs); +	/* dst: img, src: imgcol */ +	rgb_to_gray (mp->img, mp->imgcol, n, c); /* cropping occurs later? */ +	PDBG (pixma_dbg (4, "*fill_buffer: did grayscale conversion \n")); +      } +      /* Color image processing */ +      else { +	shift_rgb (mp->rawimg, n, shift[0], shift[1], shift[2], mp->stripe_shift, mp->line_size, +		   mp->img + mp->imgbuf_ofs); +	PDBG (pixma_dbg (4, "*fill_buffer: no grayscale conversion---keep color \n")); +      } + +      /* entering remaining unprocessed bytes after last complete pixel into mp->rawimg buffer -- no influence on mp->img */ +      n *= 3; +      mp->shifted_bytes += n; +      mp->rawimg_left -= n;	/* rawimg_left = 0, 1 or 2 bytes left in the buffer. */ +      mp->last_block_size = n; +      memcpy (mp->rawimg, mp->rawimg + n, mp->rawimg_left); + +    } +  while (mp->shifted_bytes <= 0); + +  if ((unsigned) mp->shifted_bytes < mp->last_block_size) +    { +      if (is_ccd_grayscale (s)) +	ib->rptr = mp->img + mp->last_block_size/3 - mp->shifted_bytes/3; /* testing---works OK */ +      else +	ib->rptr = mp->img + mp->last_block_size - mp->shifted_bytes; +    } +  else +    ib->rptr = mp->img; +  if (is_ccd_grayscale (s)) +    ib->rend = mp->img + mp->last_block_size/3; /* testing---works OK */ +  else +    ib->rend = mp->img + mp->last_block_size; +  return ib->rend - ib->rptr; +} + +static void +mp750_finish_scan (pixma_t * s) +{ +  int error; +  mp750_t *mp = (mp750_t *) s->subdriver; + +  switch (mp->state) +    { +    case state_transfering: +      drain_bulk_in (s); +      /* fall through */ +    case state_scanning: +    case state_warmup: +      error = abort_session (s); +      if (error == PIXMA_ECANCELED) +	read_error_info (s, NULL, 0); +      /* fall through */ +    case state_finished: +      if (s->param->source == PIXMA_SOURCE_FLATBED) +	{ +	   /*SIM*/ query_status (s); +	  if (abort_session (s) == PIXMA_ECANCELED) +	    { +	      read_error_info (s, NULL, 0); +	      query_status (s); +	    } +	} +      query_status (s); +       /*SIM*/ activate (s, 0); +      if (mp->needs_abort) +	{ +	  mp->needs_abort = 0; +	  abort_session (s); +	} +      free (mp->buf); +      mp->buf = mp->rawimg = NULL; +      mp->state = state_idle; +      /* fall through */ +    case state_idle: +      break; +    } +} + +static void +mp750_wait_event (pixma_t * s, int timeout) +{ +  /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for +   * instance. */ +  while (s->events == 0 && handle_interrupt (s, timeout) > 0) +    { +    } +} + +static int +mp750_get_status (pixma_t * s, pixma_device_status_t * status) +{ +  int error; + +  error = query_status (s); +  if (error < 0) +    return error; +  status->hardware = PIXMA_HARDWARE_OK; +  status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER; +  status->cal = +    (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF; +  status->lamp = (is_warming_up (s)) ? PIXMA_LAMP_WARMING_UP : PIXMA_LAMP_OK; +  return 0; +} + + +static const pixma_scan_ops_t pixma_mp750_ops = { +  mp750_open, +  mp750_close, +  mp750_scan, +  mp750_fill_buffer, +  mp750_finish_scan, +  mp750_wait_event, +  mp750_check_param, +  mp750_get_status +}; + +#define DEVICE(name, model, pid, dpi, cap) {		\ +	name,                  /* name */		\ +	model,                 /* model */		\ +	0x04a9, pid,           /* vid pid */		\ +	0,                     /* iface */		\ +	&pixma_mp750_ops,      /* ops */		\ +        0,                     /* min_xdpi not used in this subdriver */ \ +	dpi, 2*(dpi),          /* xdpi, ydpi */		\ +        0, 0,                  /* adftpu_min_dpi & adftpu_max_dpi not used in this subdriver */ \ +        0, 0,                  /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */   \ +	637, 877,              /* width, height */	\ +        PIXMA_CAP_CCD|         /* all scanners with CCD */ \ +        PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap \ +} + +const pixma_config_t pixma_mp750_devices[] = { +  DEVICE ("Canon PIXMA MP750", "MP750", MP750_PID, 2400, PIXMA_CAP_ADF), +  DEVICE ("Canon PIXMA MP760/770", "MP760/770", MP760_PID, 2400, PIXMA_CAP_TPU), +  DEVICE ("Canon PIXMA MP780/790", "MP780/790", MP780_PID, 2400, PIXMA_CAP_ADF), +  DEVICE (NULL, NULL, 0, 0, 0) +}; diff --git a/backend/pixma/pixma_mp800.c b/backend/pixma/pixma_mp800.c new file mode 100644 index 0000000..feef611 --- /dev/null +++ b/backend/pixma/pixma_mp800.c @@ -0,0 +1,2434 @@ +/* SANE - Scanner Access Now Easy. + + Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> + Copyright (C) 2007-2009 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. + */ +/* test cases + 1. short USB packet (must be no -ETIMEDOUT) + 2. cancel using button on the printer (look for abort command) + 3. start scan while busy (status 0x1414) + 4. cancel using ctrl-c (must send abort command) + */ + +#define TPU_48             /* uncomment to activate TPU scan at 48 bits */ +/*#define DEBUG_TPU_48*//* uncomment to debug 48 bits TPU on a non TPU device */ +/*#define DEBUG_TPU_24*//* uncomment to debug 24 bits TPU on a non TPU device */ + +/*#define TPUIR_USE_RGB*/      /* uncomment to use RGB channels and convert them to gray; otherwise use R channel only */ + +#include "../include/sane/config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h>		/* localtime(C90) */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + +/* Some macro code to enhance readability */ +#define RET_IF_ERR(x) do {	\ +    if ((error = (x)) < 0)	\ +      return error;		\ +  } while(0) + +#define WAIT_INTERRUPT(x) do {			\ +    error = handle_interrupt (s, x);		\ +    if (s->cancel)				\ +      return PIXMA_ECANCELED;			\ +    if (error != PIXMA_ECANCELED && error < 0)	\ +      return error;				\ +  } while(0) + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +/* Size of the command buffer should be multiple of wMaxPacketLength and + greater than 4096+24. + 4096 = size of gamma table. 24 = header + checksum */ +#define IMAGE_BLOCK_SIZE (512*1024) +#define CMDBUF_SIZE (4096 + 24) +#define DEFAULT_GAMMA 2.0	/***** Gamma different from 1.0 is potentially impacting color profile generation *****/ +#define UNKNOWN_PID 0xffff + +#define CANON_VID 0x04a9 + +/* Generation 1 */ +#define MP800_PID 0x170d +#define MP800R_PID 0x170e +#define MP830_PID 0x1713 + +/* Generation 2 */ +#define MP810_PID 0x171a +#define MP960_PID 0x171b + +/* Generation 3 */ +/* PIXMA 2007 vintage */ +#define MP970_PID 0x1726 + +/* Flatbed scanner CCD (2007) */ +#define CS8800F_PID 0x1901 + +/* PIXMA 2008 vintage */ +#define MP980_PID 0x172d + +/* Generation 4 */ +#define MP990_PID 0x1740 + +/* Flatbed scanner CCD (2010) */ +#define CS9000F_PID 0x1908 + +/* 2010 new device (untested) */ +#define MG8100_PID 0x174b /* CCD */ + +/* 2011 new device (untested) */ +#define MG8200_PID 0x1756 /* CCD */ + +/* 2013 new device */ +#define CS9000F_MII_PID 0x190d + +/* Generation 4 XML messages that encapsulates the Pixma protocol messages */ +#define XML_START_1   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\ +<ivec:contents><ivec:operation>StartJob</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +<ivec:bidi>1</ivec:bidi></ivec:param_set></ivec:contents></cmd>" + +#define XML_START_2   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\" xmlns:vcn=\"http://www.canon.com/ns/cmd/2008/07/canon/\">\ +<ivec:contents><ivec:operation>VendorCmd</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +<vcn:ijoperation>ModeShift</vcn:ijoperation><vcn:ijmode>1</vcn:ijmode>\ +</ivec:param_set></ivec:contents></cmd>" + +#define XML_END   \ +"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\ +<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\ +<ivec:contents><ivec:operation>EndJob</ivec:operation>\ +<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\ +</ivec:param_set></ivec:contents></cmd>" + +#define XML_OK   "<ivec:response>OK</ivec:response>" + +enum mp810_state_t +{ +  state_idle, +  state_warmup, +  state_scanning, +  state_transfering, +  state_finished +}; + +enum mp810_cmd_t +{ +  cmd_start_session = 0xdb20, +  cmd_select_source = 0xdd20, +  cmd_gamma = 0xee20, +  cmd_scan_param = 0xde20, +  cmd_status = 0xf320, +  cmd_abort_session = 0xef20, +  cmd_time = 0xeb80, +  cmd_read_image = 0xd420, +  cmd_error_info = 0xff20, + +  cmd_start_calibrate_ccd_3 = 0xd520, +  cmd_end_calibrate_ccd_3 = 0xd720, +  cmd_scan_param_3 = 0xd820, +  cmd_scan_start_3 = 0xd920, +  cmd_status_3 = 0xda20, +  cmd_get_tpu_info_3 = 0xf520, +  cmd_set_tpu_info_3 = 0xea20, + +  cmd_e920 = 0xe920 /* seen in MP800 */ +}; + +typedef struct mp810_t +{ +  enum mp810_state_t state; +  pixma_cmdbuf_t cb; +  uint8_t *imgbuf; +  uint8_t current_status[16]; +  unsigned last_block; +  uint8_t generation; +  /* for Generation 3 and CCD shift */ +  uint8_t *linebuf; +  uint8_t *data_left_ofs; +  unsigned data_left_len; +  int shift[3]; +  unsigned color_shift; +  unsigned stripe_shift; +  unsigned stripe_shift2; /* added for MP810, MP960 at 4800dpi & 9000F at 9600dpi */ +  unsigned jumplines; /* added for MP810, MP960 at 4800dpi & 9000F at 9600dpi */ +  uint8_t tpu_datalen; +  uint8_t tpu_data[0x40]; +} mp810_t; + +/* + STAT:  0x0606 = ok, + 0x1515 = failed (PIXMA_ECANCELED), + 0x1414 = busy (PIXMA_EBUSY) + + Transaction scheme + 1. command_header/data | result_header + 2. command_header      | result_header/data + 3. command_header      | result_header/image_data + + - data has checksum in the last byte. + - image_data has no checksum. + - data and image_data begins in the same USB packet as + command_header or result_header. + + command format #1: + u16be      cmd + u8[6]      0 + u8[4]      0 + u32be      PLEN parameter length + u8[PLEN-1] parameter + u8         parameter check sum + result: + u16be      STAT + u8         0 + u8         0 or 0x21 if STAT == 0x1414 + u8[4]      0 + + command format #2: + u16be      cmd + u8[6]      0 + u8[4]      0 + u32be      RLEN result length + result: + u16be      STAT + u8[6]      0 + u8[RLEN-1] result + u8         result check sum + + command format #3: (only used by read_image_block) + u16be      0xd420 + u8[6]      0 + u8[4]      0 + u32be      max. block size + 8 + result: + u16be      STAT + u8[6]      0 + u8         block info bitfield: 0x8 = end of scan, 0x10 = no more paper, 0x20 = no more data + u8[3]      0 + u32be      ILEN image data size + u8[ILEN]   image data + */ + +static void mp810_finish_scan (pixma_t * s); + +static int is_scanning_from_adf (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADF +           || s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static int is_scanning_from_adfdup (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_ADFDUP); +} + +static int is_scanning_from_tpu (pixma_t * s) +{ +  return (s->param->source == PIXMA_SOURCE_TPU); +} + +static int send_xml_dialog (pixma_t * s, const char * xml_message) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  int datalen; + +  datalen = pixma_cmd_transaction (s, xml_message, strlen (xml_message), +                                   mp->cb.buf, 1024); +  if (datalen < 0) +    return datalen; + +  mp->cb.buf[datalen] = 0; + +  PDBG(pixma_dbg (10, "XML message sent to scanner:\n%s\n", xml_message)); +  PDBG(pixma_dbg (10, "XML response back from scanner:\n%s\n", mp->cb.buf)); + +  return (strcasestr ((const char *) mp->cb.buf, XML_OK) != NULL); +} + +static void new_cmd_tpu_msg (pixma_t *s, pixma_cmdbuf_t * cb, uint16_t cmd) +{ +  pixma_newcmd (cb, cmd, 0, 0); +  cb->buf[3] = (is_scanning_from_tpu (s)) ? 0x01 : 0x00; +} + +static int start_session (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  new_cmd_tpu_msg (s, &mp->cb, cmd_start_session); +  return pixma_exec (s, &mp->cb); +} + +static int start_scan_3 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  new_cmd_tpu_msg (s, &mp->cb, cmd_scan_start_3); +  return pixma_exec (s, &mp->cb); +} + +static int send_cmd_start_calibrate_ccd_3 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  pixma_newcmd (&mp->cb, cmd_start_calibrate_ccd_3, 0, 0); +  mp->cb.buf[3] = 0x01; +  return pixma_exec (s, &mp->cb); +} + +static int is_calibrated (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  if (mp->generation >= 3) +  { +    return ((mp->current_status[0] & 0x01) == 1); +  } +  if (mp->generation == 1) +  { +    return (mp->current_status[8] == 1); +  } +  else +  { +    return (mp->current_status[9] == 1); +  } +} + +static int has_paper (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  if (is_scanning_from_adfdup (s)) +    return (mp->current_status[1] == 0 || mp->current_status[2] == 0); +  else +    return (mp->current_status[1] == 0); +} + +static void drain_bulk_in (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0) +    ; +} + +static int abort_session (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session); +} + +static int send_cmd_e920 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  return pixma_exec_short_cmd (s, &mp->cb, cmd_e920); +} + +static int select_source (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; + +  data = pixma_newcmd (&mp->cb, cmd_select_source, 12, 0); +  data[5] = ((mp->generation == 2) ? 1 : 0); +  switch (s->param->source) +  { +    case PIXMA_SOURCE_FLATBED: +      data[0] = 1; +      data[1] = 1; +      break; + +    case PIXMA_SOURCE_ADF: +      data[0] = 2; +      data[5] = 1; +      data[6] = 1; +      break; + +    case PIXMA_SOURCE_ADFDUP: +      data[0] = 2; +      data[5] = 3; +      data[6] = 3; +      break; + +    case PIXMA_SOURCE_TPU: +      data[0] = 4; +      data[1] = 2; +      break; +  } +  return pixma_exec (s, &mp->cb); +} + +static int send_get_tpu_info_3 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_get_tpu_info_3, 0, 0x34); +  RET_IF_ERR(pixma_exec (s, &mp->cb)); +  memcpy (mp->tpu_data, data, 0x34); +  return error; +} + +static int send_set_tpu_info (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; + +  if (mp->tpu_datalen == 0) +    return 0; +  data = pixma_newcmd (&mp->cb, cmd_set_tpu_info_3, 0x34, 0); +  memcpy (data, mp->tpu_data, 0x34); +  return pixma_exec (s, &mp->cb); +} + +static int send_gamma_table (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  const uint8_t *lut = s->param->gamma_table; +  uint8_t *data; + +  if (mp->generation == 1) +  { +    data = pixma_newcmd (&mp->cb, cmd_gamma, 4096 + 8, 0); +    data[0] = (s->param->channels == 3) ? 0x10 : 0x01; +    pixma_set_be16 (0x1004, data + 2); +    if (lut) +      memcpy (data + 4, lut, 4096); +    else +      pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 4096); +  } +  else +  { +    /* FIXME: Gamma table for 2nd generation: 1024 * uint16_le */ +    data = pixma_newcmd (&mp->cb, cmd_gamma, 2048 + 8, 0); +    data[0] = 0x10; +    pixma_set_be16 (0x0804, data + 2); +    if (lut) +    { +      int i; +      for (i = 0; i < 1024; i++) +      { +        int j = (i << 2) + (i >> 8); +        data[4 + 2 * i + 0] = lut[j]; +        data[4 + 2 * i + 1] = lut[j]; +      } +    } +    else +    { +      int i; +      pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 2048); +      for (i = 0; i < 1024; i++) +      { +        int j = (i << 1) + (i >> 9); +        data[4 + 2 * i + 0] = data[4 + j]; +        data[4 + 2 * i + 1] = data[4 + j]; +      } +    } +  } +  return pixma_exec (s, &mp->cb); +} + +static unsigned calc_raw_width (const mp810_t * mp, +                                const pixma_scan_param_t * param) +{ +  unsigned raw_width; +  /* NOTE: Actually, we can send arbitary width to MP810. Lines returned +     are always padded to multiple of 4 or 12 pixels. Is this valid for +     other models, too? */ +  if (mp->generation >= 2) +  { +    raw_width = ALIGN_SUP (param->w + param->xs, 32); +    /* PDBG (pixma_dbg (4, "*calc_raw_width***** width %u extended by %u and rounded to %u *****\n", param->w, param->xs, raw_width)); */ +  } +  else if (param->channels == 1) +  { +    raw_width = ALIGN_SUP (param->w + param->xs, 12); +  } +  else +  { +    raw_width = ALIGN_SUP (param->w + param->xs, 4); +  } +  return raw_width; +} + +static int has_ccd_sensor (pixma_t * s) +{ +  return ((s->cfg->cap & PIXMA_CAP_CCD) != 0); +} + +#if 0 +static int is_color (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_COLOR); +} + +static int is_color_48 (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_COLOR_48); +} + +static int is_color_negative (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_COLOR); +} + +static int is_color_all (pixma_t * s) +{ +  return (is_color (s) || is_color_48 (s) || is_color_negative (s)); +} +#endif + +static int is_gray (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_GRAY); +} + +static int is_gray_16 (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_GRAY_16); +} + +static int is_gray_negative (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_GRAY); +} + +static int is_gray_all (pixma_t * s) +{ +  return (is_gray (s) || is_gray_16 (s) || is_gray_negative (s)); +} + +static int is_lineart (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_LINEART); +} + +static int is_tpuir (pixma_t * s) +{ +  return (s->param->mode == PIXMA_SCAN_MODE_TPUIR); +} + +/* CCD sensors don't have neither a Grayscale mode nor a Lineart mode, + * but use color mode instead */ +static unsigned get_cis_ccd_line_size (pixma_t * s) +{ +  return (( +      s->param->wx ? s->param->line_size / s->param->w * s->param->wx +                   : s->param->line_size) +      * ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : 1)); +} + +static unsigned calc_shifting (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  /* If stripes shift needed (CCD devices), how many pixels shift */ +  mp->stripe_shift = 0; +  mp->stripe_shift2 = 0; +  mp->jumplines = 0; + +  switch (s->cfg->pid) +  { +    case MP800_PID: +    case MP800R_PID: +    case MP830_PID: +      if (s->param->xdpi == 2400) +        { +          if (is_scanning_from_tpu(s)) +            mp->stripe_shift = 6; +          else +            mp->stripe_shift = 3; +        } +      if (s->param->ydpi > 75) +        { +          mp->color_shift = s->param->ydpi / ((s->param->ydpi < 1200) ? 150 : 75); + +          if (is_scanning_from_tpu (s)) +            mp->color_shift = s->param->ydpi / 75; + +          /* If you're trying to decipher this color-shifting code, +             the following line is where the magic is revealed. */ +          mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s); +          if (is_scanning_from_adf (s)) +            {  /* ADF */ +              mp->shift[0] = 0; +              mp->shift[2] = 2 * mp->shift[1]; +            } +          else +            {  /* Flatbed or TPU */ +              mp->shift[0] = 2 * mp->shift[1]; +              mp->shift[2] = 0; +            } +        } +      break; + +    case MP970_PID: /* MP970 at 4800 dpi */ +    case CS8800F_PID: /* CanoScan 8800F at 4800 dpi */ +      if (s->param->xdpi == 4800) +      { +        if (is_scanning_from_tpu (s)) +          mp->stripe_shift = 6; +        else +          mp->stripe_shift = 3; +      } +      break; + +    case CS9000F_PID: /* CanoScan 9000F at 4800 dpi */ +    case CS9000F_MII_PID: +      if (s->param->xdpi == 4800) +      { +        if (is_scanning_from_tpu (s)) +          mp->stripe_shift = 6; /* should work for CS9000F same as manual */ +        else +          mp->stripe_shift = 3; +      } +      if (s->param->xdpi == 9600) +      { +        if (is_scanning_from_tpu (s)) +        { +          /* need to double up for TPU */ +          mp->stripe_shift = 6; /* for 1st set of 4 images */ +          /* unfortunately there are 2 stripe shifts */ +          mp->stripe_shift2 = 6; /* for 2nd set of 4 images */ +          mp->jumplines = 32; /* try 33 or 34 */ +        } +        /* there is no 9600dpi support in flatbed mode */ +      } +      break; + +    case MP960_PID: +      if (s->param->xdpi == 2400) +      { +        if (is_scanning_from_tpu (s)) +          mp->stripe_shift = 6; +        else +          mp->stripe_shift = 3; +      } +      if (s->param->xdpi == 4800) +      { +        if (is_scanning_from_tpu (s)) +        { +          mp->stripe_shift = 6; +          mp->stripe_shift2 = 6; +        } +        else +        { +          mp->stripe_shift = 3; +          mp->stripe_shift2 = 3; +        } +        mp->jumplines = 33; /* better than 32 or 34 : applies to flatbed & TPU */ +      } +      break; + +    case MP810_PID: +      if (s->param->xdpi == 2400) +      { +        if (is_scanning_from_tpu (s)) +          mp->stripe_shift = 6; +        else +          mp->stripe_shift = 3; +      } +      if (s->param->xdpi == 4800) +      { +        if (is_scanning_from_tpu (s)) +        { +          mp->stripe_shift = 6; +          mp->stripe_shift2 = 6; +        } +        else +        { +          mp->stripe_shift = 3; +          mp->stripe_shift2 = 3; +        } +        mp->jumplines = 33; /* better than 32 or 34 : applies to flatbed & TPU */ +      } +      break; + +    case MP990_PID: +      if (s->param->xdpi == 4800) +      { +        if (is_scanning_from_tpu (s)) +        { +          mp->stripe_shift = 6; +          mp->stripe_shift2 = 6; +        } +        else +        { +          mp->stripe_shift = 3; +          mp->stripe_shift2 = 3; +        } +        mp->jumplines = 34; /* better than 32 or 34 : applies to flatbed & TPU */ +      } +      break; + +    default: /* Default, and all CIS devices */ +      break; +  } +  /* If color plane shift (CCD devices), how many pixels shift */ +  mp->color_shift = mp->shift[0] = mp->shift[1] = mp->shift[2] = 0; +  if (s->param->ydpi > 75) +  { +    switch (s->cfg->pid) +    { +      case MP970_PID: +      case CS8800F_PID: /* CanoScan 8800F */ +        mp->color_shift = s->param->ydpi / 50; +        mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s); +        mp->shift[0] = 0; +        mp->shift[2] = 2 * mp->shift[1]; +        break; + +      case CS9000F_PID: /* CanoScan 9000F */ +      case CS9000F_MII_PID: +        mp->color_shift = s->param->ydpi / 30; +        mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s); +        mp->shift[0] = 0; +        mp->shift[2] = 2 * mp->shift[1]; +        break; + +      case MP980_PID: +      case MP990_PID: +      case MG8200_PID: +        if (s->param->ydpi > 150) +        { +          mp->color_shift = s->param->ydpi / 75; +          mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s); +          mp->shift[0] = 0; +          mp->shift[2] = 2 * mp->shift[1]; +        } +        break; + +      case MP810_PID: +      case MP960_PID: +        mp->color_shift = s->param->ydpi / 50; +        if (is_scanning_from_tpu (s)) +          mp->color_shift = s->param->ydpi / 50; +        mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s); +        mp->shift[0] = 2 * mp->shift[1]; +        mp->shift[2] = 0; +        break; + +      default: +        break; +    } +  } +  /* special settings for 16 bit flatbed mode @ 75 dpi +   * minimum of 150 dpi is used yet */ +  /* else if (!is_scanning_from_tpu (s)) +    { +      switch (s->cfg->pid) +      { +        case CS9000F_PID: +        case CS9000F_MII_PID: +          if (is_color_48 (s) || is_gray_16 (s)) +          { +            mp->color_shift = 5; +            mp->shift[1] = 0; +            mp->shift[0] = 0; +            mp->shift[2] = 0; +          } +          break; +       } +    } */ +  /* PDBG (pixma_dbg (4, "*calc_shifing***** color_shift = %u, stripe_shift = %u, jumplines = %u  *****\n", +                   mp->color_shift, mp->stripe_shift, mp->jumplines)); */ +  return (2 * mp->color_shift + mp->stripe_shift + mp->jumplines); /* note impact of stripe shift2 later if needed! */ +} + +static int send_scan_param (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  unsigned raw_width = calc_raw_width (mp, s->param); +  unsigned h, h1, h2, shifting; + +  /* TPU scan does not support lineart */ +  if (is_scanning_from_tpu (s) && is_lineart (s)) +  { +    return PIXMA_ENOTSUP; +  } + +  shifting = calc_shifting (s); +  h1 = s->param->h + shifting; /* add lines for color shifting */ +  /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 1 %u \n", h1 )); */ +  if (mp->generation >= 4) /* use most global condition */ +  { +    /* tested for MP960, 9000F */ +    /* add lines for color shifting */ +    /* otherwise you cannot scan all lines defined for flatbed mode */ +    /* this shouldn't affect TPU mode */ +    h2 = s->cfg->height * s->param->ydpi / 75 + shifting; +    /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 2 %u = %u max. lines for scanner + %u lines for color shifting \n", h2, s->cfg->height * s->param->ydpi / 75, shifting )); */ +  } +  else +  { +    /* TODO: Check for other scanners. */ +    h2 = s->cfg->height * s->param->ydpi / 75; /* this might be causing problems for generation 1 devices */ +    /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 2 %u \n", h2 )); */ +  } +  h = MIN (h1, h2); + +  if (mp->generation <= 2) +  { +    data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x30, 0); +    pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x04); +    pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x06); +    pixma_set_be32 (s->param->x, data + 0x08); +    if (mp->generation == 2) +      pixma_set_be32 (s->param->x - s->param->xs, data + 0x08); +    pixma_set_be32 (s->param->y, data + 0x0c); +    pixma_set_be32 (raw_width, data + 0x10); +    pixma_set_be32 (h, data + 0x14); +    data[0x18] = +        ((s->param->channels != 1) || is_gray_all (s) || is_lineart (s)) ? +            0x08 : 0x04; +    data[0x19] = ((s->param->software_lineart) ? 8 : s->param->depth) +                  * ((is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels); /* bits per pixel */ +    data[0x1a] = (is_scanning_from_tpu (s) ? 1 : 0); +    data[0x20] = 0xff; +    data[0x23] = 0x81; +    data[0x26] = 0x02; +    data[0x27] = 0x01; +  } +  else +  { +    /* scan parameters: +     * ================ +     * +     * byte | # of  |  mode   | value / description +     *      | bytes |         | +     * -----+-------+---------+--------------------------- +     * 0x00 |   1   | default | 0x01 +     *      |       |   adf   | 0x02 +     *      |       |   tpu   | 0x04 +     *      |       |  tpuir  | cs9000f: 0x03 +     * -----+-------+---------+--------------------------- +     * 0x01 |   1   | default | 0x00 +     *      |       |   tpu   | 0x02 +     * -----+-------+---------+--------------------------- +     * 0x02 |   1   | default | 0x01 +     *      |       | adfdup  | 0x03 +     * -----+-------+---------+--------------------------- +     * 0x03 |   1   | default | 0x00 +     *      |       | adfdup  | 0x03 +     * -----+-------+---------+--------------------------- +     * 0x04 |   1   |   all   | 0x00 +     * -----+-------+---------+--------------------------- +     * 0x05 |   1   |   all   | 0x01: This one also seen at 0. Don't know yet what's used for. +     * -----+-------+---------+--------------------------- +     *  ... |   1   |   all   | 0x00 +     * -----+-------+---------+--------------------------- +     * 0x08 |   2   |   all   | xdpi | 0x8000 +     * -----+-------+---------+--------------------------- +     * 0x0a |   2   |   all   | ydpi | 0x8000: Must be the same as xdpi. +     * -----+-------+---------+--------------------------- +     * 0x0c |   4   |   all   | x position of start pixel +     * -----+-------+---------+--------------------------- +     * 0x10 |   4   |   all   | y position of start pixel +     * -----+-------+---------+--------------------------- +     * 0x14 |   4   |   all   | # of pixels in 1 line +     * -----+-------+---------+--------------------------- +     * 0x18 |   4   |   all   | # of scan lines +     * -----+-------+---------+--------------------------- +     * 0x1c |   1   |   all   | 0x08 +     *      |       |         | 0x04 = relict from cis scanners? +     * -----+-------+---------+--------------------------- +     * 0x1d |   1   |   all   | # of bits per pixel +     * -----+-------+---------+--------------------------- +     * 0x1e |   1   | default | 0x00: paper +     *      |       |   tpu   | 0x01: positives +     *      |       |   tpu   | 0x02: negatives +     *      |       |  tpuir  | 0x01: positives +     * -----+-------+---------+--------------------------- +     * 0x1f |   1   |   all   | 0x01 +     *      |       |         | cs9000f: 0x00: Not sure if that is because of positives. +     * -----+-------+---------+--------------------------- +     * 0x20 |   1   |   all   | 0xff +     * -----+-------+---------+--------------------------- +     * 0x21 |   1   |   all   | 0x81 +     * -----+-------+---------+--------------------------- +     * 0x22 |   1   |   all   | 0x00 +     * -----+-------+---------+--------------------------- +     * 0x23 |   1   |   all   | 0x02 +     * -----+-------+---------+--------------------------- +     * 0x24 |   1   |   all   | 0x01 +     * -----+-------+---------+--------------------------- +     * 0x25 |   1   | default | 0x00; cs8800f: 0x01 +     *      |       |   tpu   | 0x00; cs9000f, mg8200, mp990: 0x01 +     *      |       |  tpuir  | cs9000f: 0x00 +     * -----+-------+---------+--------------------------- +     *  ... |   1   |   all   | 0x00 +     * -----+-------+---------+--------------------------- +     * 0x30 |   1   |   all   | 0x01 +     * +     */ + +    data = pixma_newcmd (&mp->cb, cmd_scan_param_3, 0x38, 0); +    data[0x00] = is_scanning_from_adf (s) ? 0x02 : 0x01; +    data[0x01] = 0x01; +    if (is_scanning_from_tpu (s)) +    { +      data[0x00] = is_tpuir (s) ? 0x03 : 0x04; +      data[0x01] = 0x02; +      data[0x1e] = 0x02; /* NB: CanoScan 8800F: 0x02->negatives, 0x01->positives, paper->0x00 */ +    } +    data[0x02] = 0x01; +    if (is_scanning_from_adfdup (s)) +    { +      data[0x02] = 0x03; +      data[0x03] = 0x03; +    } +    if (s->cfg->pid != MG8200_PID) +      data[0x05] = 0x01; /* This one also seen at 0. Don't know yet what's used for */ +    /* the scanner controls the scan */ +    /* no software control needed */ +    pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x08); +    pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x0a); +    /*PDBG (pixma_dbg (4, "*send_scan_param***** Setting: xdpi=%hi ydpi=%hi  x=%u y=%u  w=%u h=%u ***** \n", +     s->param->xdpi,s->param->ydpi,(s->param->x)-(s->param->xs),s->param->y,raw_width,h));*/ +    pixma_set_be32 (s->param->x - s->param->xs, data + 0x0c); +    pixma_set_be32 (s->param->y, data + 0x10); +    pixma_set_be32 (raw_width, data + 0x14); +    pixma_set_be32 (h, data + 0x18); +    data[0x1c] = ((s->param->channels != 1) || is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 0x08 : 0x04; + +#ifdef DEBUG_TPU_48 +    data[0x1d] = 24; +#else +    data[0x1d] = (is_scanning_from_tpu (s)) ? 48 +                                            : (((s->param->software_lineart) ? 8 : s->param->depth) +                                               * ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels)); /* bits per pixel */ +#endif + +    data[0x1f] = 0x01; /* for 9000F this appears to be 0x00, not sure if that is because of positives */ + +    if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID || s->cfg->pid == MG8200_PID) +    { +      data[0x1f] = 0x00; +    } + +    data[0x20] = 0xff; +    data[0x21] = 0x81; +    data[0x23] = 0x02; +    data[0x24] = 0x01; + +    /* MG8200 & MP990 addition */ +    if (s->cfg->pid == MG8200_PID || s->cfg->pid == MP990_PID) +    { +      if (is_scanning_from_tpu (s)) +      { +        data[0x25] = 0x01; +      } +    } + +    /* CS8800F & CS9000F addition */ +    if (s->cfg->pid == CS8800F_PID || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) +    { +      if (is_scanning_from_tpu (s)) +      { /* TPU */ +        /* 0x02->negatives, 0x01->positives, paper->0x00 +         * no paper in TPU mode */ +        if (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_COLOR +            || s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_GRAY) +        { +          PDBG( +              pixma_dbg (4, "*send_scan_param***** TPU scan negatives *****\n")); +          data[0x1e] = 0x02; +        } +        else +        { +          PDBG( +              pixma_dbg (4, "*send_scan_param***** TPU scan positives *****\n")); +          data[0x1e] = 0x01; +        } +        /* CS8800F: 0x00 for TPU color management */ +        if (s->cfg->pid == CS8800F_PID) +          data[0x25] = 0x00; +        /* CS9000F: 0x01 for TPU */ +        if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) +          data[0x25] = 0x01; +        if (s->param->mode == PIXMA_SCAN_MODE_TPUIR) +          data[0x25] = 0x00; +      } +      else +      { /* flatbed and ADF */ +        /* paper->0x00 */ +        data[0x1e] = 0x00; +        /* CS8800F: 0x01 normally */ +        if (s->cfg->pid == CS8800F_PID) +          data[0x25] = 0x01; +        /* CS9000F: 0x00 normally */ +        if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) +          data[0x25] = 0x00; +      } +    } + +    data[0x30] = 0x01; +  } +  return pixma_exec (s, &mp->cb); +} + +static int query_status_3 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  int error, status_len; + +  status_len = 8; +  data = pixma_newcmd (&mp->cb, cmd_status_3, 0, status_len); +  RET_IF_ERR(pixma_exec (s, &mp->cb)); +  memcpy (mp->current_status, data, status_len); +  return error; +} + +static int query_status (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  int error, status_len; + +  status_len = (mp->generation == 1) ? 12 : 16; +  data = pixma_newcmd (&mp->cb, cmd_status, 0, status_len); +  RET_IF_ERR(pixma_exec (s, &mp->cb)); +  memcpy (mp->current_status, data, status_len); +  PDBG( +      pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u busy=%u\n", data[1], data[8], data[7], data[9])); +  return error; +} + +#if 0 +static int send_time (pixma_t * s) +{ +  /* Why does a scanner need a time? */ +  time_t now; +  struct tm *t; +  uint8_t *data; +  mp810_t *mp = (mp810_t *) s->subdriver; + +  data = pixma_newcmd (&mp->cb, cmd_time, 20, 0); +  pixma_get_time (&now, NULL); +  t = localtime (&now); +  strftime ((char *) data, 16, "%y/%m/%d %H:%M", t); +  PDBG(pixma_dbg (3, "Sending time: '%s'\n", (char *) data)); +  return pixma_exec (s, &mp->cb); +} +#endif + +/* TODO: Simplify this function. Read the whole data packet in one shot. */ +static int read_image_block (pixma_t * s, uint8_t * header, uint8_t * data) +{ +  uint8_t cmd[16]; +  mp810_t *mp = (mp810_t *) s->subdriver; +  const int hlen = 8 + 8; +  int error, datalen; + +  memset (cmd, 0, sizeof(cmd)); +  /* PDBG (pixma_dbg (4, "* read_image_block: last_block\n", mp->last_block)); */ +  pixma_set_be16 (cmd_read_image, cmd); +  if ((mp->last_block & 0x20) == 0) +    pixma_set_be32 ((IMAGE_BLOCK_SIZE / 65536) * 65536 + 8, cmd + 0xc); +  else +    pixma_set_be32 (32 + 8, cmd + 0xc); + +  mp->state = state_transfering; +  mp->cb.reslen = pixma_cmd_transaction (s, cmd, sizeof(cmd), mp->cb.buf, 512); /* read 1st 512 bytes of image block */ +  datalen = mp->cb.reslen; +  if (datalen < 0) +    return datalen; + +  memcpy (header, mp->cb.buf, hlen); +  /* PDBG (pixma_dbg (4, "* read_image_block: datalen %i\n", datalen)); */ +  /* PDBG (pixma_dbg (4, "* read_image_block: hlen %i\n", hlen)); */ +  if (datalen >= hlen) +  { +    datalen -= hlen; +    memcpy (data, mp->cb.buf + hlen, datalen); +    data += datalen; +    if (mp->cb.reslen == 512) +    { /* read the rest of the image block */ +      error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen); +      RET_IF_ERR(error); +      datalen += error; +    } +  } + +  mp->state = state_scanning; +  mp->cb.expected_reslen = 0; +  RET_IF_ERR(pixma_check_result (&mp->cb)); +  if (mp->cb.reslen < hlen) +    return PIXMA_EPROTO; +  return datalen; +} + +static int read_error_info (pixma_t * s, void *buf, unsigned size) +{ +  unsigned len = 16; +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  int error; + +  data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len); +  RET_IF_ERR(pixma_exec (s, &mp->cb)); +  if (buf && len < size) +  { +    size = len; +    /* NOTE: I've absolutely no idea what the returned data mean. */ +    memcpy (buf, data, size); +    error = len; +  } +  return error; +} + +/* + handle_interrupt() waits until it receives an interrupt packet or times out. + It calls send_time() and query_status() if necessary. Therefore, make sure + that handle_interrupt() is only called from a safe context for send_time() + and query_status(). + + Returns: + 0     timed out + 1     an interrupt packet received + PIXMA_ECANCELED interrupted by signal + <0    error + */ +static int handle_interrupt (pixma_t * s, int timeout) +{ +  uint8_t buf[64];      /* check max. packet size with 'lsusb -v' for "EP 9 IN" */ +  int len; + +  len = pixma_wait_interrupt (s->io, buf, sizeof(buf), timeout); +  if (len == PIXMA_ETIMEDOUT) +    return 0; +  if (len < 0) +    return len; +  if (len%16)           /* len must be a multiple of 16 bytes */ +  { +    PDBG(pixma_dbg (1, "WARNING:unexpected interrupt packet length %d\n", len)); +    return PIXMA_EPROTO; +  } + +  /* s->event = 0x0brroott +   * b:  button +   * oo: original +   * tt: target +   * rr: scan resolution +   * poll event with 'scanimage -A' */ +  if (s->cfg->pid == MG8200_PID) +  /* button no. in buf[7] +   * size in buf[10] 01=A4; 02=Letter; 08=10x15; 09=13x18; 0b=auto +   * format in buf[11] 01=JPEG; 02=TIFF; 03=PDF; 04=Kompakt-PDF +   * dpi in buf[12] 01=75; 02=150; 03=300; 04=600 +   * target = format; original = size; scan-resolution = dpi */ +  { +    if (buf[7] & 1) +      s->events = PIXMA_EV_BUTTON1 | buf[11] | buf[10]<<8 | buf[12]<<16;    /* color scan */ +    if (buf[7] & 2) +      s->events = PIXMA_EV_BUTTON2 | buf[11] | buf[10]<<8 | buf[12]<<16;    /* b/w scan */ +  } +  else if (s->cfg->pid == CS8800F_PID +            || s->cfg->pid == CS9000F_PID +            || s->cfg->pid == CS9000F_MII_PID) +  /* button no. in buf[1] +   * target = button no. +   * "Finish PDF" is Button-2, all others are Button-1 */ +  { +    if ((s->cfg->pid == CS8800F_PID && buf[1] == 0x70) +        || (s->cfg->pid != CS8800F_PID && buf[1] == 0x50)) +      s->events = PIXMA_EV_BUTTON2 | buf[1] >> 4;  /* button 2 = cancel / end scan */ +    else +      s->events = PIXMA_EV_BUTTON1 | buf[1] >> 4;  /* button 1 = start scan */ +  } +  else +  /* button no. in buf[0] +   * original in buf[0] +   * target in buf[1] */ +  { +    /* More than one event can be reported at the same time. */ +    if (buf[3] & 1) +      /* FIXME: This function makes trouble with a lot of scanners +      send_time (s); +       */ +      PDBG (pixma_dbg (1, "WARNING:send_time() disabled!\n")); +    if (buf[9] & 2) +      query_status (s); + +    if (buf[0] & 2) +      s->events = PIXMA_EV_BUTTON2 | buf[1] | ((buf[0] & 0xf0) << 4); /* b/w scan */ +    if (buf[0] & 1) +      s->events = PIXMA_EV_BUTTON1 | buf[1] | ((buf[0] & 0xf0) << 4); /* color scan */ +  } +  return 1; +} + +static int init_ccd_lamp_3 (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  uint8_t *data; +  int error, status_len, tmo; + +  status_len = 8; +  RET_IF_ERR(query_status (s)); +  RET_IF_ERR(query_status (s)); +  RET_IF_ERR(send_cmd_start_calibrate_ccd_3 (s)); +  RET_IF_ERR(query_status (s)); +  tmo = 20; /* like Windows driver, CCD lamp adjustment */ +  while (--tmo >= 0) +  { +    data = pixma_newcmd (&mp->cb, cmd_end_calibrate_ccd_3, 0, status_len); +    RET_IF_ERR(pixma_exec (s, &mp->cb)); +    memcpy (mp->current_status, data, status_len); +    PDBG(pixma_dbg (3, "Lamp status: %u , timeout in: %u\n", data[0], tmo)); +    if (mp->current_status[0] == 3 || !is_scanning_from_tpu (s)) +      break; +    WAIT_INTERRUPT(1000); +  } +  return error; +} + +static int wait_until_ready (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  int error, tmo = 60; + +  RET_IF_ERR((mp->generation >= 3) ? query_status_3 (s) : query_status (s)); +  while (!is_calibrated (s)) +  { +    WAIT_INTERRUPT(1000); +    if (mp->generation >= 3) +      RET_IF_ERR(query_status_3 (s)); +    else if (s->cfg->pid == MP800R_PID) +      RET_IF_ERR (query_status (s)); +    if (--tmo == 0) +    { +      PDBG(pixma_dbg (1, "WARNING: Timed out in wait_until_ready()\n")); +      PDBG(query_status (s)); +      return PIXMA_ETIMEDOUT; +    } +  } +  return 0; +} + +/* the RGB images are shifted by # of lines              */ +/* the R image is shifted by colshift[0]                 */ +/* the G image is shifted by colshift[1]                 */ +/* the B image is shifted by colshift[2]                 */ +/* usually one of the RGB images must not be shifted     */ +/* which one depends on the scanner                      */ +/* some scanners have an additional stripe shift         */ +/* e.g. colshift[0]=0, colshift[1]=1, colshift[2]=2      */ +/* R image is OK: RGBRGBRGB...                           */ +/*                 ^^ ^^ ^^                              */ +/*                 || || ||                              */ +/* shift G image: RG|RG|RG|...                           */ +/*                  |  |  |                              */ +/* shift B image: RGBRGBRGB...                           */ +/* this doesn't affect the G and B images                */ +/* G image will become the R image in the next run       */ +/* B image will become the G image in the next run       */ +/* the next line will become the B image in the next run */ +static uint8_t * +shift_colors (uint8_t * dptr, uint8_t * sptr, unsigned w, unsigned dpi, +              unsigned pid, unsigned c, int * colshft, unsigned strshft) +{ +  unsigned i, sr, sg, sb, st; +  UNUSED(dpi); +  UNUSED(pid); +  sr = colshft[0]; +  sg = colshft[1]; +  sb = colshft[2]; + +  /* PDBG (pixma_dbg (4, "*shift_colors***** c=%u, w=%i, sr=%u, sg=%u, sb=%u, strshft=%u ***** \n", +        c, w, sr, sg, sb, strshft)); */ + +  for (i = 0; i < w; i++) +  { +    /* stripes shift for MP970 at 4800 dpi, MP810 at 2400 dpi */ +    st = (i % 2 == 0) ? strshft : 0; + +    *sptr++ = *(dptr++ + sr + st); +    if (c == 6) +      *sptr++ = *(dptr++ + sr + st); +    *sptr++ = *(dptr++ + sg + st); +    if (c == 6) +      *sptr++ = *(dptr++ + sg + st); +    *sptr++ = *(dptr++ + sb + st); +    if (c == 6) +      *sptr++ = *(dptr++ + sb + st); +  } + +  return dptr; +} + +static uint8_t * +shift_colorsCS9000 (uint8_t * dptr, uint8_t * sptr, unsigned w, unsigned dpi, +                    unsigned pid, unsigned c, int * colshft, unsigned strshft, +                    unsigned strshft2, unsigned jump) + +{ +  unsigned i, sr, sg, sb, st, st2; +  UNUSED(dpi); +  UNUSED(pid); +  sr = colshft[0]; +  sg = colshft[1]; +  sb = colshft[2]; + +  for (i = 0; i < w; i++) +  { +    if (i < (w / 2)) +    { +      /* stripes shift for 1st 4 images for Canoscan 9000F at 9600dpi */ +      st = (i % 2 == 0) ? strshft : 0; +      *sptr++ = *(dptr++ + sr + st); +      if (c == 6) +        *sptr++ = *(dptr++ + sr + st); +      *sptr++ = *(dptr++ + sg + st); +      if (c == 6) +        *sptr++ = *(dptr++ + sg + st); +      *sptr++ = *(dptr++ + sb + st); +      if (c == 6) +        *sptr++ = *(dptr++ + sb + st); +    } +    if (i >= (w / 2)) +    { +      /* stripes shift for 2nd 4 images for Canoscan 9000F at 9600dpi */ +      st2 = (i % 2 == 0) ? strshft2 : 0; +      *sptr++ = *(dptr++ + sr + jump + st2); +      if (c == 6) +        *sptr++ = *(dptr++ + sr + jump + st2); +      *sptr++ = *(dptr++ + sg + jump + st2); +      if (c == 6) +        *sptr++ = *(dptr++ + sg + jump + st2); +      *sptr++ = *(dptr++ + sb + jump + st2); +      if (c == 6) +        *sptr++ = *(dptr++ + sb + jump + st2); +    } +  } +  return dptr; +} + +static uint8_t * +shift_colorsCS9000_4800 (uint8_t * dptr, uint8_t * sptr, unsigned w, +                         unsigned dpi, unsigned pid, unsigned c, int * colshft, +                         unsigned strshft, unsigned strshft2, unsigned jump) + +{ +  unsigned i, sr, sg, sb, st2; +  UNUSED(dpi); +  UNUSED(pid); +  UNUSED(strshft); +  sr = colshft[0]; +  sg = colshft[1]; +  sb = colshft[2]; + +  for (i = 0; i < w; i++) +  { +    /* stripes shift for 2nd 4 images +     * for Canoscan 9000F with 16 bit flatbed scans at 4800dpi */ +    st2 = (i % 2 == 0) ? strshft2 : 0; +    *sptr++ = *(dptr++ + sr + jump + st2); +    if (c == 6) +      *sptr++ = *(dptr++ + sr + jump + st2); +    *sptr++ = *(dptr++ + sg + jump + st2); +    if (c == 6) +      *sptr++ = *(dptr++ + sg + jump + st2); +    *sptr++ = *(dptr++ + sb + jump + st2); +    if (c == 6) +      *sptr++ = *(dptr++ + sb + jump + st2); +  } +  return dptr; +} + +/* under some conditions some scanners have sub images in one line */ +/* e.g. doubled image, line size = 8				   */ +/* line before reordering: px1 px3 px5 px7 px2 px4 px6 px8         */ +/* line after reordering:  px1 px2 px3 px4 px5 px6 px7 px8         */ +static void reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, +                            unsigned n, unsigned m, unsigned w, +                            unsigned line_size) +{ +  unsigned i; + +  for (i = 0; i < w; i++) +  { /* process complete line */ +    memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c); +  } +  memcpy (sptr, linebuf, line_size); +} + +/* special reorder matrix for mp960 */ +static void mp960_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, +                                      unsigned n, unsigned m, unsigned w, +                                      unsigned line_size) +{ +  unsigned i, i2; + +  /* try and copy 2 px at once */ +  for (i = 0; i < w; i++) +  { /* process complete line */ +    i2 = i % 2; +    if (i < w / 2) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m)), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m)), sptr + c * i, c); +    } +    else +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 1), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 1), sptr + c * i, c); +    } +  } + +  memcpy (sptr, linebuf, line_size); +} + +/* special reorder matrix for mp970 */ +static void mp970_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, +                                      unsigned w, unsigned line_size) +{ +  unsigned i, i8; + +  for (i = 0; i < w; i++) +  { /* process complete line */ +    i8 = i % 8; +    memcpy (linebuf + c * (i + i8 - ((i8 > 3) ? 7 : 0)), sptr + c * i, c); +  } +  memcpy (sptr, linebuf, line_size); +} + +/* special reorder matrix for CS9000F */ +static void cs9000f_initial_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, +                                                 unsigned c, unsigned n, unsigned m, +                                                 unsigned w, unsigned line_size) +{ +  unsigned i, i2; + +  /* try and copy 2 px at once */ +  for (i = 0; i < w; i++) +  { /* process complete line */ +    i2 = i % 2; +    if (i < w / 8) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m)), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m)), sptr + c * i, c); +    } +    else if (i >= w / 8 && i < w / 4) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 1), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 1), sptr + c * i, c); +    } +    else if (i >= w / 4 && i < 3 * w / 8) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 2), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 2), sptr + c * i, c); +    } +    else if (i >= 3 * w / 8 && i < w / 2) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 3), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 3), sptr + c * i, c); +    } +    else if (i >= w / 2 && i < 5 * w / 8) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 4), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 4), sptr + c * i, c); +    } +    else if (i >= 5 * w / 8 && i < 3 * w / 4) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 5), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 5), sptr + c * i, c); +    } +    else if (i >= 3 * w / 4 && i < 7 * w / 8) +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 6), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 6), sptr + c * i, c); +    } +    else +    { +      if (i2 == 0) +        memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 7), sptr + c * i, c); +      else +        memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 7), sptr + c * i, c); +    } +  } + +  memcpy (sptr, linebuf, line_size); +} + +/* CS9000F 9600dpi reorder: actually 4800dpi since each pixel is doubled      */ +/* need to rearrange each sequence of 16 pairs of pixels as follows:          */ +/* start px : 0   2   4   6   8   10  12  14  16  18  20  22  24  26  28  30  */ +/* before   : p1a p1b p1c p1d p2a p2b p2c p2d p3a p3b p3c p3d p4a p4b p4c p4d */ +/* after    : p1a p3a p1b p3b p1c p3c p1d p3d p2a p4a p2b p4b p2c p4c p2d p4d */ +/* start px : 0   16  2   18  4   20  6   22  8   24  10  26  12  28  14  30  */ +/* change   :      *   *   *   *   *   *   *   *   *   *   *   *   *   *      */ +/* no change:  *                                                           *  */ +/* so the 1st and the 3rd set are interleaved, followed by the 2nd and 4th sets interleaved */ +static void cs9000f_second_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, +                                                unsigned c, unsigned w, +                                                unsigned line_size) +{ +  unsigned i, i8; +  static const int shifts[8] = +  { 2, 4, 6, 8, -8, -6, -4, -2 }; + +  for (i = 0; i < w; i += 2) +  { /* process complete line */ +    i8 = (i >> 1) & 0x7; +    /* Copy 2 pixels at once */ +    memcpy (linebuf + c * (i + shifts[i8]), sptr + c * i, c * 2); +  } + +  memcpy (sptr, linebuf, line_size); +} + +#ifndef TPU_48 +static unsigned +pack_48_24_bpc (uint8_t * sptr, unsigned n) +{ +  unsigned i; +  uint8_t *cptr, lsb; +  static uint8_t offset = 0; + +  cptr = sptr; +  if (n % 2 != 0) +  PDBG (pixma_dbg (3, "WARNING: misaligned image.\n")); +  for (i = 0; i < n; i += 2) +  { +    /* offset = 1 + (offset % 3); */ +    lsb = *sptr++; +    *cptr++ = ((*sptr++) << offset) | lsb >> (8 - offset); +  } +  return (n / 2); +} +#endif + +/* This function deals both with PIXMA CCD sensors producing shifted color + * planes images, Grayscale CCD scan and Generation >= 3 high dpi images. + * Each complete line in mp->imgbuf is processed for shifting CCD sensor + * color planes, reordering pixels above 600 dpi for Generation >= 3, and + * converting to Grayscale for CCD sensors. */ +static unsigned post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  unsigned c, lines, line_size, n, m, cw, cx, reducelines; +  uint8_t *sptr, *dptr, *gptr, *cptr; +  unsigned /*color_shift, stripe_shift, stripe_shift2,*/ jumplines /*, height*/; +  int test; + +  /* For testers: */ +  /* set this to 1 in order to get unprocessed images next to one another at 9600dpi */ +  /* other resolutions should not be affected */ +  /* set this to 2 if you want to see the same with jumplines=0 */ +  test = 0; +  jumplines = 0; + +  c = ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels) +      * ((s->param->software_lineart) ? 8 : s->param->depth) / 8; +  cw = c * s->param->w; +  cx = c * s->param->xs; + +  /* PDBG (pixma_dbg (4, "*post_process_image_data***** c = %u, cw = %u, cx = %u  *****\n", c, cw, cx)); */ + +  if (mp->generation >= 3) +    n = s->param->xdpi / 600; +  else +    /* FIXME: maybe need different values for CIS and CCD sensors */ +    n = s->param->xdpi / 2400; + +  /* Some exceptions to global rules here */ +  if (s->cfg->pid == MP970_PID || s->cfg->pid == MP990_PID || s->cfg->pid == MG8200_PID +      || s->cfg->pid == CS8800F_PID || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) +    n = MIN (n, 4); + +  /* exception for 9600dpi on Canoscan 9000F */ +  if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600)) +  { +    n = 8; +    if (test > 0) +      n = 1; /* test if 8 images are next to one another */ +  } + +  /* test if 2 images are next to one another */ +  if ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800) && (test > 0)) +  { +    n = 1; +  } + +  m = (n > 0) ? s->param->wx / n : 1; + +  sptr = dptr = gptr = cptr = mp->imgbuf; +  line_size = get_cis_ccd_line_size (s); +  /* PDBG (pixma_dbg (4, "*post_process_image_data***** ----- Set n=%u, m=%u, line_size=%u ----- ***** \n", n, m, line_size)); */ +  /* PDBG (pixma_dbg (4, "*post_process_image_data***** ----- spr=dpr=%u, linebuf=%u ----- ***** \n", sptr, mp->linebuf)); */ + +  lines = (mp->data_left_ofs - mp->imgbuf) / line_size; +  /* PDBG (pixma_dbg (4, "*post_process_image_data***** lines = %i > 2 * mp->color_shift + mp->stripe_shift = %i ***** \n", +               lines, 2 * mp->color_shift + mp->stripe_shift)); */ +  /* PDBG (pixma_dbg (4, "*post_process_image_data***** mp->color_shift = %u, mp->stripe_shift = %u, , mp->stripe_shift2 = %u ***** \n", +               mp->color_shift, mp->stripe_shift, mp->stripe_shift2)); */ + +  /*color_shift = mp->color_shift;*/ +  /*stripe_shift = mp->stripe_shift;*/ +  /*stripe_shift2 = mp->stripe_shift2;*/ +  jumplines = mp->jumplines; + +  /* height not needed here! */ +  /* removed to avoid confusion */ +  /* height = MIN (s->param->h + calc_shifting (s), +                    s->cfg->height * s->param->ydpi / 75); */ + +  /* have to test if rounding down is OK or not -- currently 0.5 lines is rounded down */ +  /* note stripe shifts doubled already in definitions */ +  if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600) && (test > 0)) +  { +    /* using test==2 you can check in GIMP the required offset, and +       use the below line (uncommented) and replace XXX with that +       number, and then compile again with test set to 1. */ + +    jumplines = 32; +    if (test == 2) +      jumplines = 0; +  } + +  /* mp960 test */ +  if ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800) && (test > 0)) +  { +    jumplines = 32; +    if (test == 2) +      jumplines = 0; +  } + +  reducelines = ((2 * mp->color_shift + mp->stripe_shift) + jumplines); +  /* PDBG (pixma_dbg (4, "*post_process_image_data: lines %u, reducelines %u \n", lines, reducelines)); */ +  if (lines > reducelines) +  { /* (line - reducelines) of image lines can be converted */ +    unsigned i; + +    lines -= reducelines; + +    for (i = 0; i < lines; i++, sptr += line_size) +    { /* convert only full image lines */ +      /* Color plane and stripes shift needed by e.g. CCD */ +      /* PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, w=%i, line_size=%u ***** \n", +       c, n, m, s->param->wx, line_size)); */ +      if (c >= 3) +      { +        if (((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600)) +            || ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800)) +            || ((s->cfg->pid == MP810_PID) && (s->param->xdpi == 4800))) +        { +          dptr = shift_colorsCS9000 (dptr, sptr, s->param->wx, s->param->xdpi, +                                     s->cfg->pid, c, mp->shift, +                                     mp->stripe_shift, mp->stripe_shift2, +                                     jumplines * line_size); +        } + +        else if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) /* 9000F: 16 bit flatbed scan at 4800dpi */ +                  && ((s->param->mode == PIXMA_SCAN_MODE_COLOR_48) +                      || (s->param->mode == PIXMA_SCAN_MODE_GRAY_16)) +                  && (s->param->xdpi == 4800) +                  && (s->param->source == PIXMA_SOURCE_FLATBED)) +          dptr = shift_colorsCS9000_4800 (dptr, sptr, s->param->wx, +                                          s->param->xdpi, s->cfg->pid, c, +                                          mp->shift, mp->stripe_shift, +                                          mp->stripe_shift2, +                                          jumplines * line_size); + +        else +          /* all except 9000F at 9600dpi */ +          dptr = shift_colors (dptr, sptr, s->param->wx, s->param->xdpi, +                               s->cfg->pid, c, mp->shift, mp->stripe_shift); +      } + +      /*PDBG (pixma_dbg (4, "*post_process_image_data***** test = %i  *****\n", test)); */ + +      /*--comment out all between this line and the one below for 9000F tests at 9600dpi or MP960 at 4800dpi ------*/ +      /* if ( 0 ) */ +      if ((((s->cfg->pid != CS9000F_PID && s->cfg->pid != CS9000F_MII_PID) || (s->param->xdpi < 9600)) +          && ((s->cfg->pid != MP960_PID) || (s->param->xdpi < 4800)) +          && ((s->cfg->pid != MP810_PID) || (s->param->xdpi < 4800))) +          || (test == 0)) +      { +        /* PDBG (pixma_dbg (4, "*post_process_image_data***** MUST GET HERE WHEN TEST == 0  *****\n")); */ + +        if (!((s->cfg->pid == MP810_PID) && (s->param->xdpi == 4800)) +            && !((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800)) +            && !((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600))) +        { /* for both flatbed & TPU */ +          /* PDBG (pixma_dbg (4, "*post_process_image_data***** reordering pixels normal n = %i  *****\n", n)); */ +          reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx, line_size); +        } + +        if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600)) +        { +          /* PDBG (pixma_dbg (4, "*post_process_image_data***** cs900f_initial_reorder_pixels n = %i  *****\n", n)); */ +          /* this combines pixels from 8 images 2px at a time from left to right: 1122334455667788... */ +          cs9000f_initial_reorder_pixels (mp->linebuf, sptr, c, n, m, +                                          s->param->wx, line_size); +          /* final interleaving */ +          cs9000f_second_reorder_pixels (mp->linebuf, sptr, c, s->param->wx, +                                         line_size); +        } + +        /* comment: special image format for MP960 in flatbed mode +         at 4800dpi. It is actually 2400dpi, with each pixel +         doubled. The TPU mode has proper pixel ordering */ +        if ((((s->cfg->pid == MP960_PID) || (s->cfg->pid == MP810_PID)) && (s->param->xdpi == 4800)) +            && (n > 0)) +        { +          /* for both flatbed & TPU */ +          /* PDBG (pixma_dbg (4, "*post_process_image_data***** flatbed mp960_reordering pixels n = %i  *****\n", n)); */ +          mp960_reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx, +                                line_size); +        } + +        /* comment: MP970, CS8800F, CS9000F specific reordering for 4800 dpi */ +        if ((s->cfg->pid == MP970_PID || s->cfg->pid == CS8800F_PID +            || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID +            || s->cfg->pid == MP990_PID) && (s->param->xdpi == 4800)) +        { +          /*PDBG (pixma_dbg (4, "*post_process_image_data***** mp970_reordering pixels n = %i  *****\n", n)); */ +          mp970_reorder_pixels (mp->linebuf, sptr, c, s->param->wx, line_size); +        } + +      } +      /*-------------------------------------------------------*/ + +      /* PDBG (pixma_dbg (4, "*post_process_image_data: sptr=%u, dptr=%u \n", sptr, dptr)); */ + +      /* Crop line to selected borders */ +      memmove (cptr, sptr + cx, cw); +      /* PDBG (pixma_dbg (4, "*post_process_image_data***** crop line: cx=%u, cw=%u ***** \n", cx, cw)); */ + +      /* Color to Lineart convert for CCD sensor */ +      if (is_lineart (s)) +        cptr = gptr = pixma_binarize_line (s->param, gptr, cptr, s->param->w, c); +#ifndef TPUIR_USE_RGB +      /* save IR only for CCD sensor */ +      else if (is_tpuir (s)) +        cptr = gptr = pixma_r_to_ir (gptr, cptr, s->param->w, c); +      /* Color to Grayscale convert for CCD sensor */ +      else if (is_gray_all (s)) +#else +      /* IR *and* Color to Grayscale convert for CCD sensor */ +      else if (is_tpuir (s) || is_gray_all (s)) +#endif +        cptr = gptr = pixma_rgb_to_gray (gptr, cptr, s->param->w, c); +      else +        cptr += cw; +    } +    /* PDBG (pixma_dbg (4, "*post_process_image_data: sptr=%u, dptr=%u \n", sptr, dptr)); */ +  } +  ib->rptr = mp->imgbuf; +  ib->rend = cptr; +  return mp->data_left_ofs - sptr; /* # of non processed bytes */ +  /* contains shift color data for new lines */ +  /* and already received data for the next line */ +} + +static int mp810_open (pixma_t * s) +{ +  mp810_t *mp; +  uint8_t *buf; + +  mp = (mp810_t *) calloc (1, sizeof(*mp)); +  if (!mp) +    return PIXMA_ENOMEM; + +  buf = (uint8_t *) malloc (CMDBUF_SIZE + IMAGE_BLOCK_SIZE); +  if (!buf) +  { +    free (mp); +    return PIXMA_ENOMEM; +  } + +  s->subdriver = mp; +  mp->state = state_idle; + +  mp->cb.buf = buf; +  mp->cb.size = CMDBUF_SIZE; +  mp->cb.res_header_len = 8; +  mp->cb.cmd_header_len = 16; +  mp->cb.cmd_len_field_ofs = 14; + +  mp->imgbuf = buf + CMDBUF_SIZE; + +  /* General rules for setting Pixma protocol generation # */ +  mp->generation = (s->cfg->pid >= MP810_PID) ? 2 : 1; + +  if (s->cfg->pid >= MP970_PID) +    mp->generation = 3; + +  if (s->cfg->pid >= MP990_PID) +    mp->generation = 4; + +  /* And exceptions to be added here */ +  if (s->cfg->pid == CS8800F_PID) +    mp->generation = 3; + +  if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) +    mp->generation = 4; + +  /* TPU info data setup */ +  mp->tpu_datalen = 0; + +  if (mp->generation < 4) +  { +    /* Canoscan 8800F ignores commands if not initialized */ +    if (s->cfg->pid == CS8800F_PID) +      abort_session (s); +    else +    { +      query_status (s); +      handle_interrupt (s, 200); +      if (mp->generation == 3 && has_ccd_sensor (s)) +        send_cmd_start_calibrate_ccd_3 (s); +    } +  } +  return 0; +} + +static void mp810_close (pixma_t * s) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; + +  mp810_finish_scan (s); +  free (mp->cb.buf); +  free (mp); +  s->subdriver = NULL; +} + +static int mp810_check_param (pixma_t * s, pixma_scan_param_t * sp) +{ +  mp810_t *mp = (mp810_t *) s->subdriver; +  unsigned w_max; + +  /* PDBG (pixma_dbg (4, "*mp810_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */ + +  sp->channels = 3; +  sp->software_lineart = 0; +  switch (sp->mode) +  { +    /* standard scan modes +     * 8 bit per channel in color and grayscale mode +     * 16 bit per channel with TPU */ +    case PIXMA_SCAN_MODE_GRAY: +    case PIXMA_SCAN_MODE_NEGATIVE_GRAY: +    case PIXMA_SCAN_MODE_TPUIR: +      sp->channels = 1; +      /* fall through */ +    case PIXMA_SCAN_MODE_COLOR: +    case PIXMA_SCAN_MODE_NEGATIVE_COLOR: +      sp->depth = 8; +#ifdef TPU_48 +#ifndef DEBUG_TPU_48 +      if (sp->source == PIXMA_SOURCE_TPU) +#endif +        sp->depth = 16; /* TPU in 16 bits mode */ +#endif +      break; +      /* extended scan modes for 48 bit flatbed scanners +       * 16 bit per channel in color and grayscale mode */ +    case PIXMA_SCAN_MODE_GRAY_16: +      sp->channels = 1; +      sp->depth = 16; +      break; +    case PIXMA_SCAN_MODE_COLOR_48: +      sp->channels = 3; +      sp->depth = 16; +      break; +      /* software lineart +       * 1 bit per channel */ +    case PIXMA_SCAN_MODE_LINEART: +      sp->software_lineart = 1; +      sp->channels = 1; +      sp->depth = 1; +      break; +  } + +  /* for software lineart w must be a multiple of 8 +   * I don't know why is_lineart(s) doesn't work here */ +  if (sp->software_lineart == 1 && sp->w % 8) +  { +    sp->w += 8 - (sp->w % 8); + +    /* do not exceed the scanner capability */ +    w_max = s->cfg->width * s->cfg->xdpi / 75; +    w_max -= w_max % 8; +    if (sp->w > w_max) +      sp->w = w_max; +  } + +  if (sp->source == PIXMA_SOURCE_TPU && !sp->tpu_offset_added) +  { +    unsigned fixed_offset_y; /* TPU offsets for CanoScan 8800F, or other CCD at 300dpi. */ +    unsigned max_y; /* max TPU height for CS9000F at 75 dpi */ + +    /* CanoScan 8800F and others adding an offset depending on resolution */ +    /* CS9000F and others maximum TPU height */ +    switch (s->cfg->pid) +    { +      case CS8800F_PID: +        fixed_offset_y = 140; +        max_y = MIN (740, s->cfg->height); +        break; +      case CS9000F_PID: +      case CS9000F_MII_PID: +        fixed_offset_y = 146; +        max_y = MIN (740, s->cfg->height); +        break; +      default: +        fixed_offset_y = 0; +        max_y = s->cfg->height; +        break; +    } + +    /* cropping y and h to scanable area */ +    max_y *= (sp->ydpi) / 75; +    sp->y = MIN(sp->y, max_y); +    sp->h = MIN(sp->h, max_y - sp->y); +    /* PDBG (pixma_dbg (4, "*mp810_check_param***** Cropping: y=%u, h=%u *****\n", +     sp->y, sp->h)); */ +    if (!sp->h) +      return SANE_STATUS_INVAL; /* no lines */ + +    /* Convert the offsets from 300dpi to actual resolution */ +    fixed_offset_y = fixed_offset_y * (sp->xdpi) / 300; + +    /* In TPU mode, the CS9000F appears to always subtract 146 from the +     vertical starting position, but clamps its at 0. Therefore vertical +     offsets 0 through 146 (@300 dpi) get all mapped onto the same +     physical starting position: line 0. Then, starting from 147, the +     offsets get mapped onto successive physical lines: +     y    line +     0 ->  0 +     1 ->  0 +     2 ->  0 +     ... +     146 ->  0 +     147 ->  1 +     148 ->  2 +     ... +     Since a preview scan is typically made starting from y = 0, but +     partial image scans usually start at y >> 147, this results in a +     discontinuity in the y to line mapping, resulting in wrong offsets. +     To prevent this, we must always add (at least) 146 to the y +     offset before it is sent to the scanner. The scanner will then +     map y = 0 (146) to the first line, y = 1 (147) to the second line, +     and so on. Any distance that is then measured on the preview scan, +     can be translated without any discontinuity. + +     However, there is one complication: during a preview scan, which +     normally covers the whole scan area of the scanner, we should _not_ +     add the offset because it will result in a reduced number of lines +     being returned (the scan height is clamped in +     pixma_check_scan_param()). Since the frontend has no way of telling +     that the scan area has been reduced, it would derive an incorrect +     effective scan resolution, and any position calculations based on +     this would therefore be inaccurate. + +     To prevent this, we don't add the offset in case y = 0, which is +     typically the case during a preview scan (the scanner effectively +     adds the offset for us, see above). In that way we keep the +     linearity and we don't affect the scan area during previews. +     */ + +    if (sp->y > 0) +      sp->y += fixed_offset_y; + +    /* Prevent repeated corrections as check_param may be called multiple times */ +    sp->tpu_offset_added = 1; +  } + +  if (mp->generation >= 2) +  { +    /* mod 32 and expansion of the X scan limits */ +    /* PDBG (pixma_dbg (4, "*mp810_check_param***** (gen>=2) xs=mod32 ----- Initially: x=%u, y=%u, w=%u, h=%u *****\n", sp->x, sp->y, sp->w, sp->h)); */ +    sp->xs = (sp->x) % 32; +  } +  else +  { +    sp->xs = 0; +    /* PDBG (pixma_dbg (4, "*mp810_check_param***** (else) xs=0 Selected origin, origin shift: %u, %u *****\n", sp->x, sp->xs)); */ +  } +  sp->wx = calc_raw_width (mp, sp); +  sp->line_size = sp->w * sp->channels * (((sp->software_lineart) ? 8 : sp->depth) / 8); /* bytes per line per color after cropping */ +  /* PDBG (pixma_dbg (4, "*mp810_check_param***** (else) Final scan width and line-size: %u, %"PRIu64" *****\n", sp->wx, sp->line_size)); */ + +  /* highest res is 600, 2400, 4800 or 9600 dpi */ +  { +    uint8_t k; + +    if ((sp->source == PIXMA_SOURCE_ADF || sp->source == PIXMA_SOURCE_ADFDUP) +        && mp->generation >= 4) +      /* ADF/ADF duplex mode: max scan res is 600 dpi, at least for generation 4 */ +      k = sp->xdpi / MIN (sp->xdpi, 600); +    else if (sp->source == PIXMA_SOURCE_TPU && sp->mode == PIXMA_SCAN_MODE_TPUIR) +      /* TPUIR mode: max scan res is 2400 dpi */ +      k = sp->xdpi / MIN (sp->xdpi, 2400); +    else if (sp->source == PIXMA_SOURCE_TPU && (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)) +      /* CS9000F in TPU mode */ +      k = sp->xdpi / MIN (sp->xdpi, 9600); +    else +      /* default */ +      k = sp->xdpi / MIN (sp->xdpi, 4800); + +    sp->x /= k; +    sp->xs /= k; +    sp->y /= k; +    sp->w /= k; +    sp->wx /= k; +    sp->h /= k; +    sp->xdpi /= k; +    sp->ydpi = sp->xdpi; +  } + +  /* lowest res is 75, 150, 300 or 600 dpi */ +  { +    uint8_t k; + +    if (sp->source == PIXMA_SOURCE_TPU && sp->mode == PIXMA_SCAN_MODE_TPUIR) +      /* TPUIR mode */ +      k = MAX (sp->xdpi, 600) / sp->xdpi; +    else if (sp->source == PIXMA_SOURCE_TPU +              && ((mp->generation >= 3) || (s->cfg->pid == MP810_PID) || (s->cfg->pid == MP960_PID))) +      /* TPU mode for generation 3+ scanners +       * MP810, MP960 appear to have a 200dpi mode for low-res scans, not 150 dpi */ +      k = MAX (sp->xdpi, 300) / sp->xdpi; +    else if (sp->source == PIXMA_SOURCE_TPU +              || sp->mode == PIXMA_SCAN_MODE_COLOR_48 || sp->mode == PIXMA_SCAN_MODE_GRAY_16) +      /* TPU mode and 16 bit flatbed scans +       * TODO: either the frontend (xsane) cannot handle 48 bit flatbed scans @ 75 dpi (prescan) +       *       or there is a bug in this subdriver */ +      k = MAX (sp->xdpi, 150) / sp->xdpi; +    else +      /* default */ +      k = MAX (sp->xdpi, 75) / sp->xdpi; + +    sp->x *= k; +    sp->xs *= k; +    sp->y *= k; +    sp->w *= k; +    sp->wx *= k; +    sp->h *= k; +    sp->xdpi *= k; +    sp->ydpi = sp->xdpi; +  } + +  /* PDBG (pixma_dbg (4, "*mp810_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n", +                   sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */ + +  return 0; +} + +static int mp810_scan (pixma_t * s) +{ +  int error = 0, tmo; +  mp810_t *mp = (mp810_t *) s->subdriver; + +  if (mp->state != state_idle) +    return PIXMA_EBUSY; + +  /* Generation 4: send XML dialog */ +  if (mp->generation == 4 && s->param->adf_pageid == 0) +  { +    if (!send_xml_dialog (s, XML_START_1)) +      return PIXMA_EPROTO; +    if (!send_xml_dialog (s, XML_START_2)) +      return PIXMA_EPROTO; +  } + +  /* clear interrupt packets buffer */ +  while (handle_interrupt (s, 0) > 0) +  { +  } + +  /* FIXME: Duplex ADF: check paper status only before odd pages (1,3,5,...). */ +  if (is_scanning_from_adf (s)) +  { +    if ((error = query_status (s)) < 0) +      return error; +    tmo = 10; +    while (!has_paper (s) && --tmo >= 0) +    { +      WAIT_INTERRUPT(1000); +      PDBG(pixma_dbg (2, "No paper in ADF. Timed out in %d sec.\n", tmo)); +    } +    if (!has_paper (s)) +      return PIXMA_ENO_PAPER; +  } + +  if (has_ccd_sensor (s) && (mp->generation <= 2)) +  { +    error = send_cmd_e920 (s); +    switch (error) +    { +      case PIXMA_ECANCELED: +      case PIXMA_EBUSY: +        PDBG(pixma_dbg (2, "cmd e920 or d520 returned %s\n", pixma_strerror (error))); +        /* fall through */ +      case 0: +        query_status (s); +        break; +      default: +        PDBG(pixma_dbg (1, "WARNING: cmd e920 or d520 failed %s\n", pixma_strerror (error))); +        return error; +    } +    tmo = 3; /* like Windows driver, CCD calibration ? */ +    while (--tmo >= 0) +    { +      WAIT_INTERRUPT(1000); +      PDBG(pixma_dbg (2, "CCD Calibration ends in %d sec.\n", tmo)); +    } +    /* pixma_sleep(2000000); */ +  } + +  tmo = 10; +  if (s->param->adf_pageid == 0 || mp->generation <= 2) +  { +    error = start_session (s); +    while (error == PIXMA_EBUSY && --tmo >= 0) +    { +      if (s->cancel) +      { +        error = PIXMA_ECANCELED; +        break; +      } +      PDBG(pixma_dbg (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1)); +      pixma_sleep (1000000); +      error = start_session (s); +    } +    if (error == PIXMA_EBUSY || error == PIXMA_ETIMEDOUT) +    { +      /* The scanner maybe hangs. We try to empty output buffer of the +       * scanner and issue the cancel command. */ +      PDBG(pixma_dbg (2, "Scanner hangs? Sending abort_session command.\n")); +      drain_bulk_in (s); +      abort_session (s); +      pixma_sleep (500000); +      error = start_session (s); +    } +    if ((error >= 0) || (mp->generation >= 3)) +      mp->state = state_warmup; +    if ((error >= 0) && (mp->generation <= 2)) +      error = select_source (s); +    if ((error >= 0) && (mp->generation >= 3) && has_ccd_sensor (s)) +      error = init_ccd_lamp_3 (s); +    if ((error >= 0) && !is_scanning_from_tpu (s)) +    { +      int i; +      /* FIXME: 48 bit flatbed scans don't need gamma tables +       *        the code below doesn't run */ +      /*if (is_color_48 (s) || is_gray_16 (s)) +       error = 0; +       else*/ +      for (i = (mp->generation >= 3) ? 3 : 1; i > 0 && error >= 0; i--) +        error = send_gamma_table (s); +    } +    else if (error >= 0) /* in TPU mode, for gen 1, 2, and 3 */ +      error = send_set_tpu_info (s); +  } +  else +    /* ADF pageid != 0 and gen3 or above */ +    pixma_sleep (1000000); + +  if ((error >= 0) || (mp->generation >= 3)) +    mp->state = state_warmup; +  if (error >= 0) +    error = send_scan_param (s); +  if ((error >= 0) && (mp->generation >= 3)) +    error = start_scan_3 (s); +  if (error < 0) +  { +    mp->last_block = 0x38; /* Force abort session if ADF scan */ +    mp810_finish_scan (s); +    return error; +  } +  return 0; +} + +static int mp810_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib) +{ +  int error; +  mp810_t *mp = (mp810_t *) s->subdriver; +  unsigned block_size, bytes_received, proc_buf_size, line_size; +  uint8_t header[16]; + +  if (mp->state == state_warmup) +  { /* prepare read image data */ +    /* PDBG (pixma_dbg (4, "**mp810_fill_buffer***** warmup *****\n")); */ + +    RET_IF_ERR(wait_until_ready (s)); +    pixma_sleep (1000000); /* No need to sleep, actually, but Window's driver +                            * sleep 1.5 sec. */ +    mp->state = state_scanning; +    mp->last_block = 0; + +    line_size = get_cis_ccd_line_size (s); +    proc_buf_size = (2 * calc_shifting (s) + 2) * line_size; +    mp->cb.buf = realloc (mp->cb.buf, CMDBUF_SIZE + IMAGE_BLOCK_SIZE + proc_buf_size); +    if (!mp->cb.buf) +      return PIXMA_ENOMEM; +    mp->linebuf = mp->cb.buf + CMDBUF_SIZE; +    mp->imgbuf = mp->data_left_ofs = mp->linebuf + line_size; +    mp->data_left_len = 0; +  } + +  do +  { /* read complete image data from the scanner */ +    if (s->cancel) +      return PIXMA_ECANCELED; +    if ((mp->last_block & 0x28) == 0x28) +    { /* end of image */ +      mp->state = state_finished; +      /* PDBG (pixma_dbg (4, "**mp810_fill_buffer***** end of image *****\n")); */ +      return 0; +    } +    /* PDBG (pixma_dbg (4, "*mp810_fill_buffer***** moving %u bytes into buffer *****\n", mp->data_left_len)); */ +    memmove (mp->imgbuf, mp->data_left_ofs, mp->data_left_len); +    error = read_image_block (s, header, mp->imgbuf + mp->data_left_len); +    if (error < 0) +    { +      if (error == PIXMA_ECANCELED) +      { +        /* NOTE: I see this in traffic logs but I don't know its meaning. */ +        read_error_info (s, NULL, 0); +      } +      return error; +    } + +    bytes_received = error; +    /*PDBG (pixma_dbg (4, "*mp810_fill_buffer***** %u bytes received by read_image_block *****\n", bytes_received));*/ +    block_size = pixma_get_be32 (header + 12); +    mp->last_block = header[8] & 0x38; +    if ((header[8] & ~0x38) != 0) +    { +      PDBG(pixma_dbg (1, "WARNING: Unexpected result header\n")); +      PDBG(pixma_hexdump (1, header, 16)); +    } +    PASSERT(bytes_received == block_size); + +    if (block_size == 0) +    { /* no image data at this moment. */ +      pixma_sleep (10000); +    } +    /* For TPU at 48 bits/pixel to output at 24 bits/pixel */ +#ifndef DEBUG_TPU_48 +#ifndef TPU_48 +    PDBG (pixma_dbg (1, "WARNING: 9000F using 24 instead of 48 bit processing \n")); +#ifndef DEBUG_TPU_24 +    if (is_scanning_from_tpu (s)) +#endif +    bytes_received = pack_48_24_bpc (mp->imgbuf + mp->data_left_len, bytes_received); +#endif +#endif +    /* Post-process the image data */ +    mp->data_left_ofs = mp->imgbuf + mp->data_left_len + bytes_received; +    mp->data_left_len = post_process_image_data (s, ib); +    mp->data_left_ofs -= mp->data_left_len; +    /* PDBG (pixma_dbg (4, "* mp810_fill_buffer: data_left_len %u \n", mp->data_left_len)); */ +    /* PDBG (pixma_dbg (4, "* mp810_fill_buffer: data_left_ofs %u \n", mp->data_left_ofs)); */ +  } +  while (ib->rend == ib->rptr); + +  return ib->rend - ib->rptr; +} + +static void mp810_finish_scan (pixma_t * s) +{ +  int error; +  mp810_t *mp = (mp810_t *) s->subdriver; + +  switch (mp->state) +  { +    case state_transfering: +      drain_bulk_in (s); +      /* fall through */ +    case state_scanning: +    case state_warmup: +    case state_finished: +      /* Send the get TPU info message */ +      if (is_scanning_from_tpu (s) && mp->tpu_datalen == 0) +        send_get_tpu_info_3 (s); +      /* FIXME: to process several pages ADF scan, must not send +       * abort_session and start_session between pages (last_block=0x28) */ +      if (mp->generation <= 2 || !is_scanning_from_adf (s) +          || mp->last_block == 0x38) +      { +        error = abort_session (s); /* FIXME: it probably doesn't work in duplex mode! */ +        if (error < 0) +          PDBG(pixma_dbg (1, "WARNING:abort_session() failed %d\n", error)); + +        /* Generation 4: send XML end of scan dialog */ +        if (mp->generation == 4) +        { +          if (!send_xml_dialog (s, XML_END)) +            PDBG(pixma_dbg (1, "WARNING:XML_END dialog failed \n")); +        } +      } +      mp->state = state_idle; +      /* fall through */ +    case state_idle: +      break; +  } +} + +static void mp810_wait_event (pixma_t * s, int timeout) +{ +  /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for +   * instance. */ +  while (s->events == 0 && handle_interrupt (s, timeout) > 0) +  { +  } +} + +static int mp810_get_status (pixma_t * s, pixma_device_status_t * status) +{ +  int error; + +  RET_IF_ERR(query_status (s)); +  status->hardware = PIXMA_HARDWARE_OK; +  status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER; +  status->cal = +      (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF; +  return 0; +} + +static const pixma_scan_ops_t pixma_mp800_ops = +{ +  mp810_open, +  mp810_close, +  mp810_scan, +  mp810_fill_buffer, +  mp810_finish_scan, +  mp810_wait_event, +  mp810_check_param, +  mp810_get_status +}; + +#define DEVICE(name, model, pid, dpi, adftpu_min_dpi, adftpu_max_dpi, tpuir_min_dpi, tpuir_max_dpi, w, h, cap) { \ +        name,              /* name */               \ +        model,             /* model */              \ +        CANON_VID, pid,    /* vid pid */            \ +        0,                 /* iface */              \ +        &pixma_mp800_ops,  /* ops */                \ +        0,                 /* min_xdpi not used in this subdriver */ \ +        dpi, 2*(dpi),      /* xdpi, ydpi */         \ +        adftpu_min_dpi, adftpu_max_dpi,  /* adftpu_min_dpi, adftpu_max_dpi */ \ +        tpuir_min_dpi, tpuir_max_dpi,    /* tpuir_min_dpi, tpuir_max_dpi */   \ +        w, h,              /* width, height */      \ +        PIXMA_CAP_CCD|     /* all scanners with CCD*/ \ +        PIXMA_CAP_EASY_RGB|                         \ +        PIXMA_CAP_GRAY|    /* all scanners with software grayscale */ \ +        PIXMA_CAP_LINEART| /* all scanners with software lineart */ \ +        PIXMA_CAP_GAMMA_TABLE|PIXMA_CAP_EVENTS|cap  \ +} + +#define END_OF_DEVICE_LIST DEVICE(NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +const pixma_config_t pixma_mp800_devices[] = +{ +  /* Generation 1: CCD */ +  DEVICE ("Canon PIXMA MP800", "MP800", MP800_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), +  DEVICE ("Canon PIXMA MP800R", "MP800R", MP800R_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), +  DEVICE ("Canon PIXMA MP830", "MP830", MP830_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_ADFDUP), + +  /* Generation 2: CCD */ +  DEVICE ("Canon PIXMA MP810", "MP810", MP810_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), +  DEVICE ("Canon PIXMA MP960", "MP960", MP960_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Generation 3 CCD not managed as Generation 2 */ +  DEVICE ("Canon Pixma MP970", "MP970", MP970_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Flatbed scanner CCD (2007) */ +  DEVICE ("Canoscan 8800F", "8800F", CS8800F_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU /*| PIXMA_CAP_NEGATIVE*/ | PIXMA_CAP_48BIT), + +  /* PIXMA 2008 vintage CCD */ +  DEVICE ("Canon MP980 series", "MP980", MP980_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Generation 4 CCD */ +  DEVICE ("Canon MP990 series", "MP990", MP990_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Flatbed scanner (2010) */ +  DEVICE ("Canoscan 9000F", "9000F", CS9000F_PID, 4800, 300, 9600, 600, 2400, 638, 877, PIXMA_CAP_TPUIR /*| PIXMA_CAP_NEGATIVE*/ | PIXMA_CAP_48BIT), + +  /* Latest devices (2010) Generation 4 CCD untested */ +  DEVICE ("Canon PIXMA MG8100", "MG8100", MG8100_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Latest devices (2011) Generation 4 CCD untested */ +  DEVICE ("Canon PIXMA MG8200", "MG8200", MG8200_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU), + +  /* Flatbed scanner (2013) */ +  DEVICE ("Canoscan 9000F Mark II", "9000FMarkII", CS9000F_MII_PID, 4800, 300, 9600, 600, 2400, 638, 877, PIXMA_CAP_TPUIR | PIXMA_CAP_48BIT), + +  END_OF_DEVICE_LIST +}; diff --git a/backend/pixma/pixma_rename.h b/backend/pixma/pixma_rename.h new file mode 100644 index 0000000..ad3d960 --- /dev/null +++ b/backend/pixma/pixma_rename.h @@ -0,0 +1,105 @@ +/* SANE - Scanner Access Now Easy. + +   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. + */ +#ifndef PIXMA_RENAME_H +#define PIXMA_RENAME_H + + +#undef BACKEND_NAME +#define BACKEND_NAME pixma + +#define pixma_cancel sanei_pixma_cancel +#define pixma_check_dpi sanei_pixma_check_dpi +#define pixma_check_result sanei_pixma_check_result +#define pixma_check_scan_param sanei_pixma_check_scan_param +#define pixma_cleanup sanei_pixma_cleanup +#define pixma_close sanei_pixma_close +#define pixma_cmd_transaction sanei_pixma_cmd_transaction +#define pixma_collect_devices sanei_pixma_collect_devices +#define pixma_connect sanei_pixma_connect +#define pixma_dbg DBG +#define pixma_disconnect sanei_pixma_disconnect +#define pixma_dump sanei_pixma_dump +#define pixma_enable_background sanei_pixma_enable_background +#define pixma_exec sanei_pixma_exec +#define pixma_exec_short_cmd sanei_pixma_exec_short_cmd +#define pixma_fill_gamma_table sanei_pixma_fill_gamma_table +#define pixma_find_scanners sanei_pixma_find_scanners +#define pixma_get_be16 sanei_pixma_get_be16 +#define pixma_get_be32 sanei_pixma_get_be32 +#define pixma_get_config sanei_pixma_get_config +#define pixma_get_device_config sanei_pixma_get_device_config +#define pixma_get_device_id sanei_pixma_get_device_id +#define pixma_get_device_model sanei_pixma_get_device_model +#define pixma_get_device_status sanei_pixma_get_device_status +#define pixma_get_string sanei_pixma_get_string +#define pixma_get_time sanei_pixma_get_time +#define pixma_hexdump sanei_pixma_hexdump +#define pixma_init sanei_pixma_init +#define pixma_io_cleanup sanei_pixma_io_cleanup +#define pixma_io_init sanei_pixma_io_init +#define pixma_map_status_errno sanei_pixma_map_status_errno +#define pixma_mp150_devices sanei_pixma_mp150_devices +#define pixma_mp730_devices sanei_pixma_mp730_devices +#define pixma_mp750_devices sanei_pixma_mp750_devices +#define pixma_mp800_devices sanei_pixma_mp800_devices +#define pixma_iclass_devices sanei_pixma_iclass_devices +#define pixma_newcmd sanei_pixma_newcmd +#define pixma_open sanei_pixma_open +#define pixma_print_supported_devices sanei_pixma_print_supported_devices +#define pixma_read_image sanei_pixma_read_image +#define pixma_read sanei_pixma_read +#define pixma_reset_device sanei_pixma_reset_device +#define pixma_scan sanei_pixma_scan +#define pixma_set_be16 sanei_pixma_set_be16 +#define pixma_set_be32 sanei_pixma_set_be32 +#define pixma_set_debug_level sanei_pixma_set_debug_level +#define pixma_set_interrupt_mode sanei_pixma_set_interrupt_mode +#define pixma_sleep sanei_pixma_sleep +#define pixma_strerror sanei_pixma_strerror +#define pixma_sum_bytes sanei_pixma_sum_bytes +#define pixma_wait_event sanei_pixma_wait_event +#define pixma_wait_interrupt sanei_pixma_wait_interrupt +#define pixma_write sanei_pixma_write + + +#endif diff --git a/backend/pixma/pixma_sane_options.c b/backend/pixma/pixma_sane_options.c new file mode 100644 index 0000000..2b8f609 --- /dev/null +++ b/backend/pixma/pixma_sane_options.c @@ -0,0 +1,362 @@ +/* Automatically generated from pixma_sane.c */ +static const SANE_Range constraint_gamma_table = +  { 0,255,0 }; +static const SANE_Range constraint_gamma = +  { SANE_FIX(0.3),SANE_FIX(5),SANE_FIX(0) }; +static const SANE_Range constraint_threshold = +  { 0,100,1 }; +static const SANE_Range constraint_threshold_curve = +  { 0,127,1 }; +static const SANE_Range constraint_adf_wait = +  { 0,3600,1 }; + + +static +int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list) +{ +  int i; +  for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {} +  return i; +} + +static +int build_option_descriptors(struct pixma_sane_t *ss) +{ +  SANE_Option_Descriptor *sod; +  option_descriptor_t *opt; + +  memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX)); + +  opt = &(OPT_IN_CTX[opt_opt_num_opts]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_TITLE_NUM_OPTIONS; +  sod->desc = SANE_DESC_NUM_OPTIONS; +  sod->name = ""; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_opt_num_opts].info = 0; +  opt->def.w = opt_last; +  opt->val.w = opt_last; + +  opt = &(OPT_IN_CTX[opt__group_1]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_GROUP; +  sod->title = SANE_I18N("Scan mode"); +  sod->desc = sod->title; + +  opt = &(OPT_IN_CTX[opt_resolution]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_TITLE_SCAN_RESOLUTION; +  sod->desc = SANE_DESC_SCAN_RESOLUTION; +  sod->name = "resolution"; +  sod->unit = SANE_UNIT_DPI; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_WORD_LIST; +  sod->constraint.word_list = ss->dpi_list; +  OPT_IN_CTX[opt_resolution].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.w = 75; +  opt->val.w = 75; + +  opt = &(OPT_IN_CTX[opt_mode]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_STRING; +  sod->title = SANE_TITLE_SCAN_MODE; +  sod->desc = SANE_DESC_SCAN_MODE; +  sod->name = "mode"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 31; +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_STRING_LIST; +  sod->constraint.string_list = ss->mode_list; +  OPT_IN_CTX[opt_mode].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.s = SANE_VALUE_SCAN_MODE_COLOR; +  opt->val.w = find_string_in_list(opt->def.s, sod->constraint.string_list); + +  opt = &(OPT_IN_CTX[opt_source]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_STRING; +  sod->title = SANE_TITLE_SCAN_SOURCE; +  sod->desc = SANE_I18N("Selects the scan source (such as a document-feeder). Set source before mode and resolution. Resets mode and resolution to auto values."); +  sod->name = "source"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 31; +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT; +  sod->constraint_type = SANE_CONSTRAINT_STRING_LIST; +  sod->constraint.string_list = ss->source_list; +  OPT_IN_CTX[opt_source].info = 0; +  opt->def.s = SANE_I18N("Flatbed"); +  opt->val.w = find_string_in_list(opt->def.s, sod->constraint.string_list); + +  opt = &(OPT_IN_CTX[opt_button_controlled]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_BOOL; +  sod->title = SANE_I18N("Button-controlled scan"); +  sod->desc = SANE_I18N("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."); +  sod->name = "button-controlled"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_button_controlled].info = 0; +  opt->def.w = SANE_FALSE; +  opt->val.w = SANE_FALSE; + +  opt = &(OPT_IN_CTX[opt__group_2]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_GROUP; +  sod->title = SANE_I18N("Gamma"); +  sod->desc = sod->title; + +  opt = &(OPT_IN_CTX[opt_custom_gamma]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_BOOL; +  sod->title = SANE_TITLE_CUSTOM_GAMMA; +  sod->desc = SANE_DESC_CUSTOM_GAMMA; +  sod->name = "custom-gamma"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_custom_gamma].info = 0; +  opt->def.w = SANE_TRUE; +  opt->val.w = SANE_TRUE; + +  opt = &(OPT_IN_CTX[opt_gamma_table]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_TITLE_GAMMA_VECTOR; +  sod->desc = SANE_DESC_GAMMA_VECTOR; +  sod->name = "gamma-table"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 4096 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &constraint_gamma_table; +  OPT_IN_CTX[opt_gamma_table].info = 0; + +  opt = &(OPT_IN_CTX[opt_gamma]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_FIXED; +  sod->title = SANE_I18N("Gamma function exponent"); +  sod->desc = SANE_I18N("Changes intensity of midtones"); +  sod->name = "gamma"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &constraint_gamma; +  OPT_IN_CTX[opt_gamma].info = 0; +  opt->def.w = SANE_FIX(AUTO_GAMMA); +  opt->val.w = SANE_FIX(AUTO_GAMMA); + +  opt = &(OPT_IN_CTX[opt__group_3]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_GROUP; +  sod->title = SANE_I18N("Geometry"); +  sod->desc = sod->title; + +  opt = &(OPT_IN_CTX[opt_tl_x]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_FIXED; +  sod->title = SANE_TITLE_SCAN_TL_X; +  sod->desc = SANE_DESC_SCAN_TL_X; +  sod->name = "tl-x"; +  sod->unit = SANE_UNIT_MM; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &ss->xrange; +  OPT_IN_CTX[opt_tl_x].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.w = SANE_FIX(0); +  opt->val.w = SANE_FIX(0); + +  opt = &(OPT_IN_CTX[opt_tl_y]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_FIXED; +  sod->title = SANE_TITLE_SCAN_TL_Y; +  sod->desc = SANE_DESC_SCAN_TL_Y; +  sod->name = "tl-y"; +  sod->unit = SANE_UNIT_MM; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &ss->yrange; +  OPT_IN_CTX[opt_tl_y].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.w = SANE_FIX(0); +  opt->val.w = SANE_FIX(0); + +  opt = &(OPT_IN_CTX[opt_br_x]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_FIXED; +  sod->title = SANE_TITLE_SCAN_BR_X; +  sod->desc = SANE_DESC_SCAN_BR_X; +  sod->name = "br-x"; +  sod->unit = SANE_UNIT_MM; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &ss->xrange; +  OPT_IN_CTX[opt_br_x].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.w = sod->constraint.range->max; +  opt->val.w = sod->constraint.range->max; + +  opt = &(OPT_IN_CTX[opt_br_y]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_FIXED; +  sod->title = SANE_TITLE_SCAN_BR_Y; +  sod->desc = SANE_DESC_SCAN_BR_Y; +  sod->name = "br-y"; +  sod->unit = SANE_UNIT_MM; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &ss->yrange; +  OPT_IN_CTX[opt_br_y].info = SANE_INFO_RELOAD_PARAMS; +  opt->def.w = sod->constraint.range->max; +  opt->val.w = sod->constraint.range->max; + +  opt = &(OPT_IN_CTX[opt__group_4]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_GROUP; +  sod->title = SANE_I18N("Buttons"); +  sod->desc = sod->title; + +  opt = &(OPT_IN_CTX[opt_button_update]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_BUTTON; +  sod->title = SANE_I18N("Update button state"); +  sod->desc = sod->title; +  sod->name = "button-update"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 0; +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_button_update].info = 0; + +  opt = &(OPT_IN_CTX[opt_button_1]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Button 1"); +  sod->desc = sod->title; +  sod->name = "button-1"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_button_1].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  opt = &(OPT_IN_CTX[opt_button_2]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Button 2"); +  sod->desc = sod->title; +  sod->name = "button-2"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_button_2].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  opt = &(OPT_IN_CTX[opt_original]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Type of original to scan"); +  sod->desc = sod->title; +  sod->name = "original"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_original].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  opt = &(OPT_IN_CTX[opt_target]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Target operation type"); +  sod->desc = sod->title; +  sod->name = "target"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_target].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  opt = &(OPT_IN_CTX[opt_scan_resolution]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Scan resolution"); +  sod->desc = sod->title; +  sod->name = "scan-resolution"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; +  sod->constraint_type = SANE_CONSTRAINT_NONE; +  OPT_IN_CTX[opt_scan_resolution].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  opt = &(OPT_IN_CTX[opt__group_5]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_GROUP; +  sod->title = SANE_I18N("Extras"); +  sod->desc = sod->title; + +  opt = &(OPT_IN_CTX[opt_threshold]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_TITLE_THRESHOLD; +  sod->desc = SANE_DESC_THRESHOLD; +  sod->name = "threshold"; +  sod->unit = SANE_UNIT_PERCENT; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &constraint_threshold; +  OPT_IN_CTX[opt_threshold].info = 0; +  opt->def.w = 50; +  opt->val.w = 50; + +  opt = &(OPT_IN_CTX[opt_threshold_curve]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("Threshold curve"); +  sod->desc = SANE_I18N("Dynamic threshold curve, from light to dark, normally 50-65"); +  sod->name = "threshold-curve"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &constraint_threshold_curve; +  OPT_IN_CTX[opt_threshold_curve].info = 0; + +  opt = &(OPT_IN_CTX[opt_adf_wait]); +  sod = &opt->sod; +  sod->type = SANE_TYPE_INT; +  sod->title = SANE_I18N("ADF Waiting Time"); +  sod->desc = SANE_I18N("When set, the scanner waits upto the specified time in seconds for a new document inserted into the automatic document feeder."); +  sod->name = "adf-wait"; +  sod->unit = SANE_UNIT_NONE; +  sod->size = 1 * sizeof(SANE_Word); +  sod->cap  = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE; +  sod->constraint_type = SANE_CONSTRAINT_RANGE; +  sod->constraint.range = &constraint_adf_wait; +  OPT_IN_CTX[opt_adf_wait].info = 0; +  opt->def.w = 0; +  opt->val.w = 0; + +  return 0; + +} diff --git a/backend/pixma/pixma_sane_options.h b/backend/pixma/pixma_sane_options.h new file mode 100644 index 0000000..1472f1f --- /dev/null +++ b/backend/pixma/pixma_sane_options.h @@ -0,0 +1,51 @@ +/* Automatically generated from pixma_sane.c */ + +typedef union { +  SANE_Word w; +  SANE_Int  i; +  SANE_Bool b; +  SANE_Fixed f; +  SANE_String s; +  void *ptr; +} option_value_t; + +typedef enum { +  opt_opt_num_opts, +  opt__group_1, +  opt_resolution, +  opt_mode, +  opt_source, +  opt_button_controlled, +  opt__group_2, +  opt_custom_gamma, +  opt_gamma_table, +  opt_gamma, +  opt__group_3, +  opt_tl_x, +  opt_tl_y, +  opt_br_x, +  opt_br_y, +  opt__group_4, +  opt_button_update, +  opt_button_1, +  opt_button_2, +  opt_original, +  opt_target, +  opt_scan_resolution, +  opt__group_5, +  opt_threshold, +  opt_threshold_curve, +  opt_adf_wait, +  opt_last +} option_t; + + +typedef struct { +  SANE_Option_Descriptor sod; +  option_value_t val,def; +  SANE_Word info; +} option_descriptor_t; + + +struct pixma_sane_t; +static int build_option_descriptors(struct pixma_sane_t *ss); diff --git a/backend/pixma/scripts/pixma_gen_options.py b/backend/pixma/scripts/pixma_gen_options.py new file mode 100755 index 0000000..c4c75e0 --- /dev/null +++ b/backend/pixma/scripts/pixma_gen_options.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python + +import sys,os,re + +class Error(Exception): +    pass + + +class ParseError(Error): +    def __init__(self, errline): +        Error.__init__(self, errline) + + +class Struct: +    pass + + +def createCNameMap(): +    t = '' +    for i in range(256): +        if ((ord('A') <= i) and (i <= ord('Z'))) or \ +               ((ord('a') <= i) and (i <= ord('z'))) or \ +               ((ord('0') <= i) and (i <= ord('9'))): +            t += chr(i) +        else: +            t += '_' +    return t + + +def seekBegin(f): +    while True: +        line = f.readline() +        if not line: +            return False +        if line.startswith('BEGIN SANE_Option_Descriptor'): +            return True + + +def parseVerbatim(o, line): +    words = line.split(None, 1) +    if (len(words) < 2) or (words[1][0] != '@'): +        return False +    o[words[0]] = words[1] +    return True + + +def parseLine_type(o, line): +    words = line.split(None, 2) +    otype = words[1] +    o['type'] = 'SANE_TYPE_' + otype.upper() +    if otype == 'group': +        g.ngroups += 1 +        oname = '_group_%d' % g.ngroups +        o['size'] = 0 +    else: +        temp = words[2] +        idx = temp.find('[') +        if idx == -1: +            oname = temp +            o['size'] = 1 +        else: +            oname = temp[0:idx] +            o['size'] = int(temp[idx+1:-1]) +    o['name'] = oname + + +def parseLine_title(o, line): +    o['title'] = line.split(None, 1)[1] + + +def parseLine_desc(o, line): +    o['desc'] = line.split(None, 1)[1] + + +def parseLine_unit(o, line): +    o['unit'] = 'SANE_UNIT_' + line.split(None, 1)[1].upper() + + +def parseLine_default(o, line): +    o['default'] = line.split(None, 1)[1] + + +def parseLine_cap(o, line): +    words = line.split() +    o['cap'] = ['SANE_CAP_' + s.upper() for s in words[1:]] + + +def parseLine_constraint(o, line): +    c = line.split(None,1)[1] +    if c[0] == '{': +        o['constraint'] = c[1:-1].split('|') +    elif c[0] == '(': +        o['constraint'] = tuple(c[1:-1].split(',')) +    else: +        sys.stderr.write('Ignored: %s\n' % line) + + +def parseLine_info(o, line): +    words = line.split() +    o['info'] = ['SANE_INFO_' + s.upper() for s in words[1:]] + +def parseLine_rem(o, line): +    pass + +def normalize(o): +    if 'cname' not in o: +        cname = o['name'].translate(cnameMap) +        o['cname'] = cname +    else: +        cname = o['cname'] +    o['cname_opt'] = 'opt_' + cname +    o['cname_con'] = 'constraint_' + cname +    if 'title' not in o: +        o['title'] = 'NO TITLE' +    if 'desc' not in o: +        o['desc'] = '@sod->title' % o +    if 'unit' not in o: +        o['unit'] = 'SANE_UNIT_NONE' +    if 'constraint_type' not in o: +        if 'constraint' not in o: +            ct = 'SANE_CONSTRAINT_NONE' +        elif isinstance(o['constraint'], list): +            if o['type'] == 'SANE_TYPE_STRING': +                ct = 'SANE_CONSTRAINT_STRING_LIST' +            else: +                ct = 'SANE_CONSTRAINT_WORD_LIST' +        elif isinstance(o['constraint'], tuple): +            ct = 'SANE_CONSTRAINT_RANGE' +        elif isinstance(o['constraint'], str): +            oc = o['constraint'] +            if oc.startswith('@range'): +                ct = 'SANE_CONSTRAINT_RANGE' +            elif oc.startswith('@word_list'): +                ct = 'SANE_CONSTRAINT_WORD_LIST' +            elif oc.startswith('@string_list'): +                ct = 'SANE_CONSTRAINT_STRING_LIST' +        o['constraint_type'] = ct +    return o + + +def parseFile(f): +    if not seekBegin(f): +        return None +    options = [ { +        'name' : '', +        'cname' : 'opt_num_opts', +        'title' : '@SANE_TITLE_NUM_OPTIONS', +        'desc' : '@SANE_DESC_NUM_OPTIONS', +        'type' : 'SANE_TYPE_INT', +        'unit' : 'SANE_UNIT_NONE', +        'size' : 1, +        'cap' : ['SANE_CAP_SOFT_DETECT'], +        'constraint_type' : 'SANE_CONSTRAINT_NONE', +        'default' : '@w = ' + opt_prefix + 'last' +        } ] +    o = {} +    while True: +        line = f.readline() +        if not line: +            break +        line = line.strip() +        if not line: +            continue +        token = line.split(None, 1)[0].lower() +        if token == 'end': +            break +        if token == 'type': +            if 'name' in o: +                options.append(o) +            o = {} +        funcName = 'parseLine_' + token +        if funcName in globals(): +            if not parseVerbatim(o, line): +                func = globals()[funcName] +                func(o, line) +        else: +            sys.stderr.write('Skip: %s\n' % line) +    if 'name' in o: +        options.append(o) +    return [normalize(o) for o in options] + + +def genHeader(options): +    print """ +typedef union { +  SANE_Word w; +  SANE_Int  i; +  SANE_Bool b; +  SANE_Fixed f; +  SANE_String s; +  void *ptr; +} option_value_t; +""" +    print 'typedef enum {' +    for o in options: +        print '  %(cname_opt)s,' % o +    print '  ' + opt_prefix + 'last' +    print '} option_t;' +    print """ + +typedef struct { +  SANE_Option_Descriptor sod; +  option_value_t val,def; +  SANE_Word info; +} option_descriptor_t; + + +struct pixma_sane_t; +static int build_option_descriptors(struct pixma_sane_t *ss); +""" + + +def genMinMaxRange(n, t, r): +    if t == 'SANE_TYPE_FIXED': +        r = ['SANE_FIX(%s)' % x for x in r] +    print 'static const SANE_Range ' + n + ' = ' +    print '  { ' + r[0] + ',' + r[1] + ',' + r[2] + ' };' + + +def genList(n, t, l): +    if t == 'SANE_TYPE_INT': +        etype = 'SANE_Word' +        l = [str(len(l))] + l +    elif t == 'SANE_TYPE_FIXED': +        etype = 'SANE_Word' +        l = [str(len(l))] + ['SANE_FIX(%s)' % x for x in l] +    elif t == 'SANE_TYPE_STRING': +        etype = 'SANE_String_Const' +        l = ['SANE_I18N("%s")' % x for x in l] + ['NULL'] +    print 'static const %s %s[%d] = {' % (etype, n, len(l)) +    for x in l[0:-1]: +        print '\t' + x + ',' +    print '\t' + l[-1] + ' };' + + +def genConstraints(options): +    for o in options: +        if 'constraint' not in o: continue +        c = o['constraint'] +        oname = o['cname_con'] +        otype = o['type'] +        if isinstance(c, tuple): +            genMinMaxRange(oname, otype, c) +        elif isinstance(c, list): +            genList(oname, otype, c) +    print + +def buildCodeVerbatim(o): +    for f in ('name', 'title', 'desc', 'type', 'unit', 'size', 'cap', +              'constraint_type', 'constraint', 'default'): +        if (f not in o): continue +        temp = o[f] +        if (not isinstance(temp,str)) or \ +           (len(temp) < 1) or (temp[0] != '@'): +            continue +        o['code_' + f] = temp[1:] + +def ccode(o): +    buildCodeVerbatim(o) +    if 'code_name' not in o: +        o['code_name'] = '"' + o['name'] + '"' +    for f in ('title', 'desc'): +        cf = 'code_' + f +        if cf in o: continue +        o[cf] = 'SANE_I18N("' + o[f] + '")' + +    for f in ('type', 'unit', 'constraint_type'): +        cf = 'code_' + f +        if cf in o: continue +        o[cf] = o[f] + +    if 'code_size' not in o: +        otype = o['type'] +        osize = o['size'] +        if otype == 'SANE_TYPE_STRING': +            code = str(osize + 1) +        elif otype == 'SANE_TYPE_INT' or otype == 'SANE_TYPE_FIXED': +            code = str(osize) + ' * sizeof(SANE_Word)' +        elif otype == 'SANE_TYPE_BUTTON': +            code = '0' +        else: +            code = 'sizeof(SANE_Word)' +        o['code_size'] = code + +    if ('code_cap' not in o) and ('cap' in o): +        o['code_cap'] = reduce(lambda a,b: a+'|'+b, o['cap']) +    else: +        o['code_cap'] = '0' + +    if ('code_info' not in o) and ('info' in o): +        o['code_info'] = reduce(lambda a,b: a+'|'+b, o['info']) +    else: +        o['code_info'] = '0' + +    if ('code_default' not in o) and ('default' in o): +        odefault = o['default'] +        otype = o['type'] +        if odefault == '_MIN': +            rhs = 'w = sod->constraint.range->min' +        elif odefault == '_MAX': +            rhs = 'w = sod->constraint.range->max' +        elif otype in ('SANE_TYPE_INT', 'SANE_TYPE_BOOL'): +            rhs = 'w = %(default)s' +        elif otype == 'SANE_TYPE_FIXED': +            rhs = 'w = SANE_FIX(%(default)s)' +        elif otype == 'SANE_TYPE_STRING': +            rhs = 's = SANE_I18N("%(default)s")' +        o['code_default'] = rhs % o +    if 'code_default' in o: +        code = '  opt->def.%(code_default)s;\n' +        if o['constraint_type'] != 'SANE_CONSTRAINT_STRING_LIST': +            code += '  opt->val.%(code_default)s;\n' +        else: +            code += '  opt->val.w = find_string_in_list' \ +                    '(opt->def.s, sod->constraint.string_list);\n' +        o['full_code_default'] = code % o +    else: +        o['full_code_default'] = '' + +    if ('code_constraint' not in o) and ('constraint' in o): +        ct = o['constraint_type'] +        idx = len('SANE_CONSTRAINT_') +        ctype = ct[idx:].lower() +        if ctype == 'range': +            rhs = '&%(cname_con)s' % o +        else: +            rhs = '%(cname_con)s' % o +        o['code_constraint'] = ctype + ' = ' + rhs +    if 'code_constraint' in o: +        code = '  sod->constraint.%(code_constraint)s;\n' +        o['full_code_constraint'] = code % o +    else: +        o['full_code_constraint'] = '' + +    return o + +def genBuildOptions(options): +  print """ +static +int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list) +{ +  int i; +  for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {} +  return i; +} + +static +int build_option_descriptors(struct pixma_sane_t *ss) +{ +  SANE_Option_Descriptor *sod; +  option_descriptor_t *opt; + +  memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX));""" + +  for o in options: +      o = ccode(o) +      otype = o['type'] +      code = '\n  opt = &(OPT_IN_CTX[%(cname_opt)s]);\n' \ +             '  sod = &opt->sod;\n' \ +             '  sod->type = %(code_type)s;\n' \ +             '  sod->title = %(code_title)s;\n' \ +             '  sod->desc = %(code_desc)s;\n' +      if otype != 'SANE_TYPE_GROUP': +          code += '  sod->name = %(code_name)s;\n' \ +                  '  sod->unit = %(code_unit)s;\n' \ +                  '  sod->size = %(code_size)s;\n' \ +                  '  sod->cap  = %(code_cap)s;\n' \ +                  '  sod->constraint_type = %(code_constraint_type)s;\n' \ +                  '%(full_code_constraint)s' \ +                  '  OPT_IN_CTX[%(cname_opt)s].info = %(code_info)s;\n' \ +                  '%(full_code_default)s' +      sys.stdout.write(code % o) +  print +  print '  return 0;\n' +  print '}' +  print + +g = Struct() +g.ngroups = 0 +opt_prefix = 'opt_' +con_prefix = 'constraint_' +cnameMap = createCNameMap() +options = parseFile(sys.stdin) +print "/* Automatically generated from pixma_sane.c */" +if (len(sys.argv) == 2) and (sys.argv[1] == 'h'): +    genHeader(options) +else: +    genConstraints(options) +    genBuildOptions(options)  | 
