summaryrefslogtreecommitdiff
path: root/backend/pixma
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2020-02-02 17:13:01 +0100
committerJörg Frings-Fürst <debian@jff-webhosting.net>2020-02-02 17:13:01 +0100
commitffa8801644a7d53cc1c785e3450f794c07a14eb0 (patch)
tree8d72a18a9a08b9151d12badcb1c78ce06a059f62 /backend/pixma
parent1687222e1b9e74c89cafbb5910e72d8ec7bfd40f (diff)
New upstream version 1.0.29upstream/1.0.29
Diffstat (limited to 'backend/pixma')
-rw-r--r--backend/pixma/pixma.c2166
-rw-r--r--backend/pixma/pixma.h506
-rw-r--r--backend/pixma/pixma_bjnp.c2645
-rw-r--r--backend/pixma/pixma_bjnp.h201
-rw-r--r--backend/pixma/pixma_bjnp_private.h384
-rw-r--r--backend/pixma/pixma_common.c1187
-rw-r--r--backend/pixma/pixma_common.h232
-rw-r--r--backend/pixma/pixma_imageclass.c985
-rw-r--r--backend/pixma/pixma_io.h186
-rw-r--r--backend/pixma/pixma_io_sanei.c556
-rw-r--r--backend/pixma/pixma_mp150.c1817
-rw-r--r--backend/pixma/pixma_mp730.c846
-rw-r--r--backend/pixma/pixma_mp750.c972
-rw-r--r--backend/pixma/pixma_mp800.c2434
-rw-r--r--backend/pixma/pixma_rename.h105
-rw-r--r--backend/pixma/pixma_sane_options.c362
-rw-r--r--backend/pixma/pixma_sane_options.h51
-rwxr-xr-xbackend/pixma/scripts/pixma_gen_options.py389
18 files changed, 16024 insertions, 0 deletions
diff --git a/backend/pixma/pixma.c b/backend/pixma/pixma.c
new file mode 100644
index 0000000..f763496
--- /dev/null
+++ b/backend/pixma/pixma.c
@@ -0,0 +1,2166 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#include "../include/sane/config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef USE_PTHREAD
+# include <pthread.h>
+#endif
+#include <signal.h> /* sigaction(POSIX) */
+#include <unistd.h> /* POSIX: write read close pipe */
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+
+#include "pixma_rename.h"
+#include "pixma.h"
+
+# define DEBUG_NOT_STATIC
+# include "../include/sane/sane.h"
+# include "../include/sane/sanei.h"
+# include "../include/sane/saneopts.h"
+# include "../include/sane/sanei_thread.h"
+# include "../include/sane/sanei_backend.h"
+# include "../include/sane/sanei_config.h"
+# include "../include/sane/sanei_jpeg.h"
+
+#ifdef NDEBUG
+# define PDBG(x)
+#else
+# define PDBG(x) IF_DBG(x)
+#endif /* NDEBUG */
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+#define DECL_CTX pixma_sane_t *ss = check_handle(h)
+#define OPT_IN_CTX ss->opt
+#define SOD(opt) OPT_IN_CTX[opt].sod
+#define OVAL(opt) OPT_IN_CTX[opt].val
+#define AUTO_GAMMA 2.2
+
+/* pixma_sane_options.h generated by
+ * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h
+ */
+#include "pixma_sane_options.h"
+
+#define BUTTON_GROUP_SIZE ( opt_scan_resolution - opt_button_1 + 1 )
+#define BUTTON_GROUP_INDEX(x) ( x - opt_button_1 )
+
+typedef struct pixma_sane_t
+{
+ struct pixma_sane_t *next;
+ pixma_t *s;
+ pixma_scan_param_t sp;
+ SANE_Bool cancel;
+
+ /* valid states: idle, !idle && scanning, !idle && !scanning */
+ SANE_Bool idle;
+ SANE_Bool scanning;
+ SANE_Status last_read_status; /* valid if !idle && !scanning */
+
+ option_descriptor_t opt[opt_last];
+ char button_option_is_cached[BUTTON_GROUP_SIZE];
+ SANE_Range xrange, yrange;
+ SANE_Word dpi_list[9]; /* up to 9600 dpi */
+ SANE_String_Const mode_list[6];
+ pixma_scan_mode_t mode_map[6];
+ uint8_t gamma_table[4096];
+ SANE_String_Const source_list[4];
+ pixma_paper_source_t source_map[4];
+
+ unsigned byte_pos_in_line, output_line_size;
+ uint64_t image_bytes_read;
+ unsigned page_count; /* valid for ADF */
+
+ SANE_Pid reader_taskid;
+ int wpipe, rpipe;
+ SANE_Bool reader_stop;
+
+ /* Valid for JPEG source */
+ djpeg_dest_ptr jdst;
+ struct jpeg_decompress_struct jpeg_cinfo;
+ struct jpeg_error_mgr jpeg_err;
+ SANE_Bool jpeg_header_seen;
+} pixma_sane_t;
+
+typedef struct
+{
+ struct jpeg_source_mgr jpeg;
+
+ pixma_sane_t *s;
+ JOCTET *buffer;
+
+ SANE_Byte *linebuffer;
+ SANE_Int linebuffer_size;
+ SANE_Int linebuffer_index;
+} pixma_jpeg_src_mgr;
+
+
+static const char vendor_str[] = "CANON";
+static const char type_str[] = "multi-function peripheral";
+
+static pixma_sane_t *first_scanner = NULL;
+static const SANE_Device **dev_list = NULL;
+static const char* conf_devices[MAX_CONF_DEVICES];
+
+static void mark_all_button_options_cached ( struct pixma_sane_t * ss )
+{
+ int i;
+ for (i = 0; i < (opt__group_5 - opt_button_1); i++ )
+ ss -> button_option_is_cached[i] = 1;
+}
+
+static SANE_Status config_attach_pixma(SANEI_Config * config, const char *devname)
+{
+ int i;
+ UNUSED(config);
+ for (i=0; i < (MAX_CONF_DEVICES -1); i++)
+ {
+ if(conf_devices[i] == NULL)
+ {
+ conf_devices[i] = strdup(devname);
+ return SANE_STATUS_GOOD;
+ }
+ }
+ return SANE_STATUS_INVAL;
+}
+
+static SANE_Status
+map_error (int error)
+{
+ if (error >= 0)
+ return SANE_STATUS_GOOD;
+
+ switch (error)
+ {
+ case PIXMA_ENOMEM:
+ return SANE_STATUS_NO_MEM;
+ case PIXMA_ECANCELED:
+ return SANE_STATUS_CANCELLED;
+ case PIXMA_EBUSY:
+ return SANE_STATUS_DEVICE_BUSY;
+ case PIXMA_EINVAL:
+ return SANE_STATUS_INVAL;
+ case PIXMA_EACCES:
+ return SANE_STATUS_ACCESS_DENIED;
+ case PIXMA_EPAPER_JAMMED:
+ return SANE_STATUS_JAMMED;
+ case PIXMA_ENO_PAPER:
+ return SANE_STATUS_NO_DOCS;
+ case PIXMA_ECOVER_OPEN:
+ return SANE_STATUS_COVER_OPEN;
+ case PIXMA_ENOTSUP:
+ return SANE_STATUS_UNSUPPORTED;
+ case PIXMA_EPROTO:
+ case PIXMA_ENODEV:
+ case PIXMA_EIO:
+ case PIXMA_ETIMEDOUT:
+ return SANE_STATUS_IO_ERROR;
+ }
+ PDBG (pixma_dbg (1, "BUG: unmapped error %d\n", error));
+ return SANE_STATUS_IO_ERROR;
+}
+
+static int
+getenv_atoi (const char *name, int def)
+{
+ const char *str = getenv (name);
+ return (str) ? atoi (str) : def;
+}
+
+#define CONST_CAST(t,x) (t)(x)
+
+static void
+free_block (const void * ptr)
+{
+ free (CONST_CAST (void *, ptr));
+}
+
+static void
+cleanup_device_list (void)
+{
+ if (dev_list)
+ {
+ int i;
+ for (i = 0; dev_list[i]; i++)
+ {
+ free_block ((const void *) dev_list[i]->name);
+ free_block ((const void *) dev_list[i]->model);
+ free_block ((const void *) dev_list[i]);
+ }
+ }
+ free (dev_list);
+ dev_list = NULL;
+}
+
+static void
+find_scanners (SANE_Bool local_only)
+{
+ unsigned i, nscanners;
+
+ cleanup_device_list ();
+ nscanners = pixma_find_scanners (conf_devices, local_only);
+ PDBG (pixma_dbg (3, "pixma_find_scanners() found %u devices\n", nscanners));
+ dev_list =
+ (const SANE_Device **) calloc (nscanners + 1, sizeof (*dev_list));
+ if (!dev_list)
+ return;
+ for (i = 0; i != nscanners; i++)
+ {
+ SANE_Device *sdev = (SANE_Device *) calloc (1, sizeof (*sdev));
+ char *name, *model;
+ if (!sdev)
+ goto nomem;
+ name = strdup (pixma_get_device_id (i));
+ model = strdup (pixma_get_device_model (i));
+ if (!name || !model)
+ {
+ free (name);
+ free (model);
+ free (sdev);
+ goto nomem;
+ }
+ sdev->name = name;
+ sdev->model = model;
+ sdev->vendor = vendor_str;
+ sdev->type = type_str;
+ dev_list[i] = sdev;
+ }
+ /* dev_list is already NULL terminated by calloc(). */
+ return;
+
+nomem:
+ PDBG (pixma_dbg (1, "WARNING:not enough memory for device list\n"));
+ return;
+}
+
+static pixma_sane_t *
+check_handle (SANE_Handle h)
+{
+ pixma_sane_t *p;
+
+ for (p = first_scanner; p && (SANE_Handle) p != h; p = p->next)
+ {
+ }
+ return p;
+}
+
+static void
+update_button_state (pixma_sane_t * ss, SANE_Int * info)
+{
+ SANE_Int b1 = OVAL (opt_button_1).w;
+ SANE_Int b2 = OVAL (opt_button_2).w;
+ uint32_t ev = pixma_wait_event (ss->s, 300);
+ switch (ev & ~PIXMA_EV_ACTION_MASK)
+ {
+ case PIXMA_EV_BUTTON1:
+ b1 = 1;
+ break;
+ case PIXMA_EV_BUTTON2:
+ b2 = 1;
+ break;
+ }
+
+ if (b1 != OVAL (opt_button_1).w || b2 != OVAL (opt_button_2).w)
+ {
+ *info |= SANE_INFO_RELOAD_OPTIONS;
+ OVAL (opt_button_1).w = b1;
+ OVAL (opt_button_2).w = b2;
+ OVAL (opt_original).w = GET_EV_ORIGINAL(ev);
+ OVAL (opt_target).w = GET_EV_TARGET(ev);
+ OVAL (opt_scan_resolution).w = GET_EV_DPI(ev);
+ }
+ mark_all_button_options_cached(ss);
+}
+
+static SANE_Bool
+enable_option (pixma_sane_t * ss, SANE_Int o, SANE_Bool enable)
+{
+ SANE_Word save = SOD (o).cap;
+ if (enable)
+ SOD (o).cap &= ~SANE_CAP_INACTIVE;
+ else
+ SOD (o).cap |= SANE_CAP_INACTIVE;
+ return (save != SOD (o).cap);
+}
+
+static void
+clamp_value (pixma_sane_t * ss, SANE_Int n, void *v, SANE_Int * info)
+{
+ SANE_Option_Descriptor *sod = &SOD (n);
+ SANE_Word *va = (SANE_Word *) v;
+ const SANE_Range *range = sod->constraint.range;
+ int i, nmemb;
+
+ nmemb = sod->size / sizeof (SANE_Word);
+ for (i = 0; i < nmemb; i++)
+ {
+ SANE_Word value = va[i];
+ if (value < range->min)
+ {
+ value = range->min;
+ }
+ else if (value > range->max)
+ {
+ value = range->max;
+ }
+ if (range->quant != 0)
+ {
+ value = (value - range->min + range->quant / 2) /
+ range->quant * range->quant;
+ }
+ if (value != va[i])
+ {
+ va[i] = value;
+ *info |= SANE_INFO_INEXACT;
+ }
+ }
+}
+
+/* create dynamic mode_list
+ * ss: scanner device
+ * tpu = 0: flatbed or ADF mode
+ * 1 bit lineart, 8 bit grayscale and 24 bit color scans
+ * tpu = 1: TPU mode
+ * 16 bit grayscale and 48 bit color scans */
+static void
+create_mode_list (pixma_sane_t * ss)
+{
+ SANE_Bool tpu;
+ const pixma_config_t *cfg;
+ int i;
+
+ cfg = pixma_get_config (ss->s);
+ tpu = (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU);
+
+ /* setup available mode */
+ i = 0;
+ ss->mode_list[i] = SANE_VALUE_SCAN_MODE_COLOR;
+ ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR;
+ i++;
+ if (cfg->cap & PIXMA_CAP_GRAY)
+ {
+ ss->mode_list[i] = SANE_VALUE_SCAN_MODE_GRAY;
+ ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY;
+ i++;
+ }
+ if (tpu && (cfg->cap & PIXMA_CAP_NEGATIVE))
+ {
+ ss->mode_list[i] = SANE_I18N ("Negative color");
+ ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_COLOR;
+ i++;
+ if (cfg->cap & PIXMA_CAP_GRAY)
+ {
+ ss->mode_list[i] = SANE_I18N ("Negative gray");
+ ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_GRAY;
+ i++;
+ }
+ }
+ if (tpu && (cfg->cap & PIXMA_CAP_TPUIR) == PIXMA_CAP_TPUIR)
+ {
+ ss->mode_list[i] = SANE_I18N ("Infrared");
+ ss->mode_map[i] = PIXMA_SCAN_MODE_TPUIR;
+ i++;
+ }
+ if (!tpu && (cfg->cap & PIXMA_CAP_48BIT))
+ {
+ ss->mode_list[i] = SANE_I18N ("48 bits color");
+ ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR_48;
+ i++;
+ if (cfg->cap & PIXMA_CAP_GRAY)
+ {
+ ss->mode_list[i] = SANE_I18N ("16 bits gray");
+ ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY_16;
+ i++;
+ }
+ }
+ if (!tpu && (cfg->cap & PIXMA_CAP_LINEART))
+ {
+ ss->mode_list[i] = SANE_VALUE_SCAN_MODE_LINEART;
+ ss->mode_map[i] = PIXMA_SCAN_MODE_LINEART;
+ i++;
+ }
+ /* terminate mode_list and mode_map */
+ ss->mode_list[i] = 0;
+ ss->mode_map[i] = 0;
+}
+
+/* create dynamic dpi_list
+ * ss: scanner device */
+static void
+create_dpi_list (pixma_sane_t * ss)
+{
+ const pixma_config_t *cfg;
+ int i, j;
+ int min;
+ unsigned min_dpi;
+ unsigned max_dpi;
+
+ cfg = pixma_get_config (ss->s);
+
+ /* get min/max dpi */
+ max_dpi = cfg->xdpi;
+ min_dpi = 75;
+ if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU
+ && ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_TPUIR)
+ { /* IR mode */
+ /*PDBG (pixma_dbg (4, "*create_dpi_list***** TPUIR mode\n"));*/
+ min_dpi = (cfg->tpuir_min_dpi) ? cfg->tpuir_min_dpi : 75;
+ max_dpi = (cfg->tpuir_max_dpi) ? cfg->tpuir_max_dpi : cfg->xdpi;
+ }
+ else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU
+ || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADF
+ || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADFDUP)
+ { /* ADF / TPU mode */
+ /*PDBG (pixma_dbg (4, "*create_dpi_list***** ADF/TPU mode\n"));*/
+ min_dpi = (cfg->adftpu_min_dpi) ? cfg->adftpu_min_dpi : 75;
+ max_dpi = (cfg->adftpu_max_dpi) ? cfg->adftpu_max_dpi : cfg->xdpi;
+ }
+ else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED
+ && (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_COLOR_48
+ || ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_GRAY_16))
+ { /* 48 bits flatbed */
+ /*PDBG (pixma_dbg (4, "*create_dpi_list***** 48 bits flatbed mode\n"));*/
+ min_dpi = 150;
+ }
+
+ /* set j for min. dpi
+ * 75 dpi: j = 0
+ * 150 dpi: j = 1 \
+ * 300 dpi: j = 2 |--> from cfg->adftpu_min_dpi or cfg->tpuir_min_dpi
+ * 600 dpi: j = 3 /
+ * */
+ j = -1;
+ min = min_dpi / 75;
+ do
+ {
+ j++;
+ min >>= 1;
+ }
+ while (min > 0);
+
+ /* create dpi_list
+ * use j for min. dpi */
+ i = 0;
+ do
+ {
+ i++; j++;
+ ss->dpi_list[i] = 75 * (1 << (j - 1)); /* 75 x 2^(j-1) */
+ }
+ while ((unsigned) ss->dpi_list[i] < max_dpi);
+ ss->dpi_list[0] = i;
+ /*PDBG (pixma_dbg (4, "*create_dpi_list***** min_dpi = %d, max_dpi = %d\n", min_dpi, max_dpi));*/
+}
+
+static void
+select_value_from_list (pixma_sane_t * ss, SANE_Int n, void *v,
+ SANE_Int * info)
+{
+ SANE_Option_Descriptor *sod = &SOD (n);
+ SANE_Word *va = (SANE_Word *) v;
+ const SANE_Word *list = sod->constraint.word_list;
+ int i, j, nmemb;
+
+ nmemb = sod->size / sizeof (SANE_Word);
+ for (i = 0; i < nmemb; i++)
+ {
+ SANE_Word value = va[i];
+ SANE_Word mindelta = abs (value - list[1]);
+ SANE_Word nearest = list[1];
+ for (j = 2; j <= list[0]; j++)
+ {
+ SANE_Word delta = abs (value - list[j]);
+ if (delta < mindelta)
+ {
+ mindelta = delta;
+ nearest = list[j];
+ }
+ if (mindelta == 0)
+ break;
+ }
+ if (va[i] != nearest)
+ {
+ va[i] = nearest;
+ *info |= SANE_INFO_INEXACT;
+ }
+ }
+}
+
+static SANE_Status
+control_scalar_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v,
+ SANE_Int * info)
+{
+ option_descriptor_t *opt = &(OPT_IN_CTX[n]);
+ SANE_Word val;
+
+ switch (a)
+ {
+ case SANE_ACTION_GET_VALUE:
+ switch (opt->sod.type)
+ {
+ case SANE_TYPE_BOOL:
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ *(SANE_Word *) v = opt->val.w;
+ break;
+ default:
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ return SANE_STATUS_GOOD;
+
+ case SANE_ACTION_SET_VALUE:
+ switch (opt->sod.type)
+ {
+ case SANE_TYPE_BOOL:
+ val = *(SANE_Word *) v;
+ if (val != SANE_TRUE && val != SANE_FALSE)
+ return SANE_STATUS_INVAL;
+ opt->val.w = val;
+ break;
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ if (opt->sod.constraint_type == SANE_CONSTRAINT_RANGE)
+ clamp_value (ss, n, v, info);
+ else if (opt->sod.constraint_type == SANE_CONSTRAINT_WORD_LIST)
+ select_value_from_list (ss, n, v, info);
+ opt->val.w = *(SANE_Word *) v;
+ break;
+ default:
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ *info |= opt->info;
+ return SANE_STATUS_GOOD;
+
+ case SANE_ACTION_SET_AUTO:
+ switch (opt->sod.type)
+ {
+ case SANE_TYPE_BOOL:
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ opt->val.w = opt->def.w;
+ break;
+ default:
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ *info |= opt->info;
+ return SANE_STATUS_GOOD;
+ }
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+static SANE_Status
+control_string_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v,
+ SANE_Int * info)
+{
+ option_descriptor_t *opt = &(OPT_IN_CTX[n]);
+ const SANE_String_Const *slist = opt->sod.constraint.string_list;
+ SANE_String str = (SANE_String) v;
+
+ if (opt->sod.constraint_type == SANE_CONSTRAINT_NONE)
+ {
+ switch (a)
+ {
+ case SANE_ACTION_GET_VALUE:
+ strcpy (str, opt->val.s);
+ break;
+ case SANE_ACTION_SET_AUTO:
+ str = opt->def.s;
+ /* fall through */
+ case SANE_ACTION_SET_VALUE:
+ strncpy (opt->val.s, str, opt->sod.size - 1);
+ *info |= opt->info;
+ break;
+ }
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ int i;
+
+ switch (a)
+ {
+ case SANE_ACTION_GET_VALUE:
+ strcpy (str, slist[opt->val.w]);
+ break;
+ case SANE_ACTION_SET_AUTO:
+ str = opt->def.ptr;
+ /* fall through */
+ case SANE_ACTION_SET_VALUE:
+ i = 0;
+ while (slist[i] && strcasecmp (str, slist[i]) != 0)
+ i++;
+ if (!slist[i])
+ return SANE_STATUS_INVAL;
+ if (strcmp (slist[i], str) != 0)
+ {
+ strcpy (str, slist[i]);
+ *info |= SANE_INFO_INEXACT;
+ }
+ opt->val.w = i;
+ *info |= opt->info;
+ break;
+ }
+ return SANE_STATUS_GOOD;
+ }
+}
+
+static SANE_Status
+control_option (pixma_sane_t * ss, SANE_Int n,
+ SANE_Action a, void *v, SANE_Int * info)
+{
+ int result, i;
+ const pixma_config_t *cfg;
+ SANE_Int dummy;
+
+ /* info may be null, better to set a dummy here then test everywhere */
+ if (info == NULL)
+ info = &dummy;
+
+ cfg = pixma_get_config (ss->s);
+
+ /* PDBG (pixma_dbg (4, "*control_option***** n = %u, a = %u\n", n, a)); */
+
+ /* first deal with options that require special treatment */
+ result = SANE_STATUS_UNSUPPORTED;
+ switch (n)
+ {
+ case opt_gamma_table:
+ switch (a)
+ {
+ case SANE_ACTION_SET_VALUE:
+ clamp_value (ss, n, v, info);
+ for (i = 0; i != 4096; i++)
+ ss->gamma_table[i] = *((SANE_Int *) v + i);
+ break;
+ case SANE_ACTION_GET_VALUE:
+ for (i = 0; i != 4096; i++)
+ *((SANE_Int *) v + i) = ss->gamma_table[i];
+ break;
+ case SANE_ACTION_SET_AUTO:
+ pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table,
+ sizeof (ss->gamma_table));
+ break;
+ default:
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ return SANE_STATUS_GOOD;
+
+ case opt_button_update:
+ if (a == SANE_ACTION_SET_VALUE)
+ {
+ update_button_state (ss, info);
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ return SANE_STATUS_INVAL;
+ }
+ break;
+ case opt_button_1:
+ case opt_button_2:
+ case opt_original:
+ case opt_target:
+ case opt_scan_resolution:
+ /* poll scanner if option is not cached */
+ if (! ss->button_option_is_cached[ BUTTON_GROUP_INDEX(n) ] )
+ update_button_state (ss, info);
+ /* mark this option as read */
+ ss->button_option_is_cached[ BUTTON_GROUP_INDEX(n) ] = 0;
+ }
+
+ /* now deal with getting and setting of options */
+ switch (SOD (n).type)
+ {
+ case SANE_TYPE_BOOL:
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ result = control_scalar_option (ss, n, a, v, info);
+ break;
+ case SANE_TYPE_STRING:
+ result = control_string_option (ss, n, a, v, info);
+ break;
+ case SANE_TYPE_BUTTON:
+ case SANE_TYPE_GROUP:
+ PDBG (pixma_dbg (1, "BUG:control_option():Unhandled option\n"));
+ result = SANE_STATUS_INVAL;
+ break;
+ }
+ if (result != SANE_STATUS_GOOD)
+ return result;
+
+ /* deal with dependencies between options */
+ switch (n)
+ {
+ case opt_custom_gamma:
+ if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)
+ {
+ if (enable_option (ss, opt_gamma_table, OVAL (opt_custom_gamma).b))
+ *info |= SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+ case opt_gamma:
+ if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)
+ {
+ /* PDBG (pixma_dbg (4, "*control_option***** gamma = %f *\n",
+ SANE_UNFIX (OVAL (opt_gamma).w))); */
+ pixma_fill_gamma_table (SANE_UNFIX (OVAL (opt_gamma).w),
+ ss->gamma_table, sizeof (ss->gamma_table));
+ }
+ break;
+ case opt_mode:
+ if (cfg->cap & (PIXMA_CAP_48BIT|PIXMA_CAP_LINEART|PIXMA_CAP_TPUIR)
+ && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO))
+ { /* new mode selected: Color, Gray, ... */
+ /* PDBG (pixma_dbg (4, "*control_option***** mode = %u *\n",
+ ss->mode_map[OVAL (opt_mode).w])); */
+ /* recreate dynamic lists */
+ create_dpi_list (ss);
+ if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART)
+ { /* lineart */
+ enable_option (ss, opt_threshold, SANE_TRUE);
+ enable_option (ss, opt_threshold_curve, SANE_TRUE);
+ }
+ else
+ { /* all other modes */
+ enable_option (ss, opt_threshold, SANE_FALSE);
+ enable_option (ss, opt_threshold_curve, SANE_FALSE);
+ }
+ *info |= SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+ case opt_source:
+ if ((cfg->cap & (PIXMA_CAP_ADF|PIXMA_CAP_ADFDUP|PIXMA_CAP_TPU))
+ && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO))
+ { /* new source selected: flatbed, ADF, TPU, ... */
+ /* to avoid fatal errors,
+ * select first entry of dynamic mode_list
+ * identifiers are unknown here */
+ OVAL (opt_mode).w = ss->mode_map[0];
+ /* recreate dynamic lists */
+ create_mode_list (ss);
+ create_dpi_list (ss);
+ /* to avoid fatal errors,
+ * select first entry of dynamic dpi_list
+ * identifiers are unknown here */
+ OVAL (opt_resolution).w = ss->dpi_list[1];
+ if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART)
+ { /* lineart */
+ enable_option (ss, opt_threshold, SANE_TRUE);
+ enable_option (ss, opt_threshold_curve, SANE_TRUE);
+ }
+ else
+ { /* all other modes */
+ enable_option (ss, opt_threshold, SANE_FALSE);
+ enable_option (ss, opt_threshold_curve, SANE_FALSE);
+ }
+ if (cfg->cap & (PIXMA_CAP_ADF_WAIT))
+ { /* adf-wait */
+ enable_option (ss, opt_adf_wait, SANE_TRUE);
+ }
+ else
+ { /* disable adf-wait */
+ enable_option (ss, opt_adf_wait, SANE_FALSE);
+ }
+ *info |= SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+ }
+
+ return result;
+}
+
+#ifndef NDEBUG
+static void
+print_scan_param (int level, const pixma_scan_param_t * sp)
+{
+ pixma_dbg (level, "Scan parameters\n");
+ pixma_dbg (level, " line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n",
+ sp->line_size, sp->image_size, sp->channels, sp->depth);
+ pixma_dbg (level, " dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n",
+ sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h);
+ pixma_dbg (level, " gamma_table=%p source=%d\n", sp->gamma_table,
+ sp->source);
+ pixma_dbg (level, " adf-wait=%d\n", sp->adf_wait);
+}
+#endif
+
+static int
+calc_scan_param (pixma_sane_t * ss, pixma_scan_param_t * sp)
+{
+ int x1, y1, x2, y2;
+ int error;
+
+ memset (sp, 0, sizeof (*sp));
+
+ sp->channels = (OVAL (opt_mode).w == 0) ? 3 : 1;
+ sp->depth = (OVAL (opt_mode).w == 2) ? 1 : 8;
+ sp->xdpi = sp->ydpi = OVAL (opt_resolution).w;
+
+#define PIXEL(x,dpi) (int)((SANE_UNFIX(x) / 25.4 * (dpi)) + 0.5)
+ x1 = PIXEL (OVAL (opt_tl_x).w, sp->xdpi);
+ x2 = PIXEL (OVAL (opt_br_x).w, sp->xdpi);
+ if (x2 < x1)
+ {
+ int temp = x1;
+ x1 = x2;
+ x2 = temp;
+ }
+ y1 = PIXEL (OVAL (opt_tl_y).w, sp->ydpi);
+ y2 = PIXEL (OVAL (opt_br_y).w, sp->ydpi);
+ if (y2 < y1)
+ {
+ int temp = y1;
+ y1 = y2;
+ y2 = temp;
+ }
+#undef PIXEL
+ sp->x = x1;
+ sp->y = y1;
+ sp->w = x2 - x1;
+ sp->h = y2 - y1;
+ if (sp->w == 0)
+ sp->w = 1;
+ if (sp->h == 0)
+ sp->h = 1;
+ sp->tpu_offset_added = 0;
+
+ sp->gamma_table = (OVAL (opt_custom_gamma).b) ? ss->gamma_table : NULL;
+ sp->source = ss->source_map[OVAL (opt_source).w];
+ sp->mode = ss->mode_map[OVAL (opt_mode).w];
+ sp->adf_pageid = ss->page_count;
+ sp->threshold = 2.55 * OVAL (opt_threshold).w;
+ sp->threshold_curve = OVAL (opt_threshold_curve).w;
+ sp->adf_wait = OVAL (opt_adf_wait).w;
+
+ error = pixma_check_scan_param (ss->s, sp);
+ if (error < 0)
+ {
+ PDBG (pixma_dbg (1, "BUG:calc_scan_param() failed %d\n", error));
+ PDBG (print_scan_param (1, sp));
+ }
+ return error;
+}
+
+static void
+init_option_descriptors (pixma_sane_t * ss)
+{
+ const pixma_config_t *cfg;
+ int i;
+
+ cfg = pixma_get_config (ss->s);
+
+ /* setup range for the scan area. */
+ ss->xrange.min = SANE_FIX (0);
+ ss->xrange.max = SANE_FIX (cfg->width / 75.0 * 25.4);
+ ss->xrange.quant = SANE_FIX (0);
+
+ ss->yrange.min = SANE_FIX (0);
+ ss->yrange.max = SANE_FIX (cfg->height / 75.0 * 25.4);
+ ss->yrange.quant = SANE_FIX (0);
+
+ /* mode_list and source_list were already NULL-terminated,
+ * because the whole pixma_sane_t was cleared during allocation. */
+
+ /* setup available mode. */
+ create_mode_list (ss);
+
+ /* setup dpi up to the value supported by the scanner. */
+ create_dpi_list (ss);
+
+ /* setup paper source */
+ i = 0;
+ ss->source_list[i] = SANE_I18N ("Flatbed");
+ ss->source_map[i] = PIXMA_SOURCE_FLATBED;
+ i++;
+ if (cfg->cap & PIXMA_CAP_ADF)
+ {
+ ss->source_list[i] = SANE_I18N ("Automatic Document Feeder");
+ ss->source_map[i] = PIXMA_SOURCE_ADF;
+ i++;
+ }
+ if ((cfg->cap & PIXMA_CAP_ADFDUP) == PIXMA_CAP_ADFDUP)
+ {
+ ss->source_list[i] = SANE_I18N ("ADF Duplex");
+ ss->source_map[i] = PIXMA_SOURCE_ADFDUP;
+ i++;
+ }
+ if (cfg->cap & PIXMA_CAP_TPU)
+ {
+ ss->source_list[i] = SANE_I18N ("Transparency Unit");
+ ss->source_map[i] = PIXMA_SOURCE_TPU;
+ i++;
+ }
+
+ build_option_descriptors (ss);
+
+ /* Enable options that are available only in some scanners. */
+ if (cfg->cap & PIXMA_CAP_GAMMA_TABLE)
+ {
+ enable_option (ss, opt_gamma, SANE_TRUE);
+ enable_option (ss, opt_custom_gamma, SANE_TRUE);
+ sane_control_option (ss, opt_custom_gamma, SANE_ACTION_SET_AUTO,
+ NULL, NULL);
+ pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table, 4096);
+ }
+ enable_option (ss, opt_button_controlled,
+ ((cfg->cap & PIXMA_CAP_EVENTS) != 0));
+}
+
+/* Writing to reader_ss outside reader_process() is a BUG! */
+static pixma_sane_t *reader_ss = NULL;
+
+static void
+reader_signal_handler (int sig)
+{
+ if (reader_ss)
+ {
+ reader_ss->reader_stop = SANE_TRUE;
+ /* reader process is ended by SIGTERM, so no cancel in this case */
+ if (sig != SIGTERM)
+ pixma_cancel (reader_ss->s);
+ }
+}
+
+static int
+write_all (pixma_sane_t * ss, void *buf_, size_t size)
+{
+ uint8_t *buf = (uint8_t *) buf_;
+ int count;
+
+ while (size != 0 && !ss->reader_stop)
+ {
+ count = write (ss->wpipe, buf, size);
+ if (count == -1 && errno != EINTR)
+ break;
+ if (count == -1 && errno == EINTR)
+ continue;
+ buf += count;
+ size -= count;
+ }
+ return buf - (uint8_t *) buf_;
+}
+
+/* NOTE: reader_loop() runs either in a separate thread or process. */
+static SANE_Status
+reader_loop (pixma_sane_t * ss)
+{
+ void *buf;
+ unsigned bufsize;
+ int count = 0;
+
+ PDBG (pixma_dbg (3, "Reader task started\n"));
+ /*bufsize = ss->sp.line_size + 1;*/ /* XXX: "odd" bufsize for testing pixma_read_image() */
+ bufsize = ss->sp.line_size; /* bufsize EVEN needed by Xsane for 48 bits depth */
+ buf = malloc (bufsize);
+ if (!buf)
+ {
+ count = PIXMA_ENOMEM;
+ goto done;
+ }
+
+ count = pixma_activate_connection (ss->s);
+ if (count < 0)
+ goto done;
+
+ pixma_enable_background (ss->s, 1);
+ if (OVAL (opt_button_controlled).b && ss->page_count == 0)
+ {
+ int start = 0;
+#ifndef NDEBUG
+ pixma_dbg (1, "==== Button-controlled scan mode is enabled.\n");
+ pixma_dbg (1, "==== To proceed, press 'SCAN' or 'COLOR' button. "
+ "To cancel, press 'GRAY' or 'END' button.\n");
+#endif
+ while (pixma_wait_event (ss->s, 10) != 0)
+ {
+ }
+ while (!start)
+ {
+ uint32_t events;
+ if (ss->reader_stop)
+ {
+ count = PIXMA_ECANCELED;
+ goto done;
+ }
+ events = pixma_wait_event (ss->s, 1000);
+ switch (events & ~PIXMA_EV_ACTION_MASK)
+ {
+ case PIXMA_EV_BUTTON1:
+ start = 1;
+ break;
+ case PIXMA_EV_BUTTON2:
+ count = PIXMA_ECANCELED;
+ goto done;
+ }
+ }
+ }
+ count = pixma_scan (ss->s, &ss->sp);
+ if (count >= 0)
+ {
+ while ((count = pixma_read_image (ss->s, buf, bufsize)) > 0)
+ {
+ if (write_all (ss, buf, count) != count)
+ pixma_cancel (ss->s);
+ }
+ }
+
+done:
+ pixma_enable_background (ss->s, 0);
+ pixma_deactivate_connection (ss->s);
+ free (buf);
+ close (ss->wpipe);
+ ss->wpipe = -1;
+ if (count >= 0)
+ {
+ PDBG (pixma_dbg (3, "Reader task terminated\n"));
+ }
+ else
+ {
+ PDBG (pixma_dbg
+ (2, "Reader task terminated: %s\n", pixma_strerror (count)));
+ }
+ return map_error (count);
+}
+
+static int
+reader_process (void *arg)
+{
+ pixma_sane_t *ss = (pixma_sane_t *) arg;
+ struct SIGACTION sa;
+
+ reader_ss = ss;
+ memset (&sa, 0, sizeof (sa));
+ sigemptyset (&sa.sa_mask);
+ sa.sa_handler = reader_signal_handler;
+ /* FIXME: which signal else? */
+ sigaction (SIGHUP, &sa, NULL);
+ sigaction (SIGINT, &sa, NULL);
+ sigaction (SIGPIPE, &sa, NULL);
+ sigaction (SIGTERM, &sa, NULL);
+ close (ss->rpipe);
+ ss->rpipe = -1;
+ return reader_loop (ss);
+}
+
+static int
+reader_thread (void *arg)
+{
+ pixma_sane_t *ss = (pixma_sane_t *) arg;
+#ifdef USE_PTHREAD
+ /* Block SIGPIPE. We will handle this in reader_loop() by checking
+ ss->reader_stop and the return value from write(). */
+ sigset_t sigs;
+ sigemptyset (&sigs);
+ sigaddset (&sigs, SIGPIPE);
+ pthread_sigmask (SIG_BLOCK, &sigs, NULL);
+#endif /* USE_PTHREAD */
+ return reader_loop (ss);
+}
+
+static SANE_Pid
+terminate_reader_task (pixma_sane_t * ss, int *exit_code)
+{
+ SANE_Pid result, pid;
+ int status = 0;
+
+ pid = ss->reader_taskid;
+ if (!sanei_thread_is_valid (pid))
+ return pid;
+ if (sanei_thread_is_forked ())
+ {
+ sanei_thread_kill (pid);
+ }
+ else
+ {
+ ss->reader_stop = SANE_TRUE;
+/* pixma_cancel (ss->s); What is this for ? Makes end-of-scan buggy => removing */
+ }
+ result = sanei_thread_waitpid (pid, &status);
+ sanei_thread_invalidate (ss->reader_taskid);
+
+ if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
+ ss->idle = SANE_TRUE;
+
+ if (result == pid)
+ {
+ if (exit_code)
+ *exit_code = status;
+ return pid;
+ }
+ else
+ {
+ PDBG (pixma_dbg (1, "WARNING:waitpid() failed %s\n", strerror (errno)));
+ sanei_thread_invalidate (pid);
+ return pid;
+ }
+}
+
+static int
+start_reader_task (pixma_sane_t * ss)
+{
+ int fds[2];
+ SANE_Pid pid;
+ int is_forked;
+
+ if (ss->rpipe != -1 || ss->wpipe != -1)
+ {
+ PDBG (pixma_dbg
+ (1, "BUG:rpipe = %d, wpipe = %d\n", ss->rpipe, ss->wpipe));
+ close (ss->rpipe);
+ close (ss->wpipe);
+ ss->rpipe = -1;
+ ss->wpipe = -1;
+ }
+ if (sanei_thread_is_valid (ss->reader_taskid))
+ {
+ PDBG (pixma_dbg
+ (1, "BUG:reader_taskid(%ld) != -1\n", (long) ss->reader_taskid));
+ terminate_reader_task (ss, NULL);
+ }
+ if (pipe (fds) == -1)
+ {
+ PDBG (pixma_dbg (1, "ERROR:start_reader_task():pipe() failed %s\n",
+ strerror (errno)));
+ return PIXMA_ENOMEM;
+ }
+ ss->rpipe = fds[0];
+ ss->wpipe = fds[1];
+ ss->reader_stop = SANE_FALSE;
+
+ is_forked = sanei_thread_is_forked ();
+ if (is_forked)
+ {
+ pid = sanei_thread_begin (reader_process, ss);
+ if (sanei_thread_is_valid (pid))
+ {
+ close (ss->wpipe);
+ ss->wpipe = -1;
+ }
+ }
+ else
+ {
+ pid = sanei_thread_begin (reader_thread, ss);
+ }
+ if (!sanei_thread_is_valid (pid))
+ {
+ close (ss->wpipe);
+ close (ss->rpipe);
+ ss->wpipe = -1;
+ ss->rpipe = -1;
+ PDBG (pixma_dbg (1, "ERROR:unable to start reader task\n"));
+ return PIXMA_ENOMEM;
+ }
+ PDBG (pixma_dbg (3, "Reader task id=%ld (%s)\n", (long) pid,
+ (is_forked) ? "forked" : "threaded"));
+ ss->reader_taskid = pid;
+ return 0;
+}
+
+/* libJPEG API callbacks */
+static void
+jpeg_init_source(j_decompress_ptr __sane_unused__ cinfo)
+{
+ /* No-op */
+}
+
+static void
+jpeg_term_source(j_decompress_ptr __sane_unused__ cinfo)
+{
+ /* No-op */
+}
+
+static boolean
+jpeg_fill_input_buffer(j_decompress_ptr cinfo)
+{
+ pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src;
+ int size;
+ int retry;
+
+ for (retry = 0; retry < 30; retry ++ )
+ {
+ size = read (mgr->s->rpipe, mgr->buffer, 1024);
+ if (size == 0)
+ {
+ return FALSE;
+ }
+ else if (size < 0)
+ {
+ sleep (1);
+ }
+ else
+ {
+ mgr->jpeg.next_input_byte = mgr->buffer;
+ mgr->jpeg.bytes_in_buffer = size;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+ pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src;
+
+ if (num_bytes > 0)
+ {
+ /* Read and throw away extra */
+ while (num_bytes > (long)mgr->jpeg.bytes_in_buffer)
+ {
+ num_bytes -= (long)mgr->jpeg.bytes_in_buffer;
+ jpeg_fill_input_buffer(cinfo);
+ }
+
+ /* Update jpeg info structure with leftover */
+ mgr->jpeg.next_input_byte += (size_t) num_bytes;
+ mgr->jpeg.bytes_in_buffer -= (size_t) num_bytes;
+ }
+}
+
+/* Pixma JPEG reader helpers */
+static SANE_Status
+pixma_jpeg_start(pixma_sane_t *s)
+{
+ pixma_jpeg_src_mgr *mgr;
+
+ s->jpeg_cinfo.err = jpeg_std_error(&s->jpeg_err);
+
+ jpeg_create_decompress(&s->jpeg_cinfo);
+
+ s->jpeg_cinfo.src = (struct jpeg_source_mgr *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo,
+ JPOOL_PERMANENT, sizeof(pixma_jpeg_src_mgr));
+
+ memset(s->jpeg_cinfo.src, 0, sizeof(pixma_jpeg_src_mgr));
+
+ mgr = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src;
+ mgr->s = s;
+
+ mgr->buffer = (JOCTET *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo,
+ JPOOL_PERMANENT,
+ 1024 * sizeof(JOCTET));
+
+ mgr->jpeg.init_source = jpeg_init_source;
+ mgr->jpeg.fill_input_buffer = jpeg_fill_input_buffer;
+ mgr->jpeg.skip_input_data = jpeg_skip_input_data;
+ mgr->jpeg.resync_to_restart = jpeg_resync_to_restart;
+ mgr->jpeg.term_source = jpeg_term_source;
+ mgr->jpeg.bytes_in_buffer = 0;
+ mgr->jpeg.next_input_byte = NULL;
+
+ s->jpeg_header_seen = 0;
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+pixma_jpeg_read_header(pixma_sane_t *s)
+{
+ pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src;
+
+ if (jpeg_read_header(&s->jpeg_cinfo, TRUE))
+ {
+ s->jdst = sanei_jpeg_jinit_write_ppm(&s->jpeg_cinfo);
+
+ if (jpeg_start_decompress(&s->jpeg_cinfo))
+ {
+ int size;
+
+ DBG(3, "%s: w: %d, h: %d, components: %d\n",
+ __func__,
+ s->jpeg_cinfo.output_width, s->jpeg_cinfo.output_height,
+ s->jpeg_cinfo.output_components);
+
+ size = s->jpeg_cinfo.output_width * s->jpeg_cinfo.output_components * 1;
+
+ src->linebuffer = (*s->jpeg_cinfo.mem->alloc_large)((j_common_ptr)&s->jpeg_cinfo,
+ JPOOL_PERMANENT, size);
+
+ src->linebuffer_size = 0;
+ src->linebuffer_index = 0;
+
+ s->jpeg_header_seen = 1;
+
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ DBG(0, "%s: decompression failed\n", __func__);
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ else
+ {
+ DBG(0, "%s: cannot read JPEG header\n", __func__);
+ return SANE_STATUS_IO_ERROR;
+ }
+}
+
+static void
+pixma_jpeg_finish(pixma_sane_t *ss)
+{
+ jpeg_destroy_decompress(&ss->jpeg_cinfo);
+}
+
+static void
+pixma_jpeg_read(pixma_sane_t *ss, SANE_Byte *data,
+ SANE_Int max_length, SANE_Int *length)
+{
+ struct jpeg_decompress_struct cinfo = ss->jpeg_cinfo;
+ pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)ss->jpeg_cinfo.src;
+
+ int l;
+
+ *length = 0;
+
+ /* copy from line buffer if available */
+ if (src->linebuffer_size && src->linebuffer_index < src->linebuffer_size)
+ {
+ *length = src->linebuffer_size - src->linebuffer_index;
+
+ if (*length > max_length)
+ *length = max_length;
+
+ memcpy(data, src->linebuffer + src->linebuffer_index, *length);
+ src->linebuffer_index += *length;
+
+ return;
+ }
+
+ if (cinfo.output_scanline >= cinfo.output_height)
+ {
+ *length = 0;
+ return;
+ }
+
+ /* scanlines of decompressed data will be in ss->jdst->buffer
+ * only one line at time is supported
+ */
+
+ l = jpeg_read_scanlines(&cinfo, ss->jdst->buffer, 1);
+ if (l == 0)
+ return;
+
+ /* from ss->jdst->buffer to linebuffer
+ * linebuffer holds width * bytesperpixel
+ */
+
+ (*ss->jdst->put_pixel_rows)(&cinfo, ss->jdst, 1, (char *)src->linebuffer);
+
+ *length = ss->sp.w * ss->sp.channels;
+ /* Convert RGB into grayscale */
+ if (ss->sp.channels == 1)
+ {
+ unsigned int i;
+ unsigned char *d = (unsigned char *)src->linebuffer;
+ unsigned char *s = (unsigned char *)src->linebuffer;
+ for (i = 0; i < ss->sp.w; i++)
+ {
+ /* Using BT.709 luma formula, fixed-point */
+ int sum = ( s[0]*2126 + s[1]*7152 + s[2]*722 );
+ *d = sum / 10000;
+ d ++;
+ s += 3;
+ }
+ }
+
+ /* Maybe pack into lineary binary image */
+ if (ss->sp.depth == 1)
+ {
+ *length /= 8;
+ unsigned int i;
+ unsigned char *d = (unsigned char *)src->linebuffer;
+ unsigned char *s = (unsigned char *)src->linebuffer;
+ unsigned char b = 0;
+ for (i = 1; i < ss->sp.w + 1; i++)
+ {
+ if (*(s++) > 127)
+ b = (b << 1) | 0;
+ else
+ b = (b << 1) | 1;
+ }
+ if ((i % 8) == 0)
+ *(d++) = b;
+ }
+
+ src->linebuffer_size = *length;
+ src->linebuffer_index = 0;
+
+ if (*length > max_length)
+ *length = max_length;
+
+ memcpy(data, src->linebuffer + src->linebuffer_index, *length);
+ src->linebuffer_index += *length;
+}
+
+
+
+static SANE_Status
+read_image (pixma_sane_t * ss, void *buf, unsigned size, int *readlen)
+{
+ int count, status;
+
+ if (readlen)
+ *readlen = 0;
+ if (ss->image_bytes_read >= ss->sp.image_size)
+ return SANE_STATUS_EOF;
+
+ do
+ {
+ if (ss->cancel)
+ /* ss->rpipe has already been closed by sane_cancel(). */
+ return SANE_STATUS_CANCELLED;
+ if (ss->sp.mode_jpeg && !ss->jpeg_header_seen)
+ {
+ status = pixma_jpeg_read_header(ss);
+ if (status != SANE_STATUS_GOOD)
+ {
+ close (ss->rpipe);
+ pixma_jpeg_finish(ss);
+ ss->rpipe = -1;
+ if (sanei_thread_is_valid (terminate_reader_task (ss, &status))
+ && status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ else
+ {
+ /* either terminate_reader_task failed or
+ rpipe was closed but we expect more data */
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ }
+
+ if (ss->sp.mode_jpeg)
+ {
+ count = -1;
+ pixma_jpeg_read(ss, buf, size, &count);
+ }
+ else
+ count = read (ss->rpipe, buf, size);
+ }
+ while (count == -1 && errno == EINTR);
+
+ if (count == -1)
+ {
+ if (errno == EAGAIN)
+ return SANE_STATUS_GOOD;
+ if (!ss->cancel)
+ {
+ PDBG (pixma_dbg (1, "WARNING:read_image():read() failed %s\n",
+ strerror (errno)));
+ }
+ close (ss->rpipe);
+ ss->rpipe = -1;
+ terminate_reader_task (ss, NULL);
+ if (ss->sp.mode_jpeg)
+ pixma_jpeg_finish(ss);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* here count >= 0 */
+ ss->image_bytes_read += count;
+ if (ss->image_bytes_read > ss->sp.image_size)
+ {
+ PDBG (pixma_dbg (1, "BUG:ss->image_bytes_read > ss->sp.image_size\n"));
+ }
+ if (ss->image_bytes_read >= ss->sp.image_size)
+ {
+ close (ss->rpipe);
+ ss->rpipe = -1;
+ terminate_reader_task (ss, NULL);
+ if (ss->sp.mode_jpeg)
+ pixma_jpeg_finish(ss);
+ }
+ else if (count == 0)
+ {
+ PDBG (pixma_dbg (3, "read_image():reader task closed the pipe:%"
+ PRIu64" bytes received, %"PRIu64" bytes expected\n",
+ ss->image_bytes_read, ss->sp.image_size));
+ close (ss->rpipe);
+ if (ss->sp.mode_jpeg)
+ pixma_jpeg_finish(ss);
+ ss->rpipe = -1;
+ if (sanei_thread_is_valid (terminate_reader_task (ss, &status))
+ && status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ else
+ {
+ /* either terminate_reader_task failed or
+ rpipe was closed but we expect more data */
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ if (readlen)
+ *readlen = count;
+ return SANE_STATUS_GOOD;
+}
+
+
+/*******************************************************************
+ ** SANE API
+ *******************************************************************/
+SANE_Status
+sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
+{
+ int status, myversion, i;
+ SANEI_Config config;
+
+ UNUSED (authorize);
+
+ if (!version_code)
+ return SANE_STATUS_INVAL;
+ myversion = 100 * PIXMA_VERSION_MAJOR + PIXMA_VERSION_MINOR;
+ *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, myversion);
+ DBG_INIT ();
+ sanei_thread_init ();
+ pixma_set_debug_level (DBG_LEVEL);
+
+ PDBG(pixma_dbg(2, "pixma is compiled %s pthread support.\n",
+ (sanei_thread_is_forked () ? "without" : "with")));
+
+ for (i = 0; i < MAX_CONF_DEVICES; i++)
+ conf_devices[i] = NULL;
+
+ config.count = 0;
+ config.descriptors = NULL;
+ config.values = NULL;
+
+ if (sanei_configure_attach(PIXMA_CONFIG_FILE, &config, config_attach_pixma) !=
+ SANE_STATUS_GOOD)
+ PDBG(pixma_dbg(2, "Could not read pixma configuration file: %s\n",
+ PIXMA_CONFIG_FILE));
+
+ status = pixma_init ();
+ if (status < 0)
+ {
+ PDBG (pixma_dbg (2, "pixma_init() failed %s\n", pixma_strerror (status)));
+ }
+ return map_error (status);
+}
+
+void
+sane_exit (void)
+{
+ while (first_scanner)
+ sane_close (first_scanner);
+ cleanup_device_list ();
+ pixma_cleanup ();
+}
+
+SANE_Status
+sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
+{
+ if (!device_list)
+ return SANE_STATUS_INVAL;
+ find_scanners (local_only);
+ *device_list = dev_list;
+ return (dev_list) ? SANE_STATUS_GOOD : SANE_STATUS_NO_MEM;
+}
+
+SANE_Status
+sane_open (SANE_String_Const name, SANE_Handle * h)
+{
+ unsigned i, j, nscanners;
+ int error = 0;
+ pixma_sane_t *ss = NULL;
+ const pixma_config_t *cfg;
+
+ if (!name || !h)
+ return SANE_STATUS_INVAL;
+
+ *h = NULL;
+ nscanners = pixma_find_scanners (conf_devices, SANE_FALSE);
+ if (nscanners == 0)
+ return SANE_STATUS_INVAL;
+ if (name[0] == '\0')
+ name = pixma_get_device_id (0);
+
+ /* Have we already opened the scanner? */
+ for (ss = first_scanner; ss; ss = ss->next)
+ {
+ if (strcmp (pixma_get_string (ss->s, PIXMA_STRING_ID), name) == 0)
+ {
+ /* We have already opened it! */
+ return SANE_STATUS_DEVICE_BUSY;
+ }
+ }
+
+ i = 0;
+ while (strcmp (pixma_get_device_id (i), name) != 0)
+ {
+ if (++i >= nscanners)
+ return SANE_STATUS_INVAL;
+ }
+ cfg = pixma_get_device_config (i);
+ if ((cfg->cap & PIXMA_CAP_EXPERIMENT) != 0)
+ {
+#ifndef NDEBUG
+ pixma_dbg (1, "WARNING:"
+ "Experimental backend CAN DAMAGE your hardware!\n");
+ if (getenv_atoi ("PIXMA_EXPERIMENT", 0) == 0)
+ {
+ pixma_dbg (1, "Experimental SANE backend for %s is disabled "
+ "by default.\n", pixma_get_device_model (i));
+ pixma_dbg (1, "To enable it, set the environment variable "
+ "PIXMA_EXPERIMENT to non-zero.\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#else
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
+ ss = (pixma_sane_t *) calloc (1, sizeof (*ss));
+ if (!ss)
+ return SANE_STATUS_NO_MEM;
+ ss->next = first_scanner;
+ first_scanner = ss;
+ sanei_thread_initialize (ss->reader_taskid);
+ ss->wpipe = -1;
+ ss->rpipe = -1;
+ ss->idle = SANE_TRUE;
+ ss->scanning = SANE_FALSE;
+ ss->sp.frontend_cancel = SANE_FALSE;
+ for (j=0; j < BUTTON_GROUP_SIZE; j++)
+ ss->button_option_is_cached[j] = 0;
+ error = pixma_open (i, &ss->s);
+ if (error < 0)
+ {
+ sane_close (ss);
+ return map_error (error);
+ }
+ pixma_enable_background (ss->s, 0);
+ init_option_descriptors (ss);
+ *h = ss;
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle h)
+{
+ pixma_sane_t **p, *ss;
+
+ for (p = &first_scanner; *p && *p != (pixma_sane_t *) h; p = &((*p)->next))
+ {
+ }
+ if (!(*p))
+ return;
+ ss = *p;
+ sane_cancel (ss);
+ pixma_close (ss->s);
+ *p = ss->next;
+ free (ss);
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle h, SANE_Int n)
+{
+ DECL_CTX;
+
+ if (ss && 0 <= n && n < opt_last)
+ return &SOD (n);
+ return NULL;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle h, SANE_Int n,
+ SANE_Action a, void *v, SANE_Int * i)
+{
+ DECL_CTX;
+ SANE_Int info = 0;
+ int error;
+ option_descriptor_t *opt;
+
+ if (i)
+ *i = 0;
+ if (!ss)
+ return SANE_STATUS_INVAL;
+ if (n < 0 || n >= opt_last)
+ return SANE_STATUS_UNSUPPORTED;
+ if (!ss->idle && a != SANE_ACTION_GET_VALUE)
+ {
+ PDBG (pixma_dbg (3, "Warning: !idle && !SANE_ACTION_GET_VALUE\n"));
+ if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
+ return SANE_STATUS_INVAL;
+ }
+
+ opt = &(OPT_IN_CTX[n]);
+ if (!SANE_OPTION_IS_ACTIVE (opt->sod.cap))
+ return SANE_STATUS_INVAL;
+ switch (a)
+ {
+ case SANE_ACTION_SET_VALUE:
+ if ((opt->sod.type != SANE_TYPE_BUTTON && !v) ||
+ !SANE_OPTION_IS_SETTABLE (opt->sod.cap))
+ return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */
+ break;
+ case SANE_ACTION_SET_AUTO:
+ if (!(opt->sod.cap & SANE_CAP_AUTOMATIC) ||
+ !SANE_OPTION_IS_SETTABLE (opt->sod.cap))
+ return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */
+ break;
+ case SANE_ACTION_GET_VALUE:
+ if (!v || !(opt->sod.cap & SANE_CAP_SOFT_DETECT))
+ return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */
+ break;
+ default:
+ return SANE_STATUS_UNSUPPORTED;
+ }
+
+ error = control_option (ss, n, a, v, &info);
+ if (error == SANE_STATUS_GOOD && i)
+ *i = info;
+ return error;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle h, SANE_Parameters * p)
+{
+ DECL_CTX;
+ pixma_scan_param_t temp, *sp;
+
+ if (!ss || !p)
+ return SANE_STATUS_INVAL;
+
+ if (!ss->idle)
+ {
+ sp = &ss->sp; /* sp is calculated in sane_start() */
+ }
+ else
+ {
+ calc_scan_param (ss, &temp);
+ sp = &temp;
+ }
+ p->format = (sp->channels == 3) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
+ p->last_frame = SANE_TRUE;
+ p->lines = sp->h;
+ p->depth = sp->depth;
+ p->pixels_per_line = sp->w;
+ /* p->bytes_per_line = sp->line_size; NOTE: It should work this way, but it doesn't. No SANE frontend can cope with this. */
+ p->bytes_per_line = (sp->w * sp->channels * sp->depth) / 8;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_start (SANE_Handle h)
+{
+ DECL_CTX;
+ int error = 0;
+
+ if (!ss)
+ return SANE_STATUS_INVAL;
+ if (!ss->idle && ss->scanning)
+ {
+ PDBG (pixma_dbg (3, "Warning in Sane_start: !idle && scanning. idle=%d, ss->scanning=%d\n",
+ ss->idle, ss->scanning));
+ if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
+ return SANE_STATUS_INVAL;
+ }
+
+ ss->cancel = SANE_FALSE;
+ if (ss->idle ||
+ ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED ||
+ ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU)
+ ss->page_count = 0; /* start from idle state or scan from flatbed or TPU */
+ else
+ ss->page_count++;
+ if (calc_scan_param (ss, &ss->sp) < 0)
+ return SANE_STATUS_INVAL;
+
+ /* Prepare the JPEG decompressor, if needed */
+ if (ss->sp.mode_jpeg)
+ {
+ SANE_Status status;
+ status = pixma_jpeg_start(ss);
+ if (status != SANE_STATUS_GOOD)
+ {
+ PDBG (pixma_dbg(1, "%s: pixma_jpeg_start: %s\n", __func__, sane_strstatus(status)) );
+ return status;
+ }
+ }
+
+ ss->image_bytes_read = 0;
+ /* TODO: Check paper here in sane_start(). A function like
+ pixma_get_status() is needed. */
+ error = start_reader_task (ss);
+ if (error >= 0)
+ {
+ ss->output_line_size = (ss->sp.w * ss->sp.channels * ss->sp.depth) / 8;
+ ss->byte_pos_in_line = 0;
+ ss->last_read_status = SANE_STATUS_GOOD;
+ ss->scanning = SANE_TRUE;
+ ss->idle = SANE_FALSE;
+ if (ss->sp.mode_jpeg && !ss->jpeg_header_seen)
+ {
+ SANE_Status status;
+ status = pixma_jpeg_read_header(ss);
+ if (status != SANE_STATUS_GOOD)
+ {
+ close (ss->rpipe);
+ pixma_jpeg_finish(ss);
+ ss->rpipe = -1;
+ if (sanei_thread_is_valid (terminate_reader_task (ss, &error))
+ && error != SANE_STATUS_GOOD)
+ {
+ return error;
+ }
+ }
+ }
+ }
+ return map_error (error);
+}
+
+SANE_Status
+sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
+{
+ DECL_CTX;
+ int sum, n;
+ /* Due to 32 pixels alignment, sizeof(temp) is to be greater than:
+ * max(nchannels) * max (sp.line_size - output_line_size)
+ * so currently: 3 * 32 = 96 for better end line cropping efficiency */
+ SANE_Byte temp[100];
+ SANE_Status status;
+
+ if (len)
+ *len = 0;
+ if (!ss || !buf || !len)
+ return SANE_STATUS_INVAL;
+ if (ss->cancel)
+ return SANE_STATUS_CANCELLED;
+ if ((ss->idle)
+ && (ss->sp.source == PIXMA_SOURCE_ADF || ss->sp.source == PIXMA_SOURCE_ADFDUP))
+ return SANE_STATUS_INVAL;
+ if (!ss->scanning)
+ return ss->last_read_status;
+
+ status = SANE_STATUS_GOOD;
+ /* CCD scanners use software lineart
+ * the scanner must scan 24 bit color or 8 bit grayscale for one bit lineart */
+ if ((ss->sp.line_size - ((ss->sp.software_lineart == 1) ? (ss->output_line_size * 8) : ss->output_line_size)) == 0)
+ {
+ status = read_image (ss, buf, maxlen, &sum);
+ }
+ else
+ {
+ /* FIXME: Because there is no frontend that can cope with padding at
+ the end of line, we've to remove it here in the backend! */
+ PDBG (pixma_dbg (1, "*sane_read***** Warning: padding may cause incomplete scan results\n"));
+ sum = 0;
+ while (sum < maxlen)
+ {
+ if (ss->byte_pos_in_line < ss->output_line_size)
+ {
+ n = ss->output_line_size - ss->byte_pos_in_line;
+ if ((maxlen - sum) < n)
+ n = maxlen - sum;
+ status = read_image (ss, buf, n, &n);
+ if (n == 0)
+ break;
+ sum += n;
+ buf += n;
+ ss->byte_pos_in_line += n;
+ }
+ else
+ {
+ /* skip padding */
+ n = ss->sp.line_size - ss->byte_pos_in_line;
+ if (n > (int) sizeof (temp))
+ {
+ PDBG (pixma_dbg (3, "Inefficient skip buffer. Should be %d\n", n));
+ n = sizeof (temp);
+ }
+ status = read_image (ss, temp, n, &n);
+ if (n == 0)
+ break;
+ ss->byte_pos_in_line += n;
+ if (ss->byte_pos_in_line == ss->sp.line_size)
+ ss->byte_pos_in_line = 0;
+ }
+ }
+ }
+ if (ss->cancel)
+ status = SANE_STATUS_CANCELLED;
+ else if ((status == SANE_STATUS_GOOD || status == SANE_STATUS_EOF) &&
+ sum > 0)
+ {
+ *len = sum;
+ status = SANE_STATUS_GOOD;
+ }
+ ss->scanning = (status == SANE_STATUS_GOOD);
+ ss->last_read_status = status;
+ return status;
+}
+
+void
+sane_cancel (SANE_Handle h)
+{
+ DECL_CTX;
+
+ if (!ss)
+ return;
+ ss->cancel = SANE_TRUE;
+ ss->sp.frontend_cancel = SANE_TRUE;
+ if (ss->idle)
+ return;
+ close (ss->rpipe);
+ if (ss->sp.mode_jpeg)
+ pixma_jpeg_finish(ss);
+ ss->rpipe = -1;
+ terminate_reader_task (ss, NULL);
+ ss->idle = SANE_TRUE;
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle h, SANE_Bool m)
+{
+ DECL_CTX;
+
+ if (!ss || ss->idle || ss->rpipe == -1)
+ return SANE_STATUS_INVAL;
+#ifdef HAVE_FCNTL_H
+ PDBG (pixma_dbg (2, "Setting %sblocking mode\n", (m) ? "non-" : ""));
+ if (fcntl (ss->rpipe, F_SETFL, (m) ? O_NONBLOCK : 0) == -1)
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING:fcntl(F_SETFL) failed %s\n", strerror (errno)));
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ return SANE_STATUS_GOOD;
+#else
+ return (m) ? SANE_STATUS_UNSUPPORTED : SANE_STATUS_GOOD;
+#endif
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle h, SANE_Int * fd)
+{
+ DECL_CTX;
+
+ *fd = -1;
+ if (!ss || !fd || ss->idle || ss->rpipe == -1)
+ return SANE_STATUS_INVAL;
+ *fd = ss->rpipe;
+ return SANE_STATUS_GOOD;
+}
+
+/*
+BEGIN SANE_Option_Descriptor
+
+rem -------------------------------------------
+type group
+ title Scan mode
+
+type int resolution
+ unit dpi
+ constraint @word_list = ss->dpi_list
+ default 75
+ title @SANE_TITLE_SCAN_RESOLUTION
+ desc @SANE_DESC_SCAN_RESOLUTION
+ cap soft_select soft_detect automatic
+ info reload_params
+
+type string mode[30]
+ constraint @string_list = ss->mode_list
+ default @s = SANE_VALUE_SCAN_MODE_COLOR
+ title @SANE_TITLE_SCAN_MODE
+ desc @SANE_DESC_SCAN_MODE
+ cap soft_select soft_detect automatic
+ info reload_params
+
+type string source[30]
+ constraint @string_list = ss->source_list
+ title @SANE_TITLE_SCAN_SOURCE
+ desc Selects the scan source (such as a document-feeder). Set source before mode and resolution. Resets mode and resolution to auto values.
+ default Flatbed
+ cap soft_select soft_detect
+
+type bool button-controlled
+ title Button-controlled scan
+ desc When enabled, scan process will not start immediately. To proceed, press \"SCAN\" button (for MP150) or \"COLOR\" button (for other models). To cancel, press \"GRAY\" button.
+ default SANE_FALSE
+ cap soft_select soft_detect inactive
+
+rem -------------------------------------------
+type group
+ title Gamma
+
+type bool custom-gamma
+ default SANE_TRUE
+ title @SANE_TITLE_CUSTOM_GAMMA
+ desc @SANE_DESC_CUSTOM_GAMMA
+ cap soft_select soft_detect automatic inactive
+
+type int gamma-table[4096]
+ constraint (0,255,0)
+ title @SANE_TITLE_GAMMA_VECTOR
+ desc @SANE_DESC_GAMMA_VECTOR
+ cap soft_select soft_detect automatic inactive
+
+type fixed gamma
+ default AUTO_GAMMA
+ constraint (0.3,5,0)
+ title Gamma function exponent
+ desc Changes intensity of midtones
+ cap soft_select soft_detect automatic inactive
+
+rem -------------------------------------------
+type group
+ title Geometry
+
+type fixed tl-x
+ unit mm
+ default 0
+ constraint @range = &ss->xrange
+ title @SANE_TITLE_SCAN_TL_X
+ desc @SANE_DESC_SCAN_TL_X
+ cap soft_select soft_detect automatic
+ info reload_params
+
+type fixed tl-y
+ unit mm
+ default 0
+ constraint @range = &ss->yrange
+ title @SANE_TITLE_SCAN_TL_Y
+ desc @SANE_DESC_SCAN_TL_Y
+ cap soft_select soft_detect automatic
+ info reload_params
+
+type fixed br-x
+ unit mm
+ default _MAX
+ constraint @range = &ss->xrange
+ title @SANE_TITLE_SCAN_BR_X
+ desc @SANE_DESC_SCAN_BR_X
+ cap soft_select soft_detect automatic
+ info reload_params
+
+type fixed br-y
+ unit mm
+ default _MAX
+ constraint @range = &ss->yrange
+ title @SANE_TITLE_SCAN_BR_Y
+ desc @SANE_DESC_SCAN_BR_Y
+ cap soft_select soft_detect automatic
+ info reload_params
+
+rem -------------------------------------------
+type group
+ title Buttons
+
+type button button-update
+ title Update button state
+ cap soft_select soft_detect advanced
+
+type int button-1
+ default 0
+ title Button 1
+ cap soft_detect advanced
+
+type int button-2
+ default 0
+ title Button 2
+ cap soft_detect advanced
+
+type int original
+ default 0
+ title Type of original to scan
+ cap soft_detect advanced
+
+type int target
+ default 0
+ title Target operation type
+ cap soft_detect advanced
+
+type int scan-resolution
+ default 0
+ title Scan resolution
+ cap soft_detect advanced
+
+rem -------------------------------------------
+type group
+ title Extras
+
+type int threshold
+ unit PERCENT
+ default 50
+ constraint (0,100,1)
+ title @SANE_TITLE_THRESHOLD
+ desc @SANE_DESC_THRESHOLD
+ cap soft_select soft_detect automatic inactive
+
+type int threshold-curve
+ constraint (0,127,1)
+ title Threshold curve
+ desc Dynamic threshold curve, from light to dark, normally 50-65
+ cap soft_select soft_detect automatic inactive
+
+type int adf-wait
+ default 0
+ constraint (0,3600,1)
+ title ADF Waiting Time
+ desc When set, the scanner searches the waiting time in seconds for a new document inserted into the automatic document feeder.
+ cap soft_select soft_detect automatic inactive
+
+rem -------------------------------------------
+END SANE_Option_Descriptor
+*/
+
+/* pixma_sane_options.c generated by
+ * scripts/pixma_gen_options.py < pixma.c > pixma_sane_options.c
+ *
+ * pixma_sane_options.h generated by
+ * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h
+ */
+#include "pixma_sane_options.c"
diff --git a/backend/pixma/pixma.h b/backend/pixma/pixma.h
new file mode 100644
index 0000000..c2df3cc
--- /dev/null
+++ b/backend/pixma/pixma.h
@@ -0,0 +1,506 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#ifndef PIXMA_H
+#define PIXMA_H
+
+#include "../include/sane/sane.h"
+
+
+/*!
+ * \mainpage Scanner driver for Canon PIXMA MP series
+ * \section example Sample code for application
+ * \code
+ * pixma_set_debug_level(level);
+ * pixma_init();
+ * nscanners = pixma_find_scanners();
+ * devnr = choose_scanner(nscanners);
+ * scanner = pixma_open(devnr);
+ * setup_param(param);
+ * pixma_check_scan_param(scanner, param);
+ * do {
+ * if (I_need_events &&
+ * (ev = pixma_wait_event(scanner, timeout)) > 0) {
+ * handle_event(ev);
+ * }
+ * pixma_scan(scanner, param);
+ * while ((count = pixma_read_image(scanner, buf, len)) > 0) {
+ * write(buf, count);
+ * if (error_occured_in_write) {
+ * pixma_cancel(scanner);
+ * }
+ * }
+ * } while (!enough);
+ * pixma_close(scanner);
+ * pixma_cleanup();
+ * \endcode
+ *
+ * <b>Note:</b> pixma_cancel() can be called asynchronously to
+ * interrupt pixma_read_image(). It does not cancel the operation
+ * immediately. pixma_read_image() <em>must</em> be called until it
+ * returns zero or an error (probably \c PIXMA_ECANCELED).
+ *
+ * \section reference Reference
+ * - \subpage API
+ * - \subpage IO
+ * - \subpage subdriver
+ * - \subpage debug
+ */
+
+/*!
+ * \defgroup API The driver API
+ * \brief The driver API.
+ *
+ * The return value of functions that returns \c int has the following
+ * meaning if not otherwise specified:
+ * - >= 0 if succeeded
+ * - < 0 if failed
+ */
+
+#ifdef HAVE_STDINT_H
+# include <stdint.h> /* available in ISO C99 */
+#else
+# include <sys/types.h>
+typedef uint8_t uint8_t;
+typedef uint16_t uint16_t;
+typedef uint32_t uint32_t;
+#endif /* HAVE_STDINT_H */
+
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h> /* available in ISO C99 */
+#endif /* HAVE_INTTYPES_H */
+
+/** \addtogroup API
+ * @{ */
+/** Don't forget to update the backend version in the SANE Backend specification
+ * file: doc/descriptions/pixma.desc !!!
+ */
+/** \name Version of the driver */
+/**@{*/
+#define PIXMA_VERSION_MAJOR 0
+#define PIXMA_VERSION_MINOR 27
+#define PIXMA_VERSION_BUILD 0
+/**@}*/
+
+/** \name Error codes */
+/**@{*/
+#define PIXMA_EIO -1
+#define PIXMA_ENODEV -2
+#define PIXMA_EACCES -3
+#define PIXMA_ENOMEM -4
+#define PIXMA_EINVAL -5
+#define PIXMA_EBUSY -6
+#define PIXMA_ECANCELED -7
+#define PIXMA_ENOTSUP -8
+#define PIXMA_ETIMEDOUT -9
+#define PIXMA_EPROTO -10
+#define PIXMA_EPAPER_JAMMED -11
+#define PIXMA_ECOVER_OPEN -12
+#define PIXMA_ENO_PAPER -13
+#define PIXMA_EOF -14
+/**@}*/
+
+/** \name Capabilities for using with pixma_config_t::cap */
+/**@{*/
+#define PIXMA_CAP_EASY_RGB (1 << 0)
+#define PIXMA_CAP_GRAY (1 << 1)
+#define PIXMA_CAP_ADF (1 << 2)
+#define PIXMA_CAP_48BIT (1 << 3)
+#define PIXMA_CAP_GAMMA_TABLE (1 << 4)
+#define PIXMA_CAP_EVENTS (1 << 5)
+#define PIXMA_CAP_TPU (1 << 6)
+#define PIXMA_CAP_ADFDUP ((1 << 7) | PIXMA_CAP_ADF)
+#define PIXMA_CAP_CIS (0)
+#define PIXMA_CAP_CCD (1 << 8)
+#define PIXMA_CAP_LINEART (1 << 9)
+#define PIXMA_CAP_NEGATIVE (1 << 10)
+#define PIXMA_CAP_TPUIR ((1 << 11) | PIXMA_CAP_TPU)
+#define PIXMA_CAP_ADF_WAIT (1 << 12)
+#define PIXMA_CAP_ADF_JPEG (1 << 13)
+#define PIXMA_CAP_EXPERIMENT (1 << 31)
+/**@}*/
+
+/** \name Button events and related information returned by pixma_wait_event() */
+/**@{*/
+#define PIXMA_EV_NONE 0
+#define PIXMA_EV_ACTION_MASK (0xffffff)
+#define PIXMA_EV_BUTTON1 (1 << 24)
+#define PIXMA_EV_BUTTON2 (2 << 24)
+#define PIXMA_EV_TARGET_MASK (0xff)
+#define PIXMA_EV_ORIGINAL_MASK (0xff00)
+#define PIXMA_EV_DPI_MASK (0xff0000)
+
+#define GET_EV_TARGET(x) (x & PIXMA_EV_TARGET_MASK)
+#define GET_EV_ORIGINAL(x) ( (x & PIXMA_EV_ORIGINAL_MASK) >> 8 )
+#define GET_EV_DPI(x) ( (x & PIXMA_EV_DPI_MASK) >> 16 )
+
+/**@}*/
+/** @} end of API group */
+
+#define PIXMA_CONFIG_FILE "pixma.conf"
+#define MAX_CONF_DEVICES 15
+
+struct pixma_t;
+struct pixma_scan_ops_t;
+struct pixma_scan_param_t;
+struct pixma_config_t;
+struct pixma_cmdbuf_t;
+struct pixma_imagebuf_t;
+struct pixma_device_status_t;
+
+typedef struct pixma_t pixma_t;
+typedef struct pixma_scan_ops_t pixma_scan_ops_t;
+typedef struct pixma_scan_param_t pixma_scan_param_t;
+typedef struct pixma_config_t pixma_config_t;
+typedef struct pixma_cmdbuf_t pixma_cmdbuf_t;
+typedef struct pixma_imagebuf_t pixma_imagebuf_t;
+typedef struct pixma_device_status_t pixma_device_status_t;
+
+
+/** \addtogroup API
+ * @{ */
+/** String index constants */
+typedef enum pixma_string_index_t
+{
+ PIXMA_STRING_MODEL,
+ PIXMA_STRING_ID,
+ PIXMA_STRING_LAST
+} pixma_string_index_t;
+
+/** Paper sources */
+typedef enum pixma_paper_source_t
+{
+ PIXMA_SOURCE_FLATBED,
+ PIXMA_SOURCE_ADF,
+ PIXMA_SOURCE_TPU,
+ PIXMA_SOURCE_ADFDUP /* duplex */
+} pixma_paper_source_t;
+
+/** Scan modes */
+typedef enum pixma_scan_mode_t
+{
+ /* standard scan modes */
+ PIXMA_SCAN_MODE_COLOR,
+ PIXMA_SCAN_MODE_GRAY,
+ /* TPU scan modes for negatives */
+ PIXMA_SCAN_MODE_NEGATIVE_COLOR,
+ PIXMA_SCAN_MODE_NEGATIVE_GRAY,
+ /* extended scan modes for 48 bit flatbed scanners */
+ PIXMA_SCAN_MODE_COLOR_48,
+ PIXMA_SCAN_MODE_GRAY_16,
+ /* 1 bit lineart scan mode */
+ PIXMA_SCAN_MODE_LINEART,
+ /* TPUIR scan mode */
+ PIXMA_SCAN_MODE_TPUIR
+} pixma_scan_mode_t;
+
+typedef enum pixma_hardware_status_t
+{
+ PIXMA_HARDWARE_OK,
+ PIXMA_HARDWARE_ERROR
+} pixma_hardware_status_t;
+
+typedef enum pixma_lamp_status_t
+{
+ PIXMA_LAMP_OK,
+ PIXMA_LAMP_WARMING_UP,
+ PIXMA_LAMP_OFF,
+ PIXMA_LAMP_ERROR
+} pixma_lamp_status_t;
+
+typedef enum pixma_adf_status_t
+{
+ PIXMA_ADF_OK,
+ PIXMA_ADF_NO_PAPER,
+ PIXMA_ADF_JAMMED,
+ PIXMA_ADF_COVER_OPEN,
+ PIXMA_ADF_ERROR
+} pixma_adf_status_t;
+
+typedef enum pixma_calibration_status_t
+{
+ PIXMA_CALIBRATION_OK,
+ PIXMA_CALIBRATION_IN_PROGRESS,
+ PIXMA_CALIBRATION_OFF,
+ PIXMA_CALIBRATION_ERROR
+} pixma_calibration_status_t;
+
+/** Device status. */
+struct pixma_device_status_t
+{
+ pixma_hardware_status_t hardware;
+ pixma_lamp_status_t lamp;
+ pixma_adf_status_t adf;
+ pixma_calibration_status_t cal;
+};
+
+/** Scan parameters. */
+struct pixma_scan_param_t
+{
+ /** Size in bytes of one image line (row).
+ * line_size >= depth / 8 * channels * w <br>
+ * This field will be set by pixma_check_scan_param(). */
+ uint64_t line_size;
+
+ /** Size in bytes of the whole image.
+ * image_size = line_size * h <br>
+ * This field will be set by pixma_check_scan_param(). */
+ uint64_t image_size;
+
+ /** Channels per pixel. 1 = grayscale and lineart, 3 = color */
+ unsigned channels;
+
+ /** Bits per channels.
+ * 1 = 1 bit B/W lineart (flatbed)
+ * 8 = 8 bit grayscale,
+ * 24 bit color (both flatbed)
+ * 16 = 16 bit grayscale (TPU, flatbed not implemeted),
+ * 48 bit color (TPU, flatbed not implemented) */
+ unsigned depth;
+
+ /*@{ */
+ /** Resolution. Valid values are 75,150,300,600,1200... */
+ unsigned xdpi, ydpi;
+ /*@} */
+
+ /*! \name Scan area in pixels
+ * (0,0) = top left; positive x extends to the right; positive y to the
+ * bottom; in pixels.
+ * xs is the offset in x direction of the selected scan range relative
+ * to the range read from the scanner and wx the width in x direction
+ * of the scan line read from scanner. */
+ /*@{ */
+ unsigned x, y, w, h, xs, wx;
+ /*@} */
+
+ /** Flag indicating whether the offset correction for TPU scans
+ * was already performed (to avoid repeated corrections).
+ * Currently only used in pixma_mp800.c sub-driver */
+ unsigned tpu_offset_added;
+
+ /* Flag indicating if data from scanner will be in JPEG format */
+ unsigned mode_jpeg;
+
+ /** Flag indicating whether a software-lineart scan is in progress
+ * 0 = other scan
+ * 1 = software-lineart scan */
+ unsigned software_lineart;
+
+ /** Threshold for software-lineart scans */
+ unsigned threshold;
+
+ /** lineart threshold curve for dynamic rasterization */
+ unsigned threshold_curve;
+
+ /* look up table used in dynamic rasterization */
+ unsigned char lineart_lut[256];
+
+ /** Gamma table. 4096 entries, 12 bit => 8 bit. If \c NULL, default gamma
+ * specified by subdriver will be used. */
+ const uint8_t *gamma_table;
+
+ /** \see #pixma_paper_source_t */
+ pixma_paper_source_t source;
+
+ /** \see #pixma_scan_mode_t */
+ pixma_scan_mode_t mode;
+
+ /** The current page # in the same ADF scan session, 0 in non ADF */
+ unsigned adf_pageid;
+
+ /** adf-wait */
+ unsigned adf_wait;
+ unsigned frontend_cancel;
+};
+
+/** PIXMA model information */
+struct pixma_config_t
+{
+ /* If you change this structure, don't forget to update the device list in
+ * subdrivers. */
+ const char *name; /**< Model name. */
+ const char *model; /**< Short model */
+ uint16_t vid; /**< USB Vendor ID */
+ uint16_t pid; /**< USB Product ID */
+ unsigned iface; /**< USB Interface number */
+ const pixma_scan_ops_t *ops; /**< Subdriver ops */
+ unsigned min_xdpi; /**< Minimum horizontal resolution[DPI] */
+ unsigned xdpi; /**< Maximum horizontal resolution[DPI] */
+ unsigned ydpi; /**< Maximum vertical resolution[DPI] */
+ unsigned adftpu_min_dpi; /**< Maximum horizontal resolution[DPI] for adf/tpu
+ * only needed if ADF/TPU has another min. dpi value than 75 dpi */
+ unsigned adftpu_max_dpi; /**< Maximum vertical resolution[DPI] for adf/tpu
+ * only needed if ADF/TPU has another max. dpi value than xdpi */
+ unsigned tpuir_min_dpi; /**< Minimum resolution[DPI] for tpu-ir
+ * only needed if TPU-IR has another min. dpi value than 75 dpi */
+ unsigned tpuir_max_dpi; /**< Maximum resolution[DPI] for tpu-ir
+ * only needed if TPU-IR has another max. dpi value than xdpi */
+ unsigned width; /**< Maximum width of scannable area in pixels at 75DPI */
+ unsigned height; /**< Maximum height of scannable area in pixels at 75DPI */
+ unsigned cap; /**< Capability bitfield \see PIXMA_CAP_* */
+};
+
+
+/* Defined in pixma_common.c */
+
+/** Initialize the driver. It must be called before any other functions
+ * except pixma_set_debug_level(). */
+int pixma_init (void);
+
+/** Free resources allocated by the driver. */
+void pixma_cleanup (void);
+
+/** Set the debug level.
+ * \param[in] level the debug level
+ * - 0 No debug output at all
+ * - 1 Only errors and warning
+ * - 2 General information
+ * - 3 Debugging messages
+ * - 10 USB traffic dump */
+void pixma_set_debug_level (int level);
+
+/** Find scanners. The device number used in pixma_open(),
+ * pixma_get_device_model(), pixma_get_device_id() and
+ * pixma_get_device_config() must be less than the value returned by the last
+ * call of this function.
+ *
+ * \return The number of scanners found currently. The return value is
+ * guaranteed to be valid until the next call to pixma_find_scanners(). */
+int pixma_find_scanners (const char **conf_devices, SANE_Bool local_only);
+
+/** Return the model name of the device \a devnr. */
+const char *pixma_get_device_model (unsigned devnr);
+
+/** Return the unique ID of the device \a devnr. */
+const char *pixma_get_device_id (unsigned devnr);
+
+/** Return the device configuration of the device \a devnr. */
+const struct pixma_config_t *pixma_get_device_config (unsigned devnr);
+
+/** Open a connection to the scanner \a devnr.
+ * \param[in] devnr The scanner number
+ * \param[out] handle The device handle
+ * \see pixma_find_scanners() */
+int pixma_open (unsigned devnr, pixma_t ** handle);
+
+/** Close the connection to the scanner. The scanning process is aborted
+ * if necessary before the function returns. */
+void pixma_close (pixma_t * s);
+
+/** Initiate an image acquisition process. You must keep \a sp valid until the
+ * image acquisition process has finished. */
+int pixma_scan (pixma_t *, pixma_scan_param_t * sp);
+
+/** Read a block of image data. It blocks until there is at least one byte
+ * available or an error occurs.
+ *
+ * \param[out] buf Pointer to the buffer
+ * \param[in] len Size of the buffer
+ *
+ * \retval count Number of bytes written to the buffer or error. Possible
+ * return value:
+ * - count = 0 for end of image
+ * - count = \a len
+ * - 0 < count < \a len if and only if it is the last block.
+ * - count < 0 for error */
+int pixma_read_image (pixma_t *, void *buf, unsigned len);
+
+#if 0
+/** Read a block of image data and write to \a fd.
+ * \param[in] fd output file descriptor
+ * \see pixma_read_image() */
+int pixma_read_image_write (pixma_t *, int fd);
+#endif
+
+/** Cancel the scanning process. No effect if no scanning process is in
+ * progress. It can be called asynchronously e.g. within a signal
+ * handle. pixma_cancel() doesn't abort the operation immediately. It
+ * guarantees that the current call or, at the latest, the next call to
+ * pixma_read_image() will return zero or an error (probably PIXMA_ECANCELED). */
+void pixma_cancel (pixma_t *);
+
+/** Check the scan parameters. This function can change your parameters to
+ * match the device capability, e.g. adjust width and height to the available
+ * area.
+ * \return PIXMA_EINVAL for invalid parameters. */
+int pixma_check_scan_param (pixma_t *, pixma_scan_param_t *);
+
+/** Wait until a scanner button is pressed or it times out. It should not be
+ * called during image acquisition is in progress.
+ * \param[in] timeout in milliseconds, less than 0 means forever
+ * \return
+ * - \c PIXMA_EV_NONE if it timed out.
+ * - non-zero value indicates which button was pressed.
+ * \see PIXMA_EV_*
+ */
+uint32_t pixma_wait_event (pixma_t *, int timeout);
+
+/** Activate connection to scanner */
+int pixma_activate_connection (pixma_t *);
+
+/** De-activate connection to scanner */
+
+int pixma_deactivate_connection (pixma_t *);
+
+
+/** Enable or disable background tasks. Currently, the only one task
+ * is submitting interrupt URB in background.
+ * \param[in] enabled if not zero, enable background task.
+ * \see pixma_set_interrupt_mode() */
+int pixma_enable_background (pixma_t *, int enabled);
+
+/** Read the current device status.
+ * \param[out] status the current device status
+ * \return 0 if succeeded. Otherwise, failed.
+ */
+int pixma_get_device_status (pixma_t *, pixma_device_status_t * status);
+
+const char *pixma_get_string (pixma_t *, pixma_string_index_t);
+const pixma_config_t *pixma_get_config (pixma_t *);
+void pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n);
+const char *pixma_strerror (int error);
+
+/** @} end of API group */
+
+#endif
diff --git a/backend/pixma/pixma_bjnp.c b/backend/pixma/pixma_bjnp.c
new file mode 100644
index 0000000..34ba918
--- /dev/null
+++ b/backend/pixma/pixma_bjnp.c
@@ -0,0 +1,2645 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2008 2012 by Louis Lagendijk
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#undef BACKEND_NAME
+#define BACKEND_NAME bjnp
+
+#include "../include/sane/config.h"
+#include "../include/sane/sane.h"
+
+/*
+ * Standard types etc
+ */
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <unistd.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/*
+ * networking stuff
+ */
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <net/if.h>
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "pixma_bjnp_private.h"
+#include "pixma_bjnp.h"
+/* #include "pixma_rename.h" */
+#include "pixma.h"
+#include "pixma_common.h"
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX LONG_MAX
+#endif
+
+/* static data */
+static bjnp_device_t device[BJNP_NO_DEVICES];
+static int bjnp_no_devices = 0;
+
+/*
+ * Private functions
+ */
+
+static const struct pixma_config_t *lookup_scanner(const char *makemodel,
+ const struct pixma_config_t *const pixma_devices[])
+{
+ int i;
+ const struct pixma_config_t *cfg;
+ char *match;
+
+ for (i = 0; pixma_devices[i]; i++)
+ {
+ /* loop through the device classes (mp150, mp730 etc) */
+ for (cfg = pixma_devices[i]; cfg->name; cfg++)
+ {
+ /* loop through devices in class */
+ PDBG( bjnp_dbg( LOG_DEBUG3, "lookup_scanner: Checking for %s in %s\n", makemodel, cfg->model));
+ if ((match = strcasestr (makemodel, cfg->model)) != NULL)
+ {
+ /* possible match found, make sure it is not a partial match */
+ /* MP600 and MP600R are different models! */
+ /* some models contain ranges, so check for a '-' too */
+
+ if ((match[strlen(cfg->model)] == ' ') ||
+ (match[strlen(cfg->model)] == '\0') ||
+ (match[strlen(cfg->model)] == '-'))
+ {
+ PDBG( bjnp_dbg (LOG_DEBUG, "lookup_scanner: Scanner model found: Name %s(%s) matches %s\n", cfg->model, cfg->name, makemodel));
+ return cfg;
+ }
+ }
+ }
+ }
+ PDBG( bjnp_dbg (LOG_DEBUG, "lookup_scanner: Scanner model %s not found, giving up!\n", makemodel));
+ return NULL;
+}
+
+static void
+u8tohex (char *string, const uint8_t *value, int len )
+{
+ int i;
+ int x;
+ const char hdigit[16] =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+ 'e', 'f'
+ };
+ for (i = 0; i < len; i++)
+ {
+ x = value[i];
+ string[ 2 * i ] = hdigit[(x >> 4) & 0xf];
+ string[ 2 * i + 1] = hdigit[x & 0xf];
+ }
+ string[2 * len ] = '\0';
+}
+
+static void
+u32tohex (uint32_t x, char *str)
+{
+ uint8_t uint8[4];
+ uint8[0] = (uint8_t)(x >> 24);
+ uint8[1] = (uint8_t)(x >> 16);
+ uint8[2] = (uint8_t)(x >> 8);
+ uint8[3] = (uint8_t)x ;
+ u8tohex(str, uint8, 4);
+}
+
+static void
+bjnp_hexdump (int level, const void *d_, unsigned len)
+{
+ const uint8_t *d = (const uint8_t *) (d_);
+ unsigned ofs, c, plen;
+ char line[100]; /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */
+
+ if (level > DBG_LEVEL)
+ return;
+ if (level == DBG_LEVEL)
+ /* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */
+ plen = (len > 64) ? 32: len;
+ else
+ plen = len;
+ ofs = 0;
+ while (ofs < plen)
+ {
+ char *p;
+ line[0] = ' ';
+ u32tohex (ofs, line + 1);
+ line[9] = ':';
+ p = line + 10;
+ for (c = 0; c != 16 && (ofs + c) < plen; c++)
+ {
+ u8tohex (p, d + ofs + c, 1);
+ p[2] = ' ';
+ p += 3;
+ if (c == 7)
+ {
+ p[0] = ' ';
+ p++;
+ }
+ }
+ p[0] = '\0';
+ bjnp_dbg (level, "%s\n", line);
+ ofs += c;
+ }
+ if (len > plen)
+ bjnp_dbg(level, "......\n");
+}
+
+static int sa_is_equal( const bjnp_sockaddr_t * sa1, const bjnp_sockaddr_t * sa2)
+{
+ if ((sa1 == NULL) || (sa2 == NULL) )
+ return 0;
+
+ if (sa1->addr.sa_family == sa2-> addr.sa_family)
+ {
+ if( sa1 -> addr.sa_family == AF_INET)
+ {
+ if ( (sa1->ipv4.sin_port == sa2->ipv4.sin_port) &&
+ (sa1->ipv4.sin_addr.s_addr == sa2->ipv4.sin_addr.s_addr))
+ {
+ return 1;
+ }
+ }
+#ifdef ENABLE_IPV6
+ else if (sa1 -> addr.sa_family == AF_INET6 )
+ {
+ if ( (sa1-> ipv6.sin6_port == sa2->ipv6.sin6_port) &&
+ (memcmp(&(sa1->ipv6.sin6_addr), &(sa2->ipv6.sin6_addr), sizeof(struct in6_addr)) == 0))
+ {
+ return 1;
+ }
+ }
+#endif
+ }
+ return 0;
+}
+
+static int
+sa_size( const bjnp_sockaddr_t *sa)
+{
+ switch (sa -> addr.sa_family)
+ {
+ case AF_INET:
+ return (sizeof(struct sockaddr_in) );
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ return (sizeof(struct sockaddr_in6) );
+#endif
+ default:
+ /* should not occur */
+ return sizeof( bjnp_sockaddr_t );
+ }
+}
+
+static int
+get_protocol_family( const bjnp_sockaddr_t *sa)
+{
+ switch (sa -> addr.sa_family)
+ {
+ case AF_INET:
+ return PF_INET;
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ return PF_INET6;
+ break;
+#endif
+ default:
+ /* should not occur */
+ return -1;
+ }
+}
+
+static void
+get_address_info ( const bjnp_sockaddr_t *addr, char * addr_string, int *port)
+{
+ char tmp_addr[BJNP_HOST_MAX];
+ if ( addr->addr.sa_family == AF_INET)
+ {
+ inet_ntop( AF_INET, &(addr -> ipv4.sin_addr.s_addr), addr_string, BJNP_HOST_MAX);
+ *port = ntohs (addr->ipv4.sin_port);
+ }
+#ifdef ENABLE_IPV6
+ else if (addr->addr.sa_family == AF_INET6)
+ {
+ inet_ntop( AF_INET6, addr -> ipv6.sin6_addr.s6_addr, tmp_addr, sizeof(tmp_addr) );
+
+ if (IN6_IS_ADDR_LINKLOCAL( &(addr -> ipv6.sin6_addr) ) )
+ sprintf(addr_string, "[%s%%%d]", tmp_addr, addr -> ipv6.sin6_scope_id);
+
+ *port = ntohs (addr->ipv6.sin6_port);
+ }
+#endif
+ else
+ {
+ /* unknown address family, should not occur */
+ strcpy(addr_string, "Unknown address family");
+ *port = 0;
+ }
+}
+
+static int
+parse_IEEE1284_to_model (char *scanner_id, char *model)
+{
+/*
+ * parses the IEEE1284 ID of the scanner to retrieve make and model
+ * of the scanner
+ * Returns: 0 = not found
+ * 1 = found, model is set
+ */
+
+ char s[BJNP_IEEE1284_MAX];
+ char *tok;
+ char * model_str;
+
+ strncpy (s, scanner_id, BJNP_IEEE1284_MAX);
+ s[BJNP_IEEE1284_MAX - 1] = '\0';
+ model[0] = '\0';
+
+ tok = strtok (s, ";");
+ while (tok != NULL)
+ {
+ /* MDL contains make and model */
+
+ if (strncmp (tok, "MDL:", strlen("MDL:")) == 0)
+ {
+ model_str = tok + strlen("MDL:");
+ strncpy (model, model_str, BJNP_MODEL_MAX);
+ model[BJNP_MODEL_MAX -1] = '\0';
+ return 1;
+ }
+ tok = strtok (NULL, ";");
+ }
+ return 0;
+}
+
+static int
+charTo2byte (char *d, const char *s, int len)
+{
+ /*
+ * copy ASCII string to UTF-16 unicode string
+ * len is length of destination buffer
+ * Returns: number of characters copied
+ */
+
+ int done = 0;
+ int copied = 0;
+ int i;
+
+ len = len / 2;
+ for (i = 0; i < len; i++)
+ {
+ d[2 * i] = '\0';
+ if (s[i] == '\0')
+ {
+ done = 1;
+ }
+ if (done == 0)
+ {
+ d[2 * i + 1] = s[i];
+ copied++;
+ }
+ else
+ d[2 * i + 1] = '\0';
+ }
+ return copied;
+}
+
+static bjnp_protocol_defs_t *get_protocol_by_method( char *method)
+{
+ int i = 0;
+ while ( bjnp_protocol_defs[i].method_string != NULL)
+ {
+ if (strcmp(method, bjnp_protocol_defs[i].method_string) == 0)
+ {
+ return &bjnp_protocol_defs[i];
+ }
+ i++;
+ }
+ return NULL;
+}
+
+static bjnp_protocol_defs_t *get_protocol_by_proto_string( char *proto_string)
+{
+ int i = 0;
+ while ( bjnp_protocol_defs[i].proto_string != NULL)
+ {
+ if (strncmp(proto_string, bjnp_protocol_defs[i].proto_string, 4) == 0)
+ {
+ return &bjnp_protocol_defs[i];
+ }
+ i++;
+ }
+ return NULL;
+}
+
+static char *
+getusername (void)
+{
+ static char noname[] = "sane_pixma";
+ struct passwd *pwdent;
+
+#ifdef HAVE_PWD_H
+ if (((pwdent = getpwuid (geteuid ())) != NULL) && (pwdent->pw_name != NULL))
+ return pwdent->pw_name;
+#endif
+ return noname;
+}
+
+
+static char *
+determine_scanner_serial (const char *hostname, const char * mac_address, char *serial)
+{
+ char *dot;
+ char copy[BJNP_HOST_MAX];
+
+ /* determine a "serial number" for the scanner */
+ /* if available we use the hostname or ipv4 address of the printer */
+ /* if we only have a literal ipv6 address, we use the mac-address */
+
+ strcpy(copy, hostname);
+ if (strlen (copy) >= SERIAL_MAX)
+ {
+ /* make the string fit into the serial */
+ /* if this is a FQDN, not an ip-address, remove domain part of the name */
+ if ((dot = strchr (copy, '.')) != NULL)
+ {
+ *dot = '\0';
+ }
+ }
+ /* check if name is still to long. If so use the mac-address */
+ if (strlen(copy) >= SERIAL_MAX)
+ {
+ strcpy(copy, mac_address);
+ }
+ strcpy( serial, copy );
+ return serial;
+}
+
+static int
+bjnp_open_tcp (int devno)
+{
+ int sock;
+ int val;
+ bjnp_sockaddr_t *addr = device[devno].addr;
+ char host[BJNP_HOST_MAX];
+ int port;
+ int connect_timeout = BJNP_TIMEOUT_TCP_CONNECT;
+
+ get_address_info( addr, host, &port);
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_open_tcp: Setting up a TCP socket, dest: %s port %d\n",
+ host, port ) );
+
+ if ((sock = socket (get_protocol_family( addr ) , SOCK_STREAM, 0)) < 0)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT, "bjnp_open_tcp: ERROR - Can not create socket: %s\n",
+ strerror (errno)));
+ return -1;
+ }
+
+ val = 1;
+ setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val));
+
+#if 0
+ val = 1;
+ setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, &val, sizeof (val));
+
+ val = 1;
+#endif
+
+ /*
+ * Using TCP_NODELAY improves responsiveness, especially on systems
+ * with a slow loopback interface...
+ */
+
+ val = 1;
+ setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof (val));
+
+/*
+ * Close this socket when starting another process...
+ */
+
+ fcntl (sock, F_SETFD, FD_CLOEXEC);
+
+ while (connect_timeout > 0)
+ {
+ if (connect
+ (sock, &(addr->addr), sa_size(device[devno].addr)) == 0)
+ {
+ device[devno].tcp_socket = sock;
+ return 0;
+ }
+ PDBG (bjnp_dbg( LOG_INFO, "bjnp_open_tcp: INFO - Can not yet connect over TCP to scanner: %s, retrying\n",
+ strerror(errno)));
+ usleep(BJNP_TCP_CONNECT_INTERVAL * BJNP_USLEEP_MS);
+ connect_timeout = connect_timeout - BJNP_TCP_CONNECT_INTERVAL;
+ }
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_open_tcp: ERROR - Can not connect to scanner, giving up!"));
+ return -1;
+}
+
+static int
+split_uri (const char *devname, char *method, char *host, char *port,
+ char *args)
+{
+ char copy[1024];
+ char *start;
+ char next;
+ int i;
+
+ strncpy (copy, devname, 1024);
+ copy[1023] = '\0';
+ start = copy;
+
+/*
+ * retrieve method
+ */
+ i = 0;
+ while ((start[i] != '\0') && (start[i] != ':'))
+ {
+ i++;
+ }
+
+ if (((strncmp (start + i, "://", 3) != 0)) || (i > BJNP_METHOD_MAX -1 ))
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find method in %s (offset %d)\n",
+ devname, i));
+ return -1;
+ }
+
+ start[i] = '\0';
+ strcpy (method, start);
+ start = start + i + 3;
+
+/*
+ * retrieve host
+ */
+
+ if (start[0] == '[')
+ {
+ /* literal IPv6 address */
+
+ char *end_of_address = strchr(start, ']');
+
+ if ( ( end_of_address == NULL) ||
+ ( (end_of_address[1] != ':') && (end_of_address[1] != '/' ) && (end_of_address[1] != '\0' )) ||
+ ( (end_of_address - start) >= BJNP_HOST_MAX ) )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find hostname or address in %s\n", devname));
+ return -1;
+ }
+ next = end_of_address[1];
+ *end_of_address = '\0';
+ strcpy(host, start + 1);
+ start = end_of_address + 2;
+ }
+ else
+ {
+ i = 0;
+ while ((start[i] != '\0') && (start[i] != '/') && (start[i] != ':'))
+ {
+ i++;
+ }
+ next = start[i];
+ start[i] = '\0';
+ if ((i == 0) || (i >= BJNP_HOST_MAX ) )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find hostname or address in %s\n", devname));
+ return -1;
+ }
+ strcpy (host, start);
+ start = start + i +1;
+ }
+
+
+/*
+ * retrieve port number
+ */
+
+ if (next != ':')
+ strcpy(port, "");
+ else
+ {
+ char *end_of_port = strchr(start, '/');
+ if (end_of_port == NULL)
+ {
+ next = '\0';
+ }
+ else
+ {
+ next = *end_of_port;
+ *end_of_port = '\0';
+ }
+ if ((strlen(start) == 0) || (strlen(start) >= BJNP_PORT_MAX ) )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Can not find port in %s (have \"%s\")\n", devname, start));
+ return -1;
+ }
+ strcpy(port, start);
+ start = end_of_port + 1;
+ }
+
+/*
+ * Retrieve arguments
+ */
+
+ if (next == '/')
+ {
+ i = strlen(start);
+ if ( i >= BJNP_ARGS_MAX)
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "split_uri: ERROR - Argument string too long in %s\n", devname));
+ }
+ strcpy (args, start);
+ }
+ else
+ strcpy (args, "");
+ return 0;
+}
+
+
+
+static void
+set_cmd_from_string (char* protocol_string, struct BJNP_command *cmd, char cmd_code, int payload_len)
+{
+ /*
+ * Set command buffer with command code, session_id and length of payload
+ * Returns: sequence number of command
+ */
+
+ memcpy (cmd->BJNP_id, protocol_string, sizeof (cmd->BJNP_id));
+ cmd->dev_type = BJNP_CMD_SCAN;
+ cmd->cmd_code = cmd_code;
+ cmd->unknown1 = htons (0);
+
+ /* device not yet opened, use 0 for serial and session) */
+ cmd->seq_no = htons (0);
+ cmd->session_id = htons (0);
+ cmd->payload_len = htonl (payload_len);
+}
+
+static void
+set_cmd_for_dev (int devno, struct BJNP_command *cmd, char cmd_code, int payload_len)
+{
+ /*
+ * Set command buffer with command code, session_id and length of payload
+ * Returns: sequence number of command
+ */
+
+ memcpy(cmd->BJNP_id, device[devno].protocol_string, sizeof (cmd->BJNP_id));
+ cmd->dev_type = BJNP_CMD_SCAN;
+ cmd->cmd_code = cmd_code;
+ cmd->unknown1 = htons (0);
+ cmd->seq_no = htons (++(device[devno].serial));
+ cmd->session_id = (cmd_code == CMD_UDP_POLL ) ? 0 : htons (device[devno].session_id);
+ device[devno].last_cmd = cmd_code;
+ cmd->payload_len = htonl (payload_len);
+}
+
+static int
+bjnp_setup_udp_socket ( const int dev_no )
+{
+ /*
+ * Setup a udp socket for the given device
+ * Returns the socket or -1 in case of error
+ */
+
+ int sockfd;
+ char addr_string[256];
+ int port;
+ bjnp_sockaddr_t * addr = device[dev_no].addr;
+
+ get_address_info( addr, addr_string, &port);
+
+ PDBG (bjnp_dbg (LOG_DEBUG, "setup_udp_socket: Setting up a UDP socket, dest: %s port %d\n",
+ addr_string, port ) );
+
+ if ((sockfd = socket (get_protocol_family( addr ), SOCK_DGRAM, IPPROTO_UDP)) == -1)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "setup_udp_socket: ERROR - can not open socket - %s\n",
+ strerror (errno)));
+ return -1;
+ }
+
+ if (connect
+ (sockfd, &(device[dev_no].addr->addr), sa_size(device[dev_no].addr) )!= 0)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "setup_udp_socket: ERROR - connect failed- %s\n",
+ strerror (errno)));
+ close(sockfd);
+ return -1;
+ }
+ return sockfd;
+}
+
+static int
+udp_command (const int dev_no, char *command, int cmd_len, char *response,
+ int resp_len)
+{
+ /*
+ * send udp command to given device and recieve the response`
+ * returns: the legth of the response or -1
+ */
+ int sockfd;
+ struct timeval timeout;
+ int result;
+ int try, attempt;
+ int numbytes;
+ fd_set fdset;
+ struct BJNP_command *resp = (struct BJNP_command *) response;
+ struct BJNP_command *cmd = (struct BJNP_command *) command;
+
+ if ( (sockfd = bjnp_setup_udp_socket(dev_no) ) == -1 )
+ {
+ PDBG (bjnp_dbg( LOG_CRIT, "udp_command: ERROR - Can not setup socket\n") );
+ return -1;
+ }
+
+ for (try = 0; try < BJNP_UDP_RETRY_MAX; try++)
+ {
+ if ((numbytes = send (sockfd, command, cmd_len, 0)) != cmd_len)
+ {
+ PDBG (bjnp_dbg
+ (LOG_NOTICE, "udp_command: ERROR - Sent %d bytes, expected %d\n",
+ numbytes, cmd_len));
+ continue;
+ }
+
+ attempt = 0;
+
+ /* wait for data to be received, ignore signals being received */
+ /* skip late udp responses (they have an incorrect sequence number */
+ do
+ {
+ FD_ZERO (&fdset);
+ FD_SET (sockfd, &fdset);
+
+ timeout.tv_sec = device[dev_no].bjnp_ip_timeout /1000;
+ timeout.tv_usec = device[dev_no].bjnp_ip_timeout %1000;
+ }
+ while (((result =
+ select (sockfd + 1, &fdset, NULL, NULL, &timeout)) <= 0)
+ && (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS)
+ && resp-> seq_no != cmd->seq_no);
+
+ if (result <= 0)
+ {
+ PDBG (bjnp_dbg
+ (LOG_NOTICE, "udp_command: ERROR - select failed: %s\n",
+ result == 0 ? "timed out" : strerror (errno)));
+ continue;
+ }
+
+ if ((numbytes = recv (sockfd, response, resp_len, 0)) == -1)
+ {
+ PDBG (bjnp_dbg
+ (LOG_NOTICE, "udp_command: ERROR - recv failed: %s",
+ strerror (errno)));
+ continue;
+ }
+ close(sockfd);
+ return numbytes;
+ }
+
+ /* no response even after retry */
+
+ close(sockfd);
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "udp_command: ERROR - no data received (timeout = %d)\n", device[dev_no].bjnp_ip_timeout ) );
+ return -1;
+}
+
+static int
+get_scanner_id (const int dev_no, char *model)
+{
+ /*
+ * get scanner identity
+ * Sets model (make and model)
+ * Return 0 on success, -1 in case of errors
+ */
+
+ struct BJNP_command cmd;
+ struct IDENTITY *id;
+ char scanner_id[BJNP_IEEE1284_MAX];
+ int resp_len;
+ char resp_buf[BJNP_RESP_MAX];
+ int id_len;
+
+ /* set defaults */
+
+ strcpy (model, "Unidentified scanner");
+
+ set_cmd_for_dev (dev_no, &cmd, CMD_UDP_GET_ID, 0);
+
+ PDBG (bjnp_dbg (LOG_DEBUG2, "get_scanner_id: Get scanner identity\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &cmd,
+ sizeof (struct BJNP_command)));
+
+ if ( ( resp_len = udp_command (dev_no, (char *) &cmd, sizeof (struct BJNP_command),
+ resp_buf, BJNP_RESP_MAX) ) < (int)sizeof(struct BJNP_command) )
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG, "get_scanner_id: ERROR - Failed to retrieve scanner identity:\n"));
+ return -1;
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG2, "get_scanner_id: scanner identity:\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len));
+
+ id = (struct IDENTITY *) resp_buf;
+
+ if (device[dev_no].protocol == PROTOCOL_BJNP)
+ {
+ id_len = MIN(ntohl( id-> cmd.payload_len ) - sizeof(id-> payload.bjnp.id_len), BJNP_IEEE1284_MAX);
+ strncpy(scanner_id, id->payload.bjnp.id, id_len);
+ scanner_id[id_len] = '\0';
+ }
+ else
+ {
+ id_len = MIN(ntohl( id-> cmd.payload_len ), BJNP_IEEE1284_MAX);
+ strncpy(scanner_id, id->payload.mfnp.id, id_len);
+ scanner_id[id_len] = '\0';
+ }
+ PDBG (bjnp_dbg (LOG_INFO, "get_scanner_id: Scanner identity string = %s - length = %d\n", scanner_id, id_len));
+
+ /* get make&model from IEEE1284 id */
+
+ if (model != NULL)
+ {
+ parse_IEEE1284_to_model (scanner_id, model);
+ PDBG (bjnp_dbg (LOG_INFO, "get_scanner_id: Scanner model = %s\n", model));
+ }
+ return 0;
+}
+
+static int
+get_scanner_name(const bjnp_sockaddr_t *scanner_sa, char *host)
+{
+ /*
+ * Parse identify command responses to ip-address
+ * and hostname. Return qulity of the address
+ */
+
+ struct addrinfo *results;
+ struct addrinfo *result;
+ char ip_address[BJNP_HOST_MAX];
+ int port;
+ int error;
+ int match = 0;
+ int level;
+ char service[64];
+
+#ifdef ENABLE_IPV6
+ if ( ( scanner_sa -> addr.sa_family == AF_INET6 ) &&
+ ( IN6_IS_ADDR_LINKLOCAL( &(scanner_sa -> ipv6.sin6_addr ) ) ) )
+ level = BJNP_ADDRESS_IS_LINK_LOCAL;
+ else
+#endif
+ level = BJNP_ADDRESS_IS_GLOBAL;
+
+ get_address_info( scanner_sa, ip_address, &port );
+
+ /* do reverse name lookup, if hostname can not be found return ip-address */
+
+ if( (error = getnameinfo( &(scanner_sa -> addr) , sa_size( scanner_sa),
+ host, BJNP_HOST_MAX , NULL, 0, NI_NAMEREQD) ) != 0 )
+ {
+ PDBG (bjnp_dbg(LOG_INFO, "get_scanner_name: Name for %s not found : %s\n",
+ ip_address, gai_strerror(error) ) );
+ strcpy(host, ip_address);
+ return level;
+ }
+ else
+ {
+ sprintf(service, "%d", port);
+ /* some buggy routers return rubbish if reverse lookup fails, so
+ * we do a forward lookup on the received name to see if the result matches */
+
+ if (getaddrinfo(host , service, NULL, &results) == 0)
+ {
+ result = results;
+
+ while (result != NULL)
+ {
+ if(sa_is_equal( scanner_sa, (bjnp_sockaddr_t *)result-> ai_addr))
+ {
+ /* found match, good */
+ PDBG (bjnp_dbg (LOG_INFO,
+ "get_scanner_name: Forward lookup for %s succeeded, using as hostname\n", host));
+ match = 1;
+ level = BJNP_ADDRESS_HAS_FQDN;
+ break;
+ }
+ result = result-> ai_next;
+ }
+ freeaddrinfo(results);
+
+ if (match != 1)
+ {
+ PDBG (bjnp_dbg (LOG_INFO,
+ "get_scanner_name: Forward lookup for %s succeeded, IP-address does not match, using IP-address %s instead\n",
+ host, ip_address));
+ strcpy (host, ip_address);
+ }
+ }
+ else
+ {
+ /* forward lookup failed, use ip-address */
+ PDBG ( bjnp_dbg (LOG_INFO, "get_scanner_name: Forward lookup of %s failed, using IP-address", ip_address));
+ strcpy (host, ip_address);
+ }
+ }
+ return level;
+}
+
+static int
+get_port_from_sa(const bjnp_sockaddr_t scanner_sa)
+{
+#ifdef ENABLE_IPV6
+ if ( scanner_sa.addr.sa_family == AF_INET6 )
+ {
+ return ntohs(scanner_sa.ipv6.sin6_port);
+ }
+ else
+#endif
+ if ( scanner_sa.addr.sa_family == AF_INET )
+ {
+ return ntohs(scanner_sa.ipv4.sin_port);
+ }
+ return -1;
+}
+
+static int create_broadcast_socket( const bjnp_sockaddr_t * local_addr )
+{
+ int sockfd = -1;
+ int broadcast = 1;
+ int ipv6_v6only = 1;
+
+
+ if ((sockfd = socket (local_addr-> addr.sa_family, SOCK_DGRAM, 0)) == -1)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "create_broadcast_socket: ERROR - can not open socket - %s",
+ strerror (errno)));
+ return -1;
+ }
+
+ /* Set broadcast flag on socket */
+
+ if (setsockopt
+ (sockfd, SOL_SOCKET, SO_BROADCAST, (const char *) &broadcast,
+ sizeof (broadcast)) != 0)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "create_broadcast_socket: ERROR - setting socket option SO_BROADCAST failed - %s",
+ strerror (errno)));
+ close (sockfd);
+ return -1;
+ };
+
+ /* For an IPv6 socket, bind to v6 only so a V6 socket can co-exist with a v4 socket */
+ if ( (local_addr -> addr.sa_family == AF_INET6) && ( setsockopt
+ (sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &ipv6_v6only,
+ sizeof (ipv6_v6only)) != 0) )
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "create_broadcast_socket: ERROR - setting socket option IPV6_V6ONLY failed - %s",
+ strerror (errno)));
+ close (sockfd);
+ return -1;
+ };
+
+ if (bind
+ (sockfd, &(local_addr->addr),
+ (socklen_t) sa_size( local_addr)) != 0)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "create_broadcast_socket: ERROR - bind socket to local address failed - %s\n",
+ strerror (errno)));
+ close (sockfd);
+ return -1;
+ }
+ return sockfd;
+}
+
+static int
+prepare_socket(const char *if_name, const bjnp_sockaddr_t *local_sa,
+ const bjnp_sockaddr_t *broadcast_sa, bjnp_sockaddr_t * dest_sa)
+{
+ /*
+ * Prepare a socket for broadcast or multicast
+ * Input:
+ * if_name: the name of the interface
+ * local_sa: local address to use
+ * broadcast_sa: broadcast address to use, if NULL we use all hosts
+ * dest_sa: (write) where to return destination address of broadcast
+ * retuns: open socket or -1
+ */
+
+ int socket = -1;
+ bjnp_sockaddr_t local_sa_copy;
+
+ if ( local_sa == NULL )
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG,
+ "prepare_socket: %s is not a valid IPv4 interface, skipping...\n",
+ if_name));
+ return -1;
+ }
+
+ memset( &local_sa_copy, 0, sizeof(local_sa_copy) );
+ memcpy( &local_sa_copy, local_sa, sa_size(local_sa) );
+
+ switch( local_sa_copy.addr.sa_family )
+ {
+ case AF_INET:
+ {
+ local_sa_copy.ipv4.sin_port = htons(BJNP_PORT_SCAN);
+
+ if (local_sa_copy.ipv4.sin_addr.s_addr == htonl (INADDR_LOOPBACK) )
+ {
+ /* not a valid interface */
+
+ PDBG (bjnp_dbg (LOG_DEBUG,
+ "prepare_socket: %s is not a valid IPv4 interface, skipping...\n",
+ if_name));
+ return -1;
+ }
+
+
+ /* send broadcasts to the broadcast address of the interface */
+
+ memcpy(dest_sa, broadcast_sa, sa_size(dest_sa) );
+
+ /* we fill port when we send the broadcast */
+ dest_sa -> ipv4.sin_port = htons(0);
+
+ if ( (socket = create_broadcast_socket( &local_sa_copy) ) != -1)
+ {
+ PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: %s is IPv4 capable, sending broadcast, socket = %d\n",
+ if_name, socket));
+ }
+ else
+ {
+ PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: ERROR - %s is IPv4 capable, but failed to create a socket.\n",
+ if_name));
+ return -1;
+ }
+ }
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ {
+ local_sa_copy.ipv6.sin6_port = htons(BJNP_PORT_SCAN);
+
+ if (IN6_IS_ADDR_LOOPBACK( &(local_sa_copy.ipv6.sin6_addr) ) )
+ {
+ /* not a valid interface */
+
+ PDBG (bjnp_dbg (LOG_DEBUG,
+ "prepare_socket: %s is not a valid IPv6 interface, skipping...\n",
+ if_name));
+ return -1;
+ }
+ else
+ {
+ dest_sa -> ipv6.sin6_family = AF_INET6;
+
+ /* We fill port when we send the broadcast */
+ dest_sa -> ipv6.sin6_port = htons(0);
+
+ inet_pton(AF_INET6, "ff02::1", dest_sa -> ipv6.sin6_addr.s6_addr);
+ if ( (socket = create_broadcast_socket( &local_sa_copy ) ) != -1)
+ {
+ PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: %s is IPv6 capable, sending broadcast, socket = %d\n",
+ if_name, socket));
+ }
+ else
+ {
+ PDBG (bjnp_dbg (LOG_INFO, "prepare_socket: ERROR - %s is IPv6 capable, but failed to create a socket.\n",
+ if_name));
+ return -1;
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ socket = -1;
+ }
+ return socket;
+}
+
+static int
+bjnp_send_broadcast (int sockfd, const bjnp_sockaddr_t * broadcast_addr, int port,
+ struct BJNP_command cmd, int size)
+{
+ int num_bytes;
+ bjnp_sockaddr_t dest_addr;
+
+ /* set address to send packet to broadcast address of interface, */
+ /* with port set to the destination port */
+
+ memcpy(&dest_addr, broadcast_addr, sizeof(dest_addr));
+ if( dest_addr.addr.sa_family == AF_INET)
+ {
+ dest_addr.ipv4.sin_port = htons(port);
+ }
+#ifdef ENABLE_IPV6
+ if( dest_addr.addr.sa_family == AF_INET6)
+ {
+ dest_addr.ipv6.sin6_port = htons(port);
+ }
+#endif
+
+ if ((num_bytes = sendto (sockfd, &cmd, size, 0,
+ &(dest_addr.addr),
+ sa_size( broadcast_addr)) ) != size)
+ {
+ PDBG (bjnp_dbg (LOG_INFO,
+ "bjnp_send_broadcast: Socket: %d: ERROR - sent only %x = %d bytes of packet, error = %s\n",
+ sockfd, num_bytes, num_bytes, strerror (errno)));
+ /* not allowed, skip this interface */
+
+ return -1;
+ }
+ return sockfd;
+}
+
+static void
+bjnp_finish_job (int devno)
+{
+/*
+ * Signal end of scanjob to scanner
+ */
+
+ char resp_buf[BJNP_RESP_MAX];
+ int resp_len;
+ struct BJNP_command cmd;
+
+ set_cmd_for_dev (devno, &cmd, CMD_UDP_CLOSE, 0);
+
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_finish_job: Finish scanjob\n"));
+ PDBG (bjnp_hexdump
+ (LOG_DEBUG2, (char *) &cmd, sizeof (struct BJNP_command)));
+ resp_len =
+ udp_command (devno, (char *) &cmd, sizeof (struct BJNP_command), resp_buf,
+ BJNP_RESP_MAX);
+
+ if (resp_len != sizeof (struct BJNP_command))
+ {
+ PDBG (bjnp_dbg
+ (LOG_INFO,
+ "bjnp_finish_job: ERROR - Received %d characters on close scanjob command, expected %d\n",
+ resp_len, (int) sizeof (struct BJNP_command)));
+ return;
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_finish_job: Finish scanjob response\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len));
+
+}
+
+#ifdef PIXMA_BJNP_USE_STATUS
+static int
+bjnp_poll_scanner (int devno, char type,char *hostname, char *user, SANE_Byte *status, int size)
+{
+/*
+ * send details of user to the scanner
+ */
+
+ char cmd_buf[BJNP_CMD_MAX];
+ char resp_buf[BJNP_RESP_MAX];
+ int resp_len;
+ int len = 0; /* payload length */
+ int buf_len; /* length of the whole command buffer */
+ struct POLL_DETAILS *poll;
+ struct POLL_RESPONSE *response;
+ char user_host[256];
+ time_t t;
+ int user_host_len;
+
+ poll = (struct POLL_DETAILS *) cmd_buf;
+ memset( poll, 0, sizeof( struct POLL_DETAILS));
+ memset( &resp_buf, 0, sizeof( resp_buf) );
+
+
+ /* create payload */
+ poll->type = htons(type);
+
+ user_host_len = sizeof( poll -> extensions.type2.user_host);
+ snprintf(user_host, (user_host_len /2) ,"%s %s", user, hostname);
+ user_host[ user_host_len /2 + 1] = '\0';
+
+ switch( type) {
+ case 0:
+ len = 80;
+ break;
+ case 1:
+ charTo2byte(poll->extensions.type1.user_host, user_host, user_host_len);
+ len = 80;
+ break;
+ case 2:
+ poll->extensions.type2.dialog = htonl(device[devno].dialog);
+ charTo2byte(poll->extensions.type2.user_host, user_host, user_host_len);
+ poll->extensions.type2.unknown_1 = htonl(0x14);
+ poll->extensions.type2.unknown_2 = htonl(0x10);
+ t = time (NULL);
+ strftime (poll->extensions.type2.ascii_date,
+ sizeof (poll->extensions.type2.ascii_date),
+ "%Y%m%d%H%M%S", localtime (&t));
+ len = 116;
+ break;
+ case 5:
+ poll->extensions.type5.dialog = htonl(device[devno].dialog);
+ charTo2byte(poll->extensions.type5.user_host, user_host, user_host_len);
+ poll->extensions.type5.unknown_1 = htonl(0x14);
+ poll->extensions.type5.key = htonl(device[devno].status_key);
+ len = 100;
+ break;
+ default:
+ PDBG (bjnp_dbg (LOG_INFO, "bjnp_poll_scanner: unknown packet type: %d\n", type));
+ return -1;
+ };
+ /* we can only now set the header as we now know the length of the payload */
+ set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_POLL,
+ len);
+
+ buf_len = len + sizeof(struct BJNP_command);
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_poll_scanner: Poll details (type %d)\n", type));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, cmd_buf,
+ buf_len));
+
+ resp_len = udp_command (devno, cmd_buf, buf_len, resp_buf, BJNP_RESP_MAX);
+
+ if (resp_len > 0)
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_poll_scanner: Poll details response:\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len));
+ response = (struct POLL_RESPONSE *) resp_buf;
+
+ device[devno].dialog = ntohl( response -> dialog );
+
+ if ( response -> result[3] == 1 )
+ {
+ return BJNP_RESTART_POLL;
+ }
+ if ( (response -> result[2] & 0x80) != 0)
+ {
+ memcpy( status, response->status, size);
+ PDBG( bjnp_dbg(LOG_INFO, "bjnp_poll_scanner: received button status!\n"));
+ PDBG (bjnp_hexdump( LOG_DEBUG2, status, size ));
+ device[devno].status_key = ntohl( response -> key );
+ return size;
+ }
+ }
+ return 0;
+}
+#endif
+
+static void
+bjnp_send_job_details (int devno, char *hostname, char *user, char *title)
+{
+/*
+ * send details of scanjob to scanner
+ */
+
+ char cmd_buf[BJNP_CMD_MAX];
+ char resp_buf[BJNP_RESP_MAX];
+ int resp_len;
+ struct JOB_DETAILS *job;
+ struct BJNP_command *resp;
+
+ /* send job details command */
+
+ set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_JOB_DETAILS,
+ sizeof (*job) - sizeof (struct BJNP_command));
+
+ /* create payload */
+
+ job = (struct JOB_DETAILS *) (cmd_buf);
+ charTo2byte (job->unknown, "", sizeof (job->unknown));
+ charTo2byte (job->hostname, hostname, sizeof (job->hostname));
+ charTo2byte (job->username, user, sizeof (job->username));
+ charTo2byte (job->jobtitle, title, sizeof (job->jobtitle));
+
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_send_job_details: Job details\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, cmd_buf,
+ (sizeof (struct BJNP_command) + sizeof (*job))));
+
+ resp_len = udp_command (devno, cmd_buf,
+ sizeof (struct JOB_DETAILS), resp_buf,
+ BJNP_RESP_MAX);
+
+ if (resp_len > 0)
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_send_job_details: Job details response:\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len));
+ resp = (struct BJNP_command *) resp_buf;
+ device[devno].session_id = ntohs (resp->session_id);
+ }
+}
+
+static int
+bjnp_get_scanner_mac_address ( int devno, char *mac_address )
+{
+/*
+ * send discover to scanner
+ */
+
+ char cmd_buf[BJNP_CMD_MAX];
+ char resp_buf[BJNP_RESP_MAX];
+ int resp_len;
+ struct DISCOVER_RESPONSE *resp = (struct DISCOVER_RESPONSE * )&resp_buf;;
+
+ /* send job details command */
+
+ set_cmd_for_dev (devno, (struct BJNP_command *) cmd_buf, CMD_UDP_DISCOVER, 0);
+ resp_len = udp_command (devno, cmd_buf,
+ sizeof (struct BJNP_command), resp_buf,
+ BJNP_RESP_MAX);
+
+ if (resp_len > 0)
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_get_scanner_mac_address: Discover response:\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, resp_buf, resp_len));
+ u8tohex( mac_address, resp -> mac_addr, sizeof( resp -> mac_addr ) );
+ return 0;
+ }
+ return -1;
+}
+
+static int
+bjnp_write (int devno, const SANE_Byte * buf, size_t count)
+{
+/*
+ * This function writes TCP data to the scanner.
+ * Returns: number of bytes written to the scanner
+ */
+ int sent_bytes;
+ int terrno;
+ struct SCAN_BUF bjnp_buf;
+
+ if (device[devno].scanner_data_left)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_write: ERROR - scanner data left = 0x%lx = %ld\n",
+ (unsigned long) device[devno].scanner_data_left,
+ (unsigned long) device[devno].scanner_data_left));
+ }
+ /* set BJNP command header */
+
+ set_cmd_for_dev (devno, (struct BJNP_command *) &bjnp_buf, CMD_TCP_SEND, count);
+ memcpy (bjnp_buf.scan_data, buf, count);
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_write: sending 0x%lx = %ld bytes\n",
+ (unsigned long) count, (unsigned long) count);
+ PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &bjnp_buf,
+ sizeof (struct BJNP_command) + count)));
+
+ if ((sent_bytes =
+ send (device[devno].tcp_socket, &bjnp_buf,
+ sizeof (struct BJNP_command) + count, 0)) <
+ (ssize_t) (sizeof (struct BJNP_command) + count))
+ {
+ /* return result from write */
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT, "bjnp_write: ERROR - Could not send data!\n"));
+ errno = terrno;
+ return sent_bytes;
+ }
+ /* correct nr of bytes sent for length of command */
+
+ else if (sent_bytes != (int) (sizeof (struct BJNP_command) + count))
+ {
+ errno = EIO;
+ return -1;
+ }
+ return count;
+}
+
+static int
+bjnp_send_read_request (int devno)
+{
+/*
+ * This function reads responses from the scanner.
+ * Returns: 0 on success, else -1
+ *
+ */
+ int sent_bytes;
+ int terrno;
+ struct BJNP_command bjnp_buf;
+
+ if (device[devno].scanner_data_left)
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "bjnp_send_read_request: ERROR - scanner data left = 0x%lx = %ld\n",
+ (unsigned long) device[devno].scanner_data_left,
+ (unsigned long) device[devno].scanner_data_left));
+
+ /* set BJNP command header */
+
+ set_cmd_for_dev (devno, (struct BJNP_command *) &bjnp_buf, CMD_TCP_REQ, 0);
+
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_send_read_req sending command\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, (char *) &bjnp_buf,
+ sizeof (struct BJNP_command)));
+
+ if ((sent_bytes =
+ send (device[devno].tcp_socket, &bjnp_buf, sizeof (struct BJNP_command),
+ 0)) < 0)
+ {
+ /* return result from write */
+ terrno = errno;
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_send_read_request: ERROR - Could not send data!\n"));
+ errno = terrno;
+ return -1;
+ }
+ return 0;
+}
+
+static SANE_Status
+bjnp_recv_header (int devno, size_t *payload_size )
+{
+/*
+ * This function receives the response header to bjnp commands.
+ * devno device number
+ * size: return value for data size returned by scanner
+ * Returns:
+ * SANE_STATUS_IO_ERROR when any IO error occurs
+ * SANE_STATUS_GOOD in case no errors were encountered
+ */
+ struct BJNP_command resp_buf;
+ fd_set input;
+ struct timeval timeout;
+ int recv_bytes;
+ int terrno;
+ int result;
+ int fd;
+ int attempt;
+
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_recv_header: receiving response header\n") );
+ fd = device[devno].tcp_socket;
+
+ *payload_size = 0;
+ attempt = 0;
+ do
+ {
+ /* wait for data to be received, ignore signals being received */
+ FD_ZERO (&input);
+ FD_SET (fd, &input);
+
+ timeout.tv_sec = device[devno].bjnp_ip_timeout /1000;
+ timeout.tv_usec = device[devno].bjnp_ip_timeout %1000;
+ }
+ while ( ( (result = select (fd + 1, &input, NULL, NULL, &timeout)) <= 0) &&
+ (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS));
+
+ if (result < 0)
+ {
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_header: ERROR - could not read response header (select): %s!\n",
+ strerror (terrno)));
+ errno = terrno;
+ return SANE_STATUS_IO_ERROR;
+ }
+ else if (result == 0)
+ {
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_header: ERROR - could not read response header (select timed out after %d ms)!\n",
+ device[devno].bjnp_ip_timeout ) );
+ errno = terrno;
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* get response header */
+
+ if ((recv_bytes =
+ recv (fd, (char *) &resp_buf,
+ sizeof (struct BJNP_command),
+ 0)) != sizeof (struct BJNP_command))
+ {
+ terrno = errno;
+ if (recv_bytes == 0)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_header: ERROR - (recv) Scanner closed the TCP-connection!\n"));
+ } else {
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_header: ERROR - (recv) could not read response header, received %d bytes!\n",
+ recv_bytes));
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_recv_header: ERROR - (recv) error: %s!\n",
+ strerror (terrno)));
+ }
+ errno = terrno;
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (resp_buf.cmd_code != device[devno].last_cmd)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "bjnp_recv_header: ERROR - Received response has cmd code %d, expected %d\n",
+ resp_buf.cmd_code, device[devno].last_cmd));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (ntohs (resp_buf.seq_no) != (uint16_t) device[devno].serial)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "bjnp_recv_header: ERROR - Received response has serial %d, expected %d\n",
+ (int) ntohs (resp_buf.seq_no), (int) device[devno].serial));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* got response header back, retrieve length of payload */
+
+
+ *payload_size = ntohl (resp_buf.payload_len);
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_recv_header: TCP response header(payload data = %ld bytes):\n",
+ *payload_size) );
+ PDBG (bjnp_hexdump
+ (LOG_DEBUG2, (char *) &resp_buf, sizeof (struct BJNP_command)));
+ return SANE_STATUS_GOOD;
+}
+
+static int
+bjnp_init_device_structure(int dn, bjnp_sockaddr_t *sa, bjnp_protocol_defs_t *protocol_defs, int ip_timeout)
+{
+ /* initialize device structure */
+
+ char name[BJNP_HOST_MAX];
+
+ device[dn].open = 0;
+#ifdef PIXMA_BJNP_USE_STATUS
+ device[dn].polling_status = BJNP_POLL_STOPPED;
+ device[dn].dialog = 0;
+ device[dn].status_key = 0;
+#endif
+ device[dn].protocol = protocol_defs->protocol_version;
+ device[dn].protocol_string = protocol_defs->proto_string;
+ device[dn].tcp_socket = -1;
+
+ device[dn].addr = (bjnp_sockaddr_t *) malloc(sizeof ( bjnp_sockaddr_t) );
+ memset( device[dn].addr, 0, sizeof( bjnp_sockaddr_t ) );
+ memcpy(device[dn].addr, sa, sa_size((bjnp_sockaddr_t *)sa) );
+ device[dn].address_level = get_scanner_name(sa, name);
+ device[dn].session_id = 0;
+ device[dn].serial = -1;
+ device[dn].bjnp_ip_timeout = ip_timeout;
+ device[dn].bjnp_scanner_timeout = 1000;
+ device[dn].scanner_data_left = 0;
+ device[dn].last_cmd = 0;
+ device[dn].blocksize = BJNP_BLOCKSIZE_START;
+ device[dn].last_block = 0;
+ /* fill mac_address */
+
+ if (bjnp_get_scanner_mac_address(dn, device[dn].mac_address) != 0 )
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_init_device_structure: Cannot read mac address, skipping this scanner\n" ) );
+ device[dn].open = 0;
+ return -1;
+ }
+ device[dn].open = 1;
+ return 0;
+}
+
+static void
+bjnp_free_device_structure( int dn)
+{
+ if (device[dn].addr != NULL)
+ {
+ free (device[dn].addr );
+ device[dn].addr = NULL;
+ }
+ device[dn].open = 0;
+}
+
+static SANE_Status
+bjnp_recv_data (int devno, SANE_Byte * buffer, size_t start_pos, size_t * len)
+{
+/*
+ * This function receives the payload data.
+ * NOTE: len may not exceed SSIZE_MAX (as that is max for recv)
+ * len will be restricted to SSIZE_MAX to be sure
+ * Returns: number of bytes of payload received from device
+ */
+
+ fd_set input;
+ struct timeval timeout;
+ ssize_t recv_bytes;
+ int terrno;
+ int result;
+ int fd;
+ int attempt;
+
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_recv_data: read response payload (0x%lx bytes max), buffer: 0x%lx, start_pos: 0x%lx\n",
+ (long) *len, (long) buffer, (long) start_pos));
+
+
+ if (*len == 0)
+ {
+ /* nothing to do */
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_recv_data: Nothing to do (%ld bytes requested)\n",
+ (long) *len));
+ return SANE_STATUS_GOOD;
+ }
+ else if ( *len > SSIZE_MAX )
+ {
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_recv_data: WARNING - requested block size (%ld) exceeds maximum, setting to maximum %ld\n",
+ (long)*len, SSIZE_MAX));
+ *len = SSIZE_MAX;
+ }
+
+ fd = device[devno].tcp_socket;
+ attempt = 0;
+ do
+ {
+ /* wait for data to be received, retry on a signal being received */
+ FD_ZERO (&input);
+ FD_SET (fd, &input);
+ timeout.tv_sec = device[devno].bjnp_ip_timeout /1000;
+ timeout.tv_usec = device[devno].bjnp_ip_timeout %1000;
+ }
+ while (((result = select (fd + 1, &input, NULL, NULL, &timeout)) <= 0) &&
+ (errno == EINTR) && (attempt++ < BJNP_MAX_SELECT_ATTEMPTS));
+
+ if (result < 0)
+ {
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_data: ERROR - could not read response payload (select failed): %s!\n",
+ strerror (errno)));
+ errno = terrno;
+ *len = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+ else if (result == 0)
+ {
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_data: ERROR - could not read response payload (select timed out after %d ms)!\n",
+ device[devno].bjnp_ip_timeout) );
+ errno = terrno;
+ *len = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if ((recv_bytes = recv (fd, buffer + start_pos, *len, 0)) < 0)
+ {
+ terrno = errno;
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "bjnp_recv_data: ERROR - could not read response payload (%ld + %ld = %ld) (recv): %s!\n",
+ (long) buffer, (long) start_pos, (long) buffer + start_pos, strerror (errno)));
+ errno = terrno;
+ *len = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG2, "bjnp_recv_data: Received TCP response payload (%ld bytes):\n",
+ (unsigned long) recv_bytes));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, buffer, recv_bytes));
+
+ *len = recv_bytes;
+ return SANE_STATUS_GOOD;
+}
+
+static BJNP_Status
+bjnp_allocate_device (SANE_String_Const devname,
+ SANE_Int * dn, char *resulting_host)
+{
+ char method[BJNP_METHOD_MAX];
+ char host[BJNP_HOST_MAX];
+ char port[BJNP_PORT_MAX] = "";
+ char args[BJNP_ARGS_MAX];
+ bjnp_protocol_defs_t *protocol_defs;
+ struct addrinfo *res, *cur;
+ struct addrinfo hints;
+ int result;
+ int i;
+ int ip_timeout = BJNP_TIMEOUT_DEFAULT;
+
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_allocate_device(%s) %d\n", devname, bjnp_no_devices));
+
+ if (split_uri (devname, method, host, port, args) != 0)
+ {
+ return BJNP_STATUS_INVAL;
+ }
+
+ if (strlen (args) > 0)
+ {
+ /* get device specific ip timeout if any */
+
+ if (strncmp(args, "timeout=", strlen("timeout=")) == 0)
+ {
+ ip_timeout = atoi(args + strlen("timeout="));
+ } else {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "bjnp_allocate_device: ERROR - Unrecognized argument: %s\n",
+ devname));
+
+ return BJNP_STATUS_INVAL;
+ }
+ }
+ if ( (protocol_defs = get_protocol_by_method(method)) == NULL)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "bjnp_allocate_device: ERROR - URI %s contains invalid method: %s\n",
+ devname, method));
+ return BJNP_STATUS_INVAL;
+ }
+
+ if (strlen(port) == 0)
+ {
+ sprintf( port, "%d", protocol_defs->default_port );
+ }
+
+ hints.ai_flags = 0;
+#ifdef ENABLE_IPV6
+ hints.ai_family = AF_UNSPEC;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+
+ result = getaddrinfo (host, port, &hints, &res );
+ if (result != 0 )
+ {
+ PDBG (bjnp_dbg (LOG_CRIT, "bjnp_allocate_device: ERROR - Cannot resolve host: %s port %s\n", host, port));
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Check if a device number is already allocated to any of the scanner's addresses */
+
+ cur = res;
+ while( cur != NULL)
+ {
+ /* create a new device structure for this address */
+
+ if (bjnp_no_devices == BJNP_NO_DEVICES)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT,
+ "bjnp_allocate_device: WARNING - Too many devices, ran out of device structures, cannot add %s\n",
+ devname));
+ freeaddrinfo(res);
+ return BJNP_STATUS_INVAL;
+ }
+ if (bjnp_init_device_structure( bjnp_no_devices, (bjnp_sockaddr_t *)cur -> ai_addr,
+ protocol_defs, ip_timeout) != 0)
+ {
+ /* giving up on this address, try next one if any */
+ cur = cur->ai_next;
+ continue;
+ }
+ for (i = 0; i < bjnp_no_devices; i++)
+ {
+
+ /* Check if found the scanner before, if so we use the best address
+ * but still make sure the scanner is listed only once.
+ * We check for matching addresses as wel as matching mac_addresses as
+ * an IPv6 host can have multiple adresses */
+
+ if ( strcmp( device[i].mac_address, device[bjnp_no_devices].mac_address ) == 0 )
+ {
+ if ( device[i].address_level < device[bjnp_no_devices].address_level )
+ {
+ /* use the new address instead as it is better */
+ free (device[i].addr);
+ device[i].addr = device[bjnp_no_devices].addr;
+ device[bjnp_no_devices].addr = NULL;
+ device[i].address_level = device[bjnp_no_devices].address_level;
+ }
+
+ /* Leave timeout values unchanged, as they were probably specified by the user */
+
+ freeaddrinfo(res);
+ *dn = i;
+ bjnp_free_device_structure( bjnp_no_devices);
+ return BJNP_STATUS_ALREADY_ALLOCATED;
+ }
+ }
+ cur = cur->ai_next;
+ }
+ freeaddrinfo(res);
+
+ if (device[bjnp_no_devices].open == 0)
+ {
+ PDBG (bjnp_dbg(LOG_NOTICE, "bjnp_allocate_device: Cannot access scanner, skipping!"));
+ return BJNP_STATUS_INVAL;
+ }
+
+ PDBG (bjnp_dbg (LOG_INFO, "bjnp_allocate_device: Scanner not yet in our list, added it: %s:%s\n", host, port));
+
+ /* Commit new device structure */
+
+ *dn = bjnp_no_devices;
+ bjnp_no_devices++;
+
+ /* return hostname if required */
+
+ if (resulting_host != NULL)
+ {
+ strcpy (resulting_host, host);
+ }
+
+ return BJNP_STATUS_GOOD;
+}
+
+static void add_scanner(SANE_Int *dev_no,
+ const char *uri,
+ SANE_Status (*attach_bjnp)
+ (SANE_String_Const devname,
+ SANE_String_Const serial,
+ const struct pixma_config_t *cfg),
+ const struct pixma_config_t *const pixma_devices[])
+
+{
+ char scanner_host[BJNP_HOST_MAX];
+ char serial[BJNP_SERIAL_MAX];
+ char makemodel[BJNP_MODEL_MAX];
+ const struct pixma_config_t *cfg = NULL;
+
+ /* Allocate device structure for scanner */
+ switch (bjnp_allocate_device (uri, dev_no, scanner_host))
+ {
+ case BJNP_STATUS_GOOD:
+ if (get_scanner_id (*dev_no, makemodel) != 0)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: ERROR - Cannot read scanner make & model: %s\n",
+ uri));
+ }
+ else
+ {
+ /*
+ * fetch scanner configuration
+ */
+ if ((cfg = lookup_scanner(makemodel, pixma_devices)) == (struct pixma_config_t *)NULL)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: Scanner %s is not supported, model is unknown! Please report upstream\n", makemodel));
+ break;
+ }
+
+ /*
+ * inform caller of found scanner
+ */
+
+ determine_scanner_serial (scanner_host, device[*dev_no].mac_address, serial);
+
+ switch (attach_bjnp (uri, serial, cfg))
+ {
+ case SANE_STATUS_GOOD:
+ PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: New scanner added: %s, serial %s, mac address: %s.\n",
+ uri, serial, device[*dev_no].mac_address));
+ break;
+ default:
+ PDBG (bjnp_dbg (LOG_CRIT, "add_scanner: unexpected error (out of memory?), adding %s\n", makemodel));
+ }
+ }
+
+ break;
+ case BJNP_STATUS_ALREADY_ALLOCATED:
+ PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: Scanner at %s was added before, good!\n",
+ uri));
+ break;
+
+ case BJNP_STATUS_INVAL:
+ PDBG (bjnp_dbg (LOG_NOTICE, "add_scanner: Scanner at %s can not be added\n",
+ uri));
+ break;
+ }
+}
+
+int add_timeout_to_uri(char *uri, int timeout, int max_len)
+{
+ char method[BJNP_METHOD_MAX];
+ char host[BJNP_HOST_MAX];
+ char port_str[BJNP_PORT_MAX];
+ char args[BJNP_HOST_MAX];
+ int port;
+ bjnp_protocol_defs_t *proto_struct;
+
+ if (split_uri(uri, method, host, port_str, args ) != 0)
+ {
+ return -1;
+ }
+
+ port = atoi(port_str);
+
+ if (port == 0)
+ {
+ proto_struct = get_protocol_by_method(method);
+ if (proto_struct == NULL)
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "uri: %s: Method %s cannot be recognized\n", uri, method));
+ }
+ else
+ {
+ port = proto_struct-> default_port;
+ }
+ }
+
+ /* add timeout value only if missing in URI */
+
+ if (strstr(args, "timeout=") == NULL)
+ {
+ sprintf(args, "timeout=%d", timeout);
+ }
+
+ snprintf(uri, max_len -1, "%s://%s:%d/%s", method,host, port, args);
+ uri[max_len - 1] = '\0';
+ return 0;
+}
+
+/** Public functions **/
+
+/** Initialize sanei_bjnp.
+ *
+ * Call this before any other sanei_bjnp function.
+ */
+extern void
+sanei_bjnp_init (void)
+{
+ DBG_INIT();
+ bjnp_no_devices = 0;
+}
+
+/**
+ * Find devices that implement the bjnp protocol
+ *
+ * The function attach is called for every device which has been found.
+ *
+ * @param attach attach function
+ *
+ * @return SANE_STATUS_GOOD - on success (even if no scanner was found)
+ */
+extern SANE_Status
+sanei_bjnp_find_devices (const char **conf_devices,
+ SANE_Status (*attach_bjnp)
+ (SANE_String_Const devname,
+ SANE_String_Const serial,
+ const struct pixma_config_t *cfg),
+ const struct pixma_config_t *const pixma_devices[])
+{
+ int numbytes = 0;
+ struct BJNP_command cmd;
+ unsigned char resp_buf[2048];
+ struct DISCOVER_RESPONSE *disc_resp = ( struct DISCOVER_RESPONSE *) & resp_buf;
+ int socket_fd[BJNP_SOCK_MAX];
+ int no_sockets;
+ int i;
+ int j;
+ int attempt;
+ int last_socketfd = 0;
+ fd_set fdset;
+ fd_set active_fdset;
+ struct timeval timeout;
+ char scanner_host[HOST_NAME_MAX];
+ char uri[HOST_NAME_MAX + 32];
+ int dev_no;
+ int port;
+ int auto_detect = 1;
+ int timeout_default = BJNP_TIMEOUT_DEFAULT;
+ bjnp_sockaddr_t broadcast_addr[BJNP_SOCK_MAX];
+ bjnp_sockaddr_t scanner_sa;
+ socklen_t socklen;
+ bjnp_protocol_defs_t *protocol_defs;
+
+ memset( broadcast_addr, 0, sizeof( broadcast_addr) );
+ memset( &scanner_sa, 0 ,sizeof( scanner_sa ) );
+ PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_find_devices, pixma backend version: %d.%d.%d\n",
+ PIXMA_VERSION_MAJOR, PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD));
+ bjnp_no_devices = 0;
+
+ for (i=0; i < BJNP_SOCK_MAX; i++)
+ {
+ socket_fd[i] = -1;
+ }
+
+ /* parse config file */
+
+ if (conf_devices[0] != NULL)
+ {
+ if (strcmp(conf_devices[0], "networking=no") == 0)
+ {
+ /* networking=no may only occur on the first non-commented line */
+
+ PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Networked scanner detection is disabled in configuration file.\n" ) );
+ return SANE_STATUS_GOOD;
+ }
+ /* parse configuration file */
+
+ for (i = 0; conf_devices[i] != NULL; i++)
+ {
+ if (strncmp(conf_devices[i], "bjnp-timeout=", strlen("bjnp-timeout="))== 0)
+ {
+ timeout_default = atoi(conf_devices[i] + strlen("bjnp-timeout=") );
+ PDBG ( bjnp_dbg (LOG_DEBUG, "Set new default timeout value: %d ms.", timeout_default));
+ continue;
+ }
+ else if (strncmp(conf_devices[i], "auto_detection=no", strlen("auto_detection=no"))== 0)
+ {
+ auto_detect = 0;
+ PDBG ( bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: auto detection of scanners disabled"));
+ continue;
+ }
+ else
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Adding scanner from pixma.conf: %s\n", conf_devices[i]));
+ memcpy(uri, conf_devices[i], sizeof(uri));
+ add_timeout_to_uri(uri, timeout_default, sizeof(uri));
+ add_scanner(&dev_no, uri, attach_bjnp, pixma_devices);
+ }
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Added all specified scanners.\n"));
+ }
+ else
+ {
+ PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Configuration file is empty, No devices specified.\n" ) );
+ }
+
+ if (auto_detect == 0)
+ {
+ return SANE_STATUS_GOOD;
+ }
+ /*
+ * Send UDP DISCOVER to discover scanners and return the list of scanners found
+ */
+
+ PDBG (bjnp_dbg( LOG_DEBUG, "sanei_bjnp_find_devices: Start auto-detection.\n" ) );
+ FD_ZERO (&fdset);
+
+ no_sockets = 0;
+#ifdef HAVE_IFADDRS_H
+ {
+ struct ifaddrs *interfaces = NULL;
+ struct ifaddrs *interface;
+ getifaddrs (&interfaces);
+
+ /* create a socket for each suitable interface */
+
+ interface = interfaces;
+ while ((no_sockets < BJNP_SOCK_MAX) && (interface != NULL))
+ {
+ if ( ! (interface -> ifa_flags & IFF_POINTOPOINT) &&
+ ( (socket_fd[no_sockets] =
+ prepare_socket( interface -> ifa_name,
+ (bjnp_sockaddr_t *) interface -> ifa_addr,
+ (bjnp_sockaddr_t *) interface -> ifa_broadaddr,
+ &broadcast_addr[no_sockets] ) ) != -1 ) )
+ {
+ /* track highest used socket for later use in select */
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ FD_SET (socket_fd[no_sockets], &fdset);
+ no_sockets++;
+ }
+ interface = interface->ifa_next;
+ }
+ freeifaddrs (interfaces);
+ }
+#else
+ /* we have no easy way to find interfaces with their broadcast addresses. */
+ /* use global broadcast and all-hosts instead */
+ {
+ bjnp_sockaddr_t local;
+ bjnp_sockaddr_t bc_addr;
+
+ memset( &local, 0, sizeof( local) );
+ local.ipv4.sin_family = AF_INET;
+ local.ipv4.sin_addr.s_addr = htonl (INADDR_ANY);
+
+ bc_addr.ipv4.sin_family = AF_INET;
+ bc_addr.ipv4.sin_port = htons(0);
+ bc_addr.ipv4.sin_addr.s_addr = htonl (INADDR_BROADCAST);
+
+ socket_fd[no_sockets] = prepare_socket( "any_interface",
+ &local,
+ &bc_addr,
+ &broadcast_addr[no_sockets] );
+ if (socket_fd[no_sockets] >= 0)
+ {
+ FD_SET (socket_fd[no_sockets], &fdset);
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ no_sockets++;
+ }
+#ifdef ENABLE_IPV6
+ local.ipv6.sin6_family = AF_INET6;
+ local.ipv6.sin6_addr = in6addr_any;
+
+ socket_fd[no_sockets] = prepare_socket( "any_interface",
+ &local,
+ NULL,
+ &broadcast_addr[no_sockets] );
+ if (socket_fd[no_sockets] >= 0)
+ {
+ FD_SET (socket_fd[no_sockets], &fdset);
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ no_sockets++;
+ }
+#endif
+ }
+#endif
+
+ /* send BJNP_MAX_BROADCAST_ATTEMPTS broadcasts on each prepared socket */
+ for (attempt = 0; attempt < BJNP_MAX_BROADCAST_ATTEMPTS; attempt++)
+ {
+ for ( i=0; i < no_sockets; i++)
+ {
+ j = 0;
+ while(bjnp_protocol_defs[j].protocol_version != PROTOCOL_NONE)
+ {
+ set_cmd_from_string (bjnp_protocol_defs[j].proto_string, &cmd, CMD_UDP_DISCOVER, 0);
+ bjnp_send_broadcast ( socket_fd[i], &broadcast_addr[i],
+ bjnp_protocol_defs[j].default_port, cmd, sizeof (cmd));
+ j++;
+ }
+ }
+ /* wait for some time between broadcast packets */
+ usleep (BJNP_BROADCAST_INTERVAL * BJNP_USLEEP_MS);
+ }
+
+ /* wait for a UDP response */
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * BJNP_USLEEP_MS;
+
+
+ active_fdset = fdset;
+
+ while (select (last_socketfd + 1, &active_fdset, NULL, NULL, &timeout) > 0)
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG, "sanei_bjnp_find_devices: Select returned, time left %d.%d....\n",
+ (int) timeout.tv_sec, (int) timeout.tv_usec));
+ for (i = 0; i < no_sockets; i++)
+ {
+ if (FD_ISSET (socket_fd[i], &active_fdset))
+ {
+ socklen = sizeof(scanner_sa);
+ if ((numbytes =
+ recvfrom (socket_fd[i], resp_buf, sizeof (resp_buf), 0,
+ &(scanner_sa.addr), &socklen ) ) == -1)
+ {
+ PDBG (bjnp_dbg
+ (LOG_INFO, "sanei_find_devices: no data received"));
+ break;
+ }
+ else
+ {
+ PDBG (bjnp_dbg (LOG_DEBUG2, "sanei_find_devices: Discover response:\n"));
+ PDBG (bjnp_hexdump (LOG_DEBUG2, &resp_buf, numbytes));
+
+ /* check if something sensible is returned */
+ protocol_defs = get_protocol_by_proto_string(disc_resp-> response.BJNP_id);
+ if ( (numbytes < (int)sizeof (struct BJNP_command)) ||
+ (protocol_defs == NULL))
+ {
+ /* not a valid response, assume not a scanner */
+
+ char bjnp_id[5];
+ strncpy(bjnp_id, disc_resp-> response.BJNP_id, 4);
+ bjnp_id[4] = '\0';
+ PDBG (bjnp_dbg (LOG_INFO,
+ "sanei_find_devices: Invalid discover response! Length = %d, Id = %s\n",
+ numbytes, bjnp_id ) );
+ break;
+ }
+ if ( !(disc_resp -> response.dev_type & 0x80) )
+ {
+ /* not a response, a command from somebody else or */
+ /* a discover command that we generated */
+ break;
+ }
+ };
+
+ port = get_port_from_sa(scanner_sa);
+ /* scanner found, get IP-address or hostname */
+ get_scanner_name( &scanner_sa, scanner_host);
+
+ /* construct URI */
+ sprintf (uri, "%s://%s:%d/timeout=%d", protocol_defs->method_string, scanner_host,
+ port, timeout_default);
+
+ add_scanner( &dev_no, uri, attach_bjnp, pixma_devices);
+
+ }
+ }
+ active_fdset = fdset;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * BJNP_USLEEP_MS;
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG, "sanei_find_devices: scanner discovery finished...\n"));
+
+ for (i = 0; i < no_sockets; i++)
+ close (socket_fd[i]);
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Open a BJNP device.
+ *
+ * The device is opened by its name devname and the device number is
+ * returned in dn on success.
+ *
+ * Device names consist of an URI
+ * Where:
+ * type = bjnp
+ * hostname = resolvable name or IP-address
+ * port = 8612 for a scanner
+ * An example could look like this: bjnp://host.domain:8612
+ *
+ * @param devname name of the device to open
+ * @param dn device number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ * permissions
+ * - SANE_STATUS_INVAL - on every other error
+ */
+
+extern SANE_Status
+sanei_bjnp_open (SANE_String_Const devname, SANE_Int * dn)
+{
+ int result;
+
+ PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_open(%s, %d):\n", devname, *dn));
+
+ result = bjnp_allocate_device (devname, dn, NULL);
+ if ( (result != BJNP_STATUS_GOOD) && (result != BJNP_STATUS_ALREADY_ALLOCATED ) ) {
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+}
+
+/** Close a BJNP device.
+ *
+ * @param dn device number
+ */
+
+void
+sanei_bjnp_close (SANE_Int dn)
+{
+ PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_close(%d):\n", dn));
+
+ device[dn].open = 0;
+ sanei_bjnp_deactivate(dn);
+}
+
+/** Activate BJNP device connection
+ *
+ * @param dn device number
+ */
+
+SANE_Status
+sanei_bjnp_activate (SANE_Int dn)
+{
+ char hostname[256];
+ char pid_str[64];
+
+ PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_activate (%d)\n", dn));
+ gethostname (hostname, 256);
+ hostname[255] = '\0';
+ sprintf (pid_str, "Process ID = %d", getpid ());
+
+ bjnp_send_job_details (dn, hostname, getusername (), pid_str);
+
+ if (bjnp_open_tcp (dn) != 0)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Deactivate BJNP device connection
+ *
+ * @paran dn device number
+ */
+
+SANE_Status
+sanei_bjnp_deactivate (SANE_Int dn)
+{
+ PDBG (bjnp_dbg (LOG_INFO, "sanei_bjnp_deactivate (%d)\n", dn));
+ if ( device[dn].tcp_socket != -1)
+ {
+ bjnp_finish_job (dn);
+ close (device[dn].tcp_socket);
+ device[dn].tcp_socket = -1;
+ }
+ return SANE_STATUS_GOOD;
+}
+
+/** Set the timeout for interrupt reads.
+ * we do not use it for bulk reads!
+ * @param timeout the new timeout in ms
+ */
+extern void
+sanei_bjnp_set_timeout (SANE_Int devno, SANE_Int timeout)
+{
+ PDBG (bjnp_dbg (LOG_INFO, "bjnp_set_timeout to %d\n",
+ timeout));
+
+ device[devno].bjnp_scanner_timeout = timeout;
+}
+
+/** Initiate a bulk transfer read.
+ *
+ * Read up to size bytes from the device to buffer. After the read, size
+ * contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_bjnp_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+ SANE_Status result;
+ SANE_Status error;
+ size_t recvd;
+ size_t read_size;
+ size_t read_size_max;
+ size_t requested;
+
+ PDBG (bjnp_dbg
+ (LOG_INFO, "bjnp_read_bulk(dn=%d, bufferptr=%lx, 0x%lx = %ld)\n", dn,
+ (long) buffer, (unsigned long) *size, (unsigned long) *size));
+
+ recvd = 0;
+ requested = *size;
+
+ PDBG (bjnp_dbg
+ (LOG_DEBUG, "bjnp_read_bulk: 0x%lx = %ld bytes available at start\n",
+ (unsigned long) device[dn].scanner_data_left,
+ (unsigned long) device[dn].scanner_data_left ) );
+
+ while ( (recvd < requested) && !( device[dn].last_block && (device[dn].scanner_data_left == 0)) )
+ {
+ PDBG (bjnp_dbg
+ (LOG_DEBUG,
+ "bjnp_read_bulk: Already received 0x%lx = %ld bytes, backend requested 0x%lx = %ld bytes\n",
+ (unsigned long) recvd, (unsigned long) recvd,
+ (unsigned long) requested, (unsigned long)requested ));
+
+ /* Check first if there is data in flight from the scanner */
+
+ if (device[dn].scanner_data_left == 0)
+ {
+ /* There is no data in flight from the scanner, send new read request */
+
+ PDBG (bjnp_dbg (LOG_DEBUG,
+ "bjnp_read_bulk: No (more) scanner data available, requesting more( blocksize = %ld = %lx\n",
+ (long int) device[dn].blocksize, (long int) device[dn].blocksize ));
+
+ if ((error = bjnp_send_read_request (dn)) != SANE_STATUS_GOOD)
+ {
+ *size = recvd;
+ return SANE_STATUS_IO_ERROR;
+ }
+ if ( ( error = bjnp_recv_header (dn, &(device[dn].scanner_data_left) ) ) != SANE_STATUS_GOOD)
+ {
+ *size = recvd;
+ return SANE_STATUS_IO_ERROR;
+ }
+ /* correct blocksize if applicable */
+
+ device[dn].blocksize = MAX (device[dn].blocksize, device[dn].scanner_data_left);
+
+ if ( device[dn].scanner_data_left < device[dn].blocksize)
+ {
+ /* the scanner will not react at all to a read request, when no more data is available */
+ /* we now determine end of data by comparing the payload size to the maximun blocksize */
+ /* this block is shorter than blocksize, so after this block we are done */
+
+ device[dn].last_block = 1;
+ }
+ }
+
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: In flight: 0x%lx = %ld bytes available\n",
+ (unsigned long) device[dn].scanner_data_left,
+ (unsigned long) device[dn].scanner_data_left));
+
+ /* read as many bytes as needed and available */
+
+ read_size_max = MIN( device[dn].scanner_data_left, (requested - recvd) );
+ read_size = read_size_max;
+
+ PDBG (bjnp_dbg
+ (LOG_DEBUG,
+ "bjnp_read_bulk: Try to read 0x%lx = %ld (of max 0x%lx = %ld) bytes\n",
+ (unsigned long) read_size_max,
+ (unsigned long) read_size_max,
+ (unsigned long) device[dn].scanner_data_left,
+ (unsigned long) device[dn].scanner_data_left) );
+
+ result = bjnp_recv_data (dn, buffer , recvd, &read_size);
+ if (result != SANE_STATUS_GOOD)
+ {
+ *size = recvd;
+ return SANE_STATUS_IO_ERROR;
+ }
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: Expected at most %ld bytes, received this time: %ld\n",
+ read_size_max, read_size) );
+
+ device[dn].scanner_data_left = device[dn].scanner_data_left - read_size;
+ recvd = recvd + read_size;
+ }
+
+ PDBG (bjnp_dbg (LOG_DEBUG, "bjnp_read_bulk: %s: Returning %ld bytes, backend expexts %ld\n",
+ (recvd == *size)? "OK": "NOTICE",recvd, *size ) );
+ *size = recvd;
+ if ( *size == 0 )
+ return SANE_STATUS_EOF;
+ return SANE_STATUS_GOOD;
+}
+
+/** Initiate a bulk transfer write.
+ *
+ * Write up to size bytes from buffer to the device. After the write size
+ * contains the number of bytes actually written.
+ *
+ * @param dn device number
+ * @param buffer buffer to write to device
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_IO_ERROR - if an error occured during the write
+ * - SANE_STATUS_INVAL - on every other error
+ */
+
+extern SANE_Status
+sanei_bjnp_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
+{
+ ssize_t sent;
+ size_t recvd;
+ uint32_t buf;
+ size_t payload_size;
+
+ /* Write received data to scanner */
+
+ sent = bjnp_write (dn, buffer, *size);
+ if (sent < 0)
+ return SANE_STATUS_IO_ERROR;
+ if (sent != (int) *size)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Sent only %ld bytes to scanner, expected %ld!!\n",
+ (unsigned long) sent, (unsigned long) *size));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (bjnp_recv_header (dn, &payload_size) != SANE_STATUS_GOOD)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Could not read response to command!\n"));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (payload_size != 4)
+ {
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "sanei_bjnp_write_bulk: ERROR - Scanner length of write confirmation = 0x%lx bytes = %ld, expected %d!!\n",
+ (unsigned long) payload_size,
+ (unsigned long) payload_size, 4));
+ return SANE_STATUS_IO_ERROR;
+ }
+ recvd = payload_size;
+ if ((bjnp_recv_data (dn, (unsigned char *) &buf, 0, &recvd) !=
+ SANE_STATUS_GOOD) || (recvd != payload_size))
+ {
+ PDBG (bjnp_dbg (LOG_CRIT,
+ "sanei_bjnp_write_bulk: ERROR - Could not read length of data confirmed by device\n"));
+ return SANE_STATUS_IO_ERROR;
+ }
+ recvd = ntohl (buf);
+ if (recvd != *size)
+ {
+ PDBG (bjnp_dbg
+ (LOG_CRIT, "sanei_bjnp_write_bulk: ERROR - Scanner confirmed %ld bytes, expected %ld!!\n",
+ (unsigned long) recvd, (unsigned long) *size));
+ return SANE_STATUS_IO_ERROR;
+ }
+ /* we can expect data from the scanner */
+
+ device[dn].last_block = 0;
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Initiate a interrupt transfer read.
+ *
+ * Read up to size bytes from the interrupt endpoint from the device to
+ * buffer. After the read, size contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_bjnp_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+#ifndef PIXMA_BJNP_USE_STATUS
+ PDBG (bjnp_dbg
+ (LOG_INFO, "bjnp_read_int(%d, bufferptr, 0x%lx = %ld):\n", dn,
+ (unsigned long) *size, (unsigned long) *size));
+
+ memset (buffer, 0, *size);
+ sleep (1);
+ return SANE_STATUS_IO_ERROR;
+#else
+
+ char hostname[256];
+ int resp_len;
+ int timeout;
+ int interval;
+
+ PDBG (bjnp_dbg
+ (LOG_INFO, "bjnp_read_int(%d, bufferptr, 0x%lx = %ld):\n", dn,
+ (unsigned long) *size, (unsigned long) *size));
+
+ memset (buffer, 0, *size);
+
+ gethostname (hostname, 32);
+ hostname[32] = '\0';
+
+
+ switch (device[dn].polling_status)
+ {
+ case BJNP_POLL_STOPPED:
+
+ /* establish dialog */
+
+ if ( (bjnp_poll_scanner (dn, 0, hostname, getusername (), buffer, *size ) != 0) ||
+ (bjnp_poll_scanner (dn, 1, hostname, getusername (), buffer, *size ) != 0) )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: WARNING - Failed to setup read_intr dialog with device!\n"));
+ device[dn].dialog = 0;
+ device[dn].status_key = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+ device[dn].polling_status = BJNP_POLL_STARTED;
+
+ /* fall through */
+ case BJNP_POLL_STARTED:
+ /* we use only seonds (rounded up) accuracy between poll attempts */
+ timeout = device[dn].bjnp_scanner_timeout /1000 + 1;
+ if (device[dn].bjnp_scanner_timeout %1000 > 0)
+ {
+ timeout++;
+
+ }
+ interval = 1;
+ do
+ {
+ if ( (resp_len = bjnp_poll_scanner (dn, 2, hostname, getusername (), buffer, *size ) ) < 0 )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: Poll failed, Restarting polling dialog!\n"));
+ device[dn].polling_status = BJNP_POLL_STOPPED;
+ *size = 0;
+ return SANE_STATUS_EOF;
+ }
+ *size = (size_t) resp_len;
+ if ( resp_len > 0 )
+ {
+ device[dn].polling_status = BJNP_POLL_STATUS_RECEIVED;
+ return SANE_STATUS_GOOD;
+ }
+ timeout = timeout - interval;
+ if (timeout <= 0)
+ return SANE_STATUS_EOF;
+ sleep(interval);
+ } while ( timeout > 0 ) ;
+ break;
+ case BJNP_POLL_STATUS_RECEIVED:
+ if ( (resp_len = bjnp_poll_scanner (dn, 5, hostname, getusername (), buffer, *size ) ) < 0 )
+ {
+ PDBG (bjnp_dbg (LOG_NOTICE, "bjnp_read_int: Restarting polling dialog!\n"));
+ device[dn].polling_status = BJNP_POLL_STOPPED;
+ *size = 0;
+ break;
+ }
+ }
+ return SANE_STATUS_EOF;
+#endif
+}
diff --git a/backend/pixma/pixma_bjnp.h b/backend/pixma/pixma_bjnp.h
new file mode 100644
index 0000000..79e084e
--- /dev/null
+++ b/backend/pixma/pixma_bjnp.h
@@ -0,0 +1,201 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2008 by Louis Lagendijk
+ based on sane_usb.h:
+ Copyright (C) 2003, 2005 Rene Rebe (sanei_read_int,sanei_set_timeout)
+ Copyright (C) 2001, 2002 Henning Meier-Geinitz
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+/** @file sanei_bjnp.h
+ * This file provides a generic BJNP interface.
+ */
+
+#ifndef sanei_bjnp_h
+#define sanei_bjnp_h
+
+#include "../include/sane/config.h"
+#include "../include/sane/sane.h"
+#include "pixma.h"
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h> /* for size_t */
+#endif
+
+/** Initialize sanei_bjnp.
+ *
+ * Call this before any other sanei_bjnp function.
+ */
+extern void sanei_bjnp_init (void);
+
+/** Find scanners responding to a BJNP broadcast.
+ *
+ * The function sanei_bjnp_attach is called for every device which has
+ * been found.
+ * Serial is the address of the scanner in human readable form of max
+ * SERIAL_MAX characters
+ * @param conf_devices list of pre-configures device URI's to attach
+ * @param attach attach function
+ * @param pixma_devices device informatio needed by attach function
+ *
+ * @return SANE_STATUS_GOOD - on success (even if no scanner was found)
+ */
+
+#define SERIAL_MAX 16
+
+extern SANE_Status
+sanei_bjnp_find_devices (const char **conf_devices,
+ SANE_Status (*attach_bjnp)
+ (SANE_String_Const devname,
+ SANE_String_Const serial,
+ const struct pixma_config_t *cfg),
+ const struct pixma_config_t *const pixma_devices[]);
+
+/** Open a BJNP device.
+ *
+ * The device is opened by its name devname and the device number is
+ * returned in dn on success.
+ *
+ * Device names consist of an URI
+ * Where:
+ * method = bjnp
+ * hostname = resolvable name or IP-address
+ * port = 8612 for a bjnp scanner, 8610 for a mfnp device
+ * An example could look like this: bjnp://host.domain:8612
+ *
+ * @param devname name of the device to open
+ * @param dn device number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ * permissions
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status sanei_bjnp_open (SANE_String_Const devname, SANE_Int * dn);
+
+/** Close a BJNP device.
+ *
+ * @param dn device number
+ */
+
+extern void sanei_bjnp_close (SANE_Int dn);
+
+/** Activate a BJNP device connection
+ *
+ * @param dn device number
+ */
+
+extern SANE_Status sanei_bjnp_activate (SANE_Int dn);
+
+/** De-activate a BJNP device connection
+ *
+ * @param dn device number
+ */
+
+extern SANE_Status sanei_bjnp_deactivate (SANE_Int dn);
+
+/** Set the libbjnp timeout for bulk and interrupt reads.
+ *
+ * @param devno device number
+ * @param timeout the new timeout in ms
+ */
+extern void sanei_bjnp_set_timeout (SANE_Int devno, SANE_Int timeout);
+
+/** Check if sanei_bjnp_set_timeout() is available.
+ */
+#define HAVE_SANEI_BJNP_SET_TIMEOUT
+
+/** Initiate a bulk transfer read.
+ *
+ * Read up to size bytes from the device to buffer. After the read, size
+ * contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+extern SANE_Status
+sanei_bjnp_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/** Initiate a bulk transfer write.
+ *
+ * Write up to size bytes from buffer to the device. After the write size
+ * contains the number of bytes actually written.
+ *
+ * @param dn device number
+ * @param buffer buffer to write to device
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_IO_ERROR - if an error occured during the write
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status
+sanei_bjnp_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size);
+
+/** Initiate a interrupt transfer read.
+ *
+ * Read up to size bytes from the interrupt endpoint from the device to
+ * buffer. After the read, size contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_bjnp_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/*------------------------------------------------------*/
+#endif /* sanei_bjnp_h */
diff --git a/backend/pixma/pixma_bjnp_private.h b/backend/pixma/pixma_bjnp_private.h
new file mode 100644
index 0000000..edfb330
--- /dev/null
+++ b/backend/pixma/pixma_bjnp_private.h
@@ -0,0 +1,384 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2008 by Louis Lagendijk
+
+ This file is part of the SANE package.
+
+ Data structures and definitions for
+ bjnp backend for the Common UNIX Printing System (CUPS).
+
+ These coded instructions, statements, and computer programs are the
+ property of Louis Lagendijk and are protected by Federal copyright
+ law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ "LICENSE" which should have been included with this file. If this
+ file is missing or damaged, see the license at "http://www.cups.org/".
+
+ This file is subject to the Apple OS-Developed Software exception.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/*
+ * BJNP definitions
+ */
+
+/* selection of options */
+/* This works now, disable when it gives you problems */
+#define PIXMA_BJNP_USE_STATUS 1
+
+/* sizes */
+
+#define BJNP_PRINTBUF_MAX 1400 /* size of printbuffer */
+#define BJNP_CMD_MAX 2048 /* size of BJNP response buffer */
+#define BJNP_RESP_MAX 2048 /* size of BJNP response buffer */
+#define BJNP_SOCK_MAX 256 /* maximum number of open sockets */
+#define BJNP_MODEL_MAX 64 /* max allowed size for make&model */
+#define BJNP_STATUS_MAX 256 /* max size for status string */
+#define BJNP_IEEE1284_MAX 1024 /* max. allowed size of IEEE1284 id */
+#define BJNP_METHOD_MAX 16 /* max length of method */
+#define BJNP_HOST_MAX 128 /* max length of hostname or address */
+#define BJNP_PORT_MAX 64 /* max length of port string */
+#define BJNP_ARGS_MAX 128 /* max length of argument string */
+#define BJNP_SERIAL_MAX 16 /* maximum length of serial number */
+#define BJNP_NO_DEVICES 16 /* max number of open devices */
+#define BJNP_SCAN_BUF_MAX 65536 /* size of scanner data intermediate buffer */
+#define BJNP_BLOCKSIZE_START 512 /* startsize for last block detection */
+
+/* timers */
+#define BJNP_BROADCAST_INTERVAL 10 /* ms between broadcasts */
+#define BJNP_BC_RESPONSE_TIMEOUT 500 /* waiting time for broadc. responses */
+#define BJNP_TIMEOUT_DEFAULT 10000 /* minimum tiemout value for network operations */
+#define BJNP_TIMEOUT_TCP_CONNECT 2000 /* timeout for tcp connect attempts in ms */
+#define BJNP_USLEEP_MS 1000 /* sleep for 1 msec */
+#define BJNP_TCP_CONNECT_INTERVAL 100 /* TCP retry interval in ms */
+
+/* retries */
+#define BJNP_MAX_SELECT_ATTEMPTS 3 /* max nr of retries on select (EINTR) */
+#define BJNP_MAX_BROADCAST_ATTEMPTS 2 /* number of broadcast packets to be sent */
+#define BJNP_UDP_RETRY_MAX 3 /* max nt of retries on a udp command */
+
+#define bjnp_dbg DBG
+#include "../include/sane/sanei_debug.h"
+
+/* loglevel definitions */
+
+#define LOG_CRIT 0
+#define LOG_NOTICE 1
+#define LOG_INFO 2
+#define LOG_DEBUG 3
+#define LOG_DEBUG2 4
+#define LOG_DEBUG3 5
+
+#define BJNP_RESTART_POLL -1
+
+/*************************************/
+/* BJNP protocol related definitions */
+/*************************************/
+
+/* port numbers */
+typedef enum bjnp_port_e
+{
+ MFNP_PORT_SCAN = 8610,
+ BJNP_PORT_PRINT = 8611,
+ BJNP_PORT_SCAN = 8612,
+ BJNP_PORT_3 = 8613,
+ BJNP_PORT_4 = 8614
+} bjnp_port_t;
+
+typedef enum
+{
+ PROTOCOL_BJNP = 0,
+ PROTOCOL_MFNP = 1,
+ PROTOCOL_NONE =2
+} bjnp_protocol_t;
+
+typedef struct
+{
+ bjnp_protocol_t protocol_version;
+ int default_port;
+ char * proto_string;
+ char * method_string;
+} bjnp_protocol_defs_t;
+
+bjnp_protocol_defs_t bjnp_protocol_defs[] =
+{
+ {PROTOCOL_BJNP, BJNP_PORT_SCAN,"BJNP", "bjnp"},
+ {PROTOCOL_MFNP, MFNP_PORT_SCAN,"MFNP", "mfnp"},
+ {PROTOCOL_NONE, -1, NULL, NULL}
+};
+
+/* commands */
+typedef enum bjnp_cmd_e
+{
+ CMD_UDP_DISCOVER = 0x01, /* discover if service type is listening at this port */
+ CMD_UDP_START_SCAN = 0x02, /* start scan pressed, sent from scanner to 224.0.0.1 */
+ CMD_UDP_JOB_DETAILS = 0x10, /* send print/ scanner job owner details */
+ CMD_UDP_CLOSE = 0x11, /* request connection closure */
+ CMD_UDP_GET_STATUS = 0x20, /* get printer status */
+ CMD_TCP_REQ = 0x20, /* read data from device */
+ CMD_TCP_SEND = 0x21, /* send data to device */
+ CMD_UDP_GET_ID = 0x30, /* get printer identity */
+ CMD_UDP_POLL = 0x32 /* poll scanner for button status */
+} bjnp_cmd_t;
+
+/* command type */
+
+typedef enum uint8_t
+{
+ BJNP_CMD_PRINT = 0x1, /* printer command */
+ BJNP_CMD_SCAN = 0x2, /* scanner command */
+ BJNP_RES_PRINT = 0x81, /* printer response */
+ BJNP_RES_SCAN = 0x82 /* scanner response */
+} bjnp_cmd_type_t;
+
+/***************************/
+/* BJNP protocol structure */
+/***************************/
+
+/* The common protocol header */
+
+struct __attribute__ ((__packed__)) BJNP_command
+{
+ char BJNP_id[4]; /* string: BJNP */
+ uint8_t dev_type; /* 1 = printer, 2 = scanner */
+ /* responses have MSB set */
+ uint8_t cmd_code; /* command code/response code */
+ int16_t unknown1; /* unknown, always 0? */
+ int16_t seq_no; /* sequence number */
+ uint16_t session_id; /* session id for printing */
+ uint32_t payload_len; /* length of command buffer */
+};
+
+/* Layout of the init response buffer */
+
+struct __attribute__ ((__packed__)) DISCOVER_RESPONSE
+{
+ struct BJNP_command response; /* reponse header */
+ char unknown1[4]; /* 00 01 08 00 */
+ char mac_len; /* length of mac address */
+ char addr_len; /* length of address field */
+ unsigned char mac_addr[6]; /* printers mac address */
+ union {
+ struct __attribute__ ((__packed__)) {
+ unsigned char ipv4_addr[4];
+ } ipv4;
+ struct __attribute__ ((__packed__)) {
+ unsigned char ipv6_addr_1[16];
+ unsigned char ipv6_addr_2[16];
+ } ipv6;
+ } addresses;
+};
+
+/* layout of payload for the JOB_DETAILS command */
+
+struct __attribute__ ((__packed__)) JOB_DETAILS
+{
+ struct BJNP_command cmd; /* command header */
+ char unknown[8]; /* don't know what these are for */
+ char hostname[64]; /* hostname of sender */
+ char username[64]; /* username */
+ char jobtitle[256]; /* job title */
+};
+
+/* layout of the poll command, not everything is complete */
+
+struct __attribute__ ((__packed__)) POLL_DETAILS
+{
+ struct BJNP_command cmd; /* command header */
+ uint16_t type; /* 0, 1, 2 or 5 */
+ /* 05 = reset status */
+ union {
+ struct __attribute__ ((__packed__)) {
+ char empty0[78]; /* type 0 has only 0 */
+ } type0; /* length = 80 */
+
+ struct __attribute__ ((__packed__)) {
+ char empty1[6]; /* 0 */
+ char user_host[64]; /* unicode user <space> <space> hostname */
+ uint64_t emtpy2; /* 0 */
+ } type1; /* length = 80 */
+
+ struct __attribute__ ((__packed__)) {
+ uint16_t empty_1; /* 00 00 */
+ uint32_t dialog; /* constant dialog id, from previous response */
+ char user_host[64]; /* unicode user <space> <space> hostname */
+ uint32_t unknown_1; /* 00 00 00 14 */
+ uint32_t empty_2[5]; /* only 0 */
+ uint32_t unknown_2; /* 00 00 00 10 */
+ char ascii_date[16]; /* YYYYMMDDHHMMSS only for type 2 */
+ } type2; /* length = 116 */
+
+ struct __attribute__ ((__packed__)) {
+ uint16_t empty_1; /* 00 00 */
+ uint32_t dialog; /* constant dialog id, from previous response */
+ char user_host[64]; /* unicode user <space> <space> hostname */
+ uint32_t unknown_1; /* 00 00 00 14 */
+ uint32_t key; /* copied from key field in status msg */
+ uint32_t unknown_3[5]; /* only 0 */
+ } type5; /* length = 100 */
+
+ } extensions;
+};
+
+/* the poll response layout */
+
+struct __attribute__ ((__packed__)) POLL_RESPONSE
+{
+ struct BJNP_command cmd; /* command header */
+
+ unsigned char result[4]; /* unknown stuff, result[2] = 80 -> status is available*/
+ /* result[8] is dialog, size? */
+ uint32_t dialog; /* to be returned in next request */
+ uint32_t unknown_2; /* returns the 00 00 00 14 from unknown_2 in request */
+ uint32_t key; /* to be returned in type 5 status reset */
+ unsigned char status[20]; /* interrupt status */
+};
+
+/* Layout of ID and status responses */
+
+struct __attribute__ ((__packed__)) IDENTITY
+{
+ struct BJNP_command cmd;
+ union __attribute__ ((__packed__))
+ {
+ struct __attribute__ ((__packed__)) payload_s
+ {
+ uint16_t id_len; /* length of identity */
+ char id[BJNP_IEEE1284_MAX]; /* identity */
+ } bjnp;
+ struct __attribute__ ((__packed__)) mfnp
+ {
+ char id[BJNP_IEEE1284_MAX];
+ } mfnp;
+ } payload;
+};
+
+
+/* response to TCP print command */
+
+struct __attribute__ ((__packed__)) SCAN_BUF
+{
+ struct BJNP_command cmd;
+ char scan_data[65536];
+};
+
+/**************************/
+/* Local enum definitions */
+/**************************/
+
+typedef enum bjnp_paper_status_e
+{
+ BJNP_PAPER_UNKNOWN = -1,
+ BJNP_PAPER_OK = 0,
+ BJNP_PAPER_OUT = 1
+} bjnp_paper_status_t;
+
+typedef enum
+{
+ BJNP_STATUS_GOOD,
+ BJNP_STATUS_INVAL,
+ BJNP_STATUS_ALREADY_ALLOCATED
+} BJNP_Status;
+
+/* button polling */
+
+typedef enum
+{
+ BJNP_POLL_STOPPED = 0,
+ BJNP_POLL_STARTED = 1,
+ BJNP_POLL_STATUS_RECEIVED = 2
+} BJNP_polling_status_e;
+
+typedef union
+{
+ struct sockaddr_storage storage;
+ struct sockaddr addr;
+ struct sockaddr_in ipv4;
+ struct sockaddr_in6 ipv6;
+} bjnp_sockaddr_t;
+
+typedef enum
+{
+ BJNP_ADDRESS_IS_LINK_LOCAL = 0,
+ BJNP_ADDRESS_IS_GLOBAL = 1,
+ BJNP_ADDRESS_HAS_FQDN = 2
+} bjnp_address_type_t;
+
+
+/*
+ * Device information for opened devices
+ */
+
+typedef struct device_s
+{
+ int open; /* connection to scanner is opened */
+
+ /* protocol version */
+ int protocol;
+ char *protocol_string;
+
+ /* sockets */
+
+ int tcp_socket; /* open tcp socket for communcation to scannner */
+ int16_t serial; /* sequence number of command */
+
+ /* communication state */
+
+ int session_id; /* session id used in bjnp protocol for TCP packets */
+ int last_cmd; /* last command sent */
+
+ /* TCP bulk read state information */
+
+ size_t blocksize; /* size of (TCP) blocks returned by the scanner */
+ size_t scanner_data_left; /* TCP data left from last read request */
+ char last_block; /* last TCP read command was shorter than blocksize */
+
+ /* device information */
+ char mac_address[BJNP_HOST_MAX];
+ /* mac-address, used as device serial no */
+ bjnp_sockaddr_t * addr; /* ip-address of the scanner */
+ int address_level; /* link local, public or has a FQDN */
+ int bjnp_scanner_timeout; /* timeout (msec) for next poll command */
+ int bjnp_ip_timeout; /* device specific min timeout for the IP-protocol */
+
+#ifdef PIXMA_BJNP_USE_STATUS
+ /* polling state information */
+
+ char polling_status; /* status polling ongoing */
+ uint32_t dialog; /* poll dialog */
+ uint32_t status_key; /* key of last received status message */
+#endif
+} bjnp_device_t;
diff --git a/backend/pixma/pixma_common.c b/backend/pixma/pixma_common.c
new file mode 100644
index 0000000..7b7ecec
--- /dev/null
+++ b/backend/pixma/pixma_common.c
@@ -0,0 +1,1187 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#include "../include/sane/config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <math.h> /* pow(C90) */
+
+#include <sys/time.h> /* gettimeofday(4.3BSD) */
+#include <unistd.h> /* usleep */
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+#include "../include/sane/sanei_usb.h"
+#include "../include/sane/sane.h"
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+extern const pixma_config_t pixma_mp150_devices[];
+extern const pixma_config_t pixma_mp750_devices[];
+extern const pixma_config_t pixma_mp730_devices[];
+extern const pixma_config_t pixma_mp800_devices[];
+extern const pixma_config_t pixma_iclass_devices[];
+
+static const pixma_config_t *const pixma_devices[] = {
+ pixma_mp150_devices,
+ pixma_mp750_devices,
+ pixma_mp730_devices,
+ pixma_mp800_devices,
+ pixma_iclass_devices,
+ NULL
+};
+
+static pixma_t *first_pixma = NULL;
+static time_t tstart_sec = 0;
+static uint32_t tstart_usec = 0;
+static int debug_level = 1;
+
+
+#ifndef NDEBUG
+
+static void
+u8tohex (uint8_t x, char *str)
+{
+ static const char hdigit[16] =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+ 'e', 'f'
+ };
+ str[0] = hdigit[(x >> 4) & 0xf];
+ str[1] = hdigit[x & 0xf];
+ str[2] = '\0';
+}
+
+static void
+u32tohex (uint32_t x, char *str)
+{
+ u8tohex (x >> 24, str);
+ u8tohex (x >> 16, str + 2);
+ u8tohex (x >> 8, str + 4);
+ u8tohex (x, str + 6);
+}
+
+void
+pixma_hexdump (int level, const void *d_, unsigned len)
+{
+ const uint8_t *d = (const uint8_t *) (d_);
+ unsigned ofs, c, plen;
+ char line[100]; /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */
+
+ if (level > debug_level)
+ return;
+ if (level == debug_level)
+ /* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */
+ plen = (len > 64) ? 32: len;
+ else
+ plen = len;
+ ofs = 0;
+ while (ofs < plen)
+ {
+ char *p;
+ line[0] = ' ';
+ u32tohex (ofs, line + 1);
+ line[9] = ':';
+ p = line + 10;
+ for (c = 0; c != 16 && (ofs + c) < plen; c++)
+ {
+ u8tohex (d[ofs + c], p);
+ p[2] = ' ';
+ p += 3;
+ if (c == 7)
+ {
+ p[0] = ' ';
+ p++;
+ }
+ }
+ p[0] = '\0';
+ pixma_dbg (level, "%s\n", line);
+ ofs += c;
+ }
+ if (len > plen)
+ pixma_dbg(level, "......\n");
+}
+
+static void
+time2str (char *buf, unsigned size)
+{
+ time_t sec;
+ uint32_t usec;
+
+ pixma_get_time (&sec, &usec);
+ sec -= tstart_sec;
+ if (usec >= tstart_usec)
+ {
+ usec -= tstart_usec;
+ }
+ else
+ {
+ usec = 1000000 + usec - tstart_usec;
+ sec--;
+ }
+ snprintf (buf, size, "%lu.%03u", (unsigned long) sec,
+ (unsigned) (usec / 1000));
+}
+
+void
+pixma_dump (int level, const char *type, const void *data, int len,
+ int size, int max)
+{
+ int actual_len, print_len;
+ char buf[20];
+
+ if (level > debug_level)
+ return;
+ if (debug_level >= 20)
+ max = -1; /* dump every bytes */
+
+ time2str (buf, sizeof (buf));
+ pixma_dbg (level, "%s T=%s len=%d\n", type, buf, len);
+
+ actual_len = (size >= 0) ? size : len;
+ print_len = (max >= 0 && max < actual_len) ? max : actual_len;
+ if (print_len >= 0)
+ {
+ pixma_hexdump (level, data, print_len);
+ if (print_len < actual_len)
+ pixma_dbg (level, " ...\n");
+ }
+ if (len < 0)
+ pixma_dbg (level, " ERROR: %s\n", pixma_strerror (len));
+ pixma_dbg (level, "\n");
+}
+
+
+#endif /* NDEBUG */
+
+/* NOTE: non-reentrant */
+const char *
+pixma_strerror (int error)
+{
+ static char buf[50];
+
+ /* TODO: more human friendly messages */
+ switch (error)
+ {
+ case PIXMA_EIO:
+ return "EIO";
+ case PIXMA_ENODEV:
+ return "ENODEV";
+ case PIXMA_EACCES:
+ return "EACCES";
+ case PIXMA_ENOMEM:
+ return "ENOMEM";
+ case PIXMA_EINVAL:
+ return "EINVAL";
+ case PIXMA_EBUSY:
+ return "EBUSY";
+ case PIXMA_ECANCELED:
+ return "ECANCELED";
+ case PIXMA_ENOTSUP:
+ return "ENOTSUP";
+ case PIXMA_ETIMEDOUT:
+ return "ETIMEDOUT";
+ case PIXMA_EPROTO:
+ return "EPROTO";
+ case PIXMA_EPAPER_JAMMED:
+ return "EPAPER_JAMMED";
+ case PIXMA_ECOVER_OPEN:
+ return "ECOVER_OPEN";
+ case PIXMA_ENO_PAPER:
+ return "ENO_PAPER";
+ case PIXMA_EOF:
+ return "EEOF";
+ }
+ snprintf (buf, sizeof (buf), "EUNKNOWN:%d", error);
+ return buf;
+}
+
+void
+pixma_set_debug_level (int level)
+{
+ debug_level = level;
+}
+
+void
+pixma_set_be16 (uint16_t x, uint8_t * buf)
+{
+ buf[0] = x >> 8;
+ buf[1] = x;
+}
+
+void
+pixma_set_be32 (uint32_t x, uint8_t * buf)
+{
+ buf[0] = x >> 24;
+ buf[1] = x >> 16;
+ buf[2] = x >> 8;
+ buf[3] = x;
+}
+
+uint16_t
+pixma_get_be16 (const uint8_t * buf)
+{
+ return ((uint16_t) buf[0] << 8) | buf[1];
+}
+
+uint32_t
+pixma_get_be32 (const uint8_t * buf)
+{
+ return ((uint32_t) buf[0] << 24) + ((uint32_t) buf[1] << 16) +
+ ((uint32_t) buf[2] << 8) + buf[3];
+}
+
+uint8_t
+pixma_sum_bytes (const void *data, unsigned len)
+{
+ const uint8_t *d = (const uint8_t *) data;
+ unsigned i, sum = 0;
+ for (i = 0; i != len; i++)
+ sum += d[i];
+ return sum;
+}
+
+void
+pixma_sleep (unsigned long usec)
+{
+ usleep (usec);
+}
+
+void
+pixma_get_time (time_t * sec, uint32_t * usec)
+{
+ struct timeval tv;
+ gettimeofday (&tv, NULL);
+ if (sec)
+ *sec = tv.tv_sec;
+ if (usec)
+ *usec = tv.tv_usec;
+}
+
+/* convert 24/48 bit RGB to 8/16 bit ir
+ *
+ * Formular: g = R
+ * drop G + B
+ *
+ * sptr: source color scale buffer
+ * gptr: destination gray scale buffer
+ * c == 3: 24 bit RGB -> 8 bit ir
+ * c == 6: 48 bit RGB -> 16 bit ir
+ */
+uint8_t *
+pixma_r_to_ir (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c)
+{
+ unsigned i;
+
+ /* PDBG (pixma_dbg (4, "*pixma_rgb_to_ir*****\n")); */
+
+ for (i = 0; i < w; i++)
+ {
+ *gptr++ = *sptr++;
+ if (c == 6) *gptr++ = *sptr++; /* 48 bit RGB: high byte */
+ sptr += (c == 6) ? 4 : 2; /* drop G + B */
+ }
+ return gptr;
+}
+
+/* convert 24/48 bit RGB to 8/16 bit grayscale
+ *
+ * Formular: g = (R + G + B) / 3
+ *
+ * sptr: source color scale buffer
+ * gptr: destination gray scale buffer
+ * c == 3: 24 bit RGB -> 8 bit gray
+ * c == 6: 48 bit RGB -> 16 bit gray
+ */
+uint8_t *
+pixma_rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c)
+{
+ unsigned i, j, g;
+
+ /* PDBG (pixma_dbg (4, "*pixma_rgb_to_gray*****\n")); */
+
+ for (i = 0; i < w; i++)
+ {
+ for (j = 0, g = 0; j < 3; j++)
+ {
+ g += *sptr++;
+ if (c == 6) g += (*sptr++ << 8); /* 48 bit RGB: high byte */
+ }
+
+ g /= 3; /* 8 or 16 bit gray */
+ *gptr++ = g;
+ if (c == 6) *gptr++ = (g >> 8); /* 16 bit gray: high byte */
+ }
+ return gptr;
+}
+
+/**
+ * This code was taken from the genesys backend
+ * uses threshold and threshold_curve to control software binarization
+ * @param sp device set up for the scan
+ * @param dst pointer where to store result
+ * @param src pointer to raw data
+ * @param width width of the processed line
+ * @param c 1 for 1-channel single-byte data,
+ * 3 for 3-channel single-byte data,
+ * 6 for double-byte data
+ * */
+uint8_t *
+pixma_binarize_line(pixma_scan_param_t * sp, uint8_t * dst, uint8_t * src, unsigned width, unsigned c)
+{
+ unsigned j, x, windowX, sum = 0;
+ unsigned threshold;
+ unsigned offset, addCol;
+ int dropCol, offsetX;
+ unsigned char mask;
+ uint8_t min, max;
+
+ /* PDBG (pixma_dbg (4, "*pixma_binarize_line***** src = %u, dst = %u, width = %u, c = %u, threshold = %u, threshold_curve = %u *****\n",
+ src, dst, width, c, sp->threshold, sp->threshold_curve)); */
+
+ /* 16 bit grayscale not supported */
+ if (c == 6)
+ {
+ PDBG (pixma_dbg (1, "*pixma_binarize_line***** Error: 16 bit grayscale not supported\n"));
+ return dst;
+ }
+
+ /* first, color convert to grayscale */
+ if (c != 1)
+ pixma_rgb_to_gray(dst, src, width, c);
+
+ /* second, normalize line */
+ min = 255;
+ max = 0;
+ for (x = 0; x < width; x++)
+ {
+ if (src[x] > max)
+ {
+ max = src[x];
+ }
+ if (src[x] < min)
+ {
+ min = src[x];
+ }
+ }
+
+ /* safeguard against dark or white areas */
+ if(min>80)
+ min=0;
+ if(max<80)
+ max=255;
+ for (x = 0; x < width; x++)
+ {
+ src[x] = ((src[x] - min) * 255) / (max - min);
+ }
+
+ /* third, create sliding window, prefill the sliding sum */
+ /* ~1mm works best, but the window needs to have odd # of pixels */
+ windowX = (6 * sp->xdpi) / 150;
+ if (!(windowX % 2))
+ windowX++;
+
+ /* to avoid conflicts with *dst start with offset */
+ offsetX = 1 + (windowX / 2) / 8;
+ for (j = offsetX; j <= windowX; j++)
+ sum += src[j];
+ /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** windowX = %u, startX = %u, sum = %u\n",
+ windowX, startX, sum)); */
+
+ /* fourth, walk the input buffer, output bits */
+ for (j = 0; j < width; j++)
+ {
+ /* output image location */
+ offset = j % 8;
+ mask = 0x80 >> offset;
+ threshold = sp->threshold;
+
+ /* move sum/update threshold only if there is a curve */
+ if (sp->threshold_curve)
+ {
+ addCol = j + windowX / 2;
+ dropCol = addCol - windowX;
+
+ if (dropCol >= offsetX && addCol < width)
+ {
+ sum += src[addCol];
+ sum -= (sum < src[dropCol] ? sum : src[dropCol]); /* no negative sum */
+ }
+ threshold = sp->lineart_lut[sum / windowX];
+ /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** addCol = %u, dropCol = %d, sum = %u, windowX = %u, lut-element = %d, threshold = %u\n",
+ addCol, dropCol, sum, windowX, sum/windowX, threshold)); */
+ }
+
+ /* lookup threshold */
+ if (src[j] > threshold)
+ *dst &= ~mask; /* white */
+ else
+ *dst |= mask; /* black */
+
+ if (offset == 7)
+ dst++;
+ }
+
+ /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** ready: src = %u, dst = %u *****\n", src, dst)); */
+
+ return dst;
+}
+
+/**
+ This code was taken from the genesys backend
+ Function to build a lookup table (LUT), often
+ used by scanners to implement brightness/contrast/gamma
+ or by backends to speed binarization/thresholding
+
+ offset and slope inputs are -127 to +127
+
+ slope rotates line around central input/output val,
+ 0 makes horizontal line
+
+ pos zero neg
+ . x . . x
+ . x . . x
+ out . x .xxxxxxxxxxx . x
+ . x . . x
+ ....x....... ............ .......x....
+ in in in
+
+ offset moves line vertically, and clamps to output range
+ 0 keeps the line crossing the center of the table
+
+ high low
+ . xxxxxxxx .
+ . x .
+ out x . x
+ . . x
+ ............ xxxxxxxx....
+ in in
+
+ out_min/max provide bounds on output values,
+ useful when building thresholding lut.
+ 0 and 255 are good defaults otherwise.
+ * */
+static SANE_Status
+load_lut (unsigned char * lut,
+ int in_bits, int out_bits,
+ int out_min, int out_max,
+ int slope, int offset)
+{
+ int i, j;
+ double shift, rise;
+ int max_in_val = (1 << in_bits) - 1;
+ int max_out_val = (1 << out_bits) - 1;
+ unsigned char * lut_p = lut;
+
+ /* PDBG (pixma_dbg (4, "*load_lut***** start %d %d *****\n", slope, offset)); */
+
+ /* slope is converted to rise per unit run:
+ * first [-127,127] to [-1,1]
+ * then multiply by PI/2 to convert to radians
+ * then take the tangent (T.O.A)
+ * then multiply by the normal linear slope
+ * because the table may not be square, i.e. 1024x256*/
+ rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val;
+
+ /* line must stay vertically centered, so figure
+ * out vertical offset at central input value */
+ shift = (double)max_out_val/2 - (rise*max_in_val/2);
+
+ /* convert the user offset setting to scale of output
+ * first [-127,127] to [-1,1]
+ * then to [-max_out_val/2,max_out_val/2]*/
+ shift += (double)offset / 127 * max_out_val / 2;
+
+ for(i=0;i<=max_in_val;i++){
+ j = rise*i + shift;
+
+ if(j<out_min){
+ j=out_min;
+ }
+ else if(j>out_max){
+ j=out_max;
+ }
+
+ *lut_p=j;
+ lut_p++;
+ }
+
+ /* PDBG (pixma_dbg (4, "*load_lut***** finish *****\n")); */
+ /* PDBG (pixma_hexdump (4, lut, max_in_val+1)); */
+
+ return SANE_STATUS_GOOD;
+}
+
+int
+pixma_map_status_errno (unsigned status)
+{
+ switch (status)
+ {
+ case PIXMA_STATUS_OK:
+ return 0;
+ case PIXMA_STATUS_FAILED:
+ return PIXMA_ECANCELED;
+ case PIXMA_STATUS_BUSY:
+ return PIXMA_EBUSY;
+ default:
+ return PIXMA_EPROTO;
+ }
+}
+
+int
+pixma_check_result (pixma_cmdbuf_t * cb)
+{
+ const uint8_t *r = cb->buf;
+ unsigned header_len = cb->res_header_len;
+ unsigned expected_reslen = cb->expected_reslen;
+ int error;
+ unsigned len;
+
+ if (cb->reslen < 0)
+ return cb->reslen;
+
+ len = (unsigned) cb->reslen;
+ if (len >= header_len)
+ {
+ error = pixma_map_status_errno (pixma_get_be16 (r));
+ if (expected_reslen != 0)
+ {
+ if (len == expected_reslen)
+ {
+ if (pixma_sum_bytes (r + header_len, len - header_len) != 0)
+ error = PIXMA_EPROTO;
+ }
+ else
+ {
+ /* This case will happen when a command cannot be completely
+ executed, e.g. because you press the cancel button. The
+ device will return only a header with PIXMA_STATUS_FAILED. */
+ if (len != header_len)
+ error = PIXMA_EPROTO;
+ }
+ }
+ }
+ else
+ error = PIXMA_EPROTO;
+
+#ifndef NDEBUG
+ if (error == PIXMA_EPROTO)
+ {
+ pixma_dbg (1, "WARNING: result len=%d expected %d\n",
+ len, cb->expected_reslen);
+ pixma_hexdump (1, r, MIN (len, 64));
+ }
+#endif
+ return error;
+}
+
+int
+pixma_cmd_transaction (pixma_t * s, const void *cmd, unsigned cmdlen,
+ void *data, unsigned expected_len)
+{
+ int error, tmo;
+
+ error = pixma_write (s->io, cmd, cmdlen);
+ if (error != (int) cmdlen)
+ {
+ if (error >= 0)
+ {
+ /* Write timeout is too low? */
+ PDBG (pixma_dbg
+ (1, "ERROR: incomplete write, %u out of %u written\n",
+ (unsigned) error, cmdlen));
+ error = PIXMA_ETIMEDOUT;
+ }
+ return error;
+ }
+
+ /* When you send the start_session command while the scanner optic is
+ going back to the home position after the last scan session has been
+ cancelled, you won't get the response before it arrives home. This takes
+ about 5 seconds. If the last session was succeeded, the scanner will
+ immediatly answer with PIXMA_STATUS_BUSY.
+
+ Is 8 seconds timeout enough? This affects ALL commands that use
+ pixma_cmd_transaction(). Default value set in pixma_open(). */
+ tmo = s->rec_tmo;
+ do
+ {
+ error = pixma_read (s->io, data, expected_len);
+ if (error == PIXMA_ETIMEDOUT)
+ {
+ PDBG (pixma_dbg (2, "No response yet. Timed out in %d sec.\n", tmo));
+
+#ifndef HAVE_SANEI_USB_SET_TIMEOUT
+ /* 1s timeout
+ Only needed, if sanei_usb_set_timeout() isn't available.
+ pixma_read() has an internal timeout of 1 sec. */
+ pixma_sleep (1000000);
+#endif
+ }
+ }
+ while (error == PIXMA_ETIMEDOUT && --tmo != 0);
+ if (error < 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Error in response phase. cmd:%02x%02x\n",
+ ((const uint8_t *) cmd)[0],
+ ((const uint8_t *) cmd)[1]));
+ PDBG (pixma_dbg (1," If the scanner hangs, reset it and/or unplug the "
+ "USB cable.\n"));
+ }
+ return error; /* length of the result packet or error */
+}
+
+uint8_t *
+pixma_newcmd (pixma_cmdbuf_t * cb, unsigned cmd,
+ unsigned dataout, unsigned datain)
+{
+ unsigned cmdlen = cb->cmd_header_len + dataout;
+ unsigned reslen = cb->res_header_len + datain;
+
+ if (cmdlen > cb->size || reslen > cb->size)
+ return NULL;
+ memset (cb->buf, 0, cmdlen);
+ cb->cmdlen = cmdlen;
+ cb->expected_reslen = reslen;
+ pixma_set_be16 (cmd, cb->buf);
+ pixma_set_be16 (dataout + datain, cb->buf + cb->cmd_len_field_ofs);
+ if (dataout != 0)
+ return cb->buf + cb->cmd_header_len;
+ else
+ return cb->buf + cb->res_header_len;
+}
+
+int
+pixma_exec (pixma_t * s, pixma_cmdbuf_t * cb)
+{
+ if (cb->cmdlen > cb->cmd_header_len)
+ pixma_fill_checksum (cb->buf + cb->cmd_header_len,
+ cb->buf + cb->cmdlen - 1);
+ cb->reslen =
+ pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf,
+ cb->expected_reslen);
+ return pixma_check_result (cb);
+}
+
+int
+pixma_exec_short_cmd (pixma_t * s, pixma_cmdbuf_t * cb, unsigned cmd)
+{
+ pixma_newcmd (cb, cmd, 0, 0);
+ return pixma_exec (s, cb);
+}
+
+int
+pixma_check_dpi (unsigned dpi, unsigned max)
+{
+ /* valid dpi = 75 * 2^n */
+ unsigned temp = dpi / 75;
+ if (dpi > max || dpi < 75 || 75 * temp != dpi || (temp & (temp - 1)) != 0)
+ return PIXMA_EINVAL;
+ return 0;
+}
+
+
+int
+pixma_init (void)
+{
+ PDBG (pixma_dbg (2, "pixma version %d.%d.%d\n", PIXMA_VERSION_MAJOR,
+ PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD));
+ PASSERT (first_pixma == NULL);
+ if (tstart_sec == 0)
+ pixma_get_time (&tstart_sec, &tstart_usec);
+ return pixma_io_init ();
+}
+
+void
+pixma_cleanup (void)
+{
+ while (first_pixma)
+ pixma_close (first_pixma);
+ pixma_io_cleanup ();
+}
+
+int
+pixma_open (unsigned devnr, pixma_t ** handle)
+{
+ int error;
+ pixma_t *s;
+ const pixma_config_t *cfg;
+
+ *handle = NULL;
+ cfg = pixma_get_device_config (devnr);
+ if (!cfg)
+ return PIXMA_EINVAL; /* invalid devnr */
+ PDBG (pixma_dbg (2, "pixma_open(): %s\n", cfg->name));
+
+ s = (pixma_t *) calloc (1, sizeof (s[0]));
+ if (!s)
+ return PIXMA_ENOMEM;
+ s->next = first_pixma;
+ first_pixma = s;
+
+ s->cfg = cfg;
+ s->rec_tmo = 8; /* set receive timeout to 8 seconds */
+ error = pixma_connect (devnr, &s->io);
+ if (error < 0)
+ {
+ PDBG (pixma_dbg
+ (2, "pixma_connect() failed %s\n", pixma_strerror (error)));
+ goto rollback;
+ }
+ strncpy (s->id, pixma_get_device_id (devnr), sizeof (s->id) - 1);
+ s->ops = s->cfg->ops;
+ s->scanning = 0;
+ error = s->ops->open (s);
+ if (error < 0)
+ goto rollback;
+ error = pixma_deactivate (s->io);
+ if (error < 0)
+ goto rollback;
+ *handle = s;
+ return 0;
+
+rollback:
+ PDBG (pixma_dbg (2, "pixma_open() failed %s\n", pixma_strerror (error)));
+ pixma_close (s);
+ return error;
+}
+
+void
+pixma_close (pixma_t * s)
+{
+ pixma_t **p;
+
+ if (!s)
+ return;
+ for (p = &first_pixma; *p && *p != s; p = &((*p)->next))
+ {
+ }
+ PASSERT (*p);
+ if (!(*p))
+ return;
+ PDBG (pixma_dbg (2, "pixma_close(): %s\n", s->cfg->name));
+ if (s->io)
+ {
+ if (s->scanning)
+ {
+ PDBG (pixma_dbg (3, "pixma_close(): scanning in progress, call"
+ " finish_scan()\n"));
+ s->ops->finish_scan (s);
+ }
+ s->ops->close (s);
+ pixma_disconnect (s->io);
+ }
+ *p = s->next;
+ free (s);
+}
+
+int
+pixma_scan (pixma_t * s, pixma_scan_param_t * sp)
+{
+ int error;
+
+ error = pixma_check_scan_param (s, sp);
+ if (error < 0)
+ return error;
+
+ if (sp->mode == PIXMA_SCAN_MODE_LINEART)
+ {
+ load_lut(sp->lineart_lut, 8, 8, 50, 205,
+ sp->threshold_curve, sp->threshold-127);
+ }
+
+#ifndef NDEBUG
+ pixma_dbg (3, "\n");
+ pixma_dbg (3, "pixma_scan(): start\n");
+ pixma_dbg (3, " line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n",
+ sp->line_size, sp->image_size, sp->channels, sp->depth);
+ pixma_dbg (3, " dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n",
+ sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h);
+ pixma_dbg (3, " gamma_table=%p source=%d\n", sp->gamma_table, sp->source);
+ pixma_dbg (3, " threshold=%d threshold_curve=%d\n", sp->threshold, sp->threshold_curve);
+ pixma_dbg (3, " adf-wait=%d\n", sp->adf_wait);
+ pixma_dbg (3, " ADF page count: %d\n", sp->adf_pageid);
+#endif
+
+ s->param = sp;
+ s->cancel = 0;
+ s->cur_image_size = 0;
+ s->imagebuf.wptr = NULL;
+ s->imagebuf.wend = NULL;
+ s->imagebuf.rptr = NULL;
+ s->imagebuf.rend = NULL;
+ s->underrun = 0;
+ error = s->ops->scan (s);
+ if (error >= 0)
+ {
+ s->scanning = 1;
+ }
+ else
+ {
+ PDBG (pixma_dbg
+ (3, "pixma_scan() failed %s\n", pixma_strerror (error)));
+ }
+
+ return error;
+}
+
+static uint8_t *
+fill_pixels (pixma_t * s, uint8_t * ptr, uint8_t * end, uint8_t value)
+{
+ if (s->cur_image_size < s->param->image_size)
+ {
+ long n = s->param->image_size - s->cur_image_size;
+ if (n > (end - ptr))
+ n = end - ptr;
+ memset (ptr, value, n);
+ s->cur_image_size += n;
+ ptr += n;
+ }
+ return ptr;
+}
+
+int
+pixma_read_image (pixma_t * s, void *buf, unsigned len)
+{
+ int result;
+ pixma_imagebuf_t ib;
+
+ if (!s->scanning)
+ return 0;
+ if (s->cancel)
+ {
+ result = PIXMA_ECANCELED;
+ goto cancel;
+ }
+
+ ib = s->imagebuf; /* get rptr and rend */
+ ib.wptr = (uint8_t *) buf;
+ ib.wend = ib.wptr + len;
+
+ if (s->underrun)
+ {
+ if (s->cur_image_size < s->param->image_size)
+ {
+ ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff);
+ }
+ else
+ {
+ PDBG (pixma_dbg
+ (3, "pixma_read_image(): completed (underrun detected)\n"));
+ s->scanning = 0;
+ }
+ return ib.wptr - (uint8_t *) buf;
+ }
+
+ while (ib.wptr != ib.wend)
+ {
+ if (ib.rptr == ib.rend)
+ {
+ ib.rptr = ib.rend = NULL;
+ result = s->ops->fill_buffer (s, &ib);
+ if (result < 0)
+ goto cancel;
+ if (result == 0)
+ { /* end of image? */
+ s->ops->finish_scan (s);
+ if ((s->cur_image_size != s->param->image_size) && !s->param->mode_jpeg)
+ {
+ pixma_dbg (1, "WARNING:image size mismatches\n");
+ pixma_dbg (1,
+ " %"PRIu64" expected (%d lines) but %"PRIu64" received (%"PRIu64" lines)\n",
+ s->param->image_size, s->param->h,
+ s->cur_image_size,
+ s->cur_image_size / s->param->line_size);
+ if ((s->cur_image_size % s->param->line_size) != 0)
+ {
+ pixma_dbg (1,
+ "BUG:received data not multiple of line_size\n");
+ }
+ }
+ if ((s->cur_image_size < s->param->image_size) && !s->param->mode_jpeg)
+ {
+ s->underrun = 1;
+ ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff);
+ }
+ else
+ {
+ PDBG (pixma_dbg (3, "pixma_read_image():completed\n"));
+ s->scanning = 0;
+ }
+ break;
+ }
+ s->cur_image_size += result;
+
+ PASSERT (s->cur_image_size <= s->param->image_size);
+ }
+ if (ib.rptr)
+ {
+ unsigned count = MIN (ib.rend - ib.rptr, ib.wend - ib.wptr);
+ memcpy (ib.wptr, ib.rptr, count);
+ ib.rptr += count;
+ ib.wptr += count;
+ }
+ }
+ s->imagebuf = ib; /* store rptr and rend */
+ return ib.wptr - (uint8_t *) buf;
+
+cancel:
+ s->ops->finish_scan (s);
+ s->scanning = 0;
+ if (result == PIXMA_ECANCELED)
+ {
+ PDBG (pixma_dbg (3, "pixma_read_image(): cancelled by %sware\n",
+ (s->cancel) ? "soft" : "hard"));
+ }
+ else
+ {
+ PDBG (pixma_dbg (3, "pixma_read_image() failed %s\n",
+ pixma_strerror (result)));
+ }
+ return result;
+}
+
+void
+pixma_cancel (pixma_t * s)
+{
+ s->cancel = 1;
+}
+
+int
+pixma_enable_background (pixma_t * s, int enabled)
+{
+ return pixma_set_interrupt_mode (s->io, enabled);
+}
+
+int
+pixma_activate_connection(pixma_t * s)
+{
+ return pixma_activate (s->io);
+}
+
+int
+pixma_deactivate_connection(pixma_t * s)
+{
+ return pixma_deactivate (s->io);
+}
+
+uint32_t
+pixma_wait_event (pixma_t * s, int timeout /*ms */ )
+{
+ unsigned events;
+
+ if (s->events == PIXMA_EV_NONE && s->ops->wait_event)
+ s->ops->wait_event (s, timeout);
+ events = s->events;
+ s->events = PIXMA_EV_NONE;
+ return events;
+}
+
+#define CLAMP2(x,w,min,max,dpi) do { \
+ unsigned m = (max) * (dpi) / 75; \
+ x = MIN(x, m - min); \
+ w = MIN(w, m - x); \
+ if (w < min) w = min; \
+} while(0)
+
+int
+pixma_check_scan_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ unsigned cfg_xdpi;
+
+ if (!(sp->channels == 3 ||
+ (sp->channels == 1 && (s->cfg->cap & PIXMA_CAP_GRAY) != 0)))
+ return PIXMA_EINVAL;
+
+ /* flatbed: use s->cfg->xdpi
+ * TPU/ADF: use s->cfg->adftpu_max_dpi, if configured with dpi value */
+ cfg_xdpi = ((sp->source == PIXMA_SOURCE_FLATBED
+ || s->cfg->adftpu_max_dpi == 0) ? s->cfg->xdpi
+ : s->cfg->adftpu_max_dpi);
+
+ if (pixma_check_dpi (sp->xdpi, cfg_xdpi) < 0 ||
+ pixma_check_dpi (sp->ydpi, s->cfg->ydpi) < 0)
+ return PIXMA_EINVAL;
+
+ /* xdpi must be equal to ydpi except that
+ xdpi = max_xdpi and ydpi = max_ydpi. */
+ if (!(sp->xdpi == sp->ydpi ||
+ (sp->xdpi == cfg_xdpi && sp->ydpi == s->cfg->ydpi)))
+ return PIXMA_EINVAL;
+
+ if (s->ops->check_param (s, sp) < 0)
+ return PIXMA_EINVAL;
+
+ /* FIXME: I assume the same minimum width and height for every model.
+ * new scanners need minimum 16 px height
+ * minimum image size: 16 px x 16 px */
+ CLAMP2 (sp->x, sp->w, 16, s->cfg->width, sp->xdpi);
+ CLAMP2 (sp->y, sp->h, 16, s->cfg->height, sp->ydpi);
+
+ switch (sp->source)
+ {
+ case PIXMA_SOURCE_FLATBED:
+ break;
+
+ case PIXMA_SOURCE_TPU:
+ if ((s->cfg->cap & PIXMA_CAP_TPU) != PIXMA_CAP_TPU)
+ {
+ sp->source = PIXMA_SOURCE_FLATBED;
+ PDBG (pixma_dbg
+ (1, "WARNING: TPU unsupported, fallback to flatbed.\n"));
+ }
+ break;
+
+ case PIXMA_SOURCE_ADF:
+ if ((s->cfg->cap & PIXMA_CAP_ADF) != PIXMA_CAP_ADF)
+ {
+ sp->source = PIXMA_SOURCE_FLATBED;
+ PDBG (pixma_dbg
+ (1, "WARNING: ADF unsupported, fallback to flatbed.\n"));
+ }
+ break;
+
+ case PIXMA_SOURCE_ADFDUP:
+ if ((s->cfg->cap & PIXMA_CAP_ADFDUP) != PIXMA_CAP_ADFDUP)
+ {
+ if (s->cfg->cap & PIXMA_CAP_ADF)
+ {
+ sp->source = PIXMA_SOURCE_ADF;
+ }
+ else
+ {
+ sp->source = PIXMA_SOURCE_FLATBED;
+ }
+ PDBG (pixma_dbg
+ (1, "WARNING: ADF duplex unsupported, fallback to %d.\n",
+ sp->source));
+ }
+ break;
+ }
+
+ if (sp->depth == 0)
+ sp->depth = 8;
+ if ((sp->depth % 8) != 0 && sp->depth != 1)
+ return PIXMA_EINVAL;
+
+ sp->line_size = 0;
+
+ if (s->ops->check_param (s, sp) < 0)
+ return PIXMA_EINVAL;
+
+ if (sp->line_size == 0)
+ sp->line_size = sp->depth / 8 * sp->channels * sp->w;
+ sp->image_size = sp->line_size * sp->h;
+
+ /* image_size for software lineart is counted in bits */
+ if (sp->software_lineart == 1)
+ sp->image_size /= 8;
+ return 0;
+}
+
+const char *
+pixma_get_string (pixma_t * s, pixma_string_index_t i)
+{
+ switch (i)
+ {
+ case PIXMA_STRING_MODEL:
+ return s->cfg->name;
+ case PIXMA_STRING_ID:
+ return s->id;
+ case PIXMA_STRING_LAST:
+ return NULL;
+ }
+ return NULL;
+}
+
+const pixma_config_t *
+pixma_get_config (pixma_t * s)
+{
+ return s->cfg;
+}
+
+void
+pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n)
+{
+ int i;
+ double r_gamma = 1.0 / gamma;
+ double out_scale = 255.0;
+ double in_scale = 1.0 / (n - 1);
+
+ for (i = 0; (unsigned) i != n; i++)
+ {
+ table[i] = (int) (out_scale * pow (i * in_scale, r_gamma) + 0.5);
+ }
+}
+
+int
+pixma_find_scanners (const char **conf_devices, SANE_Bool local_only)
+{
+ return pixma_collect_devices (conf_devices, pixma_devices, local_only);
+}
+
+const char *
+pixma_get_device_model (unsigned devnr)
+{
+ const pixma_config_t *cfg = pixma_get_device_config (devnr);
+ return (cfg) ? cfg->name : NULL;
+}
+
+
+int
+pixma_get_device_status (pixma_t * s, pixma_device_status_t * status)
+{
+ if (!status)
+ return PIXMA_EINVAL;
+ memset (status, 0, sizeof (*status));
+ return s->ops->get_status (s, status);
+}
diff --git a/backend/pixma/pixma_common.h b/backend/pixma/pixma_common.h
new file mode 100644
index 0000000..c0ed4ba
--- /dev/null
+++ b/backend/pixma/pixma_common.h
@@ -0,0 +1,232 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#ifndef PIXMA_COMMON_H
+#define PIXMA_COMMON_H
+
+
+#include <time.h> /* time_t */
+#include "pixma.h"
+
+
+/*! \defgroup subdriver Subdriver Interface
+ * \brief Subdriver interface. */
+
+/*! \defgroup debug Debug utilities
+ * \brief Debug utilities. */
+
+#ifdef NDEBUG
+# define PDBG(x) do {} while(0)
+# define PASSERT(x) do {} while(0)
+#else
+# define PDBG(x) x
+# define PASSERT(x) do { \
+ if (!(x)) \
+ pixma_dbg(1, "ASSERT failed:%s:%d: " \
+ #x "\n", __FILE__, __LINE__); \
+ } while(0)
+#endif
+
+
+#define PIXMA_STATUS_OK 0x0606
+#define PIXMA_STATUS_FAILED 0x1515
+#define PIXMA_STATUS_BUSY 0x1414
+
+#define PIXMA_MAX_ID_LEN 30
+
+/* These may have been defined elsewhere */
+#ifndef MIN
+#define MIN(x,y) (((x) < (y)) ? (x):(y))
+#endif
+#ifndef MAX
+#define MAX(x,y) (((x) < (y)) ? (y):(x))
+#endif
+#define ALIGN_SUP(x,n) (((x) + (n) - 1) / (n) * (n))
+#define ALIGN_INF(x,n) (((x) / (n)) * (n))
+
+struct pixma_io_t;
+
+struct pixma_limits_t
+{
+ unsigned xdpi, ydpi;
+ unsigned width, height;
+};
+
+struct pixma_cmdbuf_t
+{
+ unsigned cmd_header_len, res_header_len, cmd_len_field_ofs;
+ unsigned expected_reslen, cmdlen;
+ int reslen;
+ unsigned size;
+ uint8_t *buf;
+};
+
+struct pixma_imagebuf_t
+{
+ uint8_t *wptr, *wend;
+ const uint8_t *rptr, *rend;
+};
+
+struct pixma_t
+{
+ pixma_t *next;
+ struct pixma_io_t *io;
+ const pixma_scan_ops_t *ops;
+ pixma_scan_param_t *param;
+ const pixma_config_t *cfg;
+ char id[PIXMA_MAX_ID_LEN + 1];
+ int cancel; /* NOTE: It can be set in a signal handler. */
+ uint32_t events;
+ void *subdriver; /* can be used by model driver. */
+ int rec_tmo; /* receive timeout [s] */
+
+ /* private */
+ uint64_t cur_image_size;
+ pixma_imagebuf_t imagebuf;
+ unsigned scanning:1;
+ unsigned underrun:1;
+};
+
+/** \addtogroup subdriver
+ * @{ */
+/** Scan operations for subdriver. */
+struct pixma_scan_ops_t
+{
+ /** Allocate a data structure for the subdriver. It is called after the
+ * core driver connected to the scanner. The subdriver should reset the
+ * scanner to a known state in this function. */
+ int (*open) (pixma_t *);
+
+ /** Free resources allocated by the subdriver. Don't forget to send abort
+ * command to the scanner if it is scanning. */
+ void (*close) (pixma_t *);
+
+ /** Setup the scanner for scan parameters defined in \a s->param. */
+ int (*scan) (pixma_t * s);
+
+ /** Fill a buffer with image data. The subdriver has two choices:
+ * -# Fill the buffer pointed by ib->wptr directly and leave
+ * ib->rptr and ib->rend untouched. The length of the buffer is
+ * ib->wend - ib->wptr. It must update ib->wptr accordingly.
+ * -# Update ib->rptr and ib->rend to point to the beginning and
+ * the end of the internal buffer resp. The length of the buffer
+ * is ib->rend - ib->rptr. This function is called again if
+ * and only if pixma_read_image() has copied the whole buffer.
+ *
+ * The subdriver must wait until there is at least one byte to read or
+ * return 0 for the end of image. */
+ int (*fill_buffer) (pixma_t *, pixma_imagebuf_t * ib);
+
+ /** Cancel the scan operation if necessary and free resources allocated in
+ * scan(). */
+ void (*finish_scan) (pixma_t *);
+
+ /** [Optional] Wait for a user's event, e.g. button event. \a timeout is
+ * in milliseconds. If an event occured before it's timed out, flags in
+ * \a s->events should be set accordingly.
+ * \see PIXMA_EV_* */
+ void (*wait_event) (pixma_t * s, int timeout);
+
+ /** Check the scan parameters. The parameters can be adjusted if they are
+ * out of range, e.g. width > max_width. */
+ int (*check_param) (pixma_t *, pixma_scan_param_t *);
+
+ /** Read the device status. \see pixma_get_device_status() */
+ int (*get_status) (pixma_t *, pixma_device_status_t *);
+};
+
+
+/** \name Funtions for read and write big-endian integer values */
+/**@{*/
+void pixma_set_be16 (uint16_t x, uint8_t * buf);
+void pixma_set_be32 (uint32_t x, uint8_t * buf);
+uint16_t pixma_get_be16 (const uint8_t * buf);
+uint32_t pixma_get_be32 (const uint8_t * buf);
+/**@}*/
+
+/** \name Utility functions */
+/**@{*/
+uint8_t pixma_sum_bytes (const void *data, unsigned len);
+int pixma_check_dpi (unsigned dpi, unsigned max);
+void pixma_sleep (unsigned long usec);
+void pixma_get_time (time_t * sec, uint32_t * usec);
+uint8_t * pixma_r_to_ir (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c);
+uint8_t * pixma_rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c);
+uint8_t * pixma_binarize_line(pixma_scan_param_t *, uint8_t * dst, uint8_t * src, unsigned width, unsigned c);
+/**@}*/
+
+/** \name Command related functions */
+/**@{*/
+int pixma_cmd_transaction (pixma_t *, const void *cmd, unsigned cmdlen,
+ void *data, unsigned expected_len);
+int pixma_check_result (pixma_cmdbuf_t *);
+uint8_t *pixma_newcmd (pixma_cmdbuf_t *, unsigned cmd,
+ unsigned dataout, unsigned datain);
+int pixma_exec (pixma_t *, pixma_cmdbuf_t *);
+int pixma_exec_short_cmd (pixma_t *, pixma_cmdbuf_t *, unsigned cmd);
+int pixma_map_status_errno (unsigned status);
+/**@}*/
+
+#define pixma_fill_checksum(start, end) do { \
+ *(end) = -pixma_sum_bytes(start, (end)-(start)); \
+} while(0)
+
+/** @} end of group subdriver */
+
+/** \addtogroup debug
+ * @{ */
+void pixma_set_debug_level (int level);
+#ifndef NDEBUG
+void pixma_hexdump (int level, const void *d_, unsigned len);
+
+/* len: length of data or error code.
+ size: if >= 0, force to print 'size' bytes.
+ max: maximum number of bytes to print(-1 means no limit). */
+void pixma_dump (int level, const char *type, const void *data, int len,
+ int size, int max);
+# define DEBUG_DECLARE_ONLY
+# include "../include/sane/sanei_debug.h"
+#endif /* NDEBUG */
+/** @} end of group debug */
+
+#endif
diff --git a/backend/pixma/pixma_imageclass.c b/backend/pixma/pixma_imageclass.c
new file mode 100644
index 0000000..ce0c37d
--- /dev/null
+++ b/backend/pixma/pixma_imageclass.c
@@ -0,0 +1,985 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2009 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2008 Dennis Lou, dlou 99 at yahoo dot com
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+
+/*
+ * imageCLASS backend based on pixma_mp730.c
+ */
+
+#include "../include/sane/config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+#define IMAGE_BLOCK_SIZE (0x80000)
+#define MAX_CHUNK_SIZE (0x1000)
+#define MIN_CHUNK_SIZE (0x0200)
+#define CMDBUF_SIZE 512
+
+#define MF4100_PID 0x26a3
+#define MF4600_PID 0x26b0
+#define MF4010_PID 0x26b4
+#define MF4200_PID 0x26b5
+#define MF4360_PID 0x26ec
+#define D480_PID 0x26ed
+#define MF4320_PID 0x26ee
+#define D420_PID 0x26ef
+#define MF3200_PID 0x2684
+#define MF6500_PID 0x2686
+/* generation 2 scanners (>=0x2707) */
+#define MF8300_PID 0x2708
+#define MF4500_PID 0x2736
+#define MF4410_PID 0x2737
+#define D550_PID 0x2738
+#define MF3010_PID 0x2759
+#define MF4570_PID 0x275a
+#define MF4800_PID 0x2773
+#define MF4700_PID 0x2774
+#define MF8200_PID 0x2779
+/* the following are all untested */
+#define MF5630_PID 0x264e
+#define MF5650_PID 0x264f
+#define MF8100_PID 0x2659
+#define MF5880_PID 0x26f9
+#define MF6680_PID 0x26fa
+#define MF8030_PID 0x2707
+#define IR1133_PID 0x2742
+#define MF5900_PID 0x2743
+#define D530_PID 0x2775
+#define MF8500_PID 0x277a
+#define MF6100_PID 0x278e
+#define MF820_PID 0x27a6
+#define MF220_PID 0x27a8
+#define MF210_PID 0x27a9
+#define MF620_PID 0x27b4
+#define MF410_PID 0x27c0
+#define MF510_PID 0x27c2
+#define MF230_PID 0x27d1
+#define MF240_PID 0x27d2
+#define MF630_PID 0x27e1
+#define MF634_PID 0x27e2
+#define MF730_PID 0x27e4
+#define MF731_PID 0x27e5
+#define D570_PID 0x27e8
+#define MF110_PID 0x27ed
+#define MF520_PID 0x27f0
+#define MF420_PID 0x27f1
+#define MF260_PID 0x27f4
+#define MF740_PID 0x27fb
+#define MF743_PID 0x27fc
+#define MF640_PID 0x27fe
+#define MF645_PID 0x27fd
+
+
+enum iclass_state_t
+{
+ state_idle,
+ state_warmup, /* MF4200 always warm/calibrated; others? */
+ state_scanning,
+ state_finished
+};
+
+enum iclass_cmd_t
+{
+ cmd_start_session = 0xdb20,
+ cmd_select_source = 0xdd20,
+ cmd_scan_param = 0xde20,
+ cmd_status = 0xf320,
+ cmd_abort_session = 0xef20,
+ cmd_read_image = 0xd420,
+ cmd_read_image2 = 0xd460, /* New multifunctionals, such as MF4410 */
+ cmd_error_info = 0xff20,
+
+ cmd_activate = 0xcf60
+};
+
+typedef struct iclass_t
+{
+ enum iclass_state_t state;
+ pixma_cmdbuf_t cb;
+ unsigned raw_width;
+ uint8_t current_status[12];
+
+ uint8_t *buf, *blkptr, *lineptr;
+ unsigned buf_len, blk_len;
+
+ unsigned last_block;
+
+ uint8_t generation; /* New multifunctionals are (generation == 2) */
+
+ uint8_t adf_state; /* handle adf scanning */
+} iclass_t;
+
+
+static int is_scanning_from_adf (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADF
+ || s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static int is_scanning_from_adfdup (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static void iclass_finish_scan (pixma_t * s);
+
+/* checksumming is sometimes different than pixmas */
+static int
+iclass_exec (pixma_t * s, pixma_cmdbuf_t * cb, char invcksum)
+{
+ if (cb->cmdlen > cb->cmd_header_len)
+ pixma_fill_checksum (cb->buf + cb->cmd_header_len,
+ cb->buf + cb->cmdlen - 2);
+ cb->buf[cb->cmdlen - 1] = invcksum ? -cb->buf[cb->cmdlen - 2] : 0;
+ cb->reslen =
+ pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf,
+ cb->expected_reslen);
+ return pixma_check_result (cb);
+}
+
+static int
+has_paper (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ return ((mf->current_status[1] & 0x0f) == 0 /* allow 0x10 as ADF paper OK */
+ || mf->current_status[1] == 81); /* allow 0x51 as ADF paper OK */
+}
+
+static int
+abort_session (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mf->cb, cmd_abort_session);
+}
+
+static int
+query_status (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mf->cb, cmd_status, 0, 12);
+ error = pixma_exec (s, &mf->cb);
+ if (error >= 0)
+ {
+ memcpy (mf->current_status, data, 12);
+ /*DBG (3, "Current status: paper=0x%02x cal=%u lamp=%u\n",
+ data[1], data[8], data[7]);*/
+ PDBG (pixma_dbg (3, "Current status: paper=0x%02x cal=%u lamp=%u\n",
+ data[1], data[8], data[7]));
+ }
+ return error;
+}
+
+static int
+activate (pixma_t * s, uint8_t x)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mf->cb, cmd_activate, 10, 0);
+ data[0] = 1;
+ data[3] = x;
+ switch (s->cfg->pid)
+ {
+ case MF4200_PID:
+ case MF4600_PID:
+ case MF6500_PID:
+ case D480_PID:
+ case D420_PID:
+ case MF4360_PID:
+ case MF4100_PID:
+ case MF8300_PID:
+ return iclass_exec (s, &mf->cb, 1);
+ break;
+ default:
+ return pixma_exec (s, &mf->cb);
+ }
+}
+
+static int
+start_session (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mf->cb, cmd_start_session);
+}
+
+static int
+select_source (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mf->cb, cmd_select_source, 10, 0);
+ data[0] = (is_scanning_from_adf(s)) ? 2 : 1;
+ /* special settings for MF6100 */
+ data[5] = is_scanning_from_adfdup(s) ? 3 : ((s->cfg->pid == MF6100_PID && s->param->source == PIXMA_SOURCE_ADF) ? 1 : 0);
+ switch (s->cfg->pid)
+ {
+ case MF4200_PID:
+ case MF4600_PID:
+ case MF6500_PID:
+ case D480_PID:
+ case D420_PID:
+ case MF4360_PID:
+ case MF4100_PID:
+ case MF8300_PID:
+ return iclass_exec (s, &mf->cb, 0);
+ break;
+ default:
+ return pixma_exec (s, &mf->cb);
+ }
+}
+
+static int
+send_scan_param (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *data;
+
+ data = pixma_newcmd (&mf->cb, cmd_scan_param, 0x2e, 0);
+ pixma_set_be16 (s->param->xdpi | 0x1000, data + 0x04);
+ pixma_set_be16 (s->param->ydpi | 0x1000, data + 0x06);
+ pixma_set_be32 (s->param->x, data + 0x08);
+ pixma_set_be32 (s->param->y, data + 0x0c);
+ pixma_set_be32 (mf->raw_width, data + 0x10);
+ pixma_set_be32 (s->param->h, data + 0x14);
+ data[0x18] = (s->param->channels == 1) ? 0x04 : 0x08;
+ data[0x19] = s->param->channels * ((s->param->depth == 1) ? 8 : s->param->depth); /* bits per pixel */
+ data[0x1f] = 0x7f;
+ data[0x20] = 0xff;
+ data[0x23] = 0x81;
+ switch (s->cfg->pid)
+ {
+ case MF4200_PID:
+ case MF4600_PID:
+ case MF6500_PID:
+ case D480_PID:
+ case D420_PID:
+ case MF4360_PID:
+ case MF4100_PID:
+ case MF8300_PID:
+ return iclass_exec (s, &mf->cb, 0);
+ break;
+ default:
+ return pixma_exec (s, &mf->cb);
+ }
+}
+
+static int
+request_image_block (pixma_t * s, unsigned flag, uint8_t * info,
+ unsigned * size, uint8_t * data, unsigned * datalen)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ int error;
+ unsigned expected_len;
+ const int hlen = 2 + 6;
+
+ memset (mf->cb.buf, 0, 11);
+ /* generation 2 scanners use cmd_read_image2.
+ * MF6100, ... are exceptions */
+ pixma_set_be16 (((mf->generation >= 2
+ && s->cfg->pid != MF6100_PID) ? cmd_read_image2 : cmd_read_image), mf->cb.buf);
+ mf->cb.buf[8] = flag;
+ mf->cb.buf[10] = 0x06;
+ expected_len = (mf->generation >= 2 ||
+ s->cfg->pid == MF4600_PID ||
+ s->cfg->pid == MF6500_PID ||
+ s->cfg->pid == MF8030_PID) ? 512 : hlen;
+ mf->cb.reslen = pixma_cmd_transaction (s, mf->cb.buf, 11, mf->cb.buf, expected_len);
+ if (mf->cb.reslen >= hlen)
+ {
+ *info = mf->cb.buf[2];
+ *size = pixma_get_be16 (mf->cb.buf + 6); /* 16bit size */
+ error = 0;
+
+ if (mf->generation >= 2 ||
+ s->cfg->pid == MF4600_PID ||
+ s->cfg->pid == MF6500_PID ||
+ s->cfg->pid == MF8030_PID)
+ { /* 32bit size */
+ *datalen = mf->cb.reslen - hlen;
+ *size = (*datalen + hlen == 512) ? pixma_get_be32 (mf->cb.buf + 4) - *datalen : *size;
+ memcpy (data, mf->cb.buf + hlen, *datalen);
+ }
+ PDBG (pixma_dbg (11, "*request_image_block***** size = %u *****\n", *size));
+ }
+ else
+ {
+ error = PIXMA_EPROTO;
+ }
+ return error;
+}
+
+static int
+read_image_block (pixma_t * s, uint8_t * data, unsigned size)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ int error;
+ unsigned maxchunksize, chunksize, count = 0;
+
+ maxchunksize = MAX_CHUNK_SIZE * ((mf->generation >= 2 ||
+ s->cfg->pid == MF4600_PID ||
+ s->cfg->pid == MF6500_PID ||
+ s->cfg->pid == MF8030_PID) ? 4 : 1);
+ while (size)
+ {
+ if (size >= maxchunksize)
+ chunksize = maxchunksize;
+ else if (size < MIN_CHUNK_SIZE)
+ chunksize = size;
+ else
+ chunksize = size - (size % MIN_CHUNK_SIZE);
+ error = pixma_read (s->io, data, chunksize);
+ if (error < 0)
+ return count;
+ count += error;
+ data += error;
+ size -= error;
+ }
+ return count;
+}
+
+static int
+read_error_info (pixma_t * s, void *buf, unsigned size)
+{
+ unsigned len = 16;
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mf->cb, cmd_error_info, 0, len);
+ switch (s->cfg->pid)
+ {
+ case MF4200_PID:
+ case MF4600_PID:
+ case MF6500_PID:
+ case D480_PID:
+ case D420_PID:
+ case MF4360_PID:
+ case MF4100_PID:
+ case MF8300_PID:
+ error = iclass_exec (s, &mf->cb, 0);
+ break;
+ default:
+ error = pixma_exec (s, &mf->cb);
+ }
+ if (error < 0)
+ return error;
+ if (buf && len < size)
+ {
+ size = len;
+ /* NOTE: I've absolutely no idea what the returned data mean. */
+ memcpy (buf, data, size);
+ error = len;
+ }
+ return error;
+}
+
+static int
+handle_interrupt (pixma_t * s, int timeout)
+{
+ uint8_t buf[16];
+ int len;
+
+ len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout);
+ if (len == PIXMA_ETIMEDOUT)
+ return 0;
+ if (len < 0)
+ return len;
+ if (len != 16)
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING:unexpected interrupt packet length %d\n", len));
+ return PIXMA_EPROTO;
+ }
+ if (buf[12] & 0x40)
+ query_status (s);
+ if (buf[15] & 1)
+ s->events = PIXMA_EV_BUTTON1;
+ return 1;
+}
+
+static int
+step1 (pixma_t * s)
+{
+ int error;
+ int rec_tmo;
+ iclass_t *mf = (iclass_t *) s->subdriver;
+
+ /* don't wait full timeout for 1st command */
+ rec_tmo = s->rec_tmo; /* save globel timeout */
+ s->rec_tmo = 2; /* set timeout to 2 seconds */
+ error = query_status (s);
+ s->rec_tmo = rec_tmo; /* restore global timeout */
+ if (error < 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Resend first USB command after timeout!\n"));
+ error = query_status (s);
+ }
+ if (error < 0)
+ return error;
+
+ /* wait for inserted paper */
+ if (s->param->adf_wait != 0 && is_scanning_from_adf(s))
+ {
+ int tmo = s->param->adf_wait;
+
+ while (!has_paper (s) && --tmo >= 0 && !s->param->frontend_cancel)
+ {
+ if ((error = query_status (s)) < 0)
+ return error;
+ pixma_sleep (1000000);
+ PDBG (pixma_dbg(2, "No paper in ADF. Timed out in %d sec.\n", tmo));
+ }
+ /* canceled from frontend */
+ if (s->param->frontend_cancel)
+ {
+ return PIXMA_ECANCELED;
+ }
+ }
+ /* no paper inserted
+ * => abort session */
+ if (is_scanning_from_adf(s) && !has_paper (s))
+ {
+ return PIXMA_ENO_PAPER;
+ }
+ /* activate only seen for generation 1 scanners */
+ if (mf->generation == 1)
+ {
+ if (error >= 0)
+ error = activate (s, 0);
+ if (error >= 0)
+ error = activate (s, 4);
+ }
+ return error;
+}
+
+/* line in=rrr... ggg... bbb... line out=rgbrgbrgb... */
+static void
+pack_rgb (const uint8_t * src, unsigned nlines, unsigned w, uint8_t * dst)
+{
+ unsigned w2, stride;
+
+ w2 = 2 * w;
+ stride = 3 * w;
+ for (; nlines != 0; nlines--)
+ {
+ unsigned x;
+ for (x = 0; x != w; x++)
+ {
+ *dst++ = src[x + 0];
+ *dst++ = src[x + w];
+ *dst++ = src[x + w2];
+ }
+ src += stride;
+ }
+}
+
+static int
+iclass_open (pixma_t * s)
+{
+ iclass_t *mf;
+ uint8_t *buf;
+
+ mf = (iclass_t *) calloc (1, sizeof (*mf));
+ if (!mf)
+ return PIXMA_ENOMEM;
+
+ buf = (uint8_t *) malloc (CMDBUF_SIZE);
+ if (!buf)
+ {
+ free (mf);
+ return PIXMA_ENOMEM;
+ }
+
+ s->subdriver = mf;
+ mf->state = state_idle;
+
+ mf->cb.buf = buf;
+ mf->cb.size = CMDBUF_SIZE;
+ mf->cb.res_header_len = 2;
+ mf->cb.cmd_header_len = 10;
+ mf->cb.cmd_len_field_ofs = 7;
+
+ /* adf scanning */
+ mf->adf_state = state_idle;
+
+ /* set generation = 2 for new multifunctionals */
+ mf->generation = (s->cfg->pid >= MF8030_PID) ? 2 : 1;
+ PDBG (pixma_dbg (3, "*iclass_open***** This is a generation %d scanner. *****\n", mf->generation));
+
+ PDBG (pixma_dbg (3, "Trying to clear the interrupt buffer...\n"));
+ if (handle_interrupt (s, 200) == 0)
+ {
+ PDBG (pixma_dbg (3, " no packets in buffer\n"));
+ }
+ return 0;
+}
+
+static void
+iclass_close (pixma_t * s)
+{
+ iclass_t *mf = (iclass_t *) s->subdriver;
+
+ iclass_finish_scan (s);
+ free (mf->cb.buf);
+ free (mf->buf);
+ free (mf);
+ s->subdriver = NULL;
+}
+
+static int
+iclass_check_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ UNUSED (s);
+
+ /* PDBG (pixma_dbg (4, "*iclass_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, line_size=%" PRIu64 " , h=%u*****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->line_size, sp->h)); */
+
+ sp->depth = 8;
+ sp->software_lineart = 0;
+ if (sp->mode == PIXMA_SCAN_MODE_LINEART)
+ {
+ sp->software_lineart = 1;
+ sp->channels = 1;
+ sp->depth = 1;
+ }
+
+ if (sp->software_lineart == 1)
+ {
+ unsigned w_max;
+
+ /* for software lineart line_size and w must be a multiple of 8 */
+ sp->line_size = ALIGN_SUP (sp->w, 8) * sp->channels;
+ sp->w = ALIGN_SUP (sp->w, 8);
+
+ /* do not exceed the scanner capability */
+ w_max = s->cfg->width * s->cfg->xdpi / 75;
+ w_max -= w_max % 32;
+ if (sp->w > w_max)
+ sp->w = w_max;
+ }
+ else
+ sp->line_size = ALIGN_SUP (sp->w, 32) * sp->channels;
+
+ /* Some exceptions here for particular devices */
+ /* Those devices can scan up to Legal 14" with ADF, but A4 11.7" in flatbed */
+ /* PIXMA_CAP_ADF also works for PIXMA_CAP_ADFDUP */
+ if ((s->cfg->cap & PIXMA_CAP_ADF) && sp->source == PIXMA_SOURCE_FLATBED)
+ sp->h = MIN (sp->h, 877 * sp->xdpi / 75);
+
+ /* PDBG (pixma_dbg (4, "*iclass_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, line_size=%" PRIu64 " , h=%u*****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->line_size, sp->h)); */
+
+ return 0;
+}
+
+static int
+iclass_scan (pixma_t * s)
+{
+ int error, n;
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ uint8_t *buf, ignore;
+ unsigned buf_len, ignore2;
+
+ if (mf->state != state_idle)
+ return PIXMA_EBUSY;
+
+ /* clear interrupt packets buffer */
+ while (handle_interrupt (s, 0) > 0)
+ {
+ }
+
+ mf->raw_width = ALIGN_SUP (s->param->w, 32);
+ PDBG (pixma_dbg (3, "raw_width = %u\n", mf->raw_width));
+
+ n = IMAGE_BLOCK_SIZE / s->param->line_size + 1;
+ buf_len = (n + 1) * s->param->line_size + IMAGE_BLOCK_SIZE;
+ if (buf_len > mf->buf_len)
+ {
+ buf = (uint8_t *) realloc (mf->buf, buf_len);
+ if (!buf)
+ return PIXMA_ENOMEM;
+ mf->buf = buf;
+ mf->buf_len = buf_len;
+ }
+ mf->lineptr = mf->buf;
+ mf->blkptr = mf->buf + n * s->param->line_size;
+ mf->blk_len = 0;
+
+ error = step1 (s);
+ if (error >= 0
+ && (s->param->adf_pageid == 0 || mf->generation == 1 || mf->adf_state == state_idle))
+ { /* single sheet or first sheet from ADF */
+ PDBG (pixma_dbg (3, "*iclass_scan***** start scanning *****\n"));
+ error = start_session (s);
+ if (error >= 0)
+ mf->state = state_scanning;
+ if (error >= 0)
+ error = select_source (s);
+ }
+ else if (error >= 0)
+ { /* next sheet from ADF */
+ PDBG (pixma_dbg (3, "*iclass_scan***** scan next sheet from ADF *****\n"));
+ mf->state = state_scanning;
+ }
+ if (error >= 0)
+ error = send_scan_param (s);
+ if (error >= 0)
+ error = request_image_block (s, 0, &ignore, &ignore2, &ignore, &ignore2);
+ if (error < 0)
+ {
+ iclass_finish_scan (s);
+ return error;
+ }
+ mf->last_block = 0;
+
+ /* ADF scanning active */
+ if (is_scanning_from_adf (s))
+ mf->adf_state = state_scanning;
+ return 0;
+}
+
+
+static int
+iclass_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ int error, n;
+ iclass_t *mf = (iclass_t *) s->subdriver;
+ unsigned block_size, lines_size, lineart_lines_size, first_block_size;
+ uint8_t info;
+
+/*
+ * 1. send a block request cmd (d4 20 00... 04 00 06)
+ * 2. examine the response for block size and/or end-of-scan flag
+ * 3. read the block one chunk at a time
+ * 4. repeat until have enough to process >=1 lines
+ */
+ do
+ {
+ do
+ {
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ if (mf->last_block)
+ {
+ /* end of image */
+ mf->state = state_finished;
+ return 0;
+ }
+
+ first_block_size = 0;
+ error = request_image_block (s, 4, &info, &block_size,
+ mf->blkptr + mf->blk_len, &first_block_size);
+ /* add current block to remainder of previous */
+ mf->blk_len += first_block_size;
+ if (error < 0)
+ {
+ /* NOTE: seen in traffic logs but don't know the meaning. */
+ read_error_info (s, NULL, 0);
+ if (error == PIXMA_ECANCELED)
+ return error;
+ }
+
+ /* info: 0x28 = end; 0x38 = end + ADF empty */
+ mf->last_block = info & 0x38;
+ if ((info & ~0x38) != 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n"));
+ PDBG (pixma_hexdump (1, &info, 1));
+ }
+
+ if (block_size == 0)
+ {
+ /* no image data at this moment. */
+ /*pixma_sleep(100000); *//* FIXME: too short, too long? */
+ handle_interrupt (s, 100);
+ }
+ }
+ while (block_size == 0 && first_block_size == 0);
+
+ error = read_image_block (s, mf->blkptr + mf->blk_len, block_size);
+ block_size = error;
+ if (error < 0)
+ return error;
+
+ /* add current block to remainder of previous */
+ mf->blk_len += block_size;
+ /* n = number of full lines (rows) we have in the buffer. */
+ n = mf->blk_len / ((s->param->mode == PIXMA_SCAN_MODE_LINEART) ? mf->raw_width : s->param->line_size);
+ if (n != 0)
+ {
+ /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** Processing with n=%d, w=%i, line_size=%" PRIu64 ", raw_width=%u ***** \n",
+ n, s->param->w, s->param->line_size, mf->raw_width)); */
+ /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** scan_mode=%d, lineptr=%" PRIu64 ", blkptr=%" PRIu64 " \n",
+ s->param->mode, (uint64_t)mf->lineptr, (uint64_t)mf->blkptr)); */
+
+ /* gray to lineart convert
+ * mf->lineptr : image line
+ * mf->blkptr : scanned image block as grayscale
+ * s->param->w : image width
+ * s->param->line_size : scanned image width */
+ if (s->param->mode == PIXMA_SCAN_MODE_LINEART)
+ {
+ int i;
+ uint8_t *sptr, *dptr;
+
+ /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** Processing lineart *****\n")); */
+
+ /* process ALL lines */
+ sptr = mf->blkptr;
+ dptr = mf->lineptr;
+ for (i = 0; i < n; i++, sptr += mf->raw_width)
+ dptr = pixma_binarize_line (s->param, dptr, sptr, s->param->line_size, 1);
+ }
+ else if (s->param->channels != 1 &&
+ mf->generation == 1 &&
+ s->cfg->pid != MF4600_PID &&
+ s->cfg->pid != MF6500_PID &&
+ s->cfg->pid != MF8030_PID)
+ {
+ /* color and not MF46xx or MF65xx */
+ pack_rgb (mf->blkptr, n, mf->raw_width, mf->lineptr);
+ }
+ else
+ {
+ /* grayscale */
+ memcpy (mf->lineptr, mf->blkptr, n * s->param->line_size);
+ }
+ /* cull remainder and shift left */
+ lineart_lines_size = n * s->param->line_size / 8;
+ lines_size = n * ((s->param->mode == PIXMA_SCAN_MODE_LINEART) ? mf->raw_width : s->param->line_size);
+ mf->blk_len -= lines_size;
+ memcpy (mf->blkptr, mf->blkptr + lines_size, mf->blk_len);
+ }
+ }
+ while (n == 0);
+
+ /* output full lines, keep partial lines for next block
+ * ib->rptr : start of image buffer
+ * ib->rend : end of image buffer */
+ ib->rptr = mf->lineptr;
+ ib->rend = mf->lineptr + (s->param->mode == PIXMA_SCAN_MODE_LINEART ? lineart_lines_size : lines_size);
+ /* PDBG (pixma_dbg (4, "*iclass_fill_buffer***** rptr=%" PRIu64 ", rend=%" PRIu64 ", diff=%ld \n",
+ (uint64_t)ib->rptr, (uint64_t)ib->rend, ib->rend - ib->rptr)); */
+ return ib->rend - ib->rptr;
+}
+
+static void
+iclass_finish_scan (pixma_t * s)
+{
+ int error;
+ iclass_t *mf = (iclass_t *) s->subdriver;
+
+ switch (mf->state)
+ {
+ /* fall through */
+ case state_warmup:
+ case state_scanning:
+ error = abort_session (s);
+ if (error < 0)
+ PDBG (pixma_dbg
+ (1, "WARNING:abort_session() failed %s\n",
+ pixma_strerror (error)));
+ /* fall through */
+ case state_finished:
+ query_status (s);
+ query_status (s);
+ if (mf->generation == 1)
+ { /* activate only seen for generation 1 scanners */
+ activate (s, 0);
+ query_status (s);
+ }
+ /* generation = 1:
+ * 0x28 = last block (no multi page scan)
+ * generation >= 2:
+ * 0x38 = last block and ADF empty (generation >= 2)
+ * 0x28 = last block and Paper in ADF (multi page scan)
+ * some generation 2 scanners don't use 0x38 for ADF empty => check status */
+ if (mf->last_block==0x38 /* generation 2 scanner ADF empty */
+ || (mf->generation == 1 && mf->last_block == 0x28) /* generation 1 scanner last block */
+ || (mf->generation >= 2 && !has_paper(s))) /* check status: no paper in ADF */
+ {
+ /* ADFDUP scan: wait for 8sec to throw last page out of ADF feeder */
+ if (is_scanning_from_adfdup(s))
+ {
+ PDBG (pixma_dbg (4, "*iclass_finish_scan***** sleep for 8s *****\n"));
+ pixma_sleep(8000000); /* sleep for 8s */
+ query_status (s);
+ }
+ PDBG (pixma_dbg (3, "*iclass_finish_scan***** abort session *****\n"));
+ abort_session (s);
+ mf->adf_state = state_idle;
+ mf->last_block = 0;
+ }
+ else
+ PDBG (pixma_dbg (3, "*iclass_finish_scan***** wait for next page from ADF *****\n"));
+
+ mf->state = state_idle;
+ /* fall through */
+ case state_idle:
+ break;
+ }
+}
+
+static void
+iclass_wait_event (pixma_t * s, int timeout)
+{
+ /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for
+ * instance. */
+ while (s->events == 0 && handle_interrupt (s, timeout) > 0)
+ {
+ }
+}
+
+static int
+iclass_get_status (pixma_t * s, pixma_device_status_t * status)
+{
+ int error;
+
+ error = query_status (s);
+ if (error < 0)
+ return error;
+ status->hardware = PIXMA_HARDWARE_OK;
+ status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
+ return 0;
+}
+
+
+static const pixma_scan_ops_t pixma_iclass_ops = {
+ iclass_open,
+ iclass_close,
+ iclass_scan,
+ iclass_fill_buffer,
+ iclass_finish_scan,
+ iclass_wait_event,
+ iclass_check_param,
+ iclass_get_status
+};
+
+#define DEV(name, model, pid, dpi, adftpu_max_dpi, w, h, cap) { \
+ name, /* name */ \
+ model, /* model */ \
+ 0x04a9, pid, /* vid pid */ \
+ 1, /* iface */ \
+ &pixma_iclass_ops, /* ops */ \
+ 0, /* min_xdpi not used in this subdriver */ \
+ dpi, dpi, /* xdpi, ydpi */ \
+ 0, /* adftpu_min_dpi not used in this subdriver */ \
+ adftpu_max_dpi, /* adftpu_max_dpi */ \
+ 0, 0, /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */ \
+ w, h, /* width, height */ \
+ PIXMA_CAP_LINEART| /* all scanners have software lineart */ \
+ PIXMA_CAP_ADF_WAIT| /* adf wait for all ADF and ADFDUP scanners */ \
+ PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap \
+}
+const pixma_config_t pixma_iclass_devices[] = {
+ DEV ("Canon imageCLASS MF4270", "MF4270", MF4200_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF4150", "MF4100", MF4100_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF4690", "MF4690", MF4600_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS D420", "D420", D420_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageCLASS D480", "D480", D480_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageCLASS MF4360", "MF4360", MF4360_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageCLASS MF4320", "MF4320", MF4320_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF4010", "MF4010", MF4010_PID, 600, 0, 640, 877, 0),
+ DEV ("Canon imageCLASS MF3240", "MF3240", MF3200_PID, 600, 0, 640, 877, 0),
+ DEV ("Canon imageClass MF6500", "MF6500", MF6500_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF4410", "MF4410", MF4410_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS D550", "D550", D550_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF4500 Series", "MF4500", MF4500_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF3010", "MF3010", MF3010_PID, 600, 0, 640, 877, 0),
+ DEV ("Canon i-SENSYS MF4700 Series", "MF4700", MF4700_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF4800 Series", "MF4800", MF4800_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF4570dw", "MF4570dw", MF4570_PID, 600, 0, 640, 877, 0),
+ DEV ("Canon i-SENSYS MF8200C Series", "MF8200C", MF8200_PID, 600, 300, 640, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF8300 Series", "MF8300", MF8300_PID, 600, 0, 640, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS D530", "D530", D530_PID, 600, 0, 640, 877, 0),
+ /* FIXME: the following capabilities all need updating/verifying */
+ DEV ("Canon imageCLASS MF5630", "MF5630", MF5630_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon laserBase MF5650", "MF5650", MF5650_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageCLASS MF8170c", "MF8170c", MF8100_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon imageClass MF8030", "MF8030", MF8030_PID, 600, 0, 640, 877, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF5880dn", "MF5880", MF5880_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF6680dn", "MF6680", MF6680_PID, 600, 0, 640, 877, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageRUNNER 1133", "iR1133", IR1133_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP), /* max. w = 216mm */
+ DEV ("Canon i-SENSYS MF5900 Series", "MF5900", MF5900_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF8500C Series", "MF8500C", MF8500_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF6100 Series", "MF6100", MF6100_PID, 600, 300, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageClass MF810/820", "MF810/820", MF820_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF220 Series", "MF220", MF220_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), /* max. w = 216mm */
+ DEV ("Canon i-SENSYS MF210 Series", "MF210", MF210_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF), /* max. w = 216mm */
+ DEV ("Canon i-SENSYS MF620 Series", "MF620", MF620_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF),
+ DEV ("Canon i-SENSYS MF410 Series", "MF410", MF410_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), /* max. w = 216mm */
+ DEV ("Canon i-SENSYS MF510 Series", "MF510", MF510_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF230 Series", "MF230", MF230_PID, 600, 0, 637, 1050, PIXMA_CAP_ADF), /* max. w = 216mm */
+ DEV ("Canon i-SENSYS MF240 Series", "MF240", MF240_PID, 600, 300, 634, 1050, PIXMA_CAP_ADF), /* max. w = 215mm, */
+ /* TODO: fix black stripes for 216mm @ 600dpi */
+ DEV ("Canon i-SENSYS MF630 Series", "MF630", MF630_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF730 Series", "MF730", MF730_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF731C", "MF731", MF731_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF633C/MF635C", "MF633C/635C", MF630_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP), /* max. w = 216mm */
+ DEV ("Canon imageCLASS MF634C", "MF632C/634C", MF634_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon imageCLASS MF733C", "MF731C/733C", MF731_PID, 600, 0, 637, 1050, PIXMA_CAP_ADFDUP), /* however, we need this for ethernet/wifi */
+ DEV ("Canon imageCLASS D570", "D570", D570_PID, 600, 0, 640, 877, 0),
+ DEV ("Canon i-SENSYS MF110 Series", "MF110", MF110_PID, 600, 0, 640, 1050, 0),
+ DEV ("Canon i-SENSYS MF520 Series", "MF520", MF520_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF420 Series", "MF420", MF420_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF260 Series", "MF260", MF260_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF740 Series", "MF740", MF740_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF741C/743C", "MF741C/743C", MF743_PID, 600, 300, 640, 1050, PIXMA_CAP_ADFDUP), /* ADFDUP restricted to 300dpi */
+ DEV ("Canon i-SENSYS MF640 Series", "MF642C/643C/644C", MF640_PID, 600, 0, 640, 1050, PIXMA_CAP_ADFDUP),
+ DEV ("Canon i-SENSYS MF645C", "MF645C", MF645_PID, 600, 0, 637, 877, PIXMA_CAP_ADFDUP), /* max. w = 216mm */
+ DEV (NULL, NULL, 0, 0, 0, 0, 0, 0)
+};
diff --git a/backend/pixma/pixma_io.h b/backend/pixma/pixma_io.h
new file mode 100644
index 0000000..715bab5
--- /dev/null
+++ b/backend/pixma/pixma_io.h
@@ -0,0 +1,186 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#ifndef PIXMA_IO_H
+#define PIXMA_IO_H
+
+/* TODO: move to pixma_common.h, to reduce the number of files */
+
+/*!
+ * \defgroup IO IO interface
+ * \brief The IO interface.
+ *
+ * Return value of functions that return \c int if not otherwise specified:
+ * - >= if succeeded
+ * - < 0 if failed (e.g. \c PIXMA_ETIMEDOUT)
+ * .
+ * @{
+ */
+
+/** Timeout for pixma_read() in milliseconds */
+#define PIXMA_BULKIN_TIMEOUT 1000
+/** Timeout for pixma_write() in milliseconds */
+#define PIXMA_BULKOUT_TIMEOUT 1000
+
+
+struct pixma_io_t;
+struct pixma_config_t;
+
+/** IO handle */
+typedef struct pixma_io_t pixma_io_t;
+
+
+/** Initialize IO module. It must be called before any other functions in this
+ * module.
+ * \return 0 on success or
+ * - \c PIXMA_ENOMEM
+ * - \c PIXMA_EACCES
+ * - \c PIXMA_EIO */
+int pixma_io_init (void);
+
+/** Shutdown all connections and free resources allocated in this module. */
+void pixma_io_cleanup (void);
+
+/** Find devices currently connected to the computer.
+ * \c devnr passed to functions
+ * - pixma_get_device_config()
+ * - pixma_get_device_id()
+ * - pixma_connect()
+ * .
+ * should be less than the number of devices returned by this function.
+ * \param[in] pixma_devices A \c NULL terminated array of pointers to
+ * array of pixma_config_t which is terminated by setting
+ * pixma_config_t::name to \c NULL.
+ * \return Number of devices found */
+unsigned pixma_collect_devices (const char ** conf_devices,
+ const struct pixma_config_t *const
+ pixma_devices[], SANE_Bool local_only);
+
+/** Get device configuration. */
+const struct pixma_config_t *pixma_get_device_config (unsigned devnr);
+
+/** Get a unique ID of the device \a devnr. */
+const char *pixma_get_device_id (unsigned devnr);
+
+/** Connect to the device and claim the scanner interface.
+ * \param[in] devnr
+ * \param[out] handle
+ * \return 0 on success or
+ * - \c PIXMA_ENODEV the device is gone from the system.
+ * - \c PIXMA_EINVAL \a devnr is invalid.
+ * - \c PIXMA_EBUSY
+ * - \c PIXMA_EACCES
+ * - \c PIXMA_ENOMEM
+ * - \c PIXMA_EIO */
+int pixma_connect (unsigned devnr, pixma_io_t ** handle);
+
+/** Release the scanner interface and disconnect from the device. */
+void pixma_disconnect (pixma_io_t *);
+
+/** Activate connection to scanner */
+int pixma_activate (pixma_io_t *);
+
+/** De-activate connection to scanner */
+int pixma_deactivate (pixma_io_t *);
+
+/** Reset the USB interface. \warning Use with care! */
+int pixma_reset_device (pixma_io_t *);
+
+/** Write data to the device. This function may not be interrupted by signals.
+ * It will return iff
+ * - \a len bytes have been successfully written or
+ * - an error (inclusive timeout) occured.
+ * .
+ * \note Calling pixma_write(io, buf, n1) and pixma(io, buf+n1, n2) may
+ * not be the same as pixma_write(io, buf, n1+n2) if n1 is not
+ * multiple of the maximum packet size of the endpoint.
+ * \param[in] cmd Data
+ * \param[in] len Length of data
+ * \return Number of bytes successfully written (always = \a len) or
+ * - \c PIXMA_ETIMEDOUT
+ * - \c PIXMA_EIO
+ * - \c PIXMA_ENOMEM
+ * \see #PIXMA_BULKOUT_TIMEOUT */
+int pixma_write (pixma_io_t *, const void *cmd, unsigned len);
+
+/** Read data from the device. This function may not be interrupted by signals.
+ * It will return iff
+ * - \a size bytes have been successfully read,
+ * - a short packet has been read or
+ * - an error (inclusive timeout) occured.
+ * .
+ * \param[out] buf
+ * \param[in] size of the buffer
+ * \return Number of bytes successfully read. A return value of zero means that
+ * a zero length USB packet was received. Or
+ * - \c PIXMA_ETIMEDOUT
+ * - \c PIXMA_EIO
+ * - \c PIXMA_ENOMEM
+ * \see #PIXMA_BULKIN_TIMEOUT */
+int pixma_read (pixma_io_t *, void *buf, unsigned size);
+
+/** Wait for an interrupt. This function can be interrupted by signals.
+ * \a size should be less than or equal to the maximum packet size.
+ * \param[out] buf
+ * \param[in] size of the buffer
+ * \param[in] timeout in milliseconds; if < 0, wait forever.
+ * \return Number of bytes successfully read or
+ * - \c PIXMA_ETIMEDOUT
+ * - \c PIXMA_EIO
+ * - \c PIXMA_ENOMEM
+ * - \c PIXMA_ECANCELED if it was interrupted by a signal. */
+int pixma_wait_interrupt (pixma_io_t *, void *buf, unsigned size,
+ int timeout);
+
+/** Enable or disable background interrupt monitoring.
+ * Background mode is enabled by default.
+ * \param[in] background if not zero, a URB is submitted in background
+ * for interrupt endpoint.
+ * \return 0 on success or
+ * - \c PIXMA_ENOTSUP
+ * - \c PIXMA_EIO
+ * - \c PIXMA_ENOMEM */
+int pixma_set_interrupt_mode (pixma_io_t *, int background);
+
+/** @} end of IO group */
+
+#endif
diff --git a/backend/pixma/pixma_io_sanei.c b/backend/pixma/pixma_io_sanei.c
new file mode 100644
index 0000000..c30b404
--- /dev/null
+++ b/backend/pixma/pixma_io_sanei.c
@@ -0,0 +1,556 @@
+/* SANE - Scanner Access Now Easy.
+ * For limitations, see function sanei_usb_get_vendor_product().
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#include "../include/sane/config.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h> /* INT_MAX */
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+#include "pixma_bjnp.h"
+
+#include "../include/sane/sanei_usb.h"
+#include "../include/sane/sane.h"
+
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+/* MAC OS X does not support timeouts in darwin/libusb interrupt reads
+ * This is a very basic turnaround for MAC OS X
+ * Button scan will not work with this wrapper */
+#ifdef __APPLE__
+# define sanei_usb_read_int sanei_usb_read_bulk
+#endif
+
+
+struct pixma_io_t
+{
+ pixma_io_t *next;
+ int interface;
+ SANE_Int dev;
+};
+
+typedef struct scanner_info_t
+{
+ struct scanner_info_t *next;
+ char *devname;
+ int interface;
+ const pixma_config_t *cfg;
+ char serial[PIXMA_MAX_ID_LEN + 1]; /* "xxxxyyyy_zzzzzzz..."
+ x = vid, y = pid, z = serial */
+} scanner_info_t;
+
+#define INT_USB 0
+#define INT_BJNP 1
+
+static scanner_info_t *first_scanner = NULL;
+static pixma_io_t *first_io = NULL;
+static unsigned nscanners;
+
+
+static scanner_info_t *
+get_scanner_info (unsigned devnr)
+{
+ scanner_info_t *si;
+ for (si = first_scanner; si && devnr != 0; --devnr, si = si->next)
+ {
+ }
+ return si;
+}
+
+static SANE_Status
+attach (SANE_String_Const devname)
+{
+ scanner_info_t *si;
+
+ si = (scanner_info_t *) calloc (1, sizeof (*si));
+ if (!si)
+ return SANE_STATUS_NO_MEM;
+ si->devname = strdup (devname);
+ if (!si->devname)
+ return SANE_STATUS_NO_MEM;
+ si -> interface = INT_USB;
+ si->next = first_scanner;
+ first_scanner = si;
+ nscanners++;
+ return SANE_STATUS_GOOD;
+}
+
+
+static SANE_Status
+attach_bjnp (SANE_String_Const devname,
+ SANE_String_Const serial,
+ const struct pixma_config_t *cfg)
+{
+ scanner_info_t *si;
+
+ si = (scanner_info_t *) calloc (1, sizeof (*si));
+ if (!si)
+ return SANE_STATUS_NO_MEM;
+ si->devname = strdup (devname);
+ if (!si->devname)
+ return SANE_STATUS_NO_MEM;
+
+ si->cfg = cfg;
+ sprintf(si->serial, "%s_%s", cfg->model, serial);
+ si -> interface = INT_BJNP;
+ si->next = first_scanner;
+ first_scanner = si;
+ nscanners++;
+ return SANE_STATUS_GOOD;
+}
+
+static void
+clear_scanner_list (void)
+{
+ scanner_info_t *si = first_scanner;
+ while (si)
+ {
+ scanner_info_t *temp = si;
+ free (si->devname);
+ si = si->next;
+ free (temp);
+ }
+ nscanners = 0;
+ first_scanner = NULL;
+}
+
+static SANE_Status
+get_descriptor (SANE_Int dn, SANE_Int type, SANE_Int descidx,
+ SANE_Int index, SANE_Int length, SANE_Byte * data)
+{
+ return sanei_usb_control_msg (dn, 0x80, USB_REQ_GET_DESCRIPTOR,
+ ((type & 0xff) << 8) | (descidx & 0xff),
+ index, length, data);
+}
+
+static SANE_Status
+get_string_descriptor (SANE_Int dn, SANE_Int index, SANE_Int lang,
+ SANE_Int length, SANE_Byte * data)
+{
+ return get_descriptor (dn, USB_DT_STRING, index, lang, length, data);
+}
+
+static void
+u16tohex (uint16_t x, char *str)
+{
+ static const char hdigit[16] =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+ 'E', 'F'
+ };
+ str[0] = hdigit[(x >> 12) & 0xf];
+ str[1] = hdigit[(x >> 8) & 0xf];
+ str[2] = hdigit[(x >> 4) & 0xf];
+ str[3] = hdigit[x & 0xf];
+ str[4] = '\0';
+}
+
+static void
+read_serial_number (scanner_info_t * si)
+{
+ uint8_t unicode[2 * (PIXMA_MAX_ID_LEN - 9) + 2];
+ uint8_t ddesc[18];
+ int iSerialNumber;
+ SANE_Int usb;
+ char *serial = si->serial;
+
+ u16tohex (si->cfg->vid, serial);
+ u16tohex (si->cfg->pid, serial + 4);
+
+ if (SANE_STATUS_GOOD != sanei_usb_open (si->devname, &usb))
+ return;
+ if (get_descriptor (usb, USB_DT_DEVICE, 0, 0, 18, ddesc)
+ != SANE_STATUS_GOOD)
+ goto done;
+ iSerialNumber = ddesc[16];
+ if (iSerialNumber != 0)
+ {
+ int i, len;
+ SANE_Status status;
+
+ /*int iSerialNumber = ddesc[16];*/
+ /* Read the first language code. Assumed that there is at least one. */
+ if (get_string_descriptor (usb, 0, 0, 4, unicode) != SANE_STATUS_GOOD)
+ goto done;
+ /* Read the serial number string. */
+ status = get_string_descriptor (usb, iSerialNumber,
+ unicode[3] * 256 + unicode[2],
+ sizeof (unicode), unicode);
+ if (status != SANE_STATUS_GOOD)
+ goto done;
+ /* Assumed charset: Latin1 */
+ len = unicode[0];
+ if (len > (int) sizeof (unicode))
+ {
+ len = sizeof (unicode);
+ PDBG (pixma_dbg (1, "WARNING:Truncated serial number\n"));
+ }
+ serial[8] = '_';
+ for (i = 2; i < len; i += 2)
+ {
+ serial[9 + i / 2 - 1] = unicode[i];
+ }
+ serial[9 + i / 2 - 1] = '\0';
+ }
+ else
+ {
+ PDBG (pixma_dbg (1, "WARNING:No serial number\n"));
+ }
+done:
+ sanei_usb_close (usb);
+}
+
+static int
+map_error (SANE_Status ss)
+{
+ switch (ss)
+ {
+ case SANE_STATUS_GOOD:
+ return 0;
+ case SANE_STATUS_UNSUPPORTED:
+ return PIXMA_ENODEV;
+ case SANE_STATUS_DEVICE_BUSY:
+ return PIXMA_EBUSY;
+ case SANE_STATUS_INVAL:
+ return PIXMA_EINVAL;
+ case SANE_STATUS_IO_ERROR:
+ return PIXMA_EIO;
+ case SANE_STATUS_NO_MEM:
+ return PIXMA_ENOMEM;
+ case SANE_STATUS_ACCESS_DENIED:
+ return PIXMA_EACCES;
+ case SANE_STATUS_CANCELLED:
+ return PIXMA_ECANCELED;
+ case SANE_STATUS_JAMMED:
+ return PIXMA_EPAPER_JAMMED;
+ case SANE_STATUS_COVER_OPEN:
+ return PIXMA_ECOVER_OPEN;
+ case SANE_STATUS_NO_DOCS:
+ return PIXMA_ENO_PAPER;
+ case SANE_STATUS_EOF:
+ return PIXMA_EOF;
+#ifdef SANE_STATUS_HW_LOCKED
+ case SANE_STATUS_HW_LOCKED: /* unused by pixma */
+#endif
+#ifdef SANE_STATUS_WARMING_UP
+ case SANE_STATUS_WARMING_UP: /* unused by pixma */
+#endif
+ break;
+ }
+ PDBG (pixma_dbg (1, "BUG:Unmapped SANE Status code %d\n", ss));
+ return PIXMA_EIO; /* should not happen */
+}
+
+
+int
+pixma_io_init (void)
+{
+ sanei_usb_init ();
+ sanei_bjnp_init();
+ nscanners = 0;
+ return 0;
+}
+
+void
+pixma_io_cleanup (void)
+{
+ while (first_io)
+ pixma_disconnect (first_io);
+ clear_scanner_list ();
+}
+
+unsigned
+pixma_collect_devices (const char **conf_devices,
+ const struct pixma_config_t *const pixma_devices[], SANE_Bool local_only)
+{
+ unsigned i, j;
+ struct scanner_info_t *si;
+ const struct pixma_config_t *cfg;
+
+ clear_scanner_list ();
+ j = 0;
+ for (i = 0; pixma_devices[i]; i++)
+ {
+ for (cfg = pixma_devices[i]; cfg->name; cfg++)
+ {
+ sanei_usb_find_devices (cfg->vid, cfg->pid, attach);
+ si = first_scanner;
+ while (j < nscanners)
+ {
+ PDBG (pixma_dbg (3, "pixma_collect_devices() found %s at %s\n",
+ cfg->name, si->devname));
+ si->cfg = cfg;
+ read_serial_number (si);
+ si = si->next;
+ j++;
+ }
+ }
+ }
+ if (! local_only)
+ sanei_bjnp_find_devices(conf_devices, attach_bjnp, pixma_devices);
+
+ si = first_scanner;
+ while (j < nscanners)
+ {
+ PDBG (pixma_dbg (3, "pixma_collect_devices() found %s at %s\n",
+ si->cfg->name, si->devname));
+ si = si->next;
+ j++;
+
+ }
+ return nscanners;
+}
+
+const pixma_config_t *
+pixma_get_device_config (unsigned devnr)
+{
+ const scanner_info_t *si = get_scanner_info (devnr);
+ return (si) ? si->cfg : NULL;
+}
+
+const char *
+pixma_get_device_id (unsigned devnr)
+{
+ const scanner_info_t *si = get_scanner_info (devnr);
+ return (si) ? si->serial : NULL;
+}
+
+int
+pixma_connect (unsigned devnr, pixma_io_t ** handle)
+{
+ pixma_io_t *io;
+ SANE_Int dev;
+ const scanner_info_t *si;
+ int error;
+
+ *handle = NULL;
+ si = get_scanner_info (devnr);
+ if (!si)
+ return PIXMA_EINVAL;
+ if (si-> interface == INT_BJNP)
+ error = map_error (sanei_bjnp_open (si->devname, &dev));
+ else
+ error = map_error (sanei_usb_open (si->devname, &dev));
+
+ if (error < 0)
+ return error;
+ io = (pixma_io_t *) calloc (1, sizeof (*io));
+ if (!io)
+ {
+ if (si -> interface == INT_BJNP)
+ sanei_bjnp_close (dev);
+ else
+ sanei_usb_close (dev);
+ return PIXMA_ENOMEM;
+ }
+ io->next = first_io;
+ first_io = io;
+ io->dev = dev;
+ io->interface = si->interface;
+ *handle = io;
+ return 0;
+}
+
+
+void
+pixma_disconnect (pixma_io_t * io)
+{
+ pixma_io_t **p;
+
+ if (!io)
+ return;
+ for (p = &first_io; *p && *p != io; p = &((*p)->next))
+ {
+ }
+ PASSERT (*p);
+ if (!(*p))
+ return;
+ if (io-> interface == INT_BJNP)
+ sanei_bjnp_close (io->dev);
+ else
+ sanei_usb_close (io->dev);
+ *p = io->next;
+ free (io);
+}
+
+int pixma_activate (pixma_io_t * io)
+{
+ int error;
+ if (io->interface == INT_BJNP)
+ {
+ error = map_error(sanei_bjnp_activate (io->dev));
+ }
+ else
+ /* noop for USB interface */
+ error = 0;
+ return error;
+}
+
+int pixma_deactivate (pixma_io_t * io)
+{
+ int error;
+ if (io->interface == INT_BJNP)
+ {
+ error = map_error(sanei_bjnp_deactivate (io->dev));
+ }
+ else
+ /* noop for USB interface */
+ error = 0;
+ return error;
+
+}
+
+int
+pixma_reset_device (pixma_io_t * io)
+{
+ UNUSED (io);
+ return PIXMA_ENOTSUP;
+}
+
+int
+pixma_write (pixma_io_t * io, const void *cmd, unsigned len)
+{
+ size_t count = len;
+ int error;
+
+ if (io->interface == INT_BJNP)
+ {
+ sanei_bjnp_set_timeout (io->dev, PIXMA_BULKOUT_TIMEOUT);
+ error = map_error (sanei_bjnp_write_bulk (io->dev, cmd, &count));
+ }
+ else
+ {
+#ifdef HAVE_SANEI_USB_SET_TIMEOUT
+ sanei_usb_set_timeout (PIXMA_BULKOUT_TIMEOUT);
+#endif
+ error = map_error (sanei_usb_write_bulk (io->dev, cmd, &count));
+ }
+ if (error == PIXMA_EIO)
+ error = PIXMA_ETIMEDOUT; /* FIXME: SANE doesn't have ETIMEDOUT!! */
+ if (count != len)
+ {
+ PDBG (pixma_dbg (1, "WARNING:pixma_write(): count(%u) != len(%u)\n",
+ (unsigned) count, len));
+ error = PIXMA_EIO;
+ }
+ if (error >= 0)
+ error = count;
+ PDBG (pixma_dump (10, "OUT ", cmd, error, len, 128));
+ return error;
+}
+
+int
+pixma_read (pixma_io_t * io, void *buf, unsigned size)
+{
+ size_t count = size;
+ int error;
+
+ if (io-> interface == INT_BJNP)
+ {
+ sanei_bjnp_set_timeout (io->dev, PIXMA_BULKIN_TIMEOUT);
+ error = map_error (sanei_bjnp_read_bulk (io->dev, buf, &count));
+ }
+ else
+ {
+#ifdef HAVE_SANEI_USB_SET_TIMEOUT
+ sanei_usb_set_timeout (PIXMA_BULKIN_TIMEOUT);
+#endif
+ error = map_error (sanei_usb_read_bulk (io->dev, buf, &count));
+ }
+
+ if (error == PIXMA_EIO)
+ error = PIXMA_ETIMEDOUT; /* FIXME: SANE doesn't have ETIMEDOUT!! */
+ if (error >= 0)
+ error = count;
+ PDBG (pixma_dump (10, "IN ", buf, error, -1, 128));
+ return error;
+}
+
+int
+pixma_wait_interrupt (pixma_io_t * io, void *buf, unsigned size, int timeout)
+{
+ size_t count = size;
+ int error;
+
+ /* FIXME: What is the meaning of "timeout" in sanei_usb? */
+ if (timeout < 0)
+ timeout = INT_MAX;
+ else if (timeout < 100)
+ timeout = 100;
+ if (io-> interface == INT_BJNP)
+ {
+ sanei_bjnp_set_timeout (io->dev, timeout);
+ error = map_error (sanei_bjnp_read_int (io->dev, buf, &count));
+ }
+ else
+ {
+#ifdef HAVE_SANEI_USB_SET_TIMEOUT
+ sanei_usb_set_timeout (timeout);
+#endif
+ error = map_error (sanei_usb_read_int (io->dev, buf, &count));
+ }
+ if (error == PIXMA_EIO ||
+ (io->interface == INT_BJNP && error == PIXMA_EOF)) /* EOF is a bjnp timeout error! */
+ error = PIXMA_ETIMEDOUT; /* FIXME: SANE doesn't have ETIMEDOUT!! */
+ if (error == 0)
+ error = count;
+ if (error != PIXMA_ETIMEDOUT)
+ PDBG (pixma_dump (10, "INTR", buf, error, -1, -1));
+ return error;
+}
+
+int
+pixma_set_interrupt_mode (pixma_io_t * s, int background)
+{
+ UNUSED (s);
+ return (background) ? PIXMA_ENOTSUP : 0;
+}
diff --git a/backend/pixma/pixma_mp150.c b/backend/pixma/pixma_mp150.c
new file mode 100644
index 0000000..3973702
--- /dev/null
+++ b/backend/pixma/pixma_mp150.c
@@ -0,0 +1,1817 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2009 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+/* test cases
+ 1. short USB packet (must be no -ETIMEDOUT)
+ 2. cancel using button on the printer (look for abort command)
+ 3. start scan while busy (status 0x1414)
+ 4. cancel using ctrl-c (must send abort command)
+ */
+
+#include "../include/sane/config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* localtime(C90) */
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+/* Some macro code to enhance readability */
+#define RET_IF_ERR(x) do { \
+ if ((error = (x)) < 0) \
+ return error; \
+ } while(0)
+
+#define WAIT_INTERRUPT(x) do { \
+ error = handle_interrupt (s, x); \
+ if (s->cancel) \
+ return PIXMA_ECANCELED; \
+ if (error != PIXMA_ECANCELED && error < 0) \
+ return error; \
+ } while(0)
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+/* Size of the command buffer should be multiple of wMaxPacketLength and
+ greater than 4096+24.
+ 4096 = size of gamma table. 24 = header + checksum */
+#define IMAGE_BLOCK_SIZE (512*1024)
+#define CMDBUF_SIZE (4096 + 24)
+#define DEFAULT_GAMMA 2.0 /***** Gamma different from 1.0 is potentially impacting color profile generation *****/
+#define UNKNOWN_PID 0xffff
+
+
+#define CANON_VID 0x04a9
+
+/* Generation 1 */
+#define MP150_PID 0x1709
+#define MP170_PID 0x170a
+#define MP450_PID 0x170b
+#define MP500_PID 0x170c
+#define MP530_PID 0x1712
+
+/* Generation 2 */
+#define MP160_PID 0x1714
+#define MP180_PID 0x1715
+#define MP460_PID 0x1716
+#define MP510_PID 0x1717
+#define MP600_PID 0x1718
+#define MP600R_PID 0x1719
+
+#define MP140_PID 0x172b
+
+/* Generation 3 */
+/* PIXMA 2007 vintage */
+#define MX7600_PID 0x171c
+#define MP210_PID 0x1721
+#define MP220_PID 0x1722
+#define MP470_PID 0x1723
+#define MP520_PID 0x1724
+#define MP610_PID 0x1725
+#define MX300_PID 0x1727
+#define MX310_PID 0x1728
+#define MX700_PID 0x1729
+#define MX850_PID 0x172c
+
+/* PIXMA 2008 vintage */
+#define MP630_PID 0x172e
+#define MP620_PID 0x172f
+#define MP540_PID 0x1730
+#define MP480_PID 0x1731
+#define MP240_PID 0x1732
+#define MP260_PID 0x1733
+#define MP190_PID 0x1734
+
+/* PIXMA 2009 vintage */
+#define MX860_PID 0x1735
+#define MX320_PID 0x1736 /* untested */
+#define MX330_PID 0x1737
+
+/* Generation 4 */
+#define MP250_PID 0x173a
+#define MP270_PID 0x173b
+#define MP490_PID 0x173c
+#define MP550_PID 0x173d
+#define MP560_PID 0x173e
+#define MP640_PID 0x173f
+
+/* PIXMA 2010 vintage */
+#define MX340_PID 0x1741
+#define MX350_PID 0x1742
+#define MX870_PID 0x1743
+
+/* 2010 new devices (untested) */
+#define MP280_PID 0x1746
+#define MP495_PID 0x1747
+#define MG5100_PID 0x1748
+#define MG5200_PID 0x1749
+#define MG6100_PID 0x174a
+
+/* PIXMA 2011 vintage */
+#define MX360_PID 0x174d
+#define MX410_PID 0x174e
+#define MX420_PID 0x174f
+#define MX880_PID 0x1750
+
+/* Generation 5 */
+/* 2011 new devices (untested) */
+#define MG2100_PID 0x1751
+#define MG3100_PID 0x1752
+#define MG4100_PID 0x1753
+#define MG5300_PID 0x1754
+#define MG6200_PID 0x1755
+#define MP493_PID 0x1757
+#define E500_PID 0x1758
+
+/* 2012 new devices (untested) */
+#define MX370_PID 0x1759
+#define MX430_PID 0x175B
+#define MX510_PID 0x175C
+#define MX710_PID 0x175D
+#define MX890_PID 0x175E
+#define E600_PID 0x175A
+#define MG4200_PID 0x1763
+
+/* 2013 new devices */
+#define MP230_PID 0x175F
+#define MG6300_PID 0x1765
+
+/* 2013 new devices (untested) */
+#define MG2200_PID 0x1760
+#define E510_PID 0x1761
+#define MG3200_PID 0x1762
+#define MG5400_PID 0x1764
+#define MX390_PID 0x1766
+#define E610_PID 0x1767
+#define MX450_PID 0x1768
+#define MX520_PID 0x1769
+#define MX720_PID 0x176a
+#define MX920_PID 0x176b
+#define MG2400_PID 0x176c
+#define MG2500_PID 0x176d
+#define MG3500_PID 0x176e
+#define MG6500_PID 0x176f
+#define MG6400_PID 0x1770
+#define MG5500_PID 0x1771
+#define MG7100_PID 0x1772
+
+/* 2014 new devices (untested) */
+#define MX470_PID 0x1774
+#define MX530_PID 0x1775
+#define MB5000_PID 0x1776
+#define MB5300_PID 0x1777
+#define MB2000_PID 0x1778
+#define MB2300_PID 0x1779
+#define E400_PID 0x177a
+#define E560_PID 0x177b
+#define MG7500_PID 0x177c
+#define MG6600_PID 0x177e
+#define MG5600_PID 0x177f
+#define MG2900_PID 0x1780
+#define E460_PID 0x1788
+
+/* 2015 new devices (untested) */
+#define MX490_PID 0x1787
+#define E480_PID 0x1789
+#define MG3600_PID 0x178a
+#define MG7700_PID 0x178b
+#define MG6900_PID 0x178c
+#define MG6800_PID 0x178d
+#define MG5700_PID 0x178e
+
+/* 2016 new devices (untested) */
+#define MB2700_PID 0x1792
+#define MB2100_PID 0x1793
+#define G3000_PID 0x1794
+#define G2000_PID 0x1795
+#define TS9000_PID 0x179f
+#define TS8000_PID 0x1800
+#define TS6000_PID 0x1801
+#define TS5000_PID 0x1802
+#define MG3000_PID 0x180b
+#define E470_PID 0x180c
+#define E410_PID 0x181e
+
+/* 2017 new devices (untested) */
+#define G4000_PID 0x181d
+#define TS6100_PID 0x1822
+#define TS5100_PID 0x1825
+#define TS3100_PID 0x1827
+#define E3100_PID 0x1828
+
+/* 2018 new devices (untested) */
+#define MB5400_PID 0x178f
+#define MB5100_PID 0x1790
+#define TS9100_PID 0x1820
+#define TR8500_PID 0x1823
+#define TR7500_PID 0x1824
+#define TS9500_PID 0x185c
+#define LIDE400_PID 0x1912 /* tested */
+#define LIDE300_PID 0x1913 /* tested */
+
+/* 2019 new devices (untested) */
+#define TS8100_PID 0x1821
+#define G2010_PID 0x183a
+#define G3010_PID 0x183b
+#define G4010_PID 0x183d
+#define TS9180_PID 0x183e
+#define TS8180_PID 0x183f
+#define TS6180_PID 0x1840
+#define TR8580_PID 0x1841
+#define TS8130_PID 0x1842
+#define TS6130_PID 0x1843
+#define TR8530_PID 0x1844
+#define TR7530_PID 0x1845
+#define XK50_PID 0x1846
+#define XK70_PID 0x1847
+#define TR4500_PID 0x1854
+#define E4200_PID 0x1855
+#define TS6200_PID 0x1856
+#define TS6280_PID 0x1857
+#define TS6230_PID 0x1858
+#define TS8200_PID 0x1859
+#define TS8280_PID 0x185a
+#define TS8230_PID 0x185b
+#define TS9580_PID 0x185d
+#define TR9530_PID 0x185e
+#define G6000_PID 0x1865
+#define G6080_PID 0x1866
+#define XK80_PID 0x1873
+#define TS5300_PID 0x188b
+#define TS5380_PID 0x188c
+#define TS6300_PID 0x188d
+#define TS6380_PID 0x188e
+#define TS7330_PID 0x188f
+#define TS8300_PID 0x1890
+#define TS8380_PID 0x1891
+#define TS8330_PID 0x1892
+#define XK60_PID 0x1893
+#define TS6330_PID 0x1894
+#define TS3300_PID 0x18a2
+#define E3300_PID 0x18a3
+
+/* Generation 4 XML messages that encapsulates the Pixma protocol messages */
+#define XML_START_1 \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\
+<ivec:contents><ivec:operation>StartJob</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+<ivec:bidi>1</ivec:bidi></ivec:param_set></ivec:contents></cmd>"
+
+#define XML_START_2 \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\" xmlns:vcn=\"http://www.canon.com/ns/cmd/2008/07/canon/\">\
+<ivec:contents><ivec:operation>VendorCmd</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+<vcn:ijoperation>ModeShift</vcn:ijoperation><vcn:ijmode>1</vcn:ijmode>\
+</ivec:param_set></ivec:contents></cmd>"
+
+#define XML_END \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\
+<ivec:contents><ivec:operation>EndJob</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+</ivec:param_set></ivec:contents></cmd>"
+
+#define XML_OK "<ivec:response>OK</ivec:response>"
+
+enum mp150_state_t
+{
+ state_idle,
+ state_warmup,
+ state_scanning,
+ state_transfering,
+ state_finished
+};
+
+enum mp150_cmd_t
+{
+ cmd_start_session = 0xdb20,
+ cmd_select_source = 0xdd20,
+ cmd_gamma = 0xee20,
+ cmd_scan_param = 0xde20,
+ cmd_status = 0xf320,
+ cmd_abort_session = 0xef20,
+ cmd_time = 0xeb80,
+ cmd_read_image = 0xd420,
+ cmd_error_info = 0xff20,
+
+ cmd_scan_param_3 = 0xd820,
+ cmd_scan_start_3 = 0xd920,
+ cmd_status_3 = 0xda20,
+};
+
+typedef struct mp150_t
+{
+ enum mp150_state_t state;
+ pixma_cmdbuf_t cb;
+ uint8_t *imgbuf;
+ uint8_t current_status[16];
+ unsigned last_block;
+ uint8_t generation;
+ /* for Generation 3 shift */
+ uint8_t *linebuf;
+ uint8_t *data_left_ofs;
+ unsigned data_left_len;
+ uint8_t adf_state; /* handle adf scanning */
+ unsigned scale; /* Scale factor for lower resolutions, the
+ * scanner doesn't support. We scale down the
+ * image after scanning minimum possible
+ * resolution.
+ */
+
+} mp150_t;
+
+/*
+ STAT: 0x0606 = ok,
+ 0x1515 = failed (PIXMA_ECANCELED),
+ 0x1414 = busy (PIXMA_EBUSY)
+
+ Transaction scheme
+ 1. command_header/data | result_header
+ 2. command_header | result_header/data
+ 3. command_header | result_header/image_data
+
+ - data has checksum in the last byte.
+ - image_data has no checksum.
+ - data and image_data begins in the same USB packet as
+ command_header or result_header.
+
+ command format #1:
+ u16be cmd
+ u8[6] 0
+ u8[4] 0
+ u32be PLEN parameter length
+ u8[PLEN-1] parameter
+ u8 parameter check sum
+ result:
+ u16be STAT
+ u8 0
+ u8 0 or 0x21 if STAT == 0x1414
+ u8[4] 0
+
+ command format #2:
+ u16be cmd
+ u8[6] 0
+ u8[4] 0
+ u32be RLEN result length
+ result:
+ u16be STAT
+ u8[6] 0
+ u8[RLEN-1] result
+ u8 result check sum
+
+ command format #3: (only used by read_image_block)
+ u16be 0xd420
+ u8[6] 0
+ u8[4] 0
+ u32be max. block size + 8
+ result:
+ u16be STAT
+ u8[6] 0
+ u8 block info bitfield: 0x8 = end of scan, 0x10 = no more paper, 0x20 = no more data
+ u8[3] 0
+ u32be ILEN image data size
+ u8[ILEN] image data
+ */
+
+static void mp150_finish_scan (pixma_t * s);
+
+static int
+is_scanning_from_adf (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADF
+ || s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static int
+is_scanning_from_adfdup (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static int
+is_scanning_jpeg (pixma_t *s)
+{
+ return s->param->mode_jpeg;
+}
+
+static int
+send_xml_dialog (pixma_t * s, const char * xml_message)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ int datalen;
+
+ datalen = pixma_cmd_transaction (s, xml_message, strlen (xml_message),
+ mp->cb.buf, 1024);
+ if (datalen < 0)
+ return datalen;
+
+ mp->cb.buf[datalen] = 0;
+
+ PDBG (pixma_dbg (10, "XML message sent to scanner:\n%s\n", xml_message));
+ PDBG (pixma_dbg (10, "XML response back from scanner:\n%s\n", mp->cb.buf));
+
+ return (strcasestr ((const char *) mp->cb.buf, XML_OK) != NULL);
+}
+
+static int
+start_session (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ pixma_newcmd (&mp->cb, cmd_start_session, 0, 0);
+ mp->cb.buf[3] = 0x00;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+start_scan_3 (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ pixma_newcmd (&mp->cb, cmd_scan_start_3, 0, 0);
+ mp->cb.buf[3] = 0x00;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+is_calibrated (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ if (mp->generation >= 3)
+ {
+ return ((mp->current_status[0] & 0x01) == 1 || (mp->current_status[0] & 0x02) == 2);
+ }
+ if (mp->generation == 1)
+ {
+ return (mp->current_status[8] == 1);
+ }
+ else
+ {
+ return (mp->current_status[9] == 1);
+ }
+}
+
+static int
+has_paper (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ if (is_scanning_from_adfdup (s))
+ return (mp->current_status[1] == 0 || mp->current_status[2] == 0);
+ else
+ return (mp->current_status[1] == 0);
+}
+
+static void
+drain_bulk_in (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0);
+}
+
+static int
+abort_session (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ mp->adf_state = state_idle; /* reset adf scanning */
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session);
+}
+
+static int
+select_source (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ uint8_t *data;
+
+ data = pixma_newcmd (&mp->cb, cmd_select_source, 12, 0);
+ data[5] = ((mp->generation == 2) ? 1 : 0);
+ switch (s->param->source)
+ {
+ case PIXMA_SOURCE_FLATBED:
+ data[0] = 1;
+ data[1] = 1;
+ break;
+
+ case PIXMA_SOURCE_ADF:
+ data[0] = 2;
+ data[5] = 1;
+ data[6] = 1;
+ break;
+
+ case PIXMA_SOURCE_ADFDUP:
+ data[0] = 2;
+ data[5] = 3;
+ data[6] = 3;
+ break;
+
+ default:
+ return PIXMA_EPROTO;
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+send_gamma_table (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ const uint8_t *lut = s->param->gamma_table;
+ uint8_t *data;
+
+ if (mp->generation == 1)
+ {
+ data = pixma_newcmd (&mp->cb, cmd_gamma, 4096 + 8, 0);
+ data[0] = (s->param->channels == 3) ? 0x10 : 0x01;
+ pixma_set_be16 (0x1004, data + 2);
+ if (lut)
+ memcpy (data + 4, lut, 4096);
+ else
+ pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 4096);
+ }
+ else
+ {
+ /* FIXME: Gamma table for 2nd generation: 1024 * uint16_le */
+ data = pixma_newcmd (&mp->cb, cmd_gamma, 2048 + 8, 0);
+ data[0] = 0x10;
+ pixma_set_be16 (0x0804, data + 2);
+ if (lut)
+ {
+ int i;
+ for (i = 0; i < 1024; i++)
+ {
+ int j = (i << 2) + (i >> 8);
+ data[4 + 2 * i + 0] = lut[j];
+ data[4 + 2 * i + 1] = lut[j];
+ }
+ }
+ else
+ {
+ int i;
+ pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 2048);
+ for (i = 0; i < 1024; i++)
+ {
+ int j = (i << 1) + (i >> 9);
+ data[4 + 2 * i + 0] = data[4 + j];
+ data[4 + 2 * i + 1] = data[4 + j];
+ }
+ }
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static unsigned
+calc_raw_width (const mp150_t * mp, const pixma_scan_param_t * param)
+{
+ unsigned raw_width;
+ /* NOTE: Actually, we can send arbitary width to MP150. Lines returned
+ are always padded to multiple of 4 or 12 pixels. Is this valid for
+ other models, too? */
+ if (mp->generation >= 2)
+ {
+ raw_width = ALIGN_SUP ((param->w * mp->scale) + param->xs, 32);
+ /* PDBG (pixma_dbg (4, "*calc_raw_width***** width %i extended by %i and rounded to %i *****\n", param->w, param->xs, raw_width)); */
+ }
+ else if (param->channels == 1)
+ {
+ raw_width = ALIGN_SUP (param->w + param->xs, 12);
+ }
+ else
+ {
+ raw_width = ALIGN_SUP (param->w + param->xs, 4);
+ }
+ return raw_width;
+}
+
+static unsigned
+get_cis_line_size (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ /*PDBG (pixma_dbg (4, "%s: line_size=%ld, w=%d, wx=%d, scale=%d\n",
+ __func__, s->param->line_size, s->param->w, s->param->wx, mp->scale));*/
+
+ return (s->param->wx ? s->param->line_size / s->param->w * s->param->wx
+ : s->param->line_size) * mp->scale;
+}
+
+static int
+send_scan_param (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ uint8_t *data;
+ unsigned xdpi = s->param->xdpi * mp->scale;
+ unsigned ydpi = s->param->xdpi * mp->scale;
+ unsigned x = s->param->x * mp->scale;
+ unsigned xs = s->param->xs;
+ unsigned y = s->param->y * mp->scale;
+ unsigned wx = calc_raw_width (mp, s->param);
+ unsigned h = MIN (s->param->h, s->cfg->height * s->param->ydpi / 75) * mp->scale;
+
+ if (mp->generation <= 2)
+ {
+ PDBG (pixma_dbg (4, "*send_scan_param gen. 1-2 ***** Setting: xdpi=%hi ydpi=%hi x=%i y=%i wx=%i ***** \n",
+ xdpi, ydpi, x-xs, y, wx));
+ data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x30, 0);
+ pixma_set_be16 (xdpi | 0x8000, data + 0x04);
+ pixma_set_be16 (ydpi | 0x8000, data + 0x06);
+ pixma_set_be32 (x, data + 0x08);
+ if (mp->generation == 2)
+ pixma_set_be32 (x - s->param->xs, data + 0x08);
+ pixma_set_be32 (y, data + 0x0c);
+ pixma_set_be32 (wx, data + 0x10);
+ pixma_set_be32 (h, data + 0x14);
+ data[0x18] = (s->param->channels != 1) ? 0x08 : 0x04;
+ data[0x19] = ((s->param->software_lineart) ? 8 : s->param->depth)
+ * s->param->channels; /* bits per pixel */
+ data[0x1a] = 0;
+ data[0x20] = 0xff;
+ data[0x23] = 0x81;
+ data[0x26] = 0x02;
+ data[0x27] = 0x01;
+ }
+ else
+ {
+ PDBG (pixma_dbg (4, "*send_scan_param gen. 3+ ***** Setting: xdpi=%hi ydpi=%hi x=%i xs=%i y=%i wx=%i h=%i ***** \n",
+ xdpi, ydpi, x, xs, y, wx, h));
+ data = pixma_newcmd (&mp->cb, cmd_scan_param_3, 0x38, 0);
+ data[0x00] = (is_scanning_from_adf (s)) ? 0x02 : 0x01;
+ data[0x01] = 0x01;
+ data[0x02] = 0x01;
+ if (is_scanning_from_adfdup (s))
+ {
+ data[0x02] = 0x03;
+ data[0x03] = 0x03;
+ }
+ if (is_scanning_jpeg (s))
+ {
+ data[0x03] = 0x01;
+ }
+ else
+ {
+ data[0x05] = 0x01; /* This one also seen at 0. Don't know yet what's used for */
+ }
+ pixma_set_be16 (xdpi | 0x8000, data + 0x08);
+ pixma_set_be16 (ydpi | 0x8000, data + 0x0a);
+ pixma_set_be32 (x - xs, data + 0x0c);
+ pixma_set_be32 (y, data + 0x10);
+ pixma_set_be32 (wx, data + 0x14);
+ pixma_set_be32 (h, data + 0x18);
+ data[0x1c] = (s->param->channels != 1) ? 0x08 : 0x04;
+
+ data[0x1d] = ((s->param->software_lineart) ? 8 : s->param->depth)
+ * s->param->channels; /* bits per pixel */
+
+ data[0x1f] = 0x01; /* This one also seen at 0. Don't know yet what's used for */
+ data[0x20] = 0xff;
+ if (is_scanning_jpeg (s))
+ {
+ data[0x21] = 0x83;
+ }
+ else
+ {
+ data[0x21] = 0x81;
+ }
+ data[0x23] = 0x02;
+ data[0x24] = 0x01;
+
+ switch (s->cfg->pid)
+ {
+ case MG5300_PID:
+ /* unknown values (perhaps counter) for MG5300 series---values must be 0x30-0x39: decimal 0-9 */
+ data[0x26] = 0x32; /* using example values from a real scan here */
+ data[0x27] = 0x31;
+ data[0x28] = 0x34;
+ data[0x29] = 0x35;
+ break;
+
+ default:
+ break;
+ }
+
+ data[0x30] = 0x01;
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+query_status_3 (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ uint8_t *data;
+ int error, status_len;
+
+ status_len = 8;
+ data = pixma_newcmd (&mp->cb, cmd_status_3, 0, status_len);
+ RET_IF_ERR (pixma_exec (s, &mp->cb));
+ memcpy (mp->current_status, data, status_len);
+ return error;
+}
+
+static int
+query_status (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ uint8_t *data;
+ int error, status_len;
+
+ status_len = (mp->generation == 1) ? 12 : 16;
+ data = pixma_newcmd (&mp->cb, cmd_status, 0, status_len);
+ RET_IF_ERR (pixma_exec (s, &mp->cb));
+ memcpy (mp->current_status, data, status_len);
+ PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u busy=%u\n",
+ data[1], data[8], data[7], data[9]));
+ return error;
+}
+
+#if 0
+static int
+send_time (pixma_t * s)
+{
+ /* Why does a scanner need a time? */
+ time_t now;
+ struct tm *t;
+ uint8_t *data;
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ data = pixma_newcmd (&mp->cb, cmd_time, 20, 0);
+ pixma_get_time (&now, NULL);
+ t = localtime (&now);
+ strftime ((char *) data, 16, "%y/%m/%d %H:%M", t);
+ PDBG (pixma_dbg (3, "Sending time: '%s'\n", (char *) data));
+ return pixma_exec (s, &mp->cb);
+}
+#endif
+
+/* TODO: Simplify this function. Read the whole data packet in one shot. */
+static int
+read_image_block (pixma_t * s, uint8_t * header, uint8_t * data)
+{
+ uint8_t cmd[16];
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ const int hlen = 8 + 8;
+ int error, datalen;
+
+ memset (cmd, 0, sizeof (cmd));
+ pixma_set_be16 (cmd_read_image, cmd);
+ if ((mp->last_block & 0x20) == 0)
+ pixma_set_be32 ((IMAGE_BLOCK_SIZE / 65536) * 65536 + 8, cmd + 0xc);
+ else
+ pixma_set_be32 (32 + 8, cmd + 0xc);
+
+ mp->state = state_transfering;
+ mp->cb.reslen =
+ pixma_cmd_transaction (s, cmd, sizeof (cmd), mp->cb.buf, 512);
+ datalen = mp->cb.reslen;
+ if (datalen < 0)
+ return datalen;
+
+ memcpy (header, mp->cb.buf, hlen);
+
+ if (datalen >= hlen)
+ {
+ datalen -= hlen;
+ memcpy (data, mp->cb.buf + hlen, datalen);
+ data += datalen;
+ if (mp->cb.reslen == 512)
+ {
+ error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen);
+ RET_IF_ERR (error);
+ datalen += error;
+ }
+ }
+
+ mp->state = state_scanning;
+ mp->cb.expected_reslen = 0;
+ RET_IF_ERR (pixma_check_result (&mp->cb));
+ if (mp->cb.reslen < hlen)
+ return PIXMA_EPROTO;
+ return datalen;
+}
+
+static int
+read_error_info (pixma_t * s, void *buf, unsigned size)
+{
+ unsigned len = 16;
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len);
+ RET_IF_ERR (pixma_exec (s, &mp->cb));
+ if (buf && len < size)
+ {
+ size = len;
+ /* NOTE: I've absolutely no idea what the returned data mean. */
+ memcpy (buf, data, size);
+ error = len;
+ }
+ return error;
+}
+
+/*
+handle_interrupt() waits until it receives an interrupt packet or times out.
+It calls send_time() and query_status() if necessary. Therefore, make sure
+that handle_interrupt() is only called from a safe context for send_time()
+and query_status().
+
+ Returns:
+ 0 timed out
+ 1 an interrupt packet received
+ PIXMA_ECANCELED interrupted by signal
+ <0 error
+*/
+static int
+handle_interrupt (pixma_t * s, int timeout)
+{
+ uint8_t buf[64];
+ int len;
+
+ len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout);
+ if (len == PIXMA_ETIMEDOUT)
+ return 0;
+ if (len < 0)
+ return len;
+ if (len%16) /* len must be a multiple of 16 bytes */
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING:unexpected interrupt packet length %d\n", len));
+ return PIXMA_EPROTO;
+ }
+
+ /* s->event = 0x0brroott
+ * b: button
+ * oo: original
+ * tt: target
+ * rr: scan resolution
+ * poll event with 'scanimage -A' */
+ if (s->cfg->pid == MG5300_PID
+ || s->cfg->pid == MG5400_PID
+ || s->cfg->pid == MG6200_PID
+ || s->cfg->pid == MG6300_PID
+ || s->cfg->pid == MX520_PID
+ || s->cfg->pid == MX720_PID
+ || s->cfg->pid == MX920_PID
+ || s->cfg->pid == MB2300_PID
+ || s->cfg->pid == MB5000_PID
+ || s->cfg->pid == MB5400_PID)
+ /* button no. in buf[7]
+ * size in buf[10] 01=A4; 02=Letter; 08=10x15; 09=13x18; 0b=auto
+ * format in buf[11] 01=JPEG; 02=TIFF; 03=PDF; 04=Kompakt-PDF
+ * dpi in buf[12] 01=75; 02=150; 03=300; 04=600
+ * target = format; original = size; scan-resolution = dpi */
+ {
+ if (buf[7] & 1)
+ s->events = PIXMA_EV_BUTTON1 | buf[11] | buf[10]<<8 | buf[12]<<16; /* color scan */
+ if (buf[7] & 2)
+ s->events = PIXMA_EV_BUTTON2 | buf[11] | buf[10]<<8 | buf[12]<<16; /* b/w scan */
+ }
+ else if (s->cfg->pid == LIDE300_PID
+ || s->cfg->pid == LIDE400_PID)
+ /* unknown value in buf[4]
+ * target in buf[0x13]
+ * always set button-1 */
+ {
+ if (buf[0x13])
+ s->events = PIXMA_EV_BUTTON1 | buf[0x13];
+ }
+ else
+ /* button no. in buf[0]
+ * original in buf[0]
+ * target in buf[1] */
+ {
+ /* More than one event can be reported at the same time. */
+ if (buf[3] & 1)
+ /* FIXME: This function makes trouble with a lot of scanners
+ send_time (s);
+ */
+ PDBG (pixma_dbg (1, "WARNING:send_time() disabled!\n"));
+ if (buf[9] & 2)
+ query_status (s);
+ if (buf[0] & 2)
+ s->events = PIXMA_EV_BUTTON2 | buf[1] | ((buf[0] & 0xf0) << 4); /* b/w scan */
+ if (buf[0] & 1)
+ s->events = PIXMA_EV_BUTTON1 | buf[1] | ((buf[0] & 0xf0) << 4); /* color scan */
+ }
+ return 1;
+}
+
+static int
+wait_until_ready (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ int error, tmo = 120; /* some scanners need a long timeout */
+
+ RET_IF_ERR ((mp->generation >= 3) ? query_status_3 (s)
+ : query_status (s));
+ while (!is_calibrated (s))
+ {
+ WAIT_INTERRUPT (1000);
+ if (mp->generation >= 3)
+ RET_IF_ERR (query_status_3 (s));
+ else if (s->cfg->pid == MP600_PID ||
+ s->cfg->pid == MP600R_PID)
+ RET_IF_ERR (query_status (s));
+ if (--tmo == 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING:Timed out in wait_until_ready()\n"));
+ PDBG (query_status (s));
+ return PIXMA_ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+static void
+reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, unsigned n,
+ unsigned m, unsigned w, unsigned line_size)
+{
+ unsigned i;
+
+ for (i = 0; i < w; i++)
+ {
+ memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c);
+ }
+ memcpy (sptr, linebuf, line_size);
+}
+
+/* the scanned image must be shrinked by factor "scale"
+ * the image can be formatted as rgb (c=3) or gray (c=1)
+ * we need to crop the left side (xs)
+ * we ignore more pixels inside scanned line (wx), behind needed line (w)
+ *
+ * example (scale=2):
+ * line | pixel[0] | pixel[1] | ... | pixel[w-1]
+ * ---------
+ * 0 | rgbrgb | rgbrgb | ... | rgbrgb
+ * wx*c | rgbrgb | rgbrgb | ... | rgbrgb
+ */
+uint8_t *
+shrink_image (uint8_t * dptr, uint8_t * sptr, unsigned xs, unsigned w,
+ unsigned wx, unsigned scale, unsigned c)
+{
+ unsigned i, ic;
+ uint16_t pixel;
+ uint8_t *dst = dptr; /* don't change dptr */
+ uint8_t *src = sptr; /* don't change sptr */
+
+ /*PDBG (pixma_dbg (4, "%s: w=%d, wx=%d, c=%d, scale=%d\n",
+ __func__, w, wx, c, scale));
+ PDBG (pixma_dbg (4, "\tdptr=%ld, sptr=%ld\n",
+ dptr, sptr));*/
+
+ /* crop left side */
+ src += c * xs;
+
+ /* process line */
+ for (i = 0; i < w; i++)
+ {
+ /* process rgb or gray pixel */
+ for (ic = 0; ic < c; ic++)
+ {
+#if 0
+ dst[ic] = src[ic];
+#else
+ pixel = 0;
+
+ /* sum shrink pixels */
+ for (unsigned m = 0; m < scale; m++) /* get pixels from shrinked lines */
+ {
+ for (unsigned n = 0; n < scale; n++) /* get pixels from same line */
+ {
+ pixel += src[ic + c * n + wx * c * m];
+ }
+ }
+ dst[ic] = pixel / (scale * scale);
+#endif
+ }
+
+ /* jump over shrinked data */
+ src += c * scale;
+ /* next pixel */
+ dst += c;
+ }
+
+ return dst;
+}
+
+/* This function deals with Generation >= 3 high dpi images.
+ * Each complete line in mp->imgbuf is processed for reordering pixels above
+ * 600 dpi for Generation >= 3. */
+static unsigned
+post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ unsigned c, lines, line_size, n, m, cw, cx;
+ uint8_t *sptr, *dptr, *gptr, *cptr;
+
+ if (s->param->mode_jpeg)
+ {
+ /* No post-processing, send raw JPEG data to main */
+ ib->rptr = mp->imgbuf;
+ ib->rend = mp->data_left_ofs;
+ return 0; /* # of non processed bytes */
+ }
+
+ /* process image sizes */
+ c = s->param->channels
+ * ((s->param->software_lineart) ? 8 : s->param->depth) / 8; /* color channels count */
+ cw = c * s->param->w; /* image width */
+ cx = c * s->param->xs; /* x-offset */
+
+ /* special image format parameters
+ * n: no. of sub-images
+ * m: sub-image width
+ */
+ if (mp->generation >= 3)
+ n = s->param->xdpi / 600;
+ else
+ n = s->param->xdpi / 2400;
+ if (s->cfg->pid == MP600_PID || s->cfg->pid == MP600R_PID)
+ n = s->param->xdpi / 1200;
+ m = (n > 0) ? s->param->wx / n : 1;
+
+ /* Initialize pointers */
+ sptr = dptr = gptr = cptr = mp->imgbuf;
+
+ /* walk through complete received lines */
+ line_size = get_cis_line_size (s);
+ lines = (mp->data_left_ofs - mp->imgbuf) / line_size;
+ if (lines > 0)
+ {
+ unsigned i;
+
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, wx=%i, line_size=%u, cx=%u, cw=%u ***** \n",
+ c, n, m, s->param->wx, line_size, cx, cw));*/
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** lines = %i ***** \n", lines));*/
+
+ for (i = 0; i < lines; i++, sptr += line_size)
+ {
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, w=%i, line_size=%u ***** \n",
+ c, n, m, s->param->wx, line_size));*/
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** Pointers: sptr=%lx, dptr=%lx, linebuf=%lx ***** \n",
+ sptr, dptr, mp->linebuf));*/
+
+ /* special image format for *most* devices at high dpi.
+ * MP220, MX360 and generation 5 scanners are exceptions */
+ if (n > 1
+ && s->cfg->pid != MP220_PID
+ && s->cfg->pid != MP490_PID
+ && s->cfg->pid != MX360_PID
+ && (mp->generation < 5
+ /* generation 5 scanners *with* special image format */
+ || s->cfg->pid == MG2200_PID
+ || s->cfg->pid == MG3200_PID
+ || s->cfg->pid == MG4200_PID
+ || s->cfg->pid == MG5600_PID
+ || s->cfg->pid == MG5700_PID
+ || s->cfg->pid == MG6200_PID
+ || s->cfg->pid == MP230_PID
+ || s->cfg->pid == MX470_PID
+ || s->cfg->pid == MX510_PID
+ || s->cfg->pid == MX520_PID))
+ reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx, line_size);
+
+
+ /* scale image */
+ if (mp->scale > 1)
+ {
+ /* Crop line inside shrink_image() */
+ shrink_image(cptr, sptr, s->param->xs, s->param->w, s->param->wx, mp->scale, c);
+ }
+ else
+ {
+ /* Crop line to selected borders */
+ memmove(cptr, sptr + cx, cw);
+ }
+
+ /* Color / Gray to Lineart convert */
+ if (s->param->software_lineart)
+ cptr = gptr = pixma_binarize_line (s->param, gptr, cptr, s->param->w, c);
+ else
+ cptr += cw;
+ }
+ }
+ ib->rptr = mp->imgbuf;
+ ib->rend = cptr;
+ return mp->data_left_ofs - sptr; /* # of non processed bytes */
+}
+
+static int
+mp150_open (pixma_t * s)
+{
+ mp150_t *mp;
+ uint8_t *buf;
+
+ mp = (mp150_t *) calloc (1, sizeof (*mp));
+ if (!mp)
+ return PIXMA_ENOMEM;
+
+ buf = (uint8_t *) malloc (CMDBUF_SIZE + IMAGE_BLOCK_SIZE);
+ if (!buf)
+ {
+ free (mp);
+ return PIXMA_ENOMEM;
+ }
+
+ s->subdriver = mp;
+ mp->state = state_idle;
+
+ mp->cb.buf = buf;
+ mp->cb.size = CMDBUF_SIZE;
+ mp->cb.res_header_len = 8;
+ mp->cb.cmd_header_len = 16;
+ mp->cb.cmd_len_field_ofs = 14;
+
+ mp->imgbuf = buf + CMDBUF_SIZE;
+
+ /* General rules for setting Pixma protocol generation # */
+ mp->generation = (s->cfg->pid >= MP160_PID) ? 2 : 1;
+
+ if (s->cfg->pid >= MX7600_PID)
+ mp->generation = 3;
+
+ if (s->cfg->pid >= MP250_PID)
+ mp->generation = 4;
+
+ if (s->cfg->pid >= MG2100_PID) /* this scanners generation doesn't need */
+ mp->generation = 5; /* special image conversion @ high dpi */
+
+ /* And exceptions to be added here */
+ if (s->cfg->pid == MP140_PID)
+ mp->generation = 2;
+
+ PDBG (pixma_dbg (3, "*mp150_open***** This is a generation %d scanner. *****\n", mp->generation));
+
+ /* adf scanning */
+ mp->adf_state = state_idle;
+
+ if (mp->generation < 4)
+ {
+ query_status (s);
+ handle_interrupt (s, 200);
+ }
+ return 0;
+}
+
+static void
+mp150_close (pixma_t * s)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ mp150_finish_scan (s);
+ free (mp->cb.buf);
+ free (mp);
+ s->subdriver = NULL;
+}
+
+static int
+mp150_check_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ /* PDBG (pixma_dbg (4, "*mp150_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */
+
+ /* MP150 only supports 8 bit per channel in color and grayscale mode */
+ if (sp->depth != 1)
+ {
+ sp->software_lineart = 0;
+ sp->depth = 8;
+ }
+ else
+ {
+ /* software lineart */
+ sp->software_lineart = 1;
+ sp->depth = 1;
+ sp->channels = 1;
+ }
+
+ /* for software lineart w must be a multiple of 8 */
+ if (sp->software_lineart == 1 && sp->w % 8)
+ {
+ unsigned w_max;
+
+ sp->w += 8 - (sp->w % 8);
+
+ /* do not exceed the scanner capability */
+ w_max = s->cfg->width * s->cfg->xdpi / 75;
+ w_max -= w_max % 8;
+ if (sp->w > w_max)
+ sp->w = w_max;
+ }
+
+ if (mp->generation >= 2)
+ {
+ /* mod 32 and expansion of the X scan limits */
+ /*PDBG (pixma_dbg (4, "*mp150_check_param***** ----- Initially: x=%i, y=%i, w=%i, h=%i *****\n", sp->x, sp->y, sp->w, sp->h));*/
+ sp->xs = (sp->x * mp->scale) % 32;
+ }
+ else
+ sp->xs = 0;
+ /*PDBG (pixma_dbg (4, "*mp150_check_param***** Selected origin, origin shift: %i, %i *****\n", sp->x, sp->xs));*/
+ sp->wx = calc_raw_width (mp, sp);
+ sp->line_size = sp->w * sp->channels * (((sp->software_lineart) ? 8 : sp->depth) / 8); /* bytes per line per color after cropping */
+ /*PDBG (pixma_dbg (4, "*mp150_check_param***** Final scan width and line-size: %i, %li *****\n", sp->wx, sp->line_size));*/
+
+ /* Some exceptions here for particular devices */
+ /* Those devices can scan up to legal 14" with ADF, but A4 11.7" in flatbed */
+ /* PIXMA_CAP_ADF also works for PIXMA_CAP_ADFDUP */
+ if ((s->cfg->cap & PIXMA_CAP_ADF) && sp->source == PIXMA_SOURCE_FLATBED)
+ sp->h = MIN (sp->h, 877 * sp->xdpi / 75);
+
+ if (sp->source == PIXMA_SOURCE_ADF || sp->source == PIXMA_SOURCE_ADFDUP)
+ {
+ uint8_t k = 1;
+
+ /* ADF/ADF duplex mode: max scan res is 600 dpi, at least for generation 4+ */
+ if (mp->generation >= 4)
+ k = sp->xdpi / MIN (sp->xdpi, 600);
+ sp->x /= k;
+ sp->xs /= k;
+ sp->y /= k;
+ sp->w /= k;
+ sp->wx /= k;
+ sp->h /= k;
+ sp->xdpi /= k;
+ sp->ydpi = sp->xdpi;
+ }
+
+ sp->mode_jpeg = (s->cfg->cap & PIXMA_CAP_ADF_JPEG) &&
+ (sp->source == PIXMA_SOURCE_ADF ||
+ sp->source == PIXMA_SOURCE_ADFDUP);
+
+ mp->scale = 1;
+ if (s->cfg->min_xdpi && sp->xdpi < s->cfg->min_xdpi)
+ {
+ mp->scale = s->cfg->min_xdpi / sp->xdpi;
+ }
+ /*PDBG (pixma_dbg (4, "*mp150_check_param***** xdpi=%u, min_xdpi=%u, scale=%u *****\n",
+ sp->xdpi, s->cfg->min_xdpi, mp->scale));*/
+
+ /*PDBG (pixma_dbg (4, "*mp150_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx));*/
+ return 0;
+}
+
+static int
+mp150_scan (pixma_t * s)
+{
+ int error = 0, tmo;
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ if (mp->state != state_idle)
+ return PIXMA_EBUSY;
+
+ /* no paper inserted after first adf page => abort session */
+ if (s->param->adf_pageid && is_scanning_from_adf(s) && mp->adf_state == state_idle)
+ {
+ return PIXMA_ENO_PAPER;
+ }
+
+ /* Generation 4+: send XML dialog */
+ /* adf: first page or idle */
+ if (mp->generation >= 4 && mp->adf_state == state_idle)
+ {
+ if (!send_xml_dialog (s, XML_START_1))
+ return PIXMA_EPROTO;
+ if (!send_xml_dialog (s, XML_START_2))
+ return PIXMA_EPROTO;
+ }
+
+ /* clear interrupt packets buffer */
+ while (handle_interrupt (s, 0) > 0)
+ {
+ }
+
+ /* FIXME: Duplex ADF: check paper status only before odd pages (1,3,5,...). */
+ if (is_scanning_from_adf (s))
+ {
+ if ((error = query_status (s)) < 0)
+ return error;
+
+ /* wait for inserted paper
+ * timeout: 10 sec */
+ tmo = 10;
+ while (!has_paper (s) && --tmo >= 0)
+ {
+ if ((error = query_status (s)) < 0)
+ return error;
+ WAIT_INTERRUPT (1000);
+ PDBG (pixma_dbg
+ (2, "No paper in ADF. Timed out in %d sec.\n", tmo));
+ }
+
+ /* no paper inserted
+ * => abort session */
+ if (!has_paper (s))
+ {
+ PDBG (pixma_dbg (4, "*mp150_scan***** no paper in ADF *****\n"));
+ error = abort_session (s);
+ if (error < 0)
+ return error;
+
+ /* Generation 4+: send XML dialog */
+ /* adf: first page or idle */
+ if (mp->generation >= 4 && mp->adf_state == state_idle)
+ {
+ if (!send_xml_dialog (s, XML_END))
+ return PIXMA_EPROTO;
+ }
+
+ return PIXMA_ENO_PAPER;
+ }
+ }
+
+ tmo = 10;
+ /* adf: first page or idle */
+ if (mp->generation <= 2 || mp->adf_state == state_idle)
+ { /* single sheet or first sheet from ADF */
+ PDBG (pixma_dbg (4, "*mp150_scan***** start scanning *****\n"));
+ error = start_session (s);
+ while (error == PIXMA_EBUSY && --tmo >= 0)
+ {
+ if (s->cancel)
+ {
+ error = PIXMA_ECANCELED;
+ break;
+ }
+ PDBG (pixma_dbg
+ (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1));
+ pixma_sleep (1000000);
+ error = start_session (s);
+ }
+ if (error == PIXMA_EBUSY || error == PIXMA_ETIMEDOUT)
+ {
+ /* The scanner maybe hangs. We try to empty output buffer of the
+ * scanner and issue the cancel command. */
+ PDBG (pixma_dbg (2, "Scanner hangs? Sending abort_session command.\n"));
+ drain_bulk_in (s);
+ abort_session (s);
+ pixma_sleep (500000);
+ error = start_session (s);
+ }
+ if ((error >= 0) || (mp->generation >= 3))
+ mp->state = state_warmup;
+ if ((error >= 0) && (mp->generation <= 2))
+ error = select_source (s);
+ if ((error >= 0) && !is_scanning_jpeg (s))
+ {
+ int i;
+
+ for (i = (mp->generation >= 3) ? 3 : 1 ; i > 0 && error >= 0; i--)
+ error = send_gamma_table (s);
+ }
+ }
+ else /* ADF pageid != 0 and gen3 or above */
+ { /* next sheet from ADF */
+ PDBG (pixma_dbg (4, "*mp150_scan***** scan next sheet from ADF *****\n"));
+ pixma_sleep (1000000);
+ }
+ if ((error >= 0) || (mp->generation >= 3))
+ mp->state = state_warmup;
+ if (error >= 0)
+ error = send_scan_param (s);
+ if ((error >= 0) && (mp->generation >= 3))
+ error = start_scan_3 (s);
+ if (error < 0)
+ {
+ mp->last_block = 0x38; /* Force abort session if ADF scan */
+ mp150_finish_scan (s);
+ return error;
+ }
+
+ /* ADF scanning active */
+ if (is_scanning_from_adf (s))
+ mp->adf_state = state_scanning;
+ return 0;
+}
+
+static int
+mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ int error;
+ mp150_t *mp = (mp150_t *) s->subdriver;
+ unsigned block_size, bytes_received, proc_buf_size, line_size;
+ uint8_t header[16];
+
+ if (mp->state == state_warmup)
+ {
+ RET_IF_ERR (wait_until_ready (s));
+ pixma_sleep (1000000); /* No need to sleep, actually, but Window's driver
+ * sleep 1.5 sec. */
+ mp->state = state_scanning;
+ mp->last_block = 0;
+
+ line_size = get_cis_line_size (s);
+ proc_buf_size = 2 * line_size;
+ mp->cb.buf = realloc (mp->cb.buf,
+ CMDBUF_SIZE + IMAGE_BLOCK_SIZE + proc_buf_size);
+ if (!mp->cb.buf)
+ return PIXMA_ENOMEM;
+ mp->linebuf = mp->cb.buf + CMDBUF_SIZE;
+ mp->imgbuf = mp->data_left_ofs = mp->linebuf + line_size;
+ mp->data_left_len = 0;
+ }
+
+ do
+ {
+ if (s->cancel)
+ {
+ PDBG (pixma_dbg (4, "*mp150_fill_buffer***** s->cancel *****\n"));
+ return PIXMA_ECANCELED;
+ }
+ if ((mp->last_block & 0x28) == 0x28)
+ { /* end of image */
+ PDBG (pixma_dbg (4, "*mp150_fill_buffer***** end of image *****\n"));
+ mp->state = state_finished;
+ return 0;
+ }
+ /*PDBG (pixma_dbg (4, "*mp150_fill_buffer***** moving %u bytes into buffer *****\n", mp->data_left_len));*/
+ memmove (mp->imgbuf, mp->data_left_ofs, mp->data_left_len);
+ error = read_image_block (s, header, mp->imgbuf + mp->data_left_len);
+ if (error < 0)
+ {
+ PDBG (pixma_dbg (4, "*mp150_fill_buffer***** scanner error (%d): end scan *****\n", error));
+ mp->last_block = 0x38; /* end scan in mp150_finish_scan() */
+ if (error == PIXMA_ECANCELED)
+ {
+ /* NOTE: I see this in traffic logs but I don't know its meaning. */
+ read_error_info (s, NULL, 0);
+ }
+ return error;
+ }
+
+ bytes_received = error;
+ /*PDBG (pixma_dbg (4, "*mp150_fill_buffer***** %u bytes received by read_image_block *****\n", bytes_received));*/
+ block_size = pixma_get_be32 (header + 12);
+ mp->last_block = header[8] & 0x38;
+ if ((header[8] & ~0x38) != 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n"));
+ PDBG (pixma_hexdump (1, header, 16));
+ }
+ PASSERT (bytes_received == block_size);
+
+ if (block_size == 0)
+ { /* no image data at this moment. */
+ pixma_sleep (10000);
+ }
+ /* Post-process the image data */
+ mp->data_left_ofs = mp->imgbuf + mp->data_left_len + bytes_received;
+ mp->data_left_len = post_process_image_data (s, ib);
+ mp->data_left_ofs -= mp->data_left_len;
+ }
+ while (ib->rend == ib->rptr);
+
+ return ib->rend - ib->rptr;
+}
+
+static void
+mp150_finish_scan (pixma_t * s)
+{
+ int error;
+ mp150_t *mp = (mp150_t *) s->subdriver;
+
+ switch (mp->state)
+ {
+ case state_transfering:
+ drain_bulk_in (s);
+ /* fall through */
+ case state_scanning:
+ case state_warmup:
+ case state_finished:
+ /* FIXME: to process several pages ADF scan, must not send
+ * abort_session and start_session between pages (last_block=0x28) */
+ if (mp->generation <= 2 || !is_scanning_from_adf (s) || mp->last_block == 0x38)
+ {
+ PDBG (pixma_dbg (4, "*mp150_finish_scan***** abort session *****\n"));
+ error = abort_session (s); /* FIXME: it probably doesn't work in duplex mode! */
+ if (error < 0)
+ PDBG (pixma_dbg (1, "WARNING:abort_session() failed %d\n", error));
+
+ /* Generation 4+: send XML end of scan dialog */
+ if (mp->generation >= 4)
+ {
+ if (!send_xml_dialog (s, XML_END))
+ PDBG (pixma_dbg (1, "WARNING:XML_END dialog failed \n"));
+ }
+ }
+ else
+ PDBG (pixma_dbg (4, "*mp150_finish_scan***** wait for next page from ADF *****\n"));
+
+ mp->state = state_idle;
+ /* fall through */
+ case state_idle:
+ break;
+ }
+}
+
+static void
+mp150_wait_event (pixma_t * s, int timeout)
+{
+ /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for
+ * instance. */
+ while (s->events == 0 && handle_interrupt (s, timeout) > 0)
+ {
+ }
+}
+
+static int
+mp150_get_status (pixma_t * s, pixma_device_status_t * status)
+{
+ int error;
+
+ RET_IF_ERR (query_status (s));
+ status->hardware = PIXMA_HARDWARE_OK;
+ status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
+ status->cal =
+ (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF;
+ return 0;
+}
+
+static const pixma_scan_ops_t pixma_mp150_ops = {
+ mp150_open,
+ mp150_close,
+ mp150_scan,
+ mp150_fill_buffer,
+ mp150_finish_scan,
+ mp150_wait_event,
+ mp150_check_param,
+ mp150_get_status
+};
+
+#define DEVICE(name, model, pid, min_dpi, dpi, adftpu_min_dpi, adftpu_max_dpi, w, h, cap) { \
+ name, /* name */ \
+ model, /* model */ \
+ CANON_VID, pid, /* vid pid */ \
+ 0, /* iface */ \
+ &pixma_mp150_ops, /* ops */ \
+ min_dpi, /* min_xdpi */ \
+ dpi, 2*(dpi), /* xdpi, ydpi */ \
+ adftpu_min_dpi, adftpu_max_dpi, /* adftpu_min_dpi, adftpu_max_dpi */ \
+ 0, 0, /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */ \
+ w, h, /* width, height */ \
+ PIXMA_CAP_EASY_RGB| \
+ PIXMA_CAP_GRAY| /* CIS with native grayscale */ \
+ PIXMA_CAP_LINEART| /* all scanners with software lineart */ \
+ PIXMA_CAP_GAMMA_TABLE|PIXMA_CAP_EVENTS|cap \
+}
+
+#define END_OF_DEVICE_LIST DEVICE(NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0)
+
+const pixma_config_t pixma_mp150_devices[] = {
+ /* Generation 1: CIS */
+ DEVICE ("Canon PIXMA MP150", "MP150", MP150_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP170", "MP170", MP170_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP450", "MP450", MP450_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP500", "MP500", MP500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP530", "MP530", MP530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+
+ /* Generation 2: CIS */
+ DEVICE ("Canon PIXMA MP140", "MP140", MP140_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP160", "MP160", MP160_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP180", "MP180", MP180_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP460", "MP460", MP460_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP510", "MP510", MP510_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP600", "MP600", MP600_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP600R", "MP600R", MP600R_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Generation 3: CIS */
+ DEVICE ("Canon PIXMA MP210", "MP210", MP210_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP220", "MP220", MP220_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP470", "MP470", MP470_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP520", "MP520", MP520_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP610", "MP610", MP610_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ DEVICE ("Canon PIXMA MX300", "MX300", MX300_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MX310", "MX310", MX310_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX700", "MX700", MX700_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX850", "MX850", MX850_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA MX7600", "MX7600", MX7600_PID, 0, 4800, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+
+ DEVICE ("Canon PIXMA MP630", "MP630", MP630_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP620", "MP620", MP620_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP540", "MP540", MP540_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP480", "MP480", MP480_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP240", "MP240", MP240_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP260", "MP260", MP260_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP190", "MP190", MP190_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* PIXMA 2009 vintage */
+ DEVICE ("Canon PIXMA MX320", "MX320", MX320_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX330", "MX330", MX330_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX860", "MX860", MX860_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+/* width and height adjusted to flatbed size 21.8 x 30.2 cm^2 respective
+ * Not sure if anything's going wrong here, leaving as is
+ DEVICE ("Canon PIXMA MX860", "MX860", MX860_PID, 0, 2400, 0, 0, 638, 880, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),*/
+
+ /* PIXMA 2010 vintage */
+ DEVICE ("Canon PIXMA MX340", "MX340", MX340_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX350", "MX350", MX350_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX870", "MX870", MX870_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+
+ /* PIXMA 2011 vintage */
+ DEVICE ("Canon PIXMA MX360", "MX360", MX360_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX410", "MX410", MX410_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX420", "MX420", MX420_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX880 Series", "MX880", MX880_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+
+ /* Generation 4: CIS */
+ DEVICE ("Canon PIXMA MP640", "MP640", MP640_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP560", "MP560", MP560_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP550", "MP550", MP550_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP490", "MP490", MP490_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP250", "MP250", MP250_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP270", "MP270", MP270_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2010) Generation 4 CIS */
+ DEVICE ("Canon PIXMA MP280", "MP280", MP280_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS), /* TODO: 1200dpi doesn't work yet */
+ DEVICE ("Canon PIXMA MP495", "MP495", MP495_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5100", "MG5100", MG5100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5200", "MG5200", MG5200_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6100", "MG6100", MG6100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2011) Generation 5 CIS */
+ DEVICE ("Canon PIXMA MG2100", "MG2100", MG2100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG3100", "MG3100", MG3100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG4100", "MG4100", MG4100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5300", "MG5300", MG5300_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6200", "MG6200", MG6200_PID, 0, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MP493", "MP493", MP493_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E500", "E500", E500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2012) Generation 5 CIS */
+ DEVICE ("Canon PIXMA MX370 Series", "MX370", MX370_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX430 Series", "MX430", MX430_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX510 Series", "MX510", MX510_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX710 Series", "MX710", MX710_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA MX890 Series", "MX890", MX890_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA E600 Series", "E600", E600_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MG4200", "MG4200", MG4200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2013) Generation 5 CIS */
+ DEVICE ("Canon PIXMA E510", "E510", E510_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E610", "E610", E610_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MP230", "MP230", MP230_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG2200 Series", "MG2200", MG2200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG3200 Series", "MG3200", MG3200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5400 Series", "MG5400", MG5400_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6300 Series", "MG6300", MG6300_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MX390 Series", "MX390", MX390_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX450 Series", "MX450", MX450_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX520 Series", "MX520", MX520_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX720 Series", "MX720", MX720_PID, 0, 2400, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA MX920 Series", "MX920", MX920_PID, 0, 2400, 0, 600, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA MG2400 Series", "MG2400", MG2400_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG2500 Series", "MG2500", MG2500_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG3500 Series", "MG3500", MG3500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5500 Series", "MG5500", MG5500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6400 Series", "MG6400", MG6400_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6500 Series", "MG6500", MG6500_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG7100 Series", "MG7100", MG7100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2014) Generation 5 CIS */
+ DEVICE ("Canon PIXMA MX470 Series", "MX470", MX470_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MX530 Series", "MX530", MX530_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon MAXIFY MB5000 Series", "MB5000", MB5000_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon MAXIFY MB5300 Series", "MB5300", MB5300_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon MAXIFY MB2000 Series", "MB2000", MB2000_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon MAXIFY MB2100 Series", "MB2100", MB2100_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon MAXIFY MB2300 Series", "MB2300", MB2300_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon MAXIFY MB2700 Series", "MB2700", MB2700_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon PIXMA E400", "E400", E400_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E560", "E560", E560_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG7500 Series", "MG7500", MG7500_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6600 Series", "MG6600", MG6600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5600 Series", "MG5600", MG5600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG2900 Series", "MG2900", MG2900_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E460 Series", "E460", E460_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2015) Generation 5 CIS */
+ DEVICE ("Canon PIXMA MX490 Series", "MX490", MX490_PID, 0, 600, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA E480 Series", "E480", E480_PID, 0, 600, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MG3600 Series", "MG3600", MG3600_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG7700 Series", "MG7700", MG7700_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6900 Series", "MG6900", MG6900_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG6800 Series", "MG6800", MG6800_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG5700 Series", "MG5700", MG5700_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2016) Generation 5 CIS */
+ DEVICE ("Canon PIXMA G3000", "G3000", G3000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA G2000", "G2000", G2000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS9000 Series", "TS9000", TS9000_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8000 Series", "TS8000", TS8000_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6000 Series", "TS6000", TS6000_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS5000 Series", "TS5000", TS5000_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA MG3000 Series", "MG3000", MG3000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E470 Series", "E470", E470_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E410 Series", "E410", E410_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2017) Generation 5 CIS */
+ DEVICE ("Canon PIXMA G4000", "G4000", G4000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6100 Series", "TS6100", TS6100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS5100 Series", "TS5100", TS5100_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS3100 Series", "TS3100", TS3100_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E3100 Series", "E3100", E3100_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2018) Generation 5 CIS */
+ DEVICE ("Canon MAXIFY MB5400 Series", "MB5400", MB5400_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP | PIXMA_CAP_ADF_JPEG),
+ DEVICE ("Canon MAXIFY MB5100 Series", "MB5100", MB5100_PID, 0, 1200, 0, 0, 638, 1050, PIXMA_CAP_CIS | PIXMA_CAP_ADFDUP),
+ DEVICE ("Canon PIXMA TS9100 Series", "TS9100", TS9100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TR8500 Series", "TR8500", TR8500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TR7500 Series", "TR7500", TR7500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TS9500 Series", "TS9500", TS9500_PID, 0, 1200, 0, 600, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("CanoScan LiDE 400", "LIDE400", LIDE400_PID, 300, 4800, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("CanoScan LiDE 300", "LIDE300", LIDE300_PID, 300, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ /* Latest devices (2019) Generation 5 CIS */
+ DEVICE ("Canon PIXMA TS8100 Series", "TS8100", TS8100_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA G2010 Series", "G2010", G2010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA G3010 Series", "G3010", G3010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA G4010 Series", "G4010", G4010_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TS9180 Series", "TS9180", TS9180_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8180 Series", "TS8180", TS8180_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6180 Series", "TS6180", TS6180_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TR8580 Series", "TR8580", TR8580_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TS8130 Series", "TS8130", TS8130_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6130 Series", "TS6130", TS6130_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TR8530 Series", "TR8530", TR8530_PID, 0, 2400, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TR7530 Series", "TR7530", TR7530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXUS XK50 Series", "XK50", XK50_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXUS XK70 Series", "XK70", XK70_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TR4500 Series", "TR4500", TR4500_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA E4200 Series", "E4200", E4200_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TS6200 Series", "TS6200", TS6200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6280 Series", "TS6280", TS6280_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6230 Series", "TS6230", TS6230_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8200 Series", "TS8200", TS8200_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8280 Series", "TS8280", TS8280_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8230 Series", "TS8230", TS8230_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS9580 Series", "TS9580", TS9580_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA TR9530 Series", "TR9530", TR9530_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS | PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA G6000 Series", "G6000", G6000_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA G6080 Series", "G6080", G6080_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXUS XK80 Series", "XK80", XK80_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS5300 Series", "TS5300", TS5300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS5380 Series", "TS5380", TS5380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6300 Series", "TS6300", TS6300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6380 Series", "TS6380", TS6380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS7330 Series", "TS7330", TS7330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8380 Series", "TS8380", TS8380_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS8330 Series", "TS8330", TS8330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA XK60 Series", "XK60", XK60_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS6330 Series", "TS6330", TS6330_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA TS3300 Series", "TS3300", TS3300_PID, 0, 1200, 0, 0, 638, 877, PIXMA_CAP_CIS),
+ DEVICE ("Canon PIXMA E3300 Series", "E3300", E3300_PID, 0, 600, 0, 0, 638, 877, PIXMA_CAP_CIS),
+
+ END_OF_DEVICE_LIST
+};
diff --git a/backend/pixma/pixma_mp730.c b/backend/pixma/pixma_mp730.c
new file mode 100644
index 0000000..93d518b
--- /dev/null
+++ b/backend/pixma/pixma_mp730.c
@@ -0,0 +1,846 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#include "../include/sane/config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* localtime(C90) */
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+#define IMAGE_BLOCK_SIZE (0xc000)
+#define CMDBUF_SIZE 512
+
+#define MP10_PID 0x261f
+
+#define MP730_PID 0x262f
+#define MP700_PID 0x2630
+
+#define MP5_PID 0x2635 /* untested */
+
+#define MP360_PID 0x263c
+#define MP370_PID 0x263d
+#define MP390_PID 0x263e
+#define MP375R_PID 0x263f /* untested */
+
+#define MP740_PID 0x264c /* Untested */
+#define MP710_PID 0x264d
+
+#define MF5730_PID 0x265d /* Untested */
+#define MF5750_PID 0x265e /* Untested */
+#define MF5770_PID 0x265f
+#define MF3110_PID 0x2660
+
+#define IR1020_PID 0x26e6
+
+enum mp730_state_t
+{
+ state_idle,
+ state_warmup,
+ state_scanning,
+ state_transfering,
+ state_finished
+};
+
+enum mp730_cmd_t
+{
+ cmd_start_session = 0xdb20,
+ cmd_select_source = 0xdd20,
+ cmd_gamma = 0xee20,
+ cmd_scan_param = 0xde20,
+ cmd_status = 0xf320,
+ cmd_abort_session = 0xef20,
+ cmd_time = 0xeb80,
+ cmd_read_image = 0xd420,
+ cmd_error_info = 0xff20,
+
+ cmd_activate = 0xcf60,
+ cmd_calibrate = 0xe920
+};
+
+typedef struct mp730_t
+{
+ enum mp730_state_t state;
+ pixma_cmdbuf_t cb;
+ unsigned raw_width;
+ uint8_t current_status[12];
+
+ uint8_t *buf, *imgbuf, *lbuf;
+ unsigned imgbuf_len;
+
+ unsigned last_block:1;
+} mp730_t;
+
+
+static void mp730_finish_scan (pixma_t * s);
+
+static int
+has_paper (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ return (mp->current_status[1] == 0);
+}
+
+static void
+drain_bulk_in (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0);
+}
+
+static int
+abort_session (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session);
+}
+
+static int
+query_status (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_status, 0, 12);
+ error = pixma_exec (s, &mp->cb);
+ if (error >= 0)
+ {
+ memcpy (mp->current_status, data, 12);
+ PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u\n",
+ data[1], data[8], data[7]));
+ }
+ return error;
+}
+
+static int
+activate (pixma_t * s, uint8_t x)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mp->cb, cmd_activate, 10, 0);
+ data[0] = 1;
+ data[3] = x;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+start_session (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_start_session);
+}
+
+static int
+select_source (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mp->cb, cmd_select_source, 10, 0);
+ switch (s->param->source)
+ {
+ case PIXMA_SOURCE_ADF:
+ data[0] = 2;
+ break;
+
+ case PIXMA_SOURCE_ADFDUP:
+ data[0] = 2;
+ data[5] = 3;
+ break;
+
+ default:
+ data[0] = 1;
+ break;
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+send_scan_param (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *data;
+
+ data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x2e, 0);
+ pixma_set_be16 (s->param->xdpi | 0x1000, data + 0x04);
+ pixma_set_be16 (s->param->ydpi | 0x1000, data + 0x06);
+ pixma_set_be32 (s->param->x, data + 0x08);
+ pixma_set_be32 (s->param->y, data + 0x0c);
+ pixma_set_be32 (mp->raw_width, data + 0x10);
+ pixma_set_be32 (s->param->h, data + 0x14);
+
+ if (s->param->channels == 1)
+ {
+ if (s->param->depth == 1)
+ data[0x18] = 0x01;
+ else
+ data[0x18] = 0x04;
+ }
+ else
+ data[0x18] = 0x08;
+
+ data[0x19] = s->param->channels * s->param->depth; /* bits per pixel, for lineart should be 0x01 */
+ data[0x1e] = (s->param->depth == 1) ? 0x80 : 0x00; /* modify for lineart: 0x80 NEW */
+ data[0x1f] = (s->param->depth == 1) ? 0x80 : 0x7f; /* modify for lineart: 0x80 */
+ data[0x20] = (s->param->depth == 1) ? 0x01 : 0xff; /* modify for lineart: 0x01 */
+ data[0x23] = 0x81;
+
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+calibrate (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_calibrate);
+}
+
+static int
+read_image_block (pixma_t * s, uint8_t * header, uint8_t * data)
+{
+ static const uint8_t cmd[10] = /* 0xd420 cmd */
+ { 0xd4, 0x20, 0, 0, 0, 0, 0, IMAGE_BLOCK_SIZE / 256, 4, 0 };
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ const int hlen = 2 + 4;
+ int error, datalen;
+
+ mp->state = state_transfering;
+ mp->cb.reslen =
+ pixma_cmd_transaction (s, cmd, sizeof (cmd), mp->cb.buf, 512);
+ datalen = mp->cb.reslen;
+ if (datalen < 0)
+ return datalen;
+
+ memcpy (header, mp->cb.buf, hlen);
+ if (datalen >= hlen)
+ {
+ datalen -= hlen;
+ memcpy (data, mp->cb.buf + hlen, datalen);
+ data += datalen;
+ if (mp->cb.reslen == 512)
+ {
+ error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen);
+ if (error < 0)
+ return error;
+ datalen += error;
+ }
+ }
+
+ mp->state = state_scanning;
+ mp->cb.expected_reslen = 0;
+ error = pixma_check_result (&mp->cb);
+ if (error < 0)
+ return error;
+ if (mp->cb.reslen < hlen)
+ return PIXMA_EPROTO;
+ return datalen;
+}
+
+static int
+send_time (pixma_t * s)
+{
+ /* Why does a scanner need a time? */
+ time_t now;
+ struct tm *t;
+ uint8_t *data;
+ mp730_t *mp = (mp730_t *) s->subdriver;
+
+ data = pixma_newcmd (&mp->cb, cmd_time, 20, 0);
+ pixma_get_time (&now, NULL);
+ t = localtime (&now);
+ strftime ((char *) data, 16, "%y/%m/%d %H:%M", t);
+ PDBG (pixma_dbg (3, "Sending time: '%s'\n", (char *) data));
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+handle_interrupt (pixma_t * s, int timeout)
+{
+ uint8_t buf[16];
+ int len;
+
+ len = pixma_wait_interrupt (s->io, buf, sizeof (buf), timeout);
+ if (len == PIXMA_ETIMEDOUT)
+ return 0;
+ if (len < 0)
+ return len;
+ switch (s->cfg->pid)
+ {
+ case MP360_PID:
+ case MP370_PID:
+ case MP375R_PID:
+ case MP390_PID:
+ case MF5730_PID:
+ case MF5750_PID:
+ case MF5770_PID:
+ case MF3110_PID:
+ case IR1020_PID:
+ if (len != 16)
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING:unexpected interrupt packet length %d\n", len));
+ return PIXMA_EPROTO;
+ }
+ if (buf[12] & 0x40)
+ query_status (s);
+ if (buf[10] & 0x40)
+ send_time (s);
+ /* FIXME: following is unverified! */
+ if (buf[15] & 1)
+ s->events = PIXMA_EV_BUTTON2; /* b/w scan */
+ if (buf[15] & 2)
+ s->events = PIXMA_EV_BUTTON1; /* color scan */
+ break;
+
+ case MP5_PID:
+ case MP10_PID:
+ case MP700_PID:
+ case MP730_PID:
+ case MP710_PID:
+ case MP740_PID:
+ if (len != 8)
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING:unexpected interrupt packet length %d\n", len));
+ return PIXMA_EPROTO;
+ }
+ if (buf[7] & 0x10)
+ s->events = PIXMA_EV_BUTTON1;
+ if (buf[5] & 8)
+ send_time (s);
+ break;
+
+ default:
+ PDBG (pixma_dbg (1, "WARNING:unknown interrupt, please report!\n"));
+ PDBG (pixma_hexdump (1, buf, len));
+ }
+ return 1;
+}
+
+static int
+has_ccd_sensor (pixma_t * s)
+{
+ return (s->cfg->pid == MP360_PID ||
+ s->cfg->pid == MP370_PID ||
+ s->cfg->pid == MP375R_PID ||
+ s->cfg->pid == MP390_PID ||
+ s->cfg->pid == MF5730_PID ||
+ s->cfg->pid == MF5750_PID ||
+ s->cfg->pid == MF5770_PID);
+}
+
+static int
+read_error_info (pixma_t * s, void *buf, unsigned size)
+{
+ unsigned len = 16;
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len);
+ error = pixma_exec (s, &mp->cb);
+ if (error < 0)
+ return error;
+ if (buf && len < size)
+ {
+ size = len;
+ /* NOTE: I've absolutely no idea what the returned data mean. */
+ memcpy (buf, data, size);
+ error = len;
+ }
+ return error;
+}
+
+static int
+step1 (pixma_t * s)
+{
+ int error;
+
+ error = query_status (s);
+ if (error < 0)
+ return error;
+ if ((s->param->source == PIXMA_SOURCE_ADF
+ || s->param->source == PIXMA_SOURCE_ADFDUP)
+ && !has_paper (s))
+ return PIXMA_ENO_PAPER;
+ if (has_ccd_sensor (s))
+ {
+ switch (s->cfg->pid)
+ {
+ case MF5730_PID:
+ case MF5750_PID:
+ case MF5770_PID:
+ /* MF57x0: Wait 10 sec before starting for 1st page only */
+ if (s->param->adf_pageid == 0)
+ {
+ int tmo = 10; /* like Windows driver, 10 sec CCD calibration ? */
+ while (--tmo >= 0)
+ {
+ error = handle_interrupt (s, 1000); \
+ if (s->cancel) \
+ return PIXMA_ECANCELED; \
+ if (error != PIXMA_ECANCELED && error < 0) \
+ return error;
+ PDBG (pixma_dbg (2, "CCD Calibration ends in %d sec.\n", tmo));
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ activate (s, 0);
+ error = calibrate (s);
+
+ switch (s->cfg->pid)
+ {
+ case MF5730_PID:
+ case MF5750_PID:
+ case MF5770_PID:
+ /* MF57x0: calibration returns PIXMA_STATUS_FAILED */
+ if (error == PIXMA_ECANCELED)
+ error = read_error_info (s, NULL, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ // ignore result from calibrate()
+ // don't interrupt @ PIXMA_STATUS_BUSY
+ error = 0;
+ }
+ if (error >= 0)
+ error = activate (s, 0);
+ if (error >= 0)
+ error = activate (s, 4);
+ return error;
+}
+
+static void
+pack_rgb (const uint8_t * src, unsigned nlines, unsigned w, uint8_t * dst)
+{
+ unsigned w2, stride;
+
+ w2 = 2 * w;
+ stride = 3 * w;
+ for (; nlines != 0; nlines--)
+ {
+ unsigned x;
+ for (x = 0; x != w; x++)
+ {
+ *dst++ = src[x + 0];
+ *dst++ = src[x + w];
+ *dst++ = src[x + w2];
+ }
+ src += stride;
+ }
+}
+
+static int
+mp730_open (pixma_t * s)
+{
+ mp730_t *mp;
+ uint8_t *buf;
+
+ mp = (mp730_t *) calloc (1, sizeof (*mp));
+ if (!mp)
+ return PIXMA_ENOMEM;
+
+ buf = (uint8_t *) malloc (CMDBUF_SIZE);
+ if (!buf)
+ {
+ free (mp);
+ return PIXMA_ENOMEM;
+ }
+
+ s->subdriver = mp;
+ mp->state = state_idle;
+
+ mp->cb.buf = buf;
+ mp->cb.size = CMDBUF_SIZE;
+ mp->cb.res_header_len = 2;
+ mp->cb.cmd_header_len = 10;
+ mp->cb.cmd_len_field_ofs = 7;
+
+ PDBG (pixma_dbg (3, "Trying to clear the interrupt buffer...\n"));
+ if (handle_interrupt (s, 200) == 0)
+ {
+ PDBG (pixma_dbg (3, " no packets in buffer\n"));
+ }
+ return 0;
+}
+
+static void
+mp730_close (pixma_t * s)
+{
+ mp730_t *mp = (mp730_t *) s->subdriver;
+
+ mp730_finish_scan (s);
+ free (mp->cb.buf);
+ free (mp->buf);
+ free (mp);
+ s->subdriver = NULL;
+}
+
+static unsigned
+calc_raw_width (pixma_t * s, const pixma_scan_param_t * sp)
+{
+ unsigned raw_width;
+ /* FIXME: Does MP730 need the alignment? */
+ /* TODO test: MP710/740 */
+ if (sp->channels == 1)
+ {
+ if (sp->depth == 8) /* grayscale */
+ {
+ if (s->cfg->pid == MP5_PID ||
+ s->cfg->pid == MP10_PID ||
+ s->cfg->pid == MP700_PID ||
+ s->cfg->pid == MP730_PID ||
+ s->cfg->pid == MP360_PID ||
+ s->cfg->pid == MP370_PID ||
+ s->cfg->pid == MP375R_PID ||
+ s->cfg->pid == MP390_PID ||
+ s->cfg->pid == IR1020_PID)
+ raw_width = ALIGN_SUP (sp->w, 4);
+ else
+ raw_width = ALIGN_SUP (sp->w, 12);
+ }
+ else /* depth = 1 : LINEART */
+ raw_width = ALIGN_SUP (sp->w, 16);
+ }
+ else
+ raw_width = ALIGN_SUP (sp->w, 4);
+ return raw_width;
+}
+
+static int
+mp730_check_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ uint8_t k = 1;
+
+ /* check if channels is 3, or if depth is 1 then channels also 1 else set depth to 8 */
+ if ((sp->channels==3) || !(sp->channels==1 && sp->depth==1))
+ {
+ sp->depth=8;
+ }
+ /* for MP5, MP10, MP360/370, MP700/730 in grayscale & lineart modes, max scan res is 600 dpi */
+ if (s->cfg->pid == MP5_PID ||
+ s->cfg->pid == MP10_PID ||
+ s->cfg->pid == MP700_PID ||
+ s->cfg->pid == MP730_PID ||
+ s->cfg->pid == MP360_PID ||
+ s->cfg->pid == MP370_PID ||
+ s->cfg->pid == MP375R_PID ||
+ s->cfg->pid == MP390_PID)
+ {
+ if (sp->channels == 1)
+ k = sp->xdpi / MIN (sp->xdpi, 600);
+ }
+
+ sp->x /= k;
+ sp->y /= k;
+ sp->h /= k;
+ sp->xdpi /= k;
+ sp->ydpi = sp->xdpi;
+
+ sp->w = calc_raw_width (s, sp);
+ sp->w /= k;
+ sp->line_size = (calc_raw_width (s, sp) * sp->channels * sp->depth) / 8;
+
+ return 0;
+}
+
+static int
+mp730_scan (pixma_t * s)
+{
+ int error, n;
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ uint8_t *buf;
+
+ if (mp->state != state_idle)
+ return PIXMA_EBUSY;
+
+ /* clear interrupt packets buffer */
+ while (handle_interrupt (s, 0) > 0)
+ {
+ }
+
+ mp->raw_width = calc_raw_width (s, s->param);
+ PDBG (pixma_dbg (3, "raw_width = %u\n", mp->raw_width));
+
+ n = IMAGE_BLOCK_SIZE / s->param->line_size + 1;
+ buf = (uint8_t *) malloc ((n + 1) * s->param->line_size + IMAGE_BLOCK_SIZE);
+ if (!buf)
+ return PIXMA_ENOMEM;
+ mp->buf = buf;
+ mp->lbuf = buf;
+ mp->imgbuf = buf + n * s->param->line_size;
+ mp->imgbuf_len = 0;
+
+ error = step1 (s);
+ if (error >= 0)
+ error = start_session (s);
+ if (error >= 0)
+ mp->state = state_scanning;
+ if (error >= 0)
+ error = select_source (s);
+ if (error >= 0)
+ error = send_scan_param (s);
+ if (error < 0)
+ {
+ mp730_finish_scan (s);
+ return error;
+ }
+ mp->last_block = 0;
+ return 0;
+}
+
+static int
+mp730_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ int error, n;
+ mp730_t *mp = (mp730_t *) s->subdriver;
+ unsigned block_size, bytes_received;
+ uint8_t header[16];
+
+ do
+ {
+ do
+ {
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ if (mp->last_block) /* end of image */
+ return 0;
+
+ error = read_image_block (s, header, mp->imgbuf + mp->imgbuf_len);
+ if (error < 0)
+ return error;
+
+ bytes_received = error;
+ block_size = pixma_get_be16 (header + 4);
+ mp->last_block = ((header[2] & 0x28) == 0x28);
+ if (mp->last_block)
+ { /* end of image */
+ mp->state = state_finished;
+ }
+ if ((header[2] & ~0x38) != 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Unexpected result header\n"));
+ PDBG (pixma_hexdump (1, header, 16));
+ }
+ PASSERT (bytes_received == block_size);
+
+ if (block_size == 0)
+ {
+ /* no image data at this moment. */
+ /*pixma_sleep(100000); *//* FIXME: too short, too long? */
+ handle_interrupt (s, 100);
+ }
+ }
+ while (block_size == 0);
+
+ /* TODO: simplify! */
+ mp->imgbuf_len += bytes_received;
+ n = mp->imgbuf_len / s->param->line_size;
+ /* n = number of full lines (rows) we have in the buffer. */
+ if (n != 0)
+ {
+ if (s->param->channels != 1 &&
+ s->cfg->pid != MF5730_PID &&
+ s->cfg->pid != MF5750_PID &&
+ s->cfg->pid != MF5770_PID &&
+ s->cfg->pid != MF3110_PID &&
+ s->cfg->pid != IR1020_PID)
+ {
+ /* color, and not an MF57x0 nor MF3110 */
+ pack_rgb (mp->imgbuf, n, mp->raw_width, mp->lbuf);
+ }
+ else
+ /* grayscale/lineart or MF57x0 or MF3110 */
+ memcpy (mp->lbuf, mp->imgbuf, n * s->param->line_size);
+
+ block_size = n * s->param->line_size;
+ mp->imgbuf_len -= block_size;
+ memcpy (mp->imgbuf, mp->imgbuf + block_size, mp->imgbuf_len);
+ }
+ }
+ while (n == 0);
+
+ ib->rptr = mp->lbuf;
+ ib->rend = mp->lbuf + block_size;
+ return ib->rend - ib->rptr;
+}
+
+static void
+mp730_finish_scan (pixma_t * s)
+{
+ int error, aborted = 0;
+ mp730_t *mp = (mp730_t *) s->subdriver;
+
+ switch (mp->state)
+ {
+ case state_transfering:
+ drain_bulk_in (s);
+ /* fall through */
+ case state_scanning:
+ case state_warmup:
+ aborted = 1;
+ error = abort_session (s);
+ if (error < 0)
+ PDBG (pixma_dbg
+ (1, "WARNING:abort_session() failed %s\n",
+ pixma_strerror (error)));
+ /* fall through */
+ case state_finished:
+ query_status (s);
+ query_status (s);
+ activate (s, 0);
+
+ // MF57x0 devices don't require abort_session() after the last page
+ if (!aborted &&
+ (s->param->source == PIXMA_SOURCE_ADF ||
+ s->param->source == PIXMA_SOURCE_ADFDUP) &&
+ has_paper (s) &&
+ (s->cfg->pid == MF5730_PID ||
+ s->cfg->pid == MF5750_PID ||
+ s->cfg->pid == MF5770_PID ||
+ s->cfg->pid == IR1020_PID))
+ {
+ error = abort_session (s);
+ if (error < 0)
+ PDBG (pixma_dbg
+ (1, "WARNING:abort_session() failed %s\n",
+ pixma_strerror (error)));
+ }
+
+ mp->buf = mp->lbuf = mp->imgbuf = NULL;
+ mp->state = state_idle;
+ /* fall through */
+ case state_idle:
+ break;
+ }
+}
+
+static void
+mp730_wait_event (pixma_t * s, int timeout)
+{
+ /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for
+ * instance. */
+ while (s->events == 0 && handle_interrupt (s, timeout) > 0)
+ {
+ }
+}
+
+static int
+mp730_get_status (pixma_t * s, pixma_device_status_t * status)
+{
+ int error;
+
+ error = query_status (s);
+ if (error < 0)
+ return error;
+ status->hardware = PIXMA_HARDWARE_OK;
+ status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
+ return 0;
+}
+
+
+static const pixma_scan_ops_t pixma_mp730_ops = {
+ mp730_open,
+ mp730_close,
+ mp730_scan,
+ mp730_fill_buffer,
+ mp730_finish_scan,
+ mp730_wait_event,
+ mp730_check_param,
+ mp730_get_status
+};
+
+/* TODO: implement adftpu_min_dpi & adftpu_max_dpi for grayscale & lineart */
+#define DEVICE(name, model, pid, dpi, w, h, cap) { \
+ name, /* name */ \
+ model, /* model */ \
+ 0x04a9, pid, /* vid pid */ \
+ 1, /* iface */ \
+ &pixma_mp730_ops, /* ops */ \
+ 0, /* min_xdpi not used in this subdriver */ \
+ dpi, dpi, /* xdpi, ydpi */ \
+ 0, 0, /* adftpu_min_dpi & adftpu_max_dpi not used in this subdriver */ \
+ 0, 0, /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */ \
+ w, h, /* width, height */ \
+ PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap \
+}
+const pixma_config_t pixma_mp730_devices[] = {
+/* TODO: check area limits */
+ DEVICE ("PIXUS MP5/SmartBase MPC190/imageCLASS MPC190","MP5", MP5_PID, 600, 636, 868, PIXMA_CAP_LINEART),/* color scan can do 600x1200 */
+ DEVICE ("PIXUS MP10/SmartBase MPC200/imageCLASS MPC200","MP10", MP10_PID, 600, 636, 868, PIXMA_CAP_LINEART),/* color scan can do 600x1200 */
+ DEVICE ("PIXMA MP360", "MP360", MP360_PID, 1200, 636, 868, PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP370", "MP370", MP370_PID, 1200, 636, 868, PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP375R", "MP375R", MP375R_PID, 1200, 636, 868, PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP390", "MP390", MP390_PID, 1200, 636, 868, PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP700", "MP700", MP700_PID, 1200, 638, 877 /*1035 */ , PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP710", "MP710", MP710_PID, 1200, 637, 868, PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP730", "MP730", MP730_PID, 1200, 637, 868, PIXMA_CAP_ADF | PIXMA_CAP_LINEART),
+ DEVICE ("PIXMA MP740", "MP740", MP740_PID, 1200, 637, 868, PIXMA_CAP_ADF | PIXMA_CAP_LINEART),
+
+ DEVICE ("Canon imageCLASS MF5730", "MF5730", MF5730_PID, 1200, 636, 868, PIXMA_CAP_ADF),
+ DEVICE ("Canon imageCLASS MF5750", "MF5750", MF5750_PID, 1200, 636, 868, PIXMA_CAP_ADF),
+ DEVICE ("Canon imageCLASS MF5770", "MF5770", MF5770_PID, 1200, 636, 868, PIXMA_CAP_ADF),
+ DEVICE ("Canon imageCLASS MF3110", "MF3110", MF3110_PID, 600, 636, 868, 0),
+
+ DEVICE ("Canon iR 1020/1024/1025", "iR1020", IR1020_PID, 600, 636, 868, PIXMA_CAP_ADFDUP),
+
+ DEVICE (NULL, NULL, 0, 0, 0, 0, 0)
+};
diff --git a/backend/pixma/pixma_mp750.c b/backend/pixma/pixma_mp750.c
new file mode 100644
index 0000000..7f00023
--- /dev/null
+++ b/backend/pixma/pixma_mp750.c
@@ -0,0 +1,972 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+
+/****************************************************************************
+ * Credits should go to Martin Schewe (http://pixma.schewe.com) who analysed
+ * the protocol of MP750.
+ ****************************************************************************/
+
+#include "../include/sane/config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+/* TODO: remove lines marked with SIM. They are inserted so that I can test
+ the subdriver with the simulator. WY */
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+#define IMAGE_BLOCK_SIZE 0xc000
+#define CMDBUF_SIZE 512
+#define HAS_PAPER(s) (s[1] == 0)
+#define IS_WARMING_UP(s) (s[7] != 3)
+#define IS_CALIBRATED(s) (s[8] == 0xf)
+
+#define MP750_PID 0x1706
+#define MP760_PID 0x1708
+#define MP780_PID 0x1707
+
+
+enum mp750_state_t
+{
+ state_idle,
+ state_warmup,
+ state_scanning,
+ state_transfering,
+ state_finished
+};
+
+enum mp750_cmd_t
+{
+ cmd_start_session = 0xdb20,
+ cmd_select_source = 0xdd20,
+ cmd_scan_param = 0xde20,
+ cmd_status = 0xf320,
+ cmd_abort_session = 0xef20,
+ cmd_time = 0xeb80,
+ cmd_read_image = 0xd420,
+
+ cmd_activate = 0xcf60,
+ cmd_calibrate = 0xe920,
+ cmd_error_info = 0xff20
+};
+
+typedef struct mp750_t
+{
+ enum mp750_state_t state;
+ pixma_cmdbuf_t cb;
+ unsigned raw_width, raw_height;
+ uint8_t current_status[12];
+
+ uint8_t *buf, *rawimg, *img;
+ /* make new buffer for rgb_to_gray to act on */
+ uint8_t *imgcol;
+ unsigned line_size; /* need in 2 functions */
+
+ unsigned rawimg_left, imgbuf_len, last_block_size, imgbuf_ofs;
+ int shifted_bytes;
+ int stripe_shift; /* for 2400dpi */
+ unsigned last_block;
+
+ unsigned monochrome:1;
+ unsigned needs_abort:1;
+} mp750_t;
+
+
+
+static void mp750_finish_scan (pixma_t * s);
+static void check_status (pixma_t * s);
+
+static int
+has_paper (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return HAS_PAPER (mp->current_status);
+}
+
+static int
+is_warming_up (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return IS_WARMING_UP (mp->current_status);
+}
+
+static int
+is_calibrated (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return IS_CALIBRATED (mp->current_status);
+}
+
+static void
+drain_bulk_in (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ while (pixma_read (s->io, mp->buf, IMAGE_BLOCK_SIZE) >= 0);
+}
+
+static int
+abort_session (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session);
+}
+
+static int
+query_status (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_status, 0, 12);
+ error = pixma_exec (s, &mp->cb);
+ if (error >= 0)
+ {
+ memcpy (mp->current_status, data, 12);
+ PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u\n",
+ data[1], data[8], data[7]));
+ }
+ return error;
+}
+
+static int
+activate (pixma_t * s, uint8_t x)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mp->cb, cmd_activate, 10, 0);
+ data[0] = 1;
+ data[3] = x;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+activate_cs (pixma_t * s, uint8_t x)
+{
+ /*SIM*/ check_status (s);
+ return activate (s, x);
+}
+
+static int
+start_session (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_start_session);
+}
+
+static int
+select_source (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ uint8_t *data = pixma_newcmd (&mp->cb, cmd_select_source, 10, 0);
+ data[0] = (s->param->source == PIXMA_SOURCE_ADF) ? 2 : 1;
+ data[1] = 1;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+has_ccd_sensor (pixma_t * s)
+{
+ return ((s->cfg->cap & PIXMA_CAP_CCD) != 0);
+}
+
+static int
+is_ccd_grayscale (pixma_t * s)
+{
+ return (has_ccd_sensor (s) && (s->param->channels == 1));
+}
+
+/* CCD sensors don't have a Grayscale mode, but use color mode instead */
+static unsigned
+get_cis_ccd_line_size (pixma_t * s)
+{
+ return (s->param->wx ? s->param->line_size / s->param->w * s->param->wx
+ : s->param->line_size) * ((is_ccd_grayscale (s)) ? 3 : 1);
+}
+
+static int
+send_scan_param (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ uint8_t *data;
+
+ data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x2e, 0);
+ pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x04);
+ pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x06);
+ pixma_set_be32 (s->param->x, data + 0x08);
+ pixma_set_be32 (s->param->y, data + 0x0c);
+ pixma_set_be32 (mp->raw_width, data + 0x10);
+ pixma_set_be32 (mp->raw_height, data + 0x14);
+ data[0x18] = 8; /* 8 = color, 4 = grayscale(?) */
+ /* GH: No, there is no grayscale for CCD devices, Windows shows same */
+ data[0x19] = s->param->depth * ((is_ccd_grayscale (s)) ? 3 : s->param->channels); /* bits per pixel */
+ data[0x20] = 0xff;
+ data[0x23] = 0x81;
+ data[0x26] = 0x02;
+ data[0x27] = 0x01;
+ data[0x29] = mp->monochrome ? 0 : 1;
+
+ return pixma_exec (s, &mp->cb);
+}
+
+static int
+calibrate (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_calibrate);
+}
+
+static int
+calibrate_cs (pixma_t * s)
+{
+ /*SIM*/ check_status (s);
+ return calibrate (s);
+}
+
+static int
+request_image_block_ex (pixma_t * s, unsigned *size, uint8_t * info,
+ unsigned flag)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ int error;
+
+ memset (mp->cb.buf, 0, 10);
+ pixma_set_be16 (cmd_read_image, mp->cb.buf);
+ mp->cb.buf[7] = *size >> 8;
+ mp->cb.buf[8] = 4 | flag;
+ mp->cb.reslen = pixma_cmd_transaction (s, mp->cb.buf, 10, mp->cb.buf, 6);
+ mp->cb.expected_reslen = 0;
+ error = pixma_check_result (&mp->cb);
+ if (error >= 0)
+ {
+ if (mp->cb.reslen == 6)
+ {
+ *info = mp->cb.buf[2];
+ *size = pixma_get_be16 (mp->cb.buf + 4);
+ }
+ else
+ {
+ error = PIXMA_EPROTO;
+ }
+ }
+ return error;
+}
+
+static int
+request_image_block (pixma_t * s, unsigned *size, uint8_t * info)
+{
+ return request_image_block_ex (s, size, info, 0);
+}
+
+static int
+request_image_block2 (pixma_t * s, uint8_t * info)
+{
+ unsigned temp = 0;
+ return request_image_block_ex (s, &temp, info, 0x20);
+}
+
+static int
+read_image_block (pixma_t * s, uint8_t * data)
+{
+ int count, temp;
+
+ count = pixma_read (s->io, data, IMAGE_BLOCK_SIZE);
+ if (count < 0)
+ return count;
+ if (count == IMAGE_BLOCK_SIZE)
+ {
+ int error = pixma_read (s->io, &temp, 0);
+ if (error < 0)
+ {
+ PDBG (pixma_dbg
+ (1, "WARNING: reading zero-length packet failed %d\n", error));
+ }
+ }
+ return count;
+}
+
+static int
+read_error_info (pixma_t * s, void *buf, unsigned size)
+{
+ unsigned len = 16;
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len);
+ error = pixma_exec (s, &mp->cb);
+ if (error >= 0 && buf)
+ {
+ if (len < size)
+ size = len;
+ /* NOTE: I've absolutely no idea what the returned data mean. */
+ memcpy (buf, data, size);
+ error = len;
+ }
+ return error;
+}
+
+static int
+send_time (pixma_t * s)
+{
+ /* TODO: implement send_time() */
+ UNUSED (s);
+ PDBG (pixma_dbg (3, "send_time() is not yet implemented.\n"));
+ return 0;
+}
+
+static int
+handle_interrupt (pixma_t * s, int timeout)
+{
+ int error;
+ uint8_t intr[16];
+
+ error = pixma_wait_interrupt (s->io, intr, sizeof (intr), timeout);
+ if (error == PIXMA_ETIMEDOUT)
+ return 0;
+ if (error < 0)
+ return error;
+ if (error != 16)
+ {
+ PDBG (pixma_dbg (1, "WARNING: unexpected interrupt packet length %d\n",
+ error));
+ return PIXMA_EPROTO;
+ }
+
+ if (intr[10] & 0x40)
+ send_time (s);
+ if (intr[12] & 0x40)
+ query_status (s);
+ if (intr[15] & 1)
+ s->events = PIXMA_EV_BUTTON2; /* b/w scan */
+ if (intr[15] & 2)
+ s->events = PIXMA_EV_BUTTON1; /* color scan */
+ return 1;
+}
+
+static void
+check_status (pixma_t * s)
+{
+ while (handle_interrupt (s, 0) > 0);
+}
+
+static int
+step1 (pixma_t * s)
+{
+ int error, tmo;
+
+ error = activate (s, 0);
+ if (error < 0)
+ return error;
+ error = query_status (s);
+ if (error < 0)
+ return error;
+ if (s->param->source == PIXMA_SOURCE_ADF && !has_paper (s))
+ return PIXMA_ENO_PAPER;
+ error = activate_cs (s, 0);
+ /*SIM*/ if (error < 0)
+ return error;
+ error = activate_cs (s, 0x20);
+ if (error < 0)
+ return error;
+
+ tmo = 60;
+ error = calibrate_cs (s);
+ while (error == PIXMA_EBUSY && --tmo >= 0)
+ {
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ PDBG (pixma_dbg
+ (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1));
+ pixma_sleep (1000000);
+ error = calibrate_cs (s);
+ }
+ return error;
+}
+
+static void
+shift_rgb (const uint8_t * src, unsigned pixels,
+ int sr, int sg, int sb, int stripe_shift,
+ int line_size, uint8_t * dst)
+{
+ unsigned st;
+
+ for (; pixels != 0; pixels--)
+ {
+ st = (pixels % 2 == 0) ? -2 * stripe_shift * line_size : 0;
+ *(dst++ + sr + st) = *src++;
+ *(dst++ + sg + st) = *src++;
+ *(dst++ + sb + st) = *src++;
+ }
+}
+
+static uint8_t *
+rgb_to_gray (uint8_t * gptr, const uint8_t * cptr, unsigned pixels, unsigned c)
+{
+ unsigned i, j, g;
+
+ /* gptr: destination gray scale buffer */
+ /* cptr: source color scale buffer */
+ /* c: 3 for 3-channel single-byte data, 6 for double-byte data */
+
+ for (i=0; i < pixels; i++)
+ {
+ for (j = 0, g = 0; j < 3; j++)
+ {
+ g += *cptr++;
+ if (c == 6) g += (*cptr++ << 8);
+ }
+ g /= 3;
+ *gptr++ = g;
+ if (c == 6) *gptr++ = (g >> 8);
+ }
+ return gptr;
+}
+
+static int
+calc_component_shifting (pixma_t * s)
+{
+ switch (s->cfg->pid)
+ {
+ case MP760_PID:
+ switch (s->param->ydpi)
+ {
+ case 300:
+ return 3;
+ case 600:
+ return 6;
+ default:
+ return s->param->ydpi / 75;
+ }
+ /* never reached */
+ break;
+
+ case MP750_PID:
+ case MP780_PID:
+ default:
+ return 2 * s->param->ydpi / 75;
+ }
+}
+
+static void
+workaround_first_command (pixma_t * s)
+{
+ /* FIXME: Send a dummy command because the device doesn't response to the
+ first command that is sent directly after the USB interface has been
+ set up. Why? USB isn't set up properly? */
+ uint8_t cmd[10];
+ int error;
+
+ if (s->cfg->pid == MP750_PID)
+ return; /* MP750 doesn't have this problem(?) */
+
+ PDBG (pixma_dbg
+ (1,
+ "Work-around for the problem: device doesn't response to the first command.\n"));
+ memset (cmd, 0, sizeof (cmd));
+ pixma_set_be16 (cmd_calibrate, cmd);
+ error = pixma_write (s->io, cmd, 10);
+ if (error != 10)
+ {
+ if (error < 0)
+ {
+ PDBG (pixma_dbg
+ (1, " Sending a dummy command failed: %s\n",
+ pixma_strerror (error)));
+ }
+ else
+ {
+ PDBG (pixma_dbg
+ (1, " Sending a dummy command failed: count = %d\n", error));
+ }
+ return;
+ }
+ error = pixma_read (s->io, cmd, sizeof (cmd));
+ if (error >= 0)
+ {
+ PDBG (pixma_dbg
+ (1, " Got %d bytes response from a dummy command.\n", error));
+ }
+ else
+ {
+ PDBG (pixma_dbg
+ (1, " Reading response of a dummy command failed: %s\n",
+ pixma_strerror (error)));
+ }
+}
+
+static int
+mp750_open (pixma_t * s)
+{
+ mp750_t *mp;
+ uint8_t *buf;
+
+ mp = (mp750_t *) calloc (1, sizeof (*mp));
+ if (!mp)
+ return PIXMA_ENOMEM;
+
+ buf = (uint8_t *) malloc (CMDBUF_SIZE);
+ if (!buf)
+ {
+ free (mp);
+ return PIXMA_ENOMEM;
+ }
+
+ s->subdriver = mp;
+ mp->state = state_idle;
+
+ /* ofs: 0 1 2 3 4 5 6 7 8 9
+ cmd: cmd1 cmd2 00 00 00 00 00 00 00 00
+ data length-^^^^^ => cmd_len_field_ofs
+ |--------- cmd_header_len --------|
+
+ res: res1 res2
+ |---------| res_header_len
+ */
+ mp->cb.buf = buf;
+ mp->cb.size = CMDBUF_SIZE;
+ mp->cb.res_header_len = 2;
+ mp->cb.cmd_header_len = 10;
+ mp->cb.cmd_len_field_ofs = 7;
+
+ handle_interrupt (s, 200);
+ workaround_first_command (s);
+ return 0;
+}
+
+static void
+mp750_close (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+
+ mp750_finish_scan (s);
+ free (mp->cb.buf);
+ free (mp);
+ s->subdriver = NULL;
+}
+
+static int
+mp750_check_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ unsigned raw_width;
+
+ UNUSED (s);
+
+ sp->depth = 8; /* FIXME: Does MP750 supports other depth? */
+
+ /* GH: my implementation */
+ /* if ((sp->channels == 3) || (is_ccd_grayscale (s)))
+ raw_width = ALIGN_SUP (sp->w, 4);
+ else
+ raw_width = ALIGN_SUP (sp->w, 12);*/
+
+ /* the above code gives segmentation fault?!? why... it seems to work in the mp750_scan function */
+ raw_width = ALIGN_SUP (sp->w, 4);
+
+ /*sp->line_size = raw_width * sp->channels;*/
+ sp->line_size = raw_width * sp->channels * (sp->depth / 8); /* no cropping? */
+ return 0;
+}
+
+static int
+mp750_scan (pixma_t * s)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ int error;
+ uint8_t *buf;
+ unsigned size, dpi, spare;
+
+ dpi = s->param->ydpi;
+ /* add a stripe shift for 2400dpi */
+ mp->stripe_shift = (dpi == 2400) ? 4 : 0;
+
+ if (mp->state != state_idle)
+ return PIXMA_EBUSY;
+
+ /* clear interrupt packets buffer */
+ while (handle_interrupt (s, 0) > 0)
+ {
+ }
+
+ /* if (s->param->channels == 1)
+ mp->raw_width = ALIGN_SUP (s->param->w, 12);
+ else
+ mp->raw_width = ALIGN_SUP (s->param->w, 4);*/
+
+ /* change to use CCD grayscale mode --- why does this give segmentation error at runtime in mp750_check_param? */
+ if ((s->param->channels == 3) || (is_ccd_grayscale (s)))
+ mp->raw_width = ALIGN_SUP (s->param->w, 4);
+ else
+ mp->raw_width = ALIGN_SUP (s->param->w, 12);
+ /* not sure about MP750, but there is no need for aligning at 12 for the MP760/770, MP780/790 since always use CCD color mode */
+
+ /* modify for stripe shift */
+ spare = 2 * calc_component_shifting (s) + 2 * mp->stripe_shift; /* FIXME: or maybe (2*... + 1)? */
+ mp->raw_height = s->param->h + spare;
+ PDBG (pixma_dbg (3, "raw_width=%u raw_height=%u dpi=%u\n",
+ mp->raw_width, mp->raw_height, dpi));
+
+ /* PDBG (pixma_dbg (4, "line_size=%"PRIu64"\n",s->param->line_size)); */
+
+ mp->line_size = get_cis_ccd_line_size (s); /* scanner hardware line_size multiplied by 3 for CCD grayscale */
+
+ size = 8 + 2 * IMAGE_BLOCK_SIZE + spare * mp->line_size;
+ buf = (uint8_t *) malloc (size);
+ if (!buf)
+ return PIXMA_ENOMEM;
+ mp->buf = buf;
+ mp->rawimg = buf;
+ mp->imgbuf_ofs = spare * mp->line_size;
+ mp->imgcol = mp->rawimg + IMAGE_BLOCK_SIZE + 8; /* added to make rgb->gray */
+ mp->img = mp->rawimg + IMAGE_BLOCK_SIZE + 8;
+ mp->imgbuf_len = IMAGE_BLOCK_SIZE + mp->imgbuf_ofs;
+ mp->rawimg_left = 0;
+ mp->last_block_size = 0;
+ mp->shifted_bytes = -(int) mp->imgbuf_ofs;
+
+ error = step1 (s);
+ if (error >= 0)
+ error = start_session (s);
+ if (error >= 0)
+ mp->state = state_warmup;
+ if (error >= 0)
+ error = select_source (s);
+ if (error >= 0)
+ error = send_scan_param (s);
+ if (error < 0)
+ {
+ mp750_finish_scan (s);
+ return error;
+ }
+ return 0;
+}
+
+
+static int
+mp750_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ mp750_t *mp = (mp750_t *) s->subdriver;
+ int error;
+ uint8_t info;
+ unsigned block_size, bytes_received, n;
+ int shift[3], base_shift;
+ int c;
+
+ c = ((is_ccd_grayscale (s)) ? 3 : s->param->channels) * s->param->depth / 8; /* single-byte or double-byte data */
+
+ if (mp->state == state_warmup)
+ {
+ int tmo = 60;
+
+ query_status (s);
+ check_status (s);
+ /*SIM*/ while (!is_calibrated (s) && --tmo >= 0)
+ {
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ if (handle_interrupt (s, 1000) > 0)
+ {
+ block_size = 0;
+ error = request_image_block (s, &block_size, &info);
+ /*SIM*/ if (error < 0)
+ return error;
+ }
+ }
+ if (tmo < 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Timed out waiting for calibration\n"));
+ return PIXMA_ETIMEDOUT;
+ }
+ pixma_sleep (100000);
+ query_status (s);
+ if (is_warming_up (s) || !is_calibrated (s))
+ {
+ PDBG (pixma_dbg (1, "WARNING: Wrong status: wup=%d cal=%d\n",
+ is_warming_up (s), is_calibrated (s)));
+ return PIXMA_EPROTO;
+ }
+ block_size = 0;
+ request_image_block (s, &block_size, &info);
+ /*SIM*/ mp->state = state_scanning;
+ mp->last_block = 0;
+ }
+
+ /* TODO: Move to other place, values are constant. */
+ base_shift = calc_component_shifting (s) * mp->line_size;
+ if (s->param->source == PIXMA_SOURCE_ADF)
+ {
+ shift[0] = 0;
+ shift[1] = -base_shift;
+ shift[2] = -2 * base_shift;
+ }
+ else
+ {
+ shift[0] = -2 * base_shift;
+ shift[1] = -base_shift;
+ shift[2] = 0;
+ }
+
+ do
+ {
+ if (mp->last_block_size > 0)
+ {
+ block_size = mp->imgbuf_len - mp->last_block_size;
+ memcpy (mp->img, mp->img + mp->last_block_size, block_size);
+ }
+
+ do
+ {
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ if (mp->last_block)
+ {
+ /* end of image */
+ info = mp->last_block;
+ if (info != 0x38)
+ {
+ query_status (s);
+ /*SIM*/ while ((info & 0x28) != 0x28)
+ {
+ pixma_sleep (10000);
+ error = request_image_block2 (s, &info);
+ if (s->cancel)
+ return PIXMA_ECANCELED; /* FIXME: Is it safe to cancel here? */
+ if (error < 0)
+ return error;
+ }
+ }
+ mp->needs_abort = (info != 0x38);
+ mp->last_block = info;
+ mp->state = state_finished;
+ return 0;
+ }
+
+ check_status (s);
+ /*SIM*/ while (handle_interrupt (s, 1) > 0);
+ /*SIM*/ block_size = IMAGE_BLOCK_SIZE;
+ error = request_image_block (s, &block_size, &info);
+ if (error < 0)
+ {
+ if (error == PIXMA_ECANCELED)
+ read_error_info (s, NULL, 0);
+ return error;
+ }
+ mp->last_block = info;
+ if ((info & ~0x38) != 0)
+ {
+ PDBG (pixma_dbg (1, "WARNING: Unknown info byte %x\n", info));
+ }
+ if (block_size == 0)
+ {
+ /* no image data at this moment. */
+ pixma_sleep (10000);
+ }
+ }
+ while (block_size == 0);
+
+ error = read_image_block (s, mp->rawimg + mp->rawimg_left);
+ if (error < 0)
+ {
+ mp->state = state_transfering;
+ return error;
+ }
+ bytes_received = error;
+ PASSERT (bytes_received == block_size);
+
+ /* TODO: simplify! */
+ mp->rawimg_left += bytes_received;
+ n = mp->rawimg_left / 3;
+ /* n = number of pixels in the buffer? */
+
+ /* Color to Grayscale converion for CCD sensor */
+ if (is_ccd_grayscale (s)) {
+ shift_rgb (mp->rawimg, n, shift[0], shift[1], shift[2], mp->stripe_shift, mp->line_size,
+ mp->imgcol + mp->imgbuf_ofs);
+ /* dst: img, src: imgcol */
+ rgb_to_gray (mp->img, mp->imgcol, n, c); /* cropping occurs later? */
+ PDBG (pixma_dbg (4, "*fill_buffer: did grayscale conversion \n"));
+ }
+ /* Color image processing */
+ else {
+ shift_rgb (mp->rawimg, n, shift[0], shift[1], shift[2], mp->stripe_shift, mp->line_size,
+ mp->img + mp->imgbuf_ofs);
+ PDBG (pixma_dbg (4, "*fill_buffer: no grayscale conversion---keep color \n"));
+ }
+
+ /* entering remaining unprocessed bytes after last complete pixel into mp->rawimg buffer -- no influence on mp->img */
+ n *= 3;
+ mp->shifted_bytes += n;
+ mp->rawimg_left -= n; /* rawimg_left = 0, 1 or 2 bytes left in the buffer. */
+ mp->last_block_size = n;
+ memcpy (mp->rawimg, mp->rawimg + n, mp->rawimg_left);
+
+ }
+ while (mp->shifted_bytes <= 0);
+
+ if ((unsigned) mp->shifted_bytes < mp->last_block_size)
+ {
+ if (is_ccd_grayscale (s))
+ ib->rptr = mp->img + mp->last_block_size/3 - mp->shifted_bytes/3; /* testing---works OK */
+ else
+ ib->rptr = mp->img + mp->last_block_size - mp->shifted_bytes;
+ }
+ else
+ ib->rptr = mp->img;
+ if (is_ccd_grayscale (s))
+ ib->rend = mp->img + mp->last_block_size/3; /* testing---works OK */
+ else
+ ib->rend = mp->img + mp->last_block_size;
+ return ib->rend - ib->rptr;
+}
+
+static void
+mp750_finish_scan (pixma_t * s)
+{
+ int error;
+ mp750_t *mp = (mp750_t *) s->subdriver;
+
+ switch (mp->state)
+ {
+ case state_transfering:
+ drain_bulk_in (s);
+ /* fall through */
+ case state_scanning:
+ case state_warmup:
+ error = abort_session (s);
+ if (error == PIXMA_ECANCELED)
+ read_error_info (s, NULL, 0);
+ /* fall through */
+ case state_finished:
+ if (s->param->source == PIXMA_SOURCE_FLATBED)
+ {
+ /*SIM*/ query_status (s);
+ if (abort_session (s) == PIXMA_ECANCELED)
+ {
+ read_error_info (s, NULL, 0);
+ query_status (s);
+ }
+ }
+ query_status (s);
+ /*SIM*/ activate (s, 0);
+ if (mp->needs_abort)
+ {
+ mp->needs_abort = 0;
+ abort_session (s);
+ }
+ free (mp->buf);
+ mp->buf = mp->rawimg = NULL;
+ mp->state = state_idle;
+ /* fall through */
+ case state_idle:
+ break;
+ }
+}
+
+static void
+mp750_wait_event (pixma_t * s, int timeout)
+{
+ /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for
+ * instance. */
+ while (s->events == 0 && handle_interrupt (s, timeout) > 0)
+ {
+ }
+}
+
+static int
+mp750_get_status (pixma_t * s, pixma_device_status_t * status)
+{
+ int error;
+
+ error = query_status (s);
+ if (error < 0)
+ return error;
+ status->hardware = PIXMA_HARDWARE_OK;
+ status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
+ status->cal =
+ (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF;
+ status->lamp = (is_warming_up (s)) ? PIXMA_LAMP_WARMING_UP : PIXMA_LAMP_OK;
+ return 0;
+}
+
+
+static const pixma_scan_ops_t pixma_mp750_ops = {
+ mp750_open,
+ mp750_close,
+ mp750_scan,
+ mp750_fill_buffer,
+ mp750_finish_scan,
+ mp750_wait_event,
+ mp750_check_param,
+ mp750_get_status
+};
+
+#define DEVICE(name, model, pid, dpi, cap) { \
+ name, /* name */ \
+ model, /* model */ \
+ 0x04a9, pid, /* vid pid */ \
+ 0, /* iface */ \
+ &pixma_mp750_ops, /* ops */ \
+ 0, /* min_xdpi not used in this subdriver */ \
+ dpi, 2*(dpi), /* xdpi, ydpi */ \
+ 0, 0, /* adftpu_min_dpi & adftpu_max_dpi not used in this subdriver */ \
+ 0, 0, /* tpuir_min_dpi & tpuir_max_dpi not used in this subdriver */ \
+ 637, 877, /* width, height */ \
+ PIXMA_CAP_CCD| /* all scanners with CCD */ \
+ PIXMA_CAP_GRAY|PIXMA_CAP_EVENTS|cap \
+}
+
+const pixma_config_t pixma_mp750_devices[] = {
+ DEVICE ("Canon PIXMA MP750", "MP750", MP750_PID, 2400, PIXMA_CAP_ADF),
+ DEVICE ("Canon PIXMA MP760/770", "MP760/770", MP760_PID, 2400, PIXMA_CAP_TPU),
+ DEVICE ("Canon PIXMA MP780/790", "MP780/790", MP780_PID, 2400, PIXMA_CAP_ADF),
+ DEVICE (NULL, NULL, 0, 0, 0)
+};
diff --git a/backend/pixma/pixma_mp800.c b/backend/pixma/pixma_mp800.c
new file mode 100644
index 0000000..feef611
--- /dev/null
+++ b/backend/pixma/pixma_mp800.c
@@ -0,0 +1,2434 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de>
+ Copyright (C) 2007-2009 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+/* test cases
+ 1. short USB packet (must be no -ETIMEDOUT)
+ 2. cancel using button on the printer (look for abort command)
+ 3. start scan while busy (status 0x1414)
+ 4. cancel using ctrl-c (must send abort command)
+ */
+
+#define TPU_48 /* uncomment to activate TPU scan at 48 bits */
+/*#define DEBUG_TPU_48*//* uncomment to debug 48 bits TPU on a non TPU device */
+/*#define DEBUG_TPU_24*//* uncomment to debug 24 bits TPU on a non TPU device */
+
+/*#define TPUIR_USE_RGB*/ /* uncomment to use RGB channels and convert them to gray; otherwise use R channel only */
+
+#include "../include/sane/config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* localtime(C90) */
+
+#include "pixma_rename.h"
+#include "pixma_common.h"
+#include "pixma_io.h"
+
+/* Some macro code to enhance readability */
+#define RET_IF_ERR(x) do { \
+ if ((error = (x)) < 0) \
+ return error; \
+ } while(0)
+
+#define WAIT_INTERRUPT(x) do { \
+ error = handle_interrupt (s, x); \
+ if (s->cancel) \
+ return PIXMA_ECANCELED; \
+ if (error != PIXMA_ECANCELED && error < 0) \
+ return error; \
+ } while(0)
+
+#ifdef __GNUC__
+# define UNUSED(v) (void) v
+#else
+# define UNUSED(v)
+#endif
+
+/* Size of the command buffer should be multiple of wMaxPacketLength and
+ greater than 4096+24.
+ 4096 = size of gamma table. 24 = header + checksum */
+#define IMAGE_BLOCK_SIZE (512*1024)
+#define CMDBUF_SIZE (4096 + 24)
+#define DEFAULT_GAMMA 2.0 /***** Gamma different from 1.0 is potentially impacting color profile generation *****/
+#define UNKNOWN_PID 0xffff
+
+#define CANON_VID 0x04a9
+
+/* Generation 1 */
+#define MP800_PID 0x170d
+#define MP800R_PID 0x170e
+#define MP830_PID 0x1713
+
+/* Generation 2 */
+#define MP810_PID 0x171a
+#define MP960_PID 0x171b
+
+/* Generation 3 */
+/* PIXMA 2007 vintage */
+#define MP970_PID 0x1726
+
+/* Flatbed scanner CCD (2007) */
+#define CS8800F_PID 0x1901
+
+/* PIXMA 2008 vintage */
+#define MP980_PID 0x172d
+
+/* Generation 4 */
+#define MP990_PID 0x1740
+
+/* Flatbed scanner CCD (2010) */
+#define CS9000F_PID 0x1908
+
+/* 2010 new device (untested) */
+#define MG8100_PID 0x174b /* CCD */
+
+/* 2011 new device (untested) */
+#define MG8200_PID 0x1756 /* CCD */
+
+/* 2013 new device */
+#define CS9000F_MII_PID 0x190d
+
+/* Generation 4 XML messages that encapsulates the Pixma protocol messages */
+#define XML_START_1 \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\
+<ivec:contents><ivec:operation>StartJob</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+<ivec:bidi>1</ivec:bidi></ivec:param_set></ivec:contents></cmd>"
+
+#define XML_START_2 \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\" xmlns:vcn=\"http://www.canon.com/ns/cmd/2008/07/canon/\">\
+<ivec:contents><ivec:operation>VendorCmd</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+<vcn:ijoperation>ModeShift</vcn:ijoperation><vcn:ijmode>1</vcn:ijmode>\
+</ivec:param_set></ivec:contents></cmd>"
+
+#define XML_END \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\
+<cmd xmlns:ivec=\"http://www.canon.com/ns/cmd/2008/07/common/\">\
+<ivec:contents><ivec:operation>EndJob</ivec:operation>\
+<ivec:param_set servicetype=\"scan\"><ivec:jobID>00000001</ivec:jobID>\
+</ivec:param_set></ivec:contents></cmd>"
+
+#define XML_OK "<ivec:response>OK</ivec:response>"
+
+enum mp810_state_t
+{
+ state_idle,
+ state_warmup,
+ state_scanning,
+ state_transfering,
+ state_finished
+};
+
+enum mp810_cmd_t
+{
+ cmd_start_session = 0xdb20,
+ cmd_select_source = 0xdd20,
+ cmd_gamma = 0xee20,
+ cmd_scan_param = 0xde20,
+ cmd_status = 0xf320,
+ cmd_abort_session = 0xef20,
+ cmd_time = 0xeb80,
+ cmd_read_image = 0xd420,
+ cmd_error_info = 0xff20,
+
+ cmd_start_calibrate_ccd_3 = 0xd520,
+ cmd_end_calibrate_ccd_3 = 0xd720,
+ cmd_scan_param_3 = 0xd820,
+ cmd_scan_start_3 = 0xd920,
+ cmd_status_3 = 0xda20,
+ cmd_get_tpu_info_3 = 0xf520,
+ cmd_set_tpu_info_3 = 0xea20,
+
+ cmd_e920 = 0xe920 /* seen in MP800 */
+};
+
+typedef struct mp810_t
+{
+ enum mp810_state_t state;
+ pixma_cmdbuf_t cb;
+ uint8_t *imgbuf;
+ uint8_t current_status[16];
+ unsigned last_block;
+ uint8_t generation;
+ /* for Generation 3 and CCD shift */
+ uint8_t *linebuf;
+ uint8_t *data_left_ofs;
+ unsigned data_left_len;
+ int shift[3];
+ unsigned color_shift;
+ unsigned stripe_shift;
+ unsigned stripe_shift2; /* added for MP810, MP960 at 4800dpi & 9000F at 9600dpi */
+ unsigned jumplines; /* added for MP810, MP960 at 4800dpi & 9000F at 9600dpi */
+ uint8_t tpu_datalen;
+ uint8_t tpu_data[0x40];
+} mp810_t;
+
+/*
+ STAT: 0x0606 = ok,
+ 0x1515 = failed (PIXMA_ECANCELED),
+ 0x1414 = busy (PIXMA_EBUSY)
+
+ Transaction scheme
+ 1. command_header/data | result_header
+ 2. command_header | result_header/data
+ 3. command_header | result_header/image_data
+
+ - data has checksum in the last byte.
+ - image_data has no checksum.
+ - data and image_data begins in the same USB packet as
+ command_header or result_header.
+
+ command format #1:
+ u16be cmd
+ u8[6] 0
+ u8[4] 0
+ u32be PLEN parameter length
+ u8[PLEN-1] parameter
+ u8 parameter check sum
+ result:
+ u16be STAT
+ u8 0
+ u8 0 or 0x21 if STAT == 0x1414
+ u8[4] 0
+
+ command format #2:
+ u16be cmd
+ u8[6] 0
+ u8[4] 0
+ u32be RLEN result length
+ result:
+ u16be STAT
+ u8[6] 0
+ u8[RLEN-1] result
+ u8 result check sum
+
+ command format #3: (only used by read_image_block)
+ u16be 0xd420
+ u8[6] 0
+ u8[4] 0
+ u32be max. block size + 8
+ result:
+ u16be STAT
+ u8[6] 0
+ u8 block info bitfield: 0x8 = end of scan, 0x10 = no more paper, 0x20 = no more data
+ u8[3] 0
+ u32be ILEN image data size
+ u8[ILEN] image data
+ */
+
+static void mp810_finish_scan (pixma_t * s);
+
+static int is_scanning_from_adf (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADF
+ || s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static int is_scanning_from_adfdup (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_ADFDUP);
+}
+
+static int is_scanning_from_tpu (pixma_t * s)
+{
+ return (s->param->source == PIXMA_SOURCE_TPU);
+}
+
+static int send_xml_dialog (pixma_t * s, const char * xml_message)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ int datalen;
+
+ datalen = pixma_cmd_transaction (s, xml_message, strlen (xml_message),
+ mp->cb.buf, 1024);
+ if (datalen < 0)
+ return datalen;
+
+ mp->cb.buf[datalen] = 0;
+
+ PDBG(pixma_dbg (10, "XML message sent to scanner:\n%s\n", xml_message));
+ PDBG(pixma_dbg (10, "XML response back from scanner:\n%s\n", mp->cb.buf));
+
+ return (strcasestr ((const char *) mp->cb.buf, XML_OK) != NULL);
+}
+
+static void new_cmd_tpu_msg (pixma_t *s, pixma_cmdbuf_t * cb, uint16_t cmd)
+{
+ pixma_newcmd (cb, cmd, 0, 0);
+ cb->buf[3] = (is_scanning_from_tpu (s)) ? 0x01 : 0x00;
+}
+
+static int start_session (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ new_cmd_tpu_msg (s, &mp->cb, cmd_start_session);
+ return pixma_exec (s, &mp->cb);
+}
+
+static int start_scan_3 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ new_cmd_tpu_msg (s, &mp->cb, cmd_scan_start_3);
+ return pixma_exec (s, &mp->cb);
+}
+
+static int send_cmd_start_calibrate_ccd_3 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ pixma_newcmd (&mp->cb, cmd_start_calibrate_ccd_3, 0, 0);
+ mp->cb.buf[3] = 0x01;
+ return pixma_exec (s, &mp->cb);
+}
+
+static int is_calibrated (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ if (mp->generation >= 3)
+ {
+ return ((mp->current_status[0] & 0x01) == 1);
+ }
+ if (mp->generation == 1)
+ {
+ return (mp->current_status[8] == 1);
+ }
+ else
+ {
+ return (mp->current_status[9] == 1);
+ }
+}
+
+static int has_paper (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ if (is_scanning_from_adfdup (s))
+ return (mp->current_status[1] == 0 || mp->current_status[2] == 0);
+ else
+ return (mp->current_status[1] == 0);
+}
+
+static void drain_bulk_in (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ while (pixma_read (s->io, mp->imgbuf, IMAGE_BLOCK_SIZE) >= 0)
+ ;
+}
+
+static int abort_session (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_abort_session);
+}
+
+static int send_cmd_e920 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ return pixma_exec_short_cmd (s, &mp->cb, cmd_e920);
+}
+
+static int select_source (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+
+ data = pixma_newcmd (&mp->cb, cmd_select_source, 12, 0);
+ data[5] = ((mp->generation == 2) ? 1 : 0);
+ switch (s->param->source)
+ {
+ case PIXMA_SOURCE_FLATBED:
+ data[0] = 1;
+ data[1] = 1;
+ break;
+
+ case PIXMA_SOURCE_ADF:
+ data[0] = 2;
+ data[5] = 1;
+ data[6] = 1;
+ break;
+
+ case PIXMA_SOURCE_ADFDUP:
+ data[0] = 2;
+ data[5] = 3;
+ data[6] = 3;
+ break;
+
+ case PIXMA_SOURCE_TPU:
+ data[0] = 4;
+ data[1] = 2;
+ break;
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static int send_get_tpu_info_3 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_get_tpu_info_3, 0, 0x34);
+ RET_IF_ERR(pixma_exec (s, &mp->cb));
+ memcpy (mp->tpu_data, data, 0x34);
+ return error;
+}
+
+static int send_set_tpu_info (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+
+ if (mp->tpu_datalen == 0)
+ return 0;
+ data = pixma_newcmd (&mp->cb, cmd_set_tpu_info_3, 0x34, 0);
+ memcpy (data, mp->tpu_data, 0x34);
+ return pixma_exec (s, &mp->cb);
+}
+
+static int send_gamma_table (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ const uint8_t *lut = s->param->gamma_table;
+ uint8_t *data;
+
+ if (mp->generation == 1)
+ {
+ data = pixma_newcmd (&mp->cb, cmd_gamma, 4096 + 8, 0);
+ data[0] = (s->param->channels == 3) ? 0x10 : 0x01;
+ pixma_set_be16 (0x1004, data + 2);
+ if (lut)
+ memcpy (data + 4, lut, 4096);
+ else
+ pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 4096);
+ }
+ else
+ {
+ /* FIXME: Gamma table for 2nd generation: 1024 * uint16_le */
+ data = pixma_newcmd (&mp->cb, cmd_gamma, 2048 + 8, 0);
+ data[0] = 0x10;
+ pixma_set_be16 (0x0804, data + 2);
+ if (lut)
+ {
+ int i;
+ for (i = 0; i < 1024; i++)
+ {
+ int j = (i << 2) + (i >> 8);
+ data[4 + 2 * i + 0] = lut[j];
+ data[4 + 2 * i + 1] = lut[j];
+ }
+ }
+ else
+ {
+ int i;
+ pixma_fill_gamma_table (DEFAULT_GAMMA, data + 4, 2048);
+ for (i = 0; i < 1024; i++)
+ {
+ int j = (i << 1) + (i >> 9);
+ data[4 + 2 * i + 0] = data[4 + j];
+ data[4 + 2 * i + 1] = data[4 + j];
+ }
+ }
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static unsigned calc_raw_width (const mp810_t * mp,
+ const pixma_scan_param_t * param)
+{
+ unsigned raw_width;
+ /* NOTE: Actually, we can send arbitary width to MP810. Lines returned
+ are always padded to multiple of 4 or 12 pixels. Is this valid for
+ other models, too? */
+ if (mp->generation >= 2)
+ {
+ raw_width = ALIGN_SUP (param->w + param->xs, 32);
+ /* PDBG (pixma_dbg (4, "*calc_raw_width***** width %u extended by %u and rounded to %u *****\n", param->w, param->xs, raw_width)); */
+ }
+ else if (param->channels == 1)
+ {
+ raw_width = ALIGN_SUP (param->w + param->xs, 12);
+ }
+ else
+ {
+ raw_width = ALIGN_SUP (param->w + param->xs, 4);
+ }
+ return raw_width;
+}
+
+static int has_ccd_sensor (pixma_t * s)
+{
+ return ((s->cfg->cap & PIXMA_CAP_CCD) != 0);
+}
+
+#if 0
+static int is_color (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_COLOR);
+}
+
+static int is_color_48 (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_COLOR_48);
+}
+
+static int is_color_negative (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_COLOR);
+}
+
+static int is_color_all (pixma_t * s)
+{
+ return (is_color (s) || is_color_48 (s) || is_color_negative (s));
+}
+#endif
+
+static int is_gray (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_GRAY);
+}
+
+static int is_gray_16 (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_GRAY_16);
+}
+
+static int is_gray_negative (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_GRAY);
+}
+
+static int is_gray_all (pixma_t * s)
+{
+ return (is_gray (s) || is_gray_16 (s) || is_gray_negative (s));
+}
+
+static int is_lineart (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_LINEART);
+}
+
+static int is_tpuir (pixma_t * s)
+{
+ return (s->param->mode == PIXMA_SCAN_MODE_TPUIR);
+}
+
+/* CCD sensors don't have neither a Grayscale mode nor a Lineart mode,
+ * but use color mode instead */
+static unsigned get_cis_ccd_line_size (pixma_t * s)
+{
+ return ((
+ s->param->wx ? s->param->line_size / s->param->w * s->param->wx
+ : s->param->line_size)
+ * ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : 1));
+}
+
+static unsigned calc_shifting (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ /* If stripes shift needed (CCD devices), how many pixels shift */
+ mp->stripe_shift = 0;
+ mp->stripe_shift2 = 0;
+ mp->jumplines = 0;
+
+ switch (s->cfg->pid)
+ {
+ case MP800_PID:
+ case MP800R_PID:
+ case MP830_PID:
+ if (s->param->xdpi == 2400)
+ {
+ if (is_scanning_from_tpu(s))
+ mp->stripe_shift = 6;
+ else
+ mp->stripe_shift = 3;
+ }
+ if (s->param->ydpi > 75)
+ {
+ mp->color_shift = s->param->ydpi / ((s->param->ydpi < 1200) ? 150 : 75);
+
+ if (is_scanning_from_tpu (s))
+ mp->color_shift = s->param->ydpi / 75;
+
+ /* If you're trying to decipher this color-shifting code,
+ the following line is where the magic is revealed. */
+ mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s);
+ if (is_scanning_from_adf (s))
+ { /* ADF */
+ mp->shift[0] = 0;
+ mp->shift[2] = 2 * mp->shift[1];
+ }
+ else
+ { /* Flatbed or TPU */
+ mp->shift[0] = 2 * mp->shift[1];
+ mp->shift[2] = 0;
+ }
+ }
+ break;
+
+ case MP970_PID: /* MP970 at 4800 dpi */
+ case CS8800F_PID: /* CanoScan 8800F at 4800 dpi */
+ if (s->param->xdpi == 4800)
+ {
+ if (is_scanning_from_tpu (s))
+ mp->stripe_shift = 6;
+ else
+ mp->stripe_shift = 3;
+ }
+ break;
+
+ case CS9000F_PID: /* CanoScan 9000F at 4800 dpi */
+ case CS9000F_MII_PID:
+ if (s->param->xdpi == 4800)
+ {
+ if (is_scanning_from_tpu (s))
+ mp->stripe_shift = 6; /* should work for CS9000F same as manual */
+ else
+ mp->stripe_shift = 3;
+ }
+ if (s->param->xdpi == 9600)
+ {
+ if (is_scanning_from_tpu (s))
+ {
+ /* need to double up for TPU */
+ mp->stripe_shift = 6; /* for 1st set of 4 images */
+ /* unfortunately there are 2 stripe shifts */
+ mp->stripe_shift2 = 6; /* for 2nd set of 4 images */
+ mp->jumplines = 32; /* try 33 or 34 */
+ }
+ /* there is no 9600dpi support in flatbed mode */
+ }
+ break;
+
+ case MP960_PID:
+ if (s->param->xdpi == 2400)
+ {
+ if (is_scanning_from_tpu (s))
+ mp->stripe_shift = 6;
+ else
+ mp->stripe_shift = 3;
+ }
+ if (s->param->xdpi == 4800)
+ {
+ if (is_scanning_from_tpu (s))
+ {
+ mp->stripe_shift = 6;
+ mp->stripe_shift2 = 6;
+ }
+ else
+ {
+ mp->stripe_shift = 3;
+ mp->stripe_shift2 = 3;
+ }
+ mp->jumplines = 33; /* better than 32 or 34 : applies to flatbed & TPU */
+ }
+ break;
+
+ case MP810_PID:
+ if (s->param->xdpi == 2400)
+ {
+ if (is_scanning_from_tpu (s))
+ mp->stripe_shift = 6;
+ else
+ mp->stripe_shift = 3;
+ }
+ if (s->param->xdpi == 4800)
+ {
+ if (is_scanning_from_tpu (s))
+ {
+ mp->stripe_shift = 6;
+ mp->stripe_shift2 = 6;
+ }
+ else
+ {
+ mp->stripe_shift = 3;
+ mp->stripe_shift2 = 3;
+ }
+ mp->jumplines = 33; /* better than 32 or 34 : applies to flatbed & TPU */
+ }
+ break;
+
+ case MP990_PID:
+ if (s->param->xdpi == 4800)
+ {
+ if (is_scanning_from_tpu (s))
+ {
+ mp->stripe_shift = 6;
+ mp->stripe_shift2 = 6;
+ }
+ else
+ {
+ mp->stripe_shift = 3;
+ mp->stripe_shift2 = 3;
+ }
+ mp->jumplines = 34; /* better than 32 or 34 : applies to flatbed & TPU */
+ }
+ break;
+
+ default: /* Default, and all CIS devices */
+ break;
+ }
+ /* If color plane shift (CCD devices), how many pixels shift */
+ mp->color_shift = mp->shift[0] = mp->shift[1] = mp->shift[2] = 0;
+ if (s->param->ydpi > 75)
+ {
+ switch (s->cfg->pid)
+ {
+ case MP970_PID:
+ case CS8800F_PID: /* CanoScan 8800F */
+ mp->color_shift = s->param->ydpi / 50;
+ mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s);
+ mp->shift[0] = 0;
+ mp->shift[2] = 2 * mp->shift[1];
+ break;
+
+ case CS9000F_PID: /* CanoScan 9000F */
+ case CS9000F_MII_PID:
+ mp->color_shift = s->param->ydpi / 30;
+ mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s);
+ mp->shift[0] = 0;
+ mp->shift[2] = 2 * mp->shift[1];
+ break;
+
+ case MP980_PID:
+ case MP990_PID:
+ case MG8200_PID:
+ if (s->param->ydpi > 150)
+ {
+ mp->color_shift = s->param->ydpi / 75;
+ mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s);
+ mp->shift[0] = 0;
+ mp->shift[2] = 2 * mp->shift[1];
+ }
+ break;
+
+ case MP810_PID:
+ case MP960_PID:
+ mp->color_shift = s->param->ydpi / 50;
+ if (is_scanning_from_tpu (s))
+ mp->color_shift = s->param->ydpi / 50;
+ mp->shift[1] = mp->color_shift * get_cis_ccd_line_size (s);
+ mp->shift[0] = 2 * mp->shift[1];
+ mp->shift[2] = 0;
+ break;
+
+ default:
+ break;
+ }
+ }
+ /* special settings for 16 bit flatbed mode @ 75 dpi
+ * minimum of 150 dpi is used yet */
+ /* else if (!is_scanning_from_tpu (s))
+ {
+ switch (s->cfg->pid)
+ {
+ case CS9000F_PID:
+ case CS9000F_MII_PID:
+ if (is_color_48 (s) || is_gray_16 (s))
+ {
+ mp->color_shift = 5;
+ mp->shift[1] = 0;
+ mp->shift[0] = 0;
+ mp->shift[2] = 0;
+ }
+ break;
+ }
+ } */
+ /* PDBG (pixma_dbg (4, "*calc_shifing***** color_shift = %u, stripe_shift = %u, jumplines = %u *****\n",
+ mp->color_shift, mp->stripe_shift, mp->jumplines)); */
+ return (2 * mp->color_shift + mp->stripe_shift + mp->jumplines); /* note impact of stripe shift2 later if needed! */
+}
+
+static int send_scan_param (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ unsigned raw_width = calc_raw_width (mp, s->param);
+ unsigned h, h1, h2, shifting;
+
+ /* TPU scan does not support lineart */
+ if (is_scanning_from_tpu (s) && is_lineart (s))
+ {
+ return PIXMA_ENOTSUP;
+ }
+
+ shifting = calc_shifting (s);
+ h1 = s->param->h + shifting; /* add lines for color shifting */
+ /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 1 %u \n", h1 )); */
+ if (mp->generation >= 4) /* use most global condition */
+ {
+ /* tested for MP960, 9000F */
+ /* add lines for color shifting */
+ /* otherwise you cannot scan all lines defined for flatbed mode */
+ /* this shouldn't affect TPU mode */
+ h2 = s->cfg->height * s->param->ydpi / 75 + shifting;
+ /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 2 %u = %u max. lines for scanner + %u lines for color shifting \n", h2, s->cfg->height * s->param->ydpi / 75, shifting )); */
+ }
+ else
+ {
+ /* TODO: Check for other scanners. */
+ h2 = s->cfg->height * s->param->ydpi / 75; /* this might be causing problems for generation 1 devices */
+ /* PDBG (pixma_dbg (4, "* send_scan_param: height calc (choose lesser) 2 %u \n", h2 )); */
+ }
+ h = MIN (h1, h2);
+
+ if (mp->generation <= 2)
+ {
+ data = pixma_newcmd (&mp->cb, cmd_scan_param, 0x30, 0);
+ pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x04);
+ pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x06);
+ pixma_set_be32 (s->param->x, data + 0x08);
+ if (mp->generation == 2)
+ pixma_set_be32 (s->param->x - s->param->xs, data + 0x08);
+ pixma_set_be32 (s->param->y, data + 0x0c);
+ pixma_set_be32 (raw_width, data + 0x10);
+ pixma_set_be32 (h, data + 0x14);
+ data[0x18] =
+ ((s->param->channels != 1) || is_gray_all (s) || is_lineart (s)) ?
+ 0x08 : 0x04;
+ data[0x19] = ((s->param->software_lineart) ? 8 : s->param->depth)
+ * ((is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels); /* bits per pixel */
+ data[0x1a] = (is_scanning_from_tpu (s) ? 1 : 0);
+ data[0x20] = 0xff;
+ data[0x23] = 0x81;
+ data[0x26] = 0x02;
+ data[0x27] = 0x01;
+ }
+ else
+ {
+ /* scan parameters:
+ * ================
+ *
+ * byte | # of | mode | value / description
+ * | bytes | |
+ * -----+-------+---------+---------------------------
+ * 0x00 | 1 | default | 0x01
+ * | | adf | 0x02
+ * | | tpu | 0x04
+ * | | tpuir | cs9000f: 0x03
+ * -----+-------+---------+---------------------------
+ * 0x01 | 1 | default | 0x00
+ * | | tpu | 0x02
+ * -----+-------+---------+---------------------------
+ * 0x02 | 1 | default | 0x01
+ * | | adfdup | 0x03
+ * -----+-------+---------+---------------------------
+ * 0x03 | 1 | default | 0x00
+ * | | adfdup | 0x03
+ * -----+-------+---------+---------------------------
+ * 0x04 | 1 | all | 0x00
+ * -----+-------+---------+---------------------------
+ * 0x05 | 1 | all | 0x01: This one also seen at 0. Don't know yet what's used for.
+ * -----+-------+---------+---------------------------
+ * ... | 1 | all | 0x00
+ * -----+-------+---------+---------------------------
+ * 0x08 | 2 | all | xdpi | 0x8000
+ * -----+-------+---------+---------------------------
+ * 0x0a | 2 | all | ydpi | 0x8000: Must be the same as xdpi.
+ * -----+-------+---------+---------------------------
+ * 0x0c | 4 | all | x position of start pixel
+ * -----+-------+---------+---------------------------
+ * 0x10 | 4 | all | y position of start pixel
+ * -----+-------+---------+---------------------------
+ * 0x14 | 4 | all | # of pixels in 1 line
+ * -----+-------+---------+---------------------------
+ * 0x18 | 4 | all | # of scan lines
+ * -----+-------+---------+---------------------------
+ * 0x1c | 1 | all | 0x08
+ * | | | 0x04 = relict from cis scanners?
+ * -----+-------+---------+---------------------------
+ * 0x1d | 1 | all | # of bits per pixel
+ * -----+-------+---------+---------------------------
+ * 0x1e | 1 | default | 0x00: paper
+ * | | tpu | 0x01: positives
+ * | | tpu | 0x02: negatives
+ * | | tpuir | 0x01: positives
+ * -----+-------+---------+---------------------------
+ * 0x1f | 1 | all | 0x01
+ * | | | cs9000f: 0x00: Not sure if that is because of positives.
+ * -----+-------+---------+---------------------------
+ * 0x20 | 1 | all | 0xff
+ * -----+-------+---------+---------------------------
+ * 0x21 | 1 | all | 0x81
+ * -----+-------+---------+---------------------------
+ * 0x22 | 1 | all | 0x00
+ * -----+-------+---------+---------------------------
+ * 0x23 | 1 | all | 0x02
+ * -----+-------+---------+---------------------------
+ * 0x24 | 1 | all | 0x01
+ * -----+-------+---------+---------------------------
+ * 0x25 | 1 | default | 0x00; cs8800f: 0x01
+ * | | tpu | 0x00; cs9000f, mg8200, mp990: 0x01
+ * | | tpuir | cs9000f: 0x00
+ * -----+-------+---------+---------------------------
+ * ... | 1 | all | 0x00
+ * -----+-------+---------+---------------------------
+ * 0x30 | 1 | all | 0x01
+ *
+ */
+
+ data = pixma_newcmd (&mp->cb, cmd_scan_param_3, 0x38, 0);
+ data[0x00] = is_scanning_from_adf (s) ? 0x02 : 0x01;
+ data[0x01] = 0x01;
+ if (is_scanning_from_tpu (s))
+ {
+ data[0x00] = is_tpuir (s) ? 0x03 : 0x04;
+ data[0x01] = 0x02;
+ data[0x1e] = 0x02; /* NB: CanoScan 8800F: 0x02->negatives, 0x01->positives, paper->0x00 */
+ }
+ data[0x02] = 0x01;
+ if (is_scanning_from_adfdup (s))
+ {
+ data[0x02] = 0x03;
+ data[0x03] = 0x03;
+ }
+ if (s->cfg->pid != MG8200_PID)
+ data[0x05] = 0x01; /* This one also seen at 0. Don't know yet what's used for */
+ /* the scanner controls the scan */
+ /* no software control needed */
+ pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x08);
+ pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x0a);
+ /*PDBG (pixma_dbg (4, "*send_scan_param***** Setting: xdpi=%hi ydpi=%hi x=%u y=%u w=%u h=%u ***** \n",
+ s->param->xdpi,s->param->ydpi,(s->param->x)-(s->param->xs),s->param->y,raw_width,h));*/
+ pixma_set_be32 (s->param->x - s->param->xs, data + 0x0c);
+ pixma_set_be32 (s->param->y, data + 0x10);
+ pixma_set_be32 (raw_width, data + 0x14);
+ pixma_set_be32 (h, data + 0x18);
+ data[0x1c] = ((s->param->channels != 1) || is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 0x08 : 0x04;
+
+#ifdef DEBUG_TPU_48
+ data[0x1d] = 24;
+#else
+ data[0x1d] = (is_scanning_from_tpu (s)) ? 48
+ : (((s->param->software_lineart) ? 8 : s->param->depth)
+ * ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels)); /* bits per pixel */
+#endif
+
+ data[0x1f] = 0x01; /* for 9000F this appears to be 0x00, not sure if that is because of positives */
+
+ if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID || s->cfg->pid == MG8200_PID)
+ {
+ data[0x1f] = 0x00;
+ }
+
+ data[0x20] = 0xff;
+ data[0x21] = 0x81;
+ data[0x23] = 0x02;
+ data[0x24] = 0x01;
+
+ /* MG8200 & MP990 addition */
+ if (s->cfg->pid == MG8200_PID || s->cfg->pid == MP990_PID)
+ {
+ if (is_scanning_from_tpu (s))
+ {
+ data[0x25] = 0x01;
+ }
+ }
+
+ /* CS8800F & CS9000F addition */
+ if (s->cfg->pid == CS8800F_PID || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)
+ {
+ if (is_scanning_from_tpu (s))
+ { /* TPU */
+ /* 0x02->negatives, 0x01->positives, paper->0x00
+ * no paper in TPU mode */
+ if (s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_COLOR
+ || s->param->mode == PIXMA_SCAN_MODE_NEGATIVE_GRAY)
+ {
+ PDBG(
+ pixma_dbg (4, "*send_scan_param***** TPU scan negatives *****\n"));
+ data[0x1e] = 0x02;
+ }
+ else
+ {
+ PDBG(
+ pixma_dbg (4, "*send_scan_param***** TPU scan positives *****\n"));
+ data[0x1e] = 0x01;
+ }
+ /* CS8800F: 0x00 for TPU color management */
+ if (s->cfg->pid == CS8800F_PID)
+ data[0x25] = 0x00;
+ /* CS9000F: 0x01 for TPU */
+ if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)
+ data[0x25] = 0x01;
+ if (s->param->mode == PIXMA_SCAN_MODE_TPUIR)
+ data[0x25] = 0x00;
+ }
+ else
+ { /* flatbed and ADF */
+ /* paper->0x00 */
+ data[0x1e] = 0x00;
+ /* CS8800F: 0x01 normally */
+ if (s->cfg->pid == CS8800F_PID)
+ data[0x25] = 0x01;
+ /* CS9000F: 0x00 normally */
+ if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)
+ data[0x25] = 0x00;
+ }
+ }
+
+ data[0x30] = 0x01;
+ }
+ return pixma_exec (s, &mp->cb);
+}
+
+static int query_status_3 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ int error, status_len;
+
+ status_len = 8;
+ data = pixma_newcmd (&mp->cb, cmd_status_3, 0, status_len);
+ RET_IF_ERR(pixma_exec (s, &mp->cb));
+ memcpy (mp->current_status, data, status_len);
+ return error;
+}
+
+static int query_status (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ int error, status_len;
+
+ status_len = (mp->generation == 1) ? 12 : 16;
+ data = pixma_newcmd (&mp->cb, cmd_status, 0, status_len);
+ RET_IF_ERR(pixma_exec (s, &mp->cb));
+ memcpy (mp->current_status, data, status_len);
+ PDBG(
+ pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u busy=%u\n", data[1], data[8], data[7], data[9]));
+ return error;
+}
+
+#if 0
+static int send_time (pixma_t * s)
+{
+ /* Why does a scanner need a time? */
+ time_t now;
+ struct tm *t;
+ uint8_t *data;
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ data = pixma_newcmd (&mp->cb, cmd_time, 20, 0);
+ pixma_get_time (&now, NULL);
+ t = localtime (&now);
+ strftime ((char *) data, 16, "%y/%m/%d %H:%M", t);
+ PDBG(pixma_dbg (3, "Sending time: '%s'\n", (char *) data));
+ return pixma_exec (s, &mp->cb);
+}
+#endif
+
+/* TODO: Simplify this function. Read the whole data packet in one shot. */
+static int read_image_block (pixma_t * s, uint8_t * header, uint8_t * data)
+{
+ uint8_t cmd[16];
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ const int hlen = 8 + 8;
+ int error, datalen;
+
+ memset (cmd, 0, sizeof(cmd));
+ /* PDBG (pixma_dbg (4, "* read_image_block: last_block\n", mp->last_block)); */
+ pixma_set_be16 (cmd_read_image, cmd);
+ if ((mp->last_block & 0x20) == 0)
+ pixma_set_be32 ((IMAGE_BLOCK_SIZE / 65536) * 65536 + 8, cmd + 0xc);
+ else
+ pixma_set_be32 (32 + 8, cmd + 0xc);
+
+ mp->state = state_transfering;
+ mp->cb.reslen = pixma_cmd_transaction (s, cmd, sizeof(cmd), mp->cb.buf, 512); /* read 1st 512 bytes of image block */
+ datalen = mp->cb.reslen;
+ if (datalen < 0)
+ return datalen;
+
+ memcpy (header, mp->cb.buf, hlen);
+ /* PDBG (pixma_dbg (4, "* read_image_block: datalen %i\n", datalen)); */
+ /* PDBG (pixma_dbg (4, "* read_image_block: hlen %i\n", hlen)); */
+ if (datalen >= hlen)
+ {
+ datalen -= hlen;
+ memcpy (data, mp->cb.buf + hlen, datalen);
+ data += datalen;
+ if (mp->cb.reslen == 512)
+ { /* read the rest of the image block */
+ error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen);
+ RET_IF_ERR(error);
+ datalen += error;
+ }
+ }
+
+ mp->state = state_scanning;
+ mp->cb.expected_reslen = 0;
+ RET_IF_ERR(pixma_check_result (&mp->cb));
+ if (mp->cb.reslen < hlen)
+ return PIXMA_EPROTO;
+ return datalen;
+}
+
+static int read_error_info (pixma_t * s, void *buf, unsigned size)
+{
+ unsigned len = 16;
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ int error;
+
+ data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len);
+ RET_IF_ERR(pixma_exec (s, &mp->cb));
+ if (buf && len < size)
+ {
+ size = len;
+ /* NOTE: I've absolutely no idea what the returned data mean. */
+ memcpy (buf, data, size);
+ error = len;
+ }
+ return error;
+}
+
+/*
+ handle_interrupt() waits until it receives an interrupt packet or times out.
+ It calls send_time() and query_status() if necessary. Therefore, make sure
+ that handle_interrupt() is only called from a safe context for send_time()
+ and query_status().
+
+ Returns:
+ 0 timed out
+ 1 an interrupt packet received
+ PIXMA_ECANCELED interrupted by signal
+ <0 error
+ */
+static int handle_interrupt (pixma_t * s, int timeout)
+{
+ uint8_t buf[64]; /* check max. packet size with 'lsusb -v' for "EP 9 IN" */
+ int len;
+
+ len = pixma_wait_interrupt (s->io, buf, sizeof(buf), timeout);
+ if (len == PIXMA_ETIMEDOUT)
+ return 0;
+ if (len < 0)
+ return len;
+ if (len%16) /* len must be a multiple of 16 bytes */
+ {
+ PDBG(pixma_dbg (1, "WARNING:unexpected interrupt packet length %d\n", len));
+ return PIXMA_EPROTO;
+ }
+
+ /* s->event = 0x0brroott
+ * b: button
+ * oo: original
+ * tt: target
+ * rr: scan resolution
+ * poll event with 'scanimage -A' */
+ if (s->cfg->pid == MG8200_PID)
+ /* button no. in buf[7]
+ * size in buf[10] 01=A4; 02=Letter; 08=10x15; 09=13x18; 0b=auto
+ * format in buf[11] 01=JPEG; 02=TIFF; 03=PDF; 04=Kompakt-PDF
+ * dpi in buf[12] 01=75; 02=150; 03=300; 04=600
+ * target = format; original = size; scan-resolution = dpi */
+ {
+ if (buf[7] & 1)
+ s->events = PIXMA_EV_BUTTON1 | buf[11] | buf[10]<<8 | buf[12]<<16; /* color scan */
+ if (buf[7] & 2)
+ s->events = PIXMA_EV_BUTTON2 | buf[11] | buf[10]<<8 | buf[12]<<16; /* b/w scan */
+ }
+ else if (s->cfg->pid == CS8800F_PID
+ || s->cfg->pid == CS9000F_PID
+ || s->cfg->pid == CS9000F_MII_PID)
+ /* button no. in buf[1]
+ * target = button no.
+ * "Finish PDF" is Button-2, all others are Button-1 */
+ {
+ if ((s->cfg->pid == CS8800F_PID && buf[1] == 0x70)
+ || (s->cfg->pid != CS8800F_PID && buf[1] == 0x50))
+ s->events = PIXMA_EV_BUTTON2 | buf[1] >> 4; /* button 2 = cancel / end scan */
+ else
+ s->events = PIXMA_EV_BUTTON1 | buf[1] >> 4; /* button 1 = start scan */
+ }
+ else
+ /* button no. in buf[0]
+ * original in buf[0]
+ * target in buf[1] */
+ {
+ /* More than one event can be reported at the same time. */
+ if (buf[3] & 1)
+ /* FIXME: This function makes trouble with a lot of scanners
+ send_time (s);
+ */
+ PDBG (pixma_dbg (1, "WARNING:send_time() disabled!\n"));
+ if (buf[9] & 2)
+ query_status (s);
+
+ if (buf[0] & 2)
+ s->events = PIXMA_EV_BUTTON2 | buf[1] | ((buf[0] & 0xf0) << 4); /* b/w scan */
+ if (buf[0] & 1)
+ s->events = PIXMA_EV_BUTTON1 | buf[1] | ((buf[0] & 0xf0) << 4); /* color scan */
+ }
+ return 1;
+}
+
+static int init_ccd_lamp_3 (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ uint8_t *data;
+ int error, status_len, tmo;
+
+ status_len = 8;
+ RET_IF_ERR(query_status (s));
+ RET_IF_ERR(query_status (s));
+ RET_IF_ERR(send_cmd_start_calibrate_ccd_3 (s));
+ RET_IF_ERR(query_status (s));
+ tmo = 20; /* like Windows driver, CCD lamp adjustment */
+ while (--tmo >= 0)
+ {
+ data = pixma_newcmd (&mp->cb, cmd_end_calibrate_ccd_3, 0, status_len);
+ RET_IF_ERR(pixma_exec (s, &mp->cb));
+ memcpy (mp->current_status, data, status_len);
+ PDBG(pixma_dbg (3, "Lamp status: %u , timeout in: %u\n", data[0], tmo));
+ if (mp->current_status[0] == 3 || !is_scanning_from_tpu (s))
+ break;
+ WAIT_INTERRUPT(1000);
+ }
+ return error;
+}
+
+static int wait_until_ready (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ int error, tmo = 60;
+
+ RET_IF_ERR((mp->generation >= 3) ? query_status_3 (s) : query_status (s));
+ while (!is_calibrated (s))
+ {
+ WAIT_INTERRUPT(1000);
+ if (mp->generation >= 3)
+ RET_IF_ERR(query_status_3 (s));
+ else if (s->cfg->pid == MP800R_PID)
+ RET_IF_ERR (query_status (s));
+ if (--tmo == 0)
+ {
+ PDBG(pixma_dbg (1, "WARNING: Timed out in wait_until_ready()\n"));
+ PDBG(query_status (s));
+ return PIXMA_ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+/* the RGB images are shifted by # of lines */
+/* the R image is shifted by colshift[0] */
+/* the G image is shifted by colshift[1] */
+/* the B image is shifted by colshift[2] */
+/* usually one of the RGB images must not be shifted */
+/* which one depends on the scanner */
+/* some scanners have an additional stripe shift */
+/* e.g. colshift[0]=0, colshift[1]=1, colshift[2]=2 */
+/* R image is OK: RGBRGBRGB... */
+/* ^^ ^^ ^^ */
+/* || || || */
+/* shift G image: RG|RG|RG|... */
+/* | | | */
+/* shift B image: RGBRGBRGB... */
+/* this doesn't affect the G and B images */
+/* G image will become the R image in the next run */
+/* B image will become the G image in the next run */
+/* the next line will become the B image in the next run */
+static uint8_t *
+shift_colors (uint8_t * dptr, uint8_t * sptr, unsigned w, unsigned dpi,
+ unsigned pid, unsigned c, int * colshft, unsigned strshft)
+{
+ unsigned i, sr, sg, sb, st;
+ UNUSED(dpi);
+ UNUSED(pid);
+ sr = colshft[0];
+ sg = colshft[1];
+ sb = colshft[2];
+
+ /* PDBG (pixma_dbg (4, "*shift_colors***** c=%u, w=%i, sr=%u, sg=%u, sb=%u, strshft=%u ***** \n",
+ c, w, sr, sg, sb, strshft)); */
+
+ for (i = 0; i < w; i++)
+ {
+ /* stripes shift for MP970 at 4800 dpi, MP810 at 2400 dpi */
+ st = (i % 2 == 0) ? strshft : 0;
+
+ *sptr++ = *(dptr++ + sr + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sr + st);
+ *sptr++ = *(dptr++ + sg + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sg + st);
+ *sptr++ = *(dptr++ + sb + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sb + st);
+ }
+
+ return dptr;
+}
+
+static uint8_t *
+shift_colorsCS9000 (uint8_t * dptr, uint8_t * sptr, unsigned w, unsigned dpi,
+ unsigned pid, unsigned c, int * colshft, unsigned strshft,
+ unsigned strshft2, unsigned jump)
+
+{
+ unsigned i, sr, sg, sb, st, st2;
+ UNUSED(dpi);
+ UNUSED(pid);
+ sr = colshft[0];
+ sg = colshft[1];
+ sb = colshft[2];
+
+ for (i = 0; i < w; i++)
+ {
+ if (i < (w / 2))
+ {
+ /* stripes shift for 1st 4 images for Canoscan 9000F at 9600dpi */
+ st = (i % 2 == 0) ? strshft : 0;
+ *sptr++ = *(dptr++ + sr + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sr + st);
+ *sptr++ = *(dptr++ + sg + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sg + st);
+ *sptr++ = *(dptr++ + sb + st);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sb + st);
+ }
+ if (i >= (w / 2))
+ {
+ /* stripes shift for 2nd 4 images for Canoscan 9000F at 9600dpi */
+ st2 = (i % 2 == 0) ? strshft2 : 0;
+ *sptr++ = *(dptr++ + sr + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sr + jump + st2);
+ *sptr++ = *(dptr++ + sg + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sg + jump + st2);
+ *sptr++ = *(dptr++ + sb + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sb + jump + st2);
+ }
+ }
+ return dptr;
+}
+
+static uint8_t *
+shift_colorsCS9000_4800 (uint8_t * dptr, uint8_t * sptr, unsigned w,
+ unsigned dpi, unsigned pid, unsigned c, int * colshft,
+ unsigned strshft, unsigned strshft2, unsigned jump)
+
+{
+ unsigned i, sr, sg, sb, st2;
+ UNUSED(dpi);
+ UNUSED(pid);
+ UNUSED(strshft);
+ sr = colshft[0];
+ sg = colshft[1];
+ sb = colshft[2];
+
+ for (i = 0; i < w; i++)
+ {
+ /* stripes shift for 2nd 4 images
+ * for Canoscan 9000F with 16 bit flatbed scans at 4800dpi */
+ st2 = (i % 2 == 0) ? strshft2 : 0;
+ *sptr++ = *(dptr++ + sr + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sr + jump + st2);
+ *sptr++ = *(dptr++ + sg + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sg + jump + st2);
+ *sptr++ = *(dptr++ + sb + jump + st2);
+ if (c == 6)
+ *sptr++ = *(dptr++ + sb + jump + st2);
+ }
+ return dptr;
+}
+
+/* under some conditions some scanners have sub images in one line */
+/* e.g. doubled image, line size = 8 */
+/* line before reordering: px1 px3 px5 px7 px2 px4 px6 px8 */
+/* line after reordering: px1 px2 px3 px4 px5 px6 px7 px8 */
+static void reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c,
+ unsigned n, unsigned m, unsigned w,
+ unsigned line_size)
+{
+ unsigned i;
+
+ for (i = 0; i < w; i++)
+ { /* process complete line */
+ memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c);
+ }
+ memcpy (sptr, linebuf, line_size);
+}
+
+/* special reorder matrix for mp960 */
+static void mp960_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c,
+ unsigned n, unsigned m, unsigned w,
+ unsigned line_size)
+{
+ unsigned i, i2;
+
+ /* try and copy 2 px at once */
+ for (i = 0; i < w; i++)
+ { /* process complete line */
+ i2 = i % 2;
+ if (i < w / 2)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m)), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m)), sptr + c * i, c);
+ }
+ else
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 1), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 1), sptr + c * i, c);
+ }
+ }
+
+ memcpy (sptr, linebuf, line_size);
+}
+
+/* special reorder matrix for mp970 */
+static void mp970_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c,
+ unsigned w, unsigned line_size)
+{
+ unsigned i, i8;
+
+ for (i = 0; i < w; i++)
+ { /* process complete line */
+ i8 = i % 8;
+ memcpy (linebuf + c * (i + i8 - ((i8 > 3) ? 7 : 0)), sptr + c * i, c);
+ }
+ memcpy (sptr, linebuf, line_size);
+}
+
+/* special reorder matrix for CS9000F */
+static void cs9000f_initial_reorder_pixels (uint8_t * linebuf, uint8_t * sptr,
+ unsigned c, unsigned n, unsigned m,
+ unsigned w, unsigned line_size)
+{
+ unsigned i, i2;
+
+ /* try and copy 2 px at once */
+ for (i = 0; i < w; i++)
+ { /* process complete line */
+ i2 = i % 2;
+ if (i < w / 8)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m)), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m)), sptr + c * i, c);
+ }
+ else if (i >= w / 8 && i < w / 4)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 1), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 1), sptr + c * i, c);
+ }
+ else if (i >= w / 4 && i < 3 * w / 8)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 2), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 2), sptr + c * i, c);
+ }
+ else if (i >= 3 * w / 8 && i < w / 2)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 3), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 3), sptr + c * i, c);
+ }
+ else if (i >= w / 2 && i < 5 * w / 8)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 4), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 4), sptr + c * i, c);
+ }
+ else if (i >= 5 * w / 8 && i < 3 * w / 4)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 5), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 5), sptr + c * i, c);
+ }
+ else if (i >= 3 * w / 4 && i < 7 * w / 8)
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 6), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 6), sptr + c * i, c);
+ }
+ else
+ {
+ if (i2 == 0)
+ memcpy (linebuf + c * (n * ((i) % m) + ((i) / m) + 7), sptr + c * i, c);
+ else
+ memcpy (linebuf + c * (n * ((i - 1) % m) + 1 + ((i) / m) + 7), sptr + c * i, c);
+ }
+ }
+
+ memcpy (sptr, linebuf, line_size);
+}
+
+/* CS9000F 9600dpi reorder: actually 4800dpi since each pixel is doubled */
+/* need to rearrange each sequence of 16 pairs of pixels as follows: */
+/* start px : 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 */
+/* before : p1a p1b p1c p1d p2a p2b p2c p2d p3a p3b p3c p3d p4a p4b p4c p4d */
+/* after : p1a p3a p1b p3b p1c p3c p1d p3d p2a p4a p2b p4b p2c p4c p2d p4d */
+/* start px : 0 16 2 18 4 20 6 22 8 24 10 26 12 28 14 30 */
+/* change : * * * * * * * * * * * * * * */
+/* no change: * * */
+/* so the 1st and the 3rd set are interleaved, followed by the 2nd and 4th sets interleaved */
+static void cs9000f_second_reorder_pixels (uint8_t * linebuf, uint8_t * sptr,
+ unsigned c, unsigned w,
+ unsigned line_size)
+{
+ unsigned i, i8;
+ static const int shifts[8] =
+ { 2, 4, 6, 8, -8, -6, -4, -2 };
+
+ for (i = 0; i < w; i += 2)
+ { /* process complete line */
+ i8 = (i >> 1) & 0x7;
+ /* Copy 2 pixels at once */
+ memcpy (linebuf + c * (i + shifts[i8]), sptr + c * i, c * 2);
+ }
+
+ memcpy (sptr, linebuf, line_size);
+}
+
+#ifndef TPU_48
+static unsigned
+pack_48_24_bpc (uint8_t * sptr, unsigned n)
+{
+ unsigned i;
+ uint8_t *cptr, lsb;
+ static uint8_t offset = 0;
+
+ cptr = sptr;
+ if (n % 2 != 0)
+ PDBG (pixma_dbg (3, "WARNING: misaligned image.\n"));
+ for (i = 0; i < n; i += 2)
+ {
+ /* offset = 1 + (offset % 3); */
+ lsb = *sptr++;
+ *cptr++ = ((*sptr++) << offset) | lsb >> (8 - offset);
+ }
+ return (n / 2);
+}
+#endif
+
+/* This function deals both with PIXMA CCD sensors producing shifted color
+ * planes images, Grayscale CCD scan and Generation >= 3 high dpi images.
+ * Each complete line in mp->imgbuf is processed for shifting CCD sensor
+ * color planes, reordering pixels above 600 dpi for Generation >= 3, and
+ * converting to Grayscale for CCD sensors. */
+static unsigned post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ unsigned c, lines, line_size, n, m, cw, cx, reducelines;
+ uint8_t *sptr, *dptr, *gptr, *cptr;
+ unsigned /*color_shift, stripe_shift, stripe_shift2,*/ jumplines /*, height*/;
+ int test;
+
+ /* For testers: */
+ /* set this to 1 in order to get unprocessed images next to one another at 9600dpi */
+ /* other resolutions should not be affected */
+ /* set this to 2 if you want to see the same with jumplines=0 */
+ test = 0;
+ jumplines = 0;
+
+ c = ((is_tpuir (s) || is_gray_all (s) || is_lineart (s)) ? 3 : s->param->channels)
+ * ((s->param->software_lineart) ? 8 : s->param->depth) / 8;
+ cw = c * s->param->w;
+ cx = c * s->param->xs;
+
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** c = %u, cw = %u, cx = %u *****\n", c, cw, cx)); */
+
+ if (mp->generation >= 3)
+ n = s->param->xdpi / 600;
+ else
+ /* FIXME: maybe need different values for CIS and CCD sensors */
+ n = s->param->xdpi / 2400;
+
+ /* Some exceptions to global rules here */
+ if (s->cfg->pid == MP970_PID || s->cfg->pid == MP990_PID || s->cfg->pid == MG8200_PID
+ || s->cfg->pid == CS8800F_PID || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)
+ n = MIN (n, 4);
+
+ /* exception for 9600dpi on Canoscan 9000F */
+ if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600))
+ {
+ n = 8;
+ if (test > 0)
+ n = 1; /* test if 8 images are next to one another */
+ }
+
+ /* test if 2 images are next to one another */
+ if ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800) && (test > 0))
+ {
+ n = 1;
+ }
+
+ m = (n > 0) ? s->param->wx / n : 1;
+
+ sptr = dptr = gptr = cptr = mp->imgbuf;
+ line_size = get_cis_ccd_line_size (s);
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** ----- Set n=%u, m=%u, line_size=%u ----- ***** \n", n, m, line_size)); */
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** ----- spr=dpr=%u, linebuf=%u ----- ***** \n", sptr, mp->linebuf)); */
+
+ lines = (mp->data_left_ofs - mp->imgbuf) / line_size;
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** lines = %i > 2 * mp->color_shift + mp->stripe_shift = %i ***** \n",
+ lines, 2 * mp->color_shift + mp->stripe_shift)); */
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** mp->color_shift = %u, mp->stripe_shift = %u, , mp->stripe_shift2 = %u ***** \n",
+ mp->color_shift, mp->stripe_shift, mp->stripe_shift2)); */
+
+ /*color_shift = mp->color_shift;*/
+ /*stripe_shift = mp->stripe_shift;*/
+ /*stripe_shift2 = mp->stripe_shift2;*/
+ jumplines = mp->jumplines;
+
+ /* height not needed here! */
+ /* removed to avoid confusion */
+ /* height = MIN (s->param->h + calc_shifting (s),
+ s->cfg->height * s->param->ydpi / 75); */
+
+ /* have to test if rounding down is OK or not -- currently 0.5 lines is rounded down */
+ /* note stripe shifts doubled already in definitions */
+ if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600) && (test > 0))
+ {
+ /* using test==2 you can check in GIMP the required offset, and
+ use the below line (uncommented) and replace XXX with that
+ number, and then compile again with test set to 1. */
+
+ jumplines = 32;
+ if (test == 2)
+ jumplines = 0;
+ }
+
+ /* mp960 test */
+ if ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800) && (test > 0))
+ {
+ jumplines = 32;
+ if (test == 2)
+ jumplines = 0;
+ }
+
+ reducelines = ((2 * mp->color_shift + mp->stripe_shift) + jumplines);
+ /* PDBG (pixma_dbg (4, "*post_process_image_data: lines %u, reducelines %u \n", lines, reducelines)); */
+ if (lines > reducelines)
+ { /* (line - reducelines) of image lines can be converted */
+ unsigned i;
+
+ lines -= reducelines;
+
+ for (i = 0; i < lines; i++, sptr += line_size)
+ { /* convert only full image lines */
+ /* Color plane and stripes shift needed by e.g. CCD */
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** Processing with c=%u, n=%u, m=%u, w=%i, line_size=%u ***** \n",
+ c, n, m, s->param->wx, line_size)); */
+ if (c >= 3)
+ {
+ if (((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600))
+ || ((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800))
+ || ((s->cfg->pid == MP810_PID) && (s->param->xdpi == 4800)))
+ {
+ dptr = shift_colorsCS9000 (dptr, sptr, s->param->wx, s->param->xdpi,
+ s->cfg->pid, c, mp->shift,
+ mp->stripe_shift, mp->stripe_shift2,
+ jumplines * line_size);
+ }
+
+ else if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) /* 9000F: 16 bit flatbed scan at 4800dpi */
+ && ((s->param->mode == PIXMA_SCAN_MODE_COLOR_48)
+ || (s->param->mode == PIXMA_SCAN_MODE_GRAY_16))
+ && (s->param->xdpi == 4800)
+ && (s->param->source == PIXMA_SOURCE_FLATBED))
+ dptr = shift_colorsCS9000_4800 (dptr, sptr, s->param->wx,
+ s->param->xdpi, s->cfg->pid, c,
+ mp->shift, mp->stripe_shift,
+ mp->stripe_shift2,
+ jumplines * line_size);
+
+ else
+ /* all except 9000F at 9600dpi */
+ dptr = shift_colors (dptr, sptr, s->param->wx, s->param->xdpi,
+ s->cfg->pid, c, mp->shift, mp->stripe_shift);
+ }
+
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** test = %i *****\n", test)); */
+
+ /*--comment out all between this line and the one below for 9000F tests at 9600dpi or MP960 at 4800dpi ------*/
+ /* if ( 0 ) */
+ if ((((s->cfg->pid != CS9000F_PID && s->cfg->pid != CS9000F_MII_PID) || (s->param->xdpi < 9600))
+ && ((s->cfg->pid != MP960_PID) || (s->param->xdpi < 4800))
+ && ((s->cfg->pid != MP810_PID) || (s->param->xdpi < 4800)))
+ || (test == 0))
+ {
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** MUST GET HERE WHEN TEST == 0 *****\n")); */
+
+ if (!((s->cfg->pid == MP810_PID) && (s->param->xdpi == 4800))
+ && !((s->cfg->pid == MP960_PID) && (s->param->xdpi == 4800))
+ && !((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600)))
+ { /* for both flatbed & TPU */
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** reordering pixels normal n = %i *****\n", n)); */
+ reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx, line_size);
+ }
+
+ if ((s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID) && (s->param->xdpi == 9600))
+ {
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** cs900f_initial_reorder_pixels n = %i *****\n", n)); */
+ /* this combines pixels from 8 images 2px at a time from left to right: 1122334455667788... */
+ cs9000f_initial_reorder_pixels (mp->linebuf, sptr, c, n, m,
+ s->param->wx, line_size);
+ /* final interleaving */
+ cs9000f_second_reorder_pixels (mp->linebuf, sptr, c, s->param->wx,
+ line_size);
+ }
+
+ /* comment: special image format for MP960 in flatbed mode
+ at 4800dpi. It is actually 2400dpi, with each pixel
+ doubled. The TPU mode has proper pixel ordering */
+ if ((((s->cfg->pid == MP960_PID) || (s->cfg->pid == MP810_PID)) && (s->param->xdpi == 4800))
+ && (n > 0))
+ {
+ /* for both flatbed & TPU */
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** flatbed mp960_reordering pixels n = %i *****\n", n)); */
+ mp960_reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->wx,
+ line_size);
+ }
+
+ /* comment: MP970, CS8800F, CS9000F specific reordering for 4800 dpi */
+ if ((s->cfg->pid == MP970_PID || s->cfg->pid == CS8800F_PID
+ || s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID
+ || s->cfg->pid == MP990_PID) && (s->param->xdpi == 4800))
+ {
+ /*PDBG (pixma_dbg (4, "*post_process_image_data***** mp970_reordering pixels n = %i *****\n", n)); */
+ mp970_reorder_pixels (mp->linebuf, sptr, c, s->param->wx, line_size);
+ }
+
+ }
+ /*-------------------------------------------------------*/
+
+ /* PDBG (pixma_dbg (4, "*post_process_image_data: sptr=%u, dptr=%u \n", sptr, dptr)); */
+
+ /* Crop line to selected borders */
+ memmove (cptr, sptr + cx, cw);
+ /* PDBG (pixma_dbg (4, "*post_process_image_data***** crop line: cx=%u, cw=%u ***** \n", cx, cw)); */
+
+ /* Color to Lineart convert for CCD sensor */
+ if (is_lineart (s))
+ cptr = gptr = pixma_binarize_line (s->param, gptr, cptr, s->param->w, c);
+#ifndef TPUIR_USE_RGB
+ /* save IR only for CCD sensor */
+ else if (is_tpuir (s))
+ cptr = gptr = pixma_r_to_ir (gptr, cptr, s->param->w, c);
+ /* Color to Grayscale convert for CCD sensor */
+ else if (is_gray_all (s))
+#else
+ /* IR *and* Color to Grayscale convert for CCD sensor */
+ else if (is_tpuir (s) || is_gray_all (s))
+#endif
+ cptr = gptr = pixma_rgb_to_gray (gptr, cptr, s->param->w, c);
+ else
+ cptr += cw;
+ }
+ /* PDBG (pixma_dbg (4, "*post_process_image_data: sptr=%u, dptr=%u \n", sptr, dptr)); */
+ }
+ ib->rptr = mp->imgbuf;
+ ib->rend = cptr;
+ return mp->data_left_ofs - sptr; /* # of non processed bytes */
+ /* contains shift color data for new lines */
+ /* and already received data for the next line */
+}
+
+static int mp810_open (pixma_t * s)
+{
+ mp810_t *mp;
+ uint8_t *buf;
+
+ mp = (mp810_t *) calloc (1, sizeof(*mp));
+ if (!mp)
+ return PIXMA_ENOMEM;
+
+ buf = (uint8_t *) malloc (CMDBUF_SIZE + IMAGE_BLOCK_SIZE);
+ if (!buf)
+ {
+ free (mp);
+ return PIXMA_ENOMEM;
+ }
+
+ s->subdriver = mp;
+ mp->state = state_idle;
+
+ mp->cb.buf = buf;
+ mp->cb.size = CMDBUF_SIZE;
+ mp->cb.res_header_len = 8;
+ mp->cb.cmd_header_len = 16;
+ mp->cb.cmd_len_field_ofs = 14;
+
+ mp->imgbuf = buf + CMDBUF_SIZE;
+
+ /* General rules for setting Pixma protocol generation # */
+ mp->generation = (s->cfg->pid >= MP810_PID) ? 2 : 1;
+
+ if (s->cfg->pid >= MP970_PID)
+ mp->generation = 3;
+
+ if (s->cfg->pid >= MP990_PID)
+ mp->generation = 4;
+
+ /* And exceptions to be added here */
+ if (s->cfg->pid == CS8800F_PID)
+ mp->generation = 3;
+
+ if (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID)
+ mp->generation = 4;
+
+ /* TPU info data setup */
+ mp->tpu_datalen = 0;
+
+ if (mp->generation < 4)
+ {
+ /* Canoscan 8800F ignores commands if not initialized */
+ if (s->cfg->pid == CS8800F_PID)
+ abort_session (s);
+ else
+ {
+ query_status (s);
+ handle_interrupt (s, 200);
+ if (mp->generation == 3 && has_ccd_sensor (s))
+ send_cmd_start_calibrate_ccd_3 (s);
+ }
+ }
+ return 0;
+}
+
+static void mp810_close (pixma_t * s)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ mp810_finish_scan (s);
+ free (mp->cb.buf);
+ free (mp);
+ s->subdriver = NULL;
+}
+
+static int mp810_check_param (pixma_t * s, pixma_scan_param_t * sp)
+{
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ unsigned w_max;
+
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** Initially: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */
+
+ sp->channels = 3;
+ sp->software_lineart = 0;
+ switch (sp->mode)
+ {
+ /* standard scan modes
+ * 8 bit per channel in color and grayscale mode
+ * 16 bit per channel with TPU */
+ case PIXMA_SCAN_MODE_GRAY:
+ case PIXMA_SCAN_MODE_NEGATIVE_GRAY:
+ case PIXMA_SCAN_MODE_TPUIR:
+ sp->channels = 1;
+ /* fall through */
+ case PIXMA_SCAN_MODE_COLOR:
+ case PIXMA_SCAN_MODE_NEGATIVE_COLOR:
+ sp->depth = 8;
+#ifdef TPU_48
+#ifndef DEBUG_TPU_48
+ if (sp->source == PIXMA_SOURCE_TPU)
+#endif
+ sp->depth = 16; /* TPU in 16 bits mode */
+#endif
+ break;
+ /* extended scan modes for 48 bit flatbed scanners
+ * 16 bit per channel in color and grayscale mode */
+ case PIXMA_SCAN_MODE_GRAY_16:
+ sp->channels = 1;
+ sp->depth = 16;
+ break;
+ case PIXMA_SCAN_MODE_COLOR_48:
+ sp->channels = 3;
+ sp->depth = 16;
+ break;
+ /* software lineart
+ * 1 bit per channel */
+ case PIXMA_SCAN_MODE_LINEART:
+ sp->software_lineart = 1;
+ sp->channels = 1;
+ sp->depth = 1;
+ break;
+ }
+
+ /* for software lineart w must be a multiple of 8
+ * I don't know why is_lineart(s) doesn't work here */
+ if (sp->software_lineart == 1 && sp->w % 8)
+ {
+ sp->w += 8 - (sp->w % 8);
+
+ /* do not exceed the scanner capability */
+ w_max = s->cfg->width * s->cfg->xdpi / 75;
+ w_max -= w_max % 8;
+ if (sp->w > w_max)
+ sp->w = w_max;
+ }
+
+ if (sp->source == PIXMA_SOURCE_TPU && !sp->tpu_offset_added)
+ {
+ unsigned fixed_offset_y; /* TPU offsets for CanoScan 8800F, or other CCD at 300dpi. */
+ unsigned max_y; /* max TPU height for CS9000F at 75 dpi */
+
+ /* CanoScan 8800F and others adding an offset depending on resolution */
+ /* CS9000F and others maximum TPU height */
+ switch (s->cfg->pid)
+ {
+ case CS8800F_PID:
+ fixed_offset_y = 140;
+ max_y = MIN (740, s->cfg->height);
+ break;
+ case CS9000F_PID:
+ case CS9000F_MII_PID:
+ fixed_offset_y = 146;
+ max_y = MIN (740, s->cfg->height);
+ break;
+ default:
+ fixed_offset_y = 0;
+ max_y = s->cfg->height;
+ break;
+ }
+
+ /* cropping y and h to scanable area */
+ max_y *= (sp->ydpi) / 75;
+ sp->y = MIN(sp->y, max_y);
+ sp->h = MIN(sp->h, max_y - sp->y);
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** Cropping: y=%u, h=%u *****\n",
+ sp->y, sp->h)); */
+ if (!sp->h)
+ return SANE_STATUS_INVAL; /* no lines */
+
+ /* Convert the offsets from 300dpi to actual resolution */
+ fixed_offset_y = fixed_offset_y * (sp->xdpi) / 300;
+
+ /* In TPU mode, the CS9000F appears to always subtract 146 from the
+ vertical starting position, but clamps its at 0. Therefore vertical
+ offsets 0 through 146 (@300 dpi) get all mapped onto the same
+ physical starting position: line 0. Then, starting from 147, the
+ offsets get mapped onto successive physical lines:
+ y line
+ 0 -> 0
+ 1 -> 0
+ 2 -> 0
+ ...
+ 146 -> 0
+ 147 -> 1
+ 148 -> 2
+ ...
+ Since a preview scan is typically made starting from y = 0, but
+ partial image scans usually start at y >> 147, this results in a
+ discontinuity in the y to line mapping, resulting in wrong offsets.
+ To prevent this, we must always add (at least) 146 to the y
+ offset before it is sent to the scanner. The scanner will then
+ map y = 0 (146) to the first line, y = 1 (147) to the second line,
+ and so on. Any distance that is then measured on the preview scan,
+ can be translated without any discontinuity.
+
+ However, there is one complication: during a preview scan, which
+ normally covers the whole scan area of the scanner, we should _not_
+ add the offset because it will result in a reduced number of lines
+ being returned (the scan height is clamped in
+ pixma_check_scan_param()). Since the frontend has no way of telling
+ that the scan area has been reduced, it would derive an incorrect
+ effective scan resolution, and any position calculations based on
+ this would therefore be inaccurate.
+
+ To prevent this, we don't add the offset in case y = 0, which is
+ typically the case during a preview scan (the scanner effectively
+ adds the offset for us, see above). In that way we keep the
+ linearity and we don't affect the scan area during previews.
+ */
+
+ if (sp->y > 0)
+ sp->y += fixed_offset_y;
+
+ /* Prevent repeated corrections as check_param may be called multiple times */
+ sp->tpu_offset_added = 1;
+ }
+
+ if (mp->generation >= 2)
+ {
+ /* mod 32 and expansion of the X scan limits */
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** (gen>=2) xs=mod32 ----- Initially: x=%u, y=%u, w=%u, h=%u *****\n", sp->x, sp->y, sp->w, sp->h)); */
+ sp->xs = (sp->x) % 32;
+ }
+ else
+ {
+ sp->xs = 0;
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** (else) xs=0 Selected origin, origin shift: %u, %u *****\n", sp->x, sp->xs)); */
+ }
+ sp->wx = calc_raw_width (mp, sp);
+ sp->line_size = sp->w * sp->channels * (((sp->software_lineart) ? 8 : sp->depth) / 8); /* bytes per line per color after cropping */
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** (else) Final scan width and line-size: %u, %"PRIu64" *****\n", sp->wx, sp->line_size)); */
+
+ /* highest res is 600, 2400, 4800 or 9600 dpi */
+ {
+ uint8_t k;
+
+ if ((sp->source == PIXMA_SOURCE_ADF || sp->source == PIXMA_SOURCE_ADFDUP)
+ && mp->generation >= 4)
+ /* ADF/ADF duplex mode: max scan res is 600 dpi, at least for generation 4 */
+ k = sp->xdpi / MIN (sp->xdpi, 600);
+ else if (sp->source == PIXMA_SOURCE_TPU && sp->mode == PIXMA_SCAN_MODE_TPUIR)
+ /* TPUIR mode: max scan res is 2400 dpi */
+ k = sp->xdpi / MIN (sp->xdpi, 2400);
+ else if (sp->source == PIXMA_SOURCE_TPU && (s->cfg->pid == CS9000F_PID || s->cfg->pid == CS9000F_MII_PID))
+ /* CS9000F in TPU mode */
+ k = sp->xdpi / MIN (sp->xdpi, 9600);
+ else
+ /* default */
+ k = sp->xdpi / MIN (sp->xdpi, 4800);
+
+ sp->x /= k;
+ sp->xs /= k;
+ sp->y /= k;
+ sp->w /= k;
+ sp->wx /= k;
+ sp->h /= k;
+ sp->xdpi /= k;
+ sp->ydpi = sp->xdpi;
+ }
+
+ /* lowest res is 75, 150, 300 or 600 dpi */
+ {
+ uint8_t k;
+
+ if (sp->source == PIXMA_SOURCE_TPU && sp->mode == PIXMA_SCAN_MODE_TPUIR)
+ /* TPUIR mode */
+ k = MAX (sp->xdpi, 600) / sp->xdpi;
+ else if (sp->source == PIXMA_SOURCE_TPU
+ && ((mp->generation >= 3) || (s->cfg->pid == MP810_PID) || (s->cfg->pid == MP960_PID)))
+ /* TPU mode for generation 3+ scanners
+ * MP810, MP960 appear to have a 200dpi mode for low-res scans, not 150 dpi */
+ k = MAX (sp->xdpi, 300) / sp->xdpi;
+ else if (sp->source == PIXMA_SOURCE_TPU
+ || sp->mode == PIXMA_SCAN_MODE_COLOR_48 || sp->mode == PIXMA_SCAN_MODE_GRAY_16)
+ /* TPU mode and 16 bit flatbed scans
+ * TODO: either the frontend (xsane) cannot handle 48 bit flatbed scans @ 75 dpi (prescan)
+ * or there is a bug in this subdriver */
+ k = MAX (sp->xdpi, 150) / sp->xdpi;
+ else
+ /* default */
+ k = MAX (sp->xdpi, 75) / sp->xdpi;
+
+ sp->x *= k;
+ sp->xs *= k;
+ sp->y *= k;
+ sp->w *= k;
+ sp->wx *= k;
+ sp->h *= k;
+ sp->xdpi *= k;
+ sp->ydpi = sp->xdpi;
+ }
+
+ /* PDBG (pixma_dbg (4, "*mp810_check_param***** Finally: channels=%u, depth=%u, x=%u, y=%u, w=%u, h=%u, xs=%u, wx=%u *****\n",
+ sp->channels, sp->depth, sp->x, sp->y, sp->w, sp->h, sp->xs, sp->wx)); */
+
+ return 0;
+}
+
+static int mp810_scan (pixma_t * s)
+{
+ int error = 0, tmo;
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ if (mp->state != state_idle)
+ return PIXMA_EBUSY;
+
+ /* Generation 4: send XML dialog */
+ if (mp->generation == 4 && s->param->adf_pageid == 0)
+ {
+ if (!send_xml_dialog (s, XML_START_1))
+ return PIXMA_EPROTO;
+ if (!send_xml_dialog (s, XML_START_2))
+ return PIXMA_EPROTO;
+ }
+
+ /* clear interrupt packets buffer */
+ while (handle_interrupt (s, 0) > 0)
+ {
+ }
+
+ /* FIXME: Duplex ADF: check paper status only before odd pages (1,3,5,...). */
+ if (is_scanning_from_adf (s))
+ {
+ if ((error = query_status (s)) < 0)
+ return error;
+ tmo = 10;
+ while (!has_paper (s) && --tmo >= 0)
+ {
+ WAIT_INTERRUPT(1000);
+ PDBG(pixma_dbg (2, "No paper in ADF. Timed out in %d sec.\n", tmo));
+ }
+ if (!has_paper (s))
+ return PIXMA_ENO_PAPER;
+ }
+
+ if (has_ccd_sensor (s) && (mp->generation <= 2))
+ {
+ error = send_cmd_e920 (s);
+ switch (error)
+ {
+ case PIXMA_ECANCELED:
+ case PIXMA_EBUSY:
+ PDBG(pixma_dbg (2, "cmd e920 or d520 returned %s\n", pixma_strerror (error)));
+ /* fall through */
+ case 0:
+ query_status (s);
+ break;
+ default:
+ PDBG(pixma_dbg (1, "WARNING: cmd e920 or d520 failed %s\n", pixma_strerror (error)));
+ return error;
+ }
+ tmo = 3; /* like Windows driver, CCD calibration ? */
+ while (--tmo >= 0)
+ {
+ WAIT_INTERRUPT(1000);
+ PDBG(pixma_dbg (2, "CCD Calibration ends in %d sec.\n", tmo));
+ }
+ /* pixma_sleep(2000000); */
+ }
+
+ tmo = 10;
+ if (s->param->adf_pageid == 0 || mp->generation <= 2)
+ {
+ error = start_session (s);
+ while (error == PIXMA_EBUSY && --tmo >= 0)
+ {
+ if (s->cancel)
+ {
+ error = PIXMA_ECANCELED;
+ break;
+ }
+ PDBG(pixma_dbg (2, "Scanner is busy. Timed out in %d sec.\n", tmo + 1));
+ pixma_sleep (1000000);
+ error = start_session (s);
+ }
+ if (error == PIXMA_EBUSY || error == PIXMA_ETIMEDOUT)
+ {
+ /* The scanner maybe hangs. We try to empty output buffer of the
+ * scanner and issue the cancel command. */
+ PDBG(pixma_dbg (2, "Scanner hangs? Sending abort_session command.\n"));
+ drain_bulk_in (s);
+ abort_session (s);
+ pixma_sleep (500000);
+ error = start_session (s);
+ }
+ if ((error >= 0) || (mp->generation >= 3))
+ mp->state = state_warmup;
+ if ((error >= 0) && (mp->generation <= 2))
+ error = select_source (s);
+ if ((error >= 0) && (mp->generation >= 3) && has_ccd_sensor (s))
+ error = init_ccd_lamp_3 (s);
+ if ((error >= 0) && !is_scanning_from_tpu (s))
+ {
+ int i;
+ /* FIXME: 48 bit flatbed scans don't need gamma tables
+ * the code below doesn't run */
+ /*if (is_color_48 (s) || is_gray_16 (s))
+ error = 0;
+ else*/
+ for (i = (mp->generation >= 3) ? 3 : 1; i > 0 && error >= 0; i--)
+ error = send_gamma_table (s);
+ }
+ else if (error >= 0) /* in TPU mode, for gen 1, 2, and 3 */
+ error = send_set_tpu_info (s);
+ }
+ else
+ /* ADF pageid != 0 and gen3 or above */
+ pixma_sleep (1000000);
+
+ if ((error >= 0) || (mp->generation >= 3))
+ mp->state = state_warmup;
+ if (error >= 0)
+ error = send_scan_param (s);
+ if ((error >= 0) && (mp->generation >= 3))
+ error = start_scan_3 (s);
+ if (error < 0)
+ {
+ mp->last_block = 0x38; /* Force abort session if ADF scan */
+ mp810_finish_scan (s);
+ return error;
+ }
+ return 0;
+}
+
+static int mp810_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
+{
+ int error;
+ mp810_t *mp = (mp810_t *) s->subdriver;
+ unsigned block_size, bytes_received, proc_buf_size, line_size;
+ uint8_t header[16];
+
+ if (mp->state == state_warmup)
+ { /* prepare read image data */
+ /* PDBG (pixma_dbg (4, "**mp810_fill_buffer***** warmup *****\n")); */
+
+ RET_IF_ERR(wait_until_ready (s));
+ pixma_sleep (1000000); /* No need to sleep, actually, but Window's driver
+ * sleep 1.5 sec. */
+ mp->state = state_scanning;
+ mp->last_block = 0;
+
+ line_size = get_cis_ccd_line_size (s);
+ proc_buf_size = (2 * calc_shifting (s) + 2) * line_size;
+ mp->cb.buf = realloc (mp->cb.buf, CMDBUF_SIZE + IMAGE_BLOCK_SIZE + proc_buf_size);
+ if (!mp->cb.buf)
+ return PIXMA_ENOMEM;
+ mp->linebuf = mp->cb.buf + CMDBUF_SIZE;
+ mp->imgbuf = mp->data_left_ofs = mp->linebuf + line_size;
+ mp->data_left_len = 0;
+ }
+
+ do
+ { /* read complete image data from the scanner */
+ if (s->cancel)
+ return PIXMA_ECANCELED;
+ if ((mp->last_block & 0x28) == 0x28)
+ { /* end of image */
+ mp->state = state_finished;
+ /* PDBG (pixma_dbg (4, "**mp810_fill_buffer***** end of image *****\n")); */
+ return 0;
+ }
+ /* PDBG (pixma_dbg (4, "*mp810_fill_buffer***** moving %u bytes into buffer *****\n", mp->data_left_len)); */
+ memmove (mp->imgbuf, mp->data_left_ofs, mp->data_left_len);
+ error = read_image_block (s, header, mp->imgbuf + mp->data_left_len);
+ if (error < 0)
+ {
+ if (error == PIXMA_ECANCELED)
+ {
+ /* NOTE: I see this in traffic logs but I don't know its meaning. */
+ read_error_info (s, NULL, 0);
+ }
+ return error;
+ }
+
+ bytes_received = error;
+ /*PDBG (pixma_dbg (4, "*mp810_fill_buffer***** %u bytes received by read_image_block *****\n", bytes_received));*/
+ block_size = pixma_get_be32 (header + 12);
+ mp->last_block = header[8] & 0x38;
+ if ((header[8] & ~0x38) != 0)
+ {
+ PDBG(pixma_dbg (1, "WARNING: Unexpected result header\n"));
+ PDBG(pixma_hexdump (1, header, 16));
+ }
+ PASSERT(bytes_received == block_size);
+
+ if (block_size == 0)
+ { /* no image data at this moment. */
+ pixma_sleep (10000);
+ }
+ /* For TPU at 48 bits/pixel to output at 24 bits/pixel */
+#ifndef DEBUG_TPU_48
+#ifndef TPU_48
+ PDBG (pixma_dbg (1, "WARNING: 9000F using 24 instead of 48 bit processing \n"));
+#ifndef DEBUG_TPU_24
+ if (is_scanning_from_tpu (s))
+#endif
+ bytes_received = pack_48_24_bpc (mp->imgbuf + mp->data_left_len, bytes_received);
+#endif
+#endif
+ /* Post-process the image data */
+ mp->data_left_ofs = mp->imgbuf + mp->data_left_len + bytes_received;
+ mp->data_left_len = post_process_image_data (s, ib);
+ mp->data_left_ofs -= mp->data_left_len;
+ /* PDBG (pixma_dbg (4, "* mp810_fill_buffer: data_left_len %u \n", mp->data_left_len)); */
+ /* PDBG (pixma_dbg (4, "* mp810_fill_buffer: data_left_ofs %u \n", mp->data_left_ofs)); */
+ }
+ while (ib->rend == ib->rptr);
+
+ return ib->rend - ib->rptr;
+}
+
+static void mp810_finish_scan (pixma_t * s)
+{
+ int error;
+ mp810_t *mp = (mp810_t *) s->subdriver;
+
+ switch (mp->state)
+ {
+ case state_transfering:
+ drain_bulk_in (s);
+ /* fall through */
+ case state_scanning:
+ case state_warmup:
+ case state_finished:
+ /* Send the get TPU info message */
+ if (is_scanning_from_tpu (s) && mp->tpu_datalen == 0)
+ send_get_tpu_info_3 (s);
+ /* FIXME: to process several pages ADF scan, must not send
+ * abort_session and start_session between pages (last_block=0x28) */
+ if (mp->generation <= 2 || !is_scanning_from_adf (s)
+ || mp->last_block == 0x38)
+ {
+ error = abort_session (s); /* FIXME: it probably doesn't work in duplex mode! */
+ if (error < 0)
+ PDBG(pixma_dbg (1, "WARNING:abort_session() failed %d\n", error));
+
+ /* Generation 4: send XML end of scan dialog */
+ if (mp->generation == 4)
+ {
+ if (!send_xml_dialog (s, XML_END))
+ PDBG(pixma_dbg (1, "WARNING:XML_END dialog failed \n"));
+ }
+ }
+ mp->state = state_idle;
+ /* fall through */
+ case state_idle:
+ break;
+ }
+}
+
+static void mp810_wait_event (pixma_t * s, int timeout)
+{
+ /* FIXME: timeout is not correct. See usbGetCompleteUrbNoIntr() for
+ * instance. */
+ while (s->events == 0 && handle_interrupt (s, timeout) > 0)
+ {
+ }
+}
+
+static int mp810_get_status (pixma_t * s, pixma_device_status_t * status)
+{
+ int error;
+
+ RET_IF_ERR(query_status (s));
+ status->hardware = PIXMA_HARDWARE_OK;
+ status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
+ status->cal =
+ (is_calibrated (s)) ? PIXMA_CALIBRATION_OK : PIXMA_CALIBRATION_OFF;
+ return 0;
+}
+
+static const pixma_scan_ops_t pixma_mp800_ops =
+{
+ mp810_open,
+ mp810_close,
+ mp810_scan,
+ mp810_fill_buffer,
+ mp810_finish_scan,
+ mp810_wait_event,
+ mp810_check_param,
+ mp810_get_status
+};
+
+#define DEVICE(name, model, pid, dpi, adftpu_min_dpi, adftpu_max_dpi, tpuir_min_dpi, tpuir_max_dpi, w, h, cap) { \
+ name, /* name */ \
+ model, /* model */ \
+ CANON_VID, pid, /* vid pid */ \
+ 0, /* iface */ \
+ &pixma_mp800_ops, /* ops */ \
+ 0, /* min_xdpi not used in this subdriver */ \
+ dpi, 2*(dpi), /* xdpi, ydpi */ \
+ adftpu_min_dpi, adftpu_max_dpi, /* adftpu_min_dpi, adftpu_max_dpi */ \
+ tpuir_min_dpi, tpuir_max_dpi, /* tpuir_min_dpi, tpuir_max_dpi */ \
+ w, h, /* width, height */ \
+ PIXMA_CAP_CCD| /* all scanners with CCD*/ \
+ PIXMA_CAP_EASY_RGB| \
+ PIXMA_CAP_GRAY| /* all scanners with software grayscale */ \
+ PIXMA_CAP_LINEART| /* all scanners with software lineart */ \
+ PIXMA_CAP_GAMMA_TABLE|PIXMA_CAP_EVENTS|cap \
+}
+
+#define END_OF_DEVICE_LIST DEVICE(NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+
+const pixma_config_t pixma_mp800_devices[] =
+{
+ /* Generation 1: CCD */
+ DEVICE ("Canon PIXMA MP800", "MP800", MP800_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+ DEVICE ("Canon PIXMA MP800R", "MP800R", MP800R_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+ DEVICE ("Canon PIXMA MP830", "MP830", MP830_PID, 2400, 150, 0, 0, 0, 638, 877, PIXMA_CAP_ADFDUP),
+
+ /* Generation 2: CCD */
+ DEVICE ("Canon PIXMA MP810", "MP810", MP810_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+ DEVICE ("Canon PIXMA MP960", "MP960", MP960_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Generation 3 CCD not managed as Generation 2 */
+ DEVICE ("Canon Pixma MP970", "MP970", MP970_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Flatbed scanner CCD (2007) */
+ DEVICE ("Canoscan 8800F", "8800F", CS8800F_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU /*| PIXMA_CAP_NEGATIVE*/ | PIXMA_CAP_48BIT),
+
+ /* PIXMA 2008 vintage CCD */
+ DEVICE ("Canon MP980 series", "MP980", MP980_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Generation 4 CCD */
+ DEVICE ("Canon MP990 series", "MP990", MP990_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Flatbed scanner (2010) */
+ DEVICE ("Canoscan 9000F", "9000F", CS9000F_PID, 4800, 300, 9600, 600, 2400, 638, 877, PIXMA_CAP_TPUIR /*| PIXMA_CAP_NEGATIVE*/ | PIXMA_CAP_48BIT),
+
+ /* Latest devices (2010) Generation 4 CCD untested */
+ DEVICE ("Canon PIXMA MG8100", "MG8100", MG8100_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Latest devices (2011) Generation 4 CCD untested */
+ DEVICE ("Canon PIXMA MG8200", "MG8200", MG8200_PID, 4800, 300, 0, 0, 0, 638, 877, PIXMA_CAP_TPU),
+
+ /* Flatbed scanner (2013) */
+ DEVICE ("Canoscan 9000F Mark II", "9000FMarkII", CS9000F_MII_PID, 4800, 300, 9600, 600, 2400, 638, 877, PIXMA_CAP_TPUIR | PIXMA_CAP_48BIT),
+
+ END_OF_DEVICE_LIST
+};
diff --git a/backend/pixma/pixma_rename.h b/backend/pixma/pixma_rename.h
new file mode 100644
index 0000000..ad3d960
--- /dev/null
+++ b/backend/pixma/pixma_rename.h
@@ -0,0 +1,105 @@
+/* SANE - Scanner Access Now Easy.
+
+ Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+#ifndef PIXMA_RENAME_H
+#define PIXMA_RENAME_H
+
+
+#undef BACKEND_NAME
+#define BACKEND_NAME pixma
+
+#define pixma_cancel sanei_pixma_cancel
+#define pixma_check_dpi sanei_pixma_check_dpi
+#define pixma_check_result sanei_pixma_check_result
+#define pixma_check_scan_param sanei_pixma_check_scan_param
+#define pixma_cleanup sanei_pixma_cleanup
+#define pixma_close sanei_pixma_close
+#define pixma_cmd_transaction sanei_pixma_cmd_transaction
+#define pixma_collect_devices sanei_pixma_collect_devices
+#define pixma_connect sanei_pixma_connect
+#define pixma_dbg DBG
+#define pixma_disconnect sanei_pixma_disconnect
+#define pixma_dump sanei_pixma_dump
+#define pixma_enable_background sanei_pixma_enable_background
+#define pixma_exec sanei_pixma_exec
+#define pixma_exec_short_cmd sanei_pixma_exec_short_cmd
+#define pixma_fill_gamma_table sanei_pixma_fill_gamma_table
+#define pixma_find_scanners sanei_pixma_find_scanners
+#define pixma_get_be16 sanei_pixma_get_be16
+#define pixma_get_be32 sanei_pixma_get_be32
+#define pixma_get_config sanei_pixma_get_config
+#define pixma_get_device_config sanei_pixma_get_device_config
+#define pixma_get_device_id sanei_pixma_get_device_id
+#define pixma_get_device_model sanei_pixma_get_device_model
+#define pixma_get_device_status sanei_pixma_get_device_status
+#define pixma_get_string sanei_pixma_get_string
+#define pixma_get_time sanei_pixma_get_time
+#define pixma_hexdump sanei_pixma_hexdump
+#define pixma_init sanei_pixma_init
+#define pixma_io_cleanup sanei_pixma_io_cleanup
+#define pixma_io_init sanei_pixma_io_init
+#define pixma_map_status_errno sanei_pixma_map_status_errno
+#define pixma_mp150_devices sanei_pixma_mp150_devices
+#define pixma_mp730_devices sanei_pixma_mp730_devices
+#define pixma_mp750_devices sanei_pixma_mp750_devices
+#define pixma_mp800_devices sanei_pixma_mp800_devices
+#define pixma_iclass_devices sanei_pixma_iclass_devices
+#define pixma_newcmd sanei_pixma_newcmd
+#define pixma_open sanei_pixma_open
+#define pixma_print_supported_devices sanei_pixma_print_supported_devices
+#define pixma_read_image sanei_pixma_read_image
+#define pixma_read sanei_pixma_read
+#define pixma_reset_device sanei_pixma_reset_device
+#define pixma_scan sanei_pixma_scan
+#define pixma_set_be16 sanei_pixma_set_be16
+#define pixma_set_be32 sanei_pixma_set_be32
+#define pixma_set_debug_level sanei_pixma_set_debug_level
+#define pixma_set_interrupt_mode sanei_pixma_set_interrupt_mode
+#define pixma_sleep sanei_pixma_sleep
+#define pixma_strerror sanei_pixma_strerror
+#define pixma_sum_bytes sanei_pixma_sum_bytes
+#define pixma_wait_event sanei_pixma_wait_event
+#define pixma_wait_interrupt sanei_pixma_wait_interrupt
+#define pixma_write sanei_pixma_write
+
+
+#endif
diff --git a/backend/pixma/pixma_sane_options.c b/backend/pixma/pixma_sane_options.c
new file mode 100644
index 0000000..2b8f609
--- /dev/null
+++ b/backend/pixma/pixma_sane_options.c
@@ -0,0 +1,362 @@
+/* Automatically generated from pixma_sane.c */
+static const SANE_Range constraint_gamma_table =
+ { 0,255,0 };
+static const SANE_Range constraint_gamma =
+ { SANE_FIX(0.3),SANE_FIX(5),SANE_FIX(0) };
+static const SANE_Range constraint_threshold =
+ { 0,100,1 };
+static const SANE_Range constraint_threshold_curve =
+ { 0,127,1 };
+static const SANE_Range constraint_adf_wait =
+ { 0,3600,1 };
+
+
+static
+int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list)
+{
+ int i;
+ for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {}
+ return i;
+}
+
+static
+int build_option_descriptors(struct pixma_sane_t *ss)
+{
+ SANE_Option_Descriptor *sod;
+ option_descriptor_t *opt;
+
+ memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX));
+
+ opt = &(OPT_IN_CTX[opt_opt_num_opts]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_TITLE_NUM_OPTIONS;
+ sod->desc = SANE_DESC_NUM_OPTIONS;
+ sod->name = "";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_opt_num_opts].info = 0;
+ opt->def.w = opt_last;
+ opt->val.w = opt_last;
+
+ opt = &(OPT_IN_CTX[opt__group_1]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_GROUP;
+ sod->title = SANE_I18N("Scan mode");
+ sod->desc = sod->title;
+
+ opt = &(OPT_IN_CTX[opt_resolution]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_TITLE_SCAN_RESOLUTION;
+ sod->desc = SANE_DESC_SCAN_RESOLUTION;
+ sod->name = "resolution";
+ sod->unit = SANE_UNIT_DPI;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ sod->constraint.word_list = ss->dpi_list;
+ OPT_IN_CTX[opt_resolution].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.w = 75;
+ opt->val.w = 75;
+
+ opt = &(OPT_IN_CTX[opt_mode]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_STRING;
+ sod->title = SANE_TITLE_SCAN_MODE;
+ sod->desc = SANE_DESC_SCAN_MODE;
+ sod->name = "mode";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 31;
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ sod->constraint.string_list = ss->mode_list;
+ OPT_IN_CTX[opt_mode].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.s = SANE_VALUE_SCAN_MODE_COLOR;
+ opt->val.w = find_string_in_list(opt->def.s, sod->constraint.string_list);
+
+ opt = &(OPT_IN_CTX[opt_source]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_STRING;
+ sod->title = SANE_TITLE_SCAN_SOURCE;
+ sod->desc = SANE_I18N("Selects the scan source (such as a document-feeder). Set source before mode and resolution. Resets mode and resolution to auto values.");
+ sod->name = "source";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 31;
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
+ sod->constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ sod->constraint.string_list = ss->source_list;
+ OPT_IN_CTX[opt_source].info = 0;
+ opt->def.s = SANE_I18N("Flatbed");
+ opt->val.w = find_string_in_list(opt->def.s, sod->constraint.string_list);
+
+ opt = &(OPT_IN_CTX[opt_button_controlled]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_BOOL;
+ sod->title = SANE_I18N("Button-controlled scan");
+ sod->desc = SANE_I18N("When enabled, scan process will not start immediately. To proceed, press \"SCAN\" button (for MP150) or \"COLOR\" button (for other models). To cancel, press \"GRAY\" button.");
+ sod->name = "button-controlled";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_button_controlled].info = 0;
+ opt->def.w = SANE_FALSE;
+ opt->val.w = SANE_FALSE;
+
+ opt = &(OPT_IN_CTX[opt__group_2]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_GROUP;
+ sod->title = SANE_I18N("Gamma");
+ sod->desc = sod->title;
+
+ opt = &(OPT_IN_CTX[opt_custom_gamma]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_BOOL;
+ sod->title = SANE_TITLE_CUSTOM_GAMMA;
+ sod->desc = SANE_DESC_CUSTOM_GAMMA;
+ sod->name = "custom-gamma";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_custom_gamma].info = 0;
+ opt->def.w = SANE_TRUE;
+ opt->val.w = SANE_TRUE;
+
+ opt = &(OPT_IN_CTX[opt_gamma_table]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_TITLE_GAMMA_VECTOR;
+ sod->desc = SANE_DESC_GAMMA_VECTOR;
+ sod->name = "gamma-table";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 4096 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &constraint_gamma_table;
+ OPT_IN_CTX[opt_gamma_table].info = 0;
+
+ opt = &(OPT_IN_CTX[opt_gamma]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_FIXED;
+ sod->title = SANE_I18N("Gamma function exponent");
+ sod->desc = SANE_I18N("Changes intensity of midtones");
+ sod->name = "gamma";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &constraint_gamma;
+ OPT_IN_CTX[opt_gamma].info = 0;
+ opt->def.w = SANE_FIX(AUTO_GAMMA);
+ opt->val.w = SANE_FIX(AUTO_GAMMA);
+
+ opt = &(OPT_IN_CTX[opt__group_3]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_GROUP;
+ sod->title = SANE_I18N("Geometry");
+ sod->desc = sod->title;
+
+ opt = &(OPT_IN_CTX[opt_tl_x]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_FIXED;
+ sod->title = SANE_TITLE_SCAN_TL_X;
+ sod->desc = SANE_DESC_SCAN_TL_X;
+ sod->name = "tl-x";
+ sod->unit = SANE_UNIT_MM;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &ss->xrange;
+ OPT_IN_CTX[opt_tl_x].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.w = SANE_FIX(0);
+ opt->val.w = SANE_FIX(0);
+
+ opt = &(OPT_IN_CTX[opt_tl_y]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_FIXED;
+ sod->title = SANE_TITLE_SCAN_TL_Y;
+ sod->desc = SANE_DESC_SCAN_TL_Y;
+ sod->name = "tl-y";
+ sod->unit = SANE_UNIT_MM;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &ss->yrange;
+ OPT_IN_CTX[opt_tl_y].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.w = SANE_FIX(0);
+ opt->val.w = SANE_FIX(0);
+
+ opt = &(OPT_IN_CTX[opt_br_x]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_FIXED;
+ sod->title = SANE_TITLE_SCAN_BR_X;
+ sod->desc = SANE_DESC_SCAN_BR_X;
+ sod->name = "br-x";
+ sod->unit = SANE_UNIT_MM;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &ss->xrange;
+ OPT_IN_CTX[opt_br_x].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.w = sod->constraint.range->max;
+ opt->val.w = sod->constraint.range->max;
+
+ opt = &(OPT_IN_CTX[opt_br_y]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_FIXED;
+ sod->title = SANE_TITLE_SCAN_BR_Y;
+ sod->desc = SANE_DESC_SCAN_BR_Y;
+ sod->name = "br-y";
+ sod->unit = SANE_UNIT_MM;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &ss->yrange;
+ OPT_IN_CTX[opt_br_y].info = SANE_INFO_RELOAD_PARAMS;
+ opt->def.w = sod->constraint.range->max;
+ opt->val.w = sod->constraint.range->max;
+
+ opt = &(OPT_IN_CTX[opt__group_4]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_GROUP;
+ sod->title = SANE_I18N("Buttons");
+ sod->desc = sod->title;
+
+ opt = &(OPT_IN_CTX[opt_button_update]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_BUTTON;
+ sod->title = SANE_I18N("Update button state");
+ sod->desc = sod->title;
+ sod->name = "button-update";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 0;
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_button_update].info = 0;
+
+ opt = &(OPT_IN_CTX[opt_button_1]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Button 1");
+ sod->desc = sod->title;
+ sod->name = "button-1";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_button_1].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ opt = &(OPT_IN_CTX[opt_button_2]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Button 2");
+ sod->desc = sod->title;
+ sod->name = "button-2";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_button_2].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ opt = &(OPT_IN_CTX[opt_original]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Type of original to scan");
+ sod->desc = sod->title;
+ sod->name = "original";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_original].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ opt = &(OPT_IN_CTX[opt_target]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Target operation type");
+ sod->desc = sod->title;
+ sod->name = "target";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_target].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ opt = &(OPT_IN_CTX[opt_scan_resolution]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Scan resolution");
+ sod->desc = sod->title;
+ sod->name = "scan-resolution";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
+ sod->constraint_type = SANE_CONSTRAINT_NONE;
+ OPT_IN_CTX[opt_scan_resolution].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ opt = &(OPT_IN_CTX[opt__group_5]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_GROUP;
+ sod->title = SANE_I18N("Extras");
+ sod->desc = sod->title;
+
+ opt = &(OPT_IN_CTX[opt_threshold]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_TITLE_THRESHOLD;
+ sod->desc = SANE_DESC_THRESHOLD;
+ sod->name = "threshold";
+ sod->unit = SANE_UNIT_PERCENT;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &constraint_threshold;
+ OPT_IN_CTX[opt_threshold].info = 0;
+ opt->def.w = 50;
+ opt->val.w = 50;
+
+ opt = &(OPT_IN_CTX[opt_threshold_curve]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("Threshold curve");
+ sod->desc = SANE_I18N("Dynamic threshold curve, from light to dark, normally 50-65");
+ sod->name = "threshold-curve";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &constraint_threshold_curve;
+ OPT_IN_CTX[opt_threshold_curve].info = 0;
+
+ opt = &(OPT_IN_CTX[opt_adf_wait]);
+ sod = &opt->sod;
+ sod->type = SANE_TYPE_INT;
+ sod->title = SANE_I18N("ADF Waiting Time");
+ sod->desc = SANE_I18N("When set, the scanner waits upto the specified time in seconds for a new document inserted into the automatic document feeder.");
+ sod->name = "adf-wait";
+ sod->unit = SANE_UNIT_NONE;
+ sod->size = 1 * sizeof(SANE_Word);
+ sod->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_AUTOMATIC|SANE_CAP_INACTIVE;
+ sod->constraint_type = SANE_CONSTRAINT_RANGE;
+ sod->constraint.range = &constraint_adf_wait;
+ OPT_IN_CTX[opt_adf_wait].info = 0;
+ opt->def.w = 0;
+ opt->val.w = 0;
+
+ return 0;
+
+}
diff --git a/backend/pixma/pixma_sane_options.h b/backend/pixma/pixma_sane_options.h
new file mode 100644
index 0000000..1472f1f
--- /dev/null
+++ b/backend/pixma/pixma_sane_options.h
@@ -0,0 +1,51 @@
+/* Automatically generated from pixma_sane.c */
+
+typedef union {
+ SANE_Word w;
+ SANE_Int i;
+ SANE_Bool b;
+ SANE_Fixed f;
+ SANE_String s;
+ void *ptr;
+} option_value_t;
+
+typedef enum {
+ opt_opt_num_opts,
+ opt__group_1,
+ opt_resolution,
+ opt_mode,
+ opt_source,
+ opt_button_controlled,
+ opt__group_2,
+ opt_custom_gamma,
+ opt_gamma_table,
+ opt_gamma,
+ opt__group_3,
+ opt_tl_x,
+ opt_tl_y,
+ opt_br_x,
+ opt_br_y,
+ opt__group_4,
+ opt_button_update,
+ opt_button_1,
+ opt_button_2,
+ opt_original,
+ opt_target,
+ opt_scan_resolution,
+ opt__group_5,
+ opt_threshold,
+ opt_threshold_curve,
+ opt_adf_wait,
+ opt_last
+} option_t;
+
+
+typedef struct {
+ SANE_Option_Descriptor sod;
+ option_value_t val,def;
+ SANE_Word info;
+} option_descriptor_t;
+
+
+struct pixma_sane_t;
+static int build_option_descriptors(struct pixma_sane_t *ss);
diff --git a/backend/pixma/scripts/pixma_gen_options.py b/backend/pixma/scripts/pixma_gen_options.py
new file mode 100755
index 0000000..c4c75e0
--- /dev/null
+++ b/backend/pixma/scripts/pixma_gen_options.py
@@ -0,0 +1,389 @@
+#!/usr/bin/env python
+
+import sys,os,re
+
+class Error(Exception):
+ pass
+
+
+class ParseError(Error):
+ def __init__(self, errline):
+ Error.__init__(self, errline)
+
+
+class Struct:
+ pass
+
+
+def createCNameMap():
+ t = ''
+ for i in range(256):
+ if ((ord('A') <= i) and (i <= ord('Z'))) or \
+ ((ord('a') <= i) and (i <= ord('z'))) or \
+ ((ord('0') <= i) and (i <= ord('9'))):
+ t += chr(i)
+ else:
+ t += '_'
+ return t
+
+
+def seekBegin(f):
+ while True:
+ line = f.readline()
+ if not line:
+ return False
+ if line.startswith('BEGIN SANE_Option_Descriptor'):
+ return True
+
+
+def parseVerbatim(o, line):
+ words = line.split(None, 1)
+ if (len(words) < 2) or (words[1][0] != '@'):
+ return False
+ o[words[0]] = words[1]
+ return True
+
+
+def parseLine_type(o, line):
+ words = line.split(None, 2)
+ otype = words[1]
+ o['type'] = 'SANE_TYPE_' + otype.upper()
+ if otype == 'group':
+ g.ngroups += 1
+ oname = '_group_%d' % g.ngroups
+ o['size'] = 0
+ else:
+ temp = words[2]
+ idx = temp.find('[')
+ if idx == -1:
+ oname = temp
+ o['size'] = 1
+ else:
+ oname = temp[0:idx]
+ o['size'] = int(temp[idx+1:-1])
+ o['name'] = oname
+
+
+def parseLine_title(o, line):
+ o['title'] = line.split(None, 1)[1]
+
+
+def parseLine_desc(o, line):
+ o['desc'] = line.split(None, 1)[1]
+
+
+def parseLine_unit(o, line):
+ o['unit'] = 'SANE_UNIT_' + line.split(None, 1)[1].upper()
+
+
+def parseLine_default(o, line):
+ o['default'] = line.split(None, 1)[1]
+
+
+def parseLine_cap(o, line):
+ words = line.split()
+ o['cap'] = ['SANE_CAP_' + s.upper() for s in words[1:]]
+
+
+def parseLine_constraint(o, line):
+ c = line.split(None,1)[1]
+ if c[0] == '{':
+ o['constraint'] = c[1:-1].split('|')
+ elif c[0] == '(':
+ o['constraint'] = tuple(c[1:-1].split(','))
+ else:
+ sys.stderr.write('Ignored: %s\n' % line)
+
+
+def parseLine_info(o, line):
+ words = line.split()
+ o['info'] = ['SANE_INFO_' + s.upper() for s in words[1:]]
+
+def parseLine_rem(o, line):
+ pass
+
+def normalize(o):
+ if 'cname' not in o:
+ cname = o['name'].translate(cnameMap)
+ o['cname'] = cname
+ else:
+ cname = o['cname']
+ o['cname_opt'] = 'opt_' + cname
+ o['cname_con'] = 'constraint_' + cname
+ if 'title' not in o:
+ o['title'] = 'NO TITLE'
+ if 'desc' not in o:
+ o['desc'] = '@sod->title' % o
+ if 'unit' not in o:
+ o['unit'] = 'SANE_UNIT_NONE'
+ if 'constraint_type' not in o:
+ if 'constraint' not in o:
+ ct = 'SANE_CONSTRAINT_NONE'
+ elif isinstance(o['constraint'], list):
+ if o['type'] == 'SANE_TYPE_STRING':
+ ct = 'SANE_CONSTRAINT_STRING_LIST'
+ else:
+ ct = 'SANE_CONSTRAINT_WORD_LIST'
+ elif isinstance(o['constraint'], tuple):
+ ct = 'SANE_CONSTRAINT_RANGE'
+ elif isinstance(o['constraint'], str):
+ oc = o['constraint']
+ if oc.startswith('@range'):
+ ct = 'SANE_CONSTRAINT_RANGE'
+ elif oc.startswith('@word_list'):
+ ct = 'SANE_CONSTRAINT_WORD_LIST'
+ elif oc.startswith('@string_list'):
+ ct = 'SANE_CONSTRAINT_STRING_LIST'
+ o['constraint_type'] = ct
+ return o
+
+
+def parseFile(f):
+ if not seekBegin(f):
+ return None
+ options = [ {
+ 'name' : '',
+ 'cname' : 'opt_num_opts',
+ 'title' : '@SANE_TITLE_NUM_OPTIONS',
+ 'desc' : '@SANE_DESC_NUM_OPTIONS',
+ 'type' : 'SANE_TYPE_INT',
+ 'unit' : 'SANE_UNIT_NONE',
+ 'size' : 1,
+ 'cap' : ['SANE_CAP_SOFT_DETECT'],
+ 'constraint_type' : 'SANE_CONSTRAINT_NONE',
+ 'default' : '@w = ' + opt_prefix + 'last'
+ } ]
+ o = {}
+ while True:
+ line = f.readline()
+ if not line:
+ break
+ line = line.strip()
+ if not line:
+ continue
+ token = line.split(None, 1)[0].lower()
+ if token == 'end':
+ break
+ if token == 'type':
+ if 'name' in o:
+ options.append(o)
+ o = {}
+ funcName = 'parseLine_' + token
+ if funcName in globals():
+ if not parseVerbatim(o, line):
+ func = globals()[funcName]
+ func(o, line)
+ else:
+ sys.stderr.write('Skip: %s\n' % line)
+ if 'name' in o:
+ options.append(o)
+ return [normalize(o) for o in options]
+
+
+def genHeader(options):
+ print """
+typedef union {
+ SANE_Word w;
+ SANE_Int i;
+ SANE_Bool b;
+ SANE_Fixed f;
+ SANE_String s;
+ void *ptr;
+} option_value_t;
+"""
+ print 'typedef enum {'
+ for o in options:
+ print ' %(cname_opt)s,' % o
+ print ' ' + opt_prefix + 'last'
+ print '} option_t;'
+ print """
+
+typedef struct {
+ SANE_Option_Descriptor sod;
+ option_value_t val,def;
+ SANE_Word info;
+} option_descriptor_t;
+
+
+struct pixma_sane_t;
+static int build_option_descriptors(struct pixma_sane_t *ss);
+"""
+
+
+def genMinMaxRange(n, t, r):
+ if t == 'SANE_TYPE_FIXED':
+ r = ['SANE_FIX(%s)' % x for x in r]
+ print 'static const SANE_Range ' + n + ' = '
+ print ' { ' + r[0] + ',' + r[1] + ',' + r[2] + ' };'
+
+
+def genList(n, t, l):
+ if t == 'SANE_TYPE_INT':
+ etype = 'SANE_Word'
+ l = [str(len(l))] + l
+ elif t == 'SANE_TYPE_FIXED':
+ etype = 'SANE_Word'
+ l = [str(len(l))] + ['SANE_FIX(%s)' % x for x in l]
+ elif t == 'SANE_TYPE_STRING':
+ etype = 'SANE_String_Const'
+ l = ['SANE_I18N("%s")' % x for x in l] + ['NULL']
+ print 'static const %s %s[%d] = {' % (etype, n, len(l))
+ for x in l[0:-1]:
+ print '\t' + x + ','
+ print '\t' + l[-1] + ' };'
+
+
+def genConstraints(options):
+ for o in options:
+ if 'constraint' not in o: continue
+ c = o['constraint']
+ oname = o['cname_con']
+ otype = o['type']
+ if isinstance(c, tuple):
+ genMinMaxRange(oname, otype, c)
+ elif isinstance(c, list):
+ genList(oname, otype, c)
+ print
+
+def buildCodeVerbatim(o):
+ for f in ('name', 'title', 'desc', 'type', 'unit', 'size', 'cap',
+ 'constraint_type', 'constraint', 'default'):
+ if (f not in o): continue
+ temp = o[f]
+ if (not isinstance(temp,str)) or \
+ (len(temp) < 1) or (temp[0] != '@'):
+ continue
+ o['code_' + f] = temp[1:]
+
+def ccode(o):
+ buildCodeVerbatim(o)
+ if 'code_name' not in o:
+ o['code_name'] = '"' + o['name'] + '"'
+ for f in ('title', 'desc'):
+ cf = 'code_' + f
+ if cf in o: continue
+ o[cf] = 'SANE_I18N("' + o[f] + '")'
+
+ for f in ('type', 'unit', 'constraint_type'):
+ cf = 'code_' + f
+ if cf in o: continue
+ o[cf] = o[f]
+
+ if 'code_size' not in o:
+ otype = o['type']
+ osize = o['size']
+ if otype == 'SANE_TYPE_STRING':
+ code = str(osize + 1)
+ elif otype == 'SANE_TYPE_INT' or otype == 'SANE_TYPE_FIXED':
+ code = str(osize) + ' * sizeof(SANE_Word)'
+ elif otype == 'SANE_TYPE_BUTTON':
+ code = '0'
+ else:
+ code = 'sizeof(SANE_Word)'
+ o['code_size'] = code
+
+ if ('code_cap' not in o) and ('cap' in o):
+ o['code_cap'] = reduce(lambda a,b: a+'|'+b, o['cap'])
+ else:
+ o['code_cap'] = '0'
+
+ if ('code_info' not in o) and ('info' in o):
+ o['code_info'] = reduce(lambda a,b: a+'|'+b, o['info'])
+ else:
+ o['code_info'] = '0'
+
+ if ('code_default' not in o) and ('default' in o):
+ odefault = o['default']
+ otype = o['type']
+ if odefault == '_MIN':
+ rhs = 'w = sod->constraint.range->min'
+ elif odefault == '_MAX':
+ rhs = 'w = sod->constraint.range->max'
+ elif otype in ('SANE_TYPE_INT', 'SANE_TYPE_BOOL'):
+ rhs = 'w = %(default)s'
+ elif otype == 'SANE_TYPE_FIXED':
+ rhs = 'w = SANE_FIX(%(default)s)'
+ elif otype == 'SANE_TYPE_STRING':
+ rhs = 's = SANE_I18N("%(default)s")'
+ o['code_default'] = rhs % o
+ if 'code_default' in o:
+ code = ' opt->def.%(code_default)s;\n'
+ if o['constraint_type'] != 'SANE_CONSTRAINT_STRING_LIST':
+ code += ' opt->val.%(code_default)s;\n'
+ else:
+ code += ' opt->val.w = find_string_in_list' \
+ '(opt->def.s, sod->constraint.string_list);\n'
+ o['full_code_default'] = code % o
+ else:
+ o['full_code_default'] = ''
+
+ if ('code_constraint' not in o) and ('constraint' in o):
+ ct = o['constraint_type']
+ idx = len('SANE_CONSTRAINT_')
+ ctype = ct[idx:].lower()
+ if ctype == 'range':
+ rhs = '&%(cname_con)s' % o
+ else:
+ rhs = '%(cname_con)s' % o
+ o['code_constraint'] = ctype + ' = ' + rhs
+ if 'code_constraint' in o:
+ code = ' sod->constraint.%(code_constraint)s;\n'
+ o['full_code_constraint'] = code % o
+ else:
+ o['full_code_constraint'] = ''
+
+ return o
+
+def genBuildOptions(options):
+ print """
+static
+int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list)
+{
+ int i;
+ for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {}
+ return i;
+}
+
+static
+int build_option_descriptors(struct pixma_sane_t *ss)
+{
+ SANE_Option_Descriptor *sod;
+ option_descriptor_t *opt;
+
+ memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX));"""
+
+ for o in options:
+ o = ccode(o)
+ otype = o['type']
+ code = '\n opt = &(OPT_IN_CTX[%(cname_opt)s]);\n' \
+ ' sod = &opt->sod;\n' \
+ ' sod->type = %(code_type)s;\n' \
+ ' sod->title = %(code_title)s;\n' \
+ ' sod->desc = %(code_desc)s;\n'
+ if otype != 'SANE_TYPE_GROUP':
+ code += ' sod->name = %(code_name)s;\n' \
+ ' sod->unit = %(code_unit)s;\n' \
+ ' sod->size = %(code_size)s;\n' \
+ ' sod->cap = %(code_cap)s;\n' \
+ ' sod->constraint_type = %(code_constraint_type)s;\n' \
+ '%(full_code_constraint)s' \
+ ' OPT_IN_CTX[%(cname_opt)s].info = %(code_info)s;\n' \
+ '%(full_code_default)s'
+ sys.stdout.write(code % o)
+ print
+ print ' return 0;\n'
+ print '}'
+ print
+
+g = Struct()
+g.ngroups = 0
+opt_prefix = 'opt_'
+con_prefix = 'constraint_'
+cnameMap = createCNameMap()
+options = parseFile(sys.stdin)
+print "/* Automatically generated from pixma_sane.c */"
+if (len(sys.argv) == 2) and (sys.argv[1] == 'h'):
+ genHeader(options)
+else:
+ genConstraints(options)
+ genBuildOptions(options)