From ffa8801644a7d53cc1c785e3450f794c07a14eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Sun, 2 Feb 2020 17:13:01 +0100 Subject: New upstream version 1.0.29 --- backend/pixma/pixma.c | 2166 +++++++++++++++++++++++ backend/pixma/pixma.h | 506 ++++++ backend/pixma/pixma_bjnp.c | 2645 ++++++++++++++++++++++++++++ backend/pixma/pixma_bjnp.h | 201 +++ backend/pixma/pixma_bjnp_private.h | 384 ++++ backend/pixma/pixma_common.c | 1187 +++++++++++++ backend/pixma/pixma_common.h | 232 +++ backend/pixma/pixma_imageclass.c | 985 +++++++++++ backend/pixma/pixma_io.h | 186 ++ backend/pixma/pixma_io_sanei.c | 556 ++++++ backend/pixma/pixma_mp150.c | 1817 +++++++++++++++++++ backend/pixma/pixma_mp730.c | 846 +++++++++ backend/pixma/pixma_mp750.c | 972 ++++++++++ backend/pixma/pixma_mp800.c | 2434 +++++++++++++++++++++++++ backend/pixma/pixma_rename.h | 105 ++ backend/pixma/pixma_sane_options.c | 362 ++++ backend/pixma/pixma_sane_options.h | 51 + backend/pixma/scripts/pixma_gen_options.py | 389 ++++ 18 files changed, 16024 insertions(+) create mode 100644 backend/pixma/pixma.c create mode 100644 backend/pixma/pixma.h create mode 100644 backend/pixma/pixma_bjnp.c create mode 100644 backend/pixma/pixma_bjnp.h create mode 100644 backend/pixma/pixma_bjnp_private.h create mode 100644 backend/pixma/pixma_common.c create mode 100644 backend/pixma/pixma_common.h create mode 100644 backend/pixma/pixma_imageclass.c create mode 100644 backend/pixma/pixma_io.h create mode 100644 backend/pixma/pixma_io_sanei.c create mode 100644 backend/pixma/pixma_mp150.c create mode 100644 backend/pixma/pixma_mp730.c create mode 100644 backend/pixma/pixma_mp750.c create mode 100644 backend/pixma/pixma_mp800.c create mode 100644 backend/pixma/pixma_rename.h create mode 100644 backend/pixma/pixma_sane_options.c create mode 100644 backend/pixma/pixma_sane_options.h create mode 100755 backend/pixma/scripts/pixma_gen_options.py (limited to 'backend/pixma') 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 + Copyright (C) 2007-2008 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#ifdef USE_PTHREAD +# include +#endif +#include /* sigaction(POSIX) */ +#include /* POSIX: write read close pipe */ +#ifdef HAVE_FCNTL_H +# include +#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 + Copyright (C) 2007-2008 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 + * + * Note: pixma_cancel() can be called asynchronously to + * interrupt pixma_read_image(). It does not cancel the operation + * immediately. pixma_read_image() must 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 /* available in ISO C99 */ +#else +# include +typedef uint8_t uint8_t; +typedef uint16_t uint16_t; +typedef uint32_t uint32_t; +#endif /* HAVE_STDINT_H */ + +#ifdef HAVE_INTTYPES_H +# include /* 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
+ * 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
+ * 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 +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include +#include +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +/* + * networking stuff + */ +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_IFADDRS_H +#include +#endif +#ifdef HAVE_SYS_SELECT_H +#include +#endif +#ifdef HAVE_PWD_H +#include +#endif +#include +#ifdef HAVE_FCNTL_H +#include +#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 /* 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 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 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 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 + Copyright (C) 2007-2008 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#include +#include /* pow(C90) */ + +#include /* gettimeofday(4.3BSD) */ +#include /* 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(jout_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 + Copyright (C) 2006-2007 Wittawat Yamwong + + 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_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 + Copyright (C) 2007-2009 Nicolas Martin, + 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 +#include +#include + +#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 + + 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 + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#include /* 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 + Copyright (C) 2007-2009 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#include /* 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 \ +"\ +\ +StartJob\ +00000001\ +1" + +#define XML_START_2 \ +"\ +\ +VendorCmd\ +00000001\ +ModeShift1\ +" + +#define XML_END \ +"\ +\ +EndJob\ +00000001\ +" + +#define XML_OK "OK" + +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 + Copyright (C) 2007-2008 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#include /* 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 + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include + +#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 + Copyright (C) 2007-2009 Nicolas Martin, + Copyright (C) 2006-2007 Wittawat Yamwong + + 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 +#include +#include +#include /* 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 \ +"\ +\ +StartJob\ +00000001\ +1" + +#define XML_START_2 \ +"\ +\ +VendorCmd\ +00000001\ +ModeShift1\ +" + +#define XML_END \ +"\ +\ +EndJob\ +00000001\ +" + +#define XML_OK "OK" + +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 + + 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) -- cgit v1.2.3