diff options
Diffstat (limited to 'backend')
62 files changed, 21767 insertions, 0 deletions
diff --git a/backend/Makefile.am b/backend/Makefile.am new file mode 100644 index 0000000..4a36339 --- /dev/null +++ b/backend/Makefile.am @@ -0,0 +1,168 @@ +## Makefile.am -- an automake template for a Makefile.in file +## Copyright (C) 2004, 2005, 2009 Olaf Meeuwissen +## +## This file is part of the "Image Scan!" build infra-structure. +## +## The "Image Scan!" build infra-structure 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 FITNESS +## FOR A PARTICULAR PURPOSE or MERCHANTABILITY. +## See the GNU General Public License for more details. +## +## You should have received a verbatim copy of the GNU General Public +## License along with this program; if not, write to: +## +## Free Software Foundation, Inc. +## 59 Temple Place, Suite 330 +## Boston, MA 02111-1307 USA + + +SUBDIRS = . \ + tests + +AM_CPPFLAGS = \ + -DPIC \ + -DPKGDATADIR=\"$(datadir)/$(DATA_PKG_NAME)\" \ + -DMODELDATADIR=\"$(datadir)/$(DATA_PKG_NAME)/$(MODEL_DATA_DIR_NAME)\" \ + -DPKGLIBDIR=\"$(pkglibdir)\" \ + -DPKGLOCALSTATEDIR=\"$(localstatedir)/lib/$(PACKAGE_TARNAME)\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DENABLE_DEBUG=1 \ + -DMSG_MODULE=\"epkowa\" \ + -I$(top_srcdir) +AM_CFLAGS = \ + -fPIC + +exec_sanelibdir = $(libdir)/sane +exec_sanelib_LTLIBRARIES = \ + libsane-epkowa.la + +libsane_epkowa_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DBACKEND_NAME=epkowa +libsane_epkowa_la_LDFLAGS = \ + -export-symbols-regex ^sane_ \ + -version-info $(SANE_MAJOR):$(SANE_REVISION):$(SANE_MINOR) +libsane_epkowa_la_LIBADD = \ + libepkowa.la +libsane_epkowa_la_SOURCES = \ + backend.c \ + backend.h + +noinst_LTLIBRARIES = \ + libepkowa.la + +libepkowa_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LTDLINCL) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/non-free \ + $(XML_CFLAGS) \ + $(LIBUSB_1_0_CFLAGS) \ + -DV_MAJOR=$(SANE_MAJOR) -DV_MINOR=$(SANE_MINOR) +libepkowa_la_LDFLAGS = \ + -static +libepkowa_la_LIBADD = \ + -lm \ + $(XML_LIBS) \ + $(LIBUSB_1_0_LIBS) \ + $(LIBLTDL) +libepkowa_la_SOURCES = \ + $(sane_backends_files) \ + ipc.c \ + ipc.h \ + cfg-obj.c \ + cfg-obj.h \ + command.c \ + command.h \ + defines.h \ + hw-data.c \ + hw-data.h \ + message.c \ + message.h \ + net-obj.c \ + net-obj.h \ + dip-obj.c \ + dip-obj.h \ + device.c \ + device.h \ + timing.c \ + timing.h \ + utils.c \ + utils.h \ + epkowa_ip.c \ + epkowa_ip.h \ + epkowa_ip_api.h \ + channel.c \ + channel.h \ + channel-net.c \ + channel-pio.c \ + channel-scsi.c \ + channel-usb.c \ + model-info.c \ + model-info.h \ + list.h \ + list.c \ + get-infofile.h \ + get-infofile.c \ + xmlreader.h \ + xmlreader.c + +if ENABLE_TIMING +libepkowa_la_CPPFLAGS += -DENABLE_TIMING=1 +libepkowa_la_LIBADD += -lrt +endif + +EXTRA_DIST = \ + extension.h \ + profile.c \ + epkowa.conf + +sane_backends_files = \ + ../include/sane/sanei.h \ + ../include/sane/sanei_config.h \ + ../include/sane/sanei_debug.h \ + ../include/sane/sanei_magic.h \ + ../include/sane/sanei_pio.h \ + ../include/sane/sanei_scsi.h \ + ../include/sane/sanei_usb.h \ + ../sanei/linux_sg3_err.h \ + ../sanei/sanei_config.c \ + ../sanei/sanei_constrain_value.c \ + ../sanei/sanei_init_debug.c \ + ../sanei/sanei_magic.c \ + ../sanei/sanei_pio.c \ + ../sanei/sanei_scsi.c \ + ../sanei/sanei_usb.c \ + epkowa.c \ + epkowa.h \ + epkowa_scsi.c \ + epkowa_scsi.h + + +## There should be NO libsane.so symlink in $(exec_sanelibdir), but +## libtool insists on making one for us anyway. +## +install-exec-hook: + -rm -f $(DESTDIR)$(exec_sanelibdir)/libsane.so.$(SANE_MAJOR) + + +## Minimal sanity checks on the backends we distribute. +## WARNING: These checks may fail on non-Linux systems for a variety +## of reasons. If you know why, we would like to know (and +## patches are naturally welcome). +check-local: + for d in .libs _libs; do \ + test -d $$d || continue; \ + for l in $$d/libsane-*.so; do \ + soname=`objdump -p $$l | awk '/SONAME/ {print $$2}'`; \ + if test "$$soname" != "libsane.so.$(SANE_MAJOR)"; then \ + echo "$$l: libsane.so.$(SANE_MAJOR) != $$soname"; \ + exit 1; \ + fi; \ + done; \ + done diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 0000000..c9e6cf0 --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,600 @@ +/* backend.c -- implements the SANE epkowa backend + * Copyright (C) 2008--2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#define BACKEND_CREATE_FORWARDERS +#include "backend.h" + +#define BACKEND_BUILD 213 +#define BACKEND_SOURCE PACKAGE_STRING + +#include <errno.h> +#include <ltdl.h> +#include <string.h> + +#include "cfg-obj.h" +#include "dip-obj.h" +#include "net-obj.h" +#include "model-info.h" +#include "utils.h" + +/* Deprecated includes */ +#include "include/sane/sanei_usb.h" +#include "epkowa.h" + +/* Deprecated API calls */ +extern SANE_Status epkowa_open (const char *, SANE_Handle *, const void *); + + +typedef struct +{ + void *cfg; + void *net; + void *dip; + list *sane_dev; + void **dev_list; + void *model_info_cache; + +} backend_type; + +static backend_type *be = NULL; + + +static void be_sane_dev_dtor (void *); + + +/*! \defgroup SANE_API SANE API Entry Points + * + * The SANE API entry points make up the \e full API available to the + * SANE frontend application programmer. Users of this API should be + * careful \e never to assume \e anything about a backend's behaviour + * beyond what is required by the SANE standard. The standard can be + * retrieved via http://www.sane-project.org/docs.html. + * + * Whatever documentation may be provided here serves to document the + * implementation, if anything. In case of discrepancy with the SANE + * specification, the SANE specification is correct. + * + * @{ + */ + + +/*! \brief Prepares the backend for use by a SANE frontend. + * + * \remarks + * This function \e must be called before any other SANE API entry is + * called. It is the only SANE function that may be called after the + * sane_exit() function has been called. + * + * \todo + * Deal properly with the \c SANE_Auth_Callback. + * It is completely ignored in the current implementation. + */ +SANE_Status +sane_init (SANE_Int *version_code, SANE_Auth_Callback authorize) +{ + SANE_Status status = SANE_STATUS_GOOD; + + if (be) + { + log_call ("(%p, %p)", version_code, authorize); + err_minor ("backend already initialised"); + return status; + } + + msg_init (); + log_call ("(%p, %p)", version_code, authorize); + log_info ("%s", BACKEND_SOURCE); + log_info ("version %d.%d.%d", SANE_MAJOR, SANE_MINOR, BACKEND_BUILD); + + if (version_code) + { + *version_code = SANE_VERSION_CODE (SANE_MAJOR, SANE_MINOR, + BACKEND_BUILD); + } + + if (authorize) + { + err_minor ("authorisation not supported"); + } + + be = t_calloc (1, backend_type); + if (!be) + { + return SANE_STATUS_NO_MEM; + } + + /* Needs to be done _before_ cfg_init() because that refers to the + * model_info_cache somewhere deep down in its bowels. + */ + be->model_info_cache = model_info_cache_init (MODELDATADIR, &status); + if (!be->model_info_cache) + { + sane_exit (); + return status; + } + + be->cfg = cfg_init (PKGDATADIR, &status); + if (!be->cfg) + { + sane_exit (); + return status; + } + + if (cfg_has (be->cfg, CFG_KEY_NET)) + { + be->net = net_init (PKGLIBDIR, &status); + if (!be->net) + { + if (SANE_STATUS_GOOD != status) + err_fatal ("%s", sane_strstatus (status)); + err_major ("disabling network device support"); + cfg_set (be->cfg, CFG_KEY_NET, false); + status = SANE_STATUS_GOOD; + } + } + if (cfg_has (be->cfg, CFG_KEY_PIO)) + { + /* place holder */ + } + if (cfg_has (be->cfg, CFG_KEY_SCSI)) + { + /* place holder */ + } + if (cfg_has (be->cfg, CFG_KEY_USB)) + { + sanei_usb_init (); + } + + if (cfg_has (be->cfg, CFG_KEY_INTERPRETER)) + { + if (0 != lt_dlinit ()) + { + err_fatal ("%s", lt_dlerror ()); + err_major ("disabling interpreter support"); + cfg_set (be->cfg, CFG_KEY_INTERPRETER, false); + } + } + + be->dip = dip_init (PKGLIBDIR, &status); + if (!be->dip) + { + sane_exit (); + return status; + } + + return status; +} + + +/*! \brief Releases all resources held by the backend. + * + * \remarks + * Applications \e must call this function to terminate use of the + * backend. After it has been called, sane_init() has to be called + * before other SANE API can be used. The function needs to close + * any open handles. + * + * \remarks + * The implementation must be able to deal properly with a partially + * initialised backend so that sane_init() can use this function for + * its error recovery. + */ +void +sane_exit (void) +{ + log_call ("()"); + + if (!be) + { + msg_init (); + err_minor ("backend is not initialized"); + return; + } + + be->dip = dip_exit (be->dip); + + if (cfg_has (be->cfg, CFG_KEY_INTERPRETER)) + { + lt_dlexit (); + } + + if (cfg_has (be->cfg, CFG_KEY_USB)) + { + /* sanei_usb_exit, if only there was one! */ + } + if (cfg_has (be->cfg, CFG_KEY_SCSI)) + { + /* place holder */ + } + if (cfg_has (be->cfg, CFG_KEY_PIO)) + { + /* place holder */ + } + if (be->net) + { + be->net = net_exit (be->net); + } + + be->cfg = cfg_exit (be->cfg); + delete (be->dev_list); + list_destroy (be->sane_dev, be_sane_dev_dtor); + + be->model_info_cache = model_info_cache_exit (be->model_info_cache); + + delete (be); +} + + +/*! \brief Creates a list of devices available through the backend. + * + * \remarks + * The returned \a device_list \e must remain unchanged and valid + * until this function is called again or sane_exit() is called. + * + * \remarks + * Applications are \e not required to call this function before + * they call sane_open(). + */ +SANE_Status +sane_get_devices (const SANE_Device ***device_list, SANE_Bool local_only) +{ + SANE_Status status = SANE_STATUS_GOOD; + list *sane_dev = NULL; + + log_call ("(%p, %d)", device_list, local_only); + + if (!be) + { + msg_init (); + err_fatal ("backend is not initialized"); + return SANE_STATUS_ACCESS_DENIED; + } + + if (!device_list) + { + err_fatal ("%s", strerror (EINVAL)); + return SANE_STATUS_INVAL; + } + + sane_dev = list_create (); + if (sane_dev) + { + if (!local_only && cfg_has (be->cfg, CFG_KEY_NET)) + { + cfg_find (be->cfg, CFG_KEY_NET, sane_dev); + } + if (cfg_has (be->cfg, CFG_KEY_PIO)) + { + cfg_find (be->cfg, CFG_KEY_PIO, sane_dev); + } + if (cfg_has (be->cfg, CFG_KEY_SCSI)) + { + cfg_find (be->cfg, CFG_KEY_SCSI, sane_dev); + } + if (cfg_has (be->cfg, CFG_KEY_USB)) + { + cfg_find (be->cfg, CFG_KEY_USB, sane_dev); + } + + if (cfg_has (be->cfg, CFG_KEY_INTERPRETER)) + { + cfg_find (be->cfg, CFG_KEY_INTERPRETER, sane_dev); + } + + if (be->sane_dev) + { + delete (be->dev_list); + list_destroy (be->sane_dev, be_sane_dev_dtor); + } + be->sane_dev = sane_dev; + } + + be->dev_list = list_normalize (be->sane_dev); + *device_list = (const SANE_Device **) be->dev_list; + if (!*device_list) + { + status = SANE_STATUS_NO_MEM; + } + + return status; +} + + +/*! \brief Establishes a connection to a named device. + * + * \remarks + * Applications are allowed to call this function directly, without a + * call to sane_get_devices() first. An empty string may be used for + * the \a device_name to request the first available device. + * + * \todo Register the handle with \c be before \c SANE_STATUS_GOOD is + * returned so we can check in the other API entries whether we + * got a known handle passed in as well as call sane_close() on + * all open handles in sane_exit(). + */ +SANE_Status +sane_open (SANE_String_Const device_name, SANE_Handle *handle) +{ + const SANE_Device *sane_dev = NULL; + + log_call ("(%s, %p)", device_name, handle); + + if (!be) + { + msg_init (); + err_fatal ("backend is not initialized"); + return SANE_STATUS_ACCESS_DENIED; + } + + if (!handle) + { + err_fatal ("%s", strerror (EINVAL)); + return SANE_STATUS_INVAL; + } + + if (!device_name) + { + /* The SANE API specification explicitly talks about a zero + * length string. There is no mention about what should be + * done in the case where there is NO string at all. + * We degrade gracefully. + */ + err_minor ("assuming frontend meant to pass an empty string"); + } + + if (!be->sane_dev) + { /* FIXME: does more than necessary */ + const SANE_Device **dev = NULL; + sane_get_devices (&dev, false); + } + + if (0 == list_size (be->sane_dev)) + { + err_major ("no supported devices available"); + return SANE_STATUS_ACCESS_DENIED; + } + + if (!device_name || 0 == strlen (device_name)) + { + sane_dev = be->sane_dev->head->data; + } + else + { + list_reset (be->sane_dev); + while ((sane_dev = list_next (be->sane_dev)) + && 0 != strcmp_c (sane_dev->name, device_name)) + { + /* nothing to do, condition does all the processing */ + } + } + + if (!sane_dev) + { + err_major ("no such device"); + return SANE_STATUS_INVAL; + } + + return epkowa_open (sane_dev->name, handle, be->dip); +} + +//! Obtains the current scan parameters for a device +/*! \remarks + * The parameters are only guaranteed to be accurate between a call + * to sane_start() and the completion of that request. Outside of + * that scope the parameters are a best effort only and the backend + * is at liberty to change them. + */ +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters *parameters) +{ + SANE_Status status = SANE_STATUS_GOOD; + Epson_Scanner *s; + + log_call ("(%p, %p)", handle, parameters); + + if (!handle || !parameters) + { + err_fatal ("%s", strerror (EINVAL)); + return SANE_STATUS_INVAL; + } + + s = (Epson_Scanner *) handle; + + if (s->src->transfer_started && !s->src->transfer_stopped) + { + static const char *const color_space[] = { + "GRAY", "RGB", "RED", "GREEN", "BLUE" + }; + + const SANE_Parameters *p = &s->src->ctx; + + log_info + ("Scan area : %.2f x %.2f [mm^2]", + SANE_UNFIX (s->val[OPT_BR_X].w) - SANE_UNFIX (s->val[OPT_TL_X].w), + SANE_UNFIX (s->val[OPT_BR_Y].w) - SANE_UNFIX (s->val[OPT_TL_Y].w)); + log_info + ("Offset : (%.2f, %.2f) [mm]", + SANE_UNFIX (s->val[OPT_TL_X].w), SANE_UNFIX (s->val[OPT_TL_Y].w)); + + log_info ("Color space : %s-%d", color_space[p->format], p->depth); + log_info ("Image size : %d x %d [pixels^2] (%.2f x %.2f [mm^2])", + p->pixels_per_line, p->lines, + p->pixels_per_line * MM_PER_INCH / s->val[OPT_X_RESOLUTION].w, + p->lines * MM_PER_INCH / s->val[OPT_Y_RESOLUTION].w); + log_info ("X Resolution: %d [dpi]", s->val[OPT_X_RESOLUTION].w); + log_info ("Y Resolution: %d [dpi]", s->val[OPT_Y_RESOLUTION].w); + + memcpy (parameters, p, sizeof (*p)); + } + else + { + status = estimate_parameters (s, parameters); + } + + return status; +} + +/*! \brief Acquires up to \a max_length bytes of new image data. + * + * \remarks + * The \a length is guaranteed to be zero in case of an unsuccessful + * request. + * + * \remarks + * The implementation allows for \c NULL as a \a buffer value. This + * caters to a frontends that do not prepare buffer space when they + * expect a \c SANE_STATUS_EOF return value. + */ +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte *buffer, + SANE_Int max_length, SANE_Int *length) +{ + SANE_Status status = SANE_STATUS_GOOD; + Epson_Scanner *s; + + log_call ("(%p, %p, %i, %p)", handle, buffer, max_length, length); + + if (length) *length = 0; + + if (!handle) + { + err_fatal ("%s", strerror (EINVAL)); + return SANE_STATUS_INVAL; + } + + s = (Epson_Scanner *) handle; + + require (s->src == &s->raw || s->src == &s->img); + + if (s->src == &s->raw) + { + status = fetch_image_data (s, buffer, max_length, length); + } + else if (s->src == &s->img) + { + /**/ if (!s->img.ptr) + { + err_major ("%s", strerror (ENOMEM)); + status = SANE_STATUS_NO_MEM; + } + else if (s->img.ptr == s->img.end) + { + status = SANE_STATUS_EOF; + } + else if (s->img.cancel_requested) + { + s->img.transfer_stopped = true; + status = SANE_STATUS_CANCELLED; + } + else if (buffer && 0 < max_length) + { + SANE_Int len = s->img.end - s->img.ptr; + + if (len > max_length) len = max_length; + memcpy (buffer, s->img.ptr, len); + s->img.ptr += len; + if (length) *length = len; + } + else + { + status = SANE_STATUS_NO_MEM; + } + } + + if (SANE_STATUS_EOF == status) + { + s->src->transfer_stopped = true; + } + + return status; +} + +/*! + @} + */ + + +/*! Releases the resources held by a SANE_Device. + + This function is primarily useful to maintain the \c sane_dev + member of a backend_type object. + */ +static void +be_sane_dev_dtor (void *p) +{ + SANE_Device *sd = (SANE_Device *) p; + if (!sd) return; + + const_delete (sd->name , SANE_String); + const_delete (sd->vendor, SANE_String); + const_delete (sd->model , SANE_String); + const_delete (sd->type , SANE_String); + + delete (sd); +} diff --git a/backend/backend.h b/backend/backend.h new file mode 100644 index 0000000..848f5a7 --- /dev/null +++ b/backend/backend.h @@ -0,0 +1,380 @@ +/* backend.h -- SANE backend implementation boiler plate + * Copyright (C) 2007 EPSON AVASYS CORPORATION + * Copyright (C) 2008 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_backend_h +#define included_backend_h + +/*! \file + * \brief Provides backend implementation boiler plate. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sane/sane.h> +#include <stdio.h> + + +#ifndef BACKEND_NAME +#error "Define the BACKEND_NAME macro to match your backend's name" +#error "before including this file (backend.h)." +#error "The value should not be quoted. Characters in your backend" +#error "name that are not allowed in C variables should be replaced" +#error "by underscores, one for each such character." +#endif /* !defined (BACKEND_NAME) */ + + +#ifdef __cplusplus +extern "C" +{ +#endif +/* Set up the machinery used to create the "shadow" API entries. + * FIXME: Move these #define's into their own header file so that + * multi-file plug-ins can use them too. + * FIXME: Add an api_entry() function that mimicks the API_ENTRY() + * #define so that plug-ins can use dlsym() and friends more + * easily. You need a way to create names at run-time. The + * #define only works at compile-time! + */ +#define CONCAT_INTERNAL(s1,s2) s1##s2 +#define CONCAT(s1,s2) CONCAT_INTERNAL(s1,s2) + +#define API_ENTRY(backend,function) \ + CONCAT(sane_,CONCAT(backend,CONCAT(_,function))) + +/* For each API entry, set up "shadow" API entries with a unique name. + */ + +SANE_Status +API_ENTRY (BACKEND_NAME, init) (SANE_Int *version, + SANE_Auth_Callback authoriser); +void +API_ENTRY (BACKEND_NAME, exit) (void); + +SANE_Status +API_ENTRY (BACKEND_NAME, get_devices) (const SANE_Device ***device_list, + SANE_Bool local_only); + +SANE_Status +API_ENTRY (BACKEND_NAME, open) (SANE_String_Const device_name, + SANE_Handle *handle); +void +API_ENTRY (BACKEND_NAME, close) (SANE_Handle handle); + +const SANE_Option_Descriptor * +API_ENTRY (BACKEND_NAME, get_option_descriptor) (SANE_Handle handle, + SANE_Int index); +SANE_Status +API_ENTRY (BACKEND_NAME, control_option) (SANE_Handle handle, SANE_Int index, + SANE_Action action, void *value, + SANE_Word *info); +SANE_Status +API_ENTRY (BACKEND_NAME, get_parameters) (SANE_Handle handle, + SANE_Parameters *parameters); + +SANE_Status +API_ENTRY (BACKEND_NAME, start) (SANE_Handle handle); +SANE_Status +API_ENTRY (BACKEND_NAME, read) (SANE_Handle handle, SANE_Byte *buffer, + SANE_Int max_length, SANE_Int *length); +void +API_ENTRY (BACKEND_NAME, cancel) (SANE_Handle handle); + +SANE_Status +API_ENTRY (BACKEND_NAME, set_io_mode) (SANE_Handle handle, + SANE_Bool non_blocking); +SANE_Status +API_ENTRY (BACKEND_NAME, get_select_fd) (SANE_Handle handle, + SANE_Int *fdp); + + +SANE_String_Const +API_ENTRY (BACKEND_NAME, strstatus) (SANE_Status status); + + +#ifdef BACKEND_CREATE_FORWARDERS + +/* For each API entry, forward the original call to the "shadow" API + * entry just declared. + * Most of the argument validation can be added here. + */ + +SANE_Status +sane_init (SANE_Int *version, SANE_Auth_Callback authoriser) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, init) (version, authoriser); + return status; +} + +void +sane_exit (void) +{ + API_ENTRY (BACKEND_NAME, exit) (); + return; +} + + +SANE_Status +sane_get_devices (const SANE_Device ***device_list, SANE_Bool local_only) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, get_devices) (device_list, local_only); + return status; +} + + +SANE_Status +sane_open (SANE_String_Const device_name, SANE_Handle *handle) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, open) (device_name, handle); + return status; +} + +void +sane_close (SANE_Handle handle) +{ + API_ENTRY (BACKEND_NAME, close) (handle); + return; +} + + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int index) +{ + const SANE_Option_Descriptor *desc; + + desc = API_ENTRY (BACKEND_NAME, get_option_descriptor) (handle, index); + return desc; +} + +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int index, SANE_Action action, + void *value, SANE_Word *info) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, control_option) (handle, index, action, + value, info); + return status; +} + +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters *parameters) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, get_parameters) (handle, parameters); + return status; +} + + +SANE_Status +sane_start (SANE_Handle handle) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, start) (handle); + return status; +} + +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte *buffer, SANE_Int max_length, + SANE_Int *length) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, read) (handle, buffer, max_length, + length); + return status; +} + +void +sane_cancel (SANE_Handle handle) +{ + API_ENTRY (BACKEND_NAME, cancel) (handle); + return; +} + + +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, set_io_mode) (handle, non_blocking); + return status; +} + +SANE_Status +sane_get_select_fd (SANE_Handle handle, SANE_Int *fdp) +{ + SANE_Status status; + + status = API_ENTRY (BACKEND_NAME, get_select_fd) (handle, fdp); + return status; +} + +SANE_String_Const +sane_strstatus (SANE_Status status) +{ + SANE_String_Const str; + + str = API_ENTRY (BACKEND_NAME, strstatus) (status); + return str; +} + +/*! \brief Marks a string for the translation tools. + */ +#define N(string) (string) + +/* Backends should not be bothered with implementing their own + * version of this bit of SANE API. At best, all will do the same + * thing, at worst every one has its own implementation returning + * different strings for the same status. This implementation will + * be used by every backend without them even noticing. + */ +SANE_String_Const +API_ENTRY (BACKEND_NAME, strstatus) (SANE_Status status) +{ + switch (status) + { + case SANE_STATUS_GOOD: + return N("Success"); + case SANE_STATUS_UNSUPPORTED: + return N("Operation not supported"); + case SANE_STATUS_CANCELLED: + return N("Operation was cancelled"); + case SANE_STATUS_DEVICE_BUSY: + return N("Device busy"); + case SANE_STATUS_INVAL: + return N("Invalid argument"); + case SANE_STATUS_EOF: + return N("End of file reached"); + case SANE_STATUS_JAMMED: + return N("Document feeder jammed"); + case SANE_STATUS_NO_DOCS: + return N("Document feeder out of documents"); + case SANE_STATUS_COVER_OPEN: + return N("Scanner cover is open"); + case SANE_STATUS_IO_ERROR: + return N("Error during device I/O"); + case SANE_STATUS_NO_MEM: + return N("Out of memory"); + case SANE_STATUS_ACCESS_DENIED: + return N("Access to resource has been denied"); + + default: + { + static char msg[80]; /* not re-entrant! */ + + snprintf (msg, 80, N("Unknown status code (%d)"), status); + return msg; + } + } +} + +#undef N /* no longer needed, clean up */ + + +#endif /* !defined (BACKEND_CREATE_FORWARDERS) */ + + +/* Use the preprocessor to rename API entries in the backend to match + * the "shadow" API entries we have set up above. + * This way, the implementer can ignore all these name playing games. + */ + +#define sane_init API_ENTRY (BACKEND_NAME, init) +#define sane_exit API_ENTRY (BACKEND_NAME, exit) +#define sane_get_devices \ + API_ENTRY (BACKEND_NAME, get_devices) +#define sane_open API_ENTRY (BACKEND_NAME, open) +#define sane_close API_ENTRY (BACKEND_NAME, close) +#define sane_get_option_descriptor \ + API_ENTRY (BACKEND_NAME, get_option_descriptor) +#define sane_control_option \ + API_ENTRY (BACKEND_NAME, control_option) +#define sane_get_parameters \ + API_ENTRY (BACKEND_NAME, get_parameters) +#define sane_start API_ENTRY (BACKEND_NAME, start) +#define sane_read API_ENTRY (BACKEND_NAME, read) +#define sane_cancel API_ENTRY (BACKEND_NAME, cancel) +#define sane_set_io_mode \ + API_ENTRY (BACKEND_NAME, set_io_mode) +#define sane_get_select_fd \ + API_ENTRY (BACKEND_NAME, get_select_fd) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (included_backend_h) */ diff --git a/backend/cfg-obj.c b/backend/cfg-obj.c new file mode 100644 index 0000000..e26901c --- /dev/null +++ b/backend/cfg-obj.c @@ -0,0 +1,1619 @@ +/* cfg-obj.c -- configuration objects + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + * \todo Split the probing responsibility off into the respective + * channel classes. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cfg-obj.h" + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> + +#include "command.h" +#include "hw-data.h" +#include "net-obj.h" +#include "model-info.h" +#include "ipc.h" +#include "utils.h" + +/* Deprecated includes */ +#include "include/sane/sanei_usb.h" +#include "include/sane/sanei_scsi.h" + + +#ifndef SANE_CONFIG_DIR_NAME +#define SANE_CONFIG_DIR_NAME "/etc/sane.d" +#endif + +#define DEV_NAME_SEP_STR ":" +#define DEV_NAME_SEP DEV_NAME_SEP_STR[0] + + +const char *cfg_file_name = "epkowa.conf"; + + +typedef struct +{ + bool active[_CFG_KEY_ID_TERMINATOR_]; + list * seen[_CFG_KEY_ID_TERMINATOR_]; + +} cfg_type; + +static cfg_type *_cfg = NULL; + +static cfg_key_type _cfg_key[] = + { + "net", + "pio", + "scsi", + "usb", + + "interpreter", + "fs-blacklist", + + "option", + }; + +static char* _opt_vals[] = + { + "prefer-adf", + }; + + +static FILE *_cfg_fopen_conf (const char *filename); +static FILE *_cfg_fopen_data (const char *dirname, const char *key); + + +static cfg_key_id_type _cfg_getline (char **line, size_t *size, FILE *fp); + + +static bool _cfg_is_bare_key (const char *string); +static bool _cfg_is_valid_net_entry (const char *string); +static bool _cfg_is_valid_scsi_entry (const char *string); +static bool _cfg_is_valid_usb_entry (const char *string); +static bool _cfg_is_valid_interpreter_entry (const char *string); +static bool _cfg_is_valid_fs_blacklist_entry (const char *string); +static bool _cfg_is_valid_option_entry (const char *string); + +typedef bool (*validator) (const char *); + +static validator _cfg_validate[] = + { + _cfg_is_valid_net_entry, + _cfg_is_bare_key, + _cfg_is_valid_scsi_entry, + _cfg_is_valid_usb_entry, + + _cfg_is_valid_interpreter_entry, + _cfg_is_valid_fs_blacklist_entry, + + _cfg_is_valid_option_entry, + }; + + +static bool _cfg_register_no_op (const char *string); +static bool _cfg_register_net_entry (const char *string); +static bool _cfg_register_scsi_entry (const char *string); +static bool _cfg_register_usb_entry (const char *string); +static bool _cfg_register_interpreter_entry (const char *string); +static bool _cfg_register_fs_blacklist_entry (const char *string); +static bool _cfg_register_option_entry (const char *string); + +typedef bool (*registrar) (const char *); + +static registrar _cfg_register[] = + { + _cfg_register_net_entry, + _cfg_register_no_op, + _cfg_register_scsi_entry, + _cfg_register_usb_entry, + + _cfg_register_interpreter_entry, + _cfg_register_fs_blacklist_entry, + + _cfg_register_option_entry, + }; + + +static void _cfg_probe_no_op (list *dev_list); +static void _cfg_probe_net (list *dev_list); +static void _cfg_probe_scsi (list *dev_list); +static void _cfg_probe_usb (list *dev_list); +static void _cfg_probe_interpreter (list *dev_list); + +typedef void (*probe) (list *); + +static probe _cfg_probe[] = + { + _cfg_probe_net, + _cfg_probe_no_op, + _cfg_probe_scsi, + _cfg_probe_usb, + + _cfg_probe_interpreter, + _cfg_probe_no_op, + + _cfg_probe_no_op, + }; + + +static SANE_Status _cfg_attach (SANE_String_Const dev_name, list *dev_list); +static SANE_String_Const _cfg_get_vendor (SANE_String_Const dev_name); +static SANE_String_Const _cfg_get_model (SANE_String_Const dev_name); +static SANE_String_Const _cfg_get_type (SANE_String_Const dev_name); + +static bool _cfg_have_interpreter (const char *library, const char *firmware); + +/* Helper stuff to hack around sanei_usb/sanei_scsi API requirements + used in the various _cfg_probe_*() implementations. + */ +static list *_cfg_dev_list = NULL; +static const char *_cfg_dev_key = NULL; +static SANE_Status _cfg_scsi_attach (SANE_String_Const dev_name); +static SANE_Status _cfg_usb_attach (SANE_String_Const dev_name); + + +static void _cfg_net_dtor (void *); +static void _cfg_scsi_dtor (void *); +static void _cfg_interpreter_dtor (void *); + +typedef void (*destructor) (void *); + +static destructor _cfg_dtor[] = + { + _cfg_net_dtor, + free, + _cfg_scsi_dtor, + free, + + _cfg_interpreter_dtor, + free, + + free, + }; + + +/*! Creates and initialises a configuration object. + + The configuration object is initialised with per key data files in + the \a pkgdatadir and a configuration file, \c cfg_file_name. The + configuration file is searched for in list of directories with the + first file found being used. + + The list of directories can be customised via a \c SANE_CONFIG_DIR + environment variable. This variable uses the exact same syntax as + the \c PATH environment variable with this difference that a final + colon, \c :, appends the default locations. + + When no \c SANE_CONFIG_DIR variable is set, the default locations + will be used. The default locations are, in order: + - the process' current working directory + - the system's SANE configuration directory + + On most system's the above is equivalent to setting: + \code + SANE_CONFIG_DIR=.:/etc/sane.d + \endcode + */ +void * +cfg_init (const char *pkgdatadir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + char *lc_ctype = NULL; + FILE *fp = NULL; + cfg_key_id_type id; + + log_call ("(%s, %p)", pkgdatadir, status); + require (num_of (_cfg_key) == _CFG_KEY_ID_TERMINATOR_); + + if (_cfg) + { + if (pkgdatadir) err_minor ("been here, done that"); + if (status) *status = s; + return _cfg; + } + + _cfg = t_calloc (1, cfg_type); + if (!_cfg) + { + if (status) *status = SANE_STATUS_NO_MEM; + return _cfg; + } + + lc_ctype = setlocale (LC_CTYPE, "C"); + + if (pkgdatadir) + { + /* Look for data files containing information about the + * devices we "officially" support. Keywords without a + * corresponding data file are not an error. + */ + id = 0; + while (num_of (_cfg_key) > id) + { + fp = _cfg_fopen_data (pkgdatadir, _cfg_key[id]); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (_cfg_getline (&line, &size, fp) == id) + { + log_info ("line: '%s'", line); + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s%s%s: %s", + pkgdatadir, FILE_SEP_STR, _cfg_key[id], + strerror (errno)); + } + } + ++id; + } + + /* Allow for interpreter based packages to (un)register + * themselves upon (un)installation in a suitable place + * so we don't have to track them anymore. + */ + id = CFG_KEY_INTERPRETER; + fp = _cfg_fopen_data (PKGLOCALSTATEDIR, _cfg_key[id]); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (_cfg_getline (&line, &size, fp) == id) + { + log_info ("line: '%s'", line); + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s%s%s: %s", + PKGLOCALSTATEDIR, FILE_SEP_STR, _cfg_key[id], + strerror (errno)); + } + } + } + + fp = _cfg_fopen_conf (cfg_file_name); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (num_of (_cfg_key) != (id = _cfg_getline (&line, &size, fp))) + { + log_info ("line: '%s'", line); + + _cfg->active[id] = true; + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s: %s", cfg_file_name, strerror (errno)); + } + } + + setlocale (LC_CTYPE, lc_ctype); + + /* For backwards compatibity with older configuration files. + */ + if (cfg_has (_cfg, CFG_KEY_USB) + && 0 < list_size (_cfg->seen[CFG_KEY_INTERPRETER])) + { + log_info ("enabling interpreter support"); + cfg_set (_cfg, CFG_KEY_INTERPRETER, true); + } + + if (status) *status = s; + return _cfg; +} + +/*! Releases resources acquired by a configuration object. + + Always returns \c NULL. + + \sa cfg_init + */ +void * +cfg_exit (void *self) +{ + log_call ("(%p)", self); + require (_cfg == self); + + if (_cfg) + { + int id; + + for (id = 0; num_of (_cfg_key) > id; ++id) + { + if (_cfg->seen[id]) + { + list_destroy (_cfg->seen[id], _cfg_dtor[id]); + _cfg->seen[id] = NULL; + } + } + + delete (_cfg); + } + return _cfg; +} + + +/*! Searches for supported devices and adds them to a \a dev_list. + */ +void +cfg_find (const void *self, cfg_key_id_type id, list *dev_list) +{ + log_call ("(%p, %u, %p)", self, id, dev_list); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + if (!cfg_has (self, id)) return; + + _cfg_probe[id] (dev_list); +} + +/*! Provides read-only access to the supported devices found. + + \bug Access is not read-only. + */ +list * +cfg_seen (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg->seen[id]; +} + +/*! Tells whether configuration for a key \a id is in effect. + + \sa cfg_set + */ +bool +cfg_has (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg->active[id]; +} + +/*! Enables or disabled support for a configuration key. + + This can be used to disable configured functionality when the + required components are not available. Think network support + and interpreter based scanners. + */ +void +cfg_set (void *self, cfg_key_id_type id, bool value) +{ + log_call ("(%p, %u, %u)", self, id, value); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + _cfg->active[id] = value; +} + + +bool +cfg_has_value (const void *self, cfg_key_id_type id, const char* val) +{ + list *seen = cfg_seen (self, id); + const char *found = NULL; + + if (val && seen) + { + list_entry *cur = seen->cur; + list_reset (seen); + + while ((found = list_next (seen)) + && 0 != strcmp_c (val, found)) + { + /* condition does all the processing */ + } + seen->cur = cur; + } + log_info ("check for %s in %s: %s", val, _cfg_key[id], + (NULL != found) ? "found" : "not found"); + return (NULL != found); +} + + +/*! Returns the string that corresponds to the key \a id. + */ +cfg_key_type +cfg_key (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg_key[id]; +} + + +/*! Tries to find and open a configuration file. + + Returns a valid file pointer if successfull, \c NULL otherwise. + + \sa cfg_init + */ +static FILE * +_cfg_fopen_conf (const char *name) +{ + const char *default_path = "." PATH_SEP_STR SANE_CONFIG_DIR_NAME; + + char *path = getenv ("SANE_CONFIG_DIR"); + char *next; + char *dir; + FILE *fp; + + log_call ("(%s)", name); + require (name); + + if (path) + { + size_t len = strlen (path); + char *p; + + if (0 < len && PATH_SEP == path[len-1]) + { + len += strlen (default_path); + } + + p = t_malloc (len + 1, char); + if (p) + { + strcpy (p, path); + if (strlen (path) < len) + { + strcat (p, default_path); + } + path = strdup (p); + delete (p); + } + else + { + err_major ("SANE_CONFIG_DIR: %s", strerror (ENOMEM)); + } + } + else + { + path = strdup (default_path); + } + + if (!path) + { + errno = ENOMEM; + return NULL; + } + + fp = NULL; + next = path; + + while (!fp && (dir = strsep (&next, PATH_SEP_STR))) + { + fp = _cfg_fopen_data (dir, name); + } + delete (path); + + return fp; +} + +/*! Tries to open a configuration or data file. + + Returns a valid file pointer if successfull, \c NULL otherwise. + + \sa cfg_init + */ +static FILE * +_cfg_fopen_data (const char *dir, const char *name) +{ + FILE *fp = NULL; + char file[PATH_MAX]; + int n = snprintf (file, sizeof (file), "%s%c%s", dir, FILE_SEP, name); + + log_call ("(%s, %s)", dir, name); + require (dir && name); + + if (sizeof (file) > n) + { + fp = fopen (file, "rb"); + if (!fp) + { + log_info ("%s: %s", file, strerror (errno)); + } + } + else + { + err_minor ("%s%c%s: %s", + dir, FILE_SEP, name, strerror (ENAMETOOLONG)); + } + if (fp) log_info ("using '%s'", file); + + return fp; +} + +/*! Returns a validated line of a configuration or data file. + + Validated lines are guaranteed to start with one of the entries in + \c _cfg_key and adhere to the syntax for that particular key. + + Comments and empty lines will be silently ignored, invalid entries + will be logged and skipped and leading and/or trailing white space + as well as trailing comments will be stripped. + + Comments start with a sharp, \c #. An escape mechanism is \e not + supported. + + Returns \c _CFG_KEY_ID_TERMINATOR_ if no validated line can be + found. + */ +static cfg_key_id_type +_cfg_getline (char **line, size_t *size, FILE *fp) +{ + int id = num_of (_cfg_key); + bool valid = false; + ssize_t n; + + char *lc_ctype; + + require (line && size && fp); + + lc_ctype = setlocale (LC_CTYPE, "C"); + + while (!valid && -1 != (n = getline (line, size, fp))) + { + char *s; + + log_data ("looking at '%s'", *line); + + s = strchr (*line, '#'); + if (s) *s = '\0'; /* chomp trailing comments */ + + s = *line; + n = strlen (s); + + while (0 < n && (isspace (*s))) /* whitespace removal */ + --n, ++s; + while (0 < n && (isspace (s[n-1]))) + --n, s[n] = '\0'; + + log_data ("payload is '%s'", s); + + require (strlen (s) == n); + + if (0 < n) /* content validation */ + { + id = 0; + while (num_of (_cfg_key) > id + && 0 != strncmp_c (s, _cfg_key[id], strlen (_cfg_key[id]))) + { + log_data ("%s !~ %s", _cfg_key[id], s); + ++id; + } + + valid = (num_of (_cfg_key) > id) && _cfg_validate[id] (s); + if (valid && s != *line) + { + memmove (*line, s, strlen (s) + 1); + } + if (!valid) + { + err_major ("invalid: '%s'", s); + } + } + } + + setlocale (LC_CTYPE, lc_ctype); + + return (-1 == n ? num_of (_cfg_key) : id); +} + +/*! Tells whether a \a string consists of a single key. + */ +static bool +_cfg_is_bare_key (const char *string) +{ + int id = 0; + + require (string); + + while (num_of (_cfg_key) > id + && 0 != strcmp_c (string, _cfg_key[id])) + { + ++id; + } + return (num_of (_cfg_key) > id); +} + +/*! Says whether a \a string makes up a valid network device entry. + * + * Valid network device entries have one of the following formats: + * - \c net IPv4-address[( |:)port] + * - \c net hostname[( |:)port] + * + * \todo Tighten up validation! + * \todo Consider support for IPv4-address ranges by use of netmasks + * or CIDR notation. + */ +static bool +_cfg_is_valid_net_entry (const char *string) +{ + const char *p = string; + + int port = 0; + char c = '\0'; + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_NET])) + return false; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_NET], + strlen (_cfg_key[CFG_KEY_NET])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_NET])])) + return false; + + p += strlen (_cfg_key[CFG_KEY_NET]); + while (*p && isspace (*p)) + { + ++p; + } + + if (1 == sscanf (p, "%*s %d%1s", &port, &c)) return true; + + // check for [ip/host]:[port] format + while (*p && !isspace (*p) && ':' != *p) + { + ++p; + } + if ('\0' == *p) return true; // case without a port number + if (isspace (*p)) return false; + if (1 == sscanf (p, ":%d%1s", &port, &c)) return true; + + return false; +} + +/*! Says whether a \a string is a valid SCSI device entry. + * + * Valid SCSI device entries have the following format: + * - \c scsi[ vendor[ model]] + * + * Vendor and model specifications are case-insensitive. + * + * \todo Consider support for absolute path names to a device. + */ +static bool +_cfg_is_valid_scsi_entry (const char *string) +{ + const char *p = string; + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_SCSI])) + return true; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_SCSI], + strlen (_cfg_key[CFG_KEY_SCSI])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_SCSI])])) + return false; + + p += strlen (_cfg_key[CFG_KEY_SCSI]); + while (*p && isspace (*p)) + { + ++p; + } + while (*p && !isspace (*p)) + { + ++p; + } + if (!*p) return true; /* vendor only */ + + while (*p && isspace (*p)) + { + ++p; + } + while (*p && !isspace (*p)) + { + ++p; + } + if (!*p) return true; /* vendor model specification */ + + return false; /* we have trailing cruft */ +} + +/*! Decides whether a \a string constitutes a valid USB entry. + * + * Valid USB entries have the following format: + * - \c usb[ vendor_id prodoct_id] + * + * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences + * of one to four hexadecimal digits. Case of the digits \c A thru + * \c F is irrelevant and may be mixed, even within a single ID. + * + * \bug Does not catch IDs without intervening whitespace. + */ +static bool +_cfg_is_valid_usb_entry (const char *string) +{ + unsigned int vendor; + unsigned int product; + char x[] = "x"; /* matches 'x' or 'X' */ + char c = '\0'; /* matches trailing content */ + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_USB])) + return true; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_USB], + strlen (_cfg_key[CFG_KEY_USB])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_USB])])) + return false; + + return (4 == sscanf (string, "%*s 0%1[xX]%4x 0%1[xX]%4x%1s", + x, &vendor, x, &product, &c)); +} + +/*! Decides whether a \a string constitutes a valid interpreter entry. + * + * Valid interpreter entries have the following format: + * - \c interpreter usb vendor_id prodoct_id library[ firmware-file] + * + * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences + * of one to four hexadecimal digits. Case of the digits \c A thru + * \c F is irrelevant and may be mixed, even within a single ID. + * + * \bug Does not catch IDs without intervening whitespace. + */ +static bool +_cfg_is_valid_interpreter_entry (const char *string) +{ + const char *p1 = string; + const char *p2 = _cfg_key[CFG_KEY_INTERPRETER]; + + unsigned int vendor; + unsigned int product; + char x[] = "x"; /* matches 'x' or 'X' */ + char c1 = '\0'; /* matches required whitespace */ + char c2 = '\0'; /* matches start of library-name */ + + require (string); + + if (0 != strncmp_c (p1, p2, strlen (p2))) + return false; + + p1 += strlen (p2); + while (*p1 && isspace (*p1)) + { + ++p1; + } + + p2 = _cfg_key[CFG_KEY_USB]; + if (0 != strncmp_c (p1, p2, strlen (p2)) + || !isspace (p1[strlen(p2)])) + return false; + + return (6 == sscanf (string, "%*s %*s 0%1[xX]%4x 0%1[xX]%4x%c %c", + x, &vendor, x, &product, &c1, &c2) + && isspace (c1)); +} + +/*! Says whether \a string is a valid generic key/value entry. + * + * Valid entries have the following format: + * - \c <key> <value> + */ +static bool +_cfg_is_valid_key_value_entry (cfg_key_id_type key_id, const char *string) +{ + require (string); + + if (0 != strncmp_c (string, _cfg_key[key_id], + strlen (_cfg_key[key_id])) + || !isspace (string[strlen(_cfg_key[key_id])])) + return false; + + return true; +} + +/*! Says whether \a string is a valid FS blacklist entry. + * + * Valid blacklist entries have the following format: + * - \c fs-blacklist fw_name + */ +static bool +_cfg_is_valid_fs_blacklist_entry (const char *string) +{ + return _cfg_is_valid_key_value_entry (CFG_KEY_FS_BLACKLIST, string); +} + +/*! Says whether \a string is a valid option entry. + * + * Valid option entries have the following format: + * - \c option option_name + */ +static bool +_cfg_is_valid_option_entry (const char *string) +{ + return _cfg_is_valid_key_value_entry (CFG_KEY_OPTION, string); +} + +/*! Does \e not register a \a string at all. + + This function should never be called at run-time. It is solely + meant as a place holder for use in the \c _cfg_register array. + + Always returns \c false. + */ +static bool +_cfg_register_no_op (const char *string) +{ + require (string); + + err_minor ("internal error: '%s'", string); + return false; +} + +static bool +_cfg_register_net_entry (const char *string) +{ + cfg_net_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_NET]) + _cfg->seen[CFG_KEY_NET] = list_create (); + if (!_cfg->seen[CFG_KEY_NET]) return false; + + info = t_malloc (1, cfg_net_info); + if (info) + { + char *spec = NULL; + int port = 0; + + string += strlen (_cfg_key[CFG_KEY_NET]); + while (++string && isspace (*string)) + { + /* condition does all the processing */ + } + spec = strdup (string); + + if (1 == sscanf (spec, "%*s %d", &port)) + { + char *colon; + char *p = spec; + while (*p && !isspace (*p)) + { + ++p; + } + *p = ':'; + colon = p; + ++p; + while (*p && isspace (*p)) + { + ++p; + } + memmove (colon + 1, p, strlen (p) + 1); + } + + if (list_append (_cfg->seen[CFG_KEY_NET], info)) + { + info->spec = spec; + log_info ("registered '%s'", info->spec); + } + else + { + delete (spec); + delete (info); + } + } + + return (NULL != info); +} + +/*! Attemps to register supported SCSI device information. + */ +static bool +_cfg_register_scsi_entry (const char *string) +{ + cfg_scsi_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_SCSI]) + _cfg->seen[CFG_KEY_SCSI] = list_create (); + if (!_cfg->seen[CFG_KEY_SCSI]) return false; + + info = t_malloc (1, cfg_scsi_info); + if (info) + { + char *vendor = NULL; + char *model = NULL; + + sscanf (string, "%*s %as %as", &vendor, &model); + + if (list_append (_cfg->seen[CFG_KEY_SCSI], info)) + { + info->vendor = vendor; + info->model = model; + log_info ("registered '%s'", string); + } + else + { + delete (vendor); + delete (model); + delete (info); + } + } + + return (NULL != info); +} + +/*! Attempts to register information for a USB entry. + * + * Relies on the assumption that the \a string has been validated and + * returns \c true if successfull. When not successful, this will be + * caused by lack of memory and the function returns \c false. + */ +static bool +_cfg_register_usb_entry (const char *string) +{ + cfg_usb_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_USB]) + _cfg->seen[CFG_KEY_USB] = list_create (); + if (!_cfg->seen[CFG_KEY_USB]) return false; + + info = t_malloc (1, cfg_usb_info); + if (info) + { + unsigned int vendor; + unsigned int product; + + sscanf (string, "%*s %x %x", &vendor, &product); + + if (list_append (_cfg->seen[CFG_KEY_USB], info)) + { + info->vendor = vendor; + info->product = product; + log_info ("registered '%s'", string); + } + else + { + delete (info); + } + } + + return (NULL != info); +} + +/*! Tries to register information for an interpreter entry. + * + * \todo Check for existence of the \a library and \a firmware file. + */ +static bool +_cfg_register_interpreter_entry (const char *string) +{ + cfg_interpreter_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_INTERPRETER]) + _cfg->seen[CFG_KEY_INTERPRETER] = list_create (); + if (!_cfg->seen[CFG_KEY_INTERPRETER]) return false; + + info = t_malloc (1, cfg_interpreter_info); + if (info) + { + unsigned int vendor; + unsigned int product; + char *library = NULL; + char *firmware = NULL; + + sscanf (string, "%*s %*s %x %x %as %as", + &vendor, &product, &library, &firmware); + + if (library && _cfg_have_interpreter (library, firmware) + && list_append (_cfg->seen[CFG_KEY_INTERPRETER], info)) + { + info->vendor = vendor; + info->product = product; + info->library = library; + info->firmware = firmware; + log_info ("registered '%s'", string); + } + else + { + delete (library); + delete (firmware); + delete (info); + } + } + return (NULL != info); +} + +/*! Registers information for a generic key/value entry + */ +static bool +_cfg_register_key_value_entry (cfg_key_id_type key_id, const char *string) +{ + char *value = NULL; + + require (string); + + if (!_cfg->seen[key_id]) + _cfg->seen[key_id] = list_create (); + if (!_cfg->seen[key_id]) return false; + + string += strlen (_cfg_key[key_id]); + while (++string && isspace (*string)) + { + /* condition does all the processing */ + } + value = strdup (string); + + if (!list_append (_cfg->seen[key_id], value)) + { + delete (value); + } + if (NULL != value) + { + log_info ("registered '%s %s'", _cfg_key[key_id], value); + } + return (NULL != value); +} + +/*! Registers information for an FS command blacklist entry + */ +static bool +_cfg_register_fs_blacklist_entry (const char *string) +{ + return _cfg_register_key_value_entry (CFG_KEY_FS_BLACKLIST, string); +} + +/*! Registers information for an option entry + */ +static bool +_cfg_register_option_entry (const char *string) +{ + int i = 0; + bool found = false; + const char* value = NULL; + + value = string + strlen (_cfg_key[CFG_KEY_OPTION]); + while (++value && isspace (*value)) + { + /* condition does all the processing */ + } + + for (i=0; i<num_of (_opt_vals); ++i) + { + if (0 == strcmp_c (value, _opt_vals[i])) + { + found = true; + break; + } + } + if (!found) + { + log_info ("unknown option: '%s'", value); + return false; + } + return _cfg_register_key_value_entry (CFG_KEY_OPTION, string); +} + +/*! + */ +static void +_cfg_probe_no_op (list *dev_list) +{ + return; +} + +/* Reuse the SCSI attach function because it does all we need to do, + at least for as long as it caters to the sanei_scsi API. + */ +#define _cfg_net_attach _cfg_scsi_attach + +/*! + */ +static void +_cfg_probe_net (list *dev_list) +{ + char* buf = NULL; + char* rbuf = NULL; + + size_t size = 0; + + ssize_t n = 0; + uint16_t id = 0; + uint8_t status; + + void* net = NULL; + int sock = -1; + + int i = 0; + char* cp; + + list *registry = _cfg->seen[CFG_KEY_NET]; + cfg_net_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + net = net_init (NULL, NULL); + if (net) sock = net_get_sock (net); + + if (!net || 0 > sock) + { + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + + /* construct string from list of scanners */ + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + /* +1 for separator */ + size += strlen (info->spec) + 1; + } + registry->cur = cur; + + buf = t_malloc (size+1, char); /* +1 for NULL termination character */ + if (!buf) + { + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + memset (buf, 0, size+1); + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + strcat (buf, info->spec); + strcat (buf, "\n"); + } + registry->cur = cur; + + log_info ("Probe network:\n%s", buf); + + /* send string of NULL separated scanner ips to the network plugin */ + for (i=0; i<size; ++i) if ('\n' == buf[i]) buf[i] = '\0'; + n = ipc_send (sock, 0, TYPE_LIST, size, buf); + delete (buf); + + if (n != size) + { + log_info ("Communication error occurred. Disabling network plugin."); + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + + /* receive a string of available network scanners */ + n = -1; + int tries = 3; + while (0 > n && 0 < tries) + { + n = ipc_recv (sock, &id, &status, (void **) &rbuf); + --tries; + } + + /* bail if no network scanners were found or something bad happened */ + if (0 >= n || 0 == strlen (rbuf) || status != STATUS_OK) + { + log_info ("No network scanners detected. Disabling network plugin."); + cfg_set (_cfg, CFG_KEY_NET, false); + delete (rbuf); + return; + } + + /* process the scanner listing string */ + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_NET]; + + cp = rbuf; + for (i=0; i<n; ++i) + { + if ('\0' == rbuf[i]) + { + log_info ("Detected network scanner: %s", cp); + _cfg_net_attach (cp); + cp = rbuf + i + 1; + } + } + + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + + delete (rbuf); + + return; +} + +/*! + */ +static void +_cfg_probe_scsi (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_SCSI]; + cfg_scsi_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_SCSI]; + sanei_scsi_find_devices (info->vendor, info->model, NULL, + -1, -1, -1, -1, _cfg_scsi_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +static void +_cfg_probe_usb (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_USB]; + cfg_usb_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_USB]; + sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +/*! + */ +static void +_cfg_probe_interpreter (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_INTERPRETER]; + cfg_interpreter_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_INTERPRETER]; + sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +static SANE_Status +_cfg_scsi_attach (SANE_String_Const dev_name) +{ + SANE_String name = NULL; + size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR) + + strlen (dev_name) + 1); + + name = t_malloc (len_name, SANE_Char); + if (name) + { + SANE_Status s = SANE_STATUS_GOOD; + + strcpy (name, _cfg_dev_key); + strcat (name, DEV_NAME_SEP_STR); + strcat (name, dev_name); + + s = _cfg_attach (name, _cfg_dev_list); + if (SANE_STATUS_NO_MEM == s) + { + delete (name); + } + return s; + } + return SANE_STATUS_NO_MEM; +} + +static SANE_Status +_cfg_usb_attach (SANE_String_Const dev_name) +{ + const char *sanei_usb_prefix = "libusb:"; + + SANE_String name = NULL; + size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR) + + strlen (dev_name) + 1); + + if (0 == strncmp_c (dev_name, sanei_usb_prefix, strlen (sanei_usb_prefix))) + { + len_name -= strlen (sanei_usb_prefix); + dev_name += strlen (sanei_usb_prefix); + } + + name = t_malloc (len_name, SANE_Char); + if (name) + { + SANE_Status s = SANE_STATUS_GOOD; + + strcpy (name, _cfg_dev_key); + strcat (name, DEV_NAME_SEP_STR); + strcat (name, dev_name); + + s = _cfg_attach (name, _cfg_dev_list); + if (SANE_STATUS_NO_MEM == s) + { + delete (name); + } + return s; + } + return SANE_STATUS_NO_MEM; +} + +/*! Creates a SANE_Device and attaches it to the \a dev_list. + * + * While perhaps not particularly user-friendly, it is perfectly okay + * to attach a SANE_Device with the vendor, model and type fields set + * to something that does not exactly correspond with the device that + * is dangling on the other end of the connection. + * + * We may not be able to get reliable information for several reasons, + * not in the least because of (intermittent?) failure to communicate + * with the device. + * + * Returns SANE_STATUS_NO_MEM if a SANE_Device could not be created + * and added to the \a dev_list, SANE_STATUS_GOOD otherwise. Note + * that in the latter case only the name field is guaranteed to be + * not \c NULL. + */ +static SANE_Status +_cfg_attach (SANE_String_Const dev_name, list *dev_list) +{ + SANE_Device *dev = t_malloc (1, SANE_Device); + + require (dev_name); + + if (!dev || !list_append (dev_list, dev)) + { + delete (dev); + return SANE_STATUS_NO_MEM; + } + + dev->name = dev_name; + + dev->vendor = _cfg_get_vendor (dev_name); + dev->model = _cfg_get_model (dev_name); + dev->type = _cfg_get_type (dev_name); + + return SANE_STATUS_GOOD; +} + +/*! Returns a best effort vendor name for a device. + * + * \todo Remove hard-coding (as soon as we support non-Epson + * devices). + */ +static SANE_String_Const +_cfg_get_vendor (SANE_String_Const dev_name) +{ + return strdup ("Epson"); +} + +/*! Returns a best effort model name for a device. + */ +static SANE_String_Const +_cfg_get_model (SANE_String_Const dev_name) +{ + SANE_String_Const model = NULL; + SANE_Status status = SANE_STATUS_GOOD; + + char *fw_name = NULL; + channel *ch = NULL; + + require (dev_name); + + ch = channel_create (dev_name, &status); + if (!ch || SANE_STATUS_GOOD != status) + { + err_minor ("%s", sane_strstatus (status)); + } + else + { + ch->open (ch, &status); + if (SANE_STATUS_GOOD == status) + { + fw_name = get_fw_name (ch); + } + ch->close (ch, NULL); + ch = ch->dtor (ch); + } + + log_info ("F/W name: '%s'", fw_name); + + model = model_info_cache_get_model (fw_name); + delete (fw_name); + + return model; +} + +/*! Returns a best effort type description for a device. + * + * \todo Figure out how to deal with the predefined strings in the + * face of devices that have a flatbed and ADF and/or TPU. + * Then there is also the various ideas people have about + * multi-function peripherals. Are these SPC's, all-in-ones, + * MFPPs or even plain scanners with both a flatbed and ADF? + */ +static SANE_String_Const +_cfg_get_type (SANE_String_Const dev_name) +{ + return strdup ("flatbed scanner"); +} + +/*! Divines whether a certain interpreter is usable. + */ +static bool +_cfg_have_interpreter (const char *library, const char *firmware) +{ + require (library); + + return true; +} + + +static void +_cfg_net_dtor (void *self) +{ + cfg_net_info *p = self; + + if (!p) return; + + const_delete (p->spec, char *); + delete (p); +} + +static void +_cfg_scsi_dtor (void *self) +{ + cfg_scsi_info *p = self; + + if (!p) return; + + const_delete (p->vendor, char *); + const_delete (p->model , char *); + delete (p); +} + +static void +_cfg_interpreter_dtor (void *self) +{ + cfg_interpreter_info *p = self; + + if (!p) return; + + const_delete (p->library , char *); + const_delete (p->firmware, char *); + delete (p); +} diff --git a/backend/cfg-obj.h b/backend/cfg-obj.h new file mode 100644 index 0000000..1e414fa --- /dev/null +++ b/backend/cfg-obj.h @@ -0,0 +1,151 @@ +/* cfg-obj.h -- configuration objects + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_cfg_obj_h +#define included_cfg_obj_h + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> + +#include <sane/sane.h> + +#include "list.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + extern const char *cfg_file_name; + + typedef enum + { + CFG_KEY_NET = 0, + CFG_KEY_PIO, + CFG_KEY_SCSI, + CFG_KEY_USB, + + CFG_KEY_INTERPRETER, + CFG_KEY_FS_BLACKLIST, + + CFG_KEY_OPTION, + + _CFG_KEY_ID_TERMINATOR_ + } + cfg_key_id_type; + + typedef const char * cfg_key_type; + + typedef struct + { + const char *spec; + + } cfg_net_info; + + typedef struct + { + const char *vendor; + const char *model; + + } cfg_scsi_info; + + typedef struct + { + uint16_t vendor; + uint16_t product; + + } cfg_usb_info; + + typedef struct + { + uint16_t vendor; + uint16_t product; + const char *library; + const char *firmware; + + } cfg_interpreter_info; + + + void * cfg_init (const char *pkgdatadir, SANE_Status *status); + void * cfg_exit (void *self); + + void cfg_find (const void *self, cfg_key_id_type id, list *dev_list); + list * cfg_seen (const void *self, cfg_key_id_type id); + + bool cfg_has (const void *self, cfg_key_id_type id); + void cfg_set (void *self, cfg_key_id_type id, bool value); + + bool cfg_has_value (const void *self, cfg_key_id_type id, const char* val); + + cfg_key_type cfg_key (const void *self, cfg_key_id_type id); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (included_cfg_obj_h) */ diff --git a/backend/channel-net.c b/backend/channel-net.c new file mode 100644 index 0000000..38439fd --- /dev/null +++ b/backend/channel-net.c @@ -0,0 +1,242 @@ +/* channel_net.c -- network device communication channel + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + * \brief Implements a network device communication channel. + * \todo Deal correctly with \c SIGPIPE. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "channel.h" + +#include <string.h> +#include <unistd.h> + +#include "ipc.h" +#include "net-obj.h" +#include "utils.h" +#include "hw-data.h" + + +static void channel_net_open (channel *, SANE_Status *); +static void channel_net_close (channel *, SANE_Status *); + +static ssize_t channel_net_send (channel *, const void *, + size_t, SANE_Status *); +static ssize_t channel_net_recv (channel *, void *, + size_t, SANE_Status *); + + +channel * +channel_net_ctor (channel *self, const char *dev_name, SANE_Status *status) +{ + log_call ("(%p, '%s', %p)", self, dev_name, status); + + if (status) *status = SANE_STATUS_GOOD; + + require (self && dev_name); + require (0 == strncmp_c (dev_name, "net:", strlen ("net:"))); + + self->name = strdup (dev_name); + if (!self->name) + { + if (status) *status = SANE_STATUS_NO_MEM; + return self->dtor (self); + } + + self->open = channel_net_open; + self->close = channel_net_close; + + self->send = channel_net_send; + self->recv = channel_net_recv; + + return self; +} + +/*! Each call must consist of a complete handshake step. + * Cannot send in parts. + */ +static ssize_t +channel_net_send (channel *self, const void *buffer, + size_t size, SANE_Status *status) +{ + ssize_t n = 0; + if (0 > self->fd) + { + if (status) *status = SANE_STATUS_IO_ERROR; + return -1; + } + if (status) *status = SANE_STATUS_GOOD; + + require (self && buffer); + require (0 < self->id); + + n = ipc_send (self->fd, self->id, TYPE_ESC, size, buffer); + if (n != size) + { + if (status) *status = SANE_STATUS_IO_ERROR; + } + return n; +} + +/*! Each call must consist of a complete handshake step. + * Cannot receive in parts. + */ +static ssize_t +channel_net_recv (channel *self, void *buffer, + size_t size, SANE_Status *status) +{ + char* rbuf = NULL; + uint16_t id = 0; + uint8_t ipc_status = STATUS_OK; + ssize_t n = 0; + + if (0 > self->fd) + { + if (status) *status = SANE_STATUS_IO_ERROR; + return -1; + } + if (status) *status = SANE_STATUS_GOOD; + + require (self && buffer); + require (0 < self->id); + + n = ipc_recv (self->fd, &id, &ipc_status, (void**)&rbuf); + if (n != size) err_major ("expected %zd bytes, received %zd bytes", size, n); + if (!rbuf || id != self->id || STATUS_OK != ipc_status || n != size) + { + if (status) *status = SANE_STATUS_IO_ERROR; + delete (rbuf); + return -1; + } + + memcpy (buffer, rbuf, n); + delete (rbuf); + return n; +} + +static void +channel_net_open (channel *self, SANE_Status *status) +{ + void* net = NULL; + ssize_t n = 0; + + uint8_t ipc_status = STATUS_OK; + + char* scanner = self->name + strlen ("net:"); + + if (status) *status = SANE_STATUS_GOOD; + + net = net_init ("", NULL); + if (net) self->fd = net_get_sock (net); + if (!net || 0 > self->fd) + { + if (status) *status = SANE_STATUS_IO_ERROR; + return; + } + + n = ipc_send (self->fd, 0, TYPE_OPEN, strlen (scanner), scanner); + if (n != strlen (scanner)) + { + self->fd = -1; + if (status) *status = SANE_STATUS_IO_ERROR; + return; + } + + n = ipc_recv (self->fd, &self->id, &ipc_status, NULL); + + if (STATUS_OK != ipc_status || 0 != n) + { + self->id = 0; + self->fd = -1; + if (status) *status = SANE_STATUS_IO_ERROR; + return; + } + + log_info ("Opened network scanner at: %s", scanner); +} + +static void +channel_net_close (channel *self, SANE_Status *status) +{ + ssize_t n = 0; + + if (status) *status = SANE_STATUS_GOOD; + + n = ipc_send (self->fd, self->id, TYPE_CLOSE, 0, NULL); + self->id = 0; + self->fd = -1; + if (0 != n) + { + if (status) *status = SANE_STATUS_IO_ERROR; + log_info ("failed to close network scanner: %s", + self->name + strlen ("net:")); + return; + } + log_info ("closed network scanner: %s", self->name + strlen ("net:")); +} diff --git a/backend/channel-pio.c b/backend/channel-pio.c new file mode 100644 index 0000000..c4f202c --- /dev/null +++ b/backend/channel-pio.c @@ -0,0 +1,165 @@ +/* channel_pio.c -- parallel device communication channel + * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a parallel device communication channel. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "channel.h" + +#include <string.h> + +#include "utils.h" + +/* Deprecated includes */ +#include "sane/sanei_pio.h" + + +static void channel_pio_open (channel *, SANE_Status *); +static void channel_pio_close (channel *, SANE_Status *); + +static ssize_t channel_pio_send (channel *, const void *, + size_t, SANE_Status *); +static ssize_t channel_pio_recv (channel *, void *, + size_t, SANE_Status *); + +static void channel_pio_set_max_request_size (channel *, size_t); + +channel * +channel_pio_ctor (channel *self, const char *dev_name, SANE_Status *status) +{ + require (self && dev_name); + require (0 == strncmp_c (dev_name, "pio:", strlen ("pio:"))); + + self->open = channel_pio_open; + self->close = channel_pio_close; + + self->send = channel_pio_send; + self->recv = channel_pio_recv; + + self->set_max_request_size = channel_pio_set_max_request_size; + + if (status) *status = SANE_STATUS_UNSUPPORTED; + return self->dtor (self); +} + +static ssize_t +channel_pio_send (channel *self, const void *buffer, + size_t size, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + ssize_t n = sanei_pio_write (self->fd, buffer, size); + + if (size != n) + s = SANE_STATUS_INVAL; + if (status) *status = s; + + return n; +} + +static ssize_t +channel_pio_recv (channel *self, void *buffer, + size_t size, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + ssize_t n = sanei_pio_read (self->fd, buffer, (size_t) size); + + if (size != n) + s = SANE_STATUS_INVAL; + if (status) *status = s; + + return n; +} + +static void +channel_pio_open (channel *self, SANE_Status *status) +{ + SANE_Status s = sanei_pio_open (self->name, &self->fd); + + if (SANE_STATUS_GOOD != s) + { + err_fatal ("can not open %s (%s)", self->name, sane_strstatus (s)); + } + if (status) *status = s; +} + +static void +channel_pio_close (channel *self, SANE_Status *status) +{ + sanei_pio_close (self->fd); + self->fd = -1; +} + +static void +channel_pio_set_max_request_size (channel *self, size_t size) +{ + require (self); + + self->max_size = (size < (32 * 1024)) ? size : (32 * 1024); +} diff --git a/backend/channel-scsi.c b/backend/channel-scsi.c new file mode 100644 index 0000000..20c9e93 --- /dev/null +++ b/backend/channel-scsi.c @@ -0,0 +1,170 @@ +/* channel_scsi.c -- SCSI device communication channel + * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a SCSI communication channel. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define USE_PROTECTED_CHANNEL_API +#include "channel.h" + +#include <string.h> + +#include "utils.h" + +/* Deprecated includes */ +#include "epkowa_scsi.h" + + +static void channel_scsi_open (channel *, SANE_Status *); +static void channel_scsi_close (channel *, SANE_Status *); + +static ssize_t channel_scsi_send (channel *, const void *, + size_t, SANE_Status *); +static ssize_t channel_scsi_recv (channel *, void *, + size_t, SANE_Status *); + +static void channel_scsi_set_max_request_size (channel *, size_t); + + +channel * +channel_scsi_ctor (channel *self, const char *dev_name, SANE_Status *status) +{ + size_t name_len = 0; + + require (self && dev_name); + require (0 == strncmp_c (dev_name, "scsi:", strlen ("scsi:"))); + + dev_name += strlen ("scsi:"); + name_len = strlen (dev_name) + 1; + + self->name = t_malloc (name_len, char); + if (!self->name) + { + if (status) *status = SANE_STATUS_NO_MEM; + return self->dtor (self); + } + strcpy (self->name, dev_name); + + self->open = channel_scsi_open; + self->close = channel_scsi_close; + + self->send = channel_scsi_send; + self->recv = channel_scsi_recv; + + self->set_max_request_size = channel_scsi_set_max_request_size; + + self->max_size = sanei_scsi_max_request_size; + + return self; +} + +static ssize_t +channel_scsi_send (channel *self, const void *buffer, + size_t size, SANE_Status *status) +{ + return sanei_epson_scsi_write (self->fd, buffer, size, status); +} + +static ssize_t +channel_scsi_recv (channel *self, void *buffer, + size_t size, SANE_Status *status) +{ + return sanei_epson_scsi_read (self->fd, buffer, size, status); +} + +static void +channel_scsi_open (channel *self, SANE_Status *status) +{ + SANE_Status s = sanei_scsi_open (self->name, &self->fd, + sanei_epson_scsi_sense_handler, NULL); + if (SANE_STATUS_GOOD != s) + { + err_fatal ("can not open %s (%s)", self->name, sane_strstatus (s)); + } + if (status) *status = s; +} + +static void +channel_scsi_close (channel *self, SANE_Status *status) +{ + sanei_scsi_close (self->fd); + self->fd = -1; + + if (status) *status = SANE_STATUS_GOOD; +} + +static void +channel_scsi_set_max_request_size (channel *self, size_t size) +{ + require (self); + + self->max_size = (size < sanei_scsi_max_request_size) + ? size : sanei_scsi_max_request_size; +} diff --git a/backend/channel-usb.c b/backend/channel-usb.c new file mode 100644 index 0000000..f83dcb1 --- /dev/null +++ b/backend/channel-usb.c @@ -0,0 +1,285 @@ +/* channel_usb.c -- USB device communication channel + * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a USB communication channel. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define USE_PROTECTED_CHANNEL_API +#include "channel.h" + +#include <string.h> + +#include "utils.h" + +/* Deprecated includes */ +#include "include/sane/sanei_usb.h" +#include "epkowa_ip.h" + + +static void channel_usb_open (channel *, SANE_Status *); +static void channel_usb_close (channel *, SANE_Status *); + +static ssize_t channel_usb_send (channel *, const void *, + size_t, SANE_Status *); +static ssize_t channel_usb_recv (channel *, void *, + size_t, SANE_Status *); + + +channel * +channel_usb_ctor (channel *self, const char *dev_name, SANE_Status *status) +{ + size_t name_len = 0; + + require (self && dev_name); + require (0 == strncmp_c (dev_name, "usb:", strlen ("usb:"))); + + dev_name += strlen ("usb:"); + name_len = strlen ("libusb:") + strlen (dev_name) + 1; + + self->name = t_malloc (name_len, char); + if (!self->name) + { + if (status) *status = SANE_STATUS_NO_MEM; + return self->dtor (self); + } + self->name[0] = '\0'; + strcat (self->name, "libusb:"); + strcat (self->name, dev_name); + + self->open = channel_usb_open; + self->close = channel_usb_close; + + self->send = channel_usb_send; + self->recv = channel_usb_recv; + + self->max_size = 128 * 1024; + + return self; +} + +static ssize_t +channel_usb_send (channel *self, const void *buffer, + size_t size, SANE_Status *status) +{ + ssize_t n = size; + + if (self->interpreter) + { + n = self->interpreter->send (self, buffer, size, status); + } + else + { + SANE_Status s; + s = sanei_usb_write_bulk (self->fd, buffer, (size_t *)&n); + if (status) *status = s; + } + + return n; +} + +static ssize_t +channel_usb_recv (channel *self, void *buffer, + size_t size, SANE_Status *status) +{ + ssize_t n = size; + + if (self->interpreter) + { + n = self->interpreter->recv (self, buffer, size, status); + } + else + { + SANE_Status s = SANE_STATUS_GOOD; + s = sanei_usb_read_bulk (self->fd, (SANE_Byte *) buffer, (size_t *)&n); + if (status) *status = s; + } + + return n; +} + +static void +channel_usb_open (channel *self, SANE_Status *status) +{ + SANE_Status s; + + s = sanei_usb_open (self->name, &self->fd); + + if (SANE_STATUS_GOOD == s) + { + SANE_Word product_id = -1; + + sanei_usb_get_vendor_product (self->fd, NULL, &product_id); + + if (-1 != product_id) + { + self->id = product_id; + } + } + + if (self->interpreter && SANE_STATUS_GOOD == s) + { + if (0 > self->interpreter->open (self)) + { + s = SANE_STATUS_IO_ERROR; + } + } + + if (status) *status = s; +} + +static void +channel_usb_close (channel *self, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + if (self->interpreter) + { + self->interpreter->close (self); + } + + sanei_usb_close (self->fd); + self->fd = -1; + if (status) *status = s; +} + + +static channel * channel_interpreter_dtor (channel *self); + +channel * +channel_interpreter_ctor (channel *self, const char *dev_name, + SANE_Status *status) +{ + char *name = NULL; + size_t name_len = 0; + + require (self && dev_name); + require (0 == strncmp_c (dev_name, "interpreter:", strlen ("interpreter:"))); + + dev_name += strlen ("interpreter:"); + name_len = strlen ("usb:") + strlen (dev_name) + 1; + + name = t_malloc (name_len, char); + if (!name) + { + if (status) *status = SANE_STATUS_NO_MEM; + return self->dtor (self); + } + strcpy (name, "usb:"); + strcat (name, dev_name); + + self = channel_usb_ctor (self, name, status); + delete (name); + + if (self) + { + SANE_Status s = SANE_STATUS_GOOD; + SANE_Word vendor; + SANE_Word product; + + self->open (self, &s); + if (SANE_STATUS_GOOD == s) + { + s = sanei_usb_get_vendor_product (self->fd, + &vendor, &product); + } + self->close (self, NULL); + if (SANE_STATUS_GOOD == s) + { + s = create_interpreter (self, product); + } + + if (!self->interpreter) + { + if (status) *status = s; + return self->dtor (self); + } + else + { + self->dtor = channel_interpreter_dtor; + } + } + + self->max_size = 32 * 1024; + + return self; +} + +static channel * +channel_interpreter_dtor (channel *self) +{ + require (self); + + if (self->interpreter) + { + self->interpreter->dtor (self); + } + self->dtor = channel_dtor; + return self->dtor (self); +} diff --git a/backend/channel.c b/backend/channel.c new file mode 100644 index 0000000..754f640 --- /dev/null +++ b/backend/channel.c @@ -0,0 +1,321 @@ +/* channel.c -- device communication channel + * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a hardware communication channel. + + Hardware channels supported are usb, scsi and parallel. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define USE_PROTECTED_CHANNEL_API +#include "channel.h" + +#include "utils.h" + +extern channel * +channel_net_ctor (channel *self, const char *dev_name, SANE_Status *status); + +extern channel * +channel_pio_ctor (channel *self, const char *dev_name, SANE_Status *status); + +extern channel * +channel_scsi_ctor (channel *self, const char *dev_name, SANE_Status *status); + +extern channel * +channel_usb_ctor (channel *self, const char *dev_name, SANE_Status *status); + +extern channel * +channel_interpreter_ctor (channel *self, + const char *dev_name, SANE_Status *status); + + +#include <string.h> + +#include "epkowa_ip.h" + +#include "hw-data.h" + + +/*! A ::channel factory method. + * + * Creates and initializes a channel object to match a \a dev_name. + * + * \todo Validate \a dev_name? + */ +channel * +channel_create (const char *dev_name, SANE_Status *status) +{ + channel *ch = NULL; + + require (dev_name); + + if (status) *status = SANE_STATUS_GOOD; + + ch = t_calloc (1, channel); + if (!ch) + { + if (status) *status = SANE_STATUS_NO_MEM; + return NULL; + } + + ch->dtor = channel_dtor; + ch->is_open = channel_is_open; + ch->max_request_size = channel_max_request_size; + ch->set_max_request_size = channel_set_max_request_size; + + ch->fd = -1; + ch->id = 0; + ch->max_size = 32 * 1024; + + if (0 == strncmp_c (dev_name, "net:", strlen ("net:"))) + { + ch->ctor = channel_net_ctor; + ch->type = CHAN_NET; + } + if (0 == strncmp_c (dev_name, "pio:", strlen ("pio:"))) + { + ch->ctor = channel_pio_ctor; + ch->type = CHAN_PIO; + } + if (0 == strncmp_c (dev_name, "scsi:", strlen ("scsi:"))) + { + ch->ctor = channel_scsi_ctor; + ch->type = CHAN_SCSI; + } + if (0 == strncmp_c (dev_name, "usb:", strlen ("usb:"))) + { + ch->ctor = channel_usb_ctor; + ch->type = CHAN_USB; + } + if (0 == strncmp_c (dev_name, "interpreter:", strlen ("interpreter:"))) + { + ch->ctor = channel_interpreter_ctor; + ch->type = CHAN_INTERP; + } + + if (!ch->ctor) + { + err_major ("unsupported channel for '%s'", dev_name); + if (status) *status = SANE_STATUS_UNSUPPORTED; + delete (ch); + return NULL; + } + + return ch->ctor (ch, dev_name, status); +} + +/*! Logging wrapper around a channel's send() method. + */ +ssize_t +channel_send (channel* ch, const void *buffer, size_t size, + SANE_Status *status) +{ + ssize_t n = 0; + + log_call ("(%zd)", size); + dbg_hex (buffer, size); + + n = ch->send (ch, buffer, size, status); + + log_call ("transferred %zd bytes", n); + return n; +} + +/*! Logging wrapper around a channel's recv() method. + */ +ssize_t +channel_recv (channel *ch, void *buffer, size_t size, SANE_Status *status) +{ + ssize_t n = 0; + + log_call ("(%zd)", size); + + if (size < 256) + memset (buffer, 0x00, size); + + n = ch->recv (ch, buffer, size, status); + + if (0 < n) + { + if (size < 256) + { dbg_hex (buffer, n); } + else + { dbg_img (buffer, n); } + } + + log_call ("transferred %zd bytes", n); + return n; +} + +/*! Throttle the number of bytes read in a single go + */ +static ssize_t +channel_recv_throttle (channel *ch, void *buffer, size_t size, + SANE_Status *status) +{ + size_t max = ch->max_request_size (ch); + return ch->recv (ch, buffer, size < max ? size : max, status); +} + +ssize_t channel_recv_all (channel *ch, void *buffer, + size_t size, SANE_Status *status) +{ + return channel_recv_all_retry (ch, buffer, size, 1, status); +} + +ssize_t +channel_recv_all_retry (channel *ch, void *buffer, size_t size, + size_t max_attempts, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + ssize_t n = 0; + ssize_t t = 0; + size_t attempts = 0; + + log_call ("(%zd)", size); + + while (n < size && attempts < max_attempts) + { + t = channel_recv_throttle (ch, buffer + n, size - n, &s); + if (SANE_STATUS_GOOD != s || 0 >= t) + { + ++attempts; + log_info ("attempts: %zd/%zd", attempts, max_attempts); + } + if (0 < t) n += t; + log_call ("transferred %zd bytes, total %zd/%zd", t, n, size); + } + + if (0 < n) + { + if (size < 256) + { dbg_hex (buffer, n); } + else + { dbg_img (buffer, n); } + } + + if (status) *status = s; + + return n; +} + +/*! Tells whether a channel is ready to send() and recv() data. + */ +bool +channel_is_open (const struct channel *self) +{ + return (self && 0 <= self->fd); +} + +/*! Indicates the maximum number of bytes the channel should read + * in a singe request. + */ +size_t +channel_max_request_size (const struct channel *self) +{ + require (self); + + return self->max_size; +} + +/*! Change the maximum number of bytes a channel should read in a + * single request. + */ +void +channel_set_max_request_size (struct channel *self, size_t size) +{ + require (self); + + self->max_size = size; +} + +/*! "Base class" destructor. + */ +channel * +channel_dtor (struct channel *self) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call ("(fd = %d)", self->fd); + + if (!self) return NULL; + + if (self->interpreter) self->interpreter->dtor (self); + + if (self->is_open (self)) + { + self->close (self, &status); + } + + delete (self->name); + delete (self); + + return NULL; +} diff --git a/backend/channel.h b/backend/channel.h new file mode 100644 index 0000000..b89732d --- /dev/null +++ b/backend/channel.h @@ -0,0 +1,143 @@ +/* channel.h -- device communication channels + * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef channel_h_included +#define channel_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sane/sane.h> +#include <sys/types.h> +#include <stdint.h> + +#include "defines.h" + +typedef enum +{ + CHAN_NET = 0, + CHAN_PIO, + CHAN_SCSI, + CHAN_USB, + CHAN_INTERP +} +channel_type; + +typedef struct channel +{ + struct channel * (*ctor) (struct channel *self, + const char *dev_name, SANE_Status *status); + struct channel * (*dtor) (struct channel *self); + + void (*open) (struct channel *self, SANE_Status *status); + void (*close) (struct channel *self, SANE_Status *status); + + bool (*is_open) (const struct channel *self); + + ssize_t (*send) (struct channel *self, const void *buffer, + size_t buf_size, SANE_Status *status); + ssize_t (*recv) (struct channel *self, void *buffer, + size_t buf_size, SANE_Status *status); + size_t (*max_request_size) (const struct channel *self); + void (*set_max_request_size) (struct channel *self, size_t size); + + char *name; + channel_type type; + int fd; + uint16_t id; /* target scanner ID when used with the network plugin + * or USB product ID + */ + size_t max_size; + + struct interpreter_type *interpreter; + +} channel; + + +channel * channel_create (const char *dev_name, SANE_Status *status); + + +/* Convenience API */ +ssize_t channel_send (channel *ch, const void *buffer, + size_t size, SANE_Status *status); +ssize_t channel_recv (channel *ch, void *buffer, + size_t size, SANE_Status *status); +ssize_t channel_recv_all (channel *ch, void *buffer, + size_t size, SANE_Status *status); +ssize_t channel_recv_all_retry (channel *ch, void *buffer, size_t size, + size_t max_attempts, SANE_Status *status); + + +#ifdef USE_PROTECTED_CHANNEL_API + +channel *channel_dtor (channel *self); +bool channel_is_open (const channel *self); +size_t channel_max_request_size (const channel *self); +void channel_set_max_request_size (struct channel *self, size_t size); + +#endif + + +#endif /* !defined (channel_h_included) */ diff --git a/backend/command.c b/backend/command.c new file mode 100644 index 0000000..7c35aa6 --- /dev/null +++ b/backend/command.c @@ -0,0 +1,814 @@ +/* command.c -- assorted ESC/I protocol commands + * Copyright (C) 2008--2014 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements 'bare' ESC/I commands. + + This file contains functions that implement of a number of ESC/I + commands. These functions only handle the sending and receiving + of data, data interpretation and I/O or memory related errors. + + Interpretation of received data is limited to taking bytes apart + into bit flags and combining bytes into larger entities, such as + 2- or 4-byte integers and strings. The hardware device object's + state is updated to correspond with the interpreted data. + + Under \e no circumstances shall the functions implemented in this + file draw any conclusions whatsoever as to the device's resulting + state. This is the reponsibility of the hardware device object. + Also, it is the hardware device object's responsibility to check + the timing/validity of calling any of the functions implemented + here. + + All functions shall return a status that is one of: + + - \c SANE_STATUS_GOOD + - \c SANE_STATUS_NO_MEM + - \c SANE_STATUS_IO_ERROR + - \c SANE_STATUS_INVAL + + Function names follow the description in the ESC/I specification + and shall start with a \c cmd_ prefix. + + \note I want to replace the device::cmd crap with function + pointers and use a no-op for commands that are not supported by + the device. Functions may return SANE_STATUS_UNSUPPORTED in the + interim. + + \note I also want to introduce a function pointer based callback + mechanism to fix up broken firmware replies. That way, we do not + have to pass the firmware name everytime. We can set appropriate + callbacks for a device once and be done with it. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "command.h" + +#include "utils.h" + +#include <string.h> + +#define SANE_INT_MAX 2147483647 + + +static void +fixme_request_identity (const char *fw_name, byte *buf, size_t size) +{ + if (!fw_name) return; + if (!buf) return; + + if (0 == strcmp_c ("NX100", fw_name) && 16 < size) + { + buf[12] = 'A'; + buf[13] = 0xEC; + buf[14] = 0x13; + buf[15] = 0x6C; + buf[16] = 0x1B; + } +} + +/*! \brief Establishes basic device capabilities. + + \note This command is assumed to be supported by \e all ESC/I + devices. + + \todo Implement error checking. + */ +SANE_Status +cmd_request_identity (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, 'I' }; + byte info[4]; + + byte *data = NULL; + size_t size; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, info, num_of (info), &status); + + hw->status = info[1]; + size = info[3] << 8 | info[2]; + + if (0 < size) + { + data = t_calloc (size, byte); + if (!data) + { + return SANE_STATUS_NO_MEM; + } + + channel_recv (hw->channel, data, size, &status); + if (SANE_STATUS_GOOD == status) + { + byte *p = data + 2; + + fixme_request_identity (hw->fw_name, data, size); + + hw->cmd_lvl[0] = data[0]; + hw->cmd_lvl[1] = data[1]; + + free_resolution_info (&hw->res); + init_resolution_info (&hw->res, p); + init_resolution_info (&hw->resolution, NULL); + copy_resolution_info (&hw->resolution, &hw->res, SANE_TRUE); + + hw->max_x = data[size-3] << 8 | data[size-4]; + hw->max_y = data[size-1] << 8 | data[size-2]; + } + delete (data); + } + + return status; +} + +static void +fixme_request_hardware_property (const char *fw_name, byte *buf, size_t size) +{ + if (!fw_name) return; + if (!buf) return; + + if (0 == strcmp_c ("NX100", fw_name) && 33 < size) + { + buf[32] = 0xB0; + buf[33] = 0x04; + } +} + +/*! \brief Query additional device capabilities. + + \note This command is not supported for B level devices. It is + supported for D level devices. + + \todo Implement error checking. + */ +SANE_Status +cmd_request_hardware_property (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, 'i' }; + byte info[4]; + + byte *data = NULL; + size_t size; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, info, num_of (info), &status); + + hw->status = info[1]; + size = info[3] << 8 | info[2]; + + if (0 < size) + { + data = t_calloc (size, byte); + if (!data) + { + return SANE_STATUS_NO_MEM; + } + + channel_recv (hw->channel, data, size, &status); + if (SANE_STATUS_GOOD == status) + { + byte *p = data + 14; + + fixme_request_hardware_property (hw->fw_name, data, size); + + hw->optical_res = data[1] << 8 | data[0]; + hw->sensor_info = data[2]; + hw->scan_order = data[3]; + hw->line_dist_x = data[4]; + hw->line_dist_y = data[5]; + + free_resolution_info (&hw->res_x); + init_resolution_info (&hw->res_x, p); + + while (resolution_info_ESC_i_cond (p)) + p += 2; + + p += 2; /* start of sub resolution info */ + free_resolution_info (&hw->res_y); + init_resolution_info (&hw->res_y, p); + } + delete (data); + } + + return status; +} + +SANE_Status +cmd_set_scanning_parameter (device* hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte FS_W[] = {FS, 'W'}; + + byte ack_buf; + + log_call (); + require (hw); + + channel_send (hw->channel, FS_W, 2, &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &ack_buf, 1, &status); + if (SANE_STATUS_GOOD != status) return status; + if (ACK != ack_buf) return SANE_STATUS_UNSUPPORTED; + + channel_send (hw->channel, hw->param_buf, 64, &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &ack_buf, 1, &status); + if (SANE_STATUS_GOOD != status) return status; + if (ACK != ack_buf) return SANE_STATUS_INVAL; + + return status; +} + +SANE_Status +cmd_request_scanning_parameter (device* hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + const byte FS_S[] = {FS, 'S'}; + + log_call (); + require (hw); + + channel_send (hw->channel, FS_S, 2, &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, hw->param_buf, 64, &status); + + return status; +} + +SANE_Status +cmd_request_scanner_status (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { FS, 'F' }; + byte buf[16]; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, buf, num_of (buf), &status); + + hw->fsf_status = buf[0]; + { /* transfer corresponding flags */ + byte mask = FSF_STATUS_FER | FSF_STATUS_WU; + hw->ext_status &= ~mask; + hw->ext_status |= (mask & hw->fsf_status); + } + + if ((ADF_STATUS_IST & buf[1]) && !hw->adf) + { + hw->adf = t_calloc (1, adf_extension); + if (!hw->adf) return SANE_STATUS_NO_MEM; + } + if ((TPU_STATUS_IST & buf[2]) && !hw->tpu) + { + hw->tpu = t_calloc (1, tpu_extension); + if (!hw->tpu) return SANE_STATUS_NO_MEM; + } + + if (hw->fbf) + { + hw->fbf->status = buf[3]; + update_doc_size (hw->fbf, buf[8] << 8 | buf[7]); + } + if (hw->adf) + { + hw->adf->status = buf[1]; + hw->adf->ext_status = buf[10]; + update_doc_size (hw->adf, buf[6] << 8 | buf[5]); + } + if (hw->tpu) + { + hw->tpu->status = buf[2]; + } + + return SANE_STATUS_GOOD; +} + +static SANE_Int +buf_to_sane_int (const byte *p, const char *var) +{ + SANE_Int result = 0; + + require (p); + result = buf_to_uint32 (p); + + if (SANE_INT_MAX < result) + { + err_major ("overflow: %s", var); + result = SANE_INT_MAX; + } + return result; +} + +SANE_Status +cmd_request_extended_identity (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { FS, 'I' }; + byte buf[80]; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, buf, num_of (buf), &status); + + hw->cmd_lvl[0] = buf[0]; + hw->cmd_lvl[1] = buf[1]; + + hw->version[0] = buf[62]; + hw->version[1] = buf[63]; + hw->version[2] = buf[64]; + hw->version[3] = buf[65]; + + hw->fsi_cap_1 = buf[44]; + hw->fsi_cap_2 = buf[45]; + hw->fsi_cap_3 = buf[76]; + { /* transfer corresponding flags */ + byte mask = (EXT_STATUS_NO_FBF | EXT_STATUS_ADFT | EXT_STATUS_ADFS + | EXT_STATUS_ADFO | EXT_STATUS_LID | EXT_STATUS_PB); + hw->ext_status &= ~mask; + hw->ext_status |= (mask & hw->fsi_cap_1); + } + + if (!(EXT_STATUS_NO_FBF & hw->fsi_cap_1) && !hw->fbf) + { + hw->fbf = t_calloc (1, fbf_extension); + if (!hw->fbf) return SANE_STATUS_NO_MEM; + } + + hw->cmd->request_push_button_status + = ((EXT_STATUS_PB & hw->fsi_cap_1) ? '!' : 0); + + hw->base_res = buf_to_sane_int (buf + 4, "base resolution"); + + hw->dpi_range.min = buf_to_sane_int (buf + 8, "minimum resolution"); + hw->dpi_range.max = buf_to_sane_int (buf + 12, "maixmum resolution"); + hw->dpi_range.quant = 1; + + hw->scan_width_limit = buf_to_sane_int (buf + 16, "scan width"); + + if (hw->fbf) + { + hw->fbf->max_x = buf_to_sane_int (buf + 20, "fbf max_x"); + hw->fbf->max_y = buf_to_sane_int (buf + 24, "fbf max_y"); + update_ranges (hw, hw->fbf); + } + if (hw->adf) + { + hw->adf->max_x = buf_to_sane_int (buf + 28, "adf max_x"); + hw->adf->max_y = buf_to_sane_int (buf + 32, "adf max_y"); + update_ranges (hw, hw->adf); + } + if (hw->tpu) + { + hw->tpu->max_x = buf_to_sane_int (buf + 36, "tpu max_x"); + hw->tpu->max_y = buf_to_sane_int (buf + 40, "tpu max_y"); + update_ranges (hw, hw->tpu); + } + + return SANE_STATUS_GOOD; +} + +static void +fixme_request_extended_status (const char *fw_name, byte *buf, size_t size) +{ + if (!fw_name) return; + if (!buf) return; + + if (0 == strcmp_c ("GT-8200", fw_name) && 15 < size) + { + uint16_t max_x; + uint16_t max_y; + + max_x = buf[13] << 8 | buf[12]; + max_y = buf[15] << 8 | buf[14]; + if (max_y < max_x) + { + err_minor ("Fixing up buggy FBF max scan dimensions."); + max_y *= 2; + buf[14] = 0xFF & max_y; + buf[15] = 0xFF & (max_y >> 8); + } + + max_x = buf[ 8] << 8 | buf[7]; + max_y = buf[10] << 8 | buf[9]; + if (max_y < max_x) + { + err_minor ("Fixing up buggy TPU max scan dimensions."); + max_y *= 2; + buf[ 9] = 0xFF & max_y; + buf[10] = 0xFF & (max_y >> 8); + } + } + + if ((0 == strcmp_c ("ES-9000H", fw_name) || + 0 == strcmp_c ("GT-30000", fw_name)) + && 5 < size) + { + err_minor ("Fixing up buggy ADF max scan dimensions."); + buf[2] = 0xB0; + buf[3] = 0x6D; + buf[4] = 0x60; + buf[5] = 0x9F; + } +} + +/*! Updates the extended status of a hardware device object. + */ +SANE_Status +cmd_request_extended_status (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, 'f' }; + byte info[4]; + + byte *data = NULL; + size_t size; + + const size_t DEVNAME_OFFSET = 26; + + const byte DEVT_MASK = 0xC0; + const byte DEVTYPE_3 = 0xC0; + + log_call (); + require (hw); + + if (!hw->cmd->request_extended_status) + return SANE_STATUS_UNSUPPORTED; + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, info, num_of (info), &status); + + hw->status = info[1]; + size = info[3] << 8 | info[2]; + + require (DEVNAME_OFFSET + DEVNAME_LENGTH <= size); + + if (0 < size) + { + data = t_calloc (size, byte); + if (!data) + { + return SANE_STATUS_NO_MEM; + } + + channel_recv (hw->channel, data, size, &status); + if (SANE_STATUS_GOOD == status) + { + fixme_request_extended_status (hw->fw_name, data, size); + + hw->ext_status = data[0]; + + hw->cmd->request_push_button_status + = ((EXT_STATUS_PB & data[0]) ? '!' : 0); + + if (!(EXT_STATUS_NO_FBF & data[0]) && !hw->fbf) + { + hw->fbf = t_calloc (1, fbf_extension); + if (!hw->fbf) status = SANE_STATUS_NO_MEM; + } + if ((ADF_STATUS_IST & data[1]) && !hw->adf) + { + hw->adf = t_calloc (1, adf_extension); + if (!hw->adf) status = SANE_STATUS_NO_MEM; + } + if ((TPU_STATUS_IST & data[6]) && !hw->tpu) + { + hw->tpu = t_calloc (1, tpu_extension); + if (!hw->tpu) status = SANE_STATUS_NO_MEM; + } + + if (hw->fbf) + { + hw->fbf->status = 0x00; + if (DEVTYPE_3 == (DEVT_MASK & data[11])) + { + hw->fbf->status = data[11]; + hw->fbf->max_x = data[13] << 8 | data[12]; + hw->fbf->max_y = data[15] << 8 | data[14]; + } + else + { + hw->fbf->max_x = hw->max_x; + hw->fbf->max_y = hw->max_y; + } + update_ranges (hw, hw->fbf); + update_doc_size (hw->fbf, data[19] << 8 | data[18]); + } + if (hw->adf) + { + hw->adf->status = data[1]; + hw->adf->max_x = data[3] << 8 | data[2]; + hw->adf->max_y = data[5] << 8 | data[4]; + update_ranges (hw, hw->adf); + update_doc_size (hw->adf, data[17] << 8 | data[16]); + } + if (hw->tpu) + { + hw->tpu->status = data[6]; + hw->tpu->max_x = data[ 8] << 8 | data[7]; + hw->tpu->max_y = data[10] << 8 | data[9]; + update_ranges (hw, hw->tpu); + } + } + delete (data); + } + + return status; +} + + +/*! Sets the option unit to use as well as the unit's behaviour. + + \todo Implement error checking. + */ +SANE_Status +cmd_control_option_unit (device *hw, byte value) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, 'e' }; + byte reply = NUL; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, &reply, 1, &status); + channel_send (hw->channel, &value, 1, &status); + channel_recv (hw->channel, &reply, 1, &status); + + return status; +} + + +/*! Resets the device to a well known state. + + \todo Implement error checking. + */ +SANE_Status +cmd_initialize (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, '@' }; + byte reply = NUL; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, &reply, 1, &status); + + return status; +} + +/*! Loads a sheet on a page type ADF extension. + + \todo Implement error checking. + */ +SANE_Status +cmd_load_paper (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { PF }; + byte reply = NUL; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, &reply, 1, &status); + + return status; +} + +/*! Ejects sheets from the ADF extension. + + \todo Implement error checking. + */ +SANE_Status +cmd_eject_paper (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { FF }; + byte reply = NUL; + + log_call (); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + channel_recv (hw->channel, &reply, 1, &status); + + return status; +} + +SANE_Status +cmd_lock (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, '(' }; + byte reply = NUL; + + log_call(); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &reply, 1, &status); + + if (SANE_STATUS_GOOD == status) + { + if (0x80 == reply) + { + hw->is_locked = true; + } + else if (0x40 == reply) + { + err_minor ("failed to acquire lock"); + status = SANE_STATUS_DEVICE_BUSY; + } + else if (NAK == reply) + { + err_minor ("locking not supported by device, disabling"); + hw->uses_locking = false; + status = SANE_STATUS_GOOD; + } + else + { + err_major ("unexpected reply to lock command (%02x)", reply); + status = SANE_STATUS_IO_ERROR; + } + } + return status; +} + +SANE_Status +cmd_unlock (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, ')' }; + byte reply = NUL; + + log_call(); + require (hw); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &reply, 1, &status); + + if (SANE_STATUS_GOOD == status) + { + if (0x80 == reply) + { + hw->is_locked = false; + } + else if (NAK == reply) + { + err_minor ("locking not supported by device, disabling"); + hw->uses_locking = false; + status = SANE_STATUS_GOOD; + } + else + { + err_major ("unexpected reply to unlock command (%02x)", reply); + status = SANE_STATUS_IO_ERROR; + } + } + + return status; +} + +SANE_Status +cmd_request_scanner_maintenance (device *hw, uint16_t mode) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const byte cmd[] = { ESC, '1' }; + byte param[8]; + byte reply = NUL; + + log_call ("(%04x)", mode); + require (hw); + + memset (param, 0, sizeof (param)); + uint16_to_buf (mode, param); + + channel_send (hw->channel, cmd, num_of (cmd), &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &reply, sizeof (reply), &status); + if (SANE_STATUS_GOOD != status) return status; + + if (ACK != reply) + { + err_major ("unexpected reply to maintenance command (%02x)", reply); + return SANE_STATUS_IO_ERROR; + } + + channel_send (hw->channel, (const byte *)param, num_of (param), &status); + if (SANE_STATUS_GOOD != status) return status; + channel_recv (hw->channel, &reply, sizeof (reply), &status); + if (SANE_STATUS_GOOD != status) return status; + + if (BUSY == reply) + { + status = SANE_STATUS_DEVICE_BUSY; + } + else if (NAK == reply) + { + err_minor ("invalid maintenance command (%04x)", mode); + status = SANE_STATUS_INVAL; + } + else if (ACK != reply) + { + err_major ("unexpected reply to maintenance command (mode=%04x, %02x)", + mode, reply); + status = SANE_STATUS_IO_ERROR; + } + return status; +} diff --git a/backend/command.h b/backend/command.h new file mode 100644 index 0000000..774984c --- /dev/null +++ b/backend/command.h @@ -0,0 +1,223 @@ +/* command.h -- assorted ESC/I protocol commands + * Copyright (C) 2008--2014 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef command_h_included +#define command_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +/* ESC/I protocol constants. + */ + +/* Readability definitions for selected ASCII codes used by the ESC/I + protocol. + */ +#define NUL 0x00 /* '\0' */ +#define STX 0x02 +#define ACK 0x06 +#define BUSY 0x07 /* BEL in ascii(7) */ +#define FF 0x0c /* '\f' */ +#define NAK 0x15 +#define CAN 0x18 +#define PF 0x19 /* EM in ascii(7) */ +#define ESC 0x1b +#define FS 0x1c + + +/* Bit flags for the status byte in the information block. + */ +#define STATUS_FATAL_ERROR 0x80 +#define STATUS_NOT_READY 0x40 /* in use via other interface */ +#define STATUS_AREA_END 0x20 /* scan finished */ +#define STATUS_OPTION 0x10 /* option detected */ +#define STATUS_COLOR_ATTR_MASK 0x0c +#define STATUS_EXT_COMMANDS 0x02 /* FS commands supported */ + +/* Bit flags for the extended image acquisition (FS G) error byte + */ +#define FSG_FATAL_ERROR 0x80 +#define FSG_NOT_READY 0x40 /* in use via other interface */ +#define FSG_PAGE_END 0x20 /* notify of the paper end */ +#define FSG_CANCEL_REQUEST 0x10 /* cancel request from scanner */ + +/* Bit flags for selected bytes in the extended status and scanner + status replies. Synchronised status flags are defined in terms + of the corresponding flag. All other values are supposed to be + unlinked. The various *_ERR flags indicate that at least one of + the other error bit flags in the _same_ block is set. + + The EXT_STATUS_* flags are specific to the ESC f command's reply, + The FSF_STATUS_*, ADF_EXT_STATUS_* and AFL_STATUS_* flags to that + of the FS F command. + */ +#define EXT_STATUS_FER 0x80 /* fatal error */ +#define EXT_STATUS_NO_FBF 0x40 /* no flat bed */ +#define EXT_STATUS_ADFT 0x20 /* ADF unit type */ +#define EXT_STATUS_ADFS 0x10 /* simplex/duplex */ +#define EXT_STATUS_ADFO 0x08 /* feed from first/last sheet */ +#define EXT_STATUS_LID 0x04 /* lid type option */ +#define EXT_STATUS_WU 0x02 /* warming up */ +#define EXT_STATUS_PB 0x01 /* scanner has a push button */ + +#define FSF_STATUS_FER 0x80 /* fatal error */ +#define FSF_STATUS_NR 0x40 /* in use via other interface */ +#define FSF_STATUS_WU 0x02 /* warming up */ +#define FSF_STATUS_CWU 0x01 /* can cancel warming up */ + +#define ADF_STATUS_IST 0x80 /* option detected */ +#define ADF_STATUS_EN 0x40 /* option enabled */ +#define ADF_STATUS_ERR 0x20 /* option error detected */ +#define ADF_STATUS_ATYP 0x10 /* photo ADF type detected */ +#define ADF_STATUS_PE 0x08 /* no paper */ +#define ADF_STATUS_PJ 0x04 /* paper jam */ +#define ADF_STATUS_OPN 0x02 /* cover open */ +#define ADF_STATUS_PAG 0x01 /* duplex selected */ + +#define ADF_EXT_STATUS_IST ADF_STATUS_IST +#define ADF_EXT_STATUS_EN ADF_STATUS_EN +#define ADF_EXT_STATUS_ERR 0x20 /* option error detected */ +#define ADF_EXT_STATUS_DFE 0x10 /* double feed */ +#define ADF_EXT_STATUS_TR_OPN 0x02 /* tray open */ + +#define TPU_STATUS_IST 0x80 /* option detected */ +#define TPU_STATUS_EN 0x40 /* option enabled */ +#define TPU_STATUS_ERR 0x20 /* option error detected */ +#define TPU_STATUS_OPN 0x02 /* cover open */ +#define TPU_STATUS_LTF 0x01 /* lamp luminescence too low */ + +#define AFL_STATUS_IST 0x80 /* option detected */ +#define AFL_STATUS_EN 0x40 /* option enabled */ +#define AFL_STATUS_ERR 0x20 /* option error detected */ +#define AFL_STATUS_OPN 0x02 /* cover open */ +#define AFL_STATUS_LTF 0x01 /* lamp luminescence too low */ + +#define DV3_STATUS_ERR 0x20 +#define DV3_STATUS_PE 0x08 /* no paper */ +#define DV3_STATUS_PJ 0x04 /* paper jam */ +#define DV3_STATUS_OPN 0x02 /* cover open */ + +/* Bit flags + */ +#define FSI_CAP_DLF 0x80 /* ??? */ +#define FSI_CAP_NO_FBF 0x40 /* no flat bed */ +#define FSI_CAP_ADFT 0x20 /* ADF unit type */ +#define FSI_CAP_ADFS 0x10 /* simplex/duplex */ +#define FSI_CAP_ADFO 0x08 /* feed from first/last sheet */ +#define FSI_CAP_LID 0x04 /* lid type option */ +#define FSI_CAP_TPIR 0x02 /* TPU with IR support */ +#define FSI_CAP_PB 0x01 /* scanner has a push button */ + +#define FSI_CAP_ADFAS 0x10 /* ADF with auto scan support */ +#define FSI_CAP_DFD 0x08 /* double feed detection */ +#define FSI_CAP_AFF 0x04 /* auto form feed */ +#define FSI_CAP_ESST 0x02 /* ??? */ +#define FSI_CAP_PED 0x01 /* paper end detection support */ + +#define FSI_CAP_DPOS_MASK 0x03 /* the document position of the ADF */ + +/* DPOS status */ +#define FSI_CAP_DPOS_UNKN 0x00 /* no information */ +#define FSI_CAP_DPOS_LEFT 0x01 /* left side position */ +#define FSI_CAP_DPOS_CNTR 0x02 /* center position */ +#define FSI_CAP_DPOS_RIGT 0x03 /* right position */ + +/* Maintenance requests. + */ +#define ESC1_REQ_CLEANING 0x0001 +#define ESC1_REQ_CALIBRATION 0x0002 +#define ESC1_REQ_STATUS 0xffff + +#include "device.h" + + +/* Getter commands. + */ +SANE_Status cmd_request_identity (device *hw); +SANE_Status cmd_request_hardware_property (device *hw); +SANE_Status cmd_request_scanner_status (device *hw); +SANE_Status cmd_request_extended_identity (device *hw); +SANE_Status cmd_request_extended_status (device *hw); +SANE_Status cmd_request_scanning_parameter (device* hw); + + +/* Setter commands. + */ +SANE_Status cmd_control_option_unit (device *hw, byte value); +SANE_Status cmd_set_scanning_parameter (device* hw); + + +/* Action commands. + */ +SANE_Status cmd_initialize (device *hw); +SANE_Status cmd_load_paper (device *hw); +SANE_Status cmd_eject_paper (device *hw); +SANE_Status cmd_lock (device *hw); +SANE_Status cmd_unlock (device *hw); +SANE_Status cmd_request_scanner_maintenance (device *hw, uint16_t mode); + + +#endif /* !defined (command_h_included) */ diff --git a/backend/defines.h b/backend/defines.h new file mode 100644 index 0000000..9940b7b --- /dev/null +++ b/backend/defines.h @@ -0,0 +1,104 @@ +/* defines.h -- a grab bag of preprocessor utilities + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + + +#ifndef defines_h +#define defines_h + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Assorted constants. + */ + +#define MM_PER_INCH 25.4 /* exactly */ + + +#ifndef __cplusplus +/*! A C++ Boolean type and corresponding keywords for our C code. + */ +typedef enum { + false = 0, + true +} bool; +#endif + + +/* Run-time contract validation. + */ +#include <stdlib.h> +#include "message.h" + +#define _assert(type,condition) \ + if (!(condition)) \ + { \ + err_fatal ("failed: %s (%s)", type, #condition); \ + exit (EXIT_FAILURE); \ + } +#define require(condition) _assert ("require", condition) +#define promise(condition) _assert ("promise", condition) + + +/* "Typed" memory allocation convenience wrappers. + * These are meant to make the invocations consistent and take care of + * the casting for you. + */ +#include <alloca.h> +#include <stdlib.h> + +#define t_alloca(sz,t) ((t *) alloca ((sz) * sizeof (t))) +#define t_calloc(sz,t) ((t *) calloc ((sz) , sizeof (t))) +#define t_malloc(sz,t) ((t *) malloc ((sz) * sizeof (t))) +#define t_realloc(p,sz,t) ((t *) realloc ((p), (sz) * sizeof (t))) + +/* Compute sizes of _statically_ allocated arrays easily. + */ +#define num_of(p) (sizeof (p) / sizeof (*p)) + +/* Safely release acquired resources. + * The const_delete() is meant for those rare cases where you need to + * clean up const t* typed memory areas. + */ +#define delete(p) do { if (p) free (p); p = 0; } while (0) +#define const_delete(p,t) do { if (p) free ((t) p); p = 0; } while (0) + + +/* Portable path and file name component separators. + */ +#ifdef __unix +#define PATH_SEP_STR ":" +#define PATH_SEP PATH_SEP_STR[0] +#define FILE_SEP_STR "/" +#define FILE_SEP FILE_SEP_STR[0] +#else +#define PATH_SEP_STR ";" +#define PATH_SEP PATH_SEP_STR[0] +#define FILE_SEP_STR "\\" +#define FILE_SEP FILE_SEP_STR[0] +#endif + + + typedef unsigned char byte; + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (defines_h) */ diff --git a/backend/device.c b/backend/device.c new file mode 100644 index 0000000..13fdc65 --- /dev/null +++ b/backend/device.c @@ -0,0 +1,563 @@ +/* device.c -- physical device representation + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a hardware device object. + + The hardware device object is built on top of the ESC/I commands. + Whereas the commands are only responsible for the I/O details and + the encoding and decoding of parameters, hardware device objects + handle the protocol logic and keep state. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> + +#include "command.h" +#include "hw-data.h" +#include "utils.h" + + +/*! Releases all resources associated with a scanner device. + * + * The channel will be closed if open and acquired memory is returned + * to the operating system. + */ +device * +dev_dtor (device *hw) +{ + if (!hw) return hw; + + hw->channel = hw->channel->dtor (hw->channel); + + delete (hw->fbf); + delete (hw->adf); + delete (hw->tpu); + delete (hw->fw_name); + if (hw->res_y.list != hw->res.list + && hw->res_y.list != hw->res_x.list) + { + delete (hw->res_y.list); + } + if (hw->res_x.list != hw->res.list) + { + delete (hw->res_x.list); + } + delete (hw->res.list); + delete (hw->resolution.list); + delete (hw); + + return hw; +} + +/*! Updates the extended status of a hardware device object. + */ +SANE_Status +dev_request_extended_status (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + if (hw->using_fs) + { + status = cmd_request_scanner_status (hw); + if (SANE_STATUS_GOOD != status) return status; + + status = cmd_request_extended_identity (hw); + if (SANE_STATUS_GOOD != status) return status; + + status = cmd_request_scanner_status (hw); + } + else + status = cmd_request_extended_status (hw); + + return status; +} + +#include <string.h> +/*! Loads a sheet on a page type ADF extension. + */ +SANE_Status +dev_load_paper (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + if (!hw->adf) return status; /* guard clauses */ + if (!(ADF_STATUS_IST & hw->adf->status)) return status; + if (!(ADF_STATUS_EN & hw->adf->status)) return status; + hw->adf->sheet_count++; + if (!(EXT_STATUS_ADFT & hw->ext_status)) return status; + + log_call (); + + status = cmd_load_paper (hw); + if (SANE_STATUS_GOOD != status) + { + hw->adf->sheet_count--; + return status; + } + log_info ("loaded sheet #%d", hw->adf->sheet_count); + + status = dev_request_extended_status (hw); + + if (ADF_STATUS_PE & hw->adf->status + && adf_early_paper_end_kills_scan (hw)) + { /* so we can scan the last sheet */ + cmd_control_option_unit (hw, 0x00); + hw->adf->status &= ~ ADF_STATUS_EN; + } + + /* Clear the ADF_STATUS_PE bit. We just successfully loaded a + sheet. Also update the ADF_STATUS_ERR bit. + */ + hw->adf->status &= ~ADF_STATUS_PE; + if ( (ADF_STATUS_PE & hw->adf->status) + || (ADF_STATUS_PJ & hw->adf->status) + || (ADF_STATUS_OPN & hw->adf->status)) + hw->adf->status |= ADF_STATUS_ERR; + else + hw->adf->status &= ~ADF_STATUS_ERR; + + return status; +} + +/*! Ejects sheets from the ADF extension. + */ +SANE_Status +dev_eject_paper (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + if (!hw->adf) return status; /* guard clauses */ + if (!(ADF_STATUS_IST & hw->adf->status)) return status; + if (!(ADF_STATUS_EN & hw->adf->status)) return status; + + log_call (); + + status = cmd_eject_paper (hw); + hw->adf->sheet_count = 0; + + return status; +} + +/*! Open the scanner device. Depending on the connection method, + * different open functions are called. + */ +SANE_Status +dev_open (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + require (hw->channel); + + if (hw->channel->is_open (hw->channel)) + { + log_info ("scanner is already open: fd = %d", hw->channel->fd); + return SANE_STATUS_GOOD; /* no need to open the scanner */ + } + + hw->channel->open (hw->channel, &status); + + return status; +} + +/*! Log extended (FS W) parameter settings + */ +SANE_Status +dev_log_scanning_parameter (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + byte *buf = hw->param_buf; /* for convenience */ + + log_info ("SANE_START: Color: %d", (int) buf[24]); + log_info ("SANE_START: Resolution (x, y): (%u, %u)", + buf_to_uint32 (buf+0), buf_to_uint32 (buf+4)); + log_info ("SANE_START: Scan offset (x, y): (%d, %d)", + buf_to_uint32 (buf+8), buf_to_uint32 (buf+12)); + log_info ("SANE_START: Scan size (w, h): (%d, %d)", + buf_to_uint32 (buf+16), buf_to_uint32 (buf+20)); + log_info ("SANE_START: Data format: %d", (int) buf[25]); + log_info ("SANE_START: Halftone: %d", (int) buf[32]); + log_info ("SANE_START: Brightness: %d", (int) buf[30]); + log_info ("SANE_START: Gamma: %d", (int) buf[29]); + log_info ("SANE_START: Color correction: %d", (int) buf[31]); + log_info ("SANE_START: Sharpness control: %d", (int) buf[35]); + log_info ("SANE_START: Scanning mode: %d", (int) buf[27]); + log_info ("SANE_START: Mirroring: %d", (int) buf[36]); + log_info ("SANE_START: Auto area segmentation: %d", (int) buf[34]); + log_info ("SANE_START: Threshold: %d", (int) buf[33]); + log_info ("SANE_START: Line counter: %d", (int) buf[28]); + log_info ("SANE_START: Option unit control: %d", (int) buf[26]); + log_info ("SANE_START: Film type: %d", (int) buf[37]); + + return status; +} + +/*! Obtain the offset and size of the parameter corresponding + * to the given ESC command in the FS W packet structure. + * Return true if the given command is in the FS W packet + * structure and obtaining the parameter info succeeded, + * otherwise return false. + */ +static bool +get_extended_param_info (byte cmd, short *offset, short *size) +{ + if (NULL == offset) return false; + if (NULL == size) return true; + + *offset = 0; + *size = 1; /* default size of most parameters */ + + if ('R' == cmd) { *offset = 0; *size = 8; } + else if ('A' == cmd) { *offset = 8; *size = 16; } + else if ('C' == cmd) *offset = 24; + else if ('D' == cmd) *offset = 25; + else if ('e' == cmd) *offset = 26; + else if ('g' == cmd) *offset = 27; + else if ('d' == cmd) *offset = 28; + else if ('Z' == cmd) *offset = 29; + else if ('L' == cmd) *offset = 30; + else if ('M' == cmd) *offset = 31; + else if ('B' == cmd) *offset = 32; + else if ('t' == cmd) *offset = 33; + else if ('s' == cmd) *offset = 34; + else if ('Q' == cmd) *offset = 35; + else if ('K' == cmd) *offset = 36; + else if ('N' == cmd) *offset = 37; + else *size = 0; + + if (0 == *size) return false; + return true; +} + +/*! Copies a number of bytes from \a param into the FS W packet buffer. + * The size and offset are looked up based on the \a cmd passed. + */ +SANE_Status +dev_set_scanning_parameter (device *hw, byte cmd, const byte* param) +{ + short offset = 0; + short size = 0; + + log_call (); + require (hw); + + if (NULL == param) return SANE_STATUS_INVAL; + if (!get_extended_param_info (cmd, &offset, &size)) return SANE_STATUS_INVAL; + + memcpy (hw->param_buf + offset, param, size); + + return SANE_STATUS_GOOD; +} + +SANE_Status +dev_set_scanning_resolution (device *hw, SANE_Int x_dpi, SANE_Int y_dpi) +{ + byte buf[8]; + uint32_t x; + uint32_t y; + + log_call (); + require (hw); + + if (x_dpi < 0 || x_dpi > UINT32_MAX) return SANE_STATUS_INVAL; + if (y_dpi < 0 || y_dpi > UINT32_MAX) return SANE_STATUS_INVAL; + + x = x_dpi; + y = y_dpi; + + uint32_to_buf (x, buf + 0); + uint32_to_buf (y, buf + 4); + + return dev_set_scanning_parameter (hw, 'R', buf); +} + +SANE_Status +dev_set_scanning_area (device *hw, SANE_Int left, SANE_Int top, + SANE_Int width, SANE_Int height) +{ + byte buf[16]; + uint32_t nx; + uint32_t ny; + uint32_t nw; + uint32_t nh; + + log_call (); + require (hw); + + if (left > UINT32_MAX) return SANE_STATUS_INVAL; + if (top > UINT32_MAX) return SANE_STATUS_INVAL; + if (width > UINT32_MAX) return SANE_STATUS_INVAL; + if (height > UINT32_MAX) return SANE_STATUS_INVAL; + + nx = left; + ny = top; + nw = width; + nh = height; + + uint32_to_buf (nx, buf + 0); + uint32_to_buf (ny, buf + 4); + uint32_to_buf (nw, buf + 8); + uint32_to_buf (nh, buf + 12); + + return dev_set_scanning_parameter (hw, 'A', buf); +} + +SANE_Status +dev_set_option_unit (device *hw, byte adf_mode) +{ + byte val = 0; + + log_call (); + require (hw); + + val = (using (hw, fbf) ? 0 : 1); + if (hw->adf && 1 == val) + val += adf_mode; /* set simplex/duplex */ + + return dev_set_scanning_parameter (hw, 'e', &val); +} + +static void +limit_res_list (resolution_info *res, int limit) +{ + int i = 0; + int new_size = 0; + + for(i=1; i<res->size; i++) // first entry is the list size, skip it + { + if (res->list[i] > limit) break; + ++new_size; + } + + res->list[0] = new_size; + res->size = new_size; + res->last = 0; + + log_info ("Limit resolution to %ddpi", res->list[res->size]); +} + +void +dev_limit_res (device *self, SANE_Constraint_Type type, int limit) +{ + if (SANE_CONSTRAINT_RANGE == type) + { + self->old_max = self->dpi_range.max; + self->dpi_range.max = limit; + } + else + { + self->old_max = self->res.size; + limit_res_list (&self->res, limit); + limit_res_list (&self->res_x, limit); + limit_res_list (&self->res_y, limit); + } +} + +void +dev_restore_res (device *self, SANE_Constraint_Type type) +{ + if (0 == self->old_max) return; + + if (SANE_CONSTRAINT_RANGE == type) + { + self->dpi_range.max = self->old_max; + } + else + { + // res_x and res_y are recreated, only restore res + self->res.size = self->old_max; + self->res.list[0] = self->old_max; + self->res.last = 0; + } +} + +/*! Sends a cancel to the device when not in the middle of retrieving scan data. + Return true if a cancel command was successfully sent, false, if not. + */ +bool +dev_force_cancel (device *self) +{ + u_char buf[14]; + u_char params[2]; + u_char *dummy = NULL; + uint32_t block_size = 0; + SANE_Status status = SANE_STATUS_GOOD; + + if (!self->using_fs) return false; + + params[0] = FS; + params[1] = self->cmd->start_scanning; + + channel_send (self->channel, params, 2, &status); + if (SANE_STATUS_GOOD != status) return false; + + channel_recv (self->channel, buf, num_of (buf), &status); + if (SANE_STATUS_GOOD != status) return false; + if (STX != buf[0]) return false; + + block_size = buf_to_uint32 (buf + 2); + + dummy = t_malloc (block_size, u_char); + if (dummy == NULL) + { + err_fatal ("%s", strerror (errno)); + return false; + } + + channel_recv_all (self->channel, dummy, + block_size, &status); + delete (dummy); + + if (SANE_STATUS_GOOD != status) return false; + + buf[0] = CAN; + channel_send (self->channel, buf, 1, &status); + if (SANE_STATUS_GOOD != status) return false; + + channel_recv (self->channel, buf, 1, &status); + if (SANE_STATUS_GOOD != status) return false; + if (ACK != buf[0]) return false; + + return true; +} + +SANE_Status +dev_lock (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + require (hw); + + if (!hw->uses_locking) return status; + if ( hw->is_locked) return status; + + return cmd_lock (hw); +} + +SANE_Status +dev_unlock (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + require (hw); + + if(!hw->uses_locking) return status; + if(!hw->is_locked) return status; + + return cmd_unlock (hw); +} + +static SANE_Status +dev_maintenance (device *hw, uint16_t mode) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call ("(%04x)", mode); + require (hw); + if (!maintenance_is_supported (hw)) + { + return SANE_STATUS_UNSUPPORTED; + } + if ( ESC1_REQ_CLEANING != mode + && ESC1_REQ_CALIBRATION != mode) + { + return SANE_STATUS_INVAL; + } + + status = cmd_request_scanner_maintenance (hw, mode); + if (SANE_STATUS_GOOD == status) + { + do + { + microsleep (hw->polling_time); + status = cmd_request_scanner_maintenance (hw, ESC1_REQ_STATUS); + } while (SANE_STATUS_DEVICE_BUSY == status); + } + + return status; +} + +/* convenience function */ +SANE_Status +dev_clean (device *hw) +{ + return dev_maintenance (hw, ESC1_REQ_CLEANING); +} + +/* convenience function */ +SANE_Status +dev_calibrate (device *hw) +{ + return dev_maintenance (hw, ESC1_REQ_CALIBRATION); +} diff --git a/backend/device.h b/backend/device.h new file mode 100644 index 0000000..739429e --- /dev/null +++ b/backend/device.h @@ -0,0 +1,265 @@ +/* device.h -- physical device representation + * Copyright (C) 2008, 2009, 2014 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef device_h_included +#define device_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include "channel.h" +#include "extension.h" + +#define DEVNAME_LENGTH 16 + + +typedef struct +{ + char *level; + + unsigned char request_identity; + unsigned char request_identity2; /* new request identity command for Dx command level */ + unsigned char request_status; + unsigned char request_condition; + unsigned char set_color_mode; + unsigned char start_scanning; + unsigned char set_data_format; + unsigned char set_resolution; + unsigned char set_zoom; + unsigned char set_scan_area; + unsigned char set_bright; + SANE_Range bright_range; + unsigned char set_gamma; + unsigned char set_halftoning; + unsigned char set_color_correction; + unsigned char initialize_scanner; + unsigned char set_speed; /* B4 and later */ + unsigned char set_lcount; + unsigned char mirror_image; /* B5 and later */ + unsigned char set_gamma_table; /* B4 and later */ + unsigned char set_outline_emphasis; /* B4 and later */ + unsigned char set_dither; /* B4 and later */ + unsigned char set_color_correction_coefficients; /* B3 and later */ + unsigned char request_extended_status; /* get extended status from scanner */ + unsigned char control_an_extension; /* for extension control */ + unsigned char eject; /* for extension control */ + unsigned char feed; + unsigned char request_push_button_status; + unsigned char control_auto_area_segmentation; + unsigned char set_film_type; /* for extension control */ + unsigned char set_exposure_time; /* F5 only */ + unsigned char set_bay; /* F5 only */ + unsigned char set_threshold; + unsigned char set_focus_position; /* B8 only */ + unsigned char request_focus_position; /* B8 only */ +} EpsonCmdRec, *EpsonCmd; + +typedef struct +{ + SANE_Int last; + SANE_Int size; + SANE_Word *list; + SANE_Bool deep; +} resolution_info; + +typedef struct +{ + int modelID; + + double color_profile[4][9]; +} EpsonScanHardRec, *EpsonScanHard; + + +/*! Software abstraction of the \e physical device. + */ +struct device +{ + struct channel *channel; /*<! for all device I/O */ + + char cmd_lvl[2+1]; /* +1 for string termination */ + char version[4+1]; /* +1 for string termination */ + char *fw_name; /*!< model name reported by firmware */ + + SANE_Byte status; /*!< info block status byte */ + SANE_Byte ext_status; + SANE_Byte fsf_status; + SANE_Byte fsi_cap_1; + SANE_Byte fsi_cap_2; + SANE_Byte fsi_cap_3; + + const extension *src; /*!< current document source */ + fbf_extension *fbf; /*!< \c NULL if not available */ + adf_extension *adf; /*!< \c NULL if not available */ + tpu_extension *tpu; /*!< \c NULL if not available */ + + /*! \brief Selectable scan sources. + * A \c NULL terminated list of up to three scan sources. Which + * ones are listed depends on device capabilities. + */ + SANE_String_Const sources[4]; + + resolution_info resolution; /* full-blown list */ + resolution_info res; /* advertised list */ + SANE_Int max_x; /* in pixels */ + SANE_Int max_y; + + SANE_Int optical_res; /* resolution in pixels per inch */ + SANE_Byte sensor_info; + SANE_Byte scan_order; + SANE_Byte line_dist_x; + SANE_Byte line_dist_y; + resolution_info res_x; + resolution_info res_y; + + + SANE_Int level; + SANE_Range dpi_range; + + SANE_Range matrix_range; + + const int *gamma_type; + const SANE_Bool *gamma_user_defined; + + const int *color_type; + const SANE_Bool *color_user_defined; + + SANE_Bool color_shuffle; /* does this scanner need color shuffling */ + SANE_Int maxDepth; /* max. color depth */ + + SANE_Int max_line_distance; + + SANE_Bool need_color_reorder; + SANE_Bool need_reset_on_source_change; + + SANE_Bool wait_for_button; /* do we have to wait until the scanner button is pressed? */ + + SANE_Int doctype; + + unsigned int productID; + + EpsonCmd cmd; + const EpsonScanHardRec *scan_hard; + + SANE_Bool using_fs; /*! determines whether to use fs commands */ + + SANE_Bool block_mode; + uint32_t image_block_size; + uint32_t final_block_size; + uint32_t block_total; /* does NOT include final block! */ + uint32_t block_count; + + unsigned char param_buf[64]; /*! holds the FS W parameter buffer */ + + SANE_Int scan_width_limit; + + SANE_Int base_res; /*! resolution at which the max scan area is defined */ + + SANE_Int old_max; /*! used to restore after limiting resolutions */ + + SANE_Int polling_time; + + bool uses_locking; + bool is_locked; +}; + +typedef struct device device; + +device *dev_dtor (device *hw); + +SANE_Status dev_request_extended_status (device *hw); +SANE_Status dev_load_paper (device *hw); +SANE_Status dev_eject_paper (device *hw); +SANE_Status dev_open (device *hw); + +/* FS W/S related functions */ +SANE_Status dev_log_scanning_parameter (device *hw); +SANE_Status dev_set_scanning_parameter (device *hw, + byte cmd, + const byte* param); +SANE_Status dev_set_scanning_resolution (device *hw, SANE_Int x_dpi, + SANE_Int y_dpi); +SANE_Status dev_set_scanning_area (device *hw, + SANE_Int left, SANE_Int top, + SANE_Int width, SANE_Int height); +SANE_Status dev_set_option_unit (device *hw, byte adf_mode); + +/* other */ + +/*! supports workarounds to temporarily limit resolution lists */ +void dev_limit_res (device *self, SANE_Constraint_Type type, int limit); + +/*! restore original resolution list after it has been limited */ +void dev_restore_res (device *self, SANE_Constraint_Type type); + +bool dev_force_cancel (device *self); + +SANE_Status dev_lock (device *hw); +SANE_Status dev_unlock (device *hw); + +SANE_Status dev_clean (device *hw); +SANE_Status dev_calibrate (device *hw); + +#endif /* !defined (device_h_included) */ diff --git a/backend/dip-obj.c b/backend/dip-obj.c new file mode 100644 index 0000000..021cb01 --- /dev/null +++ b/backend/dip-obj.c @@ -0,0 +1,746 @@ +/* dip-obj.c -- digital image processing functionality singleton + * Copyright (C) 2011 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> +#include <string.h> + +#include "dip-obj.h" +#include "defines.h" +#include "hw-data.h" +#include "include/sane/sanei_magic.h" +#include "ipc.h" + +#ifndef ENABLE_SANEI_MAGIC +#define ENABLE_SANEI_MAGIC 0 +#endif + +/*! \brief Constrain a value \a v in the interval \c [lo,hi] + */ +#define clamp(v,lo,hi) \ + ((v) < (lo) \ + ? (lo) \ + : ((v) > (hi) \ + ? (hi) \ + : (v))) \ + /**/ + +typedef struct +{ + process *plugin; + + void (*autocrop) (); + void (*deskew) (buffer *, int, int); + +} dip_type; + +static dip_type *dip = NULL; + +static void esdip_crop (buffer *buf, const device *hw, unsigned int count, + const Option_Value *val); +static void esdip_turn (buffer *buf, int res_x, int res_y); + +static void magic_crop (buffer *buf, int res_x, int res_y); +static void magic_turn (buffer *buf, int res_x, int res_y); + +void * +dip_init (const char *pkglibdir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + log_call ("(%s, %p)", pkglibdir, status); + + if (dip) + { + err_minor ("been here, done that"); + if (status) *status = s; + return dip; + } + + dip = t_calloc (1, dip_type); + if (!dip) + { + if (status) *status = SANE_STATUS_NO_MEM; + return dip; + } + + dip->plugin = ipc_exec ("esdip", pkglibdir, status); + + if (dip->plugin) + { + dip->autocrop = esdip_crop; + dip->deskew = esdip_turn; + } + else if (ENABLE_SANEI_MAGIC) /* use free alternative API */ + { + sanei_magic_init (); + dip->autocrop = magic_crop; + dip->deskew = magic_turn; + } + + if (status) *status = s; + return dip; +} + +void * +dip_exit (void *self) +{ + log_call ("(%p)", self); + require (dip == self); + + if (dip) + { + if (dip->plugin) + { + dip->plugin = ipc_kill (dip->plugin); + } + else + { + /* sanei_magic_exit, if there was one */ + } + delete (dip); + } + + return dip; +} + +bool +dip_needs_whole_image (const void *self, const Option_Value *val, + const SANE_Option_Descriptor *opt) +{ + require (dip == self && val); + + /* esdip_deskew and esdip_autocrop do not support this + */ + if (val[OPT_X_RESOLUTION].w != val[OPT_Y_RESOLUTION].w) + return false; + + return ((SANE_OPTION_IS_ACTIVE (opt[OPT_DESKEW].cap) + && val[OPT_DESKEW].b) + || (SANE_OPTION_IS_ACTIVE (opt[OPT_AUTOCROP].cap) + && val[OPT_AUTOCROP].b)); +} + +void +dip_apply_LUT (const void *self, const buffer *buf, + const LUT *m) +{ + require (dip == self && buf && m); + require (m->depth == buf->ctx.depth); + + /**/ if (16 == buf->ctx.depth) + { + uint16_t *p = (uint16_t *) buf->ptr; + uint16_t *e = (uint16_t *) buf->end; + + while (p < e) + { + *p = * (uint16_t *) (m->lut + 2 * *p); + ++p; + } + } + else if ( 8 == buf->ctx.depth) + { + SANE_Byte *p = buf->ptr; + SANE_Byte *e = buf->end; + + while (p < e) + { + *p = m->lut[*p]; + ++p; + } + } + else + err_major ("noop: unsupported bit depth %d", buf->ctx.depth); +} + +void +dip_apply_LUT_RGB (const void *self, const buffer *buf, + const LUT *r, const LUT *g, const LUT *b) +{ + require (dip == self && buf && r && g && b); + require (r->depth == buf->ctx.depth); + require (g->depth == buf->ctx.depth); + require (b->depth == buf->ctx.depth); + + if (SANE_FRAME_RGB != buf->ctx.format) + { + err_minor ("noop: image data not in RGB format"); + return; + } + + /**/ if (16 == buf->ctx.depth) + { + uint16_t *p = (uint16_t *) buf->ptr; + uint16_t *e = (uint16_t *) buf->end; + + while (p < e) + { + p[0] = * (uint16_t *) (r->lut + 2 * p[0]); + p[1] = * (uint16_t *) (g->lut + 2 * p[1]); + p[2] = * (uint16_t *) (b->lut + 2 * p[2]); + p += 3; + } + } + else if ( 8 == buf->ctx.depth) + { + SANE_Byte *p = buf->ptr; + SANE_Byte *e = buf->end; + + while (p < e) + { + p[0] = r->lut[p[0]]; + p[1] = g->lut[p[1]]; + p[2] = b->lut[p[2]]; + p += 3; + } + } + else + err_major ("noop: unsupported bit depth %d", buf->ctx.depth); +} + +/*! \brief Destroys a LUT object + */ +LUT * +dip_destroy_LUT (const void *self, LUT *m) +{ + require (dip == self); + + if (m) delete (m->lut); + delete (m); + + return m; +} + +/*! \brief Creates a LUT with an encoding \a gamma + * + * \sa http://en.wikipedia.org/wiki/Gamma_correction + */ +LUT * +dip_gamma_LUT (const void *self, int depth, + double gamma) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + double max; + + require (dip == self); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + max = (1 << depth) - 1; + + for (i = 0; i < (1 << depth); ++i) + { + double value = max * pow (i / max, 1 / gamma); + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + + return m; +} + +LUT * +dip_iscan_BCHS_LUT (const void *self, int depth, + double brightness, double contrast, + double highlight, double shadow) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + size_t max; + + int32_t b, c, h, s, f; + + require (dip == self); + require (-1 <= brightness && brightness <= 1); + require (-1 <= contrast && contrast <= 1); + require ( 0 <= highlight && highlight <= 1); + require ( 0 <= shadow && shadow <= 1); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + /* Compute algorithm parameters by scaling the double values into + * integral values that correspond to the bit depth. Computation + * of loop variable independent parts is done by absorption into + * existing variables. + */ + f = (1 << (depth - 1)) - 1; + s = f * shadow; + h = -f * highlight; h += 2 * f + 1; + b = f * brightness; + c = ((0 > contrast) + ? f * contrast + : (h - s) / 2 * contrast); + + log_data ("b = %d", b); + log_data ("c = %d", c); + log_data ("h = %d", h); + log_data ("s = %d", s); + + if (2 * c == (h - s)) --c; /* avoid zero division */ + + s += c; /* absorb scaled contrast */ + h -= c; /* absorb scaled contrast */ + h -= s; /* absorb shifted shadow */ + + log_data ("h' = %d", h); + log_data ("s' = %d", s); + + max = (1 << depth) - 1; + log_data ("max = %zd", max); + + for (i = 0; i < (1 << depth); ++i) + { + int32_t value = i; + + value -= s; + value *= max; + value /= h; + value += b; + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + return m; +} + +LUT * +dip_iscan_BC_LUT (const void *self, int depth, + double brightness, double contrast) +{ + return dip_iscan_BCHS_LUT (self, depth, brightness, contrast, 0, 0); +} + +/*! \brief Creates a LUT for brightness/contrast manipulations + * + * Algorithm indirectly taken from the GIMP. + * + * \sa http://en.wikipedia.org/wiki/Image_editing#Contrast_change_and_brightening + */ +LUT * +dip_gimp_BC_LUT (const void *self, int depth, + double brightness, double contrast) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + double max; + + require (dip == self); + require (-1 <= brightness && brightness <= 1); + require (-1 <= contrast && contrast <= 1); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + max = (1 << depth) - 1; + + for (i = 0; i < (1 << depth); ++i) + { + double value = i / max; + + if (brightness < 0.0) + value *= 1.0 + brightness; + else + value += (1 - value) * brightness; + + value = (value - 0.5) * tan ((contrast + 1) * M_PI / 4) + 0.5; + value *= max; + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + + return m; +} + +void +dip_flip_bits (const void *self, const buffer *buf) +{ + SANE_Byte *p; + + require (dip == self && buf); + + p = buf->ptr; + while (p != buf->end) + { + *p = ~*p; + ++p; + } +} + +static +void +dip_change_GRB_to_RGB_16 (const void *self, const buffer *buf) +{ + SANE_Byte *p, tmp; + + require (dip == self && buf && 16 == buf->ctx.depth); + + p = buf->ptr; + while (p < buf->end) + { + tmp = p[0]; /* most significant byte */ + p[0] = p[2]; + p[2] = p[0]; + tmp = p[1]; /* least significant byte */ + p[1] = p[3]; + p[3] = tmp; + + p += 6; + } +} + +static +void +dip_change_GRB_to_RGB_8 (const void *self, const buffer *buf) +{ + SANE_Byte *p, tmp; + + require (dip == self && buf && 8 == buf->ctx.depth); + + p = buf->ptr; + while (p < buf->end) + { + tmp = p[0]; + p[0] = p[1]; + p[1] = tmp; + + p += 3; + } +} + +void +dip_change_GRB_to_RGB (const void *self, const buffer *buf) +{ + require (dip == self && buf); + + if (SANE_FRAME_RGB != buf->ctx.format) + return; + + /**/ if (16 == buf->ctx.depth) + return dip_change_GRB_to_RGB_16 (self, buf); + else if ( 8 == buf->ctx.depth) + return dip_change_GRB_to_RGB_8 (self, buf); + + err_major ("unsupported bit depth"); + return; +} + +/*! \todo Add support for 16 bit color values (#816). + */ +void +dip_apply_color_profile (const void *self, const buffer *buf, + const double profile[9]) +{ + SANE_Int i; + SANE_Byte *r_buf, *g_buf, *b_buf; + double red, grn, blu; + + SANE_Byte *data; + SANE_Int size; + + require (dip == self && buf && profile); + require (8 == buf->ctx.depth); + + if (SANE_FRAME_RGB != buf->ctx.format) + return; + + data = buf->ptr; + size = buf->end - buf->ptr; + + for (i = 0; i < size / 3; i++) + { + r_buf = data; + g_buf = data + 1; + b_buf = data + 2; + + red = + profile[0] * (*r_buf) + profile[1] * (*g_buf) + profile[2] * (*b_buf); + grn = + profile[3] * (*r_buf) + profile[4] * (*g_buf) + profile[5] * (*b_buf); + blu = + profile[6] * (*r_buf) + profile[7] * (*g_buf) + profile[8] * (*b_buf); + + *data++ = clamp (red, 0, 255); + *data++ = clamp (grn, 0, 255); + *data++ = clamp (blu, 0, 255); + } +} + +bool +dip_has_deskew (const void *self, const device *hw) +{ + require (dip == self); + + return (magic_turn == dip->deskew + || (esdip_turn == dip->deskew + && enable_dip_deskew (hw))); +} + +static +void +esdip_turn (buffer *buf, int res_x, int res_y) +{ + ipc_dip_parms p; + + require (dip->plugin); + + memset (&p, 0, sizeof (p)); + memcpy (&p.parms, &buf->ctx, sizeof (p.parms)); + p.res_x = res_x; + p.res_y = res_y; + + ipc_dip_proc (dip->plugin, TYPE_DIP_SKEW_FLAG, &p, + &buf->ctx, (void **) &buf->buf); + + buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines; + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +static +void +magic_turn (buffer *buf, int res_x, int res_y) +{ + SANE_Status status; + int center_x, center_y; + double angle; + const int bg_sample = 0xff; /* white */ + + require (buf); + + status = sanei_magic_findSkew (&buf->ctx, buf->buf, res_x, res_y, + ¢er_x, ¢er_y, &angle); + if (SANE_STATUS_GOOD == status) + { + status = sanei_magic_rotate (&buf->ctx, buf->buf, + center_x, center_y, -angle, bg_sample); + } + + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +void +dip_deskew (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val) +{ + require (dip == self && buf && val); + + dip->deskew (buf, val[OPT_X_RESOLUTION].w, val[OPT_Y_RESOLUTION].w); +} + +/*! \bug autocrop_max_y() is the \e wrong criterion to limit autocrop + * support to selected devices. + */ +bool +dip_has_autocrop (const void *self, const device *hw) +{ + require (dip == self); + + return (magic_crop == dip->autocrop + || (esdip_crop == dip->autocrop + && 0 != autocrop_max_y (hw))); +} + +static +void +esdip_crop (buffer *buf, const device *hw, unsigned int count, + const Option_Value *val) +{ + ipc_dip_parms p; + + require (dip->plugin && hw && hw->fw_name && val); + + memset (&p, 0, sizeof (p)); + memcpy (&p.parms, &buf->ctx, sizeof (p.parms)); + p.res_x = val[OPT_X_RESOLUTION].w; + p.res_y = val[OPT_Y_RESOLUTION].w; + p.gamma = hw->gamma_type[ val[OPT_GAMMA_CORRECTION].w ]; + p.bside = SANE_FALSE; + if (using (hw, adf) && val[OPT_ADF_MODE].w) + { + p.bside = (0 == count % 2); + } + strncpy (p.fw_name, hw->fw_name, num_of (p.fw_name)); + + ipc_dip_proc (dip->plugin, TYPE_DIP_CROP_FLAG, &p, + &buf->ctx, (void **) &buf->buf); + + buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines; + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +static +void +magic_crop (buffer *buf, int res_x, int res_y) +{ + SANE_Status status; + int top, left, bottom, right; + + require (buf); + + status = sanei_magic_findEdges (&buf->ctx, buf->buf, res_x, res_y, + &top, &bottom, &left, &right); + if (SANE_STATUS_GOOD == status) + { + status = sanei_magic_crop (&buf->ctx, buf->buf, + top, bottom, left, right); + } + + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +void +dip_autocrop (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val) +{ + require (dip == self && buf && val); + + /**/ if (esdip_crop == dip->autocrop) + { + esdip_crop (buf, hw, count, val); + } + else if (magic_crop == dip->autocrop) + { + int res_x = val[OPT_X_RESOLUTION].w; + int res_y = val[OPT_Y_RESOLUTION].w; + + magic_crop (buf, res_x, res_y); + } +} diff --git a/backend/dip-obj.h b/backend/dip-obj.h new file mode 100644 index 0000000..6f010c8 --- /dev/null +++ b/backend/dip-obj.h @@ -0,0 +1,119 @@ +/* dip-obj.h -- digital image processing functionality singleton + * Copyright (C) 2011 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef dip_obj_h_included +#define dip_obj_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sane/sane.h> + +#include "device.h" +#include "epkowa.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + void * dip_init (const char *pkglibdir, SANE_Status *status); + void * dip_exit (void *self); + + bool dip_needs_whole_image (const void *self, const Option_Value *val, + const SANE_Option_Descriptor *opt); + + void dip_apply_LUT (const void *self, const buffer *buf, const LUT *m); + void dip_apply_LUT_RGB (const void *self, const buffer *buf, + const LUT *r, const LUT *g, const LUT *b); + LUT * dip_destroy_LUT (const void *self, LUT *m); + LUT * dip_gamma_LUT (const void *self, int depth, + double gamma); + LUT * dip_iscan_BCHS_LUT (const void *self, int depth, + double brightness, double contrast, + double highlight, double shadow); + LUT * dip_iscan_BC_LUT (const void *self, int depth, + double brightness, double contrast); + LUT * dip_gimp_BC_LUT (const void *self, int depth, + double brightness, double contrast); + + void dip_flip_bits (const void *self, const buffer *buf); + void dip_change_GRB_to_RGB (const void *self, const buffer *buf); + void dip_apply_color_profile (const void *self, const buffer *buf, + const double profile[9]); + + bool dip_has_deskew (const void *self, const device *hw); + bool dip_has_autocrop (const void *self, const device *hw); + + void dip_deskew (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val); + void dip_autocrop (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (dip_obj_h_included) */ diff --git a/backend/epkowa.c b/backend/epkowa.c new file mode 100644 index 0000000..14887bb --- /dev/null +++ b/backend/epkowa.c @@ -0,0 +1,6513 @@ +/* epkowa.c - SANE backend for EPSON flatbed scanners + (Image Scan! version) + + Based on the SANE Epson backend (originally from sane-1.0.3) + - updated to sane-backends-1.0.6 + - renamed from epson to epkowa to avoid confusion + - updated to sane-backends-1.0.12 + - updated to sane-backends-1.0.15 + + Based on Kazuhiro Sasayama previous + Work on epson.[ch] file from the SANE package. + + Original code taken from sane-0.71 + Copyright (C) 1997 Hypercore Software Design, Ltd. + + modifications + Copyright (C) 1998-1999 Christian Bucher <bucher@vernetzt.at> + Copyright (C) 1998-1999 Kling & Hautzinger GmbH + Copyright (C) 1999 Norihiko Sawa <sawa@yb3.so-net.ne.jp> + Copyright (C) 2000 Mike Porter <mike@udel.edu> (mjp) + Copyright (C) 1999-2004 Karl Heinz Kremer <khk@khk.net> + Copyright (C) 2001-2016 SEIKO EPSON CORPORATION + + This file is part of the EPKOWA SANE backend. + + 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 SANE_I18N +#define SANE_I18N(text) (text) +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <limits.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <math.h> + +#include <sane/saneopts.h> + +#ifndef BACKEND_NAME +#define BACKEND_NAME epkowa +#endif + +#include "backend.h" +#include "command.h" +#include "hw-data.h" +#include "utils.h" +#include "timing.h" +#include "cfg-obj.h" +#include "dip-obj.h" +#include "model-info.h" +#include "utils.h" + +#include "epkowa_scsi.h" +#include "sane/sanei_pio.h" +#include "epkowa_ip.h" /* interpreter-based scanner support */ + +#include "sane/sanei.h" + +#define DEFAULT_RESOLUTION 300 /* dpi */ +#define DEFAULT_X_RESOLUTION DEFAULT_RESOLUTION +#define DEFAULT_Y_RESOLUTION DEFAULT_RESOLUTION + +#define S_ACK "\006" +#define S_CAN "\030" + +/* Usable values when defining EPSON_LEVEL_DEFAULT + * There is also a function level "A5", used for the GT-300, a + * monochrome only scanner. This level is not supported. + */ +#define EPSON_LEVEL_A1 0 +#define EPSON_LEVEL_A2 1 +#define EPSON_LEVEL_B1 2 +#define EPSON_LEVEL_B2 3 +#define EPSON_LEVEL_B3 4 +#define EPSON_LEVEL_B4 5 +#define EPSON_LEVEL_B5 6 +#define EPSON_LEVEL_B6 7 +#define EPSON_LEVEL_B7 8 +#define EPSON_LEVEL_B8 9 +#define EPSON_LEVEL_F5 10 +#define EPSON_LEVEL_D1 11 +#define EPSON_LEVEL_D2 12 +#define EPSON_LEVEL_D7 13 +#define EPSON_LEVEL_D8 14 + +#define EPSON_LEVEL_DEFAULT EPSON_LEVEL_B3 + +static EpsonCmdRec epson_cmd[] = { +/* + * request identity + * | request identity2 + * | | request status + * | | | request condition + * | | | | set color mode + * | | | | | start scanning + * | | | | | | set data format + * | | | | | | | set resolution + * | | | | | | | | set zoom + * | | | | | | | | | set scan area + * | | | | | | | | | | set brightness + * | | | | | | | | | | | set gamma + * | | | | | | | | | | | | set halftoning + * | | | | | | | | | | | | | set color correction + * | | | | | | | | | | | | | | initialize scanner + * | | | | | | | | | | | | | | | set speed + * | | | | | | | | | | | | | | | | set lcount + * | | | | | | | | | | | | | | | | | mirror image + * | | | | | | | | | | | | | | | | | | set gamma table + * | | | | | | | | | | | | | | | | | | | set outline emphasis + * | | | | | | | | | | | | | | | | | | | | set dither + * | | | | | | | | | | | | | | | | | | | | | set color correction coefficients + * | | | | | | | | | | | | | | | | | | | | | | request extension status + * | | | | | | | | | | | | | | | | | | | | | | | control an extension + * | | | | | | | | | | | | | | | | | | | | | | | | forward feed / eject + * | | | | | | | | | | | | | | | | | | | | | | | | | feed + * | | | | | | | | | | | | | | | | | | | | | | | | | | request push button status + * | | | | | | | | | | | | | | | | | | | | | | | | | | | control auto area segmentation + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | set film type + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set exposure time + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set bay + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set threshold + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set focus position + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | request focus position + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + */ + {"A1",'I', 0 ,'F','S', 0 ,'G', 0 ,'R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"A2",'I', 0 ,'F','S', 0 ,'G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B1",'I', 0 ,'F','S','C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 ,'B', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B2",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B3",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@', 0 , 0 , 0 , 0 , 0 , 0 ,'m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B4",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d', 0 ,'z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B6",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, + {"B7",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0 ,'!','s','N', 0 , 0 ,'t', 0 , 0 }, + {"B8",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0x19,'!','s','N', 0 , 0 ,'t','p','q'}, + {"F5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z', 0 ,'M','@','g','d','K','z','Q', 0 ,'m','f','e','\f', 0 , 0 , 0 ,'N','T','P', 0 , 0 , 0 }, + {"D1",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f', 0 , 0 , 0 ,'!', 0 , 0 , 0 , 0 ,'t', 0 , 0 }, + {"D2",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e', 0 , 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 }, + {"D7",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 }, + {"D8",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 }, +}; + + +/* Definition of the mode_param struct, that is used to + * specify the valid parameters for the different scan modes. + * + * The depth variable gets updated when the bit depth is modified. + */ + +struct mode_param +{ + int color; + int mode_flags; + int dropout_mask; + int depth; +}; + +static struct mode_param mode_params[] = { + {0, 0x00, 0x30, 1}, + {0, 0x00, 0x30, 8}, + {1, 0x02, 0x00, 8} +}; + +static const SANE_String_Const mode_list[] = { + SANE_I18N ("Binary"), + SANE_I18N ("Gray"), + SANE_I18N ("Color"), + NULL +}; + +static const SANE_String_Const adf_mode_list[] = { + SANE_I18N ("Simplex"), + SANE_I18N ("Duplex"), + NULL +}; + +static const SANE_String_Const dfd_sensitivity[] = { + SANE_I18N ("None"), + SANE_I18N ("Low"), + SANE_I18N ("High"), + NULL +}; + +/* Define the different scan sources: + */ + +#define FBF_STR SANE_I18N("Flatbed") +#define TPU_STR SANE_I18N("Transparency Unit") +#define ADF_STR SANE_I18N("Automatic Document Feeder") + +#define FILM_TYPE_POSITIVE (0) +#define FILM_TYPE_NEGATIVE (1) + +static const SANE_String_Const film_list[] = { + SANE_I18N ("Positive Film"), + SANE_I18N ("Negative Film"), + NULL +}; + +static const SANE_String_Const focus_list[] = { + SANE_I18N ("Focus on glass"), + SANE_I18N ("Focus 2.5mm above glass"), + NULL +}; + +#define HALFTONE_NONE 0x01 +#define HALFTONE_TET 0x03 + +static int halftone_params[] = { + HALFTONE_NONE, + 0x00, + 0x10, + 0x20, + 0x80, + 0x90, + 0xa0, + 0xb0, + HALFTONE_TET, + 0xc0, + 0xd0 +}; + +static const SANE_String_Const halftone_list[] = { + SANE_I18N ("None"), + SANE_I18N ("Halftone A (Hard Tone)"), + SANE_I18N ("Halftone B (Soft Tone)"), + SANE_I18N ("Halftone C (Net Screen)"), + NULL +}; + +static const SANE_String_Const halftone_list_4[] = { + SANE_I18N ("None"), + SANE_I18N ("Halftone A (Hard Tone)"), + SANE_I18N ("Halftone B (Soft Tone)"), + SANE_I18N ("Halftone C (Net Screen)"), + SANE_I18N ("Dither A (4x4 Bayer)"), + SANE_I18N ("Dither B (4x4 Spiral)"), + SANE_I18N ("Dither C (4x4 Net Screen)"), + SANE_I18N ("Dither D (8x4 Net Screen)"), + NULL +}; + +static const SANE_String_Const halftone_list_7[] = { + SANE_I18N ("None"), + SANE_I18N ("Halftone A (Hard Tone)"), + SANE_I18N ("Halftone B (Soft Tone)"), + SANE_I18N ("Halftone C (Net Screen)"), + SANE_I18N ("Dither A (4x4 Bayer)"), + SANE_I18N ("Dither B (4x4 Spiral)"), + SANE_I18N ("Dither C (4x4 Net Screen)"), + SANE_I18N ("Dither D (8x4 Net Screen)"), + SANE_I18N ("Text Enhanced Technology"), + SANE_I18N ("Download pattern A"), + SANE_I18N ("Download pattern B"), + NULL +}; + +static int dropout_params[] = { + 0x00, /* none */ + 0x10, /* red */ + 0x20, /* green */ + 0x30 /* blue */ +}; + +static const SANE_String_Const dropout_list[] = { + SANE_I18N ("None"), + SANE_I18N ("Red"), + SANE_I18N ("Green"), + SANE_I18N ("Blue"), + NULL +}; + +static const SANE_String_Const brightness_method_list[] = { + SANE_I18N ("hardware"), /* needs to be first */ + SANE_I18N ("iscan"), + SANE_I18N ("gimp"), + NULL +}; + +#define max_val 100 /* any integer value will do */ +static const SANE_Range brightness_range = { -max_val, max_val, 1}; +static const SANE_Range contrast_range = { -max_val, max_val, 1}; +#if 0 +/* We don't provide highlight and shadow options yet because we + * haven't worked out the GIMP part of that and how that should + * interact with the brightness-method option. For the "iscan" + * method, the values below are OK. + */ +static const SANE_Range highlight_range = { 0, max_val, 0 }; +static const SANE_Range shadow_range = { 0, max_val, 0 }; +#endif +#undef max_val + +/* Color correction: + * One array for the actual parameters that get sent to the scanner (color_params[]), + * one array for the strings that get displayed in the user interface (color_list[]) + * and one array to mark the user defined color correction (dolor_userdefined[]). + */ + +static int color_params_ab[] = { + 0x00, + 0x01, + 0x10, + 0x20, + 0x40, + 0x80 +}; + +static SANE_Bool color_userdefined_ab[] = { + SANE_FALSE, + SANE_TRUE, + SANE_FALSE, + SANE_FALSE, + SANE_FALSE, + SANE_FALSE +}; + +static const SANE_String_Const color_list_ab[] = { + SANE_I18N ("No Correction"), + SANE_I18N ("User defined"), + SANE_I18N ("Impact-dot printers"), + SANE_I18N ("Thermal printers"), + SANE_I18N ("Ink-jet printers"), + SANE_I18N ("CRT monitors"), + NULL +}; + +static int color_params_d[] = { + 0x00 +}; + +static SANE_Bool color_userdefined_d[] = { + SANE_TRUE +}; + +static const SANE_String_Const color_list_d[] = { + "User defined", + NULL +}; + + +/* Gamma correction: + * The A and B level scanners work differently than the D level scanners, therefore + * I define two different sets of arrays, plus one set of variables that get set to + * the actally used params and list arrays at runtime. + */ + +static int gamma_params_ab[] = { + 0x01, + 0x03, + 0x04, + 0x00, + 0x10, + 0x20 +}; + +static const SANE_String_Const gamma_list_ab[] = { + SANE_I18N ("Default"), + SANE_I18N ("User defined (Gamma=1.0)"), + SANE_I18N ("User defined (Gamma=1.8)"), + SANE_I18N ("High density printing"), + SANE_I18N ("Low density printing"), + SANE_I18N ("High contrast printing"), + NULL +}; + +static SANE_Bool gamma_userdefined_ab[] = { + SANE_FALSE, + SANE_TRUE, + SANE_TRUE, + SANE_FALSE, + SANE_FALSE, + SANE_FALSE, +}; + +static int gamma_params_d[] = { + 0x03, + 0x04 +}; + +static const SANE_String_Const gamma_list_d[] = { + SANE_I18N ("User defined (Gamma=1.0)"), + SANE_I18N ("User defined (Gamma=1.8)"), + NULL +}; + +static SANE_Bool gamma_userdefined_d[] = { + SANE_TRUE, + SANE_TRUE +}; + + +/* Bay list: + * this is used for the FilmScan + */ + +static const SANE_String_Const bay_list[] = { + " 1 ", + " 2 ", + " 3 ", + " 4 ", + " 5 ", + " 6 ", + NULL +}; + +static const SANE_Range u8_range = { 0, 255, 0 }; +static const SANE_Range s8_range = { -127, 127, 0 }; +static const SANE_Range zoom_range = { 50, 200, 0 }; + +/* The "switch_params" are used for several boolean choices + */ +static int switch_params[] = { + 0, + 1 +}; + +#define mirror_params switch_params +#define speed_params switch_params +#define film_params switch_params + +static const SANE_Range outline_emphasis_range = { -2, 2, 0 }; + +enum + { + EXT_SANE_STATUS_NONE, + EXT_SANE_STATUS_MULTI_FEED, + EXT_SANE_STATUS_TRAY_CLOSED, + EXT_SANE_STATUS_MAX, + }; +static const SANE_Range ext_sane_status = { + EXT_SANE_STATUS_NONE, + EXT_SANE_STATUS_MAX - 1, + 0 +}; + +typedef struct { + double width; /* in mm */ + double height; /* in mm */ + SANE_String_Const name; +} media_type; + +static SANE_String_Const media_maximum = SANE_I18N ("Maximum"); +static SANE_String_Const media_automatic = SANE_I18N ("Automatic"); + +/*! \brief List of preset media sizes + * This list is used to populate the constraint list for the + * scan-area option. + * + * \remark When adding Landscape and Portrait variants of a medium + * size, make sure to add three(!) entries using the same + * pattern as used for those already listed. The algorithm + * used to populate the constraint list depends on this + * convention to create the most user-friendly list. + */ +static const media_type media_list[] = { + /* ISO A Series */ + { 297, 420, SANE_I18N ("A3") }, + { 297, 210, SANE_I18N ("A4 Landscape") }, + { 210, 297, SANE_I18N ("A4 Portrait") }, + { 210, 297, SANE_I18N ("A4") }, + { 210, 148, SANE_I18N ("A5 Landscape") }, + { 148, 210, SANE_I18N ("A5 Portrait") }, + { 148, 210, SANE_I18N ("A5") }, + /* JIS B Series */ + { 257, 364, SANE_I18N ("B4") }, + { 257, 184, SANE_I18N ("B5 Landscape") }, + { 184, 257, SANE_I18N ("B5 Portrait") }, + { 184, 257, SANE_I18N ("B5") }, + /* North American sizes */ + { 11.00 * MM_PER_INCH, 17.00 * MM_PER_INCH, SANE_I18N ("Ledger") }, + { 8.50 * MM_PER_INCH, 14.00 * MM_PER_INCH, SANE_I18N ("Legal") }, + { 11.00 * MM_PER_INCH, 8.50 * MM_PER_INCH, SANE_I18N ("Letter Landscape") }, + { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter Portrait") }, + { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter") }, + { 10.50 * MM_PER_INCH, 7.25 * MM_PER_INCH, SANE_I18N ("Executive Landscape") }, + { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive Portrait") }, + { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive") }, + /* Miscellaneous */ + { 120, 120, SANE_I18N ("CD")}, +}; + + +static SANE_Word *bitDepthList = NULL; + + +/* Some utility macros + */ + +/*! Returns the larger of the arguments \a a and \a b. + */ +#define max(a, b) ((a) < (b) ? (b) : (a)) + +/*! Returns the smaller of the arguments \a a and \a b. + */ +#define min(a, b) ((a) < (b) ? (a) : (b)) + + +static size_t +max_string_size (const SANE_String_Const strings[]) +{ + size_t size, max_size = 0; + int i; + + for (i = 0; strings[i]; i++) + { + size = strlen (strings[i]) + 1; + if (size > max_size) + max_size = size; + } + return max_size; +} + + +static inline +bool +need_autocrop_override (const Epson_Scanner *s) +{ + return (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap) + && s->val[OPT_AUTOCROP].b + && 0 != autocrop_max_y (s->hw)); +} + +typedef struct +{ + u_char code; + u_char status; + u_char count1; + u_char count2; + + u_char type; + u_char level; + + u_char buf[1]; + +} EpsonIdentRec, *EpsonIdent; + +typedef struct +{ + u_char code; + u_char status; + u_char count1; + u_char count2; + u_char buf[1]; + +} EpsonHdrRec, *EpsonHdr; + + +static SANE_Status +read_image_info_block (device *hw); + +static SANE_Status +get_identity_information (device *hw); + +static SANE_Status +get_hardware_property (device *hw); + +static SANE_Status +get_identity2_information (device *hw, Epson_Scanner *s); + +static SANE_Status +check_warmup (device *hw); + +static SANE_Status +check_ext_status (device *hw); + +SANE_Status +control_option_unit (device *hw, SANE_Bool use_duplex); + +static SANE_Status +initialize (device *hw); + +static SANE_Status +get_resolution_constraints (device *hw, Epson_Scanner *s); + +static SANE_Status +get_push_button_status (device *hw, SANE_Bool *button_pushed); + +static SANE_Status color_shuffle (Epson_Scanner *s, int *new_length); +static void activateOption (Epson_Scanner * s, SANE_Int option, + SANE_Bool * change); +static void deactivateOption (Epson_Scanner * s, SANE_Int option, + SANE_Bool * change); +static void setOptionState (Epson_Scanner * s, SANE_Bool state, + SANE_Int option, SANE_Bool * change); +static void filter_resolution_list (Epson_Scanner * s); +static void scan_finish (Epson_Scanner * s); + +static void get_colorcoeff_from_profile (double *profile, + unsigned char *color_coeff); +static void round_cct (double org_cct[], int rnd_cct[]); +static int get_roundup_index (double frac[], int n); +static int get_rounddown_index (double frac[], int n); + +static void handle_mode (Epson_Scanner * s, SANE_Int optindex, + SANE_Bool * reload); +static void handle_resolution (Epson_Scanner * s, SANE_Int option, + SANE_Word value); +static void change_profile_matrix (Epson_Scanner * s); +static void handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex, + SANE_Bool * reload); + +static SANE_Status create_epson_device (device **dev, channel* ch); +static SANE_Status create_epson_scanner (device *dev, Epson_Scanner **scanner); +static SANE_Status create_sane_handle (Epson_Scanner **scanner, const char *name, const void *dip); +static SANE_Status init_options (Epson_Scanner * s); + +static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode); +static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode); + +static SANE_Status handle_source (Epson_Scanner * s, + SANE_Int optindex, + char *value); + +static void adf_handle_out_of_paper (Epson_Scanner * s); +static void adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize); + +static void handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload); +static void handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload); +static void handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload); +static void handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload); + +static SANE_Status +expect_ack (device *hw) +{ + u_char result[1]; + size_t len; + SANE_Status status; + + log_call (); + + len = sizeof (result); + + channel_recv (hw->channel, result, len, &status); + + if (SANE_STATUS_GOOD != status) + return status; + + if (ACK != result[0]) + return SANE_STATUS_INVAL; + + return SANE_STATUS_GOOD; +} + + +static SANE_Status +set_cmd (device *hw, u_char cmd, u_char val) +{ + SANE_Status status; + u_char params[2]; + + if (!cmd) + return SANE_STATUS_UNSUPPORTED; + + log_call ("(%c)", cmd); + + /* Override with extended parameter command (FS W). + * Do not override A, R and e, they are handled separately. + */ + if (hw->using_fs && strchr ("CDgdZLMBtsQKN", cmd)) + return dev_set_scanning_parameter (hw, cmd, &val); + + params[0] = ESC; + params[1] = cmd; + + channel_send (hw->channel, params, 2, &status); + if (SANE_STATUS_GOOD != (status = expect_ack (hw))) + return status; + + params[0] = val; + channel_send (hw->channel, params, 1, &status); + status = expect_ack (hw); + + return status; +} + +static void +print_params (const SANE_Parameters params) +{ + log_data ("params.format = %d", params.format); + log_data ("params.last_frame = %d", params.last_frame); + log_data ("params.bytes_per_line = %d", params.bytes_per_line); + log_data ("params.pixels_per_line = %d", params.pixels_per_line); + log_data ("params.lines = %d", params.lines); + log_data ("params.depth = %d", params.depth); +} + + +#define set_focus_position(h,v) set_cmd (h,(h)->cmd->set_focus_position,v) +#define set_color_mode(h,v) set_cmd (h,(h)->cmd->set_color_mode,v) +#define set_data_format(h,v) set_cmd (h,(h)->cmd->set_data_format, v) +#define set_halftoning(h,v) set_cmd (h,(h)->cmd->set_halftoning, v) +#define set_gamma(h,v) set_cmd (h,(h)->cmd->set_gamma, v) +#define set_color_correction(h,v) set_cmd (h,(h)->cmd->set_color_correction, v) +#define set_lcount(h,v) set_cmd (h,(h)->cmd->set_lcount, v) +#define set_bright(h,v) set_cmd (h,(h)->cmd->set_bright, v) +#define mirror_image(h,v) set_cmd (h,(h)->cmd->mirror_image, v) +#define set_speed(h,v) set_cmd (h,(h)->cmd->set_speed, v) +#define set_outline_emphasis(h,v) set_cmd (h,(h)->cmd->set_outline_emphasis, v) +#define control_auto_area_segmentation(h,v) set_cmd (h,(h)->cmd->control_auto_area_segmentation, v) +#define set_film_type(h,v) set_cmd (h,(h)->cmd->set_film_type, v) +#define set_exposure_time(h,v) set_cmd (h,(h)->cmd->set_exposure_time, v) +#define set_bay(h,v) set_cmd (h,(h)->cmd->set_bay, v) +#define set_threshold(h,v) set_cmd (h,(h)->cmd->set_threshold, v) + + +static SANE_Status +set_zoom (device *hw, int x_zoom, int y_zoom) +{ + SANE_Status status; + u_char cmd[2]; + u_char params[2]; + + if (!hw->cmd->set_zoom) + return SANE_STATUS_GOOD; + + log_call (); + + cmd[0] = ESC; + cmd[1] = hw->cmd->set_zoom; + + channel_send (hw->channel, cmd, 2, &status); + status = expect_ack (hw); + + if (status != SANE_STATUS_GOOD) + return status; + + params[0] = x_zoom; + params[1] = y_zoom; + + channel_send (hw->channel, params, 2, &status); + status = expect_ack (hw); + + return status; +} + + +static SANE_Status +set_resolution (device *hw, int xres, int yres) +{ + SANE_Status status; + u_char params[4]; + + if (!hw->cmd->set_resolution) + return SANE_STATUS_GOOD; + + log_call (); + + params[0] = ESC; + params[1] = hw->cmd->set_resolution; + + channel_send (hw->channel, params, 2, &status); + status = expect_ack (hw); + + if (status != SANE_STATUS_GOOD) + return status; + + params[0] = xres; + params[1] = xres >> 8; + params[2] = yres; + params[3] = yres >> 8; + + channel_send (hw->channel, params, 4, &status); + status = expect_ack (hw); + + return status; +} + +/* set_scan_area() + * + * Sends the "set scan area" command to the scanner with the currently selected + * scan area. This scan area is already corrected for "color shuffling" if + * necessary. + */ +static SANE_Status +set_scan_area (device *hw, int x, int y, int width, int height) +{ + SANE_Status status; + u_char params[8]; + + log_call ("(%d, %d, %d, %d)", x, y, width, height); + + if (!hw->cmd->set_scan_area) + { + err_major ("set_scan_area not supported"); + return SANE_STATUS_GOOD; + } + + /* verify the scan area */ + if (x < 0 || y < 0 || width <= 0 || height <= 0) + return SANE_STATUS_INVAL; + + params[0] = ESC; + params[1] = hw->cmd->set_scan_area; + + channel_send (hw->channel, params, 2, &status); + status = expect_ack (hw); + if (status != SANE_STATUS_GOOD) + return status; + + params[0] = x; + params[1] = x >> 8; + params[2] = y; + params[3] = y >> 8; + params[4] = width; + params[5] = width >> 8; + params[6] = height; + params[7] = height >> 8; + + channel_send (hw->channel, params, 8, &status); + status = expect_ack (hw); + + return status; +} + +/* set_color_correction_coefficients() + * + * Sends the "set color correction coefficients" command with the currently selected + * parameters to the scanner. + */ + +static SANE_Status +set_color_correction_coefficients (device *hw, Epson_Scanner *s) +{ + SANE_Status status; + u_char cmd = hw->cmd->set_color_correction_coefficients; + u_char params[2]; + const int length = 9; + unsigned char cccoeff[9]; + + log_call (); + + s->cct[0] = SANE_UNFIX (s->val[OPT_CCT_1].w); + s->cct[1] = SANE_UNFIX (s->val[OPT_CCT_2].w); + s->cct[2] = SANE_UNFIX (s->val[OPT_CCT_3].w); + s->cct[3] = SANE_UNFIX (s->val[OPT_CCT_4].w); + s->cct[4] = SANE_UNFIX (s->val[OPT_CCT_5].w); + s->cct[5] = SANE_UNFIX (s->val[OPT_CCT_6].w); + s->cct[6] = SANE_UNFIX (s->val[OPT_CCT_7].w); + s->cct[7] = SANE_UNFIX (s->val[OPT_CCT_8].w); + s->cct[8] = SANE_UNFIX (s->val[OPT_CCT_9].w); + + if (!cmd) /* effect will be emulated */ + return SANE_STATUS_GOOD; + + params[0] = ESC; + params[1] = cmd; + + channel_send (hw->channel, params, 2, &status); + if (SANE_STATUS_GOOD != (status = expect_ack (hw))) + return status; + + get_colorcoeff_from_profile (s->cct, cccoeff); + + log_data ("[ %d %d %d ][ %d %d %d ][ %d %d %d]", + cccoeff[0], cccoeff[1], cccoeff[2], + cccoeff[3], cccoeff[4], cccoeff[5], + cccoeff[6], cccoeff[7], cccoeff[8]); + + channel_send (hw->channel, cccoeff, length, &status); + status = expect_ack (hw); + + log_call ("exit"); + + return status; +} + + +static SANE_Status +set_gamma_table (device *hw, const Epson_Scanner *s) +{ + SANE_Status status; + u_char cmd = hw->cmd->set_gamma_table; + u_char params[2]; + const int length = 257; + u_char gamma[257]; + int n; + int table; + static char gamma_cmds[] = { 'R', 'G', 'B' }; + + if (!cmd) + return SANE_STATUS_UNSUPPORTED; + + log_call (); + + params[0] = ESC; + params[1] = cmd; + +/* When handling inverted images, we must also invert the user + * supplied gamma function. This is *not* just 255-gamma - + * this gives a negative image. + */ + + if (strcmp_c ("GT-6600", hw->fw_name) == 0 || + strcmp_c ("Perfection 610", hw->fw_name) == 0) + { + gamma_cmds[0] = 'R'; + gamma_cmds[1] = 'B'; + gamma_cmds[2] = 'G'; + } + + for (table = 0; table < 3; table++) + { + gamma[0] = gamma_cmds[table]; + if (s->invert_image) + { + for (n = 0; n < 256; ++n) + { + gamma[n + 1] = 255 - s->gamma_table[table][255 - n]; + } + } + else + { + for (n = 0; n < 256; ++n) + { + gamma[n + 1] = s->gamma_table[table][n]; + } + } + + channel_send (hw->channel, params, 2, &status); + if (SANE_STATUS_GOOD != (status = expect_ack (hw))) + return status; + + channel_send (hw->channel, gamma, length, &status); + if (SANE_STATUS_GOOD != (status = expect_ack (hw))) + return status; + } + + log_call ("exit"); + + return status; +} + +static SANE_Status +check_warmup (device *hw) +{ + SANE_Status status = check_ext_status (hw); + + log_call (); + + if (status == SANE_STATUS_DEVICE_BUSY) + { + int timeout; + + for (timeout = 0; timeout < 60; timeout++) + { + status = check_ext_status (hw); + + if (status == SANE_STATUS_DEVICE_BUSY) + sleep (1); + else + { + return status; + } + } + } + else + return status; + + return status; +} + + +static SANE_Status +check_ext_status (device *hw) +{ + SANE_Status status; + + log_call (); + require (hw); + + status = dev_request_extended_status (hw); + + if (EXT_STATUS_WU & hw->ext_status) + { + log_info ("option: warming up"); + status = SANE_STATUS_DEVICE_BUSY; + } + + if (EXT_STATUS_FER & hw->ext_status) + { + log_info ("option: fatal error"); + status = SANE_STATUS_INVAL; + } + + if (hw->adf) + { + if (ADF_STATUS_ERR & hw->adf->status + || ADF_EXT_STATUS_ERR & hw->adf->ext_status) + { + log_info ("ADF: other error"); + status = SANE_STATUS_INVAL; + } + + if (ADF_STATUS_PE & hw->adf->status) + { + log_info ("ADF: no paper"); + status = SANE_STATUS_NO_DOCS; + } + + if (ADF_STATUS_PJ & hw->adf->status) + { + log_info ("ADF: paper jam"); + status = SANE_STATUS_JAMMED; + } + + if (ADF_STATUS_OPN & hw->adf->status) + { + log_info ("ADF: cover open"); + status = SANE_STATUS_COVER_OPEN; + } + + if (ADF_EXT_STATUS_DFE & hw->adf->ext_status) + { + log_info ("ADF: multi sheet feed"); + status = SANE_STATUS_JAMMED; + } + + if (ADF_EXT_STATUS_TR_OPN & hw->adf->ext_status) + { + log_info ("ADF: tray open"); + status = SANE_STATUS_COVER_OPEN; + } + } + + if (hw->tpu) + { + if (TPU_STATUS_ERR & hw->tpu->status) + { + log_info ("TPU: other error"); + status = SANE_STATUS_INVAL; + } + } + + if (hw->fbf) + { + /* ??? is this for MFDs? */ + if (DV3_STATUS_OPN & hw->fbf->status) + { + log_info ("UNIT: Scanner Unit open"); + status = SANE_STATUS_COVER_OPEN; + } + } + + return status; +} + + +static SANE_Status +initialize (device *hw) +{ + SANE_Status status; + u_char param[2]; + + log_call (); + + if (!hw->cmd->initialize_scanner) + return SANE_STATUS_GOOD; + + param[0] = ESC; + param[1] = hw->cmd->initialize_scanner; + + channel_send (hw->channel, param, 2, &status); + status = expect_ack (hw); + + return status; +} + + +static Epson_Scanner *first_handle = NULL; + + +static EpsonHdr +command (device *hw, const u_char * cmd, size_t cmd_size, + SANE_Status * status) +{ + EpsonHdr head; + u_char *buf; + int count; + + log_call (); + + if (NULL == (head = t_malloc (1,EpsonHdrRec))) + { + err_fatal ("%s", strerror (errno)); + *status = SANE_STATUS_NO_MEM; + return (EpsonHdr) 0; + } + + channel_send (hw->channel, cmd, cmd_size, status); + + if (SANE_STATUS_GOOD != *status) + { + /* this is necessary for the GT-8000. I don't know why, but + it seems to fix the problem. It should not have any + ill effects on other scanners. */ + *status = SANE_STATUS_GOOD; + channel_send (hw->channel, cmd, cmd_size, status); + if (SANE_STATUS_GOOD != *status) + return (EpsonHdr) 0; + } + + buf = (u_char *) head; + buf += channel_recv (hw->channel, buf, 4, status); + + if (SANE_STATUS_GOOD != *status) + { + delete (head); + return (EpsonHdr) 0; + } + + switch (head->code) + { + + case NAK: + /* fall through */ + /* !!! is this really sufficient to report an error ? */ + case ACK: + break; /* no need to read any more data after ACK or NAK */ + + case STX: + hw->status = head->status; + + if (SANE_STATUS_GOOD != *status) + { + delete (head); + return (EpsonHdr) 0; + } + + count = head->count2 * 255 + head->count1; + log_info ("need to read %d data bytes", count); + + /* Grow head so it has enough space to hold an additional count + * bytes of payload. We can _not_ use t_realloc here. + */ + if (NULL == (head = realloc (head, sizeof (EpsonHdrRec) + count))) + { + err_fatal ("%s", strerror (errno)); + *status = SANE_STATUS_NO_MEM; + return (EpsonHdr) 0; + } + + buf = head->buf; + channel_recv (hw->channel, buf, count, status); + + if (SANE_STATUS_GOOD != *status) + { + delete (head); + return (EpsonHdr) 0; + } + + break; + + default: + if (0 == head->code) + { err_major ("Incompatible printer port (probably bi/directional)"); } + else if (cmd[cmd_size - 1] == head->code) + { err_major ("Incompatible printer port (probably not bi/directional)"); } + + err_major ("Illegal response of scanner for command: %02x", head->code); + break; + } + + return head; +} + + +static SANE_Status +create_epson_device (device **devp, channel* ch) +{ + SANE_Status status = SANE_STATUS_GOOD; + device *dev = NULL; + + require (devp && ch); + + dev = t_calloc (1, device); + if (!dev) + { + err_fatal ("%s", strerror (errno)); + return SANE_STATUS_NO_MEM; + } + + dev->channel = ch; + + init_resolution_info (&dev->res , NULL); + init_resolution_info (&dev->res_x, NULL); + init_resolution_info (&dev->res_y, NULL); + + dev->cmd = &epson_cmd[EPSON_LEVEL_DEFAULT]; + + dev->fw_name = get_fw_name (dev->channel); + + { + void *cfg = cfg_init (NULL, NULL); + dev->using_fs = !cfg_has_value (cfg, CFG_KEY_FS_BLACKLIST, dev->fw_name); + } + + if (!dev->using_fs && CHAN_NET == ch->type) + { + err_fatal ("Network channel does not support scanning via ESC commands"); + return SANE_STATUS_IO_ERROR; + } + + { + size_t protocol_max = (dev->using_fs ? UINT32_MAX : UINT16_MAX); + + ch->set_max_request_size (ch, min (protocol_max, 512 * 1024)); + } + + initialize (dev); + + if (dev->cmd->request_identity != 0) + { + status = get_identity_information (dev); + if (status != SANE_STATUS_GOOD) + { + return status; + } + } + + if (dev->cmd->request_identity2 != 0) + { + get_hardware_property (dev); + if (status != SANE_STATUS_GOOD) + { + return status; + } + } + + /* Check for the max. supported color depth and assign the values to + * the bitDepthList. Note that bitDepthList is a SANE word list and + * therefore has an initial element that indicates the number of its + * elements. We store up to 2 bit depths (8 and only one of 16, 14, + * and 12). + */ + + bitDepthList = t_malloc (1 + 2, SANE_Word); + if (bitDepthList == NULL) + { + err_fatal ("%s", strerror (errno)); + return SANE_STATUS_NO_MEM; + } + + bitDepthList[0] = 1; /* we start with one element in the list */ + bitDepthList[1] = 8; /* 8bit is the default */ + + if (set_data_format (dev, 16) == SANE_STATUS_GOOD) + { + dev->maxDepth = 16; + + bitDepthList[0]++; + bitDepthList[bitDepthList[0]] = 16; + } + else if (set_data_format (dev, 14) == SANE_STATUS_GOOD) + { + dev->maxDepth = 14; + + bitDepthList[0]++; + bitDepthList[bitDepthList[0]] = 14; + } + else if (set_data_format (dev, 12) == SANE_STATUS_GOOD) + { + dev->maxDepth = 12; + + bitDepthList[0]++; + bitDepthList[bitDepthList[0]] = 12; + } + else + { + dev->maxDepth = 8; + /* the default depth is already in the list */ + } + + log_info ("maximum color depth = %d", dev->maxDepth); + + status = dev_request_extended_status (dev); + if (SANE_STATUS_GOOD != status) + { + if (SANE_STATUS_UNSUPPORTED != status) + { + err_minor ("failure processing extended status request"); + } + } + else + { + const void *info = model_info_cache_get_info (dev->fw_name, &status); + if (SANE_STATUS_GOOD == status && info) + { + dev->scan_hard = model_info_get_profile (info); + model_info_customise_commands (info, dev->cmd); + dev->uses_locking = model_info_has_lock_commands (info); + } + else + { + err_minor ("failure getting model info (%s)", sane_strstatus (status)); + } + } + + if (0 == strcmp_c ("GT-8200", dev->fw_name)) + { + /* Version 1.08 of the firmware is said to only report half of the + vertical size of the scan area. We should double that if it is + smaller than the horizontal scan area. Although we already try + doing this by fixing up the extended status reply, that doesn't + do the whole trick because we are dealing with a device type 0 + scanner. In that case we get the FBF scan area via the 'ESC I' + command which does not include model info so we can not safely + fix up that reply. + */ + if (dev->fbf && (dev->fbf->max_y < dev->fbf->max_x)) + { + err_minor ("Fixing up buggy FBF max scan dimensions."); + dev->fbf->max_y *= 2; + update_ranges (dev, dev->fbf); + dev->need_color_reorder = SANE_TRUE; + } + } + + if (dev->fbf) + { + log_info (" FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]", + SANE_UNFIX (dev->fbf->x_range.min), + SANE_UNFIX (dev->fbf->y_range.min), + SANE_UNFIX (dev->fbf->x_range.max), + SANE_UNFIX (dev->fbf->y_range.max)); + + // check for auto size detection support and cache the results + dev->fbf->has_size_check = ((0 != dev->fbf->doc_x) + && (0 != dev->fbf->doc_y)); + } + if (dev->adf) + { + log_info (" ADF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]", + SANE_UNFIX (dev->adf->x_range.min), + SANE_UNFIX (dev->adf->y_range.min), + SANE_UNFIX (dev->adf->x_range.max), + SANE_UNFIX (dev->adf->y_range.max)); + log_info (" ADF: %s, %s feed type", + (EXT_STATUS_ADFS & dev->ext_status ? "duplex" : "simplex"), + (EXT_STATUS_ADFT & dev->ext_status ? "page" : "sheet")); + + // check for auto size detection support and cache the results + dev->adf->has_size_check = ((0 != dev->adf->doc_x) + && (0 != dev->adf->doc_y)); + + // handle the special case where some scanners claim to have auto + // document size detection support for flatbed but not for adf + if (dev->fbf && (dev->fbf->has_size_check && !dev->adf->has_size_check)) + { + cmd_control_option_unit (dev, 0x01); // turn on ADF + dev_request_extended_status (dev); // now re-query scanner + // re-check for document size detection support + dev->adf->has_size_check = ((0 != dev->adf->doc_x) + && (0 != dev->adf->doc_y)); + cmd_control_option_unit (dev, 0x00); // turn off ADF + } + } + if (dev->tpu) + { + log_info (" TPU: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]", + SANE_UNFIX (dev->tpu->x_range.min), + SANE_UNFIX (dev->tpu->y_range.min), + SANE_UNFIX (dev->tpu->x_range.max), + SANE_UNFIX (dev->tpu->y_range.max)); + + // check for auto size detection support and cache the results + dev->tpu->has_size_check = ((0 != dev->tpu->doc_x) + && (0 != dev->tpu->doc_y)); + } + + + /* establish defaults */ + dev->need_reset_on_source_change = SANE_FALSE; + + if (strcmp_c ("ES-9000H", dev->fw_name) == 0 || + strcmp_c ("GT-30000", dev->fw_name) == 0) + { + dev->cmd->set_focus_position = 0; + dev->cmd->feed = 0x19; + } + else if (strcmp_c ("GT-8200", dev->fw_name) == 0 || + strcmp_c ("Perfection1640", dev->fw_name) == 0 || + strcmp_c ("GT-8700", dev->fw_name) == 0) + { + dev->cmd->feed = 0; + dev->cmd->set_focus_position = 0; + dev->need_reset_on_source_change = SANE_TRUE; + } + + { /* set up supported scan sources */ + int i = 0; + if (dev->fbf) dev->sources[i++] = FBF_STR; + if (dev->adf) dev->sources[i++] = ADF_STR; + if (dev->tpu) dev->sources[i++] = TPU_STR; + dev->sources[i] = NULL; /* terminator */ + } + + if (!dev->src) dev->src = (extension *) dev->fbf; + if (!dev->src) dev->src = (extension *) dev->adf; + if (!dev->src) dev->src = (extension *) dev->tpu; + + require (dev->src); + + if (!using (dev, fbf)) + dev_set_option_unit (dev, false); + + dev->polling_time = DEFAULT_POLLING_TIME; + if (push_button_needs_polling (dev) + || maintenance_is_supported (dev)) + { + dev->polling_time = (250 * 1000); + } + + *devp = dev; + + return SANE_STATUS_GOOD; +} /* create epson device */ + + +static SANE_Status +create_epson_scanner (device *dev, Epson_Scanner **scanner) +{ + Epson_Scanner *s = t_calloc (1, Epson_Scanner); + + if (!s) + { + err_fatal ("%s", strerror (errno)); + return SANE_STATUS_NO_MEM; + } + + s->hw = dev; + s->src = &s->raw; + + if (s->hw->cmd->request_identity2 != 0) + { + /* reset constraints because we are pointing to different lists now + FIXME: Only needs to be done the first time we (successfully) + send the ESC i command. This should be done when constructing + the device and is therefore done by the time we construct a + scanner. While the content and size of the lists may vary + depending on the selected option, the list nature is constant. + Hmm, we may actually be zapping the lists ... + */ + s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list; + s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list; + } + + s->frame_count = 0; + + promise (s->hw); + promise (s->src); + + *scanner = s; + return SANE_STATUS_GOOD; +} + + +/*! \todo Release resources in error recovery. + */ +static SANE_Status +create_sane_handle (Epson_Scanner **handle, const char *name, const void *dip) +{ + SANE_Status status = SANE_STATUS_GOOD; + device *dev = NULL; + Epson_Scanner *s = NULL; + channel *ch = NULL; + + dev = NULL; + + ch = channel_create (name, &status); + if (SANE_STATUS_GOOD != status) return status; + + ch->open (ch, &status); + if (SANE_STATUS_GOOD != status) return status; + + status = create_epson_device (&dev, ch); + if (SANE_STATUS_GOOD != status) + return status; + + status = create_epson_scanner (dev, &s); + if (!s || SANE_STATUS_GOOD != status) + return status; + + s->dip = dip; + init_options (s); + + *handle = s; + return SANE_STATUS_GOOD; +} + +static SANE_Status +init_options (Epson_Scanner * s) +{ + int i; + SANE_Bool dummy; + SANE_Bool reload; + + log_call (); + + for (i = 0; i < NUM_OPTIONS; ++i) + { + s->opt[i].size = sizeof (SANE_Word); + s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + s->val[OPT_NUM_OPTS].w = NUM_OPTIONS; + + /* "Scan Mode" group: */ + s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode"); + s->opt[OPT_MODE_GROUP].desc = ""; + s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_MODE_GROUP].cap = 0; + + /* scan mode */ + s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; + s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; + s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; + s->opt[OPT_MODE].type = SANE_TYPE_STRING; + s->opt[OPT_MODE].size = max_string_size (mode_list); + s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_MODE].constraint.string_list = mode_list; + s->val[OPT_MODE].w = 2; /* Color */ + + /* bit depth */ + s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH; + s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH; + s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH; + s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT; + s->opt[OPT_BIT_DEPTH].unit = SANE_UNIT_NONE; + s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_BIT_DEPTH].constraint.word_list = bitDepthList; + s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE; + s->val[OPT_BIT_DEPTH].w = bitDepthList[1]; /* the first "real" element is the default */ + + if (bitDepthList[0] == 1) /* only one element in the list -> hide the option */ + s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE; + + /* halftone */ + s->opt[OPT_HALFTONE].name = SANE_NAME_HALFTONE; + s->opt[OPT_HALFTONE].title = SANE_TITLE_HALFTONE; + s->opt[OPT_HALFTONE].desc = SANE_I18N ("Selects the halftone."); + + s->opt[OPT_HALFTONE].type = SANE_TYPE_STRING; + s->opt[OPT_HALFTONE].size = max_string_size (halftone_list_7); + s->opt[OPT_HALFTONE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + + if (s->hw->level >= 7) + s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_7; + else if (s->hw->level >= 4) + s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_4; + else + s->opt[OPT_HALFTONE].constraint.string_list = halftone_list; + + s->val[OPT_HALFTONE].w = 1; /* Halftone A */ + + if (!s->hw->cmd->set_halftoning) + { + s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE; + } + + /* dropout */ + s->opt[OPT_DROPOUT].name = "dropout"; + s->opt[OPT_DROPOUT].title = SANE_I18N ("Dropout"); + s->opt[OPT_DROPOUT].desc = SANE_I18N ("Selects the dropout."); + + s->opt[OPT_DROPOUT].type = SANE_TYPE_STRING; + s->opt[OPT_DROPOUT].size = max_string_size (dropout_list); + s->opt[OPT_DROPOUT].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_DROPOUT].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_DROPOUT].constraint.string_list = dropout_list; + s->val[OPT_DROPOUT].w = 0; /* None */ + + /* brightness algorithm selection */ + s->opt[OPT_BRIGHTNESS_METHOD].name = "brightness-method"; + s->opt[OPT_BRIGHTNESS_METHOD].title = SANE_I18N ("Brightness Method"); + s->opt[OPT_BRIGHTNESS_METHOD].desc = SANE_I18N ("Selects a method to change the brightness of the acquired image."); + s->opt[OPT_BRIGHTNESS_METHOD].type = SANE_TYPE_STRING; + s->opt[OPT_BRIGHTNESS_METHOD].unit = SANE_UNIT_NONE; + s->opt[OPT_BRIGHTNESS_METHOD].size = max_string_size (brightness_method_list); + s->opt[OPT_BRIGHTNESS_METHOD].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_BRIGHTNESS_METHOD].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list = brightness_method_list; s->val[OPT_BRIGHTNESS_METHOD].w = 0; /* first supported */ + + if (!s->hw->cmd->set_bright) /* skip "hardware" */ + ++s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list; + + /* brightness */ + s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT; + s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE; + s->opt[OPT_BRIGHTNESS].size = sizeof (SANE_Int); + s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; + s->val[OPT_BRIGHTNESS].w = 0; /* neutral */ + + if (s->hw->cmd->set_bright) + { + s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range; + } + else + { + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED; + s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range; + } + + /* contrast */ + s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; + s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; + s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; + s->opt[OPT_CONTRAST].type = SANE_TYPE_INT; + s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE; + s->opt[OPT_CONTRAST].size = sizeof (SANE_Int); + s->opt[OPT_CONTRAST].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CONTRAST].constraint.range = &contrast_range; + s->val[OPT_CONTRAST].w = 0; /* neutral */ + + /* sharpness */ + s->opt[OPT_SHARPNESS].name = "sharpness"; + s->opt[OPT_SHARPNESS].title = SANE_I18N ("Sharpness"); + s->opt[OPT_SHARPNESS].desc = ""; + + s->opt[OPT_SHARPNESS].type = SANE_TYPE_INT; + s->opt[OPT_SHARPNESS].unit = SANE_UNIT_NONE; + s->opt[OPT_SHARPNESS].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_SHARPNESS].constraint.range = &outline_emphasis_range; + s->val[OPT_SHARPNESS].w = 0; /* Normal */ + + if (!s->hw->cmd->set_outline_emphasis) + { + s->opt[OPT_SHARPNESS].cap |= SANE_CAP_INACTIVE; + } + + /* gamma */ + s->opt[OPT_GAMMA_CORRECTION].name = SANE_NAME_GAMMA_CORRECTION; + s->opt[OPT_GAMMA_CORRECTION].title = SANE_TITLE_GAMMA_CORRECTION; + s->opt[OPT_GAMMA_CORRECTION].desc = SANE_DESC_GAMMA_CORRECTION; + + s->opt[OPT_GAMMA_CORRECTION].type = SANE_TYPE_STRING; + s->opt[OPT_GAMMA_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST; + /* + * special handling for D1 function level - at this time I'm not + * testing for D1, I'm just assuming that all D level scanners will + * behave the same way. This has to be confirmed with the next D-level + * scanner + */ + if (s->hw->cmd->level[0] == 'D') + { + s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_d); + s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_d; + s->val[OPT_GAMMA_CORRECTION].w = 1; /* Default */ + s->hw->gamma_user_defined = gamma_userdefined_d; + s->hw->gamma_type = gamma_params_d; + } + else + { + s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_ab); + s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_ab; + s->val[OPT_GAMMA_CORRECTION].w = 0; /* Default */ + s->hw->gamma_user_defined = gamma_userdefined_ab; + s->hw->gamma_type = gamma_params_ab; + } + + if (!s->hw->cmd->set_gamma) + { + s->opt[OPT_GAMMA_CORRECTION].cap |= SANE_CAP_INACTIVE; + } + + /* red gamma vector */ + s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R; + + s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_R].wa = &s->gamma_table[0][0]; + + /* green gamma vector */ + s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G; + + s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_G].wa = &s->gamma_table[1][0]; + + /* red gamma vector */ + s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B; + + s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_B].wa = &s->gamma_table[2][0]; + + if (s->hw->cmd->set_gamma_table && + s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w]) + { + s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE; + } + + /* initialize the Gamma tables */ + memset (&s->gamma_table[0], 0, 256 * sizeof (SANE_Word)); + memset (&s->gamma_table[1], 0, 256 * sizeof (SANE_Word)); + memset (&s->gamma_table[2], 0, 256 * sizeof (SANE_Word)); + for (i = 0; i < 256; i++) + { + s->gamma_table[0][i] = i; + s->gamma_table[1][i] = i; + s->gamma_table[2][i] = i; + } + + /* color correction */ + s->opt[OPT_COLOR_CORRECTION].name = "color-correction"; + s->opt[OPT_COLOR_CORRECTION].title = SANE_I18N ("Color correction"); + s->opt[OPT_COLOR_CORRECTION].desc = + SANE_I18N + ("Sets the color correction table for the selected output device."); + + s->opt[OPT_COLOR_CORRECTION].type = SANE_TYPE_STRING; + s->opt[OPT_COLOR_CORRECTION].size = 32; + s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_COLOR_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST; + if (s->hw->cmd->level[0] == 'D') + { + s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_d); + s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_d; + s->val[OPT_COLOR_CORRECTION].w = 0; /* Default */ + s->hw->color_user_defined = color_userdefined_d; + s->hw->color_type = color_params_d; + } + else + { + s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_ab); + s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_ab; + s->val[OPT_COLOR_CORRECTION].w = 5; /* scanner default: CRT monitors */ + s->hw->color_user_defined = color_userdefined_ab; + s->hw->color_type = color_params_ab; + } + + if (!s->hw->cmd->set_color_correction) + { + s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_INACTIVE; + } + + /* resolution */ + s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; + s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; + s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; + s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT; + s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI; + + if (s->hw->cmd->level[0] != 'D') + { + s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range; + } + else + { + s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->res.list; + } + handle_resolution (s, OPT_RESOLUTION, DEFAULT_RESOLUTION); + +#undef SANE_NAME_SCAN_X_RESOLUTION +#define SANE_NAME_SCAN_X_RESOLUTION "x-resolution" + + /* resolution in main scan direction */ + s->opt[OPT_X_RESOLUTION].name = SANE_NAME_SCAN_X_RESOLUTION; + s->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION; + s->opt[OPT_X_RESOLUTION].desc = SANE_DESC_SCAN_X_RESOLUTION; + s->opt[OPT_X_RESOLUTION].type = SANE_TYPE_INT; + s->opt[OPT_X_RESOLUTION].unit = SANE_UNIT_DPI; + + if (s->hw->cmd->level[0] != 'D') + { + s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_X_RESOLUTION].constraint.range = &s->hw->dpi_range; + } + else + { + s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list; + s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_ADVANCED; + } + handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION); + + /* resolution in sub scan direction */ + s->opt[OPT_Y_RESOLUTION].name = SANE_NAME_SCAN_Y_RESOLUTION; + s->opt[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION; + s->opt[OPT_Y_RESOLUTION].desc = SANE_DESC_SCAN_Y_RESOLUTION; + s->opt[OPT_Y_RESOLUTION].type = SANE_TYPE_INT; + s->opt[OPT_Y_RESOLUTION].unit = SANE_UNIT_DPI; + + if (s->hw->cmd->level[0] != 'D') + { + s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_Y_RESOLUTION].constraint.range = &s->hw->dpi_range; + } + else + { + s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list; + s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_ADVANCED; + } + handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION); + + switch (s->hw->productID) + { + /* We already use kludges for a number of models and don't want to + be burdened with rechecking their functionality. Really fixing + their support requires more changes than merely adding main/sub + resolution lists. + */ + case 0x0116: + case 0x0118: + case 0x0119: + case 0x012b: + s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE; + break; + default: + /* use the default capability set at the top of this function */ + ; + } + + /* threshold */ + s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD; + s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD; + s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD; + + s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT; + s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE; + s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_THRESHOLD].constraint.range = &u8_range; + s->val[OPT_THRESHOLD].w = 0x80; + + if (!s->hw->cmd->set_threshold) + { + s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_CCT_GROUP].title = SANE_I18N ("Color correction coefficients"); + s->opt[OPT_CCT_GROUP].desc = SANE_I18N ("Matrix multiplication of RGB"); + s->opt[OPT_CCT_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_CCT_GROUP].cap = SANE_CAP_ADVANCED; + + /* color correction coefficients */ + s->opt[OPT_CCT_1].name = "cct-1"; + s->opt[OPT_CCT_2].name = "cct-2"; + s->opt[OPT_CCT_3].name = "cct-3"; + s->opt[OPT_CCT_4].name = "cct-4"; + s->opt[OPT_CCT_5].name = "cct-5"; + s->opt[OPT_CCT_6].name = "cct-6"; + s->opt[OPT_CCT_7].name = "cct-7"; + s->opt[OPT_CCT_8].name = "cct-8"; + s->opt[OPT_CCT_9].name = "cct-9"; + + s->opt[OPT_CCT_1].title = SANE_I18N ("Red"); + s->opt[OPT_CCT_2].title = SANE_I18N ("Shift green to red"); + s->opt[OPT_CCT_3].title = SANE_I18N ("Shift blue to red"); + s->opt[OPT_CCT_4].title = SANE_I18N ("Shift red to green"); + s->opt[OPT_CCT_5].title = SANE_I18N ("Green"); + s->opt[OPT_CCT_6].title = SANE_I18N ("Shift blue to green"); + s->opt[OPT_CCT_7].title = SANE_I18N ("Shift red to blue"); + s->opt[OPT_CCT_8].title = SANE_I18N ("Shift green to blue"); + s->opt[OPT_CCT_9].title = SANE_I18N ("Blue"); + + s->opt[OPT_CCT_1].desc = SANE_I18N ("Controls red level"); + s->opt[OPT_CCT_2].desc = SANE_I18N ("Adds to red based on green level"); + s->opt[OPT_CCT_3].desc = SANE_I18N ("Adds to red based on blue level"); + s->opt[OPT_CCT_4].desc = SANE_I18N ("Adds to green based on red level"); + s->opt[OPT_CCT_5].desc = SANE_I18N ("Controls green level"); + s->opt[OPT_CCT_6].desc = SANE_I18N ("Adds to green based on blue level"); + s->opt[OPT_CCT_7].desc = SANE_I18N ("Adds to blue based on red level"); + s->opt[OPT_CCT_8].desc = SANE_I18N ("Adds to blue based on green level"); + s->opt[OPT_CCT_9].desc = SANE_I18N ("Control blue level"); + + s->opt[OPT_CCT_1].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_2].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_3].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_4].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_5].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_6].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_7].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_8].type = SANE_TYPE_FIXED; + s->opt[OPT_CCT_9].type = SANE_TYPE_FIXED; + + s->opt[OPT_CCT_1].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_2].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_3].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_4].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_5].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_6].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_7].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_8].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CCT_9].cap |= SANE_CAP_ADVANCED; + + s->opt[OPT_CCT_1].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_2].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_3].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_4].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_5].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_6].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_7].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_8].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CCT_9].cap |= SANE_CAP_INACTIVE; + + if (!s->hw->cmd->set_color_correction_coefficients) + { + s->opt[OPT_CCT_1].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_2].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_3].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_4].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_5].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_6].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_7].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_8].cap |= SANE_CAP_EMULATED; + s->opt[OPT_CCT_9].cap |= SANE_CAP_EMULATED; + } + + s->opt[OPT_CCT_1].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_2].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_3].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_4].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_5].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_6].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_7].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_8].unit = SANE_UNIT_NONE; + s->opt[OPT_CCT_9].unit = SANE_UNIT_NONE; + + s->opt[OPT_CCT_1].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_2].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_3].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_4].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_5].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_6].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_7].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_8].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CCT_9].constraint_type = SANE_CONSTRAINT_RANGE; + + s->hw->matrix_range.min = SANE_FIX (-2.0); + s->hw->matrix_range.max = SANE_FIX (2.0); + s->hw->matrix_range.quant = 0; + + s->opt[OPT_CCT_1].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_2].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_3].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_4].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_5].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_6].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_7].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_8].constraint.range = &s->hw->matrix_range; + s->opt[OPT_CCT_9].constraint.range = &s->hw->matrix_range; + + s->val[OPT_CCT_1].w = SANE_FIX (1.0); + s->val[OPT_CCT_2].w = 0; + s->val[OPT_CCT_3].w = 0; + s->val[OPT_CCT_4].w = 0; + s->val[OPT_CCT_5].w = SANE_FIX (1.0); + s->val[OPT_CCT_6].w = 0; + s->val[OPT_CCT_7].w = 0; + s->val[OPT_CCT_8].w = 0; + s->val[OPT_CCT_9].w = SANE_FIX (1.0); + + /* "Advanced" group: */ + s->opt[OPT_ADVANCED_GROUP].title = SANE_I18N ("Advanced"); + s->opt[OPT_ADVANCED_GROUP].desc = ""; + s->opt[OPT_ADVANCED_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_ADVANCED_GROUP].cap = SANE_CAP_ADVANCED; + + /* mirror */ + s->opt[OPT_MIRROR].name = "mirror"; + s->opt[OPT_MIRROR].title = SANE_I18N ("Mirror image"); + s->opt[OPT_MIRROR].desc = SANE_I18N ("Mirror the image."); + + s->opt[OPT_MIRROR].type = SANE_TYPE_BOOL; + s->val[OPT_MIRROR].w = SANE_FALSE; + + if (!s->hw->cmd->mirror_image) + { + s->opt[OPT_MIRROR].cap |= SANE_CAP_INACTIVE; + } + + /* speed */ + s->opt[OPT_SPEED].name = SANE_NAME_SCAN_SPEED; + s->opt[OPT_SPEED].title = SANE_TITLE_SCAN_SPEED; + s->opt[OPT_SPEED].desc = SANE_DESC_SCAN_SPEED; + + s->opt[OPT_SPEED].type = SANE_TYPE_BOOL; + s->val[OPT_SPEED].w = SANE_FALSE; + + if (!s->hw->cmd->set_speed) + { + s->opt[OPT_SPEED].cap |= SANE_CAP_INACTIVE; + } + + /* preview speed */ + s->opt[OPT_PREVIEW_SPEED].name = "preview-speed"; + s->opt[OPT_PREVIEW_SPEED].title = SANE_I18N ("Speed"); + s->opt[OPT_PREVIEW_SPEED].desc = ""; + + s->opt[OPT_PREVIEW_SPEED].type = SANE_TYPE_BOOL; + s->val[OPT_PREVIEW_SPEED].w = SANE_FALSE; + + if (!s->hw->cmd->set_speed) + { + s->opt[OPT_PREVIEW_SPEED].cap |= SANE_CAP_INACTIVE; + } + + /* auto area segmentation */ + s->opt[OPT_AAS].name = "auto-area-segmentation"; + s->opt[OPT_AAS].title = SANE_I18N ("Auto area segmentation"); + s->opt[OPT_AAS].desc = ""; + + s->opt[OPT_AAS].type = SANE_TYPE_BOOL; + s->val[OPT_AAS].w = SANE_TRUE; + + if (!s->hw->cmd->control_auto_area_segmentation) + { + s->opt[OPT_AAS].cap |= SANE_CAP_INACTIVE; + } + + /* limit resolution list */ + s->opt[OPT_LIMIT_RESOLUTION].name = "short-resolution"; + s->opt[OPT_LIMIT_RESOLUTION].title = SANE_I18N ("Short resolution list"); + s->opt[OPT_LIMIT_RESOLUTION].desc = + SANE_I18N ("Display short resolution list"); + s->opt[OPT_LIMIT_RESOLUTION].type = SANE_TYPE_BOOL; + s->val[OPT_LIMIT_RESOLUTION].w = SANE_FALSE; + + if (SANE_CONSTRAINT_WORD_LIST != s->opt[OPT_RESOLUTION].constraint_type) + { + s->opt[OPT_LIMIT_RESOLUTION].cap |= SANE_CAP_INACTIVE; + } + + /* zoom */ + s->opt[OPT_ZOOM].name = "zoom"; + s->opt[OPT_ZOOM].title = SANE_I18N ("Zoom"); + s->opt[OPT_ZOOM].desc = + SANE_I18N ("Defines the zoom factor the scanner will use"); + + s->opt[OPT_ZOOM].type = SANE_TYPE_INT; + s->opt[OPT_ZOOM].unit = SANE_UNIT_NONE; + s->opt[OPT_ZOOM].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_ZOOM].constraint.range = &zoom_range; + s->val[OPT_ZOOM].w = 100; + + if (s->hw->using_fs) s->hw->cmd->set_zoom = 0; + + if (!s->hw->cmd->set_zoom) + { + s->opt[OPT_ZOOM].cap |= SANE_CAP_INACTIVE; + } + + /* "Preview settings" group: */ + s->opt[OPT_PREVIEW_GROUP].title = SANE_TITLE_PREVIEW; + s->opt[OPT_PREVIEW_GROUP].desc = ""; + s->opt[OPT_PREVIEW_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_PREVIEW_GROUP].cap = SANE_CAP_ADVANCED; + + /* preview */ + s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; + s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + + s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; + s->val[OPT_PREVIEW].w = SANE_FALSE; + + /* "Geometry" group: */ + s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry"); + s->opt[OPT_GEOMETRY_GROUP].desc = ""; + s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; + + /* media size oriented scan area setting */ + s->opt[OPT_SCAN_AREA].name = "scan-area"; + s->opt[OPT_SCAN_AREA].title = SANE_I18N ("Scan area"); + s->opt[OPT_SCAN_AREA].desc = + SANE_I18N ("Select an area to scan based on well-known media sizes."); + s->opt[OPT_SCAN_AREA].type = SANE_TYPE_STRING; + s->opt[OPT_SCAN_AREA].size = 0; + s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_SCAN_AREA].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_SCAN_AREA].constraint.string_list = NULL; + s->val[OPT_SCAN_AREA].w = 0; + + /* Quick format */ + s->opt[OPT_QUICK_FORMAT].name = "quick-format"; + s->opt[OPT_QUICK_FORMAT].title = SANE_I18N ("Quick format"); + s->opt[OPT_QUICK_FORMAT].desc = + SANE_I18N ("Select an area to scan based on well-known media sizes. (DEPRECATED)"); + s->opt[OPT_QUICK_FORMAT].type = SANE_TYPE_STRING; + s->opt[OPT_QUICK_FORMAT].size = 0; + s->opt[OPT_QUICK_FORMAT].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_QUICK_FORMAT].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_QUICK_FORMAT].constraint.string_list = NULL; + s->val[OPT_QUICK_FORMAT].w = 0; + + handle_scan_area (s, 0); /* divines device/setting dependent bits */ + + /* top-left x */ + s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; + s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; + s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; + + s->opt[OPT_TL_X].type = SANE_TYPE_FIXED; + s->opt[OPT_TL_X].unit = SANE_UNIT_MM; + s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range); + s->val[OPT_TL_X].w = 0; + + /* top-left y */ + s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; + s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; + s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; + + s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; + s->opt[OPT_TL_Y].unit = SANE_UNIT_MM; + s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range); + s->val[OPT_TL_Y].w = 0; + + /* bottom-right x */ + s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; + s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; + s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; + + s->opt[OPT_BR_X].type = SANE_TYPE_FIXED; + s->opt[OPT_BR_X].unit = SANE_UNIT_MM; + s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range); + s->val[OPT_BR_X].w = s->hw->src->x_range.max; + + /* bottom-right y */ + s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; + s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; + s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; + + s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; + s->opt[OPT_BR_Y].unit = SANE_UNIT_MM; + s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range); + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + + /* "Optional equipment" group: */ + s->opt[OPT_EQU_GROUP].title = SANE_I18N ("Optional equipment"); + s->opt[OPT_EQU_GROUP].desc = ""; + s->opt[OPT_EQU_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_EQU_GROUP].cap = SANE_CAP_ADVANCED; + + /* source */ + s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE; + s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE; + s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE; + + s->opt[OPT_SOURCE].type = SANE_TYPE_STRING; + s->opt[OPT_SOURCE].size = max_string_size (s->hw->sources); + + s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_SOURCE].constraint.string_list = s->hw->sources; + + if (!s->hw->sources[1]) /* two or more scan sources */ + { + s->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE; + } + s->val[OPT_SOURCE].w = 0; + + + /* film type */ + s->opt[OPT_FILM_TYPE].name = "film-type"; + s->opt[OPT_FILM_TYPE].title = SANE_I18N ("Film type"); + s->opt[OPT_FILM_TYPE].desc = ""; + + s->opt[OPT_FILM_TYPE].type = SANE_TYPE_STRING; + s->opt[OPT_FILM_TYPE].size = max_string_size (film_list); + + s->opt[OPT_FILM_TYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_FILM_TYPE].constraint.string_list = film_list; + + s->val[OPT_FILM_TYPE].w = 0; + + deactivateOption (s, OPT_FILM_TYPE, &dummy); /* default is inactive */ + + /* focus position */ + s->opt[OPT_FOCUS].name = SANE_EPSON_FOCUS_NAME; + s->opt[OPT_FOCUS].title = SANE_EPSON_FOCUS_TITLE; + s->opt[OPT_FOCUS].desc = SANE_EPSON_FOCUS_DESC; + s->opt[OPT_FOCUS].type = SANE_TYPE_STRING; + s->opt[OPT_FOCUS].size = max_string_size (focus_list); + s->opt[OPT_FOCUS].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_FOCUS].constraint.string_list = focus_list; + s->val[OPT_FOCUS].w = 0; + + s->opt[OPT_FOCUS].cap |= SANE_CAP_ADVANCED; + if (s->hw->tpu && s->hw->tpu->has_focus) + { + s->opt[OPT_FOCUS].cap &= ~SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_FOCUS].cap |= SANE_CAP_INACTIVE; + } + + /* forward feed / eject */ + s->opt[OPT_EJECT].name = "eject"; + s->opt[OPT_EJECT].title = SANE_I18N ("Eject"); + s->opt[OPT_EJECT].desc = SANE_I18N ("Eject the sheet in the ADF"); + + s->opt[OPT_EJECT].type = SANE_TYPE_BUTTON; + + if ((!s->hw->adf) && (!s->hw->cmd->set_bay)) + { /* Hack: Using set_bay to indicate. */ + s->opt[OPT_EJECT].cap |= SANE_CAP_INACTIVE; + } + + /* auto forward feed / eject */ + s->opt[OPT_AUTO_EJECT].name = "auto-eject"; + s->opt[OPT_AUTO_EJECT].title = SANE_I18N ("Auto eject"); + s->opt[OPT_AUTO_EJECT].desc = SANE_I18N ("Eject document after scanning"); + + s->opt[OPT_AUTO_EJECT].type = SANE_TYPE_BOOL; + s->val[OPT_AUTO_EJECT].w = SANE_TRUE; + + if (s->hw->adf) s->hw->adf->auto_eject = SANE_TRUE; + + if (!s->hw->adf) + { + s->opt[OPT_AUTO_EJECT].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_ADF_MODE].name = "adf-mode"; + s->opt[OPT_ADF_MODE].title = SANE_I18N ("ADF Mode"); + s->opt[OPT_ADF_MODE].desc = + SANE_I18N ("Selects the ADF mode (simplex/duplex)"); + s->opt[OPT_ADF_MODE].type = SANE_TYPE_STRING; + s->opt[OPT_ADF_MODE].size = max_string_size (adf_mode_list); + s->opt[OPT_ADF_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_ADF_MODE].constraint.string_list = adf_mode_list; + s->val[OPT_ADF_MODE].w = 0; /* simplex */ + + if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status))) + { + s->opt[OPT_ADF_MODE].cap |= SANE_CAP_INACTIVE; + } + + /* select bay */ + s->opt[OPT_BAY].name = "bay"; + s->opt[OPT_BAY].title = SANE_I18N ("Bay"); + s->opt[OPT_BAY].desc = SANE_I18N ("Select bay to scan"); + + s->opt[OPT_BAY].type = SANE_TYPE_STRING; + s->opt[OPT_BAY].size = max_string_size (bay_list); + s->opt[OPT_BAY].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_BAY].constraint.string_list = bay_list; + s->val[OPT_BAY].w = 0; /* Bay 1 */ + + if (!s->hw->cmd->set_bay) + { + s->opt[OPT_BAY].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_WAIT_FOR_BUTTON].name = SANE_EPSON_WAIT_FOR_BUTTON_NAME; + s->opt[OPT_WAIT_FOR_BUTTON].title = SANE_EPSON_WAIT_FOR_BUTTON_TITLE; + s->opt[OPT_WAIT_FOR_BUTTON].desc = SANE_EPSON_WAIT_FOR_BUTTON_DESC; + + s->opt[OPT_WAIT_FOR_BUTTON].type = SANE_TYPE_BOOL; + s->opt[OPT_WAIT_FOR_BUTTON].unit = SANE_UNIT_NONE; + s->opt[OPT_WAIT_FOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_WAIT_FOR_BUTTON].constraint.range = NULL; + s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_ADVANCED; + s->val[OPT_WAIT_FOR_BUTTON].w = SANE_FALSE; + + if (!s->hw->cmd->request_push_button_status + || push_button_is_black_listed (s->hw)) + { + s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_INACTIVE; + } + else if (push_button_needs_polling (s->hw)) + { + s->val[OPT_WAIT_FOR_BUTTON].w = SANE_TRUE; + } + + s->opt[OPT_MONITOR_BUTTON].name = SANE_EPSON_MONITOR_BUTTON_NAME; + s->opt[OPT_MONITOR_BUTTON].title = SANE_EPSON_MONITOR_BUTTON_TITLE; + s->opt[OPT_MONITOR_BUTTON].desc = SANE_EPSON_MONITOR_BUTTON_DESC; + + s->opt[OPT_MONITOR_BUTTON].type = SANE_TYPE_BOOL; + s->opt[OPT_MONITOR_BUTTON].unit = SANE_UNIT_NONE; + s->opt[OPT_MONITOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_MONITOR_BUTTON].constraint.range = NULL; + s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_MONITOR_BUTTON].cap &= ~SANE_CAP_SOFT_SELECT; + s->val[OPT_MONITOR_BUTTON].w = SANE_FALSE; + + if (!s->hw->cmd->request_push_button_status + || push_button_is_black_listed (s->hw)) + { + s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_POLLING_TIME].name = SANE_EPSON_POLLING_TIME_NAME; + s->opt[OPT_POLLING_TIME].title = SANE_EPSON_POLLING_TIME_TITLE; + s->opt[OPT_POLLING_TIME].desc = SANE_EPSON_POLLING_TIME_DESC; + + s->opt[OPT_POLLING_TIME].type = SANE_TYPE_INT; + s->opt[OPT_POLLING_TIME].unit = SANE_UNIT_MICROSECOND; + s->opt[OPT_POLLING_TIME].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_POLLING_TIME].cap &= ~SANE_CAP_SOFT_SELECT; + s->val[OPT_POLLING_TIME].w = s->hw->polling_time; + + if (!s->hw->cmd->request_push_button_status + || push_button_is_black_listed (s->hw)) + { + s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_NEEDS_POLLING].name = SANE_EPSON_NEEDS_POLLING_NAME; + s->opt[OPT_NEEDS_POLLING].title = SANE_EPSON_NEEDS_POLLING_TITLE; + s->opt[OPT_NEEDS_POLLING].desc = SANE_EPSON_NEEDS_POLLING_DESC; + + s->opt[OPT_NEEDS_POLLING].type = SANE_TYPE_BOOL; + s->opt[OPT_NEEDS_POLLING].unit = SANE_UNIT_NONE; + s->opt[OPT_NEEDS_POLLING].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_NEEDS_POLLING].cap &= ~SANE_CAP_SOFT_SELECT; + s->val[OPT_NEEDS_POLLING].w = SANE_FALSE; + + if (!s->hw->cmd->request_push_button_status + || push_button_is_black_listed (s->hw)) + { + s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_INACTIVE; + } + else if (push_button_needs_polling (s->hw)) + { + s->val[OPT_NEEDS_POLLING].w = SANE_TRUE; + } + + s->opt[OPT_DETECT_DOC_SIZE].name = SANE_EPSON_DETECT_DOC_SIZE_NAME; + s->opt[OPT_DETECT_DOC_SIZE].title = SANE_EPSON_DETECT_DOC_SIZE_TITLE; + s->opt[OPT_DETECT_DOC_SIZE].desc = SANE_EPSON_DETECT_DOC_SIZE_DESC; + + s->opt[OPT_DETECT_DOC_SIZE].type = SANE_TYPE_BOOL; + s->opt[OPT_DETECT_DOC_SIZE].unit = SANE_UNIT_NONE; + s->opt[OPT_DETECT_DOC_SIZE].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_DETECT_DOC_SIZE].constraint.range = NULL; + s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_ADVANCED; + s->val[OPT_DETECT_DOC_SIZE].w = SANE_FALSE; + + if (!has_size_check_support (s->hw->src)) + { + s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_SCAN_AREA_IS_VALID].name = SANE_EPSON_SCAN_AREA_IS_VALID_NAME; + s->opt[OPT_SCAN_AREA_IS_VALID].title = SANE_EPSON_SCAN_AREA_IS_VALID_TITLE; + s->opt[OPT_SCAN_AREA_IS_VALID].desc = SANE_EPSON_SCAN_AREA_IS_VALID_DESC; + + s->opt[OPT_SCAN_AREA_IS_VALID].type = SANE_TYPE_BOOL; + s->opt[OPT_SCAN_AREA_IS_VALID].unit = SANE_UNIT_NONE; + s->opt[OPT_SCAN_AREA_IS_VALID].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_SCAN_AREA_IS_VALID].constraint.range = NULL; + s->opt[OPT_SCAN_AREA_IS_VALID].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + s->val[OPT_SCAN_AREA_IS_VALID].w = SANE_FALSE; + + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].name = + SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_NAME; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].title = + SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_TITLE; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].desc = + SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_DESC; + + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].type = SANE_TYPE_BOOL; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].unit = SANE_UNIT_NONE; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint_type = + SANE_CONSTRAINT_NONE; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint.range = NULL; + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + s->val[OPT_ADF_DUPLEX_DIRECTION_MATCHES].w = SANE_FALSE; + + if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status))) + { + s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_ADF_AUTO_SCAN].name = SANE_EPSON_ADF_AUTO_SCAN_NAME; + s->opt[OPT_ADF_AUTO_SCAN].title = SANE_EPSON_ADF_AUTO_SCAN_TITLE; + s->opt[OPT_ADF_AUTO_SCAN].desc = SANE_EPSON_ADF_AUTO_SCAN_DESC; + + s->opt[OPT_ADF_AUTO_SCAN].type = SANE_TYPE_BOOL; + s->opt[OPT_ADF_AUTO_SCAN].unit = SANE_UNIT_NONE; + s->opt[OPT_ADF_AUTO_SCAN].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_ADF_AUTO_SCAN].constraint.range = NULL; + s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_ADVANCED; + s->val[OPT_ADF_AUTO_SCAN].w = SANE_FALSE; + + if (!using (s->hw, adf) || !(FSI_CAP_ADFAS & s->hw->fsi_cap_2)) + { + s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_ADF_DFD_SENSITIVITY].name = SANE_EPSON_ADF_DFD_SENSITIVITY_NAME; + s->opt[OPT_ADF_DFD_SENSITIVITY].title = SANE_EPSON_ADF_DFD_SENSITIVITY_TITLE; + s->opt[OPT_ADF_DFD_SENSITIVITY].desc = SANE_EPSON_ADF_DFD_SENSITIVITY_DESC; + + s->opt[OPT_ADF_DFD_SENSITIVITY].type = SANE_TYPE_STRING; + s->opt[OPT_ADF_DFD_SENSITIVITY].unit = SANE_UNIT_NONE; + s->opt[OPT_ADF_DFD_SENSITIVITY].size = max_string_size (dfd_sensitivity); + s->opt[OPT_ADF_DFD_SENSITIVITY].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_ADF_DFD_SENSITIVITY].constraint.string_list = dfd_sensitivity; + s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_ADVANCED; + s->val[OPT_ADF_DFD_SENSITIVITY].w = 0; + + if (!using (s->hw, adf) || !(FSI_CAP_DFD & s->hw->fsi_cap_2)) + { + s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_EXT_SANE_STATUS].name = "ext-sane-status"; + s->opt[OPT_EXT_SANE_STATUS].title = "Extended SANE Status"; + s->opt[OPT_EXT_SANE_STATUS].desc = "Ugly kludge to provide additional status message strings to a frontend."; + + s->opt[OPT_EXT_SANE_STATUS].type = SANE_TYPE_INT; + s->opt[OPT_EXT_SANE_STATUS].unit = SANE_UNIT_NONE; + s->opt[OPT_EXT_SANE_STATUS].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_EXT_SANE_STATUS].constraint.range = &ext_sane_status; + s->opt[OPT_EXT_SANE_STATUS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + s->val[OPT_EXT_SANE_STATUS].w = 0; + + /* deskew */ + s->opt[OPT_DESKEW].name = "deskew"; + s->opt[OPT_DESKEW].title = SANE_I18N ("Deskew"); + s->opt[OPT_DESKEW].desc = SANE_I18N ("Rotate image so it appears upright."); + s->opt[OPT_DESKEW].type = SANE_TYPE_BOOL; + s->opt[OPT_DESKEW].unit = SANE_UNIT_NONE; + s->opt[OPT_DESKEW].size = sizeof (SANE_Bool); + s->opt[OPT_DESKEW].cap |= SANE_CAP_EMULATED; + s->opt[OPT_DESKEW].cap |= SANE_CAP_ADVANCED; + if ( dip_has_deskew (s->dip, s->hw) ) + { + s->opt[OPT_DESKEW].cap &= ~SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_DESKEW].cap |= SANE_CAP_INACTIVE; + } + s->opt[OPT_DESKEW].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_DESKEW].w = SANE_FALSE; + + /* auto-crop */ + s->opt[OPT_AUTOCROP].name = "autocrop"; + s->opt[OPT_AUTOCROP].title = SANE_I18N ("Trim image to paper size"); + s->opt[OPT_AUTOCROP].desc = SANE_I18N ("Determines empty margins in the scanned image and removes them. This normally reduces the image to the size of the original document but may remove more."); + s->opt[OPT_AUTOCROP].type = SANE_TYPE_BOOL; + s->opt[OPT_AUTOCROP].unit = SANE_UNIT_NONE; + s->opt[OPT_AUTOCROP].size = sizeof (SANE_Bool); + s->opt[OPT_AUTOCROP].cap |= SANE_CAP_EMULATED; + s->opt[OPT_AUTOCROP].cap |= SANE_CAP_ADVANCED; + if ( dip_has_autocrop (s->dip, s->hw) ) + { + s->opt[OPT_AUTOCROP].cap &= ~SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_AUTOCROP].cap |= SANE_CAP_INACTIVE; + } + s->opt[OPT_AUTOCROP].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_AUTOCROP].w = SANE_FALSE; + + s->opt[OPT_CALIBRATE].name = SANE_EPSON_CALIBRATE_NAME; + s->opt[OPT_CALIBRATE].title = SANE_EPSON_CALIBRATE_TITLE; + s->opt[OPT_CALIBRATE].desc = SANE_EPSON_CALIBRATE_DESC; + s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON; + s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE; + s->opt[OPT_CALIBRATE].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CALIBRATE].constraint_type = SANE_CONSTRAINT_NONE; + if (!maintenance_is_supported (s->hw)) + { + s->opt[OPT_CALIBRATE].cap |= SANE_CAP_INACTIVE; + } + + s->opt[OPT_CLEAN].name = SANE_EPSON_CLEAN_NAME; + s->opt[OPT_CLEAN].title = SANE_EPSON_CLEAN_TITLE; + s->opt[OPT_CLEAN].desc = SANE_EPSON_CLEAN_DESC; + s->opt[OPT_CLEAN].type = SANE_TYPE_BUTTON; + s->opt[OPT_CLEAN].unit = SANE_UNIT_NONE; + s->opt[OPT_CLEAN].cap |= SANE_CAP_ADVANCED; + s->opt[OPT_CLEAN].constraint_type = SANE_CONSTRAINT_NONE; + if (!maintenance_is_supported (s->hw)) + { + s->opt[OPT_CLEAN].cap |= SANE_CAP_INACTIVE; + } + + handle_mode (s, s->val[OPT_MODE].w, &reload); + change_profile_matrix (s); + + /* adjust default settings based on configuration options */ + { + void *cfg = cfg_init (NULL, NULL); + if (cfg_has_value (cfg, CFG_KEY_OPTION, "prefer-adf")) + { + const char *found = NULL; + int i = 0; + while ((found = s->hw->sources[i]) + && 0 != strcmp_c (ADF_STR, found)) + { + ++i; + } + if (found) handle_source (s, i, ADF_STR); + } + } + + return SANE_STATUS_GOOD; +} + + +SANE_Status +epkowa_open (const char *name, SANE_Handle *handle, const void *dip) +{ + SANE_Status status = SANE_STATUS_GOOD; + Epson_Scanner *s = NULL; + + status = create_sane_handle (&s, name, dip); + if (SANE_STATUS_GOOD != status) return status; + + /* insert newly opened handle into list of open handles */ + s->next = first_handle; + first_handle = s; + + *handle = (SANE_Handle) s; + + return SANE_STATUS_GOOD; +} + + +void +sane_close (SANE_Handle handle) +{ + Epson_Scanner *s, *prev; + size_t i; + + /* Test if there is still data pending from the scanner. If so, then + * do a cancel. + */ + + log_call (); + + s = (Epson_Scanner *) handle; + + /* remove handle from list of open handles */ + prev = 0; + for (s = first_handle; s; s = s->next) + { + if (s == handle) + break; + prev = s; + } + + if (!s) + { + err_fatal ("invalid handle (0x%p)", handle); + return; + } + + if (prev) + prev->next = s->next; + else + first_handle = s->next; + + s->hw = dev_dtor (s->hw); + + const_delete (s->opt[OPT_BIT_DEPTH].constraint.word_list, SANE_Word *); + const_delete (s->opt[OPT_SCAN_AREA].constraint.string_list, + SANE_String_Const *); + + /* image data acquisition related resources */ + delete (s->raw.buf); + delete (s->img.buf); + for (i = 0; i < LINES_SHUFFLE_MAX; ++i) + delete (s->line_buffer[i]); + + dip_destroy_LUT (s->dip, s->lut); + + delete (s); +} + + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + Epson_Scanner *s = (Epson_Scanner *) handle; + + if (option < 0 || option >= NUM_OPTIONS) + { + log_call ("(%d)", option); + return NULL; + } + + log_call ("(%s)", s->opt[option].name); + return (s->opt + option); +} + + +static const SANE_String_Const * +search_string_list (const SANE_String_Const * list, SANE_String value) +{ + log_call ("(%s)", value); + + while (*list != NULL && strcmp_c (value, *list) != 0) + { + ++list; + } + + return ((*list == NULL) ? NULL : list); +} + + +/* Activate, deactivate an option. Subroutines so we can add + debugging info if we want. The change flag is set to TRUE + if we changed an option. If we did not change an option, + then the value of the changed flag is not modified. + */ +static void +activateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change) +{ + log_call ("(%s)", s->opt[option].name); + if (!SANE_OPTION_IS_ACTIVE (s->opt[option].cap)) + { + s->opt[option].cap &= ~SANE_CAP_INACTIVE; + *change = SANE_TRUE; + } +} + +static void +deactivateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change) +{ + log_call ("(%s)", s->opt[option].name); + if (SANE_OPTION_IS_ACTIVE (s->opt[option].cap)) + { + s->opt[option].cap |= SANE_CAP_INACTIVE; + *change = SANE_TRUE; + } +} + +static void +setOptionState (Epson_Scanner * s, SANE_Bool state, + SANE_Int option, SANE_Bool * change) +{ + if (state) + { + activateOption (s, option, change); + } + else + { + deactivateOption (s, option, change); + } +} + +static void +calculate_scan_area_offset (const Option_Value *v, int *left, int *top) +{ + *left = + SANE_UNFIX (v[OPT_TL_X].w) / MM_PER_INCH * + v[OPT_X_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5; + + *top = + SANE_UNFIX (v[OPT_TL_Y].w) / MM_PER_INCH * + v[OPT_Y_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5; +} + +static void +calculate_scan_area_max (const Epson_Scanner *s, int *x, int *y) +{ + /* Cast first value to double to force the whole computation to be + done in double. Works around integer overflows. + */ + *x = ((double) s->hw->src->max_x * s->val[OPT_X_RESOLUTION].w * + s->val[OPT_ZOOM].w / (s->hw->base_res * 100)); + *y = ((double) s->hw->src->max_y * s->val[OPT_Y_RESOLUTION].w * + s->val[OPT_ZOOM].w / (s->hw->base_res * 100)); +} + +static bool +scan_area_is_valid (Epson_Scanner *s) +{ + int left = 0; + int top = 0; + int max_x = 0; + int max_y = 0; + + bool rv = true; + + /* finalize parameters before we validate*/ + estimate_parameters (s, NULL); + + calculate_scan_area_max (s, &max_x, &max_y); + calculate_scan_area_offset (s->val, &left, &top); + + if ( s->raw.ctx.pixels_per_line > max_x) rv = false; + if ((left + s->raw.ctx.pixels_per_line) > max_x) rv = false; + + if (!need_autocrop_override (s)) + { + if ( s->raw.ctx.lines > max_y ) rv = false; + if ((top + s->raw.ctx.lines) > max_y) rv = false; + } + + /* check physical channel limitations */ + { + size_t max_req = s->hw->channel->max_request_size (s->hw->channel); + if (s->raw.ctx.bytes_per_line > max_req) rv = false; + } + + if (s->hw->using_fs) + { + if (s->raw.ctx.pixels_per_line > s->hw->scan_width_limit) rv = false; + return rv; + } + + if (SANE_FRAME_RGB == s->raw.ctx.format) /* max x according to to spec */ + if (s->raw.ctx.pixels_per_line > 21840) rv = false; + if (top > 65530) rv = false; + if (left > 65530) rv = false; + + return rv; +} + +static SANE_Status +getvalue (Epson_Scanner *s, SANE_Int option, void *value) +{ + SANE_Option_Descriptor *sopt = &(s->opt[option]); + Option_Value *sval = &(s->val[option]); + + log_call ("(%s)", sopt->name); + + switch (option) + { + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (value, sval->wa, sopt->size); + break; + + case OPT_NUM_OPTS: + case OPT_RESOLUTION: + case OPT_X_RESOLUTION: + case OPT_Y_RESOLUTION: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + case OPT_MIRROR: + case OPT_SPEED: + case OPT_PREVIEW_SPEED: + case OPT_AAS: + case OPT_PREVIEW: + case OPT_BRIGHTNESS: + case OPT_CONTRAST: + case OPT_SHARPNESS: + case OPT_AUTO_EJECT: + case OPT_CCT_1: + case OPT_CCT_2: + case OPT_CCT_3: + case OPT_CCT_4: + case OPT_CCT_5: + case OPT_CCT_6: + case OPT_CCT_7: + case OPT_CCT_8: + case OPT_CCT_9: + case OPT_THRESHOLD: + case OPT_ZOOM: + case OPT_BIT_DEPTH: + case OPT_WAIT_FOR_BUTTON: + case OPT_DETECT_DOC_SIZE: + case OPT_LIMIT_RESOLUTION: + case OPT_ADF_AUTO_SCAN: + case OPT_DESKEW: + case OPT_AUTOCROP: + case OPT_NEEDS_POLLING: + *((SANE_Word *) value) = sval->w; + break; + case OPT_MODE: + case OPT_ADF_MODE: + case OPT_HALFTONE: + case OPT_DROPOUT: + case OPT_BRIGHTNESS_METHOD: + case OPT_SCAN_AREA: + case OPT_SOURCE: + case OPT_FILM_TYPE: + case OPT_GAMMA_CORRECTION: + case OPT_COLOR_CORRECTION: + case OPT_BAY: + case OPT_FOCUS: + case OPT_ADF_DFD_SENSITIVITY: + strcpy ((char *) value, sopt->constraint.string_list[sval->w]); + break; + + case OPT_QUICK_FORMAT: + getvalue (s, OPT_SCAN_AREA, value); + break; + + case OPT_EXT_SANE_STATUS: + if (using (s->hw, adf) + && (ADF_EXT_STATUS_DFE & s->hw->adf->ext_status)) + sval->w = EXT_SANE_STATUS_MULTI_FEED; + if (using (s->hw, adf) + && (ADF_EXT_STATUS_TR_OPN & s->hw->adf->ext_status)) + sval->w = EXT_SANE_STATUS_TRAY_CLOSED; + *((SANE_Word *) value) = sval->w; + sval->w = 0; + break; + + case OPT_MONITOR_BUTTON: + if (SANE_OPTION_IS_ACTIVE (option)) + { + SANE_Bool pressed; + SANE_Status status = SANE_STATUS_GOOD; + if (SANE_STATUS_GOOD == status) + { + status = get_push_button_status (s->hw, &pressed); + if (SANE_STATUS_GOOD == status) + { + *((SANE_Bool *) value) = pressed; + } + } + return status; + } + else + { + return SANE_STATUS_UNSUPPORTED; + } + break; + case OPT_POLLING_TIME: + *((SANE_Word *) value) = sval->w; + break; + case OPT_SCAN_AREA_IS_VALID: + { + sval->w = scan_area_is_valid (s); + *((SANE_Word *) value) = sval->w; + } + break; + case OPT_ADF_DUPLEX_DIRECTION_MATCHES: + { + sval->w = adf_duplex_direction_matches (s->hw); + *((SANE_Word *) value) = sval->w; + } + break; + default: + return SANE_STATUS_INVAL; + } + + return SANE_STATUS_GOOD; +} + + +static void +handle_mode (Epson_Scanner * s, SANE_Int optindex, SANE_Bool * reload) +{ + SANE_Bool dropout, aas, halftone, threshold, cct; + SANE_Bool brightness, contrast; + + log_call (); + + *reload = SANE_FALSE; + + switch (optindex) + { + case 0: /* b & w */ + dropout = SANE_TRUE; + aas = SANE_TRUE; + halftone = SANE_TRUE; + threshold = SANE_TRUE; + cct = SANE_FALSE; + brightness = SANE_FALSE; + contrast = SANE_FALSE; + break; + case 1: /* gray */ + dropout = SANE_TRUE; + aas = SANE_FALSE; + halftone = SANE_FALSE; + threshold = SANE_FALSE; + cct = SANE_FALSE; + brightness = SANE_TRUE; + contrast = SANE_TRUE; + break; + case 2: /* color */ + dropout = SANE_FALSE; + aas = SANE_FALSE; + halftone = SANE_FALSE; + threshold = SANE_FALSE; + cct = SANE_TRUE; + brightness = SANE_TRUE; + contrast = SANE_TRUE; + break; + default: + return; + } + + if (s->hw->cmd->level[0] == 'D') + { + dropout = SANE_FALSE; + aas = SANE_FALSE; + halftone = SANE_FALSE; + } + + setOptionState (s, dropout, OPT_DROPOUT, reload); + s->val[OPT_DROPOUT].w = 0; + setOptionState (s, halftone, OPT_HALFTONE, reload); + s->val[OPT_HALFTONE].w = 0; + setOptionState (s, aas, OPT_AAS, reload); + s->val[OPT_AAS].w = SANE_FALSE; + + setOptionState (s, threshold, OPT_THRESHOLD, reload); + + setOptionState (s, brightness, OPT_BRIGHTNESS, reload); + setOptionState (s, contrast, OPT_CONTRAST, reload); + + setOptionState (s, cct, OPT_CCT_1, reload); + setOptionState (s, cct, OPT_CCT_2, reload); + setOptionState (s, cct, OPT_CCT_3, reload); + setOptionState (s, cct, OPT_CCT_4, reload); + setOptionState (s, cct, OPT_CCT_5, reload); + setOptionState (s, cct, OPT_CCT_6, reload); + setOptionState (s, cct, OPT_CCT_7, reload); + setOptionState (s, cct, OPT_CCT_8, reload); + setOptionState (s, cct, OPT_CCT_9, reload); + + /* if binary, then disable the bit depth selection */ + if (optindex == 0) + { + s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE; + } + else + { + if (bitDepthList[0] == 1) + s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE; + else + { + s->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE; + s->val[OPT_BIT_DEPTH].w = mode_params[optindex].depth; + } + } + + if (optindex == 0) /* b & w */ + handle_depth_halftone (s, 0, reload); + + *reload = SANE_TRUE; +} + + +static void +change_profile_matrix (Epson_Scanner * s) +{ + int index = 0; + + log_call (); + + require (s->hw->scan_hard); + + if (using (s->hw, tpu)) /* TPU */ + { + if (s->val[OPT_FILM_TYPE].w == 0) /* posi */ + index = 3; + else + index = 1; + } + else /* Flatbed or ADF */ + { + index = 0; + } + + s->val[OPT_CCT_1].w = SANE_FIX (s->hw->scan_hard->color_profile[index][0]); + s->val[OPT_CCT_2].w = SANE_FIX (s->hw->scan_hard->color_profile[index][1]); + s->val[OPT_CCT_3].w = SANE_FIX (s->hw->scan_hard->color_profile[index][2]); + s->val[OPT_CCT_4].w = SANE_FIX (s->hw->scan_hard->color_profile[index][3]); + s->val[OPT_CCT_5].w = SANE_FIX (s->hw->scan_hard->color_profile[index][4]); + s->val[OPT_CCT_6].w = SANE_FIX (s->hw->scan_hard->color_profile[index][5]); + s->val[OPT_CCT_7].w = SANE_FIX (s->hw->scan_hard->color_profile[index][6]); + s->val[OPT_CCT_8].w = SANE_FIX (s->hw->scan_hard->color_profile[index][7]); + s->val[OPT_CCT_9].w = SANE_FIX (s->hw->scan_hard->color_profile[index][8]); +} + + +static unsigned char +int2cpt (int val) +{ + if (val >= 0) + { + if (val > 127) + val = 127; + return (unsigned char) val; + } + else + { + val = -val; + if (val > 127) + val = 127; + return (unsigned char) (0x80 | val); + } +} + + +static void +get_colorcoeff_from_profile (double *profile, unsigned char *color_coeff) +{ + int cc_idx[] = { 4, 1, 7, 3, 0, 6, 5, 2, 8 }; + int color_table[9]; + int i; + + round_cct (profile, color_table); + + for (i = 0; i < 9; i++) + color_coeff[i] = int2cpt (color_table[cc_idx[i]]); +} + + +static void +round_cct (double org_cct[], int rnd_cct[]) +{ + int i, j, index; + double mult_cct[9], frac[9]; + int sum[3]; + int loop; + + for (i = 0; i < 9; i++) + mult_cct[i] = org_cct[i] * 32; + + for (i = 0; i < 9; i++) + rnd_cct[i] = (int) floor (org_cct[i] * 32 + 0.5); + + loop = 0; + + do + { + for (i = 0; i < 3; i++) + { + if ((rnd_cct[i * 3] == 11) && + (rnd_cct[i * 3] == rnd_cct[i * 3 + 1]) && + (rnd_cct[i * 3] == rnd_cct[i * 3 + 2])) + { + rnd_cct[i * 3 + i]--; + mult_cct[i * 3 + i] = rnd_cct[i * 3 + i]; + } + } + + for (i = 0; i < 3; i++) + { + sum[i] = 0; + for (j = 0; j < 3; j++) + sum[i] += rnd_cct[i * 3 + j]; + } + + for (i = 0; i < 9; i++) + frac[i] = mult_cct[i] - rnd_cct[i]; + + for (i = 0; i < 3; i++) + { + if (sum[i] < 32) + { + index = get_roundup_index (&frac[i * 3], 3); + if (index != -1) + { + rnd_cct[i * 3 + index]++; + mult_cct[i * 3 + index] = rnd_cct[i * 3 + index]; + sum[i]++; + } + } + else if (sum[i] > 32) + { + index = get_rounddown_index (&frac[i * 3], 3); + if (index != -1) + { + rnd_cct[i * 3 + index]--; + mult_cct[i * 3 + index] = rnd_cct[i * 3 + index]; + sum[i]--; + } + } + } + } + while ((++loop < 2) + && ((sum[0] != 32) || (sum[1] != 32) || (sum[2] != 32))); +} + + +static int +get_roundup_index (double frac[], int n) +{ + int i, index = -1; + double max_val = 0.0; + + for (i = 0; i < n; i++) + { + if (frac[i] < 0) + continue; + if (max_val < frac[i]) + { + index = i; + max_val = frac[i]; + } + } + + return index; +} + + +static int +get_rounddown_index (double frac[], int n) +{ + int i, index = -1; + double min_val = 1.0; + + for (i = 0; i < n; i++) + { + if (frac[i] > 0) + continue; + if (min_val > frac[i]) + { + index = i; + min_val = frac[i]; + } + } + + return index; +} + + +/* This routine handles common options between OPT_MODE and + OPT_HALFTONE. These options are TET (a HALFTONE mode), AAS + - auto area segmentation, and threshold. Apparently AAS + is some method to differentiate between text and photos. + Or something like that. + + AAS is available when the scan color depth is 1 and the + halftone method is not TET. + + Threshold is available when halftone is NONE, and depth is 1. +*/ +static void +handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex, + SANE_Bool * reload) +{ + SANE_Bool threshold, aas, dropout; + + log_call (); + + *reload = SANE_FALSE; + + switch (halftone_params[optindex]) + { + case HALFTONE_NONE: + threshold = SANE_TRUE; + aas = SANE_TRUE; + dropout = SANE_TRUE; + break; + case HALFTONE_TET: + threshold = SANE_FALSE; + aas = SANE_FALSE; + dropout = SANE_FALSE; + break; + default: + threshold = SANE_FALSE; + aas = SANE_TRUE; + dropout = SANE_TRUE; + } + + setOptionState (s, threshold, OPT_THRESHOLD, reload); + setOptionState (s, aas, OPT_AAS, reload); + setOptionState (s, dropout, OPT_DROPOUT, reload); + + *reload = SANE_TRUE; +} + + +static void +handle_resolution (Epson_Scanner * s, SANE_Int option, SANE_Word value) +{ + SANE_Int *last = NULL; + + SANE_Int size = 0; + SANE_Word *list = NULL; + + int f = 0; + int k = 0; + int n = 0; + + log_call ("(%s, %d)", s->opt[option].name, value); + + switch (option) + { + case OPT_RESOLUTION: + last = &s->hw->res.last; + size = s->hw->res.size; + list = s->hw->res.list; + break; + case OPT_X_RESOLUTION: + last = &s->hw->res_x.last; + size = s->hw->res_x.size; + list = s->hw->res_x.list; + break; + case OPT_Y_RESOLUTION: + last = &s->hw->res_y.last; + size = s->hw->res_y.size; + list = s->hw->res_y.list; + break; + default: + err_fatal ("%s", strerror (EINVAL)); + exit (EXIT_FAILURE); + } + + if (SANE_CONSTRAINT_RANGE == s->opt[option].constraint_type) + { + sanei_constrain_value (&(s->opt[option]), &value, NULL); + s->val[option].w = value; + } + else + { + SANE_Int best = list[size]; + int min_d = INT_MAX; + + /* find supported resolution closest to that requested */ + for (n = 1; n <= size; n++) + { + int d = abs (value - list[n]); + + if (d < min_d) + { + min_d = d; + k = n; + best = list[n]; + } + } + + /* FIXME? what's this trying to do? Use a resolution close to the + last one used if best is far away? Why??? */ + if ((value != best) && *last) + { + for (f = 1; f <= size; f++) + if (*last == list[f]) + break; + + if (f != k && f != k - 1 && f != k + 1) + { + if (k > f) + best = list[f + 1]; + else if (k < f) + best = list[f - 1]; + } + } + + *last = best; + s->val[option].w = (SANE_Word) best; + } + + if (OPT_RESOLUTION == option) + { + s->val[OPT_X_RESOLUTION].w = s->val[option].w; + s->val[OPT_Y_RESOLUTION].w = s->val[option].w; + + s->hw->res_x.last = s->hw->res.last; + s->hw->res_y.last = s->hw->res.last; + } + { + SANE_Bool dummy; + handle_deskew (s, NULL, &dummy); + } +} + +static void +limit_adf_res (Epson_Scanner * s) +{ + SANE_Constraint_Type type = s->opt[OPT_RESOLUTION].constraint_type; + int limit = large_res_kills_adf_scan (s->hw); + + if (using (s->hw, adf)) + { + dev_limit_res (s->hw, type, limit); + + /* constrain the current values to the new limit */ + handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w); + handle_resolution (s, OPT_X_RESOLUTION, s->val[OPT_X_RESOLUTION].w); + handle_resolution (s, OPT_Y_RESOLUTION, s->val[OPT_Y_RESOLUTION].w); + } + else + { + dev_restore_res (s->hw, type); + } +} + + +/* + Handles setting the source (flatbed, transparency adapter (TPU), + or auto document feeder (ADF)). + + For newer scanners it also sets the focus according to the + glass / TPU settings. +*/ +static SANE_Status +handle_source (Epson_Scanner * s, SANE_Int optindex, char *value) +{ + SANE_Bool dummy; + SANE_Status status = SANE_STATUS_GOOD; + + log_call ("(%s)", value); + + if (s->val[OPT_SOURCE].w == optindex) + return SANE_STATUS_GOOD; + + if (s->hw->adf && strcmp_c (ADF_STR, value) == 0) + { + s->val[OPT_SOURCE].w = optindex; + s->hw->src = (const extension *) s->hw->adf; + deactivateOption (s, OPT_FILM_TYPE, &dummy); + s->val[OPT_FOCUS].w = 0; + if (EXT_STATUS_ADFS & s->hw->ext_status) + { + activateOption (s, OPT_ADF_MODE, &dummy); + activateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy); + } + else + { + deactivateOption (s, OPT_ADF_MODE, &dummy); + s->val[OPT_ADF_MODE].w = 0; + deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy); + } + if (FSI_CAP_ADFAS & s->hw->fsi_cap_2) + { + activateOption (s, OPT_ADF_AUTO_SCAN, &dummy); + } + if (FSI_CAP_DFD & s->hw->fsi_cap_2) + { + activateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy); + } + else + { + deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy); + s->val[OPT_ADF_DFD_SENSITIVITY].w = 0; + } + } + else if (s->hw->tpu && strcmp_c (TPU_STR, value) == 0) + { + s->val[OPT_SOURCE].w = optindex; + s->hw->src = (const extension *) s->hw->tpu; + deactivateOption (s, OPT_ADF_MODE, &dummy); + deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy); + deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy); + deactivateOption (s, OPT_EJECT, &dummy); + deactivateOption (s, OPT_AUTO_EJECT, &dummy); + deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy); + } + else if (s->hw->fbf) + { + s->val[OPT_SOURCE].w = optindex; + s->hw->src = (const extension *) s->hw->fbf; + s->val[OPT_FOCUS].w = 0; + deactivateOption (s, OPT_ADF_MODE, &dummy); + deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy); + deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy); + deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy); + } + else + { + err_fatal ("internal inconsistency"); + return SANE_STATUS_INVAL; + } + + /* reset the scanner when we are changing the source setting - + this is necessary for the Perfection 1650 */ + if (s->hw->need_reset_on_source_change) + initialize (s->hw); + + handle_detect_doc_size (s, NULL, &dummy); + + /*change*/ + status = handle_scan_area(s, s->val[OPT_ADF_MODE].w); + + change_profile_matrix (s); + + setOptionState (s, using (s->hw, tpu), OPT_FILM_TYPE, &dummy); + setOptionState (s, using (s->hw, adf), OPT_AUTO_EJECT, &dummy); + setOptionState (s, using (s->hw, adf), OPT_EJECT, &dummy); + + if (s->hw->cmd->set_focus_position) + { + if (using (s->hw, tpu)) + { + s->val[OPT_FOCUS].w = 1; + setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy); + } + else if (using (s->hw, adf)) + { + s->val[OPT_FOCUS].w = 0; + setOptionState (s, SANE_FALSE, OPT_FOCUS, &dummy); + } + else + { + s->val[OPT_FOCUS].w = 0; + setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy); + } + } + + status = get_resolution_constraints (s->hw, s); + if (SANE_STATUS_GOOD != status) + { + return status; + } + + if (s->hw->adf) + { + if (large_res_kills_adf_scan (s->hw)) limit_adf_res (s); + + if (zoom_kills_adf_scan (s->hw)) + { + if (using (s->hw, adf)) + { + s->val[OPT_ZOOM].w = 100; + deactivateOption (s, OPT_ZOOM, &dummy); + } + else + { + if (s->hw->cmd->set_zoom) + activateOption (s, OPT_ZOOM, &dummy); + } + } + } + + return status; +} + + +static void +handle_filmtype (Epson_Scanner * s, SANE_Int optindex, char *value) +{ + log_call (); + + value = value; + + if (!s->hw->tpu || s->val[OPT_FILM_TYPE].w == optindex) + return; + + s->val[OPT_FILM_TYPE].w = optindex; + + require (s->hw->src == (extension *) s->hw->tpu); + + s->val[OPT_TL_X].w = 0; + s->val[OPT_TL_Y].w = 0; + s->val[OPT_BR_X].w = s->hw->src->x_range.max; + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + + s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range); + s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range); + s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range); + s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range); + + change_profile_matrix (s); +} + +static void +handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload) +{ + *reload = SANE_FALSE; + + if ( dip_has_autocrop (s->dip, s->hw) + && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap) + || ! s->val[OPT_DESKEW].b ) + && !s->val[OPT_PREVIEW].b ) + { + activateOption (s, OPT_AUTOCROP, reload); + if (value) + { + SANE_Bool dummy; + s->val[OPT_AUTOCROP].b = *value; + handle_deskew (s, NULL, &dummy); + *reload = SANE_TRUE; + } + } + else + { + deactivateOption (s, OPT_AUTOCROP, reload); + } +} + +static void +handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload) +{ + *reload = SANE_FALSE; + + if ( dip_has_deskew (s->dip, s->hw) + && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap) + || ! s->val[OPT_AUTOCROP].b ) + && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_BIT_DEPTH].cap) + || 8 == s->val[OPT_BIT_DEPTH].w) + && 600 >= s->val[OPT_RESOLUTION].w + && 600 >= s->val[OPT_X_RESOLUTION].w + && 600 >= s->val[OPT_Y_RESOLUTION].w + && !s->val[OPT_PREVIEW].b ) + { + activateOption (s, OPT_DESKEW , reload); + if (value) + { + SANE_Bool dummy; + s->val[OPT_DESKEW].b = *value; + handle_autocrop (s, NULL, &dummy); + *reload = SANE_TRUE; + } + } + else + { + deactivateOption (s, OPT_DESKEW, reload); + } +} + +static void +handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload) +{ + *reload = SANE_FALSE; + + if (has_size_check_support (s->hw->src) + && !s->val[OPT_PREVIEW].b) + { + activateOption (s, OPT_DETECT_DOC_SIZE, reload); + if (value) + { + s->val[OPT_DETECT_DOC_SIZE].b = *value; + *reload = SANE_TRUE; + } + } + else + { + deactivateOption (s, OPT_DETECT_DOC_SIZE, reload); + } +} + +static void +handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload) +{ + SANE_Bool dummy; + + if (value) + { + s->val[OPT_PREVIEW].b = *value; + handle_detect_doc_size (s, &s->val[OPT_DETECT_DOC_SIZE].b, &dummy); + handle_autocrop (s, &s->val[OPT_AUTOCROP].b, &dummy); + handle_deskew (s, &s->val[OPT_DESKEW].b, &dummy); + *reload = SANE_TRUE; + } +} + +static SANE_Status +setvalue (Epson_Scanner *s, SANE_Int option, void *value, SANE_Int * info) +{ + SANE_Option_Descriptor *sopt = &(s->opt[option]); + Option_Value *sval = &(s->val[option]); + + SANE_Status status; + const SANE_String_Const *optval; + int optindex; + SANE_Bool reload = SANE_FALSE; + + log_call ("(%s, value @%p)", sopt->name, value); + + status = sanei_constrain_value (sopt, value, info); + + if (status != SANE_STATUS_GOOD) + return status; + + optval = NULL; + optindex = 0; + + if (sopt->constraint_type == SANE_CONSTRAINT_STRING_LIST) + { + optval = search_string_list (sopt->constraint.string_list, + (char *) value); + + if (optval == NULL) + return SANE_STATUS_INVAL; + optindex = optval - sopt->constraint.string_list; + } + + switch (option) + { + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (sval->wa, value, sopt->size); /* Word arrays */ + break; + + case OPT_CCT_1: + case OPT_CCT_2: + case OPT_CCT_3: + case OPT_CCT_4: + case OPT_CCT_5: + case OPT_CCT_6: + case OPT_CCT_7: + case OPT_CCT_8: + case OPT_CCT_9: + sval->w = *((SANE_Word *) value); /* Simple values */ + break; + + case OPT_FILM_TYPE: + handle_filmtype (s, optindex, (char *) value); + reload = SANE_TRUE; + break; + + case OPT_DROPOUT: + case OPT_BAY: + case OPT_FOCUS: + sval->w = optindex; /* Simple lists */ + break; + + case OPT_EJECT: + dev_eject_paper (s->hw); + break; + + case OPT_RESOLUTION: + case OPT_X_RESOLUTION: + case OPT_Y_RESOLUTION: + handle_resolution (s, option, *((SANE_Word *) value)); + reload = SANE_TRUE; + break; + + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + sval->w = *((SANE_Word *) value); + log_info ("set = %f", SANE_UNFIX (sval->w)); + if (NULL != info) + *info |= SANE_INFO_RELOAD_PARAMS; + break; + + case OPT_SOURCE: + status = handle_source (s, optindex, (char *) value); + reload = SANE_TRUE; + break; + + case OPT_MODE: + { + if (sval->w == optindex) + break; + + sval->w = optindex; + + handle_mode (s, optindex, &reload); + + break; + } + + case OPT_ADF_MODE: + status = handle_scan_area(s, optindex); + reload = SANE_TRUE; + /* through intentionally */ + case OPT_ADF_DFD_SENSITIVITY: + sval->w = optindex; + break; + + case OPT_BIT_DEPTH: + { + SANE_Bool dummy; + sval->w = *((SANE_Word *) value); + mode_params[s->val[OPT_MODE].w].depth = sval->w; + handle_deskew (s, NULL, &dummy); + reload = SANE_TRUE; + break; + } + case OPT_HALFTONE: + if (sval->w == optindex) + break; + sval->w = optindex; + handle_depth_halftone (s, optindex, &reload); + break; + + case OPT_BRIGHTNESS_METHOD: + { + const SANE_Range *r; + SANE_Bool f + = s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w]; + + if (sval->w == optindex) break; + + r = s->opt[OPT_BRIGHTNESS].constraint.range; + + if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */ + sopt->constraint.string_list[optindex])) + { + s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_EMULATED; + s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range; + setOptionState (s, !f, OPT_BRIGHTNESS, &reload); + } + else + { + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED; + s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range; + setOptionState (s, SANE_TRUE, OPT_BRIGHTNESS, &reload); + } + + if (r != s->opt[OPT_BRIGHTNESS].constraint.range) + { + double v = s->val[OPT_BRIGHTNESS].w; + + /**/ if (0 < v) + { + require (0 < r->max); + + v /= r->max; + r = s->opt[OPT_BRIGHTNESS].constraint.range; + v *= r->max; + v += 0.5; + + reload = SANE_TRUE; + } + else if (0 > v) + { + require (0 > r->min); + + v /= r->min; + r = s->opt[OPT_BRIGHTNESS].constraint.range; + v *= r->min; + v -= 0.5; + + reload = SANE_TRUE; + } + else /* 0 == v */ + {} + + s->val[OPT_BRIGHTNESS].w = (SANE_Int) v; + } + + sval->w = optindex; + + break; + } + + case OPT_COLOR_CORRECTION: + { + SANE_Bool f = s->hw->color_user_defined[optindex]; + + sval->w = optindex; + setOptionState (s, f, OPT_CCT_1, &reload); + setOptionState (s, f, OPT_CCT_2, &reload); + setOptionState (s, f, OPT_CCT_3, &reload); + setOptionState (s, f, OPT_CCT_4, &reload); + setOptionState (s, f, OPT_CCT_5, &reload); + setOptionState (s, f, OPT_CCT_6, &reload); + setOptionState (s, f, OPT_CCT_7, &reload); + setOptionState (s, f, OPT_CCT_8, &reload); + setOptionState (s, f, OPT_CCT_9, &reload); + + break; + } + + case OPT_GAMMA_CORRECTION: + { + SANE_Bool f = s->hw->gamma_user_defined[optindex]; + + sval->w = optindex; + setOptionState (s, f, OPT_GAMMA_VECTOR_R, &reload); + setOptionState (s, f, OPT_GAMMA_VECTOR_G, &reload); + setOptionState (s, f, OPT_GAMMA_VECTOR_B, &reload); + + if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */ + s->opt[OPT_BRIGHTNESS_METHOD].constraint + .string_list[s->val[OPT_BRIGHTNESS_METHOD].w])) + { + setOptionState (s, !f, OPT_BRIGHTNESS, &reload); + } + + break; + } + + case OPT_AUTO_EJECT: + sval->w = *((SANE_Word *) value); + if (s->hw && s->hw->adf) s->hw->adf->auto_eject = sval->w; + break; + case OPT_MIRROR: + case OPT_SPEED: + case OPT_PREVIEW_SPEED: + case OPT_AAS: + case OPT_BRIGHTNESS: + case OPT_CONTRAST: + case OPT_SHARPNESS: + case OPT_THRESHOLD: + case OPT_ZOOM: + case OPT_WAIT_FOR_BUTTON: + case OPT_ADF_AUTO_SCAN: + sval->w = *((SANE_Word *) value); + break; + + case OPT_DETECT_DOC_SIZE: + handle_detect_doc_size (s, (SANE_Word *) value, &reload); + break; + + case OPT_PREVIEW: + handle_preview (s, (SANE_Word *) value, &reload); + break; + + case OPT_DESKEW: + handle_deskew (s, (SANE_Word *) value, &reload); + break; + + case OPT_AUTOCROP: + handle_autocrop (s, (SANE_Word *) value, &reload); + break; + + case OPT_LIMIT_RESOLUTION: + sval->w = *((SANE_Word *) value); + filter_resolution_list (s); + reload = SANE_TRUE; + break; + + case OPT_SCAN_AREA: + { + sval->w = optindex; + + /**/ if (0 == strcmp_c (sopt->constraint.string_list[sval->w], + media_maximum)) + { + s->val[OPT_TL_X].w = SANE_FIX (0.0); + s->val[OPT_TL_Y].w = SANE_FIX (0.0); + s->val[OPT_BR_X].w = s->hw->src->x_range.max; + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + } + else if (0 == strcmp_c (sopt->constraint.string_list[sval->w], + media_automatic)) + { + SANE_Bool yes = SANE_TRUE; + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap)) + { + setvalue (s, OPT_DETECT_DOC_SIZE, &yes, NULL); + } + } + else + { + size_t i = 0; + + while (i < num_of (media_list) + && 0 != strcmp_c (sopt->constraint.string_list[sval->w], + media_list[i].name)) + ++i; + + require (i < num_of (media_list)); + + s->val[OPT_TL_X].w = SANE_FIX (0.0); + s->val[OPT_TL_Y].w = SANE_FIX (0.0); + s->val[OPT_BR_X].w = SANE_FIX (media_list[i].width); + s->val[OPT_BR_Y].w = SANE_FIX (media_list[i].height); + + adf_handle_adjust_alignment (s, SANE_FALSE); + } + + reload = SANE_TRUE; + break; + } + case OPT_QUICK_FORMAT: + setvalue (s, OPT_SCAN_AREA, value, info); + sval->w = s->val[OPT_SCAN_AREA].w; + break; + + case OPT_CALIBRATE: + status = dev_calibrate (s->hw); + break; + + case OPT_CLEAN: + status = dev_clean (s->hw); + break; + + case OPT_MONITOR_BUTTON: + default: + return SANE_STATUS_INVAL; + } + + if (reload && info != NULL) + { + *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS; + } + + return status; +} + + +SANE_Status +sane_control_option (SANE_Handle handle, + SANE_Int option, + SANE_Action action, void *value, SANE_Int * info) +{ + log_call (); + + if (option < 0 || option >= NUM_OPTIONS) + return SANE_STATUS_INVAL; + + if (info != NULL) + *info = 0; + + switch (action) + { + case SANE_ACTION_GET_VALUE: + return (getvalue (handle, option, value)); + + case SANE_ACTION_SET_VALUE: + return (setvalue (handle, option, value, info)); + default: + return SANE_STATUS_INVAL; + } + + return SANE_STATUS_GOOD; +} + +/* This function is part of the SANE API and gets called when the front end + * requests information aobut the scan configuration (e.g. color depth, mode, + * bytes and pixels per line, number of lines. This information is returned + * in the SANE_Parameters structure. + * + * Once a scan was started, this routine has to report the correct values, if + * it is called before the scan is actually started, the values are based on + * the current settings. + * + */ +SANE_Status +estimate_parameters (Epson_Scanner *s, SANE_Parameters * params) +{ + int zoom, max_x, max_y; + int bytes_per_pixel; + + int x_dpi = 0; + int y_dpi = 0; + + SANE_Int max_y_orig = -1; + + log_call (); + + memset (&s->raw.ctx, 0, sizeof (SANE_Parameters)); + + x_dpi = s->val[OPT_X_RESOLUTION].w; + y_dpi = s->val[OPT_Y_RESOLUTION].w; + + zoom = s->val[OPT_ZOOM].w; + + if (need_autocrop_override (s)) + { /* yucky changes to be reverted below */ + max_y_orig = s->hw->src->max_y; + ((extension *) s->hw->src)->max_y = autocrop_max_y (s->hw); + update_ranges (s->hw, s->hw->src); + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + } + + calculate_scan_area_max (s, &max_x, &max_y); + + s->raw.ctx.pixels_per_line = + SANE_UNFIX (s->val[OPT_BR_X].w - + s->val[OPT_TL_X].w) / MM_PER_INCH * x_dpi * zoom / 100; + s->raw.ctx.lines = + SANE_UNFIX (s->val[OPT_BR_Y].w - + s->val[OPT_TL_Y].w) / MM_PER_INCH * y_dpi * zoom / 100; + + log_data ("max x:%d y:%d [in pixels]", max_x, max_y); + + if (max_x != 0 && max_y != 0) + { + if (max_x < s->raw.ctx.pixels_per_line) + s->raw.ctx.pixels_per_line = max_x; + if (max_y < s->raw.ctx.lines) + s->raw.ctx.lines = max_y; + } + + if (s->raw.ctx.pixels_per_line < 8) + s->raw.ctx.pixels_per_line = 8; + if (s->raw.ctx.lines < 1) + s->raw.ctx.lines = 1; + + log_data ("Preview = %d", s->val[OPT_PREVIEW].w); + log_data ("X Resolution = %d", s->val[OPT_X_RESOLUTION].w); + log_data ("Y Resolution = %d", s->val[OPT_Y_RESOLUTION].w); + + log_data ("Scan area: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]", + SANE_UNFIX (s->val[OPT_TL_X].w), + SANE_UNFIX (s->val[OPT_TL_Y].w), + SANE_UNFIX (s->val[OPT_BR_X].w), + SANE_UNFIX (s->val[OPT_BR_Y].w)); + + /* Calculate bytes_per_pixel and bytes_per_line for any color + * depths. The default color depth is stored in mode_params.depth. + */ + if (mode_params[s->val[OPT_MODE].w].depth == 1) + { + s->raw.ctx.depth = 1; + } + else + { + s->raw.ctx.depth = s->val[OPT_BIT_DEPTH].w; + } + + if (s->raw.ctx.depth > 8) + { + /* The frontends can only handle 8 or 16 bits for gray or color - + * so if it's more than 8, it gets automatically set to 16. This + * works as long as EPSON does not come out with a scanner that + * can handle more than 16 bits per color channel. + */ + s->raw.ctx.depth = 16; + } + + bytes_per_pixel = s->raw.ctx.depth / 8; /* this works because it can only be set to 1, 8 or 16 */ + if (s->raw.ctx.depth % 8) /* just in case ... */ + { + bytes_per_pixel++; + } + + /* All models require alignment on a multiple of 8 pixels per line. + However, some models require multiples of 32 pixels instead of 8 + when scanning in monochrome mode. + We use the largest multiple that is not larger than the original + value. + */ + s->raw.ctx.pixels_per_line &= ~7; + if (1 == s->raw.ctx.depth) + { + s->raw.ctx.pixels_per_line &= ~31; + } + + s->raw.ctx.last_frame = SANE_TRUE; + + if (mode_params[s->val[OPT_MODE].w].color) + { + s->raw.ctx.format = SANE_FRAME_RGB; + s->raw.ctx.bytes_per_line = + 3 * s->raw.ctx.pixels_per_line * bytes_per_pixel; + } + else + { + s->raw.ctx.format = SANE_FRAME_GRAY; + s->raw.ctx.bytes_per_line = + s->raw.ctx.pixels_per_line * s->raw.ctx.depth / 8; + } + + if (NULL != params) + memcpy (params, &s->raw.ctx, sizeof (SANE_Parameters)); + + print_params (s->raw.ctx); + + if (need_autocrop_override (s)) + { /* revert yucky changes made above */ + ((extension *) s->hw->src)->max_y = max_y_orig; + update_ranges (s->hw, s->hw->src); + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +device_init (Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + status = initialize (s->hw); + if (SANE_STATUS_GOOD != status) + { + return status; + } + +/* There is some undocumented special behavior with the TPU enable/disable. + * TPU power ESC e status + * on 0 NAK + * on 1 ACK + * off 0 ACK + * off 1 NAK + * + * It makes no sense to scan with TPU powered on and source flatbed, because + * light will come from both sides. + */ + + if (s->hw->adf || s->hw->tpu) + { + /* if it is previewing now, disable duplex */ + SANE_Bool adf_duplex = ((1 == s->val[OPT_ADF_MODE].w) + && !s->val[OPT_PREVIEW].b); + status = control_option_unit (s->hw, adf_duplex); + + if (SANE_STATUS_GOOD != status) + { + if (s->hw->tpu) + err_major ("You may have to power %s your TPU", + (using (s->hw, tpu) ? "on" : "off")); + + err_major ("You may have to restart the SANE frontend."); + return status; + } + + if (s->hw->cmd->request_extended_status != 0) + { + status = check_ext_status (s->hw); + + if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status) + { + return status; + } + } + } + + if (s->hw->using_fs) + { + cmd_request_scanning_parameter (s->hw); + s->hw->param_buf[39] = s->val[OPT_ADF_DFD_SENSITIVITY].w; + s->hw->param_buf[40] = (s->val[OPT_ADF_AUTO_SCAN].w ? 0xFF : 0x00); + } + + return status; +} + +static SANE_Status +device_set_focus (Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + if (!s->hw->tpu || !using (s->hw, tpu)) return status; + + /* set the focus position according to the extension used: + * if the TPU is selected, then focus 2.5mm above the glass, + * otherwise focus on the glass. Scanners that don't support + * this feature, will just ignore these calls. + */ + + if (s->hw->adf || s->hw->tpu) + { + if (s->hw->tpu && s->hw->tpu->has_focus) + { + if (s->val[OPT_FOCUS].w == 0) + { + log_info ("Setting focus to glass surface"); + status = set_focus_position (s->hw, 0x40); + } + else + { + log_info ("Setting focus to 2.5mm above glass"); + status = set_focus_position (s->hw, 0x59); + } + + if (SANE_STATUS_GOOD != status) + { + return status; + } + } + } + return status; +} + +static SANE_Status +set_scan_parameters (Epson_Scanner *s, int *x_res, int *y_res) +{ + SANE_Status status = SANE_STATUS_GOOD; + + const struct mode_param *mparam; + int x_dpi, y_dpi; + + log_call (); + + mparam = mode_params + s->val[OPT_MODE].w; + log_data ("setting data format to %d bits", mparam->depth); + status = set_data_format (s->hw, mparam->depth); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_data_format failed (%s)", + sane_strstatus (status)); + return status; + } + + /* The byte sequence mode was introduced in B5, for B[34] we need + line sequence mode + */ + if ((s->hw->cmd->level[0] == 'D' || + (s->hw->cmd->level[0] == 'B' && s->hw->level >= 5)) && + mparam->mode_flags == 0x02) + { + status = set_color_mode (s->hw, 0x13); + } + else + { + status = set_color_mode (s->hw, (mparam->mode_flags + | (mparam->dropout_mask + & dropout_params[s->val[OPT_DROPOUT].w]))); + } + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_color_mode failed (%s)", + sane_strstatus (status)); + return status; + } + + if (s->hw->cmd->set_halftoning && + SANE_OPTION_IS_ACTIVE (s->opt[OPT_HALFTONE].cap)) + { + status = set_halftoning (s->hw, halftone_params[s->val[OPT_HALFTONE].w]); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_halftoning failed (%s)", + sane_strstatus (status)); + return status; + } + } + + s->brightness = 0; + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BRIGHTNESS].cap)) + { + SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]); + Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]); + + if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */ + sopt->constraint.string_list[sval->w])) + { + status = set_bright (s->hw, s->val[OPT_BRIGHTNESS].w); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_bright failed (%s)", + sane_strstatus (status)); + return status; + } + } + else /* software emulation */ + { + s->brightness = s->val[OPT_BRIGHTNESS].w; + s->brightness /= s->opt[OPT_BRIGHTNESS].constraint.range->max; + } + } + + s->contrast = 0; + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_CONTRAST].cap)) + { + s->contrast = s->val[OPT_CONTRAST].w; + s->contrast /= s->opt[OPT_CONTRAST].constraint.range->max; + } + + s->lut = dip_destroy_LUT (s->dip, s->lut); + if (0 != s->brightness || 0 != s->contrast) /* non-linear LUT */ + { + SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]); + Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]); + + if (0 == strcmp_c (brightness_method_list[2], /* "gimp" */ + sopt->constraint.string_list[sval->w])) + s->lut = dip_gimp_BC_LUT (s->dip, s->raw.ctx.depth, + s->brightness, s->contrast); + else + /* We use "iscan" for contrast by default. If "hardware" was + * given then s->brightness is 0 and doesn't produce knock-on + * effects with this method. + */ + s->lut = dip_iscan_BC_LUT (s->dip, s->raw.ctx.depth, + s->brightness, s->contrast); + + if (!s->lut) + status = SANE_STATUS_NO_MEM; + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_contrast failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_MIRROR].cap)) + { + status = mirror_image (s->hw, mirror_params[s->val[OPT_MIRROR].w]); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("mirror_image failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SPEED].cap)) + { + + if (s->val[OPT_PREVIEW].w) + status = set_speed (s->hw, speed_params[s->val[OPT_PREVIEW_SPEED].w]); + else + status = set_speed (s->hw, speed_params[s->val[OPT_SPEED].w]); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_speed failed (%s)", sane_strstatus (status)); + return status; + } + } + + /* use of speed_params is ok here since they are false and true. + * NOTE: I think I should throw that "params" stuff as long w is + * already the value. + */ + + s->invert_image = SANE_FALSE; /* default: to not inverting the image */ + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_FILM_TYPE].cap)) + { + status = set_film_type (s->hw, film_params[s->val[OPT_FILM_TYPE].w]); + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_film_type failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BAY].cap)) + { + status = set_bay (s->hw, s->val[OPT_BAY].w); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_bay failed (%s)", sane_strstatus (status)); + return status; + } + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SHARPNESS].cap)) + { + + status = set_outline_emphasis (s->hw, s->val[OPT_SHARPNESS].w); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_outline_emphasis failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (s->hw->cmd->set_gamma && + SANE_OPTION_IS_ACTIVE (s->opt[OPT_GAMMA_CORRECTION].cap)) + { + int val; + if (s->hw->cmd->level[0] == 'D') + { + /* The D1 level has only the two user defined gamma settings. + */ + val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w]; + } + else + { + val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w]; + + /* If "Default" is selected then determine the actual value to + * send to the scanner: If bilevel mode, just send the value + * from the table (0x01), for grayscale or color mode add one + * and send 0x02. + */ + if (s->val[OPT_GAMMA_CORRECTION].w == 0) + { + val += mparam->depth == 1 ? 0 : 1; + } + } + + log_info ("set_gamma (s, 0x%x)", val); + status = set_gamma (s->hw, val); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_gamma failed (%s)", sane_strstatus (status)); + return status; + } + } + + if (s->hw->cmd->set_gamma_table && + s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w]) + { /* user defined. */ + status = set_gamma_table (s->hw, s); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_gamma_table failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (s->hw->cmd->set_color_correction) + { + int val = s->hw->color_type[s->val[OPT_COLOR_CORRECTION].w]; + + log_data ("set_color_correction (s, 0x%x)", val); + status = set_color_correction (s->hw, val); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_color_correction failed (%s)", + sane_strstatus (status)); + return status; + } + } + + if (s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w]) + { + size_t i; + + status = set_color_correction_coefficients (s->hw, s); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_color_correction_coefficients failed (%s)", + sane_strstatus (status)); + return status; + } + + log_info ("Color correction coefficients"); + for (i = 0; i < 9; ++i) + log_info ("cct[%zd] = %f", i, s->cct[i]); + } + + if (s->hw->cmd->set_threshold != 0 + && SANE_OPTION_IS_ACTIVE (s->opt[OPT_THRESHOLD].cap)) + { + status = set_threshold (s->hw, s->val[OPT_THRESHOLD].w); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_threshold failed (%s)", + sane_strstatus (status)); + return status; + } + } + + x_dpi = s->val[OPT_X_RESOLUTION].w; + y_dpi = s->val[OPT_Y_RESOLUTION].w; + + if (s->hw->using_fs) + status = dev_set_scanning_resolution (s->hw, x_dpi, y_dpi); + else + status = set_resolution (s->hw, x_dpi, y_dpi); + + *x_res = x_dpi; + *y_res = y_dpi; + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_resolution(%d, %d) failed (%s)", + x_dpi, y_dpi, sane_strstatus (status)); + return status; + } + + if (s->hw->cmd->set_zoom != 0) + { + status = set_zoom (s->hw, s->val[OPT_ZOOM].w, s->val[OPT_ZOOM].w); + if (status != SANE_STATUS_GOOD) + { + err_fatal ("set_zoom(%d) failed (%s)", + s->val[OPT_ZOOM].w, sane_strstatus (status)); + return status; + } + } + return status; +} + +static void +wait_for_button (Epson_Scanner *s) +{ + SANE_Bool button_status; + + log_call (); + + /* If WAIT_FOR_BUTTON is active, then do just that: Wait until the + * button is pressed. If the button was already pressed, then we + * will get the button Pressed event right away. + */ + if (s->val[OPT_WAIT_FOR_BUTTON].w == SANE_TRUE) + { + s->hw->wait_for_button = SANE_TRUE; + + while (s->hw->wait_for_button == SANE_TRUE) + { + if (s->raw.cancel_requested) + { + s->hw->wait_for_button = SANE_FALSE; + } + /* get the button status from the scanner */ + else if (get_push_button_status (s->hw, &button_status) == + SANE_STATUS_GOOD) + { + if (button_status == SANE_TRUE) + { + s->hw->wait_for_button = SANE_FALSE; + } + else + { + microsleep (s->hw->polling_time); + } + } + else + { + /* we run into an eror condition, just continue */ + s->hw->wait_for_button = SANE_FALSE; + } + } + } +} + +static SANE_Status +set_line_count (Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + int lcount = 1; + s->hw->block_mode = SANE_FALSE; + + log_call (); + + /* The set line count commands needs to be sent for certain scanners + * in color mode. The D1 level requires it, we are however only + * testing for 'D' and not for the actual numeric level. + */ + if (((s->hw->cmd->level[0] == 'B') && + ((s->hw->level >= 5) || ((s->hw->level >= 4) && + (!mode_params[s->val[OPT_MODE].w].color)))) + || (s->hw->cmd->level[0] == 'D')) + { + channel *ch = s->hw->channel; /* for the sake of brevity */ + s->hw->block_mode = SANE_TRUE; + lcount = ch->max_request_size (ch) / s->raw.ctx.bytes_per_line; + + if (0 >= lcount) lcount = 1; + + if (lcount > 255) + { + lcount = 255; + } + + if (using (s->hw, tpu) && lcount > 32) + { + lcount = 32; + } + + /* The D1 series of scanners only allow an even line number + * for bi-level scanning. If a bit depth of 1 is selected, then + * make sure the next lower even number is selected. + */ + { + if (3 < lcount && lcount % 2) + { + lcount -= 1; + } + } + s->line_count = lcount; + + if (lcount == 0) + { + err_fatal ("can not set zero line count"); + return SANE_STATUS_NO_MEM; + } + + status = set_lcount (s->hw, lcount); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_lcount(%d) failed (%s)", + lcount, sane_strstatus (status)); + return status; + } + } + return status; +} + +static SANE_Status +request_command_parameters (Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + if (s->hw->cmd->request_condition != 0) + { + u_char params[2]; + u_char result[4]; + u_char *buf; + size_t len; + + params[0] = ESC; + params[1] = s->hw->cmd->request_condition; + + /* send request condition */ + channel_send (s->hw->channel, params, 2, &status); + + if (SANE_STATUS_GOOD != status) + { + return status; + } + + len = 4; + channel_recv (s->hw->channel, result, len, &status); + + if (SANE_STATUS_GOOD != status) + { + return status; + } + s->hw->status = result[1]; + + len = result[3] << 8 | result[2]; + buf = t_alloca (len, u_char); + channel_recv (s->hw->channel, buf, len, &status); + + if (SANE_STATUS_GOOD != status) + { + return status; + } + + log_info ("SANE_START: Color: %d", (int) buf[1]); + log_info ("SANE_START: Resolution (x, y): (%d, %d)", + (int) (buf[4] << 8 | buf[3]), (int) (buf[6] << 8 | buf[5])); + log_info ("SANE_START: Scan offset (x, y): (%d, %d)", + (int) (buf[9] << 8 | buf[8]), (int) (buf[11] << 8 | buf[10])); + log_info ("SANE_START: Scan size (w, h): (%d, %d)", + (int) (buf[13] << 8 | buf[12]), (int) (buf[15] << 8 | buf[14])); + log_info ("SANE_START: Data format: %d", (int) buf[17]); + log_info ("SANE_START: Halftone: %d", (int) buf[19]); + log_info ("SANE_START: Brightness: %d", (int) buf[21]); + log_info ("SANE_START: Gamma: %d", (int) buf[23]); + log_info ("SANE_START: Zoom (x, y): (%d, %d)", (int) buf[26], + (int) buf[25]); + log_info ("SANE_START: Color correction: %d", (int) buf[28]); + log_info ("SANE_START: Sharpness control: %d", (int) buf[30]); + log_info ("SANE_START: Scanning mode: %d", (int) buf[32]); + log_info ("SANE_START: Mirroring: %d", (int) buf[34]); + log_info ("SANE_START: Auto area segmentation: %d", (int) buf[36]); + log_info ("SANE_START: Threshold: %d", (int) buf[38]); + log_info ("SANE_START: Line counter: %d", (int) buf[40]); + log_info ("SANE_START: Option unit control: %d", (int) buf[42]); + log_info ("SANE_START: Film type: %d", (int) buf[44]); + } + return status; +} + +static SANE_Status +device_sheet_setup (Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + int left, top; + + int x_dpi = 0; + int y_dpi = 0; + + const struct mode_param *mparam = NULL; + + status = dev_load_paper (s->hw); + if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status) + { + return status; + } + status = device_set_focus (s); + if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status) + { + return status; + } + status = dev_request_extended_status (s->hw); + if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status) + { + return status; + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap) + && s->val[OPT_AUTOCROP].b) + { + s->val[OPT_TL_X].w = SANE_FIX (0); + s->val[OPT_TL_Y].w = SANE_FIX (0); + s->val[OPT_BR_X].w = s->hw->src->x_range.max; + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + } + else if (has_size_check_support (s->hw->src) + && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap) + && s->val[OPT_DETECT_DOC_SIZE].w) + { + s->val[OPT_TL_X].w = SANE_FIX (0); + s->val[OPT_TL_Y].w = SANE_FIX (0); + s->val[OPT_BR_X].w = SANE_FIX (s->hw->src->doc_x); + s->val[OPT_BR_Y].w = SANE_FIX (s->hw->src->doc_y); + } + + adf_handle_adjust_alignment (s, SANE_TRUE); + + status = set_scan_parameters (s, &x_dpi, &y_dpi); + if (SANE_STATUS_GOOD != status) + { + return status; + } + + calculate_scan_area_offset (s->val, &left, &top); + + /* Calculate correction for line_distance in D1 scanner: Start + * line_distance lines earlier and add line_distance lines at the + * end. + * + * Because the actual line_distance is not yet calculated we have to + * do this first. + */ + + s->hw->color_shuffle = SANE_FALSE; + s->current_output_line = 0; + s->color_shuffle_line = 0; + + mparam = mode_params + s->val[OPT_MODE].w; + if ((s->hw->optical_res != 0) && (mparam->depth == 8) + && (mparam->mode_flags != 0)) + { + s->line_distance = s->hw->max_line_distance * x_dpi / s->hw->optical_res; + if (s->line_distance != 0) + { + s->hw->color_shuffle = SANE_TRUE; + } + else + s->hw->color_shuffle = SANE_FALSE; + } + + estimate_parameters (s, NULL); + { /* finalise the number of scanlines */ + int lines = s->raw.ctx.lines; + int max_lines = lines; + + if (!need_autocrop_override (s)) + { + max_lines = (SANE_UNFIX(s->hw->src->y_range.max) + * s->val[OPT_Y_RESOLUTION].w * s->val[OPT_ZOOM].w / 100 + / MM_PER_INCH) + 0.5; + } + /* add extra lines at the top and bottom to minimise the loss of + scanlines due to colour shuffling */ + if (SANE_TRUE == s->hw->color_shuffle) + { + top -= 1 * s->line_distance; + lines += 2 * s->line_distance; + } + + /* make sure values are within range + In the worst case, this chomps 2 * s->line_distance lines from + the area the user wanted to scan. C'est la vie. */ + top = max (0, top); + lines = min (lines, max_lines - top); + + if (s->hw->using_fs) + { + status = dev_set_scanning_area (s->hw, left, top, + s->raw.ctx.pixels_per_line, lines); + } + else + { + status = set_scan_area (s->hw, left, top, + s->raw.ctx.pixels_per_line, lines); + } + + /* substract the additional lines needed for colour shuffling so + the frontend can know how many lines to expect */ + if (SANE_TRUE == s->hw->color_shuffle) + { + lines -= 2 * s->line_distance; + } + s->raw.ctx.lines = lines; + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("set_scan_area failed (%s)", + sane_strstatus (status)); + return status; + } + } + + status = set_line_count (s); + if (SANE_STATUS_GOOD != status) + { + return status; + } + + if (s->hw->using_fs) + { + /* if it is previewing now, disable duplex */ + byte adf_duplex = ((1 == s->val[OPT_ADF_MODE].w) + && !s->val[OPT_PREVIEW].b) ? 0x01 : 0x00; + dev_set_option_unit (s->hw, adf_duplex); + status = cmd_set_scanning_parameter (s->hw); + } + + if (SANE_STATUS_GOOD != status) + { + return status; + } + + if (s->hw->using_fs) + { + status = cmd_request_scanning_parameter (s->hw); + dev_log_scanning_parameter (s->hw); + } + else + status = request_command_parameters (s); + + if (SANE_STATUS_GOOD != status) + { + return status; + } + + if (s->hw->channel->interpreter) + { + status = s->hw->channel->interpreter->ftor1 (s->hw->channel, &s->raw.ctx, + mparam->depth, left, x_dpi, + s->hw->optical_res); + if (SANE_STATUS_GOOD != status) + { + return status; + } + } + + wait_for_button (s); + + return status; +} + +/*! \brief Implements image data buffer resizing policy. + * + * The implementation basically reuses previously acquired image data + * acquisition buffers as much as possible. This mitigates the risk + * of a (consecutive) scan aborting someway halfway through because + * memory allocation failed. + */ +static +bool +resize_warranted (size_t needed, size_t capacity) +{ + return needed > capacity; +} + +SANE_Status +sane_start (SANE_Handle handle) +{ + Epson_Scanner *s = (Epson_Scanner *) handle; + SANE_Status status; + u_char params[4]; + size_t len_raw; + + log_call (); + + s->raw.cancel_requested = false; + s->raw.all_data_fetched = false; + s->raw.transfer_started = false; + s->raw.transfer_stopped = false; + + s->img.cancel_requested = false; + s->img.all_data_fetched = false; + s->img.transfer_started = false; + s->img.transfer_stopped = false; + + s->src = &s->raw; + + if (0 == s->frame_count) + { + /* Back up original SANE options. + * \todo string and word array have to copy individually. + */ + memcpy (s->val_bak, s->val, (sizeof (s->val[0]) * NUM_OPTIONS)); + } + else if (!s->val[OPT_ADF_AUTO_SCAN].w) + { + /* Restore original SANE options. + * \todo string and word array have to copy individually. + */ + memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS)); + } + + if (!scan_area_is_valid (s)) + { + err_fatal ("The image data resulting from the combination of the " + "specified scan area and resolution is too large."); + return SANE_STATUS_INVAL; + } + + /* for AFF devices, check paper status here instead of in scan_finish + * to avoid blocking there in dev_request_extended_status() + */ + if (0 < s->frame_count + && using (s->hw, adf) + && adf_has_auto_form_feed (s->hw)) + adf_handle_out_of_paper (s); + + if (0 == s->frame_count) + { + status = device_init (s); + if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status) + { + return status; + } + } + + if (ENABLE_TIMING && 0 == time_pass_count) + { + time_clear (); + time_stamp (time_scan, start); + } + + if (!s->val[OPT_ADF_AUTO_SCAN].w || 0 == s->frame_count) + { + status = device_sheet_setup (s); + if (SANE_STATUS_GOOD != status) + { + return status; + } + } + + if (SANE_STATUS_GOOD != (status = check_warmup (s->hw)) + && !(SANE_STATUS_NO_DOCS == status + && using (s->hw, adf))) + { + return status; + } + + status = dev_lock (s->hw); + if (SANE_STATUS_GOOD != status) + { + return status; + } + + params[0] = ESC; + if (s->hw->using_fs) params[0] = FS; + params[1] = s->hw->cmd->start_scanning; + + if (ENABLE_TIMING && TIME_PASS_MAX > time_pass_count) + time_stamp (time_pass[time_pass_count], start); + + channel_send (s->hw->channel, params, 2, &status); + + if (SANE_STATUS_GOOD != status) + { + err_fatal ("start failed (%s)", sane_strstatus (status)); + return status; + } + + if (s->hw->color_shuffle == SANE_TRUE) + { + size_t len_line_buffer = s->raw.ctx.bytes_per_line; + + if (resize_warranted (len_line_buffer, s->cap_line_buffer)) + { + size_t i; + + for (i = 0; i < 2 * s->line_distance + 1; ++i) + delete (s->line_buffer[i]); + s->cap_line_buffer = 0; + + for (i = 0; i < 2 * s->line_distance + 1; ++i) + { + SANE_Byte *p = t_malloc (len_line_buffer, SANE_Byte); + + if (p) + { + s->line_buffer[i] = p; + } + else /* clean up and bail */ + { + size_t j; + + for (j = 0; j < i; ++i) + delete (s->line_buffer[i]); + + s->cap_line_buffer = 0; + + return SANE_STATUS_NO_MEM; + } + s->cap_line_buffer = len_line_buffer; + } + } + } + + if (s->hw->using_fs) + { + s->hw->block_mode = SANE_TRUE; + status = read_image_info_block (s->hw); + s->raw.transfer_started = (s->hw->block_total + * s->hw->image_block_size + + s->hw->final_block_size + > 0); + if (SANE_STATUS_GOOD != status) return status; + + len_raw = s->hw->image_block_size + 1; /* for error code */ + } + else + { + len_raw = s->line_count * s->raw.ctx.bytes_per_line; + } + + if (resize_warranted (len_raw, s->raw.cap)) + { + delete (s->raw.buf); + s->raw.cap = 0; + + if (!(s->raw.buf = t_malloc (len_raw, SANE_Byte))) + return SANE_STATUS_NO_MEM; + + s->raw.cap = len_raw; + } + s->raw.ptr = s->raw.end = s->raw.buf; + s->raw.transfer_started = true; + + /* This here will block sane_start() until the whole image has been + * scanned and pre-processed. The assumption made here is that the + * pre-processing can not be done in place and that the resulting + * image is no larger than the image acquired. + */ + if (dip_needs_whole_image (s->dip, s->val, s->opt)) + { + SANE_Int len_img = s->raw.ctx.bytes_per_line * s->raw.ctx.lines; + SANE_Int max = len_img; + SANE_Int len = 0; + + log_info ("buffering image before returning from sane_start()"); + + if (resize_warranted (len_img, s->img.cap)) + { + delete (s->img.buf); + s->img.cap = 0; + + if (!(s->img.buf = t_malloc (len_img, SANE_Byte))) + return SANE_STATUS_NO_MEM; + + s->img.cap = len_img; + } + + s->img.ptr = s->img.buf; + do /* note: non-blocking I/O not supported */ + { + s->img.ptr += len; + max -= len; + status = fetch_image_data (s, s->img.ptr, max, &len); + } + while (SANE_STATUS_GOOD == status); + + if (SANE_STATUS_EOF != status) + return status; + + if (0 != max) + return SANE_STATUS_IO_ERROR; + + s->img.ptr = s->img.buf; + s->img.end = s->img.buf + len_img; + memcpy (&s->img.ctx, &s->raw.ctx, sizeof (s->raw.ctx)); + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap) + && s->val[OPT_DESKEW].b) + { + dip_deskew (s->dip, s->hw, s->frame_count, &s->img, s->val); + } + + if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap) + && s->val[OPT_AUTOCROP].b) + { + dip_autocrop (s->dip, s->hw, s->frame_count, &s->img, s->val); + } + + s->img.all_data_fetched = true; + s->img.transfer_started = true; + s->src = &s->img; + } + + return SANE_STATUS_GOOD; +} + +/*! Retrieves an image info block and computes image data block info. + */ +static SANE_Status +read_image_info_block (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + u_char buf[14]; /* largest image handshake "info" block */ + int buf_size = num_of (buf); + + const int limit = 30; + int ticks = 0; + + log_call (); + + if (!hw->using_fs) + buf_size = (hw->block_mode ? 6 : 4); + + channel_recv_all_retry (hw->channel, buf, buf_size, + MAX_READ_ATTEMPTS, &status); + + /* Prevent reporting of stale values if we bail early. + */ + hw->image_block_size = 0; + hw->final_block_size = 0; + if (hw->using_fs) + { + hw->block_total = 0; + hw->block_count = 0; + } + + if (SANE_STATUS_GOOD != status) + return status; + + if (STX != buf[0]) + { + log_data ("code %02x", buf[0]); + log_data ("error, expected STX"); + + return SANE_STATUS_INVAL; + } + hw->status = buf[1]; + + /* Update values here so they are also available in case we bail in + the while loop below. + */ + if (hw->using_fs) + { + hw->image_block_size = buf_to_uint32 (buf + 2); + hw->final_block_size = buf_to_uint32 (buf + 10); + hw->block_total = buf_to_uint32 (buf + 6); + hw->block_count = 0; + } + else + { + hw->image_block_size = buf[3] << 8 | buf[2]; + if (hw->block_mode) + hw->image_block_size *= buf[5] << 8 | buf[4]; + } + + /* Although spec compliant scanners have their warming up bit set + when they are getting ready for a scan, the world is less than + perfect :-( + + We hack around non-compliant behaviour by looping until 'ESC G' + succeeds or our patience runs out. In the latter case we just + claim that the device is busy. + + However, when the ADF function is used, and the last paper is + scanned, we do not need looping any more. + */ + while (hw->status & (STATUS_FATAL_ERROR | STATUS_NOT_READY) + && !(using (hw, adf) + && (hw->status & STATUS_AREA_END)) + && ticks++ < limit) + { + u_char cmd[2]; + + err_fatal ("fatal error - Status = %02x", hw->status); + + if (SANE_STATUS_GOOD != (status = check_warmup (hw))) + return status; + + cmd[0] = (hw->using_fs ? FS : ESC); + cmd[1] = 'G'; + + channel_send (hw->channel, cmd, 2, &status); + sleep (1); + channel_recv_all_retry (hw->channel, buf, buf_size, + MAX_READ_ATTEMPTS, &status); + hw->status = buf[1]; + + if (hw->using_fs) + { + hw->image_block_size = buf_to_uint32 (buf + 2); + hw->final_block_size = buf_to_uint32 (buf + 10); + hw->block_total = buf_to_uint32 (buf + 6); + hw->block_count = 0; + } + else + { + hw->image_block_size = buf[3] << 8 | buf[2]; + if (hw->block_mode) + hw->image_block_size *= buf[5] << 8 | buf[4]; + } + } + return (ticks < limit + ? status + : SANE_STATUS_DEVICE_BUSY); +} + + +static void +scan_finish (Epson_Scanner * s) +{ + log_call (); + + s->raw.transfer_stopped = true; + + if (s->hw->channel->interpreter) /* FIXME: do we really need this? */ + { + s->hw->channel->interpreter->free (s->hw->channel); + } + + s->frame_count++; + + if (!using (s->hw, adf)) return; /* we're done */ + + if (!(EXT_STATUS_ADFT & s->hw->ext_status) /* sheet feed */ + && !adf_has_auto_form_feed (s->hw)) + { + dev_eject_paper (s->hw); + } + + /* avoid blocking here on dev_request_extended_status() + * with AFF devices, unless canceling + */ + if (!adf_has_auto_form_feed (s->hw) || s->raw.cancel_requested) + adf_handle_out_of_paper (s); +} + +/* part of the scan sequence logic and should not be used generally */ +static void +adf_handle_out_of_paper (Epson_Scanner * s) +{ + if (!using (s->hw, adf)) return; + + /* if we're finishing an ADF scan and the ADF unit has been disabled, + * enable it again in order to get accurate status information + */ + if (using (s->hw, adf) && !(ADF_STATUS_EN & s->hw->adf->status)) + { + /* if it is previewing now, disable duplex */ + byte value = ((s->hw->adf->using_duplex && !s->val[OPT_PREVIEW].b) + ? 0x02 : 0x01); + cmd_control_option_unit (s->hw, value); + } + + dev_request_extended_status (s->hw); + if (!(ADF_STATUS_PE & s->hw->adf->status)) return; + + log_info ("ADF: out of paper, %s mode, %d sheets", + ((ADF_STATUS_PAG & s->hw->adf->status) ? "duplex" : "simplex"), + s->hw->adf->sheet_count); + if (!(ADF_STATUS_PAG & s->hw->adf->status) + || 0 == (s->hw->adf->sheet_count % 2)) + { + dev_eject_paper (s->hw); + s->frame_count = 0; + /* Restore original SANE options. + * \todo string and word array have to copy individually. + */ + memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS)); + } + else + log_info ("ADF: scanning reverse side"); +} + +static SANE_Bool +adf_needs_realignment (const device *hw) +{ + SANE_Byte dpos; + + require (using (hw, adf)); + + dpos = hw->fsi_cap_3 & FSI_CAP_DPOS_MASK; + + return (adf_needs_manual_centering (hw) + || FSI_CAP_DPOS_CNTR == dpos + || FSI_CAP_DPOS_RIGT == dpos); +} + +static void +adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize) +{ + scan_area_t adf_scan_area; + double shift = 0.0; + double sides = 2; /* distribute shift on both sides + --> we center by default */ + log_call(); + + if (!using (s->hw, adf)) return; + if (!adf_needs_realignment (s->hw)) return; + + log_info ("before alignment: tl-x = %.2f, br-x = %.2f", + SANE_UNFIX (s->val[OPT_TL_X].w), + SANE_UNFIX (s->val[OPT_BR_X].w)); + + adf_scan_area = + get_model_info_max_scan_area (s->hw, s->val[OPT_ADF_MODE].w); + + if(SANE_UNFIX (adf_scan_area.width) < 0 + && SANE_UNFIX (adf_scan_area.height) < 0) + { + adf_scan_area.width = s->hw->src->x_range.max; + adf_scan_area.height = s->hw->src->y_range.max; + } + + if (FSI_CAP_DPOS_RIGT == s->hw->fsi_cap_3 & FSI_CAP_DPOS_MASK) + { + sides = 1; /* put whole shift on one side */ + } + + /* scan area setting or no marquee */ + if (!finalize || + (adf_scan_area.width == (s->val[OPT_BR_X].w - s->val[OPT_TL_X].w) + && adf_scan_area.height == (s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w))) + { + double scan_width = (SANE_UNFIX (s->val[OPT_BR_X].w) + - SANE_UNFIX (s->val[OPT_TL_X].w)); + shift = SANE_UNFIX (s->hw->src->x_range.max) - scan_width; + shift /= sides; + + s->val[OPT_TL_X].w = SANE_FIX (shift + 0); + s->val[OPT_BR_X].w = SANE_FIX (shift + scan_width); + } + /* uses auto-detect document size */ + else if (has_size_check_support (s->hw->src) + && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap) + && s->val[OPT_DETECT_DOC_SIZE].w) + { + shift = SANE_UNFIX (s->hw->src->x_range.max) - s->hw->src->doc_x; + shift /= sides; + + s->val[OPT_TL_X].w = SANE_FIX (shift + 0); + s->val[OPT_BR_X].w = SANE_FIX (shift + s->hw->src->doc_x); + } + /* has marquee and adf-duplex scan area differ from adf-simplex scan area */ + else if (!(adf_scan_area.width == s->hw->src->x_range.max + && adf_scan_area.height == s->hw->src->y_range.max)) + { + shift = SANE_UNFIX (s->hw->src->x_range.max - adf_scan_area.width); + shift /= sides; + + s->val[OPT_TL_X].w += SANE_FIX (shift); + s->val[OPT_BR_X].w += SANE_FIX (shift); + } + + log_info ("after alignment : tl-x = %.2f, br-x = %.2f", + SANE_UNFIX (s->val[OPT_TL_X].w), + SANE_UNFIX (s->val[OPT_BR_X].w)); + log_info ("shifted scan area offset by %.2f mm", shift); +} + +#define GET_COLOR(x) (((x) >> 2) & 0x03) + +SANE_Status +fetch_image_data (Epson_Scanner *s, SANE_Byte * data, SANE_Int max_length, + SANE_Int * length) +{ + SANE_Status status; + int index = 0; + SANE_Bool reorder = SANE_FALSE; + SANE_Bool needStrangeReorder = SANE_FALSE; + + log_call (); + + if (s->raw.transfer_stopped && s->raw.cancel_requested) + return SANE_STATUS_CANCELLED; + +START_READ: + if (s->raw.ptr == s->raw.end) + { + size_t buf_len; + + if (s->raw.all_data_fetched) + { + *length = 0; + return SANE_STATUS_EOF; + } + + if (!s->hw->using_fs) + { + status = read_image_info_block (s->hw); + if (SANE_STATUS_GOOD != status) + { + *length = 0; + scan_finish (s); + return status; + } + buf_len = s->hw->image_block_size; + } + else + { + buf_len = s->hw->image_block_size; + if (s->hw->block_count >= s->hw->block_total) + buf_len = s->hw->final_block_size; + ++buf_len; /* include error byte */ + } + + if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format) + { + /* Read color data in line mode */ + + /* read the first color line - the number of bytes to read + * is already known (from last call to read_image_info_block() + * We determine where to write the line from the color information + * in the data block. At the end we want the order RGB, but the + * way the data is delivered does not guarantee this - actually it's + * most likely that the order is GRB if it's not RGB! + */ + switch (GET_COLOR (s->hw->status)) + { + case 1: + index = 1; + break; + case 2: + index = 0; + break; + case 3: + index = 2; + break; + } + + channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line, + buf_len, &status); + + if (SANE_STATUS_GOOD != status) + { + *length = 0; + scan_finish (s); + return status; + } + + /* send the ACK signal to the scanner in order to make + * it ready for the next image info block. + */ + channel_send (s->hw->channel, S_ACK, 1, &status); + + /* ... and request the next image info block + */ + if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw))) + { + *length = 0; + scan_finish (s); + return status; + } + + buf_len = s->hw->image_block_size; + + switch (GET_COLOR (s->hw->status)) + { + case 1: + index = 1; + break; + case 2: + index = 0; + break; + case 3: + index = 2; + break; + } + + channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line, + buf_len, &status); + + if (SANE_STATUS_GOOD != status) + { + *length = 0; + scan_finish (s); + return status; + } + + channel_send (s->hw->channel, S_ACK, 1, &status); + + /* ... and the last image info block + */ + if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw))) + { + *length = 0; + scan_finish (s); + return status; + } + + buf_len = s->hw->image_block_size; + + switch (GET_COLOR (s->hw->status)) + { + case 1: + index = 1; + break; + case 2: + index = 0; + break; + case 3: + index = 2; + break; + } + + channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line, + buf_len, &status); + + if (SANE_STATUS_GOOD != status) + { + *length = 0; + scan_finish (s); + return status; + } + } + else + { + /* Read image data in block mode */ + + /* do we have to reorder the image data ? */ + if (GET_COLOR (s->hw->status) == 0x01) + { + reorder = SANE_TRUE; + } + + channel_recv_all_retry (s->hw->channel, s->raw.buf, buf_len, + MAX_READ_ATTEMPTS, &status); + + if (SANE_STATUS_GOOD != status) + { + *length = 0; + scan_finish (s); + return status; + } + } + + if (s->hw->using_fs) + { + u_char err = 0; + + if (s->hw->block_count >= s->hw->block_total) + s->raw.all_data_fetched = true; + ++(s->hw->block_count); + log_info ("read image block %u/%u", + s->hw->block_count, s->hw->block_total + 1); + + err = s->raw.buf[--buf_len]; /* drop error byte */ + log_info ("image block error byte: %x", err); + + if ((FSG_FATAL_ERROR | FSG_NOT_READY) & err) + { + *length = 0; + scan_finish (s); + return check_ext_status (s->hw); + } + else if (FSG_CANCEL_REQUEST & err) + { + s->raw.cancel_requested = true; + } + else if (FSG_PAGE_END & err) + { + if (FSI_CAP_PED & s->hw->fsi_cap_2) + { + log_info ("paper end flag raised"); + } + else + { + err_minor ("invalid paper end flag raised"); + } + } + else if (0 != err) + { + log_info ("unknown error flag(s) raised"); + *length = 0; + scan_finish (s); + return check_ext_status (s->hw); + } + } + else + { + s->raw.all_data_fetched = (STATUS_AREA_END & s->hw->status); + } + + if (s->raw.all_data_fetched + && ENABLE_TIMING && TIME_PASS_MAX > time_pass_count) + { + time_stamp (time_pass[time_pass_count], stop); + ++time_pass_count; + } + + if (!s->raw.all_data_fetched) + { + if (s->raw.cancel_requested) + { + channel_send (s->hw->channel, S_CAN, 1, &status); + if (SANE_STATUS_GOOD != status) return status; + + status = expect_ack (s->hw); + if (SANE_STATUS_GOOD != status) return status; + + *length = 0; + + scan_finish (s); + + return SANE_STATUS_CANCELLED; + } + else + { + channel_send (s->hw->channel, S_ACK, 1, &status); + if (SANE_STATUS_GOOD != status) return status; + } + } + + s->raw.end = s->raw.buf + buf_len; + s->raw.ptr = s->raw.buf; + + /* if we have to re-order the color components (GRB->RGB) we + * are doing this here: + */ + + /* Some scanners (e.g. the Perfection 1640 and GT-2200) seem + * to have the R and G channels swapped. + * The GT-8700 is the Asian version of the Perfection1640. + * If the scanner name is one of these, and the scan mode is + * RGB then swap the colors. + */ + + needStrangeReorder = ((0 == strcmp_c (s->hw->fw_name, "GT-2200") + || 0 == strcmp_c (s->hw->fw_name, "Perfection1640") + || 0 == strcmp_c (s->hw->fw_name, "GT-8700")) + && s->raw.ctx.format == SANE_FRAME_RGB); + + /* Certain Perfection 1650 also need this re-ordering of the two + * color channels. These scanners are identified by the problem + * with the half vertical scanning area. When we corrected this, + * we also set the variable s->hw->need_color_reorder + */ + if (s->hw->need_color_reorder) + { + needStrangeReorder = SANE_TRUE; + } + + if (needStrangeReorder) + reorder = SANE_FALSE; /* reordering once is enough */ + + if (s->raw.ctx.format != SANE_FRAME_RGB) + reorder = SANE_FALSE; /* don't reorder for BW or gray */ + + if (reorder) + { + s->raw.ptr = s->raw.buf; + dip_change_GRB_to_RGB (s->dip, &s->raw); + } + + /* Do the color_shuffle if everything else is correct - at this + * time most of the stuff is hardcoded for the Perfection 610 + */ + if (s->hw->color_shuffle) + { + int new_length = 0; + + status = color_shuffle (s, &new_length); + + /* If no bytes are returned, check if the scanner is already + * done, if so, we'll probably just return, but if there is more + * data to process get the next batch. + */ + + if (new_length == 0 && s->raw.end != s->raw.ptr) + { + goto START_READ; + } + + s->raw.end = s->raw.buf + new_length; + s->raw.ptr = s->raw.buf; + } + + if ((SANE_CAP_EMULATED & s->opt[OPT_CCT_1].cap) + && s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w] + && SANE_FRAME_RGB == s->raw.ctx.format) + { + dip_apply_color_profile (s->dip, &s->raw, s->cct); + } + + if (s->hw->channel->interpreter) + { + s->hw->channel->interpreter->ftor0 (s->hw->channel, + &s->raw.ctx, + s->raw.ptr, s->raw.end); + } + + if (s->lut) + { + dip_apply_LUT (s->dip, &s->raw, s->lut); + } + + /* WARNING: The SANE specification normally uses zero to indicate + * minimum intensity. However, SANE_FRAME_GRAY images with a bit + * depth of one use zero to indicate *maximum* intensity. + * The device always uses zero for minimum intensity, irrespective + * of the color mode and bit depth. + */ + if (1 == s->raw.ctx.depth && SANE_FRAME_GRAY == s->raw.ctx.format) + { + if (!s->invert_image) + dip_flip_bits (s->dip, &s->raw); + } + else + { + if (s->invert_image) + dip_flip_bits (s->dip, &s->raw); + } + } + + /* copy the image data to the data memory area + */ + if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format) + { + max_length /= 3; + + if (max_length > s->raw.end - s->raw.ptr) + max_length = s->raw.end - s->raw.ptr; + + *length = 3 * max_length; + + if (s->raw.cancel_requested) + s->raw.ptr += max_length; + else + { + while (max_length-- != 0) + { + *data++ = s->raw.ptr[0]; + *data++ = s->raw.ptr[s->raw.ctx.pixels_per_line]; + *data++ = s->raw.ptr[2 * s->raw.ctx.pixels_per_line]; + ++s->raw.ptr; + } + } + } + else + { + if (max_length > s->raw.end - s->raw.ptr) + max_length = s->raw.end - s->raw.ptr; + + *length = max_length; + + if (s->raw.cancel_requested) + s->raw.ptr += max_length; + else + { + memcpy (data, s->raw.ptr, max_length); + s->raw.ptr += max_length; + } + } + + if (s->raw.ptr == s->raw.end && s->raw.all_data_fetched) + { + scan_finish (s); + if (0 == strcmp_c (s->hw->fw_name, "DS-30")) + { + status = check_ext_status (s->hw); + /* Ignore ADF paper empty */ + s->hw->adf->status &= ~ADF_STATUS_PE; + if ( SANE_STATUS_GOOD != status + && SANE_STATUS_NO_DOCS != status) + { + return status; + } + } + } + log_call ("exit"); + + return SANE_STATUS_GOOD; +} + + +/*! Puts raw scan data into the correct scan line. + + When scanning with a non-zero line distance, the RGB data is \e + not on a single line in the raw scan data. The RGB channels are + separated by a number of lines that depends on the line distance + as reported by the ESC i command and the resolution of the scan. + + This function reorganises raw scan data so that the RGB channels + are no longer separated and all data is on the same scan line. + + \note + It seems that the Perfection 610 and 640U both report a maximum + scan area with the two line distances included (based on 11.7" + at 600dpi = 7020, whereas the scan area is reportedly 7036 with + both line distances equal to 8). + */ +static SANE_Status +color_shuffle (Epson_Scanner *s, int *new_length) +{ + SANE_Byte *buf = s->raw.buf; + int length = s->raw.end - s->raw.buf; + + log_call (); + + if (s->hw->color_shuffle == SANE_TRUE) + { + SANE_Byte *data_ptr; /* ptr to data to process */ + SANE_Byte *data_end; /* ptr to end of processed data */ + SANE_Byte *out_data_ptr; /* ptr to memory when writing data */ + int i; /* loop counter */ + + log_call (); + + /* Initialize the variables we are going to use for the + * copying of the data. data_ptr is the pointer to + * the currently worked on scan line. data_end is the + * end of the data area as calculated from adding *length + * to the start of data. + * out_data_ptr is used when writing out the processed data + * and always points to the beginning of the next line to + * write. + */ + + data_ptr = out_data_ptr = buf; + data_end = data_ptr + length; + + /* The image data is in *raw.buf, we know that the buffer contains + * s->raw.end - s->raw.buf ( = length) bytes of data. The width of + * one line is in s->raw.ctx.bytes_per_line + */ + + /* The buffer area is supposed to have a number of full scan + * lines, let's test if this is the case. + */ + + if (length % s->raw.ctx.bytes_per_line != 0) + { + err_major ("ERROR in size of buffer: %d / %d", + length, s->raw.ctx.bytes_per_line); + return SANE_STATUS_INVAL; + } + + while (data_ptr < data_end) + { + SANE_Byte *source_ptr, *dest_ptr; + int loop; + + /* copy the green information into the current line */ + + source_ptr = data_ptr + 1; + dest_ptr = s->line_buffer[s->color_shuffle_line] + 1; + + for (i = 0; i < s->raw.ctx.bytes_per_line / 3; i++) + { + *dest_ptr = *source_ptr; + dest_ptr += 3; + source_ptr += 3; + } + + /* copy the red information n lines back */ + + if (s->color_shuffle_line >= s->line_distance) + { + source_ptr = data_ptr + 2; + dest_ptr = + s->line_buffer[s->color_shuffle_line - s->line_distance] + 2; + + for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++) + { + *dest_ptr = *source_ptr; + dest_ptr += 3; + source_ptr += 3; + } + } + + /* copy the blue information n lines forward */ + + source_ptr = data_ptr; + dest_ptr = s->line_buffer[s->color_shuffle_line + s->line_distance]; + + for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++) + { + *dest_ptr = *source_ptr; + dest_ptr += 3; + source_ptr += 3; + } + + data_ptr += s->raw.ctx.bytes_per_line; + s->raw.ptr += s->raw.ctx.bytes_per_line; + + if (s->color_shuffle_line == s->line_distance) + { + /* We just finished shuffling the line in line_buffer[0] - + * write the RGB image data in it to the output buffer and + * cyclically shift the line_buffers up by one. + */ + + SANE_Byte *first; + + if ((s->current_output_line >= s->line_distance) && + (s->current_output_line < s->raw.ctx.lines + s->line_distance)) + { + memcpy (out_data_ptr, s->line_buffer[0], s->raw.ctx.bytes_per_line); + out_data_ptr += s->raw.ctx.bytes_per_line; + } + + s->current_output_line++; + + first = s->line_buffer[0]; + for (i = 0; i < 2 * s->line_distance; ++i) + { + s->line_buffer[i] = s->line_buffer[i + 1]; + } + s->line_buffer[2 * s->line_distance] = first; + } + else + { + s->color_shuffle_line++; /* increase the buffer number */ + } + } + + /* At this time we've used up all the new data from the scanner, + * some of it is still in the line_buffers, but we are ready to + * return some of it to the front end software. To do so we have + * to adjust the size of the data area and the *new_length + * variable. + */ + + *new_length = out_data_ptr - buf; + } + + return SANE_STATUS_GOOD; +} + + +static SANE_Status +get_identity_information (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + if (!hw->cmd->request_identity) + return SANE_STATUS_INVAL; + + status = cmd_request_identity (hw); + if (SANE_STATUS_GOOD != status) + { + return SANE_STATUS_INVAL; + } + log_data ("detected command level %s", hw->cmd_lvl); + + { + char *force = getenv ("SANE_EPSON_CMD_LVL"); + + if (force) + { + hw->cmd_lvl[0] = force[0]; + hw->cmd_lvl[1] = force[1]; + log_info ("forced command level %s", hw->cmd_lvl); + } + } + + /* check if option equipment is installed */ + + if (hw->status & STATUS_OPTION) + { + log_info ("option equipment is installed"); + } + + delete (hw->tpu); /* FIXME: is this necessary?? */ + delete (hw->adf); + + /* set command type and level */ + { + int n = 0; + + while (num_of (epson_cmd) > n + && (0 != strcmp_c (hw->cmd_lvl, epson_cmd[n].level))) + ++n; + + if (num_of (epson_cmd) > n) + hw->cmd = &epson_cmd[n]; + else + err_minor ("unknown command level %s, using %s instead", + hw->cmd_lvl, hw->cmd->level); + + hw->level = hw->cmd->level[1] - '0'; + } /* set comand type and level */ + + if (hw->using_fs && 'D' != hw->cmd->level[0]) return SANE_STATUS_GOOD; + + /* Setting available resolutions and xy ranges for sane frontend. */ + { + hw->dpi_range.min = hw->res.list[1]; + hw->dpi_range.max = hw->res.list[hw->res.size]; + if (!hw->using_fs) hw->base_res = hw->res.list[hw->res.size]; + hw->dpi_range.quant = 0; + + if (hw->fbf) + { + update_ranges (hw, hw->fbf); + + log_info ("FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]", + SANE_UNFIX (hw->fbf->x_range.min), + SANE_UNFIX (hw->fbf->y_range.min), + SANE_UNFIX (hw->fbf->x_range.max), + SANE_UNFIX (hw->fbf->y_range.max)); + } + } + + copy_resolution_info (&hw->res_x, &hw->res, SANE_FALSE); + copy_resolution_info (&hw->res_y, &hw->res, SANE_FALSE); + + return SANE_STATUS_GOOD; +} /* request identity */ + + +static SANE_Status +get_hardware_property (device *hw) +{ + SANE_Status status = SANE_STATUS_GOOD; + + log_call (); + + if (hw->cmd->request_identity2 == 0) + return SANE_STATUS_UNSUPPORTED; + + status = cmd_request_hardware_property (hw); + if (SANE_STATUS_GOOD != status) + { + return status; + } + + log_info ("optical resolution: %ddpi", hw->optical_res); + + if (hw->line_dist_x != hw->line_dist_y) + { + return SANE_STATUS_INVAL; + } + hw->max_line_distance = hw->line_dist_y; + if (hw->fbf) + { + hw->fbf->y_range.max = SANE_FIX ((hw->fbf->max_y - + 2 * (hw->max_line_distance)) + * MM_PER_INCH / hw->base_res); + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_identity2_information (device *hw, Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + + require (s && (hw == s->hw)); + + status = get_hardware_property (hw); + if (SANE_STATUS_GOOD != status) + return status; + + /* reset constraints because we are pointing to different lists now + FIXME: Only needs to be done the first time we (successfully) + send the ESC i command. This should be done when constructing + the device and is therefore done by the time we construct a + scanner. While the content and size of the lists may vary + depending on the selected option, the list nature is constant. + */ + s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_X_RESOLUTION].constraint.word_list = hw->res_x.list; + s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_Y_RESOLUTION].constraint.word_list = hw->res_y.list; + + return SANE_STATUS_GOOD; +} + + +void +sane_cancel (SANE_Handle handle) +{ + Epson_Scanner *s = (Epson_Scanner *) handle; + + log_call (); + + s->img.cancel_requested = true; + + if (s->src->transfer_started && !s->src->transfer_stopped) + { + SANE_Byte dumpster[1024]; + int len; + + if (s->src == &s->raw) + { + s->raw.cancel_requested = true; + do + { + fetch_image_data (s, dumpster, num_of (dumpster), &len); + } + while (!s->raw.transfer_stopped); + } + + if (using (s->hw, adf) && 0 != s->hw->adf->sheet_count + && ((EXT_STATUS_ADFT & s->hw->ext_status) /* page type */ + || adf_has_auto_form_feed (s->hw))) + dev_eject_paper (s->hw); + } + if (!s->raw.cancel_requested && s->raw.all_data_fetched + && s->hw->using_fs && s->val[OPT_ADF_AUTO_SCAN].w + && SANE_STATUS_NO_DOCS != check_ext_status (s->hw)) + { + s->raw.cancel_requested = dev_force_cancel (s->hw); + } + dev_unlock (s->hw); + + if (ENABLE_TIMING) + { + time_stamp (time_scan, stop); + time_stats (time_pass_count); + time_pass_count = 0; + } + + s->frame_count = 0; + + /* Restore original SANE options. + * \todo string and word array have to copy individually. + */ + if (s->src->transfer_started) + { + memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS)); + } + + /* release resource hogs between scan sequences */ + delete (s->img.buf); + s->img.cap = 0; +} + +/* Request the push button status returns SANE_TRUE if the button was + * pressed and SANE_FALSE if the button was not pressed. It also + * returns SANE_TRUE in case of an error. This is necessary so that a + * process that waits for the button does not block indefinitely. + */ +static SANE_Status +get_push_button_status (device *hw, SANE_Bool *button_pushed) +{ + SANE_Status status; + int len; + u_char param[3]; + u_char result[4]; + u_char *buf; + + log_call (); + + if (hw->cmd->request_push_button_status == 0) + { + log_info ("push button status unsupported"); + return SANE_STATUS_UNSUPPORTED; + } + + param[0] = ESC; + param[1] = hw->cmd->request_push_button_status; + param[2] = '\0'; + + channel_send (hw->channel, param, 2, &status); + + if (SANE_STATUS_GOOD != status) + { + err_minor ("error sending command"); + return status; + } + + len = 4; /* get info block */ + channel_recv (hw->channel, result, len, &status); + if (SANE_STATUS_GOOD != status) + return status; + + /* get button status */ + hw->status = result[1]; + len = result[3] << 8 | result[2]; + buf = t_alloca (len, u_char); + channel_recv (hw->channel, buf, len, &status); + + log_info ("Push button status = %d", buf[0] & 0x01); + *button_pushed = ((buf[0] & 0x01) != 0); + + return (SANE_STATUS_GOOD); +} + + +static void +filter_resolution_list (Epson_Scanner * s) +{ + log_call (); + + if (SANE_TRUE == s->val[OPT_LIMIT_RESOLUTION].w) + { /* trim list */ + SANE_Int i; + + s->hw->res.size = 0; + for (i = 1; i <= s->hw->resolution.size; i++) + { + SANE_Word res = s->hw->resolution.list[i]; + + if ((res < 100) || (0 == (res % 300)) || (0 == (res % 400))) + { + s->hw->res.size++; + s->hw->res.list[s->hw->res.size] = res; + } + } + } + else + { /* restore full list */ + SANE_Int i; + + for (i = 1; i <= s->hw->resolution.size; i++) + { + s->hw->res.list[i] = s->hw->resolution.list[i]; + } + s->hw->res.size = s->hw->resolution.size; + } + s->hw->res.list[0] = s->hw->res.size; + + /* We have just created a filtered resolution list on the *full* + * list of resolutions. Now apply ADF capping if necessary. No + * need to call dev_restore_res as we've already (sorta) done so + * above. + */ + if (using (s->hw, adf) + && 0 < large_res_kills_adf_scan (s->hw)) + { + dev_limit_res (s->hw, s->opt[OPT_RESOLUTION].constraint_type, + large_res_kills_adf_scan (s->hw)); + } + handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w); +} + + +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ + log_call ("(%s-blocking)", (non_blocking ? "non" : "")); + + /* get rid of compiler warning */ + handle = handle; + + return SANE_STATUS_UNSUPPORTED; +} + + +SANE_Status +sane_get_select_fd (SANE_Handle handle, SANE_Int * fd) +{ + log_call (); + + /* get rid of compiler warnings */ + handle = handle; + fd = fd; + + return SANE_STATUS_UNSUPPORTED; +} + + +SANE_Status +control_option_unit (device *hw, SANE_Bool use_duplex) +{ + SANE_Status status = SANE_STATUS_GOOD; + u_char value = 0; + + log_call (); + + if (!hw) return SANE_STATUS_INVAL; + if (!(hw->adf || hw->tpu)) return SANE_STATUS_GOOD; + + value = using (hw, adf) || using (hw, tpu); + if (value && use_duplex) + { + value = 2; + hw->adf->using_duplex = SANE_TRUE; + } + + status = set_cmd (hw, hw->cmd->control_an_extension, value); + + if (using (hw, adf) + && (0 == strcmp_c ("ES-10000G", hw->fw_name) + || 0 == strcmp_c ("Expression10000", hw->fw_name))) + { + u_char *buf; + u_char params[2]; + EpsonHdr head; + int failures_allowed = 5; + + params[0] = ESC; + params[1] = hw->cmd->request_extended_status; + + head = (EpsonHdr) command (hw, params, 2, &status); + buf = &head->buf[0]; + + while (!(buf[1] & ADF_STATUS_EN)) + { + sleep (1); + status = set_cmd (hw, hw->cmd->control_an_extension, value); + + head = (EpsonHdr) command (hw, params, 2, &status); + if (SANE_STATUS_GOOD != status) + { + --failures_allowed; + if (!failures_allowed) + return status; + } + buf = &head->buf[0]; + } + } + return status; +} + +/* Fetches the main/sub resolution lists for the device. + */ +static SANE_Status +get_resolution_constraints (device *hw, Epson_Scanner *s) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Bool adf_duplex; + + log_call (); + + if (!hw->cmd->request_identity2) + return SANE_STATUS_GOOD; + + /* if it is previewing now, disable duplex */ + adf_duplex = ((1 == s->val[OPT_ADF_MODE].w) + && !s->val[OPT_PREVIEW].b); + status = control_option_unit (hw, adf_duplex); + status = get_identity2_information (hw, s); + + handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION); + handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION); + + return status; +} + + +static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode) +{ + SANE_Status status = SANE_STATUS_GOOD; + scan_area_t scan_area; + + scan_area.width = SANE_FIX(-1); + scan_area.height = SANE_FIX(-1); + + if(hw->adf){ + const void *info = model_info_cache_get_info (hw->fw_name, &status); + if (SANE_STATUS_GOOD == status && info){ + const char *mode = (1 == adf_mode ? "duplex": "simplex"); + + scan_area = model_info_max_scan_area(info, "adf", mode); + } + } + return scan_area; +} + + +static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode) +{ + SANE_Status status = SANE_STATUS_GOOD; + scan_area_t scan_area; + + if(s->hw->adf){ + scan_area = get_model_info_max_scan_area (s->hw, adf_mode); + + if(SANE_UNFIX (scan_area.width) >= 0 + && SANE_UNFIX (scan_area.height) >= 0){ + s->hw->adf->x_range.max = scan_area.width; + s->hw->adf->y_range.max = scan_area.height; + }else{ + err_minor ("failure getting model info (%s)", sane_strstatus (status)); + } + } + + { + SANE_Option_Descriptor *sopt = &(s->opt[OPT_SCAN_AREA]); + Option_Value *sval = &(s->val[OPT_SCAN_AREA]); + + SANE_String_Const area = NULL; + SANE_String_Const *p + = (SANE_String_Const *) sopt->constraint.string_list; + + if (!p) + { + s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE; + + /* media_list + media_maximum, media_automatic, and NULL terminator */ + p = t_calloc (num_of (media_list) + 3, SANE_String_Const); + + if (!p) /* do without the option */ + { + s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap; + return status; + } + + sopt->constraint.string_list = p; + } + area = p[sval->w]; + + memset (p, 0, (num_of (media_list) + 3) * sizeof (SANE_String_Const)); + sopt->size = 0; + + *p = media_maximum; + sopt->size = max (strlen (*p), sopt->size); + ++p; + + if (using (s->hw, tpu)) + { + s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE; + } + else + { + size_t i; + + if (has_size_check_support (s->hw->src)) + { + *p = media_automatic; + sopt->size = max (strlen (*p) + 1, sopt->size); + ++p; + s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_AUTOMATIC; + } + + for (i = 0; i < num_of (media_list); ++i) + { + SANE_Fixed w = SANE_FIX (media_list[i].width); + SANE_Fixed h = SANE_FIX (media_list[i].height); + + if ( w <= s->hw->src->x_range.max + && h <= s->hw->src->y_range.max) + { + *p = media_list[i].name; + sopt->size = max (strlen (*p) + 1, sopt->size); + ++p; + if (strstr (media_list[i].name, "Portrait")) + { + ++i; + } + } + else if (strstr (media_list[i].name, "Landscape")) + { /* no need to list "Portrait" */ + ++i; + } + } + + s->opt[OPT_SCAN_AREA].cap &= ~SANE_CAP_INACTIVE; + } + + { // recompute index of selected area in the new list + size_t i = 0; + p = (SANE_String_Const *) sopt->constraint.string_list; + while (*p && 0 != strcmp_c (*p, area)) + ++i, ++p; + sval->w = (*p ? i : 0); + } + } + + s->val[OPT_BR_X].w = s->hw->src->x_range.max; + s->val[OPT_BR_Y].w = s->hw->src->y_range.max; + s->val[OPT_TL_X].w = 0; + s->val[OPT_TL_Y].w = 0; + + s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range); + s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range); + s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range); + s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range); + + /* copy OPT_SCAN_AREA to OPT_QUICK_FORMAT */ + s->opt[OPT_QUICK_FORMAT].size = s->opt[OPT_SCAN_AREA].size; + s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap; + s->opt[OPT_QUICK_FORMAT].constraint.string_list + = s->opt[OPT_SCAN_AREA].constraint.string_list; + s->val[OPT_QUICK_FORMAT].w = s->val[OPT_SCAN_AREA].w; + + return status; +} diff --git a/backend/epkowa.conf b/backend/epkowa.conf new file mode 100644 index 0000000..fd387b1 --- /dev/null +++ b/backend/epkowa.conf @@ -0,0 +1,65 @@ +# epkowa.conf -- sample configuration for the EPKOWA SANE backend +# Copyright (C) 2004, 2008, 2009 Olaf Meeuwissen +# +# See sane-epkowa(5), sane-usb(5) and sane-scsi(5) for details. + +# Detect all devices supported by the backend. +# If you don't have a SCSI device, you can comment out the "scsi" +# keyword. Similarly for the other keywords. +# +usb +scsi + + +# For any USB scanner not known to the backend (yet), you may, at your +# own peril(!!), force the backend to recognise and use it via libusb. +# You can do so by the following configuration command: +# +# usb <USB vendor ID> <USB product ID> +# +# SEIKO EPSON's USB vendor ID is '0x04b8' (without quotes). In order +# to find the USB product ID, use lsusb(1). +# A sample configuration for the Epson Perfection 1650 (Epson GT-8200), +# which has a product ID of 0x0110, would look as follows: +# +#usb 0x04b8 0x0110 + + +# For SCSI devices not detected, you can add an entry like: +# +# scsi EPSON GT-20000 +# +# where the GT-20000 bit corresponds to the SCSI model information as +# shown in the output of dmesg(1) or in the /var/log/kern.log file. + + +# Network attached devices may be made to work by first installing the +# (non-free) iscan-network-nt package and then adding configuration lines +# as per information below. +# +# For each network attached device, you must add an entry as follows: +# +# net <IP-address|hostname> [port-number] +# +# Ask your network administrator for the device's IP address or check +# for yourself on the panel (if it has one). The port-number is very +# optional and defaults to 1865. +# Note that network attached devices are not queried unless configured +# in this file. +# +# Examples: +# +#net 192.16.136.2 1865 +#net 10.0.0.1 +#net scanner.mydomain.com + + +# Some backend behaviour can be customized by using the option keyword +# followed by an option name, as shown below. +# +# option <option-name> +# +# Currently available options: +# +# Makes the automatic document feeder the default document source +#option prefer-adf diff --git a/backend/epkowa.h b/backend/epkowa.h new file mode 100644 index 0000000..72ab909 --- /dev/null +++ b/backend/epkowa.h @@ -0,0 +1,291 @@ +/* epkowa.h - SANE backend for EPSON flatbed scanners + (Image Scan! version) + + Based on the SANE Epson backend (originally from sane-1.0.3) + - updated to sane-backends-1.0.6 + - renamed from epson to epkowa to avoid confusion + - updated to sane-backends-1.0.10 + - updated to sane-backends-1.0.15 + + Based on Kazuhiro Sasayama previous + Work on epson.[ch] file from the SANE package. + + Original code taken from sane-0.71 + Copyright (C) 1997 Hypercore Software Design, Ltd. + + modifications + Copyright (C) 1998-1999 Christian Bucher <bucher@vernetzt.at> + Copyright (C) 1998-1999 Kling & Hautzinger GmbH + Copyright (C) 1999 Norihiko Sawa <sawa@yb3.so-net.ne.jp> + Copyright (C) 2000 Karl Heinz Kremer <khk@khk.net> + Copyright (C) 2001-2009 SEIKO EPSON CORPORATION + + This file is part of the EPKOWA SANE backend. + + 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 epkowa_h +#define epkowa_h + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "device.h" + +typedef struct +{ + SANE_Byte *lut; + int depth; + +} LUT; + + +/* convenience union to access option values given to the backend + */ +typedef union +{ + SANE_Bool b; + SANE_Word w; + SANE_Word *wa; /* word array */ + SANE_String s; +} +Option_Value; + +/* string constants for GUI elements that are not defined SANE-wide */ + +#define SANE_NAME_GAMMA_CORRECTION "gamma-correction" +#define SANE_TITLE_GAMMA_CORRECTION SANE_I18N("Gamma Correction") +#define SANE_DESC_GAMMA_CORRECTION SANE_I18N("Selects the gamma correction value from a list of pre-defined devices or the user defined table, which can be downloaded to the scanner") + +#define SANE_EPSON_FOCUS_NAME "focus-position" +#define SANE_EPSON_FOCUS_TITLE SANE_I18N("Focus Position") +#define SANE_EPSON_FOCUS_DESC SANE_I18N("Sets the focus position to either the glass or 2.5mm above the glass") +#define SANE_EPSON_WAIT_FOR_BUTTON_NAME "wait-for-button" +#define SANE_EPSON_WAIT_FOR_BUTTON_TITLE SANE_I18N("Wait for Button") +#define SANE_EPSON_WAIT_FOR_BUTTON_DESC SANE_I18N("After sending the scan command, wait until the button on the scanner is pressed to actually start the scan process."); +#define SANE_EPSON_MONITOR_BUTTON_NAME "monitor-button" +#define SANE_EPSON_MONITOR_BUTTON_TITLE SANE_I18N("Monitor Button") +#define SANE_EPSON_MONITOR_BUTTON_DESC SANE_I18N("Indicates whether a button on the scanner has been pressed."); +#define SANE_EPSON_DETECT_DOC_SIZE_NAME "detect-doc-size" +#define SANE_EPSON_DETECT_DOC_SIZE_TITLE SANE_I18N("Auto-detect document size") +#define SANE_EPSON_DETECT_DOC_SIZE_DESC SANE_I18N("Activates document size auto-detection. The scan area will be set to match the detected document size.") +#define SANE_EPSON_SCAN_AREA_IS_VALID_NAME "scan-area-is-valid" +#define SANE_EPSON_SCAN_AREA_IS_VALID_TITLE SANE_I18N("Scan Area Is Valid") +#define SANE_EPSON_SCAN_AREA_IS_VALID_DESC SANE_I18N("Indicates whether the current scan area settings are valid.") + +#define SANE_EPSON_ADF_AUTO_SCAN_NAME "adf-auto-scan" +#define SANE_EPSON_ADF_AUTO_SCAN_TITLE SANE_I18N("ADF Auto Scan") +#define SANE_EPSON_ADF_AUTO_SCAN_DESC SANE_I18N("Skips per sheet device setup for faster throughput.") + +#define SANE_EPSON_ADF_DFD_SENSITIVITY_NAME "double-feed-detection-sensitivity" +#define SANE_EPSON_ADF_DFD_SENSITIVITY_TITLE SANE_I18N("Double Feed Detection Sensitivity") +#define SANE_EPSON_ADF_DFD_SENSITIVITY_DESC SANE_I18N("Sets the sensitivity with which multi-sheet page feeds are detected and reported as errors.") + +#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_NAME "adf-duplex-direction-matches" +#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_TITLE SANE_I18N("ADF Duplex Direction Matches") +#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_DESC SANE_I18N("Indicates whether the device's ADF duplex mode, if available, scans in the same direction for the front and back.") + +#define SANE_EPSON_POLLING_TIME_NAME "polling-time" +#define SANE_EPSON_POLLING_TIME_TITLE SANE_I18N("Polling Time") +#define SANE_EPSON_POLLING_TIME_DESC SANE_I18N("Time between queries when waiting for device state changes.") + +#define SANE_EPSON_NEEDS_POLLING_NAME "needs-polling" +#define SANE_EPSON_NEEDS_POLLING_TITLE SANE_I18N("Needs Polling") +#define SANE_EPSON_NEEDS_POLLING_DESC SANE_I18N("Indicates whether the scanner needs to poll.") + +#define SANE_EPSON_CALIBRATE_NAME "calibrate" +#define SANE_EPSON_CALIBRATE_TITLE SANE_I18N("Calibrate") +#define SANE_EPSON_CALIBRATE_DESC SANE_I18N("Performs color matching to make sure that the document's color tones are scanned correctly.") + +#define SANE_EPSON_CLEAN_NAME "clean" +#define SANE_EPSON_CLEAN_TITLE SANE_I18N("Clean") +#define SANE_EPSON_CLEAN_DESC SANE_I18N("Cleans the scanners reading section.") + +#define LINES_SHUFFLE_MAX (17) /* 2 x 8 lines plus 1 */ + +#define SANE_EPSON_MAX_RETRIES (120) /* how often do we retry during warmup ? */ + +#define MAX_READ_ATTEMPTS 10 /* maximum number of attempts at + reading scan data */ +#define DEFAULT_POLLING_TIME (1000 * 1000) /* usec */ + +enum { + OPT_NUM_OPTS = 0, + /* */ + OPT_MODE_GROUP, + OPT_MODE, + OPT_BIT_DEPTH, + OPT_HALFTONE, + OPT_DROPOUT, + OPT_BRIGHTNESS_METHOD, + OPT_BRIGHTNESS, + OPT_CONTRAST, + OPT_SHARPNESS, + OPT_GAMMA_CORRECTION, + OPT_COLOR_CORRECTION, + OPT_RESOLUTION, + OPT_X_RESOLUTION, + OPT_Y_RESOLUTION, + OPT_THRESHOLD, + /* */ + OPT_ADVANCED_GROUP, + OPT_MIRROR, + OPT_SPEED, + OPT_AAS, + OPT_LIMIT_RESOLUTION, + OPT_ZOOM, + OPT_GAMMA_VECTOR_R, + OPT_GAMMA_VECTOR_G, + OPT_GAMMA_VECTOR_B, + OPT_WAIT_FOR_BUTTON, + OPT_MONITOR_BUTTON, + OPT_POLLING_TIME, + OPT_NEEDS_POLLING, + /* */ + OPT_CCT_GROUP, + OPT_CCT_1, + OPT_CCT_2, + OPT_CCT_3, + OPT_CCT_4, + OPT_CCT_5, + OPT_CCT_6, + OPT_CCT_7, + OPT_CCT_8, + OPT_CCT_9, + /* */ + OPT_PREVIEW_GROUP, + OPT_PREVIEW, + OPT_PREVIEW_SPEED, + /* */ + OPT_GEOMETRY_GROUP, + OPT_SCAN_AREA, + OPT_TL_X, + OPT_TL_Y, + OPT_BR_X, + OPT_BR_Y, + OPT_QUICK_FORMAT, + /* */ + OPT_EQU_GROUP, + OPT_SOURCE, + OPT_AUTO_EJECT, + OPT_FILM_TYPE, + OPT_FOCUS, + OPT_BAY, + OPT_EJECT, + OPT_ADF_MODE, + OPT_DETECT_DOC_SIZE, + OPT_SCAN_AREA_IS_VALID, + OPT_ADF_AUTO_SCAN, + OPT_ADF_DFD_SENSITIVITY, + OPT_EXT_SANE_STATUS, + OPT_ADF_DUPLEX_DIRECTION_MATCHES, + OPT_DESKEW, + OPT_AUTOCROP, + OPT_CALIBRATE, + OPT_CLEAN, + /* */ + NUM_OPTIONS +}; + +/*! Container for image data. + */ +typedef struct +{ + size_t cap; /*!< buffer capacity */ + SANE_Byte *buf; /*!< start of image data */ + SANE_Byte *end; /*!< end of image data */ + SANE_Byte *ptr; /*!< current position */ + SANE_Parameters ctx; /*!< buffer context */ + + bool cancel_requested; + bool all_data_fetched; + bool transfer_started; + bool transfer_stopped; + +} buffer; + +/*! Software representation of the \e logical device. + */ +struct Epson_Scanner +{ + struct Epson_Scanner *next; + + const void *dip; + device *hw; + SANE_Option_Descriptor opt[NUM_OPTIONS]; + Option_Value val[NUM_OPTIONS]; + Option_Value val_bak[NUM_OPTIONS]; + + /* image data acquisition buffers and parameters */ + buffer *src; /*!< buffer to provide data to frontend */ + buffer raw; /*!< device image data blocks */ + buffer img; /*!< complete in-memory image */ + + SANE_Byte *line_buffer[LINES_SHUFFLE_MAX]; + size_t cap_line_buffer; + + SANE_Int color_shuffle_line; /* current line number for color shuffling */ + SANE_Int line_distance; /* current line distance */ + SANE_Int current_output_line; /* line counter when color shuffling */ + + SANE_Bool invert_image; + SANE_Word gamma_table[3][256]; + double cct[9]; + LUT *lut; + double brightness; + double contrast; + + /*! Number of frames acquired so far. + * This includes partial frames if scan_finish() is called before a + * frame is completed. + */ + unsigned int frame_count; + + /*! Number of raw scan lines data gotten from scanner + * This corresponds to the \c ESC_d parameter. + */ + unsigned int line_count; +}; + +typedef struct Epson_Scanner Epson_Scanner; + +SANE_Status estimate_parameters (Epson_Scanner *, SANE_Parameters *); +SANE_Status fetch_image_data (Epson_Scanner *, SANE_Byte *, SANE_Int, + SANE_Int *); + +#endif /* not epkowa_h */ diff --git a/backend/epkowa_ip.c b/backend/epkowa_ip.c new file mode 100644 index 0000000..763a34a --- /dev/null +++ b/backend/epkowa_ip.c @@ -0,0 +1,596 @@ +/* epkowa_ip.c -- for scanners that don't speak EPSON ESC/I natively + Copyright (C) 2005--2008 Olaf Meeuwissen + Copyright (C) 2009 SEIKO EPSON CORPORATION + + This file is part of the EPKOWA SANE backend. + It defines a wrapper around the backend's "Interpreter" interface. + + The EPKOWA SANE backend 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 FITNESS + FOR A PARTICULAR PURPOSE or MERCHANTABILITY. + See the GNU General Public License for more details. + + You should have received a verbatim copy of the GNU General Public + License along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02111-1307 USA + + Linking the EPKOWA SANE backend statically or dynamically with + other modules is making a combined work based on the EPKOWA SANE + backend. Thus, the terms and conditions of the GNU General Public + License cover the whole combination. + + As a special exception, the copyright holders of the EPKOWA SANE + backend give you permission to link the EPKOWA SANE backend with + independent modules that communicate with the EPKOWA SANE backend + solely through the "Interpreter" interface, regardless of the + license terms of these independent modules, and to copy and + distribute the resulting combined work under terms of your choice, + provided that every copy of the combined work is accompanied by a + complete copy of the source code of the EPKOWA SANE backend (the + version of the EPKOWA SANE backend used to produce the combined + work), being distributed under the terms of the GNU General Public + License plus this exception. An independent module is a module + which is not derived from or based on the EPKOWA SANE backend. + + Note that people who make modified versions of the EPKOWA SANE + backend are not obligated to grant this special exception for their + modified versions; it is their choice whether to do so. The GNU + General Public License gives permission to release a modified + version without this exception; this exception also makes it + possible to release a modified version which carries forward this + exception. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include "epkowa_ip.h" +#include "cfg-obj.h" + +#include "sane/sanei_usb.h" + +#define TICK_INTERVAL 60 /* seconds */ +#define TICK_TIME_OUT 15 /* intervals */ + +// FIXME: this ought to be part of the interpreter_type struct so that +// every instance can have its own. +device_type *g_epson = NULL; + + +/* default member function implementors */ + +static SANE_Status _dtor (device_type *); +static int _open (device_type *); +static int _close (device_type *); +static ssize_t _recv (device_type *, void *, size_t, SANE_Status *); +static ssize_t _send (device_type *, const void *, size_t, SANE_Status *); + +static SANE_Status _free (device_type *); +static SANE_Status _ftor0 (device_type *, SANE_Parameters *, + SANE_Byte *, SANE_Byte *); +static SANE_Status _ftor1 (device_type *, SANE_Parameters *, + int, int, int, int); + +/* private functions */ + +static void *_load (const char *, device_type *); + + +/* callback functions */ + +static ssize_t usb_read (void *buffer, size_t size); +static ssize_t usb_write (void *buffer, size_t size); +static ssize_t usb_ctrl (size_t request_type, size_t request, + size_t value, size_t index, + size_t size, void *buffer); + + +/*! Creates an interpreter object for a \a device if both necessary + and available. The object is accessible through the \a device's + \c interpreter field once successfully created. + */ +SANE_Status +create_interpreter (device_type *device, unsigned int usb_product_id) +{ + void *cfg = cfg_init (NULL, NULL); + list *seen = cfg_seen (cfg, CFG_KEY_INTERPRETER); + const cfg_interpreter_info *next = NULL; + + if (!device) /* sanity check */ + { + return SANE_STATUS_INVAL; + } + + if (device->interpreter) /* we've been here before */ + { + if (device != device->interpreter->_device) + { /* internal inconsistency! */ + return SANE_STATUS_INVAL; + } + return SANE_STATUS_GOOD; + } + + if (seen) + { + list_entry *cur = seen->cur; + list_reset (seen); + while ((next = list_next (seen)) + && usb_product_id != next->product) + { + /* condition does all the processing */ + } + seen->cur = cur; + } + if (!next) return SANE_STATUS_GOOD; + + /* If we are still here, we need to set up an interpreter and load + the corresponding module. */ + + device->interpreter = t_malloc (1, struct interpreter_type); + + if (!device->interpreter) + { + return SANE_STATUS_NO_MEM; + } + + device->interpreter->_device = device; + device->interpreter->_module = _load (next->library, device); + + if (!device->interpreter->_module) + { + delete (device->interpreter); + return SANE_STATUS_INVAL; + } + + device->interpreter->_tick_count = -1; + device->interpreter->_table = NULL; + device->interpreter->_buffer = NULL; + + device->interpreter->dtor = _dtor; + device->interpreter->open = _open; + device->interpreter->close = _close; + device->interpreter->recv = _recv; + device->interpreter->send = _send; + + device->interpreter->free = _free; + device->interpreter->ftor0 = _ftor0; + device->interpreter->ftor1 = _ftor1; + + return SANE_STATUS_GOOD; +} + + +/*! Destroys a \a device's interpreter object. + */ +SANE_Status +_dtor (device_type *device) +{ + if (!device || !device->interpreter) + { + return SANE_STATUS_INVAL; + } + + device->interpreter->close (device); + device->interpreter->free (device); + +#ifdef USE_ALARM + alarm (0); +#endif + + lt_dlclose (device->interpreter->_module); + device->interpreter->_module = 0; + + delete (device->interpreter); + + return SANE_STATUS_GOOD; +} + +/*! Frees several temporary buffers. + */ +SANE_Status +_free (device_type *device) +{ + if (!device || !device->interpreter) + { + return SANE_STATUS_INVAL; + } + + delete (device->interpreter->_table); + delete (device->interpreter->_buffer); + + return SANE_STATUS_GOOD; +} + +/*! Performs whatever actions are needed when opening an interpreter + "controlled" device. One of these actions is disarming an alarm + used to control whether the device can go into power saving mode + or not. */ +int +_open (device_type *device) +{ + struct interpreter_type *di; + + if (!device || !device->interpreter) + { + return -1; + } + di = device->interpreter; + +#ifdef USE_ALARM + alarm (0); + log_info ("alarm (%d)", 0); +#endif + di->_tick_count = -1; + + g_epson = device; + + if (device->fd < 0 + || !(di->_init + ? di->_init (device->fd, usb_read, usb_write) + : di->_init_with_ctrl (device->fd, usb_read, + usb_write, usb_ctrl))) + { + err_fatal ("failed to initialize interpreter"); + g_epson = NULL; + return -1; + } + + return device->fd; +} + +int +_close (device_type *device) +{ + if (!device || !device->interpreter) + { + return -1; + } + + device->interpreter->_fini (); + + device->interpreter->_tick_count = 0; +#ifdef USE_ALARM + alarm (TICK_INTERVAL); + log_info ("alarm (%d)", TICK_INTERVAL); +#endif + + g_epson = NULL; + + return device->fd; +} + +ssize_t +_send (device_type *device, const void *buffer, size_t size, + SANE_Status *status) +{ + if (!status) + { + return -1; + } + + if (!device || !device->interpreter) + { + *status = SANE_STATUS_INVAL; + return -1; + } + + /* ASSUMPTION: Interpreter does NOT change buffer's content. + */ + if (device->interpreter->_write ((void *)buffer, size)) + { + *status = SANE_STATUS_GOOD; + } + else + { + *status = SANE_STATUS_INVAL; + } + + return size; +} + +ssize_t +_recv (device_type *device, void *buffer, size_t size, + SANE_Status *status) +{ + if (!status) + { + return -1; + } + + if (!device || !device->interpreter) + { + *status = SANE_STATUS_INVAL; + return -1; + } + + if (device->interpreter->_read (buffer, size)) + { + *status = SANE_STATUS_GOOD; + } + else + { + *status = SANE_STATUS_INVAL; + } + + return size; +} + +/*! Callback for use by the interpreter. */ +ssize_t +usb_read (void *buffer, size_t size) +{ + size_t n = size; + + if (!g_epson || g_epson->fd < 0) + { + return 0; + } + + if (SANE_STATUS_GOOD == sanei_usb_read_bulk (g_epson->fd, buffer, &n)) + { + if (size != n) + err_minor ("Did not read number of bytes requested"); + return n; + } + else + { + return 0; + } +} + +/*! Callback for use by the interpreter. */ +ssize_t +usb_write (void *buffer, size_t size) +{ + size_t n = size; + + if (!g_epson || g_epson->fd < 0) + { + return 0; + } + + if (SANE_STATUS_GOOD == sanei_usb_write_bulk (g_epson->fd, buffer, &n)) + { + if (size != n) + err_minor ("Did not read number of bytes requested"); + return n; + } + else + return 0; +} + +/*! Callback for use by the interpreter. */ +ssize_t +usb_ctrl (size_t request_type, size_t request, size_t value, + size_t index, size_t size, void *buffer) +{ + size_t n = size; + + if (!g_epson || g_epson->fd < 0) + { + return 0; + } + + if (SANE_STATUS_GOOD == sanei_usb_control_msg (g_epson->fd, request_type, + request, value, index, + &n, buffer)) + { + if (size != n) + err_minor ("Did not read number of bytes requested"); + return n; + } + else + return 0; +} + +#ifdef USE_ALARM +void +timer_event (int sig) +{ + sig = sig; + + log_call (); + + if (!g_epson || !g_epson->interpreter) + return; + + if ((g_epson && 0 < g_epson->fd) || g_epson->interpreter->_tick_count == -1) + { + return; + } + + g_epson->interpreter->_tick_count++; + + if (TICK_TIME_OUT - 1 <= g_epson->interpreter->_tick_count) + { + g_epson->interpreter->_power (); + g_epson->interpreter->_tick_count = -1; + return; + } + + alarm (TICK_INTERVAL); + log_info ("alarm (%d)", TICK_INTERVAL); +} +#endif + +/*! Loads the interpreter module. */ +/*! + */ +void * +_load (const char *name, device_type *device) +{ + void *handle = NULL; + struct interpreter_type *di = device->interpreter; + + /* FIXME: should set the search path to a list of directories based + on the regular load path with the PACKAGE attached to each of the + individual directories. Should _not_ look in the regular load + path's directories. */ + { + const char *path = lt_dlgetsearchpath (); + if (!(path && strstr (path, PKGLIBDIR))) + { + lt_dladdsearchdir (PKGLIBDIR); + } + handle = lt_dlopenext (name); + } + if (!handle) + { + err_fatal ("%s", lt_dlerror()); + return NULL; + } + + di->_init_with_ctrl = lt_dlsym (handle, "int_init_with_ctrl"); + if (di->_init_with_ctrl) + { + di->_init = NULL; + } + else + { + di->_init = lt_dlsym (handle, "int_init"); + } + di->_fini = lt_dlsym (handle, "int_fini"); + di->_read = lt_dlsym (handle, "int_read"); + di->_write = lt_dlsym (handle, "int_write"); + di->_power = lt_dlsym (handle, "int_power_saving_mode"); + di->_s_0 = lt_dlsym (handle, "function_s_0"); + di->_s_1 = lt_dlsym (handle, "function_s_1"); + + if (! + ( (di->_init || di->_init_with_ctrl) + && di->_fini + && di->_read + && di->_write + && di->_s_0 + && di->_s_1) + ) + { + err_fatal ("failed to find all required interpreter API"); + di->_init_with_ctrl = NULL; + di->_init = NULL; + di->_fini = NULL; + di->_read = NULL; + di->_write = NULL; + di->_power = NULL; + di->_s_0 = NULL; + di->_s_1 = NULL; + lt_dlclose (handle); + return NULL; + } + + /* FIXME: if we are still here, we should set up the alarm stuff + here because _load is really a sort of ctor. Note that open + should disarm the alarm and close should re-arm it. Our dtor + should take care of disarming the alarm permanently. */ + /* FIXME: add error handling */ + { /* set up alarm for power saving */ +#ifdef USE_ALARM + struct sigaction act; + + /* FIXME: set act.sa_sigaction instead because that can hold a + void (*) (int, siginfo_t *, void *) so we can pass arguments + such as device->interpreter->_device(!) to it, perhaps. */ + act.sa_handler = timer_event; + sigemptyset (&act.sa_mask); + act.sa_flags = 0; + + sigaction (SIGALRM, &act, 0); + + alarm (TICK_INTERVAL); + log_info ("alarm (%d)", TICK_INTERVAL); +#endif + di->_tick_count = 0; + } + return handle; +} + +/* + This functor is called by sane_read only. + */ +SANE_Status +_ftor0 (device_type *device, SANE_Parameters *params, + SANE_Byte *ptr, SANE_Byte *end) +{ + if (!device || !device->interpreter || !params) + { + return SANE_STATUS_INVAL; + } + + if (params->depth != 1 + && device->interpreter->_table && device->interpreter->_buffer) + { + int i, row; + + row = (end - ptr) / params->bytes_per_line; + + for (i = 0; i < row; ++i) + { + memcpy (device->interpreter->_buffer, ptr + i * params->bytes_per_line, + params->bytes_per_line); + device->interpreter->_s_1 (device->interpreter->_buffer, + ptr + i * params->bytes_per_line, + params->pixels_per_line, + params->format == SANE_FRAME_RGB, + device->interpreter->_table); + } + } + return SANE_STATUS_GOOD; +} + +/* + This functor is called by sane_start only. + */ +SANE_Status +_ftor1 (device_type *device, SANE_Parameters *params, + int depth, int left, int x_dpi, int optical_res) +{ + if (!device || !device->interpreter || !params) + { + return SANE_STATUS_INVAL; + } + + device->interpreter->free (device); + + if (depth != 1) + { + device->interpreter->_table = + t_malloc (params->pixels_per_line, double); + + if (!device->interpreter->_table) + { + return SANE_STATUS_NO_MEM; + } + + if (device->interpreter->_s_0 (left, params->pixels_per_line, + x_dpi, optical_res, + device->interpreter->_table)) + { + device->interpreter->_buffer + = t_malloc (params->bytes_per_line, SANE_Byte); + + if (!device->interpreter->_buffer) + { + delete (device->interpreter->_table); + return SANE_STATUS_NO_MEM; + } + } + else + { + delete (device->interpreter->_table); + } + } + return SANE_STATUS_GOOD; +} diff --git a/backend/epkowa_ip.h b/backend/epkowa_ip.h new file mode 100644 index 0000000..cc0fe84 --- /dev/null +++ b/backend/epkowa_ip.h @@ -0,0 +1,124 @@ +/* epkowa_ip.h -- for scanners that don't speak EPSON ESC/I natively + Copyright (C) 2005 Olaf Meeuwissen + Copyright (C) 2009 SEIKO EPSON CORPORATION + + This file is part of the EPKOWA SANE backend. + It declares a wrapper around the backend's "Interpreter" interface. + + The EPKOWA SANE backend 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 FITNESS + FOR A PARTICULAR PURPOSE or MERCHANTABILITY. + See the GNU General Public License for more details. + + You should have received a verbatim copy of the GNU General Public + License along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02111-1307 USA + + Linking the EPKOWA SANE backend statically or dynamically with + other modules is making a combined work based on the EPKOWA SANE + backend. Thus, the terms and conditions of the GNU General Public + License cover the whole combination. + + As a special exception, the copyright holders of the EPKOWA SANE + backend give you permission to link the EPKOWA SANE backend with + independent modules that communicate with the EPKOWA SANE backend + solely through the "Interpreter" interface, regardless of the + license terms of these independent modules, and to copy and + distribute the resulting combined work under terms of your choice, + provided that every copy of the combined work is accompanied by a + complete copy of the source code of the EPKOWA SANE backend (the + version of the EPKOWA SANE backend used to produce the combined + work), being distributed under the terms of the GNU General Public + License plus this exception. An independent module is a module + which is not derived from or based on the EPKOWA SANE backend. + + Note that people who make modified versions of the EPKOWA SANE + backend are not obligated to grant this special exception for their + modified versions; it is their choice whether to do so. The GNU + General Public License gives permission to release a modified + version without this exception; this exception also makes it + possible to release a modified version which carries forward this + exception. + */ + +#ifndef epkowa_ip_h_included +#define epkowa_ip_h_included + +#include <ltdl.h> + +#include "epkowa.h" +#include "epkowa_ip_api.h" + +/* Insulate the interpreter_type declaration against changes. + */ +typedef channel device_type; + + +/*! An object-like type for ESC/I interpreters. + */ +struct interpreter_type +{ + /* public members */ + + SANE_Status (*dtor) (device_type *device); + + int (*open) (device_type *device); + int (*close) (device_type *device); + + ssize_t (*recv) (device_type *device, void *buffer, size_t size, + SANE_Status *status); + ssize_t (*send) (device_type *device, const void *buffer, size_t size, + SANE_Status *status); + + /* FIXME: very questionable public API for an _interpreter_ as this + seems to do some kind of image processing -> suggest to refactor + this out into a separate object */ + + SANE_Status (*free) (device_type *device); + + SANE_Status (*ftor0) (device_type *device, SANE_Parameters *params, + SANE_Byte *ptr, SANE_Byte *end); + SANE_Status (*ftor1) (device_type *device, SANE_Parameters *params, + int depth, int left, int x_dpi, int optical_res); + + + /* private members */ + + device_type *_device; + void *_module; + + int _tick_count; + + double *_table; + SANE_Byte *_buffer; + + /* hooks for all API declared in epkowa_ip_api.h */ + + bool (*_init) (int fd, io_callback *read, io_callback *write); + bool (*_init_with_ctrl) (int fd, io_callback *read, io_callback *write, + ctrl_callback *ctrl); + void (*_fini) (void); + int (*_read) (void *buf, size_t size); + int (*_write) (void *buf, size_t size); + void (*_power) (void); + int (*_s_0) (unsigned int offset, unsigned int width, + unsigned int resolution, unsigned int opt_resolution, + double *table); + void (*_s_1) (uint8_t *in_buf, uint8_t *out_buf, + unsigned int width, bool color, double *table); +}; + +/*! An abstract, factory-like ctor for interpreters. + */ +SANE_Status create_interpreter (device_type *device, + unsigned int usb_product_id); + +#endif /* !epkowa_ip_h_included */ diff --git a/backend/epkowa_ip_api.h b/backend/epkowa_ip_api.h new file mode 100644 index 0000000..0e3f840 --- /dev/null +++ b/backend/epkowa_ip_api.h @@ -0,0 +1,92 @@ +/* epkowa_ip_api.h -- for scanners that don't speak EPSON ESC/I natively + Copyright (C) 2005 SEIKO EPSON CORPORATION + + This file is part of the EPKOWA SANE backend. + It declares the backend's "Interpreter" interface. + + The EPKOWA SANE backend 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 FITNESS + FOR A PARTICULAR PURPOSE or MERCHANTABILITY. + See the GNU General Public License for more details. + + You should have received a verbatim copy of the GNU General Public + License along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02111-1307 USA + + Linking the EPKOWA SANE backend statically or dynamically with + other modules is making a combined work based on the EPKOWA SANE + backend. Thus, the terms and conditions of the GNU General Public + License cover the whole combination. + + As a special exception, the copyright holders of the EPKOWA SANE + backend give you permission to link the EPKOWA SANE backend with + independent modules that communicate with the EPKOWA SANE backend + solely through the "Interpreter" interface, regardless of the + license terms of these independent modules, and to copy and + distribute the resulting combined work under terms of your choice, + provided that every copy of the combined work is accompanied by a + complete copy of the source code of the EPKOWA SANE backend (the + version of the EPKOWA SANE backend used to produce the combined + work), being distributed under the terms of the GNU General Public + License plus this exception. An independent module is a module + which is not derived from or based on the EPKOWA SANE backend. + + Note that people who make modified versions of the EPKOWA SANE + backend are not obligated to grant this special exception for their + modified versions; it is their choice whether to do so. The GNU + General Public License gives permission to release a modified + version without this exception; this exception also makes it + possible to release a modified version which carries forward this + exception. + */ + +#ifndef epkowa_ip_api_h_included +#define epkowa_ip_api_h_included + +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +#include "defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef ssize_t io_callback (void *buffer, size_t length); +typedef ssize_t ctrl_callback (size_t request_type, size_t request, + size_t value, size_t index, + size_t size, void *buffer); + +bool int_init (int fd, io_callback *read, io_callback *write); +bool int_init_with_ctrl (int fd, io_callback *read, io_callback *write, + ctrl_callback *ctrl); +void int_fini (void); + +int int_read (void *buffer, size_t length); +int int_write (void *buffer, size_t length); + +void int_power_saving_mode (void); + +int function_s_0 (unsigned int offset, + unsigned int width, + unsigned int resolution, + unsigned int opt_resolution, + double * table); + +void function_s_1 (uint8_t *in_buf, uint8_t *out_buf, + unsigned int width, bool color, double *table); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !epkowa_ip_api_h_included */ diff --git a/backend/epkowa_scsi.c b/backend/epkowa_scsi.c new file mode 100644 index 0000000..9132507 --- /dev/null +++ b/backend/epkowa_scsi.c @@ -0,0 +1,103 @@ +#include "epkowa_scsi.h" + +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef NEED_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <string.h> /* for memset and memcpy */ + +/* + * sense handler for the sanei_scsi_XXX comands + */ +SANE_Status +sanei_epson_scsi_sense_handler (int scsi_fd, u_char * result, void *arg) +{ + /* to get rid of warnings */ + scsi_fd = scsi_fd; + arg = arg; + + if (result[0] && result[0] != 0x70) + { + log_data ("SCSI sense code = 0x%02x", result[0]); + return SANE_STATUS_IO_ERROR; + } + else + { + return SANE_STATUS_GOOD; + } +} + +/* + * + * + */ +SANE_Status +sanei_epson_scsi_inquiry (int fd, int page_code, void *buf, size_t * buf_size) +{ + u_char cmd[6]; + int status; + + memset (cmd, 0, 6); + cmd[0] = INQUIRY_COMMAND; + cmd[2] = page_code; + cmd[4] = *buf_size > 255 ? 255 : *buf_size; + status = sanei_scsi_cmd (fd, cmd, sizeof cmd, buf, buf_size); + + return status; +} + +/* + * + * + */ +int +sanei_epson_scsi_read (int fd, void *buf, size_t buf_size, + SANE_Status * status) +{ + u_char cmd[6]; + + memset (cmd, 0, 6); + cmd[0] = READ_6_COMMAND; + cmd[2] = buf_size >> 16; + cmd[3] = buf_size >> 8; + cmd[4] = buf_size; + + if (SANE_STATUS_GOOD == + (*status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, &buf_size))) + return buf_size; + + return 0; +} + +/* + * + * + */ +int +sanei_epson_scsi_write (int fd, const void *buf, size_t buf_size, + SANE_Status * status) +{ + u_char *cmd; + + cmd = t_alloca (8 + buf_size, u_char); + memset (cmd, 0, 8); + cmd[0] = WRITE_6_COMMAND; + cmd[2] = buf_size >> 16; + cmd[3] = buf_size >> 8; + cmd[4] = buf_size; + memcpy (cmd + 8, buf, buf_size); + + if (SANE_STATUS_GOOD == + (*status = sanei_scsi_cmd2 (fd, cmd, 6, cmd + 8, buf_size, NULL, NULL))) + return buf_size; + + return 0; +} diff --git a/backend/epkowa_scsi.h b/backend/epkowa_scsi.h new file mode 100644 index 0000000..8328c76 --- /dev/null +++ b/backend/epkowa_scsi.h @@ -0,0 +1,28 @@ +#ifndef _EPSON_SCSI_H_ +#define _EPSON_SCSI_H_ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "epkowa.h" +#include "sane/sanei_scsi.h" + +#define TEST_UNIT_READY_COMMAND (0x00) +#define READ_6_COMMAND (0x08) +#define WRITE_6_COMMAND (0x0a) +#define INQUIRY_COMMAND (0x12) +#define TYPE_PROCESSOR (0x03) + +#define INQUIRY_BUF_SIZE (36) + +SANE_Status sanei_epson_scsi_sense_handler (int scsi_fd, u_char * result, + void *arg); +SANE_Status sanei_epson_scsi_inquiry (int fd, int page_code, void *buf, + size_t * buf_size); +int sanei_epson_scsi_read (int fd, void *buf, size_t buf_size, + SANE_Status * status); +int sanei_epson_scsi_write (int fd, const void *buf, size_t buf_size, + SANE_Status * status); + +#endif diff --git a/backend/extension.h b/backend/extension.h new file mode 100644 index 0000000..b8daad2 --- /dev/null +++ b/backend/extension.h @@ -0,0 +1,177 @@ +/* extension.h -- types to handle the various scanner document sources + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_source_h +#define included_source_h + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +/*! Base "class" for hardware extensions. + + This "class" provides the common part of the other extension types. + + \sa struct _fbf_extension, fbf_extension + \sa struct _adf_extension, adf_extension + \sa struct _tpu_extension, tpu_extension + */ +struct _extension +{ + SANE_Byte status; + + SANE_Range x_range; /* in mm */ + SANE_Range y_range; + + SANE_Int max_x; /* in pixels */ + SANE_Int max_y; + + double doc_x; /* in mm */ + double doc_y; + SANE_Bool has_size_check; +}; +typedef struct _extension extension; + + +/*! Flatbed hardware "extension". + + \sa struct _extension, extension + */ +struct _fbf_extension +{ + SANE_Byte status; + + SANE_Range x_range; /* in mm */ + SANE_Range y_range; + + SANE_Int max_x; /* in pixels */ + SANE_Int max_y; + + double doc_x; /* in mm */ + double doc_y; + SANE_Bool has_size_check; +}; +typedef struct _fbf_extension fbf_extension; + + +/*! Auto Document Feeder (ADF) hardware extension. + + \sa struct _extension, extension + */ +struct _adf_extension +{ + SANE_Byte status; + + SANE_Range x_range; /* in mm */ + SANE_Range y_range; + + SANE_Int max_x; /* in pixels */ + SANE_Int max_y; + + double doc_x; /* in mm */ + double doc_y; + SANE_Bool has_size_check; + + SANE_Byte ext_status; + + unsigned int sheet_count; + SANE_Bool using_duplex; + + SANE_Bool auto_eject; +}; +typedef struct _adf_extension adf_extension; + + +/*! TransParency Unit (TPU) hardware extension. + + \sa struct _extension, extension + */ +struct _tpu_extension +{ + SANE_Byte status; + + SANE_Range x_range; /* in mm */ + SANE_Range y_range; + + SANE_Int max_x; /* in pixels */ + SANE_Int max_y; + + double doc_x; /* in mm */ + double doc_y; + SANE_Bool has_size_check; + + SANE_Bool has_focus; + SANE_Bool use_focus; +}; +typedef struct _tpu_extension tpu_extension; + + +/*! Convenience macro to test whether \a ext is currently being used. + */ +#define using(hw,ext) \ + ((hw) && ((hw)->src == (const extension *) (hw)->ext)) + + +#endif /* !defined (included_source_h) */ diff --git a/backend/get-infofile.c b/backend/get-infofile.c new file mode 100644 index 0000000..79d8c98 --- /dev/null +++ b/backend/get-infofile.c @@ -0,0 +1,277 @@ +/* get-infofile.c -- + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#include <errno.h> +#include <ltdl.h> +#include <string.h> + +#include "get-infofile.h" +#include "profile.c" + +const struct ScannerData scanner_data[] = { + {"GT-10000", 0x05, 0x05, {NULL, NULL}}, + {"ES-6000", 0x05, 0x05, {NULL, NULL}}, + {"Perfection610", 0x06, 0x01, {"Perfection 610", NULL}}, + {"GT-6600", 0x06, 0x01, {NULL, NULL}}, + {"Perfection1200", 0x07, 0x01, {"Perfection 1200", NULL}}, + {"GT-7600", 0x07, 0x01, {NULL, NULL}}, + {"Expression1600", 0x0D, 0x02, {"Expression 1600", NULL}}, + {"ES-2000", 0x0D, 0x02, {NULL, NULL}}, + {"Expression1640XL", 0x0F, 0x04, {"Expression 1640XL", NULL}}, + {"ES-8500", 0x0F, 0x04, {NULL, NULL}}, + {"Perfection640", 0x15, 0x01, {"Perfection 640", NULL}}, + {"GT-6700", 0x15, 0x01, {NULL, NULL}}, + {"Perfection1640", 0x16, 0x01, {"Perfection 1640", NULL}}, + {"GT-8700", 0x16, 0x01, {NULL, NULL}}, + {"Perfection1240", 0x18, 0x01, {"Perfection 1240", NULL}}, + {"GT-7700", 0x18, 0x01, {NULL, NULL}}, + {"GT-30000", 0x1A, 0x05, {NULL, NULL}}, + {"ES-9000H", 0x1A, 0x05, {NULL, NULL}}, + {"Expression1680", 0x1B, 0x02, {"Expression 1680", NULL}}, + {"ES-2200", 0x1B, 0x02, {NULL, NULL}}, + {"GT-7200", 0x1D, 0x01, {"Perfection 1250", "GT-7200"}}, + {"GT-8200", 0x1F, 0x01, {"Perfection 1650", "GT-8200"}}, + {"GT-9700", 0x21, 0x01, {"Perfection 2450", "GT-9700"}}, + {"GT-7300", 0x23, 0x01, {"Perfection 1260", "GT-7300"}}, + {"GT-8300", 0x25, 0x01, {"Perfection 1660", "GT-8300"}}, + {"GT-9300", 0x27, 0x01, {"Perfection 2400", "GT-9300"}}, + {"GT-9800", 0x29, 0x01, {"Perfection 3200", "GT-9800"}}, + {"ES-7000H", 0x2B, 0x05, {"GT-15000", "ES-7000H"}}, + {"LP-A500", 0x51, 0x01, {NULL, NULL}}, + {"AL-CX11", 0x51, 0x01, {"AcuLaser CX11", NULL}}, + {"GT-9400", 0x32, 0x06, {"Perfection 3170", "GT-9400"}}, + {"CC-600PX", 0x2D, 0x01, {"Stylus CX5100/CX5200", "CC-600PX"}}, + {"PM-A850", 0x3A, 0x01, {"Stylus Photo RX600", "PM-A850"}}, + {"CX5400", 0x36, 0x01, {"Stylus CX5300/CX5400", NULL}}, + {"GT-X700", 0x34, 0x01, {"Perfection 4870", "GT-X700"}}, + {"RX500", 0x38, 0x01, {"Stylus Photo RX500/RX510", NULL}}, + {"PX-A650", 0x37, 0x01, {"Stylus CX6300/CX6400", NULL}}, + {"ES-10000G", 0x3F, 0x05, {NULL, NULL}}, + {"Expression10000", 0x3F, 0x05, {"Expression 10000XL", NULL}}, + {"CX4600", 0x46, 0x01, {"Stylus CX4500/CX4600", NULL}}, + {"CX6600", 0x49, 0x01, {"Stylus CX6500/CX6600", NULL}}, + {"CX3600", 0x46, 0x01, {"Stylus CX3500/CX3600/CX3650", "PX-A550"}}, + {"RX420", 0x48, 0x01, {"Stylus Photo RX420/RX425/RX430", NULL}}, + {"PM-A700", 0x48, 0x01, {NULL, NULL}}, + {"PM-A870", 0x4B, 0x01, {"Stylus Photo RX620/RX630", "PM-A870"}}, + {"GT-F500", 0x41, 0x06, {"Perfection 2480/2580", "GT-F500/F550"}}, + {"GT-F600", 0x43, 0x06, {"Perfection 4180", "GT-F600"}}, + {"PM-A900", 0x4D, 0x01, {"Stylus Photo RX700", "PM-A900"}}, + {"GT-X800", 0x4F, 0x01, {"Perfection 4990", "GT-X800"}}, + {"GT-X750", 0x54, 0x01, {"Perfection 4490", "GT-X750"}}, + {"LP-M5500", 0x56, 0x05, {NULL, NULL}}, + {"LP-M5600", 0x78, 0x05, {NULL, "LP-M5600"}}, + {"GT-F520", 0x52, 0x01, {"Perfection 3490/3590", "GT-F520/F570"}}, + {"CX3800", 0x57, 0x01, {"Stylus CX3700/CX3800/DX3800", NULL}}, + {"CX7800", 0x5B, 0x01, {"Stylus CX7700/CX7800", NULL}}, + {"PM-A750", 0x5D, 0x01, {"Stylus Photo RX520/RX530", "PM-A750"}}, + {"CX4800", 0x58, 0x01, {"Stylus CX4700/CX4800/DX4800", "PX-A650"}}, + {"CX4200", 0x59, 0x01, {"Stylus CX4100/CX4200/DX4200", NULL}}, + {"PM-A950", 0x61, 0x01, {NULL, "PM-A950"}}, + {"PM-A890", 0x5F, 0x01, {"Stylus Photo RX640/RX650", "PM-A890"}}, + {"GT-X900", 0x63, 0x01, {"Perfection V700/V750", "GT-X900"}}, + {"CX4000", 0x6B, 0x01, {"Stylus CX3900/DX4000", "PX-A620"}}, + {"CX3000v", 0x6A, 0x01, {"Stylus CX2800/CX2900/ME 200", NULL}}, + {"ES-H300", 0x65, 0x01, {"GT-2500", "ES-H300"}}, + {"CX6000", 0x6C, 0x01, {"Stylus CX5900/CX6000/DX6000", "PX-A720"}}, + {"PM-A820", 0x70, 0x01, {"Stylus Photo RX560/RX580/RX590", "PM-A820"}}, + {"PM-A920", 0x71, 0x01, {NULL, "PM-A920"}}, + {"PM-A970", 0x73, 0x01, {NULL, "PM-A970"}}, + {"PM-T990", 0x75, 0x01, {NULL, "PM-T990"}}, + {"CX5000", 0x77, 0x01, {"Stylus CX4900/CX5000/DX5000", NULL}}, + {"GT-S600", 0x66, 0x01, {"Perfection V10/V100", "GT-S600/GT-F650"}}, + {"GT-F700", 0x68, 0x01, {"Perfection V350", "GT-F700"}}, + {"AL-CX21", 0x79, 0x01, {"AcuLaser CX21", NULL}}, + {"GT-F670", 0x7A, 0x01, {"Perfection V200", "GT-F670"}}, + {"GT-X770", 0x7C, 0x06, {"Perfection V500", "GT-X770"}}, + {"CX4400", 0x7E, 0x01, {"Stylus CX4300/CX4400/CX5500/CX5600/DX4400", NULL}}, + {"CX7400", 0x7F, 0x01, {"Stylus CX7300/CX7400/DX7400", "PX-A640"}}, + {"CX8400", 0x80, 0x01, {"Stylus CX8300/CX8400/DX8400", "PX-A740"}}, + {"CX9400Fax", 0x81, 0x01, {"Stylus CX9300F/CX9400Fax/DX9400F", "PX-FA700"}}, + {"PM-T960", 0x82, 0x01, {NULL, "PM-T960"}}, + {"PM-A940", 0x84, 0x01, {"Stylus Photo RX680/RX685/RX690", "PM-A940"}}, + {"PM-A840", 0x85, 0x01, {"Stylus Photo RX585/RX595/RX610", "PM-A840/PM-A840S"}}, + {"GT-D1000", 0x86, 0x01, {"GT-1500", "GT-D1000"}}, + {"GT-X970", 0x87, 0x01, {NULL, NULL}}, + {"LP-M5000", 0x97, 0x01, {NULL, NULL}}, + {"LP-M6000", 0x89, 0x01, {NULL, NULL}}, + {"ES-H7200", 0x8A, 0x05, {NULL, NULL}}, + {"GT-20000", 0x8A, 0x05, {NULL, NULL}}, + {"NX200", 0x8D, 0x01, {"Stylus NX200/SX200/TX200", NULL}}, + {"NX400", 0x8E, 0x01, {"Stylus NX400/SX400/TX400", "PX-501A"}}, + {"NX100", 0x93, 0x01, {"Stylus NX100/SX100/TX100/ME 300", "PX-401A"}}, + {"NX300", 0x8F, 0x01, {"Stylus BX300F/TX300F/NX300/ME Office 600F", NULL}}, + {"WorkForce 600", 0x90, 0x01, {"Stylus BX600FW/SX600FW/TX600FW/ME Office 700FW/WorkForce 600", "PX-601F"}}, + {"Artisan 800", 0x91, 0x01, {"Stylus Photo PX800FW/TX800FW/Artisan 800", "EP-901A/EP-901F"}}, + {"Artisan 700", 0x92, 0x01, {"Stylus Photo PX700W/TX700W/Artisan 700", "EP-801A"}}, + {"WorkForce 500", 0x96, 0x01, {NULL, NULL}}, + {"GT-F720", 0x8B, 0x01, {"Perfection V300", "GT-F720"}}, + {"GT-S620", 0x8B, 0x01, {"Perfection V30", "GT-S620"}}, + {"GT-S50", 0x00, 0x01, {"GT-S50", "ES-D200"}}, + {"GT-S80", 0x00, 0x01, {"GT-S80", "ES-D400"}}, + {"PID 0851", 0x98, 0x01, {"Stylus NX410/SX410/TX410 Series", NULL}}, + {"PID 084D", 0x99, 0x01, {"Stylus NX110/SX110/TX110 Series", "PX-402A"}}, + {"PID 084F", 0x9A, 0x01, {"Stylus NX210/SX210/TX210/ME OFFICE 510 Series", NULL}}, + {"PID 0854", 0x9B, 0x01, {"Stylus Office TX510FN/BX310FN/WorkForce 310/ME OFFICE 650FN Series", NULL}}, + {"PID 0856", 0x9C, 0x01, {"Stylus NX510/SX510W/TX550W Series", "PX-502A"}}, + {"PID 0855", 0x9D, 0x01, {"Stylus Office TX610FW/BX610FW/SX610FW/WorkForce 610 Series", "PX-602F"}}, + {"PID 0850", 0x9E, 0x01, {"Stylus Photo PX650/TX650 Series", "EP-702A"}}, + {"PID 0852", 0xA0, 0x01, {"Stylus Photo TX710W/PX710W/Artisan 710 Series", "EP-802A"}}, + {"PID 0853", 0x9F, 0x01, {"Stylus Photo PX810FW/Artisan 810 Series", "EP-902A"}}, + {"GT-X820", 0xA1, 0x01, {"Perfection V600 Photo", "GT-X820"}}, + {"GT-S55", 0x00, 0x01, {"GT-S55", NULL}}, + {"GT-S85", 0x00, 0x01, {"GT-S85", "ES-D350"}}, + + /* array terminator */ + {NULL, 0x00, 0x00, {NULL, NULL}}, +}; + + +static struct EpsonScanCommand scan_command[] = { + {0x01, 0, 0, ILLEGAL_CMD}, + {0x02, ILLEGAL_CMD, 0, ILLEGAL_CMD}, + {0x03, 0, ILLEGAL_CMD, ILLEGAL_CMD}, + {0x04, ILLEGAL_CMD, ILLEGAL_CMD, ILLEGAL_CMD}, + {0x05, 0, 0x19, ILLEGAL_CMD}, + {0x06, 0, 0, '\f'}, +}; + +static +const scanner_data_t * +get_scanner (const char *fw_name) +{ + const scanner_data_t *data = scanner_data; + require (data); + { /* input validation*/ + if (!fw_name || 0 == strlen (fw_name)) + return NULL; + } + + while (data->fw_name != NULL /* end of array */ + && (0 != strcmp (data->fw_name, fw_name))) + { + ++data; + } + + if(!data->fw_name){ + err_major("Unknown model name."); + return NULL; + } + + return data; +} + +char * +get_scanner_data(const char* fw_name, const char *name) +{ + const scanner_data_t *data = get_scanner (fw_name); + + if (!data) return NULL; + + if(strcmp(name, FIRMWARE) == 0) return data->fw_name; + else if(strcmp(name, MODEL_OVERSEAS) == 0) return data->name.overseas; + else if(strcmp(name, MODEL_JAPAN) == 0) return data->name.japan; + + return NULL; +} + +struct EpsonScanCommand *get_scan_command(const char *fw_name) +{ + const scanner_data_t *data = get_scanner (fw_name); + scan_command_t *custom_command = NULL; + + if (data && data->command_ID) + { + int id = data->command_ID - 1; /* adjust for offset */ + require (id >= 0); + require ((unsigned) id < num_of (scan_command)); + + custom_command = &scan_command[id]; + } + else + { + custom_command = &scan_command[0]; + } + + return custom_command; +} + +EpsonScanHard get_epson_scan_hard (const char *fw_name) +{ + const scanner_data_t *data = get_scanner (fw_name); + EpsonScanHard profile = NULL; + + int i = 0; + + if (data && data->profile_ID) + { + i = num_of (_epson_scan_hard); + + while (--i && data->profile_ID != epson_scan_hard[i].modelID) + ; + } + profile = (EpsonScanHard) &epson_scan_hard[i]; + + return profile; +} diff --git a/backend/get-infofile.h b/backend/get-infofile.h new file mode 100644 index 0000000..d3daf80 --- /dev/null +++ b/backend/get-infofile.h @@ -0,0 +1,91 @@ +/* get-infofile.h -- + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_get_infofile_h +#define included_get_infofile_h + +#include "hw-data.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*----------ã‚¿ã‚°----------------*/ +#define FIRMWARE "firmware name" +#define MODEL_OVERSEAS "model overseas" +#define MODEL_JAPAN "model japan" +/*-----------------------------*/ + +char *get_scanner_data(const char* fw_name, const char* name); + +struct EpsonScanCommand *get_scan_command(const char *fw_name); + +EpsonScanHard get_epson_scan_hard (const char *fw_name); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/backend/hw-data.c b/backend/hw-data.c new file mode 100644 index 0000000..2f8aa39 --- /dev/null +++ b/backend/hw-data.c @@ -0,0 +1,372 @@ +/* hw-data.c -- selected hardware specific data + * Copyright (C) 2004--2016 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "hw-data.h" + +#include <ctype.h> +#include <locale.h> +#include <stddef.h> +#include <string.h> +#include <time.h> + + +#include "command.h" + +#include "utils.h" + +/* Not all scanners report the model name that is printed on the case. + Here we try to adjust for that by second guessing the name received + from the hardware with the aid of some environmental data. + WARNING: this is not and can not be foolproof. + + The caller gets to manage the memory occupied by the string that is + returned. + */ + +/*! Returns a copy of the F/W name of the device on the other end of a channel. + */ +char * +get_fw_name (channel *ch) +{ + SANE_Status status = SANE_STATUS_GOOD; + const byte cmd[] = { ESC, 'f' }; + char *fw_name = NULL; + + if (!ch) return NULL; + + channel_send (ch, cmd, num_of (cmd), &status); + if (SANE_STATUS_GOOD == status) + { + byte info[4]; + + channel_recv (ch, info, num_of (info), &status); + if (SANE_STATUS_GOOD == status) + { + char reply[42+1]; /* extra byte to enable string + termination */ + channel_recv (ch, reply, num_of (reply) - 1, &status); + if (SANE_STATUS_GOOD == status) + { + char *lc_ctype = setlocale (LC_CTYPE, "C"); + size_t n = num_of (reply); + + do + { + --n, reply[n] = '\0'; + } + while (26 < n && (isspace (reply[n-1]) || '\0' == reply[n-1])); + /* workaround for non-compliant + interpreter firmware names that + are padded with NULs and spaces + */ + fw_name = strdup (reply + 26); + setlocale (LC_CTYPE, lc_ctype); + } + } + } + if (SANE_STATUS_GOOD != status) + err_minor ("%s", sane_strstatus (status)); + + /* Devices with USB product IDs 0x085C and 0x0883 may report the + * same firmware name. That breaks our data file finding scheme + * so we fix up the firmware name here to be spec compliant. + */ + if (0 == strcmp_c ("PID 085C", fw_name) + && (CHAN_USB == ch->type && 0x0883 == ch->id)) + { + strcpy (fw_name, "PID 0883"); + } + + return fw_name; +} + +static SANE_Bool +_is_listed (const char *needle, const char **haystack) +{ + if (!needle || !haystack) return SANE_FALSE; + + while (*haystack) + { + if (0 == strcmp_c (needle, *haystack)) + return SANE_TRUE; + ++haystack; + } + return SANE_FALSE; +} + + +SANE_Bool +adf_needs_manual_centering (const device *hw) +{ + const char *fw_names[] = { + "LP-M6000", + "LP-M5000", + "LP-M5300", + "LP-M8040", + "LP-M8170", + "ES-H300", + "CX9400Fax", + "PID 087C", + "GT-S80", + "GT-S50", + "GT-S85", + "GT-S55", + NULL + }; + + require (using (hw, adf)); + return _is_listed (hw->fw_name, fw_names); +} + + +SANE_Bool +adf_has_auto_form_feed (const device *hw) +{ + const char *fw_names[] = { + "LP-M6000", + "LP-M5000", + "LP-M5300", + NULL + }; + + require (using (hw, adf)); + return _is_listed (hw->fw_name, fw_names) + || (FSI_CAP_AFF & hw->fsi_cap_2); +} + +SANE_Bool +adf_early_paper_end_kills_scan (const device *hw) +{ + const char *fw_names[] = { + "ES-10000G", + "ES-7000H", + "ES-H7200", + "Expression10000", + "GT-20000", + NULL, + }; + + require (using (hw, adf)); + return _is_listed (hw->fw_name, fw_names); +} + +SANE_Bool +push_button_is_black_listed (const device *hw) +{ + const char *fw_names[] = { + "LP-M6000", + "LP-M5000", + "LP-M5300", + NULL, + }; + + // whitelist of scanners that support push button via the network + const char *fw_names_net[] = { + "ES-H7200", + "GT-20000", + NULL, + }; + + + return _is_listed (hw->fw_name, fw_names) + || hw->uses_locking + || (CHAN_NET == hw->channel->type && + !_is_listed (hw->fw_name, fw_names_net)); +} + +SANE_Int +large_res_kills_adf_scan (const device *hw) +{ + const char *fw_names1[] = { + "ES-H300", + "LP-M6000", + "LP-M5000", + "LP-M5300", + "LP-M8040", + "LP-M8170", + NULL, + }; + + const char *fw_names2[] = { + "NX300", + "WorkForce 600", + "Artisan 800", + NULL, + }; + + require (hw->adf); + if (_is_listed (hw->fw_name, fw_names1)) return 600; + if (_is_listed (hw->fw_name, fw_names2)) return 1200; + + return 0; +} +SANE_Bool +zoom_kills_adf_scan (const device *hw) +{ + const char *fw_names[] = { + "LP-M6000", + "LP-M5000", + "LP-M5300", + NULL, + }; + + require (hw->adf); + return _is_listed (hw->fw_name, fw_names); +} + +/* Say whether duplex scans scan front and back sides in the same + * direction. This is definitely the case for single pass duplex + * devices (which scan front and back simultaneously) but is only + * seldomly encountered with double pass duplexers (which scan the + * front and back one after another). + */ +SANE_Bool +adf_duplex_direction_matches (const device *hw) +{ + const char *fw_names[] = { + "GT-S80", + "GT-S50", + "GT-S85", + "GT-S55", + NULL, + }; + + return hw->adf && _is_listed (hw->fw_name, fw_names); +} + +/*! Return an override for the current source's max_y value (in pixels) + * to be used with autocropping. In case no override is needed, zero + * is returned. + * + * A number of models support scanning a slightly taller document than + * the firmware would have you believe. This "feature" is referred to + * as overscanning and used to minimize the risks of chopping off bits + * from skewed originals when performing autocropping. + * + * \todo Allow for model specific max_y values. + */ +SANE_Int +autocrop_max_y (const device *hw) +{ + SANE_Int rv = 0; + + const char *fw_names[] = { + "GT-S80", + "GT-S50", + "GT-S85", + "GT-S55", + NULL, + }; + + if (_is_listed (hw->fw_name, fw_names)) + { + if (using (hw, adf)) + { + rv = 15 * hw->base_res; + } + } + return rv; +} + +/* Restrict functionality to tested devices only. + */ +SANE_Bool +enable_dip_deskew (const device *hw) +{ + const char *fw_names[] = { + "GT-S80", + "GT-S50", + "GT-S85", + "GT-S55", + NULL, + }; + + return _is_listed (hw->fw_name, fw_names); +} + +SANE_Bool +push_button_needs_polling (const device *hw) +{ + const char *fw_names[] = { + "DS-30", + NULL, + }; + + return _is_listed (hw->fw_name, fw_names); +} + +SANE_Bool +maintenance_is_supported (const device *hw) +{ + const char *fw_names[] = { + "DS-30", + NULL, + }; + + return _is_listed (hw->fw_name, fw_names); +} diff --git a/backend/hw-data.h b/backend/hw-data.h new file mode 100644 index 0000000..9ead25a --- /dev/null +++ b/backend/hw-data.h @@ -0,0 +1,136 @@ +/* hw-data.h -- selected hardware specific data + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef hw_data_h_included +#define hw_data_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "device.h" + +struct ScannerName +{ + char *overseas; + char *japan; +}; +typedef struct ScannerName scanner_name_t; + +struct ScannerData +{ + char *fw_name; + int profile_ID; + int command_ID; + const scanner_name_t name; +}; +typedef struct ScannerData scanner_data_t; + +#define ILLEGAL_CMD 0xFF + +struct EpsonScanCommand +{ + int command_ID; + unsigned char set_focus_position; + unsigned char feed; + unsigned char eject; + + bool lock; + bool unlock; +}; +typedef struct EpsonScanCommand scan_command_t; + +struct CapabilityData +{ + char *option; + char *mode; + + long width; + long height; + long base; +}; +typedef struct CapabilityData capability_data_t; + +char * get_fw_name (channel *ch); + +SANE_Bool adf_early_paper_end_kills_scan (const device *hw); +SANE_Bool adf_has_auto_form_feed (const device *hw); +SANE_Bool adf_needs_manual_centering (const device *hw); +SANE_Bool push_button_is_black_listed (const device *hw); +SANE_Int large_res_kills_adf_scan (const device *hw); +SANE_Bool zoom_kills_adf_scan (const device *hw); +SANE_Bool adf_duplex_direction_matches (const device *hw); +SANE_Int autocrop_max_y (const device *hw); +SANE_Bool enable_dip_deskew (const device *hw); +SANE_Bool push_button_needs_polling (const device *hw); +SANE_Bool maintenance_is_supported (const device *hw); + +/*! Array with colour correction profiles. + + \todo Replace this with a data member in the scanner that gets + initialized with data read from file. + */ +extern const EpsonScanHardRec *epson_scan_hard; + +#endif /* !defined (hw_data_h_included) */ diff --git a/backend/ipc.c b/backend/ipc.c new file mode 100644 index 0000000..e1d6437 --- /dev/null +++ b/backend/ipc.c @@ -0,0 +1,563 @@ +/* ipc.c -- inter-process communication (IPC) support + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ipc.h" + +#include <errno.h> +#include <netinet/in.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "defines.h" +#include "message.h" + + +/*! Attempts to read all the data up to \a size bytes. + * MERR is returned if an error, such as a timeout, occurs. + * MEOF is returned if EOF is reached. + * Otherwise, returns the number of bytes read, which must be greater than 0. + */ +static ssize_t +recv_all (int fd, void *buf, size_t size) +{ + ssize_t n = 0; + ssize_t t = 1; + + if (0 == size) return MERR; + + while (n < size && t > 0) + { + errno = 0; + t = read (fd, buf + n, size - n); + if (0 > t) + { + err_major ("read failed: %s", strerror (errno)); + return MERR; + } + else + { + n += t; + log_call ("transferred %zd bytes, total %zd/%zd", t, n, size); + } + if (0 == t) return MEOF; + } + + return n; +} + +/*! Attempts to write all the data up to \a size bytes. + * MERR is returned if an error, such as a timeout, occurs. + * Otherwise, returns the number of bytes written. + * A return value of zero indicates that nothing was written. + */ +static ssize_t +send_all (int fd, const void *buf, size_t size) +{ + ssize_t n = 0; + ssize_t t = 1; + + if (0 == size) return MERR; + + while (n < size && t > 0) + { + errno = 0; + t = write (fd, buf + n, size - n); + if (0 > t) + { + err_major ("write failed: %s", strerror (errno)); + return MERR; + } + else + { + n += t; + log_call ("transferred %zd bytes, total %zd/%zd", t, n, size); + } + } + + return n; +} + +/*! MERR is returned if an error, such as a timeout, occurs. + * MERR is also returned if writing the ipc header failed. + * Otherwise, the number of bytes of the payload that were successfully + * written is returned. + * A return value of zero indicates that nothing was written, this should + * only occur when the payload is of zero size. + */ +ssize_t +ipc_send (int sock, uint16_t id, uint8_t type_status, + size_t size, const void* payload) +{ + ssize_t n = 0; + + n = send_all (sock, &id, sizeof (id)); + if (0 >= n) return MERR; + + n = send_all (sock, &type_status, sizeof (type_status)); + if (0 >= n) return MERR; + + n = send_all (sock, &size, sizeof (size)); + if (0 >= n) return MERR; + + if (0 == size) return 0; + if (!payload) return MERR; + + n = send_all (sock, payload, size); + + log_info ("send packet {key: %d, msg: 0x%02x, size: %zd}", + id, type_status, size); + + if (ENABLE_DEBUG && 0 < n) + { + if (MSG_DBG_IMG_THRESHOLD < n) + dbg_img (payload, n); + else + dbg_hex (payload, n); + } + + return n; +} + +/*! Caller must have allocated space for \a id, and \a type_status. + * \a payload is automatically allocated based on the size field of the + * ipcling header, and it is the responsibility of the caller to + * deallocate it. + * + * MERR is returned if an error, such as a timeout, occurs. + * MEOF is returned if EOF is reached. + * Otherwise, returns the number of bytes read. + * A return value of zero is valid, as ipc packets do not necessarily + * have to contain a payload. + */ +ssize_t +ipc_recv (int sock, uint16_t *id, uint8_t *type_status, + void** payload) +{ + size_t size = 0; + char* buf = NULL; + ssize_t n = 0; + + n = recv_all (sock, id, sizeof (*id)); + if (0 > n) return n; + + n = recv_all (sock, type_status, sizeof (*type_status)); + if (0 > n) return n; + + n = recv_all (sock, &size, sizeof (size)); + if (0 > n) return n; + + if (0 == size) return 0; + if (!payload) return MERR; + + buf = t_malloc (size, char); + if (!buf) return MERR; + + n = recv_all (sock, buf, size); + + *payload = buf; + + log_info ("recv packet {key: %d, msg: 0x%02x, size: %zd}", + *id, *type_status, size); + + if (ENABLE_DEBUG && 0 < n) + { + if (MSG_DBG_IMG_THRESHOLD < n) + dbg_img (*payload, n); + else + dbg_hex (*payload, n); + } + + return n; +} + +/*! \brief Does the real work of starting the \a child process + */ +static +SANE_Status +ipc_fork (process *child) +{ + SANE_Status s = SANE_STATUS_GOOD; + + int pipe_fd[2]; + + require (child); + + if (-1 == pipe (pipe_fd)) + { + err_fatal ("pipe: %s", strerror (errno)); + return SANE_STATUS_ACCESS_DENIED; + } + + child->pid = fork (); + if (0 == child->pid) + { + /* replace child process with a plugin program + */ + close (pipe_fd[0]); /* unused read end */ + if (0 <= dup2 (pipe_fd[1], STDOUT_FILENO)) + { + log_info ("%s[%d]: starting", child->name, getpid ()); + if (-1 == execl (child->name, child->name, NULL)) + { + err_fatal ("%s[%d]: %s", child->name, getpid (), + strerror (errno)); + } + } + else + { + err_major ("%s[%d]: %s", child->name, getpid (), + strerror (errno)); + } + + /* notify the parent process that we're done here */ + write (pipe_fd[1], "-1\n", strlen ("-1\n")); + fsync (pipe_fd[1]); + + close (pipe_fd[1]); + exit (EXIT_FAILURE); + } + + if (0 > child->pid) + { + err_fatal ("fork: %s", strerror (errno)); + s = SANE_STATUS_CANCELLED; + } + else + { + /* Check whether child process has (unexpectedly) exited. We + don't want to have zombies in our closet ;-) + */ + pid_t w = waitpid (child->pid, NULL, WNOHANG); + if (-1 == w) + { + err_minor ("waitpid: %s", strerror (errno)); + } + if (0 != w) + { + log_info ("%s[%d]: exited prematurely", child->name, child->pid); + s = SANE_STATUS_CANCELLED; + } + else + { + FILE *fp = fdopen (pipe_fd[0], "rb"); + if (fp) + { + if (1 != fscanf (fp, "%d", &(child->port))) + { + err_major ("fscanf: %s", strerror (errno)); + } + fclose (fp); + } + else + { + err_fatal ("%s", strerror (errno)); + } + } + } + close (pipe_fd[0]); + close (pipe_fd[1]); + + if (0 > child->port) + s = SANE_STATUS_CANCELLED; + + return s; +} + +/*! \brief Requests a connection to a \a child + */ +static +SANE_Status +ipc_connect (process *child) +{ + struct sockaddr_in addr; + struct timeval t; + int rv; + + require (child); + + log_call ("(%s, %d)", child->name, child->port); + + errno = 0; + child->socket = socket (AF_INET, SOCK_STREAM, 0); + if (0 > child->socket) + { + err_major ("socket: %s", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + t.tv_sec = 30; + t.tv_usec = 0; + errno = 0; + rv = setsockopt (child->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof (t)); + if (0 > rv) + { + err_minor ("socket option: %s", strerror (errno)); + } + + errno = 0; + rv = setsockopt (child->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof (t)); + if (0 > rv) + { + err_minor ("socket option: %s", strerror (errno)); + } + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons (child->port); + addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + if (0 != connect (child->socket, (struct sockaddr *) &addr, sizeof (addr))) + { + err_major ("connect: %s", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + return SANE_STATUS_GOOD; +} + +process * +ipc_exec (const char *program, const char *pkglibdir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + process *child = NULL; + + log_call ("(%s, %s, %p)", program, pkglibdir, status); + + child = t_malloc (1, process); + if (!child) + { + if (status) *status = SANE_STATUS_NO_MEM; + return NULL; + } + + child->pid = -1; + child->port = -1; + child->socket = -1; + child->name = NULL; + + if (!pkglibdir) + { + child->name = strdup (program); + } + else + { + int n = (strlen (pkglibdir) + strlen (FILE_SEP_STR) + + strlen (program) + 1); + char *name = t_malloc (n, char); + + if (name) + { + sprintf (name, "%s%s%s", pkglibdir, FILE_SEP_STR, program); + child->name = name; + } + } + + if (!child->name) + { + s = SANE_STATUS_NO_MEM; + } + else if (access (child->name, X_OK)) + { + s = SANE_STATUS_ACCESS_DENIED; + } + + if (SANE_STATUS_GOOD != s) + { + if (status) *status = s; + delete (child); + return NULL; + } + + s = ipc_fork (child); + if (SANE_STATUS_GOOD == s) + { + int tries = 5; + + do + { + if (SANE_STATUS_GOOD != s) + sleep (1); + s = ipc_connect (child); + } + while (0 < --tries && SANE_STATUS_GOOD != s); + } + + if (SANE_STATUS_GOOD != s) + { + child = ipc_kill (child); + promise (!child); + } + else + { + promise (child); + promise (0 < child->pid); + promise (0 < child->port); + promise (0 < child->socket); + promise (child->name); + } + + if (status) *status = s; + + return child; +} + +process * +ipc_kill (process *child) +{ + log_call ("(%p)", child); + + if (child) + { + int status = 0; + + log_info ("terminating %s (port %d)", child->name, child->port); + + if (0 <= child->socket) + { + if (0 != close (child->socket)) + { + err_minor ("%s", strerror (errno)); + } + } + if (1 < child->pid) + { + if (0 != kill (child->pid, SIGHUP)) + { + err_minor ("%s", strerror (errno)); + } + if (child->pid != waitpid (child->pid, &status, 0)) + { + err_major ("%s", strerror (errno)); + } + + if (!WIFSIGNALED (status)) + { + err_major ("%s[%d]: went off the deep end!", + child->name, child->pid); + } + else + { + if (SIGHUP != WTERMSIG (status)) + { + err_major ("%s[%d]: %s", child->name, child->pid, + strsignal (WTERMSIG (status))); + } + } + } + + const_delete (child->name, char *); + delete (child); + } + + return child; +} + +void +ipc_dip_proc (process *child, int flag, const ipc_dip_parms *p, + SANE_Parameters *ctx, void **buffer) +{ + int socket; + uint8_t status = STATUS_NG; + uint16_t id = 0; + ssize_t n; + + require (child); + socket = child->socket; + + require (TYPE_DIP_SKEW_FLAG == flag || TYPE_DIP_CROP_FLAG == flag); + require (0 < socket && p && ctx && buffer && *buffer); + + /* inter-process procedure call, status will be STATUS_NG in case + * anything goes wrong during IPC call sequence + */ + { + n = ipc_send (socket, id, flag | TYPE_DIP_CTOR, + strlen (p->fw_name), p->fw_name); + if (strlen (p->fw_name) == n) + { + n = ipc_recv (socket, &id, &status, NULL); + if (STATUS_OK == status) + { + if (sizeof (*p) != ipc_send (socket, id, flag | TYPE_DIP_PARM, + sizeof (*p), p)) + { + status = STATUS_NG; + } + else + { + ipc_recv (socket, &id, &status, NULL); + if (STATUS_OK == status) + { + ssize_t size = ctx->bytes_per_line * ctx->lines; + + if (size != ipc_send (socket, id, flag | TYPE_DIP_DATA, + size, *buffer)) + { + err_minor ("image truncated"); + status = STATUS_NG; + } + } + } + } + } + } + + if (STATUS_NG == status) /* abort further processing */ + { + ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL); + ipc_recv (socket, &id, &status, NULL); + return; + } + + /* acquire DIP results */ + { + uint8_t req = flag | TYPE_DIP_PARM; + void *buf = NULL; + ipc_dip_parms par; + + if (sizeof (par) == ipc_recv (socket, &id, &req, &buf)) + { + ssize_t size; + + memcpy (&par, buf, sizeof (par)); + size = par.parms.bytes_per_line * par.parms.lines; + + req = flag | TYPE_DIP_DATA; + delete (buf); + + if (size == ipc_recv (socket, &id, &req, &buf)) + { + memcpy (ctx, &par.parms, sizeof (*ctx)); + delete (*buffer); + *buffer = buf; + } + else + { + err_minor ("image truncated"); + delete (buf); + } + } + } + ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL); + ipc_recv (socket, &id, &status, NULL); +} diff --git a/backend/ipc.h b/backend/ipc.h new file mode 100644 index 0000000..46d2047 --- /dev/null +++ b/backend/ipc.h @@ -0,0 +1,121 @@ +/* ipc.h -- inter-process communication (IPC) support + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + + +#ifndef ipc_h +#define ipc_h + +#include <stdint.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sane/sane.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*! \brief Exceptional return values from ipc_recv() and ipc_send() + * In particular, a return value of zero from read() conflicts with + * our ipc_recv() API where a return value of zero does not indicate + * EOF, but a successful read of zero bytes. + */ +#define MERR -1 /*!< read/write error */ +#define MEOF -2 /*!< eof; only returned by ipc_recv() */ + +enum { + TYPE_ESC = 1, + TYPE_INIT, + TYPE_DIE, + TYPE_OPEN, + TYPE_CLOSE, + TYPE_LIST, + TYPE_STATUS, + + TYPE_DIP_CTOR = 0x01, + TYPE_DIP_DTOR = 0x02, + TYPE_DIP_PARM = 0x03, + TYPE_DIP_DATA = 0x04, + TYPE_DIP_MASK = 0x0f, + TYPE_DIP_FLAG = 0xf0, + + TYPE_DIP_SKEW_FLAG = 0x10, + TYPE_DIP_SKEW_CTOR = TYPE_DIP_SKEW_FLAG | TYPE_DIP_CTOR, + TYPE_DIP_SKEW_DTOR = TYPE_DIP_SKEW_FLAG | TYPE_DIP_DTOR, + TYPE_DIP_SKEW_PARM = TYPE_DIP_SKEW_FLAG | TYPE_DIP_PARM, + TYPE_DIP_SKEW_DATA = TYPE_DIP_SKEW_FLAG | TYPE_DIP_DATA, + TYPE_DIP_SKEW_MASK = TYPE_DIP_SKEW_FLAG | TYPE_DIP_MASK, + + TYPE_DIP_CROP_FLAG = 0x20, + TYPE_DIP_CROP_CTOR = TYPE_DIP_CROP_FLAG | TYPE_DIP_CTOR, + TYPE_DIP_CROP_DTOR = TYPE_DIP_CROP_FLAG | TYPE_DIP_DTOR, + TYPE_DIP_CROP_PARM = TYPE_DIP_CROP_FLAG | TYPE_DIP_PARM, + TYPE_DIP_CROP_DATA = TYPE_DIP_CROP_FLAG | TYPE_DIP_DATA, + TYPE_DIP_CROP_MASK = TYPE_DIP_CROP_FLAG | TYPE_DIP_MASK, +}; + +enum { + STATUS_OK = 0, + STATUS_NG +}; + +ssize_t ipc_send (int sock, uint16_t id, uint8_t type_status, + size_t size, const void* payload); +ssize_t ipc_recv (int sock, uint16_t *id, uint8_t *type_status, + void** payload); + + typedef struct + { + pid_t pid; + int port; + int socket; + + const char *name; + + } process; + + /*! \brief Attempts to start \a program as a child process + */ + process * ipc_exec (const char *program, const char *pkglibdir, + SANE_Status *status); + + /*! \brief Terminates a child process + */ + process * ipc_kill (process *child); + + typedef struct + { + SANE_Parameters parms; + SANE_Int res_x; + SANE_Int res_y; + SANE_Int gamma; + SANE_Bool bside; + char fw_name[16 + 1]; + + } ipc_dip_parms; + + /*! \brief Performs an image processing action + * + * If any of the IPC messaging signals an error, the original image + * data will not be modified at all. That is, \a ctx and \a buffer + * remain unchanged in such a case. + */ + void ipc_dip_proc (process *child, int flag, const ipc_dip_parms *p, + SANE_Parameters *ctx, void **buffer); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (ipc_h) */ diff --git a/backend/list.c b/backend/list.c new file mode 100644 index 0000000..7e0810c --- /dev/null +++ b/backend/list.c @@ -0,0 +1,198 @@ +/* list.c -- A poor implementation of a linked list + * Copyright (C) 2008 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + \brief Implements a linked list. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "list.h" + +#include <string.h> + +/*! Create a new list. + */ +list* list_create () +{ + list* lst = 0; + + lst = t_calloc (1, list); + return lst; +} + +/*! Destroy a list. + * Deletes the list related structures, and the contained data with the + * given destructor function. If a destructor is not provided the data is + * left as is. + */ +void list_destroy (list* lst, void (*dtor)(void*)) +{ + if (!lst) return; + + list_entry* entry = lst->head; + list_entry* tmp; + + while (entry != NULL) + { + if (dtor && entry->data) + (*dtor) (entry->data); + + tmp = entry->next; + delete (entry); + entry = tmp; + } + + delete (lst); +} + +/*! Adds a new element to the end of the list. + * Does *not* make a copy. + */ +bool list_append (list* lst, void* new_data) +{ + if (!lst) return false; + + list_entry* entry = 0; + + entry = t_calloc (1, list_entry); + if (!entry) return false; + + entry->data = new_data; + + if (0 == lst->num_entries) + { + lst->head = entry; + lst->tail = entry; + lst->cur = entry; + } + else + { + lst->tail->next = entry; + lst->tail = entry; + } + + lst->num_entries += 1; + return true; +} + +/*! Creates a NULL terminated array of pointers to list entries. + */ +void** list_normalize (const list *lst) +{ + void **nlst; + + if (!lst) return NULL; + + nlst = t_malloc (lst->num_entries + 1, void *); + if (nlst) + { + list *p = (list *) lst; + list_entry *cur = p->cur; + + void *entry; + int i = 0; + + list_reset (p); + while ((entry = list_next (p))) + { + nlst[i++] = entry; + } + nlst[i++] = NULL; + p->cur = cur; + } + return nlst; +} + +/*! Obtain the number of elements in the list + */ +size_t list_size (list* lst) +{ + if (!lst) return 0; + return lst->num_entries; +} + +/*! Reset iteration to start from the beginning of the list + */ +void list_reset (list* lst) +{ + if (!lst) return; + + lst->cur = lst->head; +} + +/*! Proceed to the next element in the list. + * Used to iterate over the list items. + */ +void* list_next (list* lst) +{ + if (!lst || !lst->cur) return 0; + + void* rv = lst->cur->data; + lst->cur = lst->cur->next; + + return rv; +} diff --git a/backend/list.h b/backend/list.h new file mode 100644 index 0000000..f7547f6 --- /dev/null +++ b/backend/list.h @@ -0,0 +1,117 @@ +/* list.h -- A poor implementation of a linked list + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + +#ifndef list_h_included +#define list_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "defines.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct list_entry +{ + void* data; + struct list_entry* next; +} list_entry; + +typedef struct list +{ + list_entry* head; /* first element of the list */ + list_entry* tail; /* last element of the list */ + list_entry* cur; /* current element of the list */ + size_t num_entries; /* number of elements in the list */ +} list; + +/* create a new empty list */ +list* list_create (); + +/* destroy a list */ +void list_destroy (list* lst, void (*dtor)(void*)); + +/* add one element to the end of the list */ +bool list_append (list* lst, void* new_data); + +/* convert a list to a flat array */ +void** list_normalize (const list* lst); + +/* number of elements in the list */ +size_t list_size (list* lst); + +/* reset iteration to start from the beginning of the list*/ +void list_reset (list* lst); + +/* proceed to the next element in the list */ +void* list_next (list* lst); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (list_h_included) */ diff --git a/backend/message.c b/backend/message.c new file mode 100644 index 0000000..67853d4 --- /dev/null +++ b/backend/message.c @@ -0,0 +1,152 @@ +/* message.c -- consistent error, progress and debugging feedback + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + +/*! \file + \brief Infra-structure to provide consistent backend feedback. + + \todo Describe purpose of the three message categories and their + levels. Also document usage policy and message formatting + conventions. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "message.h" + +#if ENABLE_DEBUG + +unsigned long msg_level = 0; + + +#include <ctype.h> +#include <stdlib.h> +#include <strings.h> + +/*! Initialises the message infra-structure. + + This function sets level at which the backend should produce + feedback. The value is gotten from the \c SANE_DEBUG_EPKOWA + environment variable. + + The following, case insensitive string literals are supported + (in increasing level of feedback): + + - \c FATAL + - \c MAJOR + - \c MINOR + - \c INFO + - \c CALL + - \c DATA + - \c CMD + - \c HEX + - \c IMG + + \todo Add support for decimal literal level specification. + */ +void +msg_init (void) +{ + struct level_def + { + const char *key; + msg_level_type val; + }; + + const struct level_def def[] = + { + {"FATAL", ERR_FATAL}, + {"MAJOR", ERR_MAJOR}, + {"MINOR", ERR_MINOR}, + + {"INFO" , LOG_INFO}, + {"CALL" , LOG_CALL}, + {"DATA" , LOG_DATA}, + + {"CMD" , DBG_CMD}, + {"HEX" , DBG_HEX}, + {"IMG" , DBG_IMG}, + + {NULL} /* array terminator */ + }; + + const char *level = getenv ("SANE_DEBUG_EPKOWA"); + const struct level_def *p = def; + + msg_level = 0; + + if (!level) return; + + while (p && p->key) + { + if (0 == strcasecmp (level, p->key)) + { + msg_level = p->val; + log_info ("setting message level to '%s' (%d)", + p->key, p->val); + return; + } + ++p; + } +} + + +/*! Dumps the contents of a \a buffer in hexadecimal format. + */ +void +msg_dump (const char *fmt, const void *buffer, size_t sz) +{ + const size_t quad_length = 4; + const size_t quad_count = 4; + const size_t line_length = quad_length * quad_count; + + const unsigned char *buf = buffer; + + char ascii[line_length + 1]; + size_t i = 0; + + ascii[line_length] = '\0'; + + while (i < sz) + { + if (0 == i % line_length) /* header */ + fprintf (stderr, "%s%08zx: ", fmt, i); + + ascii[i % line_length] = (isprint (buf[i]) ? buf[i] : '.'); + + fprintf (stderr, " %02x", buf[i]); + ++i; + if (0 == i % quad_length) /* spacer */ + fprintf (stderr, " "); + if (0 == i % line_length) /* trailer */ + fprintf (stderr, " |%s|\n", ascii); + } + + if (0 != i % line_length) /* last line */ + { + do + { /* align trailer */ + ascii[i % line_length] = ' '; + fprintf (stderr, " "); + ++i; + if (0 == i % quad_length) + fprintf (stderr, " "); + } + while (0 != i % line_length); + fprintf (stderr, " |%s|\n", ascii); + } +} + +#endif /* ENABLE_DEBUG */ diff --git a/backend/message.h b/backend/message.h new file mode 100644 index 0000000..622f2a1 --- /dev/null +++ b/backend/message.h @@ -0,0 +1,191 @@ +/* message.h -- consistent error, progress and debugging feedback + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + + +#ifndef message_h +#define message_h + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#ifndef ENABLE_DEBUG +#define ENABLE_DEBUG 0 +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + + typedef enum + { + ERR_FATAL = (1 << 0), + ERR_MAJOR = (1 << 1), + ERR_MINOR = (1 << 2), + + LOG_INFO = (1 << 3), + LOG_CALL = (1 << 4), + LOG_DATA = (1 << 5), + + DBG_CMD = (1 << 6), + DBG_HEX = (1 << 7), + DBG_IMG = (1 << 8), + } + msg_level_type; + + + extern unsigned long msg_level; + + + /*! \brief Maximum buffer size not considered to be image data + * + * This value can be used by low-level I/O functions that want to + * log the interesting data they handle but lack the knowledge to + * distinguish between that data and boring old image bytes. + */ +#define MSG_DBG_IMG_THRESHOLD 512 + + +#define FMT_FILE __FILE__ ":%d: " +#define FMT_LINE , __LINE__ +#define FMT_MODULE FMT_FILE "[%s]" + +#define FMT_FATAL FMT_MODULE "[F] " +#define FMT_MAJOR FMT_MODULE "[M] " +#define FMT_MINOR FMT_MODULE "[m] " +#define FMT_INFO FMT_MODULE "{I} " +#define FMT_CALL FMT_MODULE "{C} " "%s " +#define FMT_DATA FMT_MODULE "{D} " +#define FMT_CMD FMT_MODULE "(e) " +#define FMT_HEX "[" MSG_MODULE "]" "(x) " +#define FMT_IMG "[" MSG_MODULE "]" "(i) " + +#if ENABLE_DEBUG + +#define err_fatal(fmt,arg...) \ + do \ + { \ + if (ERR_FATAL <= msg_level) \ + fprintf (stderr, FMT_FATAL fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define err_major(fmt,arg...) \ + do \ + { \ + if (ERR_MAJOR <= msg_level) \ + fprintf (stderr, FMT_MAJOR fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define err_minor(fmt,arg...) \ + do \ + { \ + if (ERR_MINOR <= msg_level) \ + fprintf (stderr, FMT_MINOR fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define log_info(fmt,arg...) \ + do \ + { \ + if (LOG_INFO <= msg_level) \ + fprintf (stderr, FMT_INFO fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define log_call(fmt,arg...) \ + do \ + { \ + if (LOG_CALL <= msg_level) \ + fprintf (stderr, FMT_CALL fmt "\n" FMT_LINE, MSG_MODULE, \ + __func__, ## arg); \ + } \ + while (0) \ + /**/ + +#define log_data(fmt,arg...) \ + do \ + { \ + if (LOG_DATA <= msg_level) \ + fprintf (stderr, FMT_DATA fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define dbg_cmd(buf,sz) \ + do \ + { \ + if (DBG_CMD <= msg_level) \ + fprintf (stderr, FMT_CMD fmt "\n" FMT_LINE, MSG_MODULE, \ + ## arg); \ + } \ + while (0) \ + /**/ + +#define dbg_hex(buf,sz) \ + do \ + { \ + if (DBG_HEX <= msg_level) \ + msg_dump (FMT_HEX, buf, sz); \ + } \ + while (0) \ + /**/ + +#define dbg_img(buf,sz) \ + do \ + { \ + if (DBG_IMG <= msg_level) \ + msg_dump (FMT_IMG, buf, sz); \ + } \ + while (0) \ + /**/ + + void msg_init (void); + void msg_dump (const char *, const void *, size_t); + +#else /* !ENABLE_DEBUG */ + +#define err_fatal(fmt,arg...) +#define err_major(fmt,arg...) +#define err_minor(fmt,arg...) +#define log_info(fmt,arg...) +#define log_call(fmt,arg...) +#define log_data(fmt,arg...) +#define dbg_cmd(buf,sz) +#define dbg_hex(buf,sz) +#define dbg_img(buf,sz) + +#define msg_init() +#define msg_dump(fmt,buf,sz) + +#endif /* !ENABLE_DEBUG */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (message_h) */ diff --git a/backend/model-info.c b/backend/model-info.c new file mode 100644 index 0000000..5423483 --- /dev/null +++ b/backend/model-info.c @@ -0,0 +1,707 @@ +/* model-info.c -- per model information objects and cache + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "model-info.h" + +#include <stdlib.h> +#include <time.h> + +#include "list.h" +#include "message.h" +#include "utils.h" + +#include "get-infofile.h" +#include "xmlreader.h" + + +/*! \brief Points to our model info cache. + * + * Its value is guaranteed to be \c NULL \e outside the scope of a + * model_info_cache_init()/model_info_cache_exit() pair. + */ +static list *_cache = NULL; + + +/*! \brief Directory to search for resource files + * + * The _model_info_ctor() needs this. + */ +static const char *_datadir = NULL; + + +/*! \brief Collects all (supported) model information + */ +typedef struct +{ + char *fw_name; /* key for _model_info_cache_get_info */ + char *overseas; /* model name */ + char *japan; /* model name */ + char *name; /* points to one of overseas, japan or + * fw_name, never NULL */ + scan_command_t *command; /* command customisation info */ + EpsonScanHard profile; /* colour profiles */ + + bool from_file; /* origin of our data, used to control + * which resources need to be released + * at destruction time */ + + capability_data_t *dfault; + capability_data_t *adf_duplex; +} _model_info_t; + +typedef enum +{ + FEED, + FOCUS, + EJECT, + +} _command_id_t; + +/* Model info cache implementation details */ +static _model_info_t * _model_info_cache_get_info (const char *fw_name, + SANE_Status *status); + +/* Model info implementation details */ +static _model_info_t * _model_info_ctor (const char *fw_name, + SANE_Status *status); +static void _model_info_dtor (void *p); +static char * _model_info_guess_name (const _model_info_t *self); +static SANE_Status _model_info_merge_file (_model_info_t *self); +static void _model_info_merge_data (_model_info_t *self, + xmlNodePtr node); + +static bool _model_info_set_cmd (const _model_info_t *self, + unsigned char *cmd, _command_id_t id); + + +/*! \brief Sets up model info cache support. + * + * Model specific information will be looked for in \a pkgdatadir. + * + * \returns An opaque pointer to the cache. An additional \a status + * will be returned as well if the argument is not \c NULL. + */ +void * +model_info_cache_init (const char *pkgdatadir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + log_call ("(%s, %p)", pkgdatadir, status); + require (pkgdatadir); + + if (_cache) + { + err_minor ("been here, done that"); + if (0 != strcmp_c (_datadir, pkgdatadir)) + { + err_major ("already using %s", _datadir); + } + if (status) *status = s; + return _cache; + } + + _datadir = strdup (pkgdatadir); + _cache = list_create (); + if (!_datadir || !_cache) + { + _cache = model_info_cache_exit (_cache); + + s = SANE_STATUS_NO_MEM; + } + + if (0 != atexit (xmlCleanupParser)) + { + err_minor ("could not register XML parser cleanup function"); + } + + /* ?FIXME? + * Check for existence/readability of _pkgdatadir and log its + * absence/presence. It is _not_ fatal if the directory does + * not exist or is not readable. We just want to note it via + * an err_minor(). If not readable, we may want to return a + * SANE_STATUS_ACCESS_DENIED. + */ + + if (status) *status = s; + return _cache; +} + + +/*! \brief Tears down model info cache support. + * + * Releases all resources associated with the model info cache. + * The \a self argument should be an opaque pointer obtained via a + * call to model_info_cache_init(). + * + * Note that for error recovery purposes, model_info_cache_init() may + * call this function with an empty _cache or no cache at all. + * + * \returns \c NULL, always + */ +void * +model_info_cache_exit (void *self) +{ + log_call ("(%p)", self); + require (_cache == self); + + const_delete (_datadir, char *); + list_destroy (_cache, _model_info_dtor); + _datadir = NULL; + _cache = NULL; + + promise (!_cache); + + return _cache; +} + + +/*! \brief Attempts to find model information for \a fw_name. + * + * \return A pointer to the model information, \c NULL if not found. + */ +const void * +model_info_cache_get_info (const char *fw_name, SANE_Status *status) +{ + /* Just forward to the internal implementation. The return + * statement will handle the pointer type conversion. + */ + return _model_info_cache_get_info (fw_name, status); +} + + +/*! \brief Returns a best-effort model name based on \a fw_name. + * + * The caller gets to manage the memory occupied by the string that + * is returned. Note that \c NULL may be returned. + */ +char * +model_info_cache_get_model (const char *fw_name) +{ + SANE_Status s = SANE_STATUS_GOOD; + _model_info_t *m = NULL; + + log_call ("(%s)", fw_name); + require (_cache && _datadir); + + if (!fw_name || 0 == strlen (fw_name)) + { + err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL)); + return strdup ("(unknown model)"); + } + + m = _model_info_cache_get_info (fw_name, &s); + if (!m) + { + err_minor ("%s", sane_strstatus (s)); + return strdup (fw_name); /* best we can do */ + } + + return strdup (m->name); +} + + +/*! \brief Returns a reference to the model name. + * + * Resources associated with the reference are owned by \a self. The + * caller should \e not attempt to release them. + */ +const char * +model_info_get_name (const void *self) +{ + require (self); /* ?FIXME? check if in _cache? */ + + return ((const _model_info_t *) self)->name; +} + + +/*! \brief Returns a reference to the model's colour profiles. + * + * Resources associated with the reference are owned by \a self. The + * caller should \e not attempt to release them. + */ +const EpsonScanHard +model_info_get_profile (const void *self) +{ + require (self); /* ?FIXME? check if in _cache? */ + + return ((const _model_info_t *) self)->profile; +} + + +/*! \brief Modify selected commands in the \a cmd specification + * + * This function caters to quirks in the command level specification + * reported by the device. Especially commands for hardware options + * may be affected. + * + * \return \c true if commands have been modified, \c false otherwise + */ +bool +model_info_customise_commands (const void *self, EpsonCmd cmd) +{ + bool customised = false; + _model_info_t *self_ = NULL; + + require (self); /* ?FIXME? check if in _cache? */ + + if (!cmd) + { + err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL)); + return customised; + } + + self_ = (_model_info_t *) self; + customised |= _model_info_set_cmd (self_, &cmd->set_focus_position, FOCUS); + customised |= _model_info_set_cmd (self_, &cmd->feed, FEED); + customised |= _model_info_set_cmd (self_, &cmd->eject, EJECT); + + return customised; +} + + +/*! \brief Attempts to find model information for \a fw_name. + * + * Checks for existing information in the cache before it attempts to + * add new model information. Takes care to preserve the cache's cur + * member so as not to invalidate existing "iterators". + * + * \return A pointer to the model information. \c NULL if not found, + * in case anything went wrong trying to add the info to the + * cache or the caller passed in garbage. + */ +static _model_info_t * +_model_info_cache_get_info (const char *fw_name, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + _model_info_t *info = NULL; + list_entry *cur = NULL; + bool found = false; + + log_call ("(%s)", fw_name); + require (_cache && _datadir); + + if (!fw_name || 0 == strlen (fw_name)) + { + if (status) *status = SANE_STATUS_INVAL; + return NULL; + } + + cur = _cache->cur; /* check whether cached */ + list_reset (_cache); + while (!found && (info = list_next (_cache))) + { + found = (0 == strcmp_c (info->fw_name, fw_name)); + } + _cache->cur = cur; + + if (!found) /* try to add info to cache */ + { + info = _model_info_ctor (fw_name, &s); + if (!info || !list_append (_cache, info)) + { + _model_info_dtor (info); + info = NULL; + } + } + + if (status) *status = s; + return info; +} + + +/*! \brief Creates and initialises a model info object. + * + * \return A valid model info object or \c NULL if unable to acquire + * the necessary memory resources. + */ +static _model_info_t * +_model_info_ctor (const char *fw_name, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + _model_info_t *self = NULL; + + log_call ("(%s)", fw_name); + require (fw_name); + + self = t_calloc (1, _model_info_t); + if (!self) + { + if (status) *status = SANE_STATUS_NO_MEM; + return NULL; + } + + self->fw_name = strdup (fw_name); + if (!self->fw_name) + { + _model_info_dtor (self); + if (status) *status = SANE_STATUS_NO_MEM; + return NULL; + } + + /* Set defaults using data defined in the source code. The various + * getters decide a decent default in case self->fw_name is not one + * of the names for which we have data in our sources. + */ + self->overseas = get_scanner_data (self->fw_name, MODEL_OVERSEAS); + self->japan = get_scanner_data (self->fw_name, MODEL_JAPAN); + self->profile = get_epson_scan_hard (self->fw_name); + self->command = get_scan_command (self->fw_name); + + self->from_file = false; + + s = _model_info_merge_file (self); + + self->name = _model_info_guess_name (self); + + if (self) /* make sure things are compliant */ + { + promise (self->fw_name && self->name); + promise ( self->name == self->fw_name + || self->name == self->overseas + || self->name == self->japan); + promise (self->profile); + promise (self->command); + } + if (status) *status = s; + return self; +} + + +/*! \brief Destroys a model object. + * + * Releases any resources acquired throughout the object's life time. + */ +static void +_model_info_dtor (void *self) +{ + _model_info_t *p = (_model_info_t *) self; + + if (!p) return; + + if (p->from_file) + { + /* :FIXME: p->profile may have been acquired as several + * individual arrays at construction. Check! + */ + if (p->profile != get_epson_scan_hard (p->fw_name)) + delete (p->profile); + if (p->command != get_scan_command (p->fw_name)) + delete (p->command); + delete (p->overseas); + delete (p->japan); + if(p->dfault) + { + delete (p->dfault->option); + delete (p->dfault->mode); + } + delete (p->dfault); + if (p->adf_duplex) + { + delete (p->adf_duplex->option); + delete (p->adf_duplex->mode); + } + delete (p->adf_duplex); + } + + delete (p->fw_name); + delete (p); +} + +/*! \brief Returns a best effort guess for model name on the device + * + * This functions implements the policy used to decide which of the + * various model names \c self->name should point to. The function + * is intended for use at construction of \a self. + */ +static char * +_model_info_guess_name (const _model_info_t *self) +{ + require (self); + + if (self->japan && self->overseas) + { + time_t lt = time (NULL); + struct tm *ptr = localtime (<); + + if (ptr && 0 == strncmp_c ("JST", ptr->tm_zone, 3)) + { + return self->japan; + } + else + { + return self->overseas; + } + } + + if (self->japan) return self->japan; + if (self->overseas) return self->overseas; + return self->fw_name; +} + +static char * +get_path_name (const char *hex_name, char *path_name, size_t path_size) +{ + char *path; + size_t size = snprintf (path_name, path_size, "%s%s%s%s", + _datadir, FILE_SEP_STR, hex_name, + ".xml"); + + if (size > -1 || size < path_size) + return path_name; + + if (size > -1) + path_size = size + 1; + else + path_size *= 2; + + if (0 == path_size) + path_size = 512; + + path = t_realloc (path_name, path_size, char); + if (!path) + { + delete (path_name); + return NULL; + } + path_name = path; + + return get_path_name (hex_name, path_name, path_size); +} + +/*! \brief Attempts to merge model information from a data file. + */ +static SANE_Status +_model_info_merge_file (_model_info_t *self) +{ + xmlDocPtr doc = NULL; + + char *path_name = NULL; + char *hex_name = NULL; + + require (self); + + hex_name = fw_name_to_hex (self->fw_name); + + if (!hex_name) return SANE_STATUS_NO_MEM; + + path_name = get_path_name (hex_name, NULL, 0); + delete (hex_name); /* no longer needed */ + + if (!path_name) return SANE_STATUS_NO_MEM; + + log_data ("%s", path_name); + + doc = xmlReadFile (path_name, NULL, + XML_PARSE_NOERROR | XML_PARSE_NOWARNING); + delete (path_name); /* no longer needed */ + + if (doc) + { + _model_info_merge_data (self, xmlDocGetRootElement (doc)); + xmlFreeDoc (doc); + } + else + { + xmlErrorPtr p = xmlGetLastError (); + if (p) err_minor ("%s", p->message); + } + + return SANE_STATUS_GOOD; +} + +static void +_model_info_merge_data (_model_info_t *self, xmlNodePtr node) +{ + /* Define the XML element tags that we are able to merge. + */ + const xmlChar *device = (xmlChar *) "device"; + const xmlChar *profiles = (xmlChar *) "profile-set"; + const xmlChar *commands = (xmlChar *) "command-set"; + const xmlChar *capabilities = (xmlChar *) "capabilities"; + const xmlChar *cap_option = (xmlChar *) "option"; + const xmlChar *cap_mode = (xmlChar *) "mode"; + char *tmp; + + require (self); + + if (!node) return; + + node = node->xmlChildrenNode; + while (node) + { + if (!xmlIsBlankNode (node)) + { + if (0 == xmlStrcmp (node->name, device)) + { + char *tmp = NULL; + + tmp = parseDevices (node, MODEL_OVERSEAS); + if (tmp) self->overseas = tmp; + + tmp = parseDevices (node, MODEL_JAPAN); + if (tmp) self->japan = tmp; + } + else if (0 == xmlStrcmp (node->name, profiles)) + { + EpsonScanHard profile = parseProfiles (node); + if (profile) self->profile = profile; + } + else if (0 == xmlStrcmp (node->name, commands)) + { + scan_command_t *command = parseCommands_set (node); + if (command) self->command = command; + } + else if (0 == xmlStrcmp (node->name, capabilities)) + { + tmp = (char *)xmlGetProp(node, (const xmlChar *) cap_mode); + if(strcmp_c(tmp, "duplex") == 0){ + capability_data_t *capability = parseCapabilities (node); + if (capability) { + self->adf_duplex = capability; + self->adf_duplex->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option); + self->adf_duplex->mode = tmp; + } + }else { + capability_data_t *capability = parseCapabilities (node); + if (capability) { + self->dfault = capability; + self->dfault->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option); + self->dfault->mode = tmp; + } + } + } + } + node = node->next; + } + self->from_file = true; +} + +/*! \brief Customises a single command + * + * \return \c true if \a cmd was modified, \c false otherwise + */ +static bool +_model_info_set_cmd (const _model_info_t *self, unsigned char *cmd, + _command_id_t id) +{ + unsigned char cmd_ = ILLEGAL_CMD; + + require (self && cmd); + + if (FEED == id) cmd_ = self->command->feed; + if (FOCUS == id) cmd_ = self->command->set_focus_position; + if (EJECT == id) cmd_ = self->command->eject; + + if (ILLEGAL_CMD != cmd_) + { + *cmd = cmd_; + return true; + } + return false; +} + +bool +model_info_has_lock_commands (const void *self) +{ + _model_info_t *self_ = NULL; + + require (self); + + self_ = (_model_info_t *) self; + + return (self_->command->lock && self_->command->unlock); +} + +scan_area_t +model_info_max_scan_area(const void *self, const char *option, const char *mode) +{ + _model_info_t *self_ = NULL; + scan_area_t scan_area; + + require (self); + require (option); + + scan_area.width = SANE_FIX(-1); + scan_area.height = SANE_FIX(-1); + + self_ = (_model_info_t *) self; + + if(strcmp(option, "adf") == 0 && strcmp_c(mode, "duplex") == 0){ + if(self_->adf_duplex){ + scan_area.width = SANE_FIX (self_->adf_duplex->width * MM_PER_INCH / self_->adf_duplex->base); + scan_area.height = SANE_FIX (self_->adf_duplex->height * MM_PER_INCH / self_->adf_duplex->base); + } + }else { + if(self_->dfault){ + scan_area.width = SANE_FIX (self_->dfault->width * MM_PER_INCH / self_->dfault->base); + scan_area.height = SANE_FIX (self_->dfault->height * MM_PER_INCH / self_->dfault->base); + } + } + + return scan_area; +} diff --git a/backend/model-info.h b/backend/model-info.h new file mode 100644 index 0000000..b92defb --- /dev/null +++ b/backend/model-info.h @@ -0,0 +1,145 @@ +/* model-info.h -- per model information objects and cache + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef model_info_h_included +#define model_info_h_included + +/*! \file + * \brief Model specific information. + * + * There is a fair bit of device information that does not change + * during the life-time of the device. The ESC/I scanner protocol + * provides support to query the device to get a significant part of + * this information. However, some of the information we would like + * to use is not available from the device. In addition, the device + * sometimes returns incorrect information. + * + * For a long time we have been working around this by putting the + * additional information and the corrections directly in our code. + * The model info support provided here implements a unified API to + * the device information allowing us to move it out of the code and + * into per model resource files. + * + * The API aims for on-demand, cached data retrieval. In order to + * support that, the sane_init() and sane_exit() functions need to + * handle cache initialisation and clean-up. They can do so through + * the model_info_cache_init() and model_cache_exit() functions. + * + * The sane_get_devices() and sane_open() functions can get access to + * per model information through the model_info_cache_get_info() API + * and use its return value in calls to the model info accessors. Of + * course, other SANE API entries may do so as well, though we cannot + * think of a good need for them to do so (as the information can be + * stored in the SANE_Handle during sane_open()). + * + * The number of model info accessors is still quite limited but we + * expect that to change when the implementation proceeds. + * + * There is also convenience API meant to make initialisation of the + * SANE_Device structure marginally less verbose. + */ + +#include <sane/sane.h> + +#include "defines.h" +#include "device.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + struct ScanArea + { + SANE_Fixed width; + SANE_Fixed height; + }; + typedef struct ScanArea scan_area_t; + + /* Model info cache creation and destruction */ + void * model_info_cache_init (const char *pkgdatadir, SANE_Status *status); + void * model_info_cache_exit (void *self); + + /* Model info cache accessors */ + const void * model_info_cache_get_info (const char *fw_name, + SANE_Status *status); + + /* Model info cache convenience methods */ + char * model_info_cache_get_model (const char *fw_name); + /* ?FIXME? add convenience methods for vendor and type? */ + + /* Model info accessors */ + const char * model_info_get_name (const void *self); + const EpsonScanHard model_info_get_profile (const void *self); + bool model_info_customise_commands (const void *self, EpsonCmd cmd); + bool model_info_has_lock_commands (const void *self); + + scan_area_t model_info_max_scan_area(const void *self, const char *option, const char *mode); + /* :FIXME: add more accessors */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (model_info_h_included) */ diff --git a/backend/net-obj.c b/backend/net-obj.c new file mode 100644 index 0000000..d5ee958 --- /dev/null +++ b/backend/net-obj.c @@ -0,0 +1,123 @@ +/* net-obj.c -- + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "net-obj.h" + +#include "defines.h" +#include "ipc.h" +#include "message.h" + +const char *net_prog_name = "network"; + +static process *net = NULL; + +void * +net_init (const char *pkglibdir, SANE_Status *status) +{ + log_call ("(%s, %p)", pkglibdir, status); + + if (net) + { + err_minor ("been here, done that"); + if (status) *status = SANE_STATUS_GOOD; + return net; + } + + if (!pkglibdir) return NULL; + + net = ipc_exec (net_prog_name, pkglibdir, status); + + return net; +} + +void * +net_exit (void *self) +{ + log_call ("(%p)", self); + require (net == self); + + if (net) + { + net = ipc_kill (net); + promise (!net); + } + + return net; +} + +/*! Obtain the socket connected to the network plugin + */ +int +net_get_sock (void *self) +{ + log_call ("(%p)", self); + require (net == self); + + if (net) return net->socket; + return -1; +} diff --git a/backend/net-obj.h b/backend/net-obj.h new file mode 100644 index 0000000..e336626 --- /dev/null +++ b/backend/net-obj.h @@ -0,0 +1,89 @@ +/* net-obj.h -- + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef net_obj_h_included +#define net_obj_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sane/sane.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + + void * net_init (const char *pkglibdir, SANE_Status *status); + void * net_exit (void *self); + int net_get_sock (void *self); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (net_obj_h_included) */ diff --git a/backend/profile.c b/backend/profile.c new file mode 100644 index 0000000..57c0401 --- /dev/null +++ b/backend/profile.c @@ -0,0 +1,540 @@ +/* profile.c -- hardware colour correction coefficients + * Copyright (C) 2001--2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! Hardware colour correction coefficients (CCC). + + Each entry starts with a unique identifier, followed by four CCC + profiles; the first is for reflective materials, the second for + colour negatives, the third for monochrome negatives, and the + fourth and last one is for colour positives. +*/ +const EpsonScanHardRec _epson_scan_hard[] = { + {0x00, /* default */ + {{1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x05, + {{1.1419,-0.0596,-0.0825,-0.1234, 1.2812,-0.1413, 0.0703,-0.5720, 1.5016}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1419,-0.0596,-0.0825,-0.1234, 1.2812,-0.1413, 0.0703,-0.5720, 1.5016}}}, + {0x06, + {{1.1442,-0.0705,-0.0737,-0.0702, 1.1013,-0.0311,-0.0080,-0.3588, 1.3668}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x07, + {{1.1967,-0.1379,-0.0588,-0.0538, 1.0385, 0.0153, 0.0348,-0.4070, 1.3721}, + {1.0010,-0.0010, 0.0000,-0.1120, 1.1710,-0.0590, 0.0000,-0.0910, 1.0920}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1967,-0.1379,-0.0588,-0.0538, 1.0385, 0.0153, 0.0348,-0.4070, 1.3721}}}, + {0x0D, + {{1.1980,-0.1365,-0.0616,-0.1530, 1.1729,-0.0198,-0.0025,-0.2776, 1.2801}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1980,-0.1365,-0.0616,-0.1530, 1.1729,-0.0198,-0.0025,-0.2776, 1.2801}}}, + {0x0F, + {{1.0961,-0.0181,-0.0779,-0.1279, 1.1957,-0.0678, 0.0315,-0.3891, 1.3576}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0961,-0.0181,-0.0779,-0.1279, 1.1957,-0.0678, 0.0315,-0.3891, 1.3576}}}, + {0x15, + {{1.0999,-0.0425,-0.0574,-0.0806, 1.0835,-0.0028, 0.0057,-0.2924, 1.2866}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x16, + {{1.2020,-0.1518,-0.0502,-0.0847, 1.1385,-0.0538, 0.0059,-0.3255, 1.3196}, + {1.0030,-0.0030, 0.0000,-0.0980, 1.1500,-0.0520,-0.0030,-0.0840, 1.0880}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2456,-0.1617,-0.0839,-0.1160, 1.1862,-0.0702,-0.0036,-0.3438, 1.3473}}}, + {0x18, + {{1.1339,-0.0526,-0.0813,-0.1177, 1.1661,-0.0485,-0.0030,-0.3298, 1.3328}, + {1.0010,-0.0010, 0.0000,-0.1120, 1.1710,-0.0590, 0.0000,-0.0910, 1.0920}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2066,-0.0360,-0.1706,-0.1313, 1.2523,-0.1210,-0.0299,-0.3377, 1.3676}}}, + {0x1A, + {{1.0986, 0.0235,-0.1221,-0.1294, 1.0896, 0.0399, 0.0928,-0.6043, 1.5115}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x1B, + {{1.1855,-0.1372,-0.0483,-0.2060, 1.2468,-0.0407, 0.0358,-0.3059, 1.2701}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1976,-0.1182,-0.0794,-0.1578, 1.2720,-0.1142, 0.0122,-0.3467, 1.3345}}}, + {0x1D, + {{1.0675,-0.0586,-0.0088,-0.0332, 0.9716, 0.0616, 0.0175,-0.4054, 1.3879}, + {1.0090,-0.0090, 0.0000,-0.0390, 1.0750,-0.0360,-0.0070,-0.1060, 1.1130}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1394,-0.0829,-0.0564,-0.0003, 1.0008,-0.0004,-0.0059,-0.3674, 1.3733}}}, + {0x1F, + {{1.0800,-0.0607,-0.0193,-0.0787, 1.0846,-0.0059, 0.0135,-0.3334, 1.3199}, + {1.0040,-0.0040, 0.0000,-0.0780, 1.1360,-0.0570,-0.0020,-0.0810, 1.0830}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1334,-0.0929,-0.0405,-0.0418, 1.0689,-0.0271,-0.0521,-0.3262, 1.3783}}}, + {0x21, + {{1.0919,-0.0739,-0.0180,-0.0941, 1.1150,-0.0209, 0.0220,-0.3744, 1.3524}, + {1.0090,-0.0100, 0.0010,-0.0720, 1.1310,-0.0600, 0.0000,-0.1000, 1.1000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1374,-0.1396, 0.0021,-0.0489, 1.0655,-0.0166, 0.0081,-0.3492, 1.3411}}}, + {0x23, + {{1.0339,-0.0166,-0.0173,-0.0117, 0.9797, 0.0319, 0.0010,-0.3609, 1.3599}, + {1.0090,-0.0090, 0.0000,-0.0390, 1.0750,-0.0360,-0.0070,-0.1060, 1.1130}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1666,-0.0898,-0.0768,-0.0076, 1.0157,-0.0081, 0.0012,-0.3048, 1.3036}}}, + {0x25, + {{1.0800,-0.0607,-0.0193,-0.0787, 1.0846,-0.0059, 0.0135,-0.3334, 1.3199}, + {1.0040,-0.0040, 0.0000,-0.0780, 1.1360,-0.0570,-0.0020,-0.0810, 1.0830}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1334,-0.0929,-0.0405,-0.0418, 1.0689,-0.0271,-0.0521,-0.3262, 1.3783}}}, + {0x27, + {{1.0919,-0.0739,-0.0180,-0.0941, 1.1150,-0.0209, 0.0220,-0.3744, 1.3524}, + {1.0083,-0.0094, 0.0011,-0.0760, 1.1379,-0.0619,-0.0002,-0.0945, 1.0947}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1952,-0.1519,-0.0433,-0.0932, 1.1613,-0.0681,-0.0418,-0.3140, 1.3558}}}, + {0x29, + {{1.0369,-0.0210,-0.0160,-0.0820, 1.1160,-0.0341, 0.0150,-0.5035, 1.4885}, + {1.0122,-0.0151, 0.0029,-0.0861, 1.1402,-0.0542,-0.0061,-0.1607, 1.1669}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1764,-0.1749,-0.0014,-0.0590, 1.0983,-0.0393, 0.0208,-0.5194, 1.4986}}}, + {0x2B, + {{1.0305,-0.0116,-0.0189,-0.0936, 1.1245,-0.0309,-0.0072,-0.1413, 1.1485}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x32, + {{1.0932,-0.0529,-0.0403,-0.1077, 1.1416,-0.0338, 0.0079,-0.5525, 1.5446}, + {1.0259,-0.0356, 0.0097,-0.1085, 1.2225,-0.1140,-0.0046,-0.1848, 1.1894}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2720,-0.2665,-0.0054,-0.0672, 1.1301,-0.0629,-0.0048,-0.3917, 1.3965}}}, + {0x2D, + {{1.0436,-0.0078,-0.0359,-0.0169, 1.0114, 0.0056, 0.0308,-0.4425, 1.4117}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x3A, + {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806}, + {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}}, + {0x36, + {{1.0848,-0.0153,-0.0695,-0.0902, 1.0611, 0.0291, 0.0344,-0.5002, 1.4658}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x34, + {{1.1032,-0.0590,-0.0442,-0.1915, 1.3371,-0.1456, 0.0387,-0.5804, 1.5417}, + {1.0232,-0.0258, 0.0026,-0.1296, 1.2882,-0.1587,-0.0011,-0.1928, 1.1940}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2662,-0.2664, 0.0002,-0.1050, 1.3168,-0.2118,-0.0058,-0.4370, 1.4428}}}, + {0x38, + {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806}, + {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}}, + {0x37, + {{0.9640, 0.1455,-0.1095, 0.0108, 1.1933,-0.2041, 0.0071,-0.3487, 1.3416}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x3F, + {{1.1223,-0.0985,-0.0238,-0.0847, 1.1502,-0.0655, 0.0118,-0.5022, 1.4904}, + {1.0077,-0.0129, 0.0052,-0.0904, 1.1785,-0.0881, 0.0000,-0.1528, 1.1528}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1927,-0.1646,-0.0280,-0.0655, 1.1033,-0.0378, 0.0034,-0.4173, 1.4139}}}, + {0x41, + {{1.0732,-0.0581,-0.0150,-0.0897, 1.1553,-0.0657,-0.0179,-0.6500, 1.6679}, + {1.0163,-0.0203, 0.0040,-0.1125, 1.1797,-0.0672,-0.0091,-0.2343, 1.2434}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2437,-0.2022,-0.0415,-0.0352, 1.0735,-0.0383,-0.0188,-0.5020, 1.5209}}}, + {0x43, + {{1.0782,-0.0697,-0.0085,-0.1605, 1.2862,-0.1257, 0.0148,-0.5854, 1.5706}, + {1.0136,-0.0151, 0.0016,-0.1836, 1.3422,-0.1586,-0.0014,-0.1851, 1.1865}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1491,-0.1456,-0.0035,-0.0990, 1.2657,-0.1666, 0.0015,-0.3868, 1.3853}}}, + {0x46, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x48, + {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x49, + {{0.9640, 0.1455,-0.1095, 0.0108, 1.1933,-0.2041, 0.0071,-0.3487, 1.3416}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x4B, + {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806}, + {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}}, + {0x4D, + {{1.1011,-0.0824,-0.0186,-0.0970, 1.1991,-0.1021,-0.0161,-0.6247, 1.6408}, + {1.0259,-0.0356, 0.0097,-0.1085, 1.2225,-0.1140,-0.0046,-0.1848, 1.1894}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2150,-0.2074,-0.0076,-0.0521, 1.1430,-0.0909,-0.0204,-0.4156, 1.4360}}}, + {0x4F, + {{1.1052,-0.0850,-0.0202,-0.1050, 1.2294,-0.1245,-0.0486,-0.4160, 1.4646}, + {1.0255,-0.0272, 0.0017,-0.0919, 1.2098,-0.1180,-0.0021,-0.1296, 1.1317}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2950,-0.2619,-0.0332,-0.0562, 1.1587,-0.1025,-0.0397,-0.3100, 1.3497}}}, + {0x51, + {{1.0614,-0.0361,-0.0253,-0.1081, 1.1320,-0.0240,-0.0536,-0.2045, 1.2580}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x52, + {{1.0978,-0.0806,-0.0173,-0.0802, 1.1515,-0.0713,-0.0476,-0.4656, 1.5132}, + {1.0192,-0.0192, 0.0000,-0.0974, 1.1846,-0.0872,-0.0031,-0.1797, 1.1828}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2490,-0.2030,-0.0460,-0.0469, 1.1046,-0.0577,-0.0361,-0.3857, 1.4217}}}, + {0x54, + {{1.0905,-0.0654,-0.0251,-0.1030, 1.1801,-0.0771,-0.0685,-0.4238, 1.4923}, + {1.0206,-0.0207, 0.0000,-0.0890, 1.1770,-0.0880,-0.0014,-0.1450, 1.1464}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.3041,-0.2907,-0.0134,-0.0383, 1.0908,-0.0525,-0.0327,-0.2947, 1.3275}}}, + {0x56, + {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x57, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x58, + {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x59, + {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x5B, + {{0.9764, 0.1095,-0.0859, 0.0149, 1.1154,-0.1303, 0.0051,-0.2851, 1.2800}, + {1.0024,-0.0149, 0.0124,-0.2569, 1.3432,-0.0864,-0.0043,-0.1306, 1.1349}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1003,-0.0493,-0.0510,-0.1607, 1.2748,-0.1142,-0.0059,-0.3161, 1.3220}}}, + {0x5D, + {{0.9764, 0.1095,-0.0859, 0.0149, 1.1154,-0.1303, 0.0051,-0.2851, 1.2800}, + {1.0024,-0.0149, 0.0124,-0.2569, 1.3432,-0.0864,-0.0043,-0.1306, 1.1349}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.1003,-0.0493,-0.0510,-0.1607, 1.2748,-0.1142,-0.0059,-0.3161, 1.3220}}}, + {0x5F, + {{1.0697,-0.0561,-0.0137,-0.0824, 1.1291,-0.0467,-0.0390,-0.5218, 1.5608}, + {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2606,-0.2125,-0.0482,-0.0567, 1.1441,-0.0874,-0.0431,-0.3490, 1.3921}}}, + {0x61, + {{1.0921,-0.0722,-0.0199,-0.0831, 1.1550,-0.0718,-0.0452,-0.3721, 1.4173}, + {1.0168,-0.0168, 0.0000,-0.0953, 1.1928,-0.0975,-0.0012,-0.1235, 1.1247}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2603,-0.2763, 0.0155,-0.0398, 1.1033,-0.0635,-0.0249,-0.2675, 1.2924}}}, + {0x63, + {{1.0976,-0.0789,-0.0187,-0.0958, 1.1821,-0.0863,-0.0565,-0.4179, 1.4744}, + {1.0250,-0.0267, 0.0016,-0.0930, 1.2108,-0.1178,-0.0022,-0.1296, 1.1317}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.3111,-0.2979,-0.0132,-0.0441, 1.1148,-0.0707,-0.0348,-0.2971, 1.3319}}}, + {0x65, + {{1.0359,-0.0146,-0.0213,-0.0752, 1.0963,-0.0211,-0.0456,-0.3238, 1.3693}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x66, + {{1.0878,-0.0667,-0.0211,-0.0892, 1.1513,-0.0622,-0.0654,-0.5175, 1.5829}, + {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2688,-0.2522,-0.0166,-0.0559, 1.1291,-0.0733,-0.0377,-0.3519, 1.3896}}}, + {0x68, + {{1.0950,-0.0646,-0.0305,-0.0792, 1.1398,-0.0606,-0.0123,-0.5175, 1.5298}, + {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2697,-0.2501,-0.0195,-0.0351, 1.1236,-0.0885,-0.0131,-0.3268, 1.3400}}}, + {0x6A, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x6B, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x6C, + {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x70, + {{0.9533, 0.0885,-0.0418, 0.0033, 1.0627,-0.0660,-0.0137,-0.1904, 1.2041}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x71, + {{1.0697,-0.0561,-0.0137,-0.0824, 1.1291,-0.0467,-0.0390,-0.5218, 1.5608}, + {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2606,-0.2125,-0.0482,-0.0567, 1.1441,-0.0874,-0.0431,-0.3490, 1.3921}}}, + {0x73, + {{1.0828,-0.0739,-0.0089,-0.0895, 1.1597,-0.0702,-0.0531,-0.4291, 1.4822}, + {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2579,-0.2384,-0.0195,-0.0569, 1.1454,-0.0884,-0.0411,-0.3072, 1.3483}}}, + {0x75, + {{1.0828,-0.0739,-0.0089,-0.0895, 1.1597,-0.0702,-0.0531,-0.4291, 1.4822}, + {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.2579,-0.2384,-0.0195,-0.0569, 1.1454,-0.0884,-0.0411,-0.3072, 1.3483}}}, + {0x77, + {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x78, + {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x79, + {{1.0614,-0.0361,-0.0253,-0.1081, 1.1320,-0.0240,-0.0536,-0.2045, 1.2580}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x7A, + {{1.1754,-0.1173,-0.0580,-0.0687, 1.1307,-0.0620,-0.0255,-0.4699, 1.4954}, + {1.0150,-0.0173, 0.0022,-0.0853, 1.2238,-0.1384,-0.0073,-0.1490, 1.1562}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4283,-0.4335, 0.0052,-0.0170, 1.1308,-0.1138,-0.0147,-0.2230, 1.2377}}}, + {0x7C, + {{1.2470,-0.2041,-0.0429,-0.1920, 1.2918,-0.0998,-0.0100,-0.2503, 1.2603}, + {1.0050,-0.0076, 0.0026,-0.2532, 1.1289, 0.1243,-0.0733,-0.0960, 1.1693}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4724,-0.4599,-0.0125,-0.0876, 1.1562,-0.0686,-0.0097,-0.2278, 1.2375}}}, + {0x7E, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x7F, + {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x80, + {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x81, + {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x82, + {{1.1622,-0.1102,-0.0519,-0.0717, 1.1060,-0.0343,-0.0248,-0.4138, 1.4385}, + {0.9913, 0.0082, 0.0005,-0.1259, 1.0452, 0.0807,-0.0072,-0.0767, 1.0839}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.3900,-0.3008,-0.0892,-0.0254, 1.0890,-0.0636,-0.0300,-0.2501, 1.2801}}}, + {0x84, + {{1.0934,-0.0042,-0.0892, 0.0052, 1.1019,-0.1071, 0.0259,-0.2651, 1.2392}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x85, + {{1.0534, 0.0399,-0.0934, 0.0098, 1.0589,-0.0687, 0.0016,-0.1131, 1.1115}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x86, + {{1.1945,-0.1413,-0.0532,-0.1929, 1.2525,-0.0596,-0.0235,-0.2761, 1.2996}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x87, + {{1.1978,-0.1417,-0.0561,-0.0852, 1.1610,-0.0758,-0.0395,-0.3212, 1.3607}, + {1.0000, 0.0009,-0.0009,-0.1268, 1.0523, 0.0745,-0.0075,-0.0873, 1.0948}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4475,-0.3957,-0.0518,-0.0138, 1.0644,-0.0506,-0.0199,-0.2050, 1.2249}}}, + {0x97, + {{1.1115,-0.0377,-0.0738,-0.0658, 1.0624, 0.0034, 0.0042,-0.2883, 1.2841}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x89, + {{1.1115,-0.0377,-0.0738,-0.0658, 1.0624, 0.0034, 0.0042,-0.2883, 1.2841}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x8A, + {{1.1221,-0.0396,-0.0825,-0.0718, 1.0822,-0.0104, 0.0112,-0.2995, 1.2883}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x8B, + {{1.2402,-0.1891,-0.0511,-0.1535, 1.2008,-0.0473,-0.0316,-0.3293, 1.3609}, + {1.0027,-0.0048, 0.0021,-0.2067, 1.0878, 0.1189,-0.0408,-0.0767, 1.1175}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4524,-0.4346,-0.0178,-0.0601, 1.1273,-0.0672,-0.0173,-0.1823, 1.1996}}}, + {0x8D, + {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x8E, + {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x8F, + {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x90, + {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x91, + {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x92, + {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x93, + {{1.0934,-0.0042,-0.0892, 0.0052, 1.1019,-0.1071, 0.0259,-0.2651, 1.2392}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x96, + {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x98, + {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x99, + {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9A, + {{1.0779, 0.0132,-0.0911, 0.0214, 1.1003,-0.1217, 0.0109,-0.1487, 1.1378}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9B, + {{1.0779, 0.0132,-0.0911, 0.0214, 1.1003,-0.1217, 0.0109,-0.1487, 1.1378}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9C, + {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9D, + {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9E, + {{1.0534, 0.0399,-0.0934, 0.0098, 1.0589,-0.0687, 0.0016,-0.1131, 1.1115}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0x9F, + {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0xA0, + {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}}, + {0xA1, + {{1.2578,-0.2140,-0.0438,-0.1939, 1.2856,-0.0917,-0.0258,-0.2642, 1.2900}, + {0.9989,-0.0018, 0.0029,-0.2608, 1.1305, 0.1303,-0.0802,-0.0807, 1.1609}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4431,-0.4193,-0.0238,-0.0915, 1.1507,-0.0592,-0.0226,-0.1978, 1.2204}}}, +}; + +const EpsonScanHardRec *epson_scan_hard = _epson_scan_hard; diff --git a/backend/tests/45532d48333030.xml b/backend/tests/45532d48333030.xml new file mode 100644 index 0000000..e7d6a04 --- /dev/null +++ b/backend/tests/45532d48333030.xml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<scanner_entry> + + <interfaces> + <usb vendor="04b8" product="012b"/> + <net status="unsupported" note="option"/> + </interfaces> + + <device> + <firmware name="ES-H300"/> + <model name="GT-2500"/> + <model name="ES-H300" region="japan"/> + </device> + + <profile-set> + <profile type="reflective"> + <rr value=" 1.0359"/><rg value="-0.0146"/><rb value="-0.0213"/> + <gr value="-0.0752"/><gg value=" 1.0963"/><gb value="-0.0211"/> + <br value="-0.0456"/><bg value="-0.3238"/><bb value=" 1.3693"/> + </profile> + </profile-set> + + <command-set type="extended" level="B8"> + <command name="set_focus_position" code="1B70" status="disabled"/> + <command name="feed" code="19" status="disabled"/> + </command-set> + +</scanner_entry> diff --git a/backend/tests/47542d58393730.xml b/backend/tests/47542d58393730.xml new file mode 100644 index 0000000..26cb7e7 --- /dev/null +++ b/backend/tests/47542d58393730.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<scanner_entry> + + <interfaces> + <usb vendor="04b8" product="012b"/> + <net status="unsupported" note="option"/> + </interfaces> + + <profile-set> + <profile type="reflective"> + <rr value=" 1.1978"/><rg value="-0.1417"/><rb value="-0.0561"/> + <gr value="-0.0852"/><gg value=" 1.161"/><gb value="-0.0758"/> + <br value="-0.0395"/><bg value="-0.3212"/><bb value=" 1.3607"/> + </profile> + <profile type="color negative"> + <rr value=" 1.000"/><rg value="0.0009"/><rb value="-0.0009"/> + <gr value="-0.1268"/><gg value=" 1.0523"/><gb value="0.0745"/> + <br value="-0.0075"/><bg value="-0.0873"/><bb value=" 1.0948"/> + </profile> + <profile type="positive"> + <rr value=" 1.4475"/><rg value="-0.3957"/><rb value="-0.0518"/> + <gr value="-0.0138"/><gg value=" 1.0644"/><gb value="-0.0506"/> + <br value="-0.0199"/><bg value="-0.205"/><bb value=" 1.2249"/> + </profile> + </profile-set> + + <device> + <firmware name="GT-X970"/> + </device> + + <command-set type="extended" level="B8"> + <command name="set_focus_position" code="1B70" status="disabled"/> + <command name="feed" code="19" status="disabled"/> + <command name="lock"/> + <command name="unlock"/> + </command-set> + +</scanner_entry> diff --git a/backend/tests/50657266656374696f6e363130.xml b/backend/tests/50657266656374696f6e363130.xml new file mode 100644 index 0000000..2ac6c46 --- /dev/null +++ b/backend/tests/50657266656374696f6e363130.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<scanner_entry> + + <interfaces> + <usb vendor="04b8" product="012b"/> + <net status="unsupported" note="option"/> + </interfaces> + + <device> + <firmware name="Perfection610"/> + <model name="Perfection 610"/> + </device> + + <profile-set> + <profile type="reflective"> + <rr value=" 1.1442"/><rg value="-0.0705"/><rb value="-0.0737"/> + <gr value="-0.0702"/><gg value=" 1.1013"/><gb value="-0.0311"/> + <br value="-0.0080"/><bg value="-0.3588"/><bb value=" 1.3668"/> + </profile> + </profile-set> + + <command-set type="extended" level="B8"> + <command name="set_focus_position" code="1B70" status="disabled"/> + <command name="feed" code="19" status="disabled"/> + </command-set> + +</scanner_entry> diff --git a/backend/tests/Makefile.am b/backend/tests/Makefile.am new file mode 100644 index 0000000..ef275d1 --- /dev/null +++ b/backend/tests/Makefile.am @@ -0,0 +1,79 @@ +## Makefile.am -- an -*- automake -*- template for Makefile.in +## Copyright (C) 2008 SEIKO EPSON CORPORATION +## +## License: GPLv2+ +## Authors: AVASYS CORPORATION +## +## This file is part of the "Image Scan!" build infra-structure. +## +## The "Image Scan!" build infra-structure 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 ought to have received a copy of the GNU General Public License +## along with this package. If not, see <http://www.gnu.org/licenses/>. + + +AM_CPPFLAGS = $(XML_CFLAGS) + +check_PROGRAMS = \ + xmltest + +TESTS = + +xmltest_LDADD = ../libepkowa.la +xmltest_SOURCES = xmltest.c xmltest.h + +EXTRA_DIST = \ + 47542d58393730.xml \ + 45532d48333030.xml \ + 50657266656374696f6e363130.xml \ + xmltest-runner.sh + +if HAVE_CXXTESTGEN + +check_PROGRAMS += \ + cfg-obj \ + net-obj \ + network \ + model-info + +TESTS += \ + cfg-obj \ + net-obj \ + model-info + +cfg_obj_LDADD = ../libepkowa.la +cfg_obj_SOURCES = \ + test-cfg-obj.cc \ + test-cfg-obj.hh + +net_obj_LDADD = ../libepkowa.la +net_obj_SOURCES = \ + test-net-obj.cc \ + test-net-obj.hh + +model_info_LDADD = ../libepkowa.la +model_info_SOURCES = \ + test-model-info.cc \ + test-model-info.hh + +# Use the CxxTest code generator on all files matching test-*.hh to +# create the corresponding test-*.cc file. This convention is used +# so that we can still use regular source code without this getting +# clobbered as soon as we change the header file. + +.hh.cc: + @if test xtest- = "x`echo $^ | sed -n 's|^\(test-\).*|\1|p'`"; \ + then \ + echo "$(CXXTESTGEN) $(CXXTESTGEN_OPTS) -o $@ $^"; \ + $(CXXTESTGEN) $(CXXTESTGEN_OPTS) -o $@ $^; \ + fi + +endif ## HAVE_CXXTESTGEN diff --git a/backend/tests/network.c b/backend/tests/network.c new file mode 100644 index 0000000..8dd6dec --- /dev/null +++ b/backend/tests/network.c @@ -0,0 +1,86 @@ +/* network.c -- a network mock program for use by unit tests + * Copyright (C) 2008 SEIKO EPSON CORPORATION + * + * License: GPLv2+ + * Authors: AVASYS CORPORATION + * + * This file is part of Image Scan!'s SANE backend test suite. + * + * Image Scan!'s SANE backend test suite 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +int +main (int argc, char *argv[]) +{ + socklen_t n; + int r; + int s; + int as; + struct sockaddr_in addr; + + s = socket (AF_INET, SOCK_STREAM, 0); + if (0 > s) + { + perror ("socket"); + return EXIT_FAILURE; + } + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = PF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = INADDR_ANY; + + r = bind (s, (struct sockaddr *) &addr, sizeof (addr)); + if (0 > r) + { + perror ("bind"); + return EXIT_FAILURE; + } + + n = sizeof (addr); + r = getsockname (s, (struct sockaddr *) &addr, &n); + if (0 > r) + { + perror ("getsockname"); + return EXIT_FAILURE; + } + + setvbuf (stdout, NULL, _IONBF, BUFSIZ); + fprintf (stdout, "%d\n", ntohs (addr.sin_port)); + fclose (stdout); /* not strictly needed */ + + listen (s, 0); + as = accept (s, (struct sockaddr*) &addr, (socklen_t*) &n); + + pause (); + + close (s); + close (as); + + return EXIT_SUCCESS; +} diff --git a/backend/tests/test-cfg-obj.cc b/backend/tests/test-cfg-obj.cc new file mode 100644 index 0000000..9fe213f --- /dev/null +++ b/backend/tests/test-cfg-obj.cc @@ -0,0 +1,60 @@ +/* Generated file, do not edit */ + +#ifndef CXXTEST_RUNNING +#define CXXTEST_RUNNING +#endif + +#define _CXXTEST_HAVE_STD +#include <cxxtest/TestListener.h> +#include <cxxtest/TestTracker.h> +#include <cxxtest/TestRunner.h> +#include <cxxtest/RealDescriptions.h> +#include <cxxtest/ErrorPrinter.h> + +int main() { + return CxxTest::ErrorPrinter().run(); +} +#include "test-cfg-obj.hh" + +static test_cfg_obj suite_test_cfg_obj; + +static CxxTest::List Tests_test_cfg_obj = { 0, 0 }; +CxxTest::StaticSuiteDescription suiteDescription_test_cfg_obj( "test-cfg-obj.hh", 89, "test_cfg_obj", suite_test_cfg_obj, Tests_test_cfg_obj ); + +static class TestDescription_test_cfg_obj_test_life_cycle : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_life_cycle() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 93, "test_life_cycle" ) {} + void runTest() { suite_test_cfg_obj.test_life_cycle(); } +} testDescription_test_cfg_obj_test_life_cycle; + +static class TestDescription_test_cfg_obj_test_life_cycle_status : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_life_cycle_status() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 102, "test_life_cycle_status" ) {} + void runTest() { suite_test_cfg_obj.test_life_cycle_status(); } +} testDescription_test_cfg_obj_test_life_cycle_status; + +static class TestDescription_test_cfg_obj_test_key_query : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_key_query() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 115, "test_key_query" ) {} + void runTest() { suite_test_cfg_obj.test_key_query(); } +} testDescription_test_cfg_obj_test_key_query; + +static class TestDescription_test_cfg_obj_test_option_value_query : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_option_value_query() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 123, "test_option_value_query" ) {} + void runTest() { suite_test_cfg_obj.test_option_value_query(); } +} testDescription_test_cfg_obj_test_option_value_query; + +static class TestDescription_test_cfg_obj_test_key_mutator : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_key_mutator() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 134, "test_key_mutator" ) {} + void runTest() { suite_test_cfg_obj.test_key_mutator(); } +} testDescription_test_cfg_obj_test_key_mutator; + +static class TestDescription_test_cfg_obj_test_net_registration : public CxxTest::RealTestDescription { +public: + TestDescription_test_cfg_obj_test_net_registration() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 146, "test_net_registration" ) {} + void runTest() { suite_test_cfg_obj.test_net_registration(); } +} testDescription_test_cfg_obj_test_net_registration; + +#include <cxxtest/Root.cpp> diff --git a/backend/tests/test-cfg-obj.hh b/backend/tests/test-cfg-obj.hh new file mode 100644 index 0000000..ab8b851 --- /dev/null +++ b/backend/tests/test-cfg-obj.hh @@ -0,0 +1,254 @@ +// test-cfg-obj.hh -- test suite for configuration objects +// Copyright (C) 2008 SEIKO EPSON CORPORATION +// +// License: GPLv2+|iscan +// Authors: AVASYS CORPORATION +// +// This file is part of Image Scan!'s SANE backend test suite. +// +// Image Scan!'s SANE backend test suite 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 ought to have received a copy of the GNU General Public License +// along with this package. If not, see <http://www.gnu.org/licenses/>. +// +// +// Linking Image Scan!'s SANE backend statically or dynamically with +// other modules is making a combined work based on this SANE backend. +// Thus, the terms and conditions of the GNU General Public License +// cover the whole combination. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// SANE frontends that communicate with Image Scan!'s SANE backend +// solely through the SANE Application Programming Interface, +// regardless of the license terms of these SANE frontends, and to +// copy and distribute the resulting combined work under terms of your +// choice, provided that every copy of the combined work is +// accompanied by a complete copy of the source code of Image Scan!'s +// SANE backend (the version of Image Scan!'s SANE backend used to +// produce the combined work), being distributed under the terms of +// the GNU General Public License plus this exception. An independent +// module is a module which is not derived from or based on Image +// Scan!'s SANE backend. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// independent modules that communicate with Image Scan!'s SANE +// backend solely through the "Interpreter" interface, regardless of +// the license terms of these independent modules, and to copy and +// distribute the resulting combined work under terms of your choice, +// provided that every copy of the combined work is accompanied by a +// complete copy of the source code of Image Scan!'s SANE backend (the +// version of Image Scan!'s SANE backend used to produce the combined +// work), being distributed under the terms of the GNU General Public +// License plus this exception. An independent module is a module +// which is not derived from or based on Image Scan!'s SANE backend. +// +// Note that people who make modified versions of Image Scan!'s SANE +// backend are not obligated to grant special exceptions for their +// modified versions; it is their choice whether to do so. The GNU +// General Public License gives permission to release a modified +// version without this exception; this exception also makes it +// possible to release a modified version which carries forward this +// exception. + + +#ifndef included_test_cfg_obj_hh +#define included_test_cfg_obj_hh + +#ifndef __cplusplus +#error "This is a C++ include file. Use a C++ compiler to compile" +#error "code that includes this file." +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "../cfg-obj.h" +#include <cxxtest/TestSuite.h> + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <string> +#include <unistd.h> + +#include "../message.h" + + +class test_cfg_obj : public CxxTest::TestSuite +{ +public: + + void test_life_cycle (void) + { + cfg = cfg_init (dir.c_str (), NULL); + TS_ASSERT (cfg); + + cfg = cfg_exit (cfg); + TS_ASSERT (!cfg); + } + + void test_life_cycle_status (void) + { + SANE_Status status = SANE_STATUS_NO_MEM; + + cfg = cfg_init (dir.c_str (), &status); + TS_ASSERT (cfg); + + TS_ASSERT (SANE_STATUS_NO_MEM != status); + + cfg = cfg_exit (cfg); + TS_ASSERT (!cfg); + } + + void test_key_query (void) + { + cfg = cfg_init (dir.c_str (), NULL); + TS_ASSERT (cfg); + + TS_ASSERT (cfg_has (cfg, CFG_KEY_USB)); + } + + void test_option_value_query (void) + { + cfg = cfg_init (dir.c_str (), NULL); + TS_ASSERT (cfg); + + TS_ASSERT (cfg_has (cfg, CFG_KEY_OPTION)); + TS_ASSERT (cfg_has_value (cfg, CFG_KEY_OPTION, "prefer-adf")); + TS_ASSERT (!cfg_has_value (cfg, CFG_KEY_OPTION, "not-a-valid-option")); + TS_ASSERT (!cfg_has_value (cfg, CFG_KEY_OPTION, "not-in-config")); + } + + void test_key_mutator (void) + { + cfg = cfg_init (dir.c_str (), NULL); + TS_ASSERT (cfg); + + TS_ASSERT (cfg_has (cfg, CFG_KEY_USB)); + cfg_set (cfg, CFG_KEY_USB, false); + TS_ASSERT (!cfg_has (cfg, CFG_KEY_USB)); + cfg_set (cfg, CFG_KEY_USB, true); + TS_ASSERT (cfg_has (cfg, CFG_KEY_USB)); + } + + void test_net_registration (void) + { + add_cfg_entry ("net\n"); + + cfg = cfg_init (dir.c_str (), NULL); + TS_ASSERT (!cfg_has (cfg, CFG_KEY_NET)); + } + +private: + + void *cfg; + std::string dir; + + /*! Create a temporary, minimal configuration directory. + + The configuration directory is made available to the unit test + environment via the \c SANE_CONFIG_DIR environment variable so + configuration objects (should) take note. In addition, a very + minimal configuration file, containing the \c usb keyword and + a few options is created in this directory as well. + */ + void setUp (void) + { + int result; + msg_init (); + + char dirname_template[] = ".cfg-obj-XXXXXX"; + char *dirname = mkdtemp (dirname_template); + if (!dirname) + { + err_fatal ("mkdtemp: %s", strerror (errno)); + } + require (dirname); + + result = chdir (dirname); + require (0 == result); + + std::ofstream ofs (cfg_file_name); + ofs << "usb\n"; + ofs << "option prefer-adf \n"; + ofs << "option not-a-valid-option\n"; + + result = chdir (".."); + require (0 == result); + + result = setenv ("SANE_CONFIG_DIR", dirname, true); + require (0 == result); + + cfg = NULL; + dir = dirname; + } + + /*! Attempts to undo the effects of setUp(). + + The environment's \c SANE_CONFIG_DIR is unset and the temporary + configuration directory is recursively removed. Failures to + undo any of the necessary actions are logged but will not cause + a test to fail. + */ + void tearDown (void) + { + int result; + + if (0 != unsetenv ("SANE_CONFIG_DIR")) + { + err_minor ("unsetenv: %s", strerror (errno)); + } + + if (0 != chdir (dir.c_str ())) + { + err_minor ("%s: %s", dir.c_str (), strerror (errno)); + } + else + { + if (0 != unlink (cfg_file_name)) + { + err_minor ("%s: %s", cfg_file_name, strerror (errno)); + } + if (0 != unlink ("usb")) + { + err_minor ("%s: %s", "usb", strerror (errno)); + } + result = chdir (".."); + require (0 == result); + } + + if (0 != rmdir (dir.c_str ())) + { + err_minor ("%s: %s", dir.c_str (), strerror (errno)); + } + dir = ""; + + cfg = cfg_exit (cfg); + promise (!cfg); + } + + void add_cfg_entry (const char *str) + { + chdir (dir.c_str ()); + std::ofstream ofs (cfg_file_name, + std::ios_base::out | std::ios_base::app); + + ofs << str; + chdir (".."); + } +}; + + +#endif /* !defined (included_test_cfg_obj_hh) */ diff --git a/backend/tests/test-model-info.cc b/backend/tests/test-model-info.cc new file mode 100644 index 0000000..bb5eb7e --- /dev/null +++ b/backend/tests/test-model-info.cc @@ -0,0 +1,83 @@ +/* Generated file, do not edit */ + +#ifndef CXXTEST_RUNNING +#define CXXTEST_RUNNING +#endif + +#define _CXXTEST_HAVE_STD +#include <cxxtest/TestListener.h> +#include <cxxtest/TestTracker.h> +#include <cxxtest/TestRunner.h> +#include <cxxtest/RealDescriptions.h> +#include <cxxtest/ErrorPrinter.h> + +int main() { + return CxxTest::ErrorPrinter().run(); +} +#include "test-model-info.hh" + +static test_model_cache_info suite_test_model_cache_info; + +static CxxTest::List Tests_test_model_cache_info = { 0, 0 }; +CxxTest::StaticSuiteDescription suiteDescription_test_model_cache_info( "test-model-info.hh", 111, "test_model_cache_info", suite_test_model_cache_info, Tests_test_model_cache_info ); + +static class TestDescription_test_model_cache_info_test_cache_life_cycle : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_cache_info_test_cache_life_cycle() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 115, "test_cache_life_cycle" ) {} + void runTest() { suite_test_model_cache_info.test_cache_life_cycle(); } +} testDescription_test_model_cache_info_test_cache_life_cycle; + +static class TestDescription_test_model_cache_info_test_cache_life_cycle_status : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_cache_info_test_cache_life_cycle_status() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 124, "test_cache_life_cycle_status" ) {} + void runTest() { suite_test_model_cache_info.test_cache_life_cycle_status(); } +} testDescription_test_model_cache_info_test_cache_life_cycle_status; + +static class TestDescription_test_model_cache_info_test_cache_unique_entries : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_cache_info_test_cache_unique_entries() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 137, "test_cache_unique_entries" ) {} + void runTest() { suite_test_model_cache_info.test_cache_unique_entries(); } +} testDescription_test_model_cache_info_test_cache_unique_entries; + +static test_model_info suite_test_model_info; + +static CxxTest::List Tests_test_model_info = { 0, 0 }; +CxxTest::StaticSuiteDescription suiteDescription_test_model_info( "test-model-info.hh", 154, "test_model_info", suite_test_model_info, Tests_test_model_info ); + +static class TestDescription_test_model_info_test_get_non_existent_model : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_get_non_existent_model() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 158, "test_get_non_existent_model" ) {} + void runTest() { suite_test_model_info.test_get_non_existent_model(); } +} testDescription_test_model_info_test_get_non_existent_model; + +static class TestDescription_test_model_info_test_get_non_existent_info : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_get_non_existent_info() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 165, "test_get_non_existent_info" ) {} + void runTest() { suite_test_model_info.test_get_non_existent_info(); } +} testDescription_test_model_info_test_get_non_existent_info; + +static class TestDescription_test_model_info_test_get_info_from_loaded_cache : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_get_info_from_loaded_cache() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 171, "test_get_info_from_loaded_cache" ) {} + void runTest() { suite_test_model_info.test_get_info_from_loaded_cache(); } +} testDescription_test_model_info_test_get_info_from_loaded_cache; + +static class TestDescription_test_model_info_test_get_existing_model : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_get_existing_model() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 186, "test_get_existing_model" ) {} + void runTest() { suite_test_model_info.test_get_existing_model(); } +} testDescription_test_model_info_test_get_existing_model; + +static class TestDescription_test_model_info_test_default_values : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_default_values() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 195, "test_default_values" ) {} + void runTest() { suite_test_model_info.test_default_values(); } +} testDescription_test_model_info_test_default_values; + +static class TestDescription_test_model_info_test_profile_equality_values : public CxxTest::RealTestDescription { +public: + TestDescription_test_model_info_test_profile_equality_values() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 207, "test_profile_equality_values" ) {} + void runTest() { suite_test_model_info.test_profile_equality_values(); } +} testDescription_test_model_info_test_profile_equality_values; + +#include <cxxtest/Root.cpp> diff --git a/backend/tests/test-model-info.hh b/backend/tests/test-model-info.hh new file mode 100644 index 0000000..1796b03 --- /dev/null +++ b/backend/tests/test-model-info.hh @@ -0,0 +1,241 @@ +// test-model-info.hh -- test suite for model info objects +// Copyright (C) 2010 SEIKO EPSON CORPORATION +// +// License: GPLv2+|iscan +// Authors: AVASYS CORPORATION +// +// This file is part of Image Scan!'s SANE backend test suite. +// +// Image Scan!'s SANE backend test suite 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 ought to have received a copy of the GNU General Public License +// along with this package. If not, see <http://www.gnu.org/licenses/>. +// +// +// Linking Image Scan!'s SANE backend statically or dynamically with +// other modules is making a combined work based on this SANE backend. +// Thus, the terms and conditions of the GNU General Public License +// cover the whole combination. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// SANE frontends that communicate with Image Scan!'s SANE backend +// solely through the SANE Application Programming Interface, +// regardless of the license terms of these SANE frontends, and to +// copy and distribute the resulting combined work under terms of your +// choice, provided that every copy of the combined work is +// accompanied by a complete copy of the source code of Image Scan!'s +// SANE backend (the version of Image Scan!'s SANE backend used to +// produce the combined work), being distributed under the terms of +// the GNU General Public License plus this exception. An independent +// module is a module which is not derived from or based on Image +// Scan!'s SANE backend. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// independent modules that communicate with Image Scan!'s SANE +// backend solely through the "Interpreter" interface, regardless of +// the license terms of these independent modules, and to copy and +// distribute the resulting combined work under terms of your choice, +// provided that every copy of the combined work is accompanied by a +// complete copy of the source code of Image Scan!'s SANE backend (the +// version of Image Scan!'s SANE backend used to produce the combined +// work), being distributed under the terms of the GNU General Public +// License plus this exception. An independent module is a module +// which is not derived from or based on Image Scan!'s SANE backend. +// +// Note that people who make modified versions of Image Scan!'s SANE +// backend are not obligated to grant special exceptions for their +// modified versions; it is their choice whether to do so. The GNU +// General Public License gives permission to release a modified +// version without this exception; this exception also makes it +// possible to release a modified version which carries forward this +// exception. + + +#ifndef included_test_model_info_hh +#define included_test_model_info_hh + +#ifndef __cplusplus +#error "This is a C++ include file. Use a C++ compiler to compile" +#error "code that includes this file." +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "../model-info.h" +#include <cxxtest/TestSuite.h> + +#include <cstdlib> +#include <cstring> +#include <string> + +#include "../list.h" +#include "../message.h" +#include "../get-infofile.h" + +struct base +{ + std::string dir; + void *cache; + + void setUp (void) + { + // Log at least broken promises and unmet requirements. + setenv ("SANE_DEBUG_EPKOWA", "FATAL", false); + msg_init (); + + char *srcdir = getenv ("srcdir"); + dir = (srcdir ? srcdir : "."); + + cache = NULL; + } + + void tearDown (void) + { + cache = model_info_cache_exit (cache); + promise (!cache); + } +}; + +class test_model_cache_info : public CxxTest::TestSuite, public base +{ +public: + + void test_cache_life_cycle (void) + { + cache = model_info_cache_init (dir.c_str (), NULL); + TS_ASSERT (cache); + + cache = model_info_cache_exit (cache); + TS_ASSERT (!cache); + } + + void test_cache_life_cycle_status (void) + { + SANE_Status status = SANE_STATUS_NO_MEM; + + cache = model_info_cache_init (dir.c_str (), &status); + TS_ASSERT (cache); + + TS_ASSERT (SANE_STATUS_NO_MEM != status); + + cache = model_info_cache_exit (cache); + TS_ASSERT (!cache); + } + + void test_cache_unique_entries (void) + { + cache = model_info_cache_init (dir.c_str (), NULL); + TS_ASSERT (cache); + + for (int i = 0; i < 10; ++i) { + model_info_cache_get_info ("GT-X970", NULL); + model_info_cache_get_info ("ES-H300", NULL); + } + TS_ASSERT_EQUALS (2, list_size (static_cast<list *> (cache))); + } + +private: + void setUp (void) { base::setUp (); } + void tearDown (void) { base::setUp (); } +}; + +class test_model_info : public CxxTest::TestSuite, public base +{ +public: + + void test_get_non_existent_model (void) + { + char *model = model_info_cache_get_model (bad_fw_name); + TS_ASSERT_EQUALS (std::string (model), std::string (bad_fw_name)); + free (model); + } + + void test_get_non_existent_info (void) + { + const void *info = model_info_cache_get_info (bad_fw_name, NULL); + TS_ASSERT (info); + } + + void test_get_info_from_loaded_cache (void) + { + // load cache + model_info_cache_get_info (bad_fw_name, NULL); + model_info_cache_get_info ("GT-X970", NULL); // XML data + model_info_cache_get_info ("ES-H300", NULL); // XML data + + // get info + const void *info = model_info_cache_get_info ("GT-X970", NULL); + TS_ASSERT (info); + TS_ASSERT (model_info_cache_get_info ("ES-H300", NULL)); + TS_ASSERT (model_info_cache_get_info (bad_fw_name, NULL)); + TS_ASSERT_EQUALS (info, model_info_cache_get_info ("GT-X970", NULL)); + } + + void test_get_existing_model (void) + { + setenv ("TZ", "", true); // force UTC, i.e. overseas model name + + char *model = model_info_cache_get_model ("ES-H300"); + TS_ASSERT_EQUALS (std::string ("GT-2500"), std::string (model)); + free (model); + } + + void test_default_values (void) + { + const void *p = model_info_cache_get_info (bad_fw_name, NULL); + + const char *name = model_info_get_name (p); + TS_ASSERT_EQUALS (std::string (bad_fw_name), std::string (name)); + + const EpsonScanHard profile = model_info_get_profile (p); + TS_ASSERT_SAME_DATA (profile, &epson_scan_hard[0], + sizeof (profile)); + } + + void test_profile_equality_values (void) + { + // Identical models but for their fw_names. + const void *p1 = model_info_cache_get_info ("GT-10000", NULL); + const void *p2 = model_info_cache_get_info ("ES-6000", NULL); + + TS_ASSERT_DIFFERS (p1, p2); + TS_ASSERT_SAME_DATA (model_info_get_profile (p1), + model_info_get_profile (p2), + sizeof (const EpsonScanHard)); + } + +private: + + // A firmware name that is guaranteed not to be used. + static const char *bad_fw_name; + + void setUp (void) + { + base::setUp (); + cache = model_info_cache_init (dir.c_str (), NULL); + TS_ASSERT (cache); + // The ESC/I spec has a 16 byte limit on the F/W name. + TS_ASSERT (16 < strlen (bad_fw_name)); + } + + void tearDown (void) + { + base::tearDown (); + } +}; + +const char * test_model_info::bad_fw_name = " __ BAD F/W NAME __ "; + +#endif /* !defined (included_test_model_info_hh) */ diff --git a/backend/tests/test-net-obj.cc b/backend/tests/test-net-obj.cc new file mode 100644 index 0000000..c43610d --- /dev/null +++ b/backend/tests/test-net-obj.cc @@ -0,0 +1,35 @@ +/* Generated file, do not edit */ + +#ifndef CXXTEST_RUNNING +#define CXXTEST_RUNNING +#endif + +#include <cxxtest/TestListener.h> +#include <cxxtest/TestTracker.h> +#include <cxxtest/TestRunner.h> +#include <cxxtest/RealDescriptions.h> +#include <cxxtest/ErrorPrinter.h> + +int main() { + return CxxTest::ErrorPrinter().run(); +} +#include "test-net-obj.hh" + +static test_net_obj suite_test_net_obj; + +static CxxTest::List Tests_test_net_obj = { 0, 0 }; +CxxTest::StaticSuiteDescription suiteDescription_test_net_obj( "test-net-obj.hh", 88, "test_net_obj", suite_test_net_obj, Tests_test_net_obj ); + +static class TestDescription_test_net_obj_test_lifecycle : public CxxTest::RealTestDescription { +public: + TestDescription_test_net_obj_test_lifecycle() : CxxTest::RealTestDescription( Tests_test_net_obj, suiteDescription_test_net_obj, 92, "test_lifecycle" ) {} + void runTest() { suite_test_net_obj.test_lifecycle(); } +} testDescription_test_net_obj_test_lifecycle; + +static class TestDescription_test_net_obj_test_missing_program : public CxxTest::RealTestDescription { +public: + TestDescription_test_net_obj_test_missing_program() : CxxTest::RealTestDescription( Tests_test_net_obj, suiteDescription_test_net_obj, 100, "test_missing_program" ) {} + void runTest() { suite_test_net_obj.test_missing_program(); } +} testDescription_test_net_obj_test_missing_program; + +#include <cxxtest/Root.cpp> diff --git a/backend/tests/test-net-obj.hh b/backend/tests/test-net-obj.hh new file mode 100644 index 0000000..ef0c557 --- /dev/null +++ b/backend/tests/test-net-obj.hh @@ -0,0 +1,126 @@ +// test-net-obj.hh -- test suite for network objects +// Copyright (C) 2008 SEIKO EPSON CORPORATION +// +// License: GPLv2+|iscan +// Authors: AVASYS CORPORATION +// +// This file is part of Image Scan!'s SANE backend test suite. +// +// Image Scan!'s SANE backend test suite 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 ought to have received a copy of the GNU General Public License +// along with this package. If not, see <http://www.gnu.org/licenses/>. +// +// +// Linking Image Scan!'s SANE backend statically or dynamically with +// other modules is making a combined work based on this SANE backend. +// Thus, the terms and conditions of the GNU General Public License +// cover the whole combination. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// SANE frontends that communicate with Image Scan!'s SANE backend +// solely through the SANE Application Programming Interface, +// regardless of the license terms of these SANE frontends, and to +// copy and distribute the resulting combined work under terms of your +// choice, provided that every copy of the combined work is +// accompanied by a complete copy of the source code of Image Scan!'s +// SANE backend (the version of Image Scan!'s SANE backend used to +// produce the combined work), being distributed under the terms of +// the GNU General Public License plus this exception. An independent +// module is a module which is not derived from or based on Image +// Scan!'s SANE backend. +// +// As a special exception, the copyright holders of Image Scan!'s SANE +// backend give you permission to link Image Scan!'s SANE backend with +// independent modules that communicate with Image Scan!'s SANE +// backend solely through the "Interpreter" interface, regardless of +// the license terms of these independent modules, and to copy and +// distribute the resulting combined work under terms of your choice, +// provided that every copy of the combined work is accompanied by a +// complete copy of the source code of Image Scan!'s SANE backend (the +// version of Image Scan!'s SANE backend used to produce the combined +// work), being distributed under the terms of the GNU General Public +// License plus this exception. An independent module is a module +// which is not derived from or based on Image Scan!'s SANE backend. +// +// Note that people who make modified versions of Image Scan!'s SANE +// backend are not obligated to grant special exceptions for their +// modified versions; it is their choice whether to do so. The GNU +// General Public License gives permission to release a modified +// version without this exception; this exception also makes it +// possible to release a modified version which carries forward this +// exception. + + +#ifndef included_test_net_obj_hh +#define included_test_net_obj_hh + +#ifndef __cplusplus +#error "This is a C++ include file. Use a C++ compiler to compile" +#error "code that includes this file." +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "../net-obj.h" + +#include <cxxtest/TestSuite.h> + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <unistd.h> + +#include "../message.h" + + +class test_net_obj : public CxxTest::TestSuite +{ +public: + + void test_lifecycle (void) + { + void *net = net_init (get_current_dir_name (), NULL); + TS_ASSERT (net); + net = net_exit (net); + TS_ASSERT (!net); + } + + void test_missing_program (void) + { + char dirname_template[] = "network-XXXXXX"; + char *dirname = mkdtemp (dirname_template); + TS_ASSERT (dirname); + + void *net = net_init (dirname, NULL); + TS_ASSERT (!net); + net = net_exit (net); + TS_ASSERT (!net); + + if (0 != rmdir (dirname)) + { + err_minor ("%s: %s", dirname, strerror (errno)); + } + } + +private: + + void setUp (void) + { + msg_init (); + } +}; + + +#endif /* !defined (included_test_net_obj_hh) */ diff --git a/backend/tests/xmltest-runner.sh b/backend/tests/xmltest-runner.sh new file mode 100755 index 0000000..8bc3383 --- /dev/null +++ b/backend/tests/xmltest-runner.sh @@ -0,0 +1,87 @@ +#! /bin/sh + +test_result=PASS + +run_test () { + ./xmltest "$@" + if test 0 = $?; then + echo "PASS: xmltest $@" + else + echo "FAIL: xmltest $@" + test_result=FAIL + fi +} + +run_test GT-X970 +run_test PM-A820 +run_test EP-210F +run_test GT-X970 ES-H300 +run_test GT-X970 LP-M5600 +run_test LP-M5600 PM-A820 +run_test PM-A820 GT-X970 +run_test GT-X970 GT-X970 +run_test LP-M5600 LP-M5600 +run_test EP-210F hoge +run_test GT-X970 hoge +run_test PM-A820 MAN +run_test EP-210F ES-H300 +run_test EP-210F LP-M5600 +run_test GT-X970 ES-H300 Perfection610 +run_test GT-X970 ES-H300 PM-A820 +run_test GT-X970 ES-H300 MAN +run_test GT-X970 LP-M5600 Perfection610 +run_test GT-X970 LP-M5600 CX4600 +run_test GT-X970 LP-M5600 EP-210F +run_test ES-H300 hoge GT-X970 +run_test ES-H300 hoge PM-A820 +run_test ES-H300 hoge EP-210F +run_test PM-A820 LP-M5600 CX4600 +run_test PM-A820 LP-M5600 GT-X970 +run_test PM-A820 LP-M5600 MAN +run_test LP-M5600 Perfection610 GT-X970 +run_test LP-M5600 Perfection610 PM-A820 +run_test LP-M5600 Perfection610 EP-210F +run_test LP-M5600 EP-210F GT-X970 +run_test LP-M5600 EP-210F CX4600 +run_test LP-M5600 EP-210F MAN +run_test EP-210F MAN hoge +run_test EP-210F MAN ES-H300 +run_test EP-210F MAN LP-M5600 +run_test EP-210F GT-X970 ES-H300 +run_test EP-210F GT-X970 PM-A820 +run_test EP-210F GT-X970 hoge +run_test EP-210F CX4600 Perfection610 +run_test EP-210F CX4600 PM-A820 +run_test EP-210F CX4600 hoge +run_test ES-H300 ES-H300 ES-H300 +run_test PM-A820 PM-A820 PM-A820 +run_test GT-X970 GT-X970 CX4600 +run_test Perfection610 Perfection610 MAN +run_test Perfection610 Perfection610 GT-X970 +run_test LP-M5600 LP-M5600 GT-X970 +run_test LP-M5600 LP-M5600 PM-A820 +run_test CX4600 CX4600 hoge +run_test EP-210F EP-210F ES-H300 +run_test EP-210F EP-210F LP-M5600 +run_test EP-210F EP-210F MAN +run_test ES-H300 GT-X970 GT-X970 +run_test ES-H300 PM-A820 PM-A820 +run_test GT-X970 hoge hoge +run_test CX4600 Perfection610 Perfection610 +run_test PM-A820 LP-M5600 LP-M5600 +run_test LP-M5600 MAN MAN +run_test hoge GT-X970 GT-X970 +run_test MAN PM-A820 PM-A820 +run_test hoge EP-210F EP-210F +run_test GT-X970 CX4600 GT-X970 +run_test GT-X970 ES-H300 GT-X970 +run_test GT-X970 EP-210F GT-X970 +run_test PM-A820 ES-H300 PM-A820 +run_test LP-M5600 CX4600 LP-M5600 +run_test PM-A820 MAN PM-A820 +run_test hoge GT-X970 hoge +run_test EP-210F CX4600 EP-210F +run_test MAN hoge MAN + +test PASS = "$test_result" +exit $? diff --git a/backend/tests/xmltest.c b/backend/tests/xmltest.c new file mode 100644 index 0000000..0f3a277 --- /dev/null +++ b/backend/tests/xmltest.c @@ -0,0 +1,247 @@ +/* xmltest.c -- simple model cache info checker + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xmltest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include "../list.h" +#include "../message.h" +#include "../model-info.h" +#include "../utils.h" + +static bool check_cache_content (list *info); +static bool check_info_content (const _model_info_t *info); +static bool model_info_test_cmp (const _model_info_t *info, + const _model_info_t_test *reference); + +int +main (int argc, char** argv) +{ + bool pass = false; + list *cache = NULL; + SANE_Status status = SANE_STATUS_GOOD; + + /* Log at least broken promises and unmet requirements. + */ + setenv ("SANE_DEBUG_EPKOWA", "FATAL", false); + msg_init (); + + cache = (list *) model_info_cache_init (getenv ("srcdir"), &status); + if (!cache) + { + err_fatal ("cannot initialise model info cache (%s)", + sane_strstatus (status)); + return EXIT_FAILURE; + } + + while (--argc && ++argv) + { + const void *info = model_info_cache_get_info (*argv, &status); + if (!info) + { + err_major ("cannot get info for '%s' (%s)", *argv, + sane_strstatus (status)); + } + } + + pass = check_cache_content (cache); + cache = model_info_cache_exit (cache); + + return (pass ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/*! \brief Loops over all cache entries and checks each one of them. + */ +static bool +check_cache_content (list *cache) +{ + bool pass = true; + _model_info_t *info; + + list_reset (cache); + while ((info = list_next (cache))) + { + pass &= check_info_content (info); + } + return pass; +} + +/*! \brief Checks a cache entry against known good data. + * + * \note Unknown entries are skipped. + */ +static bool +check_info_content (const _model_info_t *info) +{ + if (0 == strcmp_c (info->fw_name, gt_x970.fw_name)) + return model_info_test_cmp (info, >_x970); + + if (0 == strcmp_c (info->fw_name, es_h300.fw_name)) + return model_info_test_cmp (info, &es_h300); + + if (0 == strcmp_c (info->fw_name, perfection_610.fw_name)) + return model_info_test_cmp (info, &perfection_610); + + if (0 == strcmp_c (info->fw_name, lp_m5600.fw_name)) + return model_info_test_cmp (info, &lp_m5600); + + if (0 == strcmp_c (info->fw_name, pm_a820.fw_name)) + return model_info_test_cmp (info, &pm_a820); + + if (0 == strcmp_c (info->fw_name, cx_4600.fw_name)) + return model_info_test_cmp (info, &cx_4600); + + /* cannot do remaining tests */ + printf (" SKIP: unexpected fw_name (%s)\n", info->fw_name); + return true; +} + +/*! \brief Compares \a info against a \a reference. + */ +static bool +model_info_test_cmp (const _model_info_t *info, + const _model_info_t_test *reference) +{ + bool pass = true; + int i, j; + + require (info && reference); + + /* Compare model names */ + if (0 != strcmp_c (info->overseas, reference->overseas)) + { + pass = false; + printf ("FAIL: overseas -> %s != %s\n", info->overseas, + reference->overseas); + } + + if (0 != strcmp_c (info->japan, reference->japan)) + { + pass = false; + printf ("FAIL: japan -> %s != %s\n", info->japan, reference->japan); + } + + /* Compare color profiles */ + for (i = 0; i < 4; i++) + { + for (j = 0; j < 9; j++) + { + if (info->profile->color_profile[i][j] + != reference->profile.color_profile[i][j]) + { + pass = false; + printf ("FAIL: profile[%i][%i] -> %f != %f\n", i, j, + info->profile->color_profile[i][j], + reference->profile.color_profile[i][j]); + } + } + } + + /* Compare custom command entries */ + if (info->command->set_focus_position + != reference->command.set_focus_position) + { + pass = false; + printf ("FAIL: focus -> %d != %d\n", info->command->set_focus_position, + reference->command.set_focus_position); + } + + if (info->command->feed != reference->command.feed) + { + pass = false; + printf ("FAIL: feed -> %d != %d\n", info->command->feed, + reference->command.feed); + } + + if (info->command->eject != reference->command.eject) + { + pass = false; + printf ("FAIL: eject -> %d != %d\n", info->command->eject, + reference->command.eject); + } + + if (info->command->lock != reference->command.lock) + { + pass = false; + printf ("FAIL: lock -> %d != %d\n", info->command->lock, + reference->command.lock); + } + + if (info->command->unlock != reference->command.unlock) + { + pass = false; + printf ("FAIL: unlock -> %d != %d\n", info->command->unlock, + reference->command.lock); + } + + return pass; +} diff --git a/backend/tests/xmltest.h b/backend/tests/xmltest.h new file mode 100644 index 0000000..02404e1 --- /dev/null +++ b/backend/tests/xmltest.h @@ -0,0 +1,190 @@ +/* xmltest.c -- simple model cache info checker + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_xmltest_h +#define included_xmltest_h + +#include "../get-infofile.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + char *fw_name; /* key for _model_info_cache_get_info */ + char *overseas; /* model name */ + char *japan; /* model name */ + char *name; /* points to one of overseas, japan or + * fw_name, never NULL */ + scan_command_t *command; /* command customisation info */ + EpsonScanHard profile; /* colour profiles */ + + bool from_file; /* origin of our data, used to control + * which members need to be free()d at + * destruction time */ + +} _model_info_t; + +typedef struct +{ + char *fw_name; /* key for _model_info_cache_get_info */ + char *overseas; /* model name */ + char *japan; /* model name */ + + scan_command_t command; /* command customisation info */ + EpsonScanHardRec profile; /* colour profiles */ + +} _model_info_t_test; + +const _model_info_t_test gt_x970 = { + "GT-X970", + NULL, + NULL, + + {0x01, 0, 0, 0xFF, true, true}, + {0x87, + {{1.1978,-0.1417,-0.0561,-0.0852, 1.1610,-0.0758,-0.0395,-0.3212, 1.3607}, + {1.0000, 0.0009,-0.0009,-0.1268, 1.0523, 0.0745,-0.0075,-0.0873, 1.0948}, + {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}, + {1.4475,-0.3957,-0.0518,-0.0138, 1.0644,-0.0506,-0.0199,-0.2050, 1.2249}}}, + +}; + +const _model_info_t_test es_h300 = { + "ES-H300", + "GT-2500", + "ES-H300", + + {0x01, 0, 0, 0xFF, false, false}, + {0x87, + {{1.0359,-0.0146,-0.0213,-0.0752, 1.0963,-0.0211,-0.0456,-0.3238, 1.3693}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}}}, + +}; + +const _model_info_t_test perfection_610 = { + "Perfection610", + "Perfection 610", + NULL, + + {0x01, 0, 0, 0xFF, false, false}, + {0x87, + {{1.1442,-0.0705,-0.0737,-0.0702, 1.1013,-0.0311,-0.0080,-0.3588, 1.3668}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}}}, + +}; + +const _model_info_t_test lp_m5600 = { + "LP-M5600", + NULL, + "LP-M5600", + + {0x01, 0, 0x19, 0xFF, false, false}, + {0x87, + {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}}}, + +}; + +const _model_info_t_test pm_a820 = { + "PM-A820", + "Stylus Photo RX560/RX580/RX590", + "PM-A820", + + {0x01, 0, 0, 0xFF, false, false}, + {0x87, + {{0.9533, 0.0885,-0.0418, 0.0033, 1.0627,-0.0660,-0.0137,-0.1904, 1.2041}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}}}, + +}; + +const _model_info_t_test cx_4600 = { + "CX4600", + "Stylus CX4500/CX4600", + NULL, + + {0x01, 0, 0, 0xFF, false, false}, + {0x87, + {{0.9828, 0.0924,-0.0752, 0.0255, 1.151,-0.1765, 0.0049,-0.325, 1.3201}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 0, 0, 0, 1}}}, + +}; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !defined (included_xmltest_h) */ diff --git a/backend/timing.c b/backend/timing.c new file mode 100644 index 0000000..1b370ce --- /dev/null +++ b/backend/timing.c @@ -0,0 +1,188 @@ +/* timing.c -- optional support for run-time time stamp collection + * Copyright (C) 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "timing.h" + +#if ENABLE_TIMING + +#include <math.h> +#include <string.h> + +size_t time_pass_count = 0; + +struct time_interval_ time_scan; +struct time_interval_ time_pass[TIME_PASS_MAX]; + +static const long NANOS_PER_SEC = 1000000000; + +static void +time_compute_span (struct time_interval_ *m) +{ + m->span.t.tv_nsec = m->stop.t.tv_nsec - m->start.t.tv_nsec; + if (0 > m->span.t.tv_nsec) + { + m->span.t.tv_nsec += NANOS_PER_SEC; + m->stop.t.tv_sec -= 1; + } + m->span.t.tv_sec = m->stop.t.tv_sec - m->start.t.tv_sec; + + m->span.is_valid = m->start.is_valid && m->stop.is_valid; +} + +void +time_clear (void) +{ + memset (&time_scan, 0, sizeof (time_scan)); + memset ( time_pass, 0, sizeof (*time_pass) * TIME_PASS_MAX); +} + +static double +time_stamp_to_double (const struct time_stamp_ *ts) +{ + return ((double) ts->t.tv_nsec) / NANOS_PER_SEC + ts->t.tv_sec; +} + +static void +time_fprintf_interval (FILE *stream, const struct time_interval_ *ti, + const char *prefix) +{ + char str[512]; + + if (ti->span.is_valid) + snprintf (str, num_of (str), "%s: %f (start: %f, stop: %f)", prefix, + time_stamp_to_double (&ti->span), + time_stamp_to_double (&ti->start), + time_stamp_to_double (&ti->stop)); + else if (ti->start.is_valid) + snprintf (str, num_of (str), "%s: --- (start: %f, stop: ---)", prefix, + time_stamp_to_double (&ti->start)); + else if (ti->stop.is_valid) + snprintf (str, num_of (str), "%s: --- (start: ---, stop: %f)", prefix, + time_stamp_to_double (&ti->stop)); + else + snprintf (str, num_of (str), "%s: --- (start: ---, stop: ---)", prefix); + + fprintf (stream, "%s\n", str); +} + +/* There is a script in utils/ that can combine the results of + multiple scans (in one or more SANE frontend sessions) into + a single CSV file. + */ +void +time_stats (size_t count) +{ + size_t i, n = 0; + double t, sum = 0, sum_sq = 0; + + fprintf (stderr, "\f\n"); /* start a new form */ + fprintf (stderr, "Per pass timing data (in seconds)\n"); + for (i = 0; i < count && i < TIME_PASS_MAX; ++i) + { + time_compute_span (&time_pass[i]); + time_fprintf_interval (stderr, &time_pass[i], "pass"); + } + fprintf (stderr, "\n"); + + fprintf (stderr, "Scan timing data (in seconds)\n"); + time_compute_span (&time_scan); + time_fprintf_interval (stderr, &time_scan, "scan"); + fprintf (stderr, "\n"); + + if (time_scan.span.is_valid) + { + if (0 < count) + fprintf (stderr, "Scan avg: %f s/pass\n", + time_stamp_to_double (&time_scan.span) / count); + } + + for (i = 0; i < count && i < TIME_PASS_MAX; ++i) + { + if (time_pass[i].span.is_valid) + { + ++n; + t = time_stamp_to_double (&time_pass[i].span); + sum += t; + sum_sq += (t * t); + } + } + + if (0 != n) + { + fprintf (stderr, "Pass sum: %f s, %zd passes\n", sum, n); + fprintf (stderr, "Pass avg: %f", sum / n); + if (1 < n) /* add standard deviation */ + { + fprintf (stderr, " (+/- %f)", + sqrt ((n * sum_sq - sum * sum) / n / (n-1))); + } + fprintf (stderr, " s/pass\n"); + } +} + +#endif /* ENABLE_TIMING */ diff --git a/backend/timing.h b/backend/timing.h new file mode 100644 index 0000000..8181cda --- /dev/null +++ b/backend/timing.h @@ -0,0 +1,119 @@ +/* timing.h -- optional support for run-time time stamp collection + * Copyright (C) 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef timing_h_included +#define timing_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#ifndef ENABLE_TIMING +#define ENABLE_TIMING 0 +#endif + +#define TIME_PASS_MAX 100 +extern size_t time_pass_count; + +#if !ENABLE_TIMING /* turn API calls into no-ops */ + +#define time_clear() do {} while (0) +#define time_stamp(interval,end) do {} while (0) +#define time_stats(count) do {} while (0) + +#else /* do something useful */ + +#include <time.h> +#include "defines.h" + +struct time_stamp_ +{ + bool is_valid; + struct timespec t; +}; + +struct time_interval_ +{ + struct time_stamp_ start; + struct time_stamp_ stop; + struct time_stamp_ span; +}; + +extern struct time_interval_ time_scan; +extern struct time_interval_ time_pass[TIME_PASS_MAX]; + +#define time_stamp(interval,end) \ + do { \ + interval.end.is_valid = (0 == clock_gettime (CLOCK_MONOTONIC, \ + &(interval.end.t))); \ + } while (0) + +void time_clear (void); +void time_stats (size_t count); + +#endif /* ENABLE_TIMING */ + +#endif /* !defined (timing_h_included) */ diff --git a/backend/utils.c b/backend/utils.c new file mode 100644 index 0000000..e43afc1 --- /dev/null +++ b/backend/utils.c @@ -0,0 +1,334 @@ +/* utils.c -- assorted utility functions and macros + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "utils.h" + +#include <errno.h> +#include <string.h> +#include <time.h> /* nanosleep */ + + +/*! Creates a string of 2-character hexadecimal values from a NUL + terminated character string. + + The implementation acquires the memory needed for the result and + the caller is responsible for releasing it. + + \return a pointer to the encoded string or \c NULL if the memory + required could not be acquired + */ +char * +fw_name_to_hex (const char *fw_name) /* '\0' terminated */ +{ + char *rv, *p; + + log_call(); + + if (!fw_name) return NULL; /* guard clause */ + + p = rv = t_malloc (2 * strlen (fw_name) + 1, char); + if (!rv) return NULL; + + while ('\0' != *fw_name) + { + sprintf (p, "%02x", *fw_name); + p += 2; + ++fw_name; + } + *p = '\0'; + return rv; +} + +/*! Sets up a properly initialised resolution info object. + */ +void +init_resolution_info (resolution_info *self, u_char *data) +{ + SANE_Bool (*cond) (const u_char *) = NULL; + size_t step = 0; + + if (!self) return; + + self->last = 0; /* set defaults */ + self->size = -1; + self->list = NULL; + self->deep = SANE_TRUE; + + if (!data) return; /* act like a default constructor */ + + self->size = 0; + self->list = t_realloc (NULL, self->size + 1, SANE_Word); + + if (!self->list) + { + err_major ("%s", strerror (ENOMEM)); + self->size = -1; + return; + } + + if ('R' == data[0]) /* ESC I data block */ + { + cond = resolution_info_ESC_I_cond; + step = 3; + } + else /* ESC i data block, hopefully */ + { + cond = resolution_info_ESC_i_cond; + step = 2; + } + + while (cond (data)) + { + void *p = self->list; + + self->size++; + self->list = t_realloc (p, self->size + 1, SANE_Word); + if (!self->list) + { + delete (p); + + err_major ("%s", strerror (ENOMEM)); + self->size = -1; + return; + } + self->list[self->size] = data[step - 1] << 8 | data[step - 2]; + data += step; + log_info ("resolution: %d dpi", self->list[self->size]); + } + self->list[0] = self->size; +} + +/*! Releases resources held by \a self and resets it to default state. + */ +void +free_resolution_info (resolution_info *self) +{ + if (!self) return; + + if (self->deep) + delete (self->list); + + init_resolution_info (self, NULL); +} + +/*! Makes an optionally deep copy of \a src to \a dest. + + Any resources held by \a dest will be returned to the system. + */ +SANE_Status +copy_resolution_info (resolution_info *dest, const resolution_info *src, + SANE_Bool deep) +{ + if (!dest || !src) return SANE_STATUS_INVAL; + + require (!src->list || src->size == src->list[0]); + + if (deep && src->list) /* copy resolution list */ + { + size_t size = (src->size + 1) * sizeof (SANE_Word); + SANE_Word *list = t_malloc (size, SANE_Word); + + if (!list) return SANE_STATUS_NO_MEM; + + memcpy (list, src->list, size); + + if (dest->deep) + delete (dest->list); + dest->list = list; + } + else /* just refer to it */ + { + if (dest->deep) + delete (dest->list); + dest->list = src->list; + } + + dest->last = src->last; + dest->size = src->size; + dest->deep = deep; + + promise (!dest->list || dest->size == dest->list[0]); + + return SANE_STATUS_GOOD; +} + + +void +_update_ranges (const device *hw, extension *src) +{ + require (hw); + require (src); + + src->x_range.min = 0; + src->x_range.max = SANE_FIX (src->max_x * MM_PER_INCH / hw->base_res); + src->x_range.quant = 0; + + src->y_range.min = 0; + src->y_range.max = SANE_FIX (src->max_y * MM_PER_INCH / hw->base_res); + src->y_range.quant = 0; + + if (!hw->cmd->request_identity2) return; + + /* correct for color shuffle offsets */ + src->y_range.max = SANE_FIX ((src->max_y - 2 * hw->max_line_distance) + * MM_PER_INCH / hw->base_res); +} + + +/*! Convenience type to hold document size information. + */ +struct _doc_size_info +{ + const double width; + const double height; + const char *label; +}; + +/*! Document size information for known sizes. + */ +static const struct _doc_size_info +doc_size[] = { + /* second byte bit flag values */ + { 182.00, 257.00, "B5V"}, + { 257.00, 182.00, "B5H"}, + { 148.00, 210.00, "A5V"}, + { 210.00, 148.00, "A5H"}, + { 184.15, 266.70, "EXV"}, + { 266.70, 184.15, "EXH"}, + { 0 , 0 , "RSV"}, /* reserved */ + { 0 , 0 , "UNK"}, /* unknown */ + /* first byte bit flag values */ + { 297.00, 420.00, "A3V"}, + { 279.40, 431.80, "WLT"}, + { 257.00, 364.00, "B4V"}, + { 215.90, 355.60, "LGV"}, + { 210.00, 297.00, "A4V"}, + { 297.00, 210.00, "A4H"}, + { 215.90, 279.40, "LTV"}, + { 279.40, 215.90, "LTH"}, +}; + + +void +_update_doc_size (extension *src, uint16_t value) +{ + const uint16_t DOC_MASK = ~0x0200; + + size_t i = 0; + + require (src); + + if ((DOC_MASK & value) != value) + { + err_minor ("clearing reserved bit flags to match spec"); + value &= DOC_MASK; + } + + if (0 == value) /* size detection not supported */ + { + src->doc_x = 0; + src->doc_y = 0; + return; + } + + while (!(0x8000 & value) && (num_of (doc_size) > i)) + { + value = value << 1; + ++i; + } + + if (0 != strcmp_c ("UNK", doc_size[i].label)) + { + src->doc_x = doc_size[i].width; + src->doc_y = doc_size[i].height; + } + else + { + src->doc_x = SANE_UNFIX (src->x_range.max); + src->doc_y = SANE_UNFIX (src->y_range.max); + } + + value = value << 1; + if (0 != value) + { + err_minor ("device detected multiple document sizes!\n"); + } + + log_info ("detected document size: %s (%.2fmm x %.2fmm)", + doc_size[i].label, src->doc_x, src->doc_y); +} + +int +microsleep (size_t usec) +{ + struct timespec ts; + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + + return nanosleep (&ts, NULL); +} diff --git a/backend/utils.h b/backend/utils.h new file mode 100644 index 0000000..7b1e18f --- /dev/null +++ b/backend/utils.h @@ -0,0 +1,199 @@ +/* utils.h -- assorted utility functions and macros + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef utils_h_included +#define utils_h_included + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "device.h" + + +/*! \brief Encodes a fw_name as a string of hexadecimals. + */ +char * fw_name_to_hex (const char *fw_name); + +/*! \brief Converts a buffer to an unsigned 32-bit integer. + */ +inline static uint32_t +buf_to_uint32 (const byte *buf) +{ + return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; +} + +/*! \brief Converts a buffer to an unsigned 16-bit integer. + */ +inline static uint16_t +buf_to_uint16 (const byte *buf) +{ + return buf[1] << 8 | buf[0]; +} + +/*! \brief Converts an unsigned 32-bit integer to a buffer. + */ +inline static void +uint32_to_buf (uint32_t val, byte *buf) +{ + if (!buf) return; + + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; + buf[3] = val >> 24; +} + +/*! \brief Converts an unsigned 16-bit integer to a buffer. + */ +inline static void +uint16_to_buf (uint16_t val, byte *buf) +{ + if (!buf) return; + + buf[0] = val; + buf[1] = val >> 8; +} + + +/* Scan area related queries and computations. + */ + +/*! \brief Tells whether an extension supports document size detection. + */ +inline static SANE_Bool +has_size_check_support (const extension *src) +{ + return src->has_size_check; +} + +/*! \brief Recomputes an extension's scan area dimensions (in mm). + \hideinitializer + */ +#define update_ranges(hw,src) _update_ranges (hw, (extension *) src) + +void _update_ranges (const device *hw, extension *src); + +/*! \brief Re-establishes the detected document size. + \hideinitializer + */ +#define update_doc_size(src,value) _update_doc_size ((extension *) src, value) + +void _update_doc_size (extension *src, uint16_t value); + + +/* Resolution information handlers. + */ + +void init_resolution_info (resolution_info *self, byte *data); +void free_resolution_info (resolution_info *self); +SANE_Status copy_resolution_info (resolution_info *dest, + const resolution_info *src, SANE_Bool deep); + +/*! \brief Tells whether we are looking at a resolution in an ESC I reply. + */ +inline static SANE_Bool +resolution_info_ESC_I_cond (const u_char *data) +{ + return ('R' == data[0]); +} + +/*! \brief Tells whether we are looking at a resolution in an ESC i reply. + */ +inline static SANE_Bool +resolution_info_ESC_i_cond (const u_char *data) +{ + return (0 != data[0] || 0 != data[1]); +} + +/*! \brief A wrapper around strcmp that checks for NULL strings + */ +inline static int +strcmp_c (const char *s1, const char *s2) +{ + if (!s1 && !s2) return 0; + if (s1 && !s2) return 1; + if (!s1 && s2) return -1; + + return strcmp (s1, s2); +} + +/*! \brief A wrapper around strncmp that checks for NULL strings + */ +inline static int +strncmp_c (const char *s1, const char *s2, size_t n) +{ + if (!s1 && !s2) return 0; + if (s1 && !s2) return 1; + if (!s1 && s2) return -1; + + return strncmp (s1, s2, n); +} + +int microsleep (size_t usec); + + +#endif /* !defined (utils_h_included) */ diff --git a/backend/xmlreader.c b/backend/xmlreader.c new file mode 100644 index 0000000..56e23e9 --- /dev/null +++ b/backend/xmlreader.c @@ -0,0 +1,378 @@ +/* xmlreader.c -- + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <time.h> + +#include "utils.h" +#include "get-infofile.h" +#include "xmlreader.h" + +typedef struct devices{ + char *firmware; + char *overseas; + char *japan; +}devices, *devicePtr; + +typedef struct commands{ + char *command_type; + char *command_level; +}commands, *commandPtr; + +static void default_profile_set(double color_profile[9]); + + +static void parseCommand(xmlNodePtr cur, scan_command_t* command); +static unsigned char parseStatus(char *status, char *name); + +char * +parseDevices(xmlNodePtr cur, char *name){ + devicePtr ret; + + char *region = NULL; + char *result = NULL; + + log_call(); + + ret = t_calloc (1, devices); + if(ret == NULL){ + err_major("out of memory"); + return NULL; + } + + cur = cur->xmlChildrenNode; + + while (cur != NULL) { + if (!xmlStrcmp(cur->name, (const xmlChar *) "firmware")) { + if(ret->firmware == NULL && strcmp(name, FIRMWARE) == 0){ + ret->firmware = (char *)xmlGetProp(cur, (const xmlChar *) "name"); + result = strdup(ret->firmware); + if(!ret->firmware){ + delete (ret); + return NULL; + } + break; + } + }else if (!xmlStrcmp(cur->name, (const xmlChar *) "model")) { + region = (char *)xmlGetProp(cur, (const xmlChar *) "region"); + if(region && strcmp(name, MODEL_JAPAN) == 0){ + if(strcmp(name, MODEL_JAPAN) == 0 && strcasecmp(region, "Japan") == 0){ + ret->japan = (char *)xmlGetProp(cur, (const xmlChar *) "name"); + result = strdup(ret->japan); + delete (ret->japan); + if(!result || isspace(result[0]) != 0 || strlen(result) == 0){ + delete (result); + delete (ret); + delete (region); + result = NULL; + err_minor("Model has no Name."); + } + } + break; + }else if(!region && strcmp(name, MODEL_OVERSEAS) == 0){ + ret->overseas = (char *)xmlGetProp(cur, (const xmlChar *) "name"); + result = strdup(ret->overseas); + delete(ret->overseas); + if(!result || isspace(result[0]) != 0 || strlen(result) == 0){ + delete (result); + delete (ret); + delete (region); + result = NULL; + err_minor("Model has no Name."); + } + break; + } + delete (region); + } + cur = cur->next; + } + + delete (region); + delete (ret); + + return result; +} + +EpsonScanHard +parseProfiles(xmlNodePtr cur){ + EpsonScanHard ret; + int i, j; + char *tmp; + char pmat[9][3] = {"rr", "rg", "rb", + "gr", "gg", "gb", + "br", "bg", "bb"}; + char *profile_type; + xmlNodePtr curtmp; + + log_call(); + + ret = t_calloc (1, EpsonScanHardRec); + if(ret == NULL){ + err_major("out of memory"); + return NULL; + } + + /*add default value*/ + for(i = 0; i < 4; i++){ + default_profile_set(ret->color_profile[i]); + } + + cur = curtmp = cur->xmlChildrenNode; + while(cur != NULL){ + if ((!xmlStrcmp(cur->name, (const xmlChar *) "profile"))){ + curtmp = cur; + profile_type = (char *)xmlGetProp(cur, (const xmlChar *) "type"); + + if(strcmp(profile_type, "reflective") == 0){ + i = 0; + }else if(strcmp(profile_type, "color negative") == 0){ + i = 1; + }else if(strcmp(profile_type, "monochrome negative") == 0){ + i = 2; + }else if(strcmp(profile_type, "positive") == 0){ + i = 3; + }else { + err_minor("profile of the wrong type."); + delete (profile_type); + delete (ret); + return NULL; + } + delete (profile_type); + + j = 0; + cur = cur->xmlChildrenNode; + while(cur != NULL){ + if(!xmlStrcmp(cur->name, (const xmlChar *)pmat[j])){ + tmp = (char*)xmlGetProp(cur, (const xmlChar *) "value"); + ret->color_profile[i][j] = atof(tmp); + delete (tmp); + j++; + } + cur = cur->next; + } + if(j != 9){ + err_minor("Value that is not sufficient exists."); + default_profile_set(ret->color_profile[i]); + } + } + cur = curtmp; + cur = curtmp = cur->next; + } + + return ret; +} + +static +void default_profile_set(double color_profile[9]){ + /*default value is assigned*/ + const double default_profile[9] = + {1.0000, 0.0000, 0.0000, + 0.0000, 1.0000, 0.0000, + 0.0000, 0.0000, 1.0000} ; + int j; + + for(j = 0; j < 9; j++){ + color_profile[j] = default_profile[j]; + } +} + +scan_command_t * +parseCommands_set(xmlNodePtr cur){ + commandPtr ret; + scan_command_t* command; + + log_call(); + + command = t_calloc (1, scan_command_t); + if(command == NULL){ + err_major("out of memory"); + return NULL; + } + + /*default value*/ + command->set_focus_position = 0xFF; + command->feed = 0xFF; + command->eject = 0xFF; + command->lock = false; + command->unlock = false; + + ret = t_calloc (1, commands); + if(ret == NULL){ + err_major("out of memory"); + delete (command); + return NULL; + } + + ret->command_type = (char *)xmlGetProp(cur, (const xmlChar *) "type"); + + ret->command_level = (char *)xmlGetProp(cur, (const xmlChar *) "level"); + + cur = cur->xmlChildrenNode; + while(cur != NULL){ + if ((!xmlStrcmp(cur->name, (const xmlChar *) "command"))){ + parseCommand(cur, command); + } + cur = cur->next; + } + + delete (ret->command_type); + delete (ret->command_level); + delete (ret); + + return command; +} + +static void +parseCommand(xmlNodePtr cur, scan_command_t* command) +{ + char *tmp; + char *status = NULL; + + status = (char *)xmlGetProp(cur, (const xmlChar *) "status"); + + if((tmp = (char *)xmlGetProp(cur, (const xmlChar *) "name")) != NULL){ + if(strcmp(tmp, "set_focus_position") == 0){ + command->set_focus_position = parseStatus(status, "set_focus_position"); + }else if(strcmp(tmp, "feed") == 0){ + command->feed = parseStatus(status, "feed"); + }else if(strcmp(tmp, "eject") == 0){ + command->eject = parseStatus(status, "eject"); + }else if(strcmp(tmp, "lock") == 0){ + command->lock = true; + if(status && strcmp(status, "disable") == 0) command->lock = false; + }else if(strcmp(tmp, "unlock") == 0){ + command->unlock = true; + if(status && strcmp(status, "disable") == 0) command->unlock = false; + } + delete (tmp); + delete (status); + } +} + +static unsigned char +parseStatus(char *status, char *name) +{ + unsigned char value; + + if(!status || strcmp(status, "enabled") == 0){/*enabled*/ + if(strcmp(name, "set_focus_position") == 0) value = 0x70; + else if(strcmp(name, "feed") == 0) value = 0x19; + else if(strcmp(name, "eject") == 0) value = 0x0C; + else value = 0; + }else if(strcmp(status, "disabled") == 0){ + value = 0; + }else{ + value = 0; + } + + return value; +} + +capability_data_t* +parseCapabilities(xmlNodePtr cur) +{ + capability_data_t *capabilities; + char *tmp; + char *endp; + + log_call(); + + capabilities = t_calloc (1, capability_data_t); + if(capabilities == NULL){ + err_major("out of memory"); + return NULL; + } + + cur = cur->xmlChildrenNode; + while(cur != NULL){ + if ((!xmlStrcmp(cur->name, (const xmlChar *) "scan-area"))){ + endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "width"); + capabilities->width = strtol(tmp, &endp, 10); + if(endp == tmp) capabilities->width = -1; + if(*endp != '\0') + err_minor("ignoring trailing garbage (%s)", endp); + delete (tmp); + + endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "height"); + capabilities->height = strtol(tmp, &endp, 10); + if(endp == tmp) capabilities->height = -1; + if(*endp != '\0') + err_minor("ignoring trailing garbage (%s)", endp); + delete (tmp); + + endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "base"); + capabilities->base = strtol(tmp, &endp, 10); + if(endp == tmp) capabilities->base = 1; + if(*endp != '\0') + err_minor("ignoring trailing garbage (%s)", endp); + delete (tmp); + } + cur = cur->next; + } + + return capabilities; +} diff --git a/backend/xmlreader.h b/backend/xmlreader.h new file mode 100644 index 0000000..cabafdf --- /dev/null +++ b/backend/xmlreader.h @@ -0,0 +1,79 @@ +/* xmlreader.h -- + * Copyright (C) 2010 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: SEIKO EPSON CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifndef included_xmlreader_h +#define included_xmlreader_h + +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> + +char *parseDevices(xmlNodePtr cur, char *name); + +EpsonScanHard parseProfiles(xmlNodePtr cur); + +scan_command_t *parseCommands_set(xmlNodePtr cur); + +capability_data_t *parseCapabilities(xmlNodePtr cur); + +#endif |