summaryrefslogtreecommitdiff
path: root/backend/pint.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/pint.c')
-rw-r--r--backend/pint.c987
1 files changed, 987 insertions, 0 deletions
diff --git a/backend/pint.c b/backend/pint.c
new file mode 100644
index 0000000..f0cc697
--- /dev/null
+++ b/backend/pint.c
@@ -0,0 +1,987 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 Gordon Matzigkeit
+ Copyright (C) 1997 David Mosberger-Tang
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice. */
+
+#include "../include/sane/config.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+extern int errno;
+
+#include "../include/sane/sane.h"
+#include "../include/sane/saneopts.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "../include/sane/sanei_backend.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#include "../include/sane/sanei_config.h"
+#define PINT_CONFIG_FILE "pint.conf"
+
+#include "pint.h"
+
+#define DECIPOINTS_PER_MM (720.0 / MM_PER_INCH)
+#define TWELVEHUNDS_PER_MM (1200.0 / MM_PER_INCH)
+
+
+static int num_devices;
+static PINT_Device *first_dev;
+static PINT_Scanner *first_handle;
+
+/* A zero-terminated list of valid scanner modes. */
+static SANE_String_Const mode_list[8];
+
+static const SANE_Range s7_range =
+ {
+ -127, /* minimum */
+ 127, /* maximum */
+ 1 /* quantization */
+ };
+
+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 SANE_Status
+attach (const char *devname, PINT_Device **devp)
+{
+ int fd;
+ long lastguess, inc;
+ PINT_Device *dev;
+ struct scan_io scanio;
+
+ for (dev = first_dev; dev; dev = dev->next)
+ if (strcmp (dev->sane.name, devname) == 0)
+ {
+ if (devp)
+ *devp = dev;
+ return SANE_STATUS_GOOD;
+ }
+
+ DBG(3, "attach: opening %s\n", devname);
+ fd = open (devname, O_RDONLY, 0);
+ if (fd < 0)
+ {
+ DBG(1, "attach: open failed (%s)\n", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG(3, "attach: sending SCIOCGET\n");
+ if (ioctl (fd, SCIOCGET, &scanio) < 0)
+ {
+ DBG(1, "attach: get status failed (%s)\n", strerror (errno));
+ close (fd);
+ return SANE_STATUS_INVAL;
+ }
+
+ dev = malloc (sizeof (*dev));
+ if (!dev)
+ return SANE_STATUS_NO_MEM;
+
+ memset(dev, 0, sizeof (*dev));
+
+ /* Copy the original scanner state to the device structure. */
+ memcpy (&dev->scanio, &scanio, sizeof (dev->scanio));
+
+ /* FIXME: PINT currently has no good way to determine maxima and minima.
+ So, do binary searches to find out what limits the driver has. */
+
+ /* Assume that minimum range of x and y is 0. */
+ dev->x_range.min = SANE_FIX (0);
+ dev->y_range.min = SANE_FIX (0);
+ dev->x_range.quant = 0;
+ dev->y_range.quant = 0;
+
+ /* x range */
+ inc = 8.5 * 1200;
+
+ /* Converge on the maximum scan width. */
+ while ((inc /= 2) != 0)
+ {
+ /* Move towards the extremum until we overflow. */
+ do
+ {
+ lastguess = scanio.scan_width;
+ scanio.scan_width += inc;
+ }
+ while (ioctl (fd, SCIOCSET, &scanio) >= 0);
+
+ /* Pick the last valid guess, divide by two, and try again. */
+ scanio.scan_width = lastguess;
+ }
+ dev->x_range.max = SANE_FIX (scanio.scan_width / TWELVEHUNDS_PER_MM);
+
+ /* y range */
+ inc = 11 * 1200;
+ while ((inc /= 2) != 0)
+ {
+ do
+ {
+ lastguess = scanio.scan_height;
+ scanio.scan_height += inc;
+ }
+ while (ioctl (fd, SCIOCSET, &scanio) >= 0);
+ scanio.scan_height = lastguess;
+ }
+ dev->y_range.max = SANE_FIX (scanio.scan_height / TWELVEHUNDS_PER_MM);
+
+ /* Converge on the minimum scan resolution. */
+ dev->dpi_range.quant = 1;
+
+ if (scanio.scan_x_resolution > scanio.scan_y_resolution)
+ scanio.scan_x_resolution = scanio.scan_y_resolution;
+ else
+ scanio.scan_y_resolution = scanio.scan_x_resolution;
+
+ inc = -scanio.scan_x_resolution;
+ while ((inc /= 2) != 0)
+ {
+ do
+ {
+ lastguess = scanio.scan_x_resolution;
+ scanio.scan_x_resolution = scanio.scan_y_resolution += inc;
+ }
+ while (ioctl (fd, SCIOCSET, &scanio) >= 0);
+ scanio.scan_x_resolution = scanio.scan_y_resolution = lastguess;
+ }
+ dev->dpi_range.min = scanio.scan_x_resolution;
+
+ /* Converge on the maximum scan resolution. */
+ inc = 600;
+ while ((inc /= 2) != 0)
+ {
+ do
+ {
+ lastguess = scanio.scan_x_resolution;
+ scanio.scan_x_resolution = scanio.scan_y_resolution += inc;
+ }
+ while (ioctl (fd, SCIOCSET, &scanio) >= 0);
+ scanio.scan_x_resolution = scanio.scan_y_resolution = lastguess;
+ }
+ dev->dpi_range.max = scanio.scan_x_resolution;
+
+ /* Determine the valid scan modes for mode_list. */
+ lastguess = 0;
+#define CHECK_MODE(flag,modename) \
+ scanio.scan_image_mode = flag; \
+ if (ioctl (fd, SCIOCSET, &scanio) >= 0) \
+ mode_list[lastguess ++] = modename
+
+ CHECK_MODE(SIM_BINARY_MONOCHROME, SANE_VALUE_SCAN_MODE_LINEART);
+ CHECK_MODE(SIM_DITHERED_MONOCHROME, SANE_VALUE_SCAN_MODE_HALFTONE);
+ CHECK_MODE(SIM_GRAYSCALE, SANE_VALUE_SCAN_MODE_GRAY);
+ CHECK_MODE(SIM_COLOR, SANE_VALUE_SCAN_MODE_COLOR);
+ CHECK_MODE(SIM_RED, "Red");
+ CHECK_MODE(SIM_GREEN, "Green");
+ CHECK_MODE(SIM_BLUE, "Blue");
+#undef CHECK_MODE
+
+ /* Zero-terminate the list of modes. */
+ mode_list[lastguess] = 0;
+
+ /* Restore the scanner state. */
+ if (ioctl (fd, SCIOCSET, &dev->scanio))
+ DBG (2, "cannot reset original scanner state: %s\n", strerror (errno));
+ close (fd);
+
+ dev->sane.name = strdup (devname);
+
+ /* Determine vendor. */
+ switch (scanio.scan_scanner_type)
+ {
+ case EPSON_ES300C:
+ dev->sane.vendor = "Epson";
+ break;
+
+ case FUJITSU_M3096G:
+ dev->sane.vendor = "Fujitsu";
+ break;
+
+ case HP_SCANJET_IIC:
+ dev->sane.vendor = "HP";
+ break;
+
+ case IBM_2456:
+ dev->sane.vendor = "IBM";
+ break;
+
+ case MUSTEK_06000CX:
+ case MUSTEK_12000CX:
+ dev->sane.vendor = "Mustek";
+ break;
+
+ case RICOH_FS1:
+ case RICOH_IS410:
+ case RICOH_IS50:
+ dev->sane.vendor = "Ricoh";
+ break;
+
+ case SHARP_JX600:
+ dev->sane.vendor = "Sharp";
+ break;
+
+ case UMAX_UC630:
+ case UMAX_UG630:
+ dev->sane.vendor = "UMAX";
+ break;
+
+ default:
+ dev->sane.vendor = "PINT";
+ }
+
+ /* Determine model. */
+ switch (scanio.scan_scanner_type)
+ {
+ case EPSON_ES300C:
+ dev->sane.vendor = "Epson";
+ break;
+
+ case FUJITSU_M3096G:
+ dev->sane.model = "M3096G";
+ break;
+
+ case HP_SCANJET_IIC:
+ dev->sane.model = "ScanJet IIc";
+ break;
+
+ case IBM_2456:
+ dev->sane.vendor = "IBM";
+ break;
+
+ case MUSTEK_06000CX:
+ case MUSTEK_12000CX:
+ dev->sane.vendor = "Mustek";
+ break;
+
+ case RICOH_FS1:
+ dev->sane.model = "FS1";
+ break;
+
+ case RICOH_IS410:
+ dev->sane.model = "IS-410";
+ break;
+
+ case RICOH_IS50:
+ dev->sane.vendor = "Ricoh";
+ break;
+
+ case SHARP_JX600:
+ dev->sane.vendor = "Sharp";
+ break;
+
+ case UMAX_UC630:
+ case UMAX_UG630:
+ dev->sane.vendor = "UMAX";
+ break;
+
+ default:
+ dev->sane.model = "unknown";
+ }
+
+ /* Determine the scanner type. */
+ switch (scanio.scan_scanner_type)
+ {
+ case HP_SCANJET_IIC:
+ dev->sane.type = "flatbed scanner";
+
+ /* FIXME: which of these are flatbed or handhelds? */
+ case EPSON_ES300C:
+ case FUJITSU_M3096G:
+ case IBM_2456:
+ case MUSTEK_06000CX:
+ case MUSTEK_12000CX:
+ case RICOH_FS1:
+ case RICOH_IS410:
+ case RICOH_IS50:
+ case SHARP_JX600:
+ case UMAX_UC630:
+ case UMAX_UG630:
+ default:
+ dev->sane.type = "generic scanner";
+ }
+
+ DBG(1, "attach: found %s %s, x=%g-%gmm, y=%g-%gmm, "
+ "resolution=%d-%ddpi\n", dev->sane.vendor, dev->sane.model,
+ SANE_UNFIX (dev->x_range.min), SANE_UNFIX (dev->x_range.max),
+ SANE_UNFIX (dev->y_range.min), SANE_UNFIX (dev->y_range.max),
+ dev->dpi_range.min, dev->dpi_range.max);
+
+ ++num_devices;
+ dev->next = first_dev;
+ first_dev = dev;
+
+ if (devp)
+ *devp = dev;
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+init_options (PINT_Scanner *s)
+{
+ int i;
+ int x0, x1, y0, y1;
+
+ memset (s->opt, 0, sizeof (s->opt));
+ memset (s->val, 0, sizeof (s->val));
+
+ 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].type = SANE_TYPE_INT;
+ s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+ s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
+
+ /* "Mode" group: */
+ s->opt[OPT_MODE_GROUP].title = "Scan Mode";
+ s->opt[OPT_MODE_GROUP].desc = "";
+ s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_MODE_GROUP].cap = 0;
+ s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* 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;
+
+ /* Translate the current PINT mode into a string. */
+ switch (s->hw->scanio.scan_image_mode)
+ {
+ case SIM_BINARY_MONOCHROME:
+ s->val[OPT_MODE].s = strdup (mode_list[0]);
+ break;
+
+ case SIM_DITHERED_MONOCHROME:
+ s->val[OPT_MODE].s = strdup (mode_list[1]);
+ break;
+
+ case SIM_COLOR:
+ s->val[OPT_MODE].s = strdup (mode_list[3]);
+ break;
+
+ case SIM_RED:
+ s->val[OPT_MODE].s = strdup (mode_list[4]);
+ break;
+
+ case SIM_GREEN:
+ s->val[OPT_MODE].s = strdup (mode_list[5]);
+ break;
+
+ case SIM_BLUE:
+ s->val[OPT_MODE].s = strdup (mode_list[6]);
+ break;
+
+ case SIM_GRAYSCALE:
+ default:
+ s->val[OPT_MODE].s = strdup (mode_list[2]);
+ }
+
+ /* 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;
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ s->val[OPT_RESOLUTION].w =
+ (s->hw->scanio.scan_x_resolution > s->hw->scanio.scan_y_resolution) ?
+ s->hw->scanio.scan_x_resolution : s->hw->scanio.scan_y_resolution;
+
+ /* "Geometry" group: */
+
+ s->opt[OPT_GEOMETRY_GROUP].title = "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;
+ s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* Calculate the x and y millimetre coordinates from the scanio. */
+ x0 = SANE_FIX (s->hw->scanio.scan_x_origin / TWELVEHUNDS_PER_MM);
+ y0 = SANE_FIX (s->hw->scanio.scan_y_origin / TWELVEHUNDS_PER_MM);
+ x1 = SANE_FIX ((s->hw->scanio.scan_x_origin + s->hw->scanio.scan_width)
+ / TWELVEHUNDS_PER_MM);
+ y1 = SANE_FIX ((s->hw->scanio.scan_y_origin + s->hw->scanio.scan_height)
+ / TWELVEHUNDS_PER_MM);
+
+ /* 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->x_range;
+ s->val[OPT_TL_X].w = x0;
+
+ /* 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->y_range;
+ s->val[OPT_TL_Y].w = y0;
+
+ /* 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->x_range;
+ s->val[OPT_BR_X].w = x1;
+
+ /* 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->y_range;
+ s->val[OPT_BR_Y].w = y1;
+
+ /* "Enhancement" group: */
+
+ s->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement";
+ s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
+ s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
+ s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* 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].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &s7_range;
+ s->val[OPT_BRIGHTNESS].w = s->hw->scanio.scan_brightness - 128;
+
+ /* 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].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CONTRAST].constraint.range = &s7_range;
+ s->val[OPT_CONTRAST].w = s->hw->scanio.scan_contrast - 128;
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+do_cancel (PINT_Scanner *s)
+{
+ /* FIXME: PINT doesn't have any good way to cancel ScanJets right now. */
+#define gobble_up_buf_len 1024
+ char buf[gobble_up_buf_len];
+
+ /* Send the restart code. */
+ buf[0] = ioctl (s->fd, SCIOCRESTART, 0);
+
+ if (!s->scanning)
+ return SANE_STATUS_CANCELLED;
+
+ s->scanning = SANE_FALSE;
+
+ /* Read to the end of the file. */
+ while (read (s->fd, buf, gobble_up_buf_len) > 0)
+ ;
+#undef gobble_up_buf_len
+
+ /* Finally, close the file descriptor. */
+ if (s->fd >= 0)
+ {
+ close (s->fd);
+ s->fd = -1;
+ }
+ return SANE_STATUS_CANCELLED;
+}
+
+SANE_Status
+sane_init (SANE_Int *version_code, SANE_Auth_Callback authorize)
+{
+ char dev_name[PATH_MAX];
+ size_t len;
+ FILE *fp;
+
+ DBG_INIT();
+
+ if (version_code)
+ *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0);
+
+ fp = sanei_config_open (PINT_CONFIG_FILE);
+ if (!fp)
+ {
+ /* default to /dev/scanner instead of insisting on config file */
+ attach ("/dev/scanner", 0);
+ return SANE_STATUS_GOOD;
+ }
+
+ while (sanei_config_read (dev_name, sizeof (dev_name), fp))
+ {
+ if (dev_name[0] == '#') /* ignore line comments */
+ continue;
+ len = strlen (dev_name);
+
+ if (!len)
+ continue; /* ignore empty lines */
+
+ attach (dev_name, 0);
+ }
+ fclose (fp);
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+ PINT_Device *dev, *next;
+
+ for (dev = first_dev; dev; dev = next)
+ {
+ next = dev->next;
+ free ((void *) dev->sane.name);
+ free (dev);
+ }
+}
+
+SANE_Status
+sane_get_devices (const SANE_Device ***device_list, SANE_Bool local_only)
+{
+ static const SANE_Device **devlist = 0;
+ PINT_Device *dev;
+ int i;
+
+ if (devlist)
+ free (devlist);
+
+ devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
+ if (!devlist)
+ return SANE_STATUS_NO_MEM;
+
+ i = 0;
+ for (dev = first_dev; i < num_devices; dev = dev->next)
+ devlist[i++] = &dev->sane;
+ devlist[i++] = 0;
+
+ *device_list = devlist;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_open (SANE_String_Const devicename, SANE_Handle *handle)
+{
+ SANE_Status status;
+ PINT_Device *dev;
+ PINT_Scanner *s;
+
+ if (devicename[0])
+ {
+ for (dev = first_dev; dev; dev = dev->next)
+ if (strcmp (dev->sane.name, devicename) == 0)
+ break;
+
+ if (!dev)
+ {
+ status = attach (devicename, &dev);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+ }
+ }
+ else
+ /* empty devicename -> use first device */
+ dev = first_dev;
+
+ if (!dev)
+ return SANE_STATUS_INVAL;
+
+ s = malloc (sizeof (*s));
+ if (!s)
+ return SANE_STATUS_NO_MEM;
+ memset (s, 0, sizeof (*s));
+ s->hw = dev;
+ s->fd = -1;
+
+ init_options (s);
+
+ /* insert newly opened handle into list of open handles: */
+ s->next = first_handle;
+ first_handle = s;
+
+ *handle = s;
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ PINT_Scanner *prev, *s;
+
+ /* 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)
+ {
+ DBG(1, "close: invalid handle %p\n", handle);
+ return; /* oops, not a handle we know about */
+ }
+
+ if (s->scanning)
+ do_cancel (handle);
+
+ if (prev)
+ prev->next = s->next;
+ else
+ first_handle = s->next;
+
+ free (handle);
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ PINT_Scanner *s = handle;
+
+ if ((unsigned) option >= NUM_OPTIONS)
+ return 0;
+ return s->opt + option;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *val, SANE_Int *info)
+{
+ PINT_Scanner *s = handle;
+ SANE_Status status;
+ SANE_Word cap;
+
+ if (info)
+ *info = 0;
+
+ if (s->scanning)
+ return SANE_STATUS_DEVICE_BUSY;
+
+ if (option >= NUM_OPTIONS)
+ return SANE_STATUS_INVAL;
+
+ cap = s->opt[option].cap;
+
+ if (!SANE_OPTION_IS_ACTIVE (cap))
+ return SANE_STATUS_INVAL;
+
+ if (action == SANE_ACTION_GET_VALUE)
+ {
+ switch (option)
+ {
+ /* word options: */
+ case OPT_RESOLUTION:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_NUM_OPTS:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ *(SANE_Word *) val = s->val[option].w;
+ return SANE_STATUS_GOOD;
+
+ /* string options: */
+ case OPT_MODE:
+ strcpy (val, s->val[option].s);
+ return SANE_STATUS_GOOD;
+ }
+ }
+ else if (action == SANE_ACTION_SET_VALUE)
+ {
+ if (!SANE_OPTION_IS_SETTABLE (cap))
+ return SANE_STATUS_INVAL;
+
+ status = sanei_constrain_value (s->opt + option, val, info);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ switch (option)
+ {
+ /* (mostly) side-effect-free word options: */
+ case OPT_RESOLUTION:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ if (info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ /* fall through */
+ case OPT_NUM_OPTS:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ s->val[option].w = *(SANE_Word *) val;
+ return SANE_STATUS_GOOD;
+
+ case OPT_MODE:
+ if (s->val[option].s)
+ free (s->val[option].s);
+ s->val[option].s = strdup (val);
+ if (info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ return SANE_STATUS_GOOD;
+ }
+ }
+ return SANE_STATUS_INVAL;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters *params)
+{
+ PINT_Scanner *s = handle;
+ struct scan_io scanio;
+
+ if (!s->scanning)
+ {
+ u_long x0, y0, width, height;
+ const char *mode;
+
+ /* Grab the scanio for this device. */
+ if (s->fd < 0)
+ {
+ s->fd = open (s->hw->sane.name, O_RDONLY, 0);
+ if (s->fd < 0)
+ {
+ DBG(1, "open of %s failed: %s\n",
+ s->hw->sane.name, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ }
+
+ if (ioctl (s->fd, SCIOCGET, &scanio) < 0)
+ {
+ DBG(1, "getting scanner state failed: %s", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ memset (&s->params, 0, sizeof (s->params));
+
+ /* FIXME: there is some lossage here: the parameters change due to
+ roundoff errors between converting to fixed point millimetres
+ and back. */
+ x0 = SANE_UNFIX (s->val[OPT_TL_X].w * TWELVEHUNDS_PER_MM);
+ y0 = SANE_UNFIX (s->val[OPT_TL_Y].w * TWELVEHUNDS_PER_MM);
+ width = SANE_UNFIX ((s->val[OPT_BR_X].w - s->val[OPT_TL_X].w)
+ * TWELVEHUNDS_PER_MM);
+ height = SANE_UNFIX ((s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w)
+ * TWELVEHUNDS_PER_MM);
+
+ /* x and y dpi: */
+ scanio.scan_x_resolution = s->val[OPT_RESOLUTION].w;
+ scanio.scan_y_resolution = s->val[OPT_RESOLUTION].w;
+
+ /* set scan extents, in 1/1200'ths of an inch */
+ scanio.scan_x_origin = x0;
+ scanio.scan_y_origin = y0;
+ scanio.scan_width = width;
+ scanio.scan_height = height;
+
+ /* brightness and contrast */
+ scanio.scan_brightness = s->val[OPT_BRIGHTNESS].w + 128;
+ scanio.scan_contrast = s->val[OPT_CONTRAST].w + 128;
+
+ /* set the scan image mode */
+ mode = s->val[OPT_MODE].s;
+ if (!strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART))
+ {
+ s->params.format = SANE_FRAME_GRAY;
+ scanio.scan_image_mode = SIM_BINARY_MONOCHROME;
+ }
+ else if (!strcmp (mode, SANE_VALUE_SCAN_MODE_HALFTONE))
+ {
+ s->params.format = SANE_FRAME_GRAY;
+ scanio.scan_image_mode = SIM_DITHERED_MONOCHROME;
+ }
+ else if (!strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY))
+ {
+ s->params.format = SANE_FRAME_GRAY;
+ scanio.scan_image_mode = SIM_GRAYSCALE;
+ }
+ else if (!strcmp (mode, "Red"))
+ {
+ s->params.format = SANE_FRAME_RED;
+ scanio.scan_image_mode = SIM_RED;
+ }
+ else if (!strcmp (mode, "Green"))
+ {
+ s->params.format = SANE_FRAME_GREEN;
+ scanio.scan_image_mode = SIM_GREEN;
+ }
+ else if (!strcmp (mode, "Blue"))
+ {
+ s->params.format = SANE_FRAME_BLUE;
+ scanio.scan_image_mode = SIM_BLUE;
+ }
+ else
+ {
+ s->params.format = SANE_FRAME_RGB;
+ scanio.scan_image_mode = SIM_COLOR;
+ }
+
+ /* inquire resulting size of image after setting it up */
+ if (ioctl (s->fd, SCIOCSET, &scanio) < 0)
+ {
+ DBG(1, "setting scan parameters failed: %s", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ if (ioctl (s->fd, SCIOCGET, &scanio) < 0)
+ {
+ DBG(1, "getting scan parameters failed: %s", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Save all the PINT-computed values. */
+ s->params.pixels_per_line = scanio.scan_pixels_per_line;
+ s->params.bytes_per_line =
+ (scanio.scan_bits_per_pixel * scanio.scan_pixels_per_line + 7) / 8;
+ s->params.lines = scanio.scan_lines;
+ s->params.depth = (scanio.scan_image_mode == SIM_COLOR) ?
+ scanio.scan_bits_per_pixel / 3 : scanio.scan_bits_per_pixel;
+
+ /* FIXME: this will need to be different for hand scanners. */
+ s->params.last_frame = SANE_TRUE;
+ }
+ if (params)
+ *params = s->params;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ PINT_Scanner *s = handle;
+ SANE_Status status;
+
+ /* First make sure we have a current parameter set. This call actually
+ uses the PINT driver to do the calculations, so we trust its results. */
+ status = sane_get_parameters (s, 0);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ DBG(1, "%d pixels per line, %d bytes, %d lines high, dpi=%d\n",
+ s->params.pixels_per_line, s->params.bytes_per_line, s->params.lines,
+ s->val[OPT_RESOLUTION].w);
+
+ /* The scan is triggered in sane_read. */
+ s->scanning = SANE_TRUE;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte *buf, SANE_Int max_len, SANE_Int *len)
+{
+ PINT_Scanner *s = handle;
+ ssize_t nread;
+
+ *len = 0;
+
+ if (!s->scanning)
+ return do_cancel (s);
+
+ /* Verrry simple. Just suck up all the data PINT passes to us. */
+ nread = read (s->fd, buf, max_len);
+ if (nread <= 0)
+ {
+ do_cancel (s);
+ return (nread == 0) ? SANE_STATUS_EOF : SANE_STATUS_IO_ERROR;
+ }
+
+ *len = nread;
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ PINT_Scanner *s = handle;
+ do_cancel (s);
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int *fd)
+{
+ return SANE_STATUS_UNSUPPORTED;
+}