diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 |
commit | ee770c2346eb37e0dcb8b6cf3eaacf3d8efd6bbc (patch) | |
tree | 58f05092be1a17a939e861f8cadcda1b6ca2ecef /backend/pixma/pixma.c | |
parent | 0da9e21872802cfc6e975b1ebaf9efb9e5934d84 (diff) | |
parent | fef76e17ed4c607ea73b81279f9ef1d7121be900 (diff) |
Merge branch 'release/experimental/1.0.29-1_experimental1'experimental/1.0.29-1_experimental1
Diffstat (limited to 'backend/pixma/pixma.c')
-rw-r--r-- | backend/pixma/pixma.c | 2166 |
1 files changed, 2166 insertions, 0 deletions
diff --git a/backend/pixma/pixma.c b/backend/pixma/pixma.c new file mode 100644 index 0000000..f763496 --- /dev/null +++ b/backend/pixma/pixma.c @@ -0,0 +1,2166 @@ +/* SANE - Scanner Access Now Easy. + + Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> + Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org> + Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de> + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + */ +#include "../include/sane/config.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#ifdef USE_PTHREAD +# include <pthread.h> +#endif +#include <signal.h> /* sigaction(POSIX) */ +#include <unistd.h> /* POSIX: write read close pipe */ +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#include "pixma_rename.h" +#include "pixma.h" + +# define DEBUG_NOT_STATIC +# include "../include/sane/sane.h" +# include "../include/sane/sanei.h" +# include "../include/sane/saneopts.h" +# include "../include/sane/sanei_thread.h" +# include "../include/sane/sanei_backend.h" +# include "../include/sane/sanei_config.h" +# include "../include/sane/sanei_jpeg.h" + +#ifdef NDEBUG +# define PDBG(x) +#else +# define PDBG(x) IF_DBG(x) +#endif /* NDEBUG */ + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +#define DECL_CTX pixma_sane_t *ss = check_handle(h) +#define OPT_IN_CTX ss->opt +#define SOD(opt) OPT_IN_CTX[opt].sod +#define OVAL(opt) OPT_IN_CTX[opt].val +#define AUTO_GAMMA 2.2 + +/* pixma_sane_options.h generated by + * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h + */ +#include "pixma_sane_options.h" + +#define BUTTON_GROUP_SIZE ( opt_scan_resolution - opt_button_1 + 1 ) +#define BUTTON_GROUP_INDEX(x) ( x - opt_button_1 ) + +typedef struct pixma_sane_t +{ + struct pixma_sane_t *next; + pixma_t *s; + pixma_scan_param_t sp; + SANE_Bool cancel; + + /* valid states: idle, !idle && scanning, !idle && !scanning */ + SANE_Bool idle; + SANE_Bool scanning; + SANE_Status last_read_status; /* valid if !idle && !scanning */ + + option_descriptor_t opt[opt_last]; + char button_option_is_cached[BUTTON_GROUP_SIZE]; + SANE_Range xrange, yrange; + SANE_Word dpi_list[9]; /* up to 9600 dpi */ + SANE_String_Const mode_list[6]; + pixma_scan_mode_t mode_map[6]; + uint8_t gamma_table[4096]; + SANE_String_Const source_list[4]; + pixma_paper_source_t source_map[4]; + + unsigned byte_pos_in_line, output_line_size; + uint64_t image_bytes_read; + unsigned page_count; /* valid for ADF */ + + SANE_Pid reader_taskid; + int wpipe, rpipe; + SANE_Bool reader_stop; + + /* Valid for JPEG source */ + djpeg_dest_ptr jdst; + struct jpeg_decompress_struct jpeg_cinfo; + struct jpeg_error_mgr jpeg_err; + SANE_Bool jpeg_header_seen; +} pixma_sane_t; + +typedef struct +{ + struct jpeg_source_mgr jpeg; + + pixma_sane_t *s; + JOCTET *buffer; + + SANE_Byte *linebuffer; + SANE_Int linebuffer_size; + SANE_Int linebuffer_index; +} pixma_jpeg_src_mgr; + + +static const char vendor_str[] = "CANON"; +static const char type_str[] = "multi-function peripheral"; + +static pixma_sane_t *first_scanner = NULL; +static const SANE_Device **dev_list = NULL; +static const char* conf_devices[MAX_CONF_DEVICES]; + +static void mark_all_button_options_cached ( struct pixma_sane_t * ss ) +{ + int i; + for (i = 0; i < (opt__group_5 - opt_button_1); i++ ) + ss -> button_option_is_cached[i] = 1; +} + +static SANE_Status config_attach_pixma(SANEI_Config * config, const char *devname) +{ + int i; + UNUSED(config); + for (i=0; i < (MAX_CONF_DEVICES -1); i++) + { + if(conf_devices[i] == NULL) + { + conf_devices[i] = strdup(devname); + return SANE_STATUS_GOOD; + } + } + return SANE_STATUS_INVAL; +} + +static SANE_Status +map_error (int error) +{ + if (error >= 0) + return SANE_STATUS_GOOD; + + switch (error) + { + case PIXMA_ENOMEM: + return SANE_STATUS_NO_MEM; + case PIXMA_ECANCELED: + return SANE_STATUS_CANCELLED; + case PIXMA_EBUSY: + return SANE_STATUS_DEVICE_BUSY; + case PIXMA_EINVAL: + return SANE_STATUS_INVAL; + case PIXMA_EACCES: + return SANE_STATUS_ACCESS_DENIED; + case PIXMA_EPAPER_JAMMED: + return SANE_STATUS_JAMMED; + case PIXMA_ENO_PAPER: + return SANE_STATUS_NO_DOCS; + case PIXMA_ECOVER_OPEN: + return SANE_STATUS_COVER_OPEN; + case PIXMA_ENOTSUP: + return SANE_STATUS_UNSUPPORTED; + case PIXMA_EPROTO: + case PIXMA_ENODEV: + case PIXMA_EIO: + case PIXMA_ETIMEDOUT: + return SANE_STATUS_IO_ERROR; + } + PDBG (pixma_dbg (1, "BUG: unmapped error %d\n", error)); + return SANE_STATUS_IO_ERROR; +} + +static int +getenv_atoi (const char *name, int def) +{ + const char *str = getenv (name); + return (str) ? atoi (str) : def; +} + +#define CONST_CAST(t,x) (t)(x) + +static void +free_block (const void * ptr) +{ + free (CONST_CAST (void *, ptr)); +} + +static void +cleanup_device_list (void) +{ + if (dev_list) + { + int i; + for (i = 0; dev_list[i]; i++) + { + free_block ((const void *) dev_list[i]->name); + free_block ((const void *) dev_list[i]->model); + free_block ((const void *) dev_list[i]); + } + } + free (dev_list); + dev_list = NULL; +} + +static void +find_scanners (SANE_Bool local_only) +{ + unsigned i, nscanners; + + cleanup_device_list (); + nscanners = pixma_find_scanners (conf_devices, local_only); + PDBG (pixma_dbg (3, "pixma_find_scanners() found %u devices\n", nscanners)); + dev_list = + (const SANE_Device **) calloc (nscanners + 1, sizeof (*dev_list)); + if (!dev_list) + return; + for (i = 0; i != nscanners; i++) + { + SANE_Device *sdev = (SANE_Device *) calloc (1, sizeof (*sdev)); + char *name, *model; + if (!sdev) + goto nomem; + name = strdup (pixma_get_device_id (i)); + model = strdup (pixma_get_device_model (i)); + if (!name || !model) + { + free (name); + free (model); + free (sdev); + goto nomem; + } + sdev->name = name; + sdev->model = model; + sdev->vendor = vendor_str; + sdev->type = type_str; + dev_list[i] = sdev; + } + /* dev_list is already NULL terminated by calloc(). */ + return; + +nomem: + PDBG (pixma_dbg (1, "WARNING:not enough memory for device list\n")); + return; +} + +static pixma_sane_t * +check_handle (SANE_Handle h) +{ + pixma_sane_t *p; + + for (p = first_scanner; p && (SANE_Handle) p != h; p = p->next) + { + } + return p; +} + +static void +update_button_state (pixma_sane_t * ss, SANE_Int * info) +{ + SANE_Int b1 = OVAL (opt_button_1).w; + SANE_Int b2 = OVAL (opt_button_2).w; + uint32_t ev = pixma_wait_event (ss->s, 300); + switch (ev & ~PIXMA_EV_ACTION_MASK) + { + case PIXMA_EV_BUTTON1: + b1 = 1; + break; + case PIXMA_EV_BUTTON2: + b2 = 1; + break; + } + + if (b1 != OVAL (opt_button_1).w || b2 != OVAL (opt_button_2).w) + { + *info |= SANE_INFO_RELOAD_OPTIONS; + OVAL (opt_button_1).w = b1; + OVAL (opt_button_2).w = b2; + OVAL (opt_original).w = GET_EV_ORIGINAL(ev); + OVAL (opt_target).w = GET_EV_TARGET(ev); + OVAL (opt_scan_resolution).w = GET_EV_DPI(ev); + } + mark_all_button_options_cached(ss); +} + +static SANE_Bool +enable_option (pixma_sane_t * ss, SANE_Int o, SANE_Bool enable) +{ + SANE_Word save = SOD (o).cap; + if (enable) + SOD (o).cap &= ~SANE_CAP_INACTIVE; + else + SOD (o).cap |= SANE_CAP_INACTIVE; + return (save != SOD (o).cap); +} + +static void +clamp_value (pixma_sane_t * ss, SANE_Int n, void *v, SANE_Int * info) +{ + SANE_Option_Descriptor *sod = &SOD (n); + SANE_Word *va = (SANE_Word *) v; + const SANE_Range *range = sod->constraint.range; + int i, nmemb; + + nmemb = sod->size / sizeof (SANE_Word); + for (i = 0; i < nmemb; i++) + { + SANE_Word value = va[i]; + if (value < range->min) + { + value = range->min; + } + else if (value > range->max) + { + value = range->max; + } + if (range->quant != 0) + { + value = (value - range->min + range->quant / 2) / + range->quant * range->quant; + } + if (value != va[i]) + { + va[i] = value; + *info |= SANE_INFO_INEXACT; + } + } +} + +/* create dynamic mode_list + * ss: scanner device + * tpu = 0: flatbed or ADF mode + * 1 bit lineart, 8 bit grayscale and 24 bit color scans + * tpu = 1: TPU mode + * 16 bit grayscale and 48 bit color scans */ +static void +create_mode_list (pixma_sane_t * ss) +{ + SANE_Bool tpu; + const pixma_config_t *cfg; + int i; + + cfg = pixma_get_config (ss->s); + tpu = (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU); + + /* setup available mode */ + i = 0; + ss->mode_list[i] = SANE_VALUE_SCAN_MODE_COLOR; + ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR; + i++; + if (cfg->cap & PIXMA_CAP_GRAY) + { + ss->mode_list[i] = SANE_VALUE_SCAN_MODE_GRAY; + ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY; + i++; + } + if (tpu && (cfg->cap & PIXMA_CAP_NEGATIVE)) + { + ss->mode_list[i] = SANE_I18N ("Negative color"); + ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_COLOR; + i++; + if (cfg->cap & PIXMA_CAP_GRAY) + { + ss->mode_list[i] = SANE_I18N ("Negative gray"); + ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_GRAY; + i++; + } + } + if (tpu && (cfg->cap & PIXMA_CAP_TPUIR) == PIXMA_CAP_TPUIR) + { + ss->mode_list[i] = SANE_I18N ("Infrared"); + ss->mode_map[i] = PIXMA_SCAN_MODE_TPUIR; + i++; + } + if (!tpu && (cfg->cap & PIXMA_CAP_48BIT)) + { + ss->mode_list[i] = SANE_I18N ("48 bits color"); + ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR_48; + i++; + if (cfg->cap & PIXMA_CAP_GRAY) + { + ss->mode_list[i] = SANE_I18N ("16 bits gray"); + ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY_16; + i++; + } + } + if (!tpu && (cfg->cap & PIXMA_CAP_LINEART)) + { + ss->mode_list[i] = SANE_VALUE_SCAN_MODE_LINEART; + ss->mode_map[i] = PIXMA_SCAN_MODE_LINEART; + i++; + } + /* terminate mode_list and mode_map */ + ss->mode_list[i] = 0; + ss->mode_map[i] = 0; +} + +/* create dynamic dpi_list + * ss: scanner device */ +static void +create_dpi_list (pixma_sane_t * ss) +{ + const pixma_config_t *cfg; + int i, j; + int min; + unsigned min_dpi; + unsigned max_dpi; + + cfg = pixma_get_config (ss->s); + + /* get min/max dpi */ + max_dpi = cfg->xdpi; + min_dpi = 75; + if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU + && ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_TPUIR) + { /* IR mode */ + /*PDBG (pixma_dbg (4, "*create_dpi_list***** TPUIR mode\n"));*/ + min_dpi = (cfg->tpuir_min_dpi) ? cfg->tpuir_min_dpi : 75; + max_dpi = (cfg->tpuir_max_dpi) ? cfg->tpuir_max_dpi : cfg->xdpi; + } + else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU + || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADF + || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADFDUP) + { /* ADF / TPU mode */ + /*PDBG (pixma_dbg (4, "*create_dpi_list***** ADF/TPU mode\n"));*/ + min_dpi = (cfg->adftpu_min_dpi) ? cfg->adftpu_min_dpi : 75; + max_dpi = (cfg->adftpu_max_dpi) ? cfg->adftpu_max_dpi : cfg->xdpi; + } + else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED + && (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_COLOR_48 + || ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_GRAY_16)) + { /* 48 bits flatbed */ + /*PDBG (pixma_dbg (4, "*create_dpi_list***** 48 bits flatbed mode\n"));*/ + min_dpi = 150; + } + + /* set j for min. dpi + * 75 dpi: j = 0 + * 150 dpi: j = 1 \ + * 300 dpi: j = 2 |--> from cfg->adftpu_min_dpi or cfg->tpuir_min_dpi + * 600 dpi: j = 3 / + * */ + j = -1; + min = min_dpi / 75; + do + { + j++; + min >>= 1; + } + while (min > 0); + + /* create dpi_list + * use j for min. dpi */ + i = 0; + do + { + i++; j++; + ss->dpi_list[i] = 75 * (1 << (j - 1)); /* 75 x 2^(j-1) */ + } + while ((unsigned) ss->dpi_list[i] < max_dpi); + ss->dpi_list[0] = i; + /*PDBG (pixma_dbg (4, "*create_dpi_list***** min_dpi = %d, max_dpi = %d\n", min_dpi, max_dpi));*/ +} + +static void +select_value_from_list (pixma_sane_t * ss, SANE_Int n, void *v, + SANE_Int * info) +{ + SANE_Option_Descriptor *sod = &SOD (n); + SANE_Word *va = (SANE_Word *) v; + const SANE_Word *list = sod->constraint.word_list; + int i, j, nmemb; + + nmemb = sod->size / sizeof (SANE_Word); + for (i = 0; i < nmemb; i++) + { + SANE_Word value = va[i]; + SANE_Word mindelta = abs (value - list[1]); + SANE_Word nearest = list[1]; + for (j = 2; j <= list[0]; j++) + { + SANE_Word delta = abs (value - list[j]); + if (delta < mindelta) + { + mindelta = delta; + nearest = list[j]; + } + if (mindelta == 0) + break; + } + if (va[i] != nearest) + { + va[i] = nearest; + *info |= SANE_INFO_INEXACT; + } + } +} + +static SANE_Status +control_scalar_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v, + SANE_Int * info) +{ + option_descriptor_t *opt = &(OPT_IN_CTX[n]); + SANE_Word val; + + switch (a) + { + case SANE_ACTION_GET_VALUE: + switch (opt->sod.type) + { + case SANE_TYPE_BOOL: + case SANE_TYPE_INT: + case SANE_TYPE_FIXED: + *(SANE_Word *) v = opt->val.w; + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + return SANE_STATUS_GOOD; + + case SANE_ACTION_SET_VALUE: + switch (opt->sod.type) + { + case SANE_TYPE_BOOL: + val = *(SANE_Word *) v; + if (val != SANE_TRUE && val != SANE_FALSE) + return SANE_STATUS_INVAL; + opt->val.w = val; + break; + case SANE_TYPE_INT: + case SANE_TYPE_FIXED: + if (opt->sod.constraint_type == SANE_CONSTRAINT_RANGE) + clamp_value (ss, n, v, info); + else if (opt->sod.constraint_type == SANE_CONSTRAINT_WORD_LIST) + select_value_from_list (ss, n, v, info); + opt->val.w = *(SANE_Word *) v; + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + *info |= opt->info; + return SANE_STATUS_GOOD; + + case SANE_ACTION_SET_AUTO: + switch (opt->sod.type) + { + case SANE_TYPE_BOOL: + case SANE_TYPE_INT: + case SANE_TYPE_FIXED: + opt->val.w = opt->def.w; + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + *info |= opt->info; + return SANE_STATUS_GOOD; + } + return SANE_STATUS_UNSUPPORTED; +} + +static SANE_Status +control_string_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v, + SANE_Int * info) +{ + option_descriptor_t *opt = &(OPT_IN_CTX[n]); + const SANE_String_Const *slist = opt->sod.constraint.string_list; + SANE_String str = (SANE_String) v; + + if (opt->sod.constraint_type == SANE_CONSTRAINT_NONE) + { + switch (a) + { + case SANE_ACTION_GET_VALUE: + strcpy (str, opt->val.s); + break; + case SANE_ACTION_SET_AUTO: + str = opt->def.s; + /* fall through */ + case SANE_ACTION_SET_VALUE: + strncpy (opt->val.s, str, opt->sod.size - 1); + *info |= opt->info; + break; + } + return SANE_STATUS_GOOD; + } + else + { + int i; + + switch (a) + { + case SANE_ACTION_GET_VALUE: + strcpy (str, slist[opt->val.w]); + break; + case SANE_ACTION_SET_AUTO: + str = opt->def.ptr; + /* fall through */ + case SANE_ACTION_SET_VALUE: + i = 0; + while (slist[i] && strcasecmp (str, slist[i]) != 0) + i++; + if (!slist[i]) + return SANE_STATUS_INVAL; + if (strcmp (slist[i], str) != 0) + { + strcpy (str, slist[i]); + *info |= SANE_INFO_INEXACT; + } + opt->val.w = i; + *info |= opt->info; + break; + } + return SANE_STATUS_GOOD; + } +} + +static SANE_Status +control_option (pixma_sane_t * ss, SANE_Int n, + SANE_Action a, void *v, SANE_Int * info) +{ + int result, i; + const pixma_config_t *cfg; + SANE_Int dummy; + + /* info may be null, better to set a dummy here then test everywhere */ + if (info == NULL) + info = &dummy; + + cfg = pixma_get_config (ss->s); + + /* PDBG (pixma_dbg (4, "*control_option***** n = %u, a = %u\n", n, a)); */ + + /* first deal with options that require special treatment */ + result = SANE_STATUS_UNSUPPORTED; + switch (n) + { + case opt_gamma_table: + switch (a) + { + case SANE_ACTION_SET_VALUE: + clamp_value (ss, n, v, info); + for (i = 0; i != 4096; i++) + ss->gamma_table[i] = *((SANE_Int *) v + i); + break; + case SANE_ACTION_GET_VALUE: + for (i = 0; i != 4096; i++) + *((SANE_Int *) v + i) = ss->gamma_table[i]; + break; + case SANE_ACTION_SET_AUTO: + pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table, + sizeof (ss->gamma_table)); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + return SANE_STATUS_GOOD; + + case opt_button_update: + if (a == SANE_ACTION_SET_VALUE) + { + update_button_state (ss, info); + return SANE_STATUS_GOOD; + } + else + { + return SANE_STATUS_INVAL; + } + break; + case opt_button_1: + case opt_button_2: + case opt_original: + case opt_target: + case opt_scan_resolution: + /* poll scanner if option is not cached */ + if (! ss->button_option_is_cached[ BUTTON_GROUP_INDEX(n) ] ) + update_button_state (ss, info); + /* mark this option as read */ + ss->button_option_is_cached[ BUTTON_GROUP_INDEX(n) ] = 0; + } + + /* now deal with getting and setting of options */ + switch (SOD (n).type) + { + case SANE_TYPE_BOOL: + case SANE_TYPE_INT: + case SANE_TYPE_FIXED: + result = control_scalar_option (ss, n, a, v, info); + break; + case SANE_TYPE_STRING: + result = control_string_option (ss, n, a, v, info); + break; + case SANE_TYPE_BUTTON: + case SANE_TYPE_GROUP: + PDBG (pixma_dbg (1, "BUG:control_option():Unhandled option\n")); + result = SANE_STATUS_INVAL; + break; + } + if (result != SANE_STATUS_GOOD) + return result; + + /* deal with dependencies between options */ + switch (n) + { + case opt_custom_gamma: + if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO) + { + if (enable_option (ss, opt_gamma_table, OVAL (opt_custom_gamma).b)) + *info |= SANE_INFO_RELOAD_OPTIONS; + } + break; + case opt_gamma: + if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO) + { + /* PDBG (pixma_dbg (4, "*control_option***** gamma = %f *\n", + SANE_UNFIX (OVAL (opt_gamma).w))); */ + pixma_fill_gamma_table (SANE_UNFIX (OVAL (opt_gamma).w), + ss->gamma_table, sizeof (ss->gamma_table)); + } + break; + case opt_mode: + if (cfg->cap & (PIXMA_CAP_48BIT|PIXMA_CAP_LINEART|PIXMA_CAP_TPUIR) + && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)) + { /* new mode selected: Color, Gray, ... */ + /* PDBG (pixma_dbg (4, "*control_option***** mode = %u *\n", + ss->mode_map[OVAL (opt_mode).w])); */ + /* recreate dynamic lists */ + create_dpi_list (ss); + if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART) + { /* lineart */ + enable_option (ss, opt_threshold, SANE_TRUE); + enable_option (ss, opt_threshold_curve, SANE_TRUE); + } + else + { /* all other modes */ + enable_option (ss, opt_threshold, SANE_FALSE); + enable_option (ss, opt_threshold_curve, SANE_FALSE); + } + *info |= SANE_INFO_RELOAD_OPTIONS; + } + break; + case opt_source: + if ((cfg->cap & (PIXMA_CAP_ADF|PIXMA_CAP_ADFDUP|PIXMA_CAP_TPU)) + && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)) + { /* new source selected: flatbed, ADF, TPU, ... */ + /* to avoid fatal errors, + * select first entry of dynamic mode_list + * identifiers are unknown here */ + OVAL (opt_mode).w = ss->mode_map[0]; + /* recreate dynamic lists */ + create_mode_list (ss); + create_dpi_list (ss); + /* to avoid fatal errors, + * select first entry of dynamic dpi_list + * identifiers are unknown here */ + OVAL (opt_resolution).w = ss->dpi_list[1]; + if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART) + { /* lineart */ + enable_option (ss, opt_threshold, SANE_TRUE); + enable_option (ss, opt_threshold_curve, SANE_TRUE); + } + else + { /* all other modes */ + enable_option (ss, opt_threshold, SANE_FALSE); + enable_option (ss, opt_threshold_curve, SANE_FALSE); + } + if (cfg->cap & (PIXMA_CAP_ADF_WAIT)) + { /* adf-wait */ + enable_option (ss, opt_adf_wait, SANE_TRUE); + } + else + { /* disable adf-wait */ + enable_option (ss, opt_adf_wait, SANE_FALSE); + } + *info |= SANE_INFO_RELOAD_OPTIONS; + } + break; + } + + return result; +} + +#ifndef NDEBUG +static void +print_scan_param (int level, const pixma_scan_param_t * sp) +{ + pixma_dbg (level, "Scan parameters\n"); + pixma_dbg (level, " line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n", + sp->line_size, sp->image_size, sp->channels, sp->depth); + pixma_dbg (level, " dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n", + sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h); + pixma_dbg (level, " gamma_table=%p source=%d\n", sp->gamma_table, + sp->source); + pixma_dbg (level, " adf-wait=%d\n", sp->adf_wait); +} +#endif + +static int +calc_scan_param (pixma_sane_t * ss, pixma_scan_param_t * sp) +{ + int x1, y1, x2, y2; + int error; + + memset (sp, 0, sizeof (*sp)); + + sp->channels = (OVAL (opt_mode).w == 0) ? 3 : 1; + sp->depth = (OVAL (opt_mode).w == 2) ? 1 : 8; + sp->xdpi = sp->ydpi = OVAL (opt_resolution).w; + +#define PIXEL(x,dpi) (int)((SANE_UNFIX(x) / 25.4 * (dpi)) + 0.5) + x1 = PIXEL (OVAL (opt_tl_x).w, sp->xdpi); + x2 = PIXEL (OVAL (opt_br_x).w, sp->xdpi); + if (x2 < x1) + { + int temp = x1; + x1 = x2; + x2 = temp; + } + y1 = PIXEL (OVAL (opt_tl_y).w, sp->ydpi); + y2 = PIXEL (OVAL (opt_br_y).w, sp->ydpi); + if (y2 < y1) + { + int temp = y1; + y1 = y2; + y2 = temp; + } +#undef PIXEL + sp->x = x1; + sp->y = y1; + sp->w = x2 - x1; + sp->h = y2 - y1; + if (sp->w == 0) + sp->w = 1; + if (sp->h == 0) + sp->h = 1; + sp->tpu_offset_added = 0; + + sp->gamma_table = (OVAL (opt_custom_gamma).b) ? ss->gamma_table : NULL; + sp->source = ss->source_map[OVAL (opt_source).w]; + sp->mode = ss->mode_map[OVAL (opt_mode).w]; + sp->adf_pageid = ss->page_count; + sp->threshold = 2.55 * OVAL (opt_threshold).w; + sp->threshold_curve = OVAL (opt_threshold_curve).w; + sp->adf_wait = OVAL (opt_adf_wait).w; + + error = pixma_check_scan_param (ss->s, sp); + if (error < 0) + { + PDBG (pixma_dbg (1, "BUG:calc_scan_param() failed %d\n", error)); + PDBG (print_scan_param (1, sp)); + } + return error; +} + +static void +init_option_descriptors (pixma_sane_t * ss) +{ + const pixma_config_t *cfg; + int i; + + cfg = pixma_get_config (ss->s); + + /* setup range for the scan area. */ + ss->xrange.min = SANE_FIX (0); + ss->xrange.max = SANE_FIX (cfg->width / 75.0 * 25.4); + ss->xrange.quant = SANE_FIX (0); + + ss->yrange.min = SANE_FIX (0); + ss->yrange.max = SANE_FIX (cfg->height / 75.0 * 25.4); + ss->yrange.quant = SANE_FIX (0); + + /* mode_list and source_list were already NULL-terminated, + * because the whole pixma_sane_t was cleared during allocation. */ + + /* setup available mode. */ + create_mode_list (ss); + + /* setup dpi up to the value supported by the scanner. */ + create_dpi_list (ss); + + /* setup paper source */ + i = 0; + ss->source_list[i] = SANE_I18N ("Flatbed"); + ss->source_map[i] = PIXMA_SOURCE_FLATBED; + i++; + if (cfg->cap & PIXMA_CAP_ADF) + { + ss->source_list[i] = SANE_I18N ("Automatic Document Feeder"); + ss->source_map[i] = PIXMA_SOURCE_ADF; + i++; + } + if ((cfg->cap & PIXMA_CAP_ADFDUP) == PIXMA_CAP_ADFDUP) + { + ss->source_list[i] = SANE_I18N ("ADF Duplex"); + ss->source_map[i] = PIXMA_SOURCE_ADFDUP; + i++; + } + if (cfg->cap & PIXMA_CAP_TPU) + { + ss->source_list[i] = SANE_I18N ("Transparency Unit"); + ss->source_map[i] = PIXMA_SOURCE_TPU; + i++; + } + + build_option_descriptors (ss); + + /* Enable options that are available only in some scanners. */ + if (cfg->cap & PIXMA_CAP_GAMMA_TABLE) + { + enable_option (ss, opt_gamma, SANE_TRUE); + enable_option (ss, opt_custom_gamma, SANE_TRUE); + sane_control_option (ss, opt_custom_gamma, SANE_ACTION_SET_AUTO, + NULL, NULL); + pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table, 4096); + } + enable_option (ss, opt_button_controlled, + ((cfg->cap & PIXMA_CAP_EVENTS) != 0)); +} + +/* Writing to reader_ss outside reader_process() is a BUG! */ +static pixma_sane_t *reader_ss = NULL; + +static void +reader_signal_handler (int sig) +{ + if (reader_ss) + { + reader_ss->reader_stop = SANE_TRUE; + /* reader process is ended by SIGTERM, so no cancel in this case */ + if (sig != SIGTERM) + pixma_cancel (reader_ss->s); + } +} + +static int +write_all (pixma_sane_t * ss, void *buf_, size_t size) +{ + uint8_t *buf = (uint8_t *) buf_; + int count; + + while (size != 0 && !ss->reader_stop) + { + count = write (ss->wpipe, buf, size); + if (count == -1 && errno != EINTR) + break; + if (count == -1 && errno == EINTR) + continue; + buf += count; + size -= count; + } + return buf - (uint8_t *) buf_; +} + +/* NOTE: reader_loop() runs either in a separate thread or process. */ +static SANE_Status +reader_loop (pixma_sane_t * ss) +{ + void *buf; + unsigned bufsize; + int count = 0; + + PDBG (pixma_dbg (3, "Reader task started\n")); + /*bufsize = ss->sp.line_size + 1;*/ /* XXX: "odd" bufsize for testing pixma_read_image() */ + bufsize = ss->sp.line_size; /* bufsize EVEN needed by Xsane for 48 bits depth */ + buf = malloc (bufsize); + if (!buf) + { + count = PIXMA_ENOMEM; + goto done; + } + + count = pixma_activate_connection (ss->s); + if (count < 0) + goto done; + + pixma_enable_background (ss->s, 1); + if (OVAL (opt_button_controlled).b && ss->page_count == 0) + { + int start = 0; +#ifndef NDEBUG + pixma_dbg (1, "==== Button-controlled scan mode is enabled.\n"); + pixma_dbg (1, "==== To proceed, press 'SCAN' or 'COLOR' button. " + "To cancel, press 'GRAY' or 'END' button.\n"); +#endif + while (pixma_wait_event (ss->s, 10) != 0) + { + } + while (!start) + { + uint32_t events; + if (ss->reader_stop) + { + count = PIXMA_ECANCELED; + goto done; + } + events = pixma_wait_event (ss->s, 1000); + switch (events & ~PIXMA_EV_ACTION_MASK) + { + case PIXMA_EV_BUTTON1: + start = 1; + break; + case PIXMA_EV_BUTTON2: + count = PIXMA_ECANCELED; + goto done; + } + } + } + count = pixma_scan (ss->s, &ss->sp); + if (count >= 0) + { + while ((count = pixma_read_image (ss->s, buf, bufsize)) > 0) + { + if (write_all (ss, buf, count) != count) + pixma_cancel (ss->s); + } + } + +done: + pixma_enable_background (ss->s, 0); + pixma_deactivate_connection (ss->s); + free (buf); + close (ss->wpipe); + ss->wpipe = -1; + if (count >= 0) + { + PDBG (pixma_dbg (3, "Reader task terminated\n")); + } + else + { + PDBG (pixma_dbg + (2, "Reader task terminated: %s\n", pixma_strerror (count))); + } + return map_error (count); +} + +static int +reader_process (void *arg) +{ + pixma_sane_t *ss = (pixma_sane_t *) arg; + struct SIGACTION sa; + + reader_ss = ss; + memset (&sa, 0, sizeof (sa)); + sigemptyset (&sa.sa_mask); + sa.sa_handler = reader_signal_handler; + /* FIXME: which signal else? */ + sigaction (SIGHUP, &sa, NULL); + sigaction (SIGINT, &sa, NULL); + sigaction (SIGPIPE, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + close (ss->rpipe); + ss->rpipe = -1; + return reader_loop (ss); +} + +static int +reader_thread (void *arg) +{ + pixma_sane_t *ss = (pixma_sane_t *) arg; +#ifdef USE_PTHREAD + /* Block SIGPIPE. We will handle this in reader_loop() by checking + ss->reader_stop and the return value from write(). */ + sigset_t sigs; + sigemptyset (&sigs); + sigaddset (&sigs, SIGPIPE); + pthread_sigmask (SIG_BLOCK, &sigs, NULL); +#endif /* USE_PTHREAD */ + return reader_loop (ss); +} + +static SANE_Pid +terminate_reader_task (pixma_sane_t * ss, int *exit_code) +{ + SANE_Pid result, pid; + int status = 0; + + pid = ss->reader_taskid; + if (!sanei_thread_is_valid (pid)) + return pid; + if (sanei_thread_is_forked ()) + { + sanei_thread_kill (pid); + } + else + { + ss->reader_stop = SANE_TRUE; +/* pixma_cancel (ss->s); What is this for ? Makes end-of-scan buggy => removing */ + } + result = sanei_thread_waitpid (pid, &status); + sanei_thread_invalidate (ss->reader_taskid); + + if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP) + ss->idle = SANE_TRUE; + + if (result == pid) + { + if (exit_code) + *exit_code = status; + return pid; + } + else + { + PDBG (pixma_dbg (1, "WARNING:waitpid() failed %s\n", strerror (errno))); + sanei_thread_invalidate (pid); + return pid; + } +} + +static int +start_reader_task (pixma_sane_t * ss) +{ + int fds[2]; + SANE_Pid pid; + int is_forked; + + if (ss->rpipe != -1 || ss->wpipe != -1) + { + PDBG (pixma_dbg + (1, "BUG:rpipe = %d, wpipe = %d\n", ss->rpipe, ss->wpipe)); + close (ss->rpipe); + close (ss->wpipe); + ss->rpipe = -1; + ss->wpipe = -1; + } + if (sanei_thread_is_valid (ss->reader_taskid)) + { + PDBG (pixma_dbg + (1, "BUG:reader_taskid(%ld) != -1\n", (long) ss->reader_taskid)); + terminate_reader_task (ss, NULL); + } + if (pipe (fds) == -1) + { + PDBG (pixma_dbg (1, "ERROR:start_reader_task():pipe() failed %s\n", + strerror (errno))); + return PIXMA_ENOMEM; + } + ss->rpipe = fds[0]; + ss->wpipe = fds[1]; + ss->reader_stop = SANE_FALSE; + + is_forked = sanei_thread_is_forked (); + if (is_forked) + { + pid = sanei_thread_begin (reader_process, ss); + if (sanei_thread_is_valid (pid)) + { + close (ss->wpipe); + ss->wpipe = -1; + } + } + else + { + pid = sanei_thread_begin (reader_thread, ss); + } + if (!sanei_thread_is_valid (pid)) + { + close (ss->wpipe); + close (ss->rpipe); + ss->wpipe = -1; + ss->rpipe = -1; + PDBG (pixma_dbg (1, "ERROR:unable to start reader task\n")); + return PIXMA_ENOMEM; + } + PDBG (pixma_dbg (3, "Reader task id=%ld (%s)\n", (long) pid, + (is_forked) ? "forked" : "threaded")); + ss->reader_taskid = pid; + return 0; +} + +/* libJPEG API callbacks */ +static void +jpeg_init_source(j_decompress_ptr __sane_unused__ cinfo) +{ + /* No-op */ +} + +static void +jpeg_term_source(j_decompress_ptr __sane_unused__ cinfo) +{ + /* No-op */ +} + +static boolean +jpeg_fill_input_buffer(j_decompress_ptr cinfo) +{ + pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src; + int size; + int retry; + + for (retry = 0; retry < 30; retry ++ ) + { + size = read (mgr->s->rpipe, mgr->buffer, 1024); + if (size == 0) + { + return FALSE; + } + else if (size < 0) + { + sleep (1); + } + else + { + mgr->jpeg.next_input_byte = mgr->buffer; + mgr->jpeg.bytes_in_buffer = size; + return TRUE; + } + } + + return FALSE; +} + +static void +jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src; + + if (num_bytes > 0) + { + /* Read and throw away extra */ + while (num_bytes > (long)mgr->jpeg.bytes_in_buffer) + { + num_bytes -= (long)mgr->jpeg.bytes_in_buffer; + jpeg_fill_input_buffer(cinfo); + } + + /* Update jpeg info structure with leftover */ + mgr->jpeg.next_input_byte += (size_t) num_bytes; + mgr->jpeg.bytes_in_buffer -= (size_t) num_bytes; + } +} + +/* Pixma JPEG reader helpers */ +static SANE_Status +pixma_jpeg_start(pixma_sane_t *s) +{ + pixma_jpeg_src_mgr *mgr; + + s->jpeg_cinfo.err = jpeg_std_error(&s->jpeg_err); + + jpeg_create_decompress(&s->jpeg_cinfo); + + s->jpeg_cinfo.src = (struct jpeg_source_mgr *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo, + JPOOL_PERMANENT, sizeof(pixma_jpeg_src_mgr)); + + memset(s->jpeg_cinfo.src, 0, sizeof(pixma_jpeg_src_mgr)); + + mgr = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src; + mgr->s = s; + + mgr->buffer = (JOCTET *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo, + JPOOL_PERMANENT, + 1024 * sizeof(JOCTET)); + + mgr->jpeg.init_source = jpeg_init_source; + mgr->jpeg.fill_input_buffer = jpeg_fill_input_buffer; + mgr->jpeg.skip_input_data = jpeg_skip_input_data; + mgr->jpeg.resync_to_restart = jpeg_resync_to_restart; + mgr->jpeg.term_source = jpeg_term_source; + mgr->jpeg.bytes_in_buffer = 0; + mgr->jpeg.next_input_byte = NULL; + + s->jpeg_header_seen = 0; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +pixma_jpeg_read_header(pixma_sane_t *s) +{ + pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src; + + if (jpeg_read_header(&s->jpeg_cinfo, TRUE)) + { + s->jdst = sanei_jpeg_jinit_write_ppm(&s->jpeg_cinfo); + + if (jpeg_start_decompress(&s->jpeg_cinfo)) + { + int size; + + DBG(3, "%s: w: %d, h: %d, components: %d\n", + __func__, + s->jpeg_cinfo.output_width, s->jpeg_cinfo.output_height, + s->jpeg_cinfo.output_components); + + size = s->jpeg_cinfo.output_width * s->jpeg_cinfo.output_components * 1; + + src->linebuffer = (*s->jpeg_cinfo.mem->alloc_large)((j_common_ptr)&s->jpeg_cinfo, + JPOOL_PERMANENT, size); + + src->linebuffer_size = 0; + src->linebuffer_index = 0; + + s->jpeg_header_seen = 1; + + return SANE_STATUS_GOOD; + } + else + { + DBG(0, "%s: decompression failed\n", __func__); + return SANE_STATUS_IO_ERROR; + } + } + else + { + DBG(0, "%s: cannot read JPEG header\n", __func__); + return SANE_STATUS_IO_ERROR; + } +} + +static void +pixma_jpeg_finish(pixma_sane_t *ss) +{ + jpeg_destroy_decompress(&ss->jpeg_cinfo); +} + +static void +pixma_jpeg_read(pixma_sane_t *ss, SANE_Byte *data, + SANE_Int max_length, SANE_Int *length) +{ + struct jpeg_decompress_struct cinfo = ss->jpeg_cinfo; + pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)ss->jpeg_cinfo.src; + + int l; + + *length = 0; + + /* copy from line buffer if available */ + if (src->linebuffer_size && src->linebuffer_index < src->linebuffer_size) + { + *length = src->linebuffer_size - src->linebuffer_index; + + if (*length > max_length) + *length = max_length; + + memcpy(data, src->linebuffer + src->linebuffer_index, *length); + src->linebuffer_index += *length; + + return; + } + + if (cinfo.output_scanline >= cinfo.output_height) + { + *length = 0; + return; + } + + /* scanlines of decompressed data will be in ss->jdst->buffer + * only one line at time is supported + */ + + l = jpeg_read_scanlines(&cinfo, ss->jdst->buffer, 1); + if (l == 0) + return; + + /* from ss->jdst->buffer to linebuffer + * linebuffer holds width * bytesperpixel + */ + + (*ss->jdst->put_pixel_rows)(&cinfo, ss->jdst, 1, (char *)src->linebuffer); + + *length = ss->sp.w * ss->sp.channels; + /* Convert RGB into grayscale */ + if (ss->sp.channels == 1) + { + unsigned int i; + unsigned char *d = (unsigned char *)src->linebuffer; + unsigned char *s = (unsigned char *)src->linebuffer; + for (i = 0; i < ss->sp.w; i++) + { + /* Using BT.709 luma formula, fixed-point */ + int sum = ( s[0]*2126 + s[1]*7152 + s[2]*722 ); + *d = sum / 10000; + d ++; + s += 3; + } + } + + /* Maybe pack into lineary binary image */ + if (ss->sp.depth == 1) + { + *length /= 8; + unsigned int i; + unsigned char *d = (unsigned char *)src->linebuffer; + unsigned char *s = (unsigned char *)src->linebuffer; + unsigned char b = 0; + for (i = 1; i < ss->sp.w + 1; i++) + { + if (*(s++) > 127) + b = (b << 1) | 0; + else + b = (b << 1) | 1; + } + if ((i % 8) == 0) + *(d++) = b; + } + + src->linebuffer_size = *length; + src->linebuffer_index = 0; + + if (*length > max_length) + *length = max_length; + + memcpy(data, src->linebuffer + src->linebuffer_index, *length); + src->linebuffer_index += *length; +} + + + +static SANE_Status +read_image (pixma_sane_t * ss, void *buf, unsigned size, int *readlen) +{ + int count, status; + + if (readlen) + *readlen = 0; + if (ss->image_bytes_read >= ss->sp.image_size) + return SANE_STATUS_EOF; + + do + { + if (ss->cancel) + /* ss->rpipe has already been closed by sane_cancel(). */ + return SANE_STATUS_CANCELLED; + if (ss->sp.mode_jpeg && !ss->jpeg_header_seen) + { + status = pixma_jpeg_read_header(ss); + if (status != SANE_STATUS_GOOD) + { + close (ss->rpipe); + pixma_jpeg_finish(ss); + ss->rpipe = -1; + if (sanei_thread_is_valid (terminate_reader_task (ss, &status)) + && status != SANE_STATUS_GOOD) + { + return status; + } + else + { + /* either terminate_reader_task failed or + rpipe was closed but we expect more data */ + return SANE_STATUS_IO_ERROR; + } + } + } + + if (ss->sp.mode_jpeg) + { + count = -1; + pixma_jpeg_read(ss, buf, size, &count); + } + else + count = read (ss->rpipe, buf, size); + } + while (count == -1 && errno == EINTR); + + if (count == -1) + { + if (errno == EAGAIN) + return SANE_STATUS_GOOD; + if (!ss->cancel) + { + PDBG (pixma_dbg (1, "WARNING:read_image():read() failed %s\n", + strerror (errno))); + } + close (ss->rpipe); + ss->rpipe = -1; + terminate_reader_task (ss, NULL); + if (ss->sp.mode_jpeg) + pixma_jpeg_finish(ss); + return SANE_STATUS_IO_ERROR; + } + + /* here count >= 0 */ + ss->image_bytes_read += count; + if (ss->image_bytes_read > ss->sp.image_size) + { + PDBG (pixma_dbg (1, "BUG:ss->image_bytes_read > ss->sp.image_size\n")); + } + if (ss->image_bytes_read >= ss->sp.image_size) + { + close (ss->rpipe); + ss->rpipe = -1; + terminate_reader_task (ss, NULL); + if (ss->sp.mode_jpeg) + pixma_jpeg_finish(ss); + } + else if (count == 0) + { + PDBG (pixma_dbg (3, "read_image():reader task closed the pipe:%" + PRIu64" bytes received, %"PRIu64" bytes expected\n", + ss->image_bytes_read, ss->sp.image_size)); + close (ss->rpipe); + if (ss->sp.mode_jpeg) + pixma_jpeg_finish(ss); + ss->rpipe = -1; + if (sanei_thread_is_valid (terminate_reader_task (ss, &status)) + && status != SANE_STATUS_GOOD) + { + return status; + } + else + { + /* either terminate_reader_task failed or + rpipe was closed but we expect more data */ + return SANE_STATUS_IO_ERROR; + } + } + if (readlen) + *readlen = count; + return SANE_STATUS_GOOD; +} + + +/******************************************************************* + ** SANE API + *******************************************************************/ +SANE_Status +sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) +{ + int status, myversion, i; + SANEI_Config config; + + UNUSED (authorize); + + if (!version_code) + return SANE_STATUS_INVAL; + myversion = 100 * PIXMA_VERSION_MAJOR + PIXMA_VERSION_MINOR; + *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, myversion); + DBG_INIT (); + sanei_thread_init (); + pixma_set_debug_level (DBG_LEVEL); + + PDBG(pixma_dbg(2, "pixma is compiled %s pthread support.\n", + (sanei_thread_is_forked () ? "without" : "with"))); + + for (i = 0; i < MAX_CONF_DEVICES; i++) + conf_devices[i] = NULL; + + config.count = 0; + config.descriptors = NULL; + config.values = NULL; + + if (sanei_configure_attach(PIXMA_CONFIG_FILE, &config, config_attach_pixma) != + SANE_STATUS_GOOD) + PDBG(pixma_dbg(2, "Could not read pixma configuration file: %s\n", + PIXMA_CONFIG_FILE)); + + status = pixma_init (); + if (status < 0) + { + PDBG (pixma_dbg (2, "pixma_init() failed %s\n", pixma_strerror (status))); + } + return map_error (status); +} + +void +sane_exit (void) +{ + while (first_scanner) + sane_close (first_scanner); + cleanup_device_list (); + pixma_cleanup (); +} + +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) +{ + if (!device_list) + return SANE_STATUS_INVAL; + find_scanners (local_only); + *device_list = dev_list; + return (dev_list) ? SANE_STATUS_GOOD : SANE_STATUS_NO_MEM; +} + +SANE_Status +sane_open (SANE_String_Const name, SANE_Handle * h) +{ + unsigned i, j, nscanners; + int error = 0; + pixma_sane_t *ss = NULL; + const pixma_config_t *cfg; + + if (!name || !h) + return SANE_STATUS_INVAL; + + *h = NULL; + nscanners = pixma_find_scanners (conf_devices, SANE_FALSE); + if (nscanners == 0) + return SANE_STATUS_INVAL; + if (name[0] == '\0') + name = pixma_get_device_id (0); + + /* Have we already opened the scanner? */ + for (ss = first_scanner; ss; ss = ss->next) + { + if (strcmp (pixma_get_string (ss->s, PIXMA_STRING_ID), name) == 0) + { + /* We have already opened it! */ + return SANE_STATUS_DEVICE_BUSY; + } + } + + i = 0; + while (strcmp (pixma_get_device_id (i), name) != 0) + { + if (++i >= nscanners) + return SANE_STATUS_INVAL; + } + cfg = pixma_get_device_config (i); + if ((cfg->cap & PIXMA_CAP_EXPERIMENT) != 0) + { +#ifndef NDEBUG + pixma_dbg (1, "WARNING:" + "Experimental backend CAN DAMAGE your hardware!\n"); + if (getenv_atoi ("PIXMA_EXPERIMENT", 0) == 0) + { + pixma_dbg (1, "Experimental SANE backend for %s is disabled " + "by default.\n", pixma_get_device_model (i)); + pixma_dbg (1, "To enable it, set the environment variable " + "PIXMA_EXPERIMENT to non-zero.\n"); + return SANE_STATUS_UNSUPPORTED; + } +#else + return SANE_STATUS_UNSUPPORTED; +#endif + } + + ss = (pixma_sane_t *) calloc (1, sizeof (*ss)); + if (!ss) + return SANE_STATUS_NO_MEM; + ss->next = first_scanner; + first_scanner = ss; + sanei_thread_initialize (ss->reader_taskid); + ss->wpipe = -1; + ss->rpipe = -1; + ss->idle = SANE_TRUE; + ss->scanning = SANE_FALSE; + ss->sp.frontend_cancel = SANE_FALSE; + for (j=0; j < BUTTON_GROUP_SIZE; j++) + ss->button_option_is_cached[j] = 0; + error = pixma_open (i, &ss->s); + if (error < 0) + { + sane_close (ss); + return map_error (error); + } + pixma_enable_background (ss->s, 0); + init_option_descriptors (ss); + *h = ss; + return SANE_STATUS_GOOD; +} + +void +sane_close (SANE_Handle h) +{ + pixma_sane_t **p, *ss; + + for (p = &first_scanner; *p && *p != (pixma_sane_t *) h; p = &((*p)->next)) + { + } + if (!(*p)) + return; + ss = *p; + sane_cancel (ss); + pixma_close (ss->s); + *p = ss->next; + free (ss); +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle h, SANE_Int n) +{ + DECL_CTX; + + if (ss && 0 <= n && n < opt_last) + return &SOD (n); + return NULL; +} + +SANE_Status +sane_control_option (SANE_Handle h, SANE_Int n, + SANE_Action a, void *v, SANE_Int * i) +{ + DECL_CTX; + SANE_Int info = 0; + int error; + option_descriptor_t *opt; + + if (i) + *i = 0; + if (!ss) + return SANE_STATUS_INVAL; + if (n < 0 || n >= opt_last) + return SANE_STATUS_UNSUPPORTED; + if (!ss->idle && a != SANE_ACTION_GET_VALUE) + { + PDBG (pixma_dbg (3, "Warning: !idle && !SANE_ACTION_GET_VALUE\n")); + if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP) + return SANE_STATUS_INVAL; + } + + opt = &(OPT_IN_CTX[n]); + if (!SANE_OPTION_IS_ACTIVE (opt->sod.cap)) + return SANE_STATUS_INVAL; + switch (a) + { + case SANE_ACTION_SET_VALUE: + if ((opt->sod.type != SANE_TYPE_BUTTON && !v) || + !SANE_OPTION_IS_SETTABLE (opt->sod.cap)) + return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */ + break; + case SANE_ACTION_SET_AUTO: + if (!(opt->sod.cap & SANE_CAP_AUTOMATIC) || + !SANE_OPTION_IS_SETTABLE (opt->sod.cap)) + return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */ + break; + case SANE_ACTION_GET_VALUE: + if (!v || !(opt->sod.cap & SANE_CAP_SOFT_DETECT)) + return SANE_STATUS_INVAL; /* or _UNSUPPORTED? */ + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + + error = control_option (ss, n, a, v, &info); + if (error == SANE_STATUS_GOOD && i) + *i = info; + return error; +} + +SANE_Status +sane_get_parameters (SANE_Handle h, SANE_Parameters * p) +{ + DECL_CTX; + pixma_scan_param_t temp, *sp; + + if (!ss || !p) + return SANE_STATUS_INVAL; + + if (!ss->idle) + { + sp = &ss->sp; /* sp is calculated in sane_start() */ + } + else + { + calc_scan_param (ss, &temp); + sp = &temp; + } + p->format = (sp->channels == 3) ? SANE_FRAME_RGB : SANE_FRAME_GRAY; + p->last_frame = SANE_TRUE; + p->lines = sp->h; + p->depth = sp->depth; + p->pixels_per_line = sp->w; + /* p->bytes_per_line = sp->line_size; NOTE: It should work this way, but it doesn't. No SANE frontend can cope with this. */ + p->bytes_per_line = (sp->w * sp->channels * sp->depth) / 8; + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_start (SANE_Handle h) +{ + DECL_CTX; + int error = 0; + + if (!ss) + return SANE_STATUS_INVAL; + if (!ss->idle && ss->scanning) + { + PDBG (pixma_dbg (3, "Warning in Sane_start: !idle && scanning. idle=%d, ss->scanning=%d\n", + ss->idle, ss->scanning)); + if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP) + return SANE_STATUS_INVAL; + } + + ss->cancel = SANE_FALSE; + if (ss->idle || + ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED || + ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU) + ss->page_count = 0; /* start from idle state or scan from flatbed or TPU */ + else + ss->page_count++; + if (calc_scan_param (ss, &ss->sp) < 0) + return SANE_STATUS_INVAL; + + /* Prepare the JPEG decompressor, if needed */ + if (ss->sp.mode_jpeg) + { + SANE_Status status; + status = pixma_jpeg_start(ss); + if (status != SANE_STATUS_GOOD) + { + PDBG (pixma_dbg(1, "%s: pixma_jpeg_start: %s\n", __func__, sane_strstatus(status)) ); + return status; + } + } + + ss->image_bytes_read = 0; + /* TODO: Check paper here in sane_start(). A function like + pixma_get_status() is needed. */ + error = start_reader_task (ss); + if (error >= 0) + { + ss->output_line_size = (ss->sp.w * ss->sp.channels * ss->sp.depth) / 8; + ss->byte_pos_in_line = 0; + ss->last_read_status = SANE_STATUS_GOOD; + ss->scanning = SANE_TRUE; + ss->idle = SANE_FALSE; + if (ss->sp.mode_jpeg && !ss->jpeg_header_seen) + { + SANE_Status status; + status = pixma_jpeg_read_header(ss); + if (status != SANE_STATUS_GOOD) + { + close (ss->rpipe); + pixma_jpeg_finish(ss); + ss->rpipe = -1; + if (sanei_thread_is_valid (terminate_reader_task (ss, &error)) + && error != SANE_STATUS_GOOD) + { + return error; + } + } + } + } + return map_error (error); +} + +SANE_Status +sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len) +{ + DECL_CTX; + int sum, n; + /* Due to 32 pixels alignment, sizeof(temp) is to be greater than: + * max(nchannels) * max (sp.line_size - output_line_size) + * so currently: 3 * 32 = 96 for better end line cropping efficiency */ + SANE_Byte temp[100]; + SANE_Status status; + + if (len) + *len = 0; + if (!ss || !buf || !len) + return SANE_STATUS_INVAL; + if (ss->cancel) + return SANE_STATUS_CANCELLED; + if ((ss->idle) + && (ss->sp.source == PIXMA_SOURCE_ADF || ss->sp.source == PIXMA_SOURCE_ADFDUP)) + return SANE_STATUS_INVAL; + if (!ss->scanning) + return ss->last_read_status; + + status = SANE_STATUS_GOOD; + /* CCD scanners use software lineart + * the scanner must scan 24 bit color or 8 bit grayscale for one bit lineart */ + if ((ss->sp.line_size - ((ss->sp.software_lineart == 1) ? (ss->output_line_size * 8) : ss->output_line_size)) == 0) + { + status = read_image (ss, buf, maxlen, &sum); + } + else + { + /* FIXME: Because there is no frontend that can cope with padding at + the end of line, we've to remove it here in the backend! */ + PDBG (pixma_dbg (1, "*sane_read***** Warning: padding may cause incomplete scan results\n")); + sum = 0; + while (sum < maxlen) + { + if (ss->byte_pos_in_line < ss->output_line_size) + { + n = ss->output_line_size - ss->byte_pos_in_line; + if ((maxlen - sum) < n) + n = maxlen - sum; + status = read_image (ss, buf, n, &n); + if (n == 0) + break; + sum += n; + buf += n; + ss->byte_pos_in_line += n; + } + else + { + /* skip padding */ + n = ss->sp.line_size - ss->byte_pos_in_line; + if (n > (int) sizeof (temp)) + { + PDBG (pixma_dbg (3, "Inefficient skip buffer. Should be %d\n", n)); + n = sizeof (temp); + } + status = read_image (ss, temp, n, &n); + if (n == 0) + break; + ss->byte_pos_in_line += n; + if (ss->byte_pos_in_line == ss->sp.line_size) + ss->byte_pos_in_line = 0; + } + } + } + if (ss->cancel) + status = SANE_STATUS_CANCELLED; + else if ((status == SANE_STATUS_GOOD || status == SANE_STATUS_EOF) && + sum > 0) + { + *len = sum; + status = SANE_STATUS_GOOD; + } + ss->scanning = (status == SANE_STATUS_GOOD); + ss->last_read_status = status; + return status; +} + +void +sane_cancel (SANE_Handle h) +{ + DECL_CTX; + + if (!ss) + return; + ss->cancel = SANE_TRUE; + ss->sp.frontend_cancel = SANE_TRUE; + if (ss->idle) + return; + close (ss->rpipe); + if (ss->sp.mode_jpeg) + pixma_jpeg_finish(ss); + ss->rpipe = -1; + terminate_reader_task (ss, NULL); + ss->idle = SANE_TRUE; +} + +SANE_Status +sane_set_io_mode (SANE_Handle h, SANE_Bool m) +{ + DECL_CTX; + + if (!ss || ss->idle || ss->rpipe == -1) + return SANE_STATUS_INVAL; +#ifdef HAVE_FCNTL_H + PDBG (pixma_dbg (2, "Setting %sblocking mode\n", (m) ? "non-" : "")); + if (fcntl (ss->rpipe, F_SETFL, (m) ? O_NONBLOCK : 0) == -1) + { + PDBG (pixma_dbg + (1, "WARNING:fcntl(F_SETFL) failed %s\n", strerror (errno))); + return SANE_STATUS_UNSUPPORTED; + } + return SANE_STATUS_GOOD; +#else + return (m) ? SANE_STATUS_UNSUPPORTED : SANE_STATUS_GOOD; +#endif +} + +SANE_Status +sane_get_select_fd (SANE_Handle h, SANE_Int * fd) +{ + DECL_CTX; + + *fd = -1; + if (!ss || !fd || ss->idle || ss->rpipe == -1) + return SANE_STATUS_INVAL; + *fd = ss->rpipe; + return SANE_STATUS_GOOD; +} + +/* +BEGIN SANE_Option_Descriptor + +rem ------------------------------------------- +type group + title Scan mode + +type int resolution + unit dpi + constraint @word_list = ss->dpi_list + default 75 + title @SANE_TITLE_SCAN_RESOLUTION + desc @SANE_DESC_SCAN_RESOLUTION + cap soft_select soft_detect automatic + info reload_params + +type string mode[30] + constraint @string_list = ss->mode_list + default @s = SANE_VALUE_SCAN_MODE_COLOR + title @SANE_TITLE_SCAN_MODE + desc @SANE_DESC_SCAN_MODE + cap soft_select soft_detect automatic + info reload_params + +type string source[30] + constraint @string_list = ss->source_list + title @SANE_TITLE_SCAN_SOURCE + desc Selects the scan source (such as a document-feeder). Set source before mode and resolution. Resets mode and resolution to auto values. + default Flatbed + cap soft_select soft_detect + +type bool button-controlled + title Button-controlled scan + desc When enabled, scan process will not start immediately. To proceed, press \"SCAN\" button (for MP150) or \"COLOR\" button (for other models). To cancel, press \"GRAY\" button. + default SANE_FALSE + cap soft_select soft_detect inactive + +rem ------------------------------------------- +type group + title Gamma + +type bool custom-gamma + default SANE_TRUE + title @SANE_TITLE_CUSTOM_GAMMA + desc @SANE_DESC_CUSTOM_GAMMA + cap soft_select soft_detect automatic inactive + +type int gamma-table[4096] + constraint (0,255,0) + title @SANE_TITLE_GAMMA_VECTOR + desc @SANE_DESC_GAMMA_VECTOR + cap soft_select soft_detect automatic inactive + +type fixed gamma + default AUTO_GAMMA + constraint (0.3,5,0) + title Gamma function exponent + desc Changes intensity of midtones + cap soft_select soft_detect automatic inactive + +rem ------------------------------------------- +type group + title Geometry + +type fixed tl-x + unit mm + default 0 + constraint @range = &ss->xrange + title @SANE_TITLE_SCAN_TL_X + desc @SANE_DESC_SCAN_TL_X + cap soft_select soft_detect automatic + info reload_params + +type fixed tl-y + unit mm + default 0 + constraint @range = &ss->yrange + title @SANE_TITLE_SCAN_TL_Y + desc @SANE_DESC_SCAN_TL_Y + cap soft_select soft_detect automatic + info reload_params + +type fixed br-x + unit mm + default _MAX + constraint @range = &ss->xrange + title @SANE_TITLE_SCAN_BR_X + desc @SANE_DESC_SCAN_BR_X + cap soft_select soft_detect automatic + info reload_params + +type fixed br-y + unit mm + default _MAX + constraint @range = &ss->yrange + title @SANE_TITLE_SCAN_BR_Y + desc @SANE_DESC_SCAN_BR_Y + cap soft_select soft_detect automatic + info reload_params + +rem ------------------------------------------- +type group + title Buttons + +type button button-update + title Update button state + cap soft_select soft_detect advanced + +type int button-1 + default 0 + title Button 1 + cap soft_detect advanced + +type int button-2 + default 0 + title Button 2 + cap soft_detect advanced + +type int original + default 0 + title Type of original to scan + cap soft_detect advanced + +type int target + default 0 + title Target operation type + cap soft_detect advanced + +type int scan-resolution + default 0 + title Scan resolution + cap soft_detect advanced + +rem ------------------------------------------- +type group + title Extras + +type int threshold + unit PERCENT + default 50 + constraint (0,100,1) + title @SANE_TITLE_THRESHOLD + desc @SANE_DESC_THRESHOLD + cap soft_select soft_detect automatic inactive + +type int threshold-curve + constraint (0,127,1) + title Threshold curve + desc Dynamic threshold curve, from light to dark, normally 50-65 + cap soft_select soft_detect automatic inactive + +type int adf-wait + default 0 + constraint (0,3600,1) + title ADF Waiting Time + desc When set, the scanner searches the waiting time in seconds for a new document inserted into the automatic document feeder. + cap soft_select soft_detect automatic inactive + +rem ------------------------------------------- +END SANE_Option_Descriptor +*/ + +/* pixma_sane_options.c generated by + * scripts/pixma_gen_options.py < pixma.c > pixma_sane_options.c + * + * pixma_sane_options.h generated by + * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h + */ +#include "pixma_sane_options.c" |