/* 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 "sane/config.h"

#include <limits.h>
#include <stdlib.h>
#include <string.h>
extern int errno;

#include "sane/sane.h"
#include "sane/saneopts.h"

#include <unistd.h>
#include <fcntl.h>

#include "sane/sanei_backend.h"

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

#include "sane/sanei_config.h"
#define PINT_CONFIG_FILE "pint.conf"

#include "pint.h"

#define MM_PER_INCH		25.4
#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, "Lineart");
  CHECK_MODE(SIM_DITHERED_MONOCHROME, "Halftone");
  CHECK_MODE(SIM_GRAYSCALE, "Gray");
  CHECK_MODE(SIM_COLOR, "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].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 (V_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, "Lineart"))
	{
	  s->params.format = SANE_FRAME_GRAY;
	  scanio.scan_image_mode = SIM_BINARY_MONOCHROME;
	}
      else if (!strcmp (mode, "Halftone"))
	{
	  s->params.format = SANE_FRAME_GRAY;
	  scanio.scan_image_mode = SIM_DITHERED_MONOCHROME;
	}
      else if (!strcmp (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;
}
