/* sane - Scanner Access Now Easy.
   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.

   This file implements a SANE backend for the Connectix QuickCam.  At
   present, only the color camera is supported though the driver
   should be able to easily accommodate black and white cameras.

   Portions of this code are derived from Scott Laird's qcam driver.
   It's copyright notice is reproduced here:

   Copyright (C) 1996 by Scott Laird

   Permission is hereby granted, free of charge, to any person
   obtaining a copy of this software and associated documentation
   files (the "Software"), to deal in the Software without
   restriction, including without limitation the rights to use, copy,
   modify, merge, publish, distribute, sublicense, and/or sell copies
   of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT.  IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY
   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
   CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */

#ifdef _AIX
# include "lalloca.h"		/* MUST come first for AIX! */
#endif

#include "sane/config.h"
#include "lalloca.h"

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

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


#define BACKEND_NAME qcam
#include "sane/sanei_backend.h"

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

#include "sane/sanei_config.h"
#define QCAM_CONFIG_FILE "qcam.conf"

#include "qcam.h"

/* status bits */
#define NeedRamTable		(1 << 1)
#define BlackBalanceInProgress	(1 << 6)
#define CameraNotReady		(1 << 7)

/* lpdata bits: */
#define Cmd0_7		0xff
#define CamRdy2		(   1 << 0)	/* byte mode */
#define Data0_6		(0x7f << 1)	/* byte mode */

/* lpstatus bits: */
#define CamRdy1		(   1 << 3)	/* nibble mode */
#define Nibble0_3	(0x0f << 4)	/* nibble mode */
#define Data7_11	(0x1f << 3)	/* byte mode */

/* lpcontrol bits: */
#define Strobe		(   1 << 0)	/* unused */
#define Autofeed	(   1 << 1)
#define Reset_N		(   1 << 2)
#define PCAck		(   1 << 3)
#define BiDir		(   1 << 5)

static int num_devices;
static QC_Device *first_dev;
static QC_Scanner *first_handle;

static const SANE_String_Const resolution_list[] = {
  "Low",			/* million-mode */
  "High",			/* billion-mode */
  0
};

static const SANE_Int mono_depth_list[] = {
  2,				/* # of elements */
  4, 6
};

static const SANE_Int color_depth_list[] = {
  /*2 */ 1,
  /* # of elements */
  /*16, */ 24
    /* "thousand" mode not implemented yet */
};

static const SANE_Int xfer_scale_list[] = {
  3,				/* # of elements */
  1, 2, 4
};

static const SANE_Range u8_range = {
  /* min, max, quantization */
  0, 255, 0
};

static const SANE_Range brightness_range = {
  /* min, max, quantization */
  0, 254, 0			/* 255 is bulb mode! */
};

static const SANE_Range x_range[] = {
  /* min, max, quantization */
  {0, 338, 2},			/* million mode */
  {0, 676, 4},			/* billion mode */
};

static const SANE_Range odd_x_range[] = {
  /* min, max, quantization */
  {1, 339, 2},			/* million mode */
  {3, 683, 4},			/* billion mode */
};

static const SANE_Range y_range[] = {
  /* min, max, quantization */
  {0, 249, 1},			/* million mode */
  {0, 498, 2},			/* billion mode */
};

static const SANE_Range odd_y_range[] = {
  /* min, max, quantization */
  {0, 249, 1},			/* million mode */
  {1, 499, 2},			/* billion mode */
};

static const SANE_Range bw_x_range = { 0, 334, 2 };
static const SANE_Range odd_bw_x_range = { 1, 335, 2 };
static const SANE_Range bw_y_range = { 0, 241, 1 };
static const SANE_Range odd_bw_y_range = { 1, 242, 1 };

#if defined(__linux__) || defined (HAVE_SYS_HW_H)

#ifdef HAVE_SYS_IO_H
# include <sys/io.h>		/* GNU libc based Linux */
#elif HAVE_ASM_IO_H
# include <asm/io.h>		/* older Linux */
#elif HAVE_SYS_HW_H
# include <sys/hw.h>		/* OS/2 */
#endif

#define read_lpdata(d)		inb ((d)->port)
#define read_lpstatus(d)	inb ((d)->port + 1)
#define read_lpcontrol(d)	inb ((d)->port + 2)
#define write_lpdata(d,v)	outb ((v), (d)->port)
#define write_lpcontrol(d,v)	outb ((v), (d)->port + 2)

#endif /* __linux__ */

static SANE_Status
enable_ports (QC_Device * q)
{
  /* better safe than sorry */
  if (q->port < 0x278 || q->port > 0x3bc)
    return SANE_STATUS_INVAL;

  if (ioperm (q->port, 3, 1) < 0)
    return SANE_STATUS_INVAL;

  return SANE_STATUS_GOOD;
}

static SANE_Status
disable_ports (QC_Device * q)
{
  if (ioperm (q->port, 3, 0) < 0)
    return SANE_STATUS_INVAL;

  return SANE_STATUS_GOOD;
}

/* We need a short delay loop -- somthing well under a millisecond.
   Unfortunately, adding 2 usleep(1)'s to qc_command slowed it down by
   a factor of over 1000 over the same loop with 2 usleep(0)'s, and
   that's too slow -- qc_start was taking over a second to run.  This
   seems to help, but if anyone has a good speed-independent pause
   routine, please tell me. -- Scott

   If you're worried about hogging the CPU: don't worry, the qcam
   interface leaves you no choice, so this doesn't make the situation
   any worse... */

static int
qc_wait (QC_Device * q)
{
  return read_lpstatus (q);
}


/* This function uses POSIX fcntl-style locking on a file created in
   the /tmp directory.  Because it uses the Unix record locking
   facility, locks are relinquished automatically on process
   termination, so "dead locks" are not a problem.  (FYI, the lock
   file will remain after process termination, but this is actually
   desired so that the next process need not re-creat(2)e it... just
   lock it.)  The wait argument indicates whether or not this funciton
   should "block" waiting for the previous lock to be relinquished.
   This is ideal so that multiple processes (eg. qcam) taking
   "snapshots" can peacefully coexist.

   -- Dave Plonka (plonka@carroll1.cc.edu) */
static SANE_Status
qc_lock_wait (QC_Device * q, int wait)
{
#ifdef F_SETLK

#ifndef HAVE_STRUCT_FLOCK
  struct flock
  {
    off_t l_start;
    off_t l_len;
    pid_t l_pid;
    short l_type;
    short l_whence;
  };
#endif /* !HAVE_STRUCT_FLOCK */
  struct flock sfl;
#endif

  DBG (3, "qc_lock_wait: acquiring lock for 0x%x\n", q->port);

#ifdef F_SETLK
  memset (&sfl, 0, sizeof (sfl));
#endif

  if (q->lock_fd < 0)
    {
      char lockfile[128];

      sprintf (lockfile, "/tmp/LOCK.qcam.0x%x", q->port);
      q->lock_fd = open (lockfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
      if (q->lock_fd < 0)
	{
	  DBG (1, "qc_lock_wait: failed to open %s (%s)\n",
	       lockfile, strerror (errno));
	  return SANE_STATUS_INVAL;
	}

    }

#ifdef F_SETLK
  sfl.l_type = F_WRLCK;
  if (fcntl (q->lock_fd, wait ? F_SETLKW : F_SETLK, &sfl) != 0)
    {
      DBG (1, "qc_lock_wait: failed to acquire lock (%s)\n",
	   strerror (errno));
      return SANE_STATUS_INVAL;
    }
#endif

  DBG (3, "qc_lock_wait: got lock for 0x%x\n", q->port);
  return SANE_STATUS_GOOD;
}

static SANE_Status
qc_unlock (QC_Device * q)
{
  SANE_Status status;
  char lockfile[128];
#ifdef F_SETLK
#ifndef HAVE_STRUCT_FLOCK
  struct flock
  {
    off_t l_start;
    off_t l_len;
    pid_t l_pid;
    short l_type;
    short l_whence;
  };
#endif /* !HAVE_STRUCT_FLOCK */
  struct flock sfl;
#endif

  DBG (3, "qc_unlock: releasing lock for 0x%x\n", q->port);

#ifdef F_SETLK
  memset (&sfl, 0, sizeof (sfl));
#endif
  if (q->lock_fd < 0)
    {
      DBG (3, "qc_unlock; port was not locked\n");
      return SANE_STATUS_INVAL;
    }
  /* clear the exclusive lock */

#ifdef F_SETLK
  sfl.l_type = F_UNLCK;

  if (fcntl (q->lock_fd, F_SETLK, &sfl) != 0)
    {
      DBG (3, "qc_unlock: failed to release lock (%s)\n", strerror (errno));
      return SANE_STATUS_INVAL;
    }
#endif
  sprintf (lockfile, "/tmp/LOCK.qcam.0x%x", q->port);
  DBG (1, "qc_unlock: /tmp/LOCK.qcam.0x%x\n", q->port);
  unlink (lockfile);
  close (q->lock_fd);
  q->lock_fd = -1;
  DBG (1, "qc_unlock: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

static SANE_Status
qc_lock (QC_Device * q)
{
  return qc_lock_wait (q, 1);
}

/* Busy-waits for a handshake signal from the QuickCam.  Almost all
   communication with the camera requires handshaking.  This is why
   qcam is a CPU hog.  */
static int
qc_waithand (QC_Device * q, int val)
{
  int status;

  while (((status = read_lpstatus (q)) & CamRdy1) != val);
  return status;
}

/* This is used when the qcam is in bidirectional ("byte") mode, and
   the handshaking signal is CamRdy2 (bit 0 of data reg) instead of
   CamRdy1 (bit 3 of status register).  It also returns the last value
   read, since this data is useful.  */
static unsigned int
qc_waithand2 (QC_Device * q, int val)
{
  unsigned int status;

  do
    {
      status = read_lpdata (q);
    }
  while ((status & CamRdy2) != (unsigned int) val);
  return status;
}

static unsigned int
qc_send (QC_Device * q, unsigned int byte)
{
  unsigned int echo;
  int n1, n2;

  write_lpdata (q, byte);
  qc_wait (q);
  write_lpcontrol (q, Autofeed | Reset_N);
  qc_wait (q);

  n1 = qc_waithand (q, CamRdy1);

  write_lpcontrol (q, Autofeed | Reset_N | PCAck);
  qc_wait (q);
  n2 = qc_waithand (q, 0);

  echo = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
#ifndef NDEBUG
  if (echo != byte)
    {
      DBG (1, "qc_send: sent 0x%02x, camera echoed 0x%02x\n", byte, echo);
      n2 = read_lpstatus (q);
      echo = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
      if (echo != byte)
	DBG (1, "qc_send: (re-read does not help)\n");
      else
	DBG (1, "qc_send: (fixed on re-read)\n");
    }
#endif
  return echo;
}

static int
qc_readparam (QC_Device * q)
{
  int n1, n2;
  int cmd;

  write_lpcontrol (q, Autofeed | Reset_N);	/* clear PCAck */
  n1 = qc_waithand (q, CamRdy1);

  write_lpcontrol (q, Autofeed | Reset_N | PCAck);	/* set PCAck */
  n2 = qc_waithand (q, 0);

  cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
  return cmd;
}

static unsigned int
qc_getstatus (QC_Device * q)
{
  unsigned int status;

  qc_send (q, QC_SEND_STATUS);
  status = qc_readparam (q);
  DBG (3, "qc_getstatus: status=0x%02x\n", status);
  return status;
}

static void
qc_setscanmode (QC_Scanner * s, u_int * modep)
{
  QC_Device *q = s->hw;
  u_int mode = 0;

  if (q->version != QC_COLOR)
    {
      switch (s->val[OPT_XFER_SCALE].w)
	{
	case 1:
	  mode = 0;
	  break;
	case 2:
	  mode = 4;
	  break;
	case 4:
	  mode = 8;
	  break;
	}
      switch (s->val[OPT_DEPTH].w)
	{
	case 4:
	  break;
	case 6:
	  mode += 2;
	  break;
	}
    }
  else
    {
      switch (s->val[OPT_XFER_SCALE].w)
	{
	case 1:
	  mode = 0;
	  break;
	case 2:
	  mode = 2;
	  break;
	case 4:
	  mode = 4;
	  break;
	}
      if (s->resolution == QC_RES_LOW)
	mode |= 0x18;		/* millions mode */
      else
	mode |= 0x10;		/* billions mode */
    }
  if (s->val[OPT_TEST].w)
    mode |= 0x40;		/* test mode */

  if (q->port_mode == QC_BIDIR)
    mode |= 1;

  DBG (2, "scanmode (before increment): 0x%x\n", mode);

  if (q->version == QC_COLOR)
    ++mode;

  *modep = mode;
}

/* Read data bytes from the camera.  The number of bytes read is
   returned as the function result.  Depending on the mode, it may be
   either 1, 3 or 6.  On failure, 0 is returned.  If buffer is 0, the
   internal state-machine is reset.  */
static size_t
qc_readbytes (QC_Scanner * s, unsigned char buffer[])
{
  QC_Device *q = s->hw;
  unsigned int hi, lo;
  unsigned int hi2, lo2;
  size_t bytes = 0;

  if (!buffer)
    {
      s->readbytes_state = 0;
      return 0;
    }

  switch (q->port_mode)
    {
    case QC_BIDIR:
      /* bi-directional port */

      /* read off 24 bits: */
      write_lpcontrol (q, Autofeed | Reset_N | BiDir);
      lo = qc_waithand2 (q, 1) >> 1;
      hi = (read_lpstatus (q) >> 3) & 0x1f;
      write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
      lo2 = qc_waithand2 (q, 0) >> 1;
      hi2 = (read_lpstatus (q) >> 3) & 0x1f;
      if (q->version == QC_COLOR)
	{
	  /* is Nibble3 inverted for color quickcams only? */
	  hi ^= 0x10;
	  hi2 ^= 0x10;
	}
      switch (s->val[OPT_DEPTH].w)
	{
	case 4:
	  buffer[0] = lo & 0xf;
	  buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3);
	  buffer[2] = (hi & 0x1e) >> 1;
	  buffer[3] = lo2 & 0xf;
	  buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3);
	  buffer[5] = (hi2 & 0x1e) >> 1;
	  bytes = 6;
	  break;

	case 6:
	  buffer[0] = lo & 0x3f;
	  buffer[1] = ((lo & 0x40) >> 6) | (hi << 1);
	  buffer[2] = lo2 & 0x3f;
	  buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1);
	  bytes = 4;
	  break;

	case 24:
	  buffer[0] = lo | ((hi & 0x1) << 7);
	  buffer[1] = ((hi2 & 0x1e) >> 1) | ((hi & 0x1e) << 3);
	  buffer[2] = lo2 | ((hi2 & 0x1) << 7);
	  bytes = 3;
	  break;
	}
      break;

    case QC_UNIDIR:		/* Unidirectional Port */
      write_lpcontrol (q, Autofeed | Reset_N);
      lo = (qc_waithand (q, CamRdy1) & 0xf0) >> 4;
      write_lpcontrol (q, Autofeed | Reset_N | PCAck);
      hi = (qc_waithand (q, 0) & 0xf0) >> 4;

      if (q->version == QC_COLOR)
	{
	  /* invert Nibble3 */
	  hi ^= 8;
	  lo ^= 8;
	}

      switch (s->val[OPT_DEPTH].w)
	{
	case 4:
	  buffer[0] = lo;
	  buffer[1] = hi;
	  bytes = 2;
	  break;

	case 6:
	  switch (s->readbytes_state)
	    {
	    case 0:
	      buffer[0] = (lo << 2) | ((hi & 0xc) >> 2);
	      s->saved_bits = (hi & 3) << 4;
	      s->readbytes_state = 1;
	      bytes = 1;
	      break;

	    case 1:
	      buffer[0] = lo | s->saved_bits;
	      s->saved_bits = hi << 2;
	      s->readbytes_state = 2;
	      bytes = 1;
	      break;

	    case 2:
	      buffer[0] = ((lo & 0xc) >> 2) | s->saved_bits;
	      buffer[1] = ((lo & 3) << 4) | hi;
	      s->readbytes_state = 0;
	      bytes = 2;
	      break;

	    default:
	      DBG (1, "qc_readbytes: bad unidir 6-bit stat %d\n",
		   s->readbytes_state);
	      break;
	    }
	  break;

	case 24:
	  buffer[0] = (lo << 4) | hi;
	  bytes = 1;
	  break;

	default:
	  DBG (1, "qc_readbytes: bad unidir bit depth %d\n",
	       s->val[OPT_DEPTH].w);
	  break;
	}
      break;

    default:
      DBG (1, "qc_readbytes: bad port_mode %d\n", q->port_mode);
      break;
    }
  return bytes;
}

static void
qc_reset (QC_Device * q)
{
  write_lpcontrol (q, Strobe | Autofeed | Reset_N | PCAck);
  qc_wait (q);
  write_lpcontrol (q, Strobe | Autofeed | PCAck);
  qc_wait (q);
  write_lpcontrol (q, Strobe | Autofeed | Reset_N | PCAck);
}

/* This function is executed as a child process.  The reason this is
   executed as a subprocess is because the qcam interface directly reads
   off of a I/O port (rather than a filedescriptor).  Thus, to have
   something to select() on, we transfer the data through a pipe.

   WARNING: Since this is executed as a subprocess, it's NOT possible
   to update any of the variables in the main process (in particular
   the scanner state cannot be updated).  */

static jmp_buf env;

static void
sighandler (int signal)
{
  DBG (3, "sighandler: got signal %d\n", signal);
  longjmp (env, 1);
}

/* Original despeckling code by Patrick Reynolds <patrickr@virginia.edu> */

static void
despeckle (int width, int height, SANE_Byte * in, SANE_Byte * out)
{
  long x, i;
  /* The light-check threshold.  Higher numbers remove more lights but
     blur the image more.  30 is good for indoor lighting.  */
# define NO_LIGHTS 30

  /* macros to make the code a little more readable, p=previous, n=next */
# define R	in[i*3]
# define G	in[i*3+1]
# define B	in[i*3+2]
# define pR	in[i*3-3]
# define pG	in[i*3-2]
# define pB	in[i*3-1]
# define nR	in[i*3+3]
# define nG	in[i*3+4]
# define nB	in[i*3+5]

  DBG (1, "despeckle: width=%d, height=%d\n", width, height);

  for (x = i = 0; i < width * height; ++i)
    {
      if (x == 0 || x == width - 1)
	memcpy (&out[i * 3], &in[i * 3], 3);
      else
	{
	  if (R - (G + B) / 2 >
	      NO_LIGHTS + ((pR - (pG + pB) / 2) + (nR - (nG + nB) / 2)))
	    out[i * 3] = (pR + nR) / 2;
	  else
	    out[i * 3] = R;

	  if (G - (R + B) / 2 >
	      NO_LIGHTS + ((pG - (pR + pB) / 2) + (nG - (nR + nB) / 2)))
	    out[i * 3 + 1] = (pG + nG) / 2;
	  else
	    out[i * 3 + 1] = G;

	  if (B - (G + R) / 2 >
	      NO_LIGHTS + ((pB - (pG + pR) / 2) + (nB - (nG + nR) / 2)))
	    out[i * 3 + 2] = (pB + nB) / 2;
	  else
	    out[i * 3 + 2] = B;
	}
      if (++x >= width)
	x = 0;
    }
# undef R
# undef G
# undef B
# undef pR
# undef pG
# undef pB
# undef nR
# undef nG
# undef nB
}

static void
despeckle32 (int width, int height, SANE_Byte * in, SANE_Byte * out)
{
  long x, i;
  /* macros to make the code a little more readable, p=previous, n=next */
# define B	in[i*4]
# define Ga	in[i*4 + 1]
# define Gb	in[i*4 + 1]	/* ignore Gb and use Ga instead---Gb is weird */
# define R	in[i*4 + 3]
# define pB	in[i*4 - 4]
# define pGa	in[i*4 - 3]
# define pGb	in[i*4 - 1]	/* ignore Gb and use Ga instead---Gb is weird */
# define pR	in[i*4 - 1]
# define nB	in[i*4 + 4]
# define nGa	in[i*4 + 5]
# define nGb	in[i*4 + 5]	/* ignore Gb and use Ga instead---Gb is weird */
# define nR	in[i*4 + 7]

  DBG (1, "despeckle32: width=%d, height=%d\n", width, height);

  for (x = i = 0; i < width * height; ++i)
    {
      if (x == 0 || x == width - 1)
	memcpy (&out[i * 4], &in[i * 4], 4);
      else
	{
	  if (x >= width - 2)
	    /* the last red pixel seems to be black at all times, use
	       R instead: */
	    nR = R;

	  if (R - ((Ga + Gb) / 2 + B) / 2 >
	      NO_LIGHTS + ((pR - ((pGa + pGb) / 2 + pB) / 2) +
			   (nR - ((nGa + nGb) / 2 + nB) / 2)))
	    out[i * 4 + 3] = (pR + nR) / 2;
	  else
	    out[i * 4 + 3] = R;

	  if (Ga - (R + B) / 2 > NO_LIGHTS + ((pGa - (pR + pB) / 2) +
					      (nGa - (nR + nB) / 2)))
	    out[i * 4 + 1] = (pGa + nGa) / 2;
	  else
	    out[i * 4 + 1] = Ga;

	  if (Gb - (R + B) / 2 > NO_LIGHTS + ((pGb - (pR + pB) / 2) +
					      (nGb - (nR + nB) / 2)))
	    out[i * 4 + 2] = (pGb + nGb) / 2;
	  else
	    out[i * 4 + 2] = Gb;

	  if (B - ((Ga + Gb) / 2 + R) / 2 >
	      NO_LIGHTS + ((pB - ((pGa + pGb) / 2 + pR) / 2) +
			   (nB - ((nGa + nGb) / 2 + nR) / 2)))
	    out[i * 4 + 0] = (pB + nB) / 2;
	  else
	    out[i * 4 + 0] = B;
	}
      if (++x >= width)
	x = 0;
    }
# undef R
# undef Ga
# undef Gb
# undef B
# undef pR
# undef pGa
# undef pGb
# undef pB
# undef nR
# undef nGa
# undef nGb
# undef nB
}

static int
reader_process (QC_Scanner * s, int in_fd, int out_fd)
{
  static SANE_Byte *buffer = 0, *extra = 0;
  static size_t buffer_size = 0;
  size_t count, len, num_bytes;
  QC_Device *q = s->hw;
  QC_Scan_Request req;
  int width, height;
  SANE_Byte *src;
  FILE *ofp;

  DBG (5, "reader_process: enter\n");

  enable_ports (q);

  ofp = fdopen (out_fd, "w");
  if (!ofp)
    return 1;

  while (1)
    {
      if (setjmp (env))
	{
	  char ch;

	  /* acknowledge the signal: */
	  DBG (1, "reader_process: sending signal ACK\n");
	  fwrite (&ch, 1, 1, ofp);
	  fflush (ofp);		/* force everything out the pipe */
	  continue;
	}
      signal (SIGINT, sighandler);

      /* the main process gets us started by writing a size_t giving
         the number of bytes we should expect: */
      if (read (in_fd, &req, sizeof (req)) != sizeof (req))
	{
	  perror ("read");
	  return 1;
	}
      num_bytes = req.num_bytes;

      DBG (3, "reader_process: got request for %lu bytes\n",
	   (u_long) num_bytes);

      /* Don't do this in sane_start() since there may be a long
         timespan between it and the first sane_read(), which would
         result in poor images.  */
      qc_send (q, QC_SEND_VIDEO_FRAME);
      qc_send (q, req.mode);

      if (req.despeckle
	  && (!extra || buffer_size < num_bytes
	      || buffer_size >= 2 * num_bytes))
	{
	  if (extra)
	    extra = realloc (extra, num_bytes);
	  else
	    extra = malloc (num_bytes);
	  if (!extra)
	    {
	      DBG (1, "reader_process: malloc(%ld) failed\n",
		   (long) num_bytes);
	      exit (1);
	    }
	}

      if (buffer_size < num_bytes || buffer_size >= 2 * num_bytes)
	{
	  if (buffer)
	    buffer = realloc (buffer, num_bytes);
	  else
	    buffer = malloc (num_bytes);
	  if (!buffer)
	    {
	      DBG (1, "reader_process: malloc(%ld) failed\n",
		   (long) num_bytes);
	      exit (1);
	    }
	  buffer_size = num_bytes;
	}

      if (q->port_mode == QC_BIDIR)
	{
	  /* turn port into input port */
	  write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
	  usleep (3);
	  write_lpcontrol (q, Autofeed | Reset_N | BiDir);
	  qc_waithand (q, CamRdy1);
	  write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
	  qc_waithand (q, 0);
	}

      if (q->version == QC_COLOR)
	for (len = 0; len < num_bytes; len += count)
	  count = qc_readbytes (s, buffer + len);
      else
	{
	  /* strange -- should be 15:63 below, but 4bpp is odd */
	  int shift, invert;
	  unsigned int i;
	  u_char val;

	  switch (s->val[OPT_DEPTH].w)
	    {
	    case 4:
	      invert = 16;
	      shift = 4;
	      break;

	    case 6:
	      invert = 63;
	      shift = 2;
	      break;

	    default:
	      DBG (1, "reader_process: unexpected depth %d\n",
		   s->val[OPT_DEPTH].w);
	      return 1;
	    }

	  for (len = 0; len < num_bytes; len += count)
	    {
	      count = qc_readbytes (s, buffer + len);
	      for (i = 0; i < count; ++i)
		{
		  /* 4bpp is odd (again) -- inverter is 16, not 15,
		     but output must be 0-15 */
		  val = buffer[len + i];
		  if (val > 0 || invert != 16)
		    val = invert - val;
		  buffer[len + i] = (val << shift) | (val >> (8 - 2 * shift));
		}
	    }
	  qc_readbytes (s, 0);	/* reset state machine */
	}
      /* we're done reading this frame: */
      DBG (2, "reader_process: frame complete\n");

      if (q->port_mode == QC_BIDIR)
	{
	  /* return port to output mode */
	  write_lpcontrol (q, Autofeed);
	  usleep (3);
	  write_lpcontrol (q, Autofeed | Reset_N);
	  usleep (3);
	  write_lpcontrol (q, Autofeed | Reset_N | PCAck);
	}

      if (req.resolution == QC_RES_HIGH)
	{
	  SANE_Byte buf[6];
	  int x, y;

	  /* in billions mode, we need to oversample the data: */
	  src = buffer;
	  width = req.params.pixels_per_line;
	  height = req.params.lines;

	  if (req.despeckle)
	    {
	      despeckle32 (width / 2, req.params.lines / 2, buffer, extra);
	      src = extra;
	    }

	  assert (!(width & 1));	/* width must be even */

	  for (y = 0; y < height; ++y)
	    {
	      /* even line */

	      for (x = 0; x < width; x += 2)
		{
		  int red1, green1, blue1, green2, blue2;

		  blue1 = src[0];
		  green1 = src[1];
		  red1 = src[3];
		  if (x >= width - 2)
		    {
		      red1 = src[-1];	/* last red seems to be missing */
		      blue2 = blue1;
		      green2 = green1;
		    }
		  else
		    {
		      blue2 = src[4];
		      green2 = src[5];
		    }
		  src += 4;

		  buf[0] = red1;
		  buf[1] = green1;
		  buf[2] = blue1;
		  buf[3] = red1;
		  buf[4] = green2;
		  buf[5] = blue2;
		  if (fwrite (buf, 1, 6, ofp) != 6)
		    {
		      perror ("fwrite: short write");
		      return 1;
		    }
		}
	      if (++y >= height)
		break;

	      src -= 2 * width;	/* 4 bytes/pixel -> 2 pixels of 3 bytes each */

	      /* odd line */
	      for (x = 0; x < width; x += 2)
		{
		  int red1, green3, blue3, green4, blue4;
		  int yoff;

		  if (x >= width - 2)
		    red1 = src[-1];	/* last red seems to be missing */
		  else
		    red1 = src[3];
		  yoff = 2 * width;
		  if (y >= height - 1)
		    yoff = 0;
		  green3 = src[yoff + 1];
		  blue3 = src[yoff + 0];
		  if (x >= width - 2)
		    {
		      blue4 = blue3;
		      green4 = green3;
		    }
		  else
		    {
		      blue4 = src[yoff + 4];
		      green4 = src[yoff + 5];
		    }
		  src += 4;

		  buf[0] = red1;
		  buf[1] = green3;
		  buf[2] = blue3;
		  buf[3] = red1;
		  buf[4] = green4;
		  buf[5] = blue4;
		  if (fwrite (buf, 1, 6, ofp) != 6)
		    {
		      perror ("fwrite: short write");
		      return 1;
		    }
		}
	    }
	}
      else
	{
	  src = buffer;
	  if (req.despeckle)
	    {
	      despeckle (req.params.pixels_per_line, req.params.lines,
			 buffer, extra);
	      src = extra;
	    }

	  /* now write the whole thing to the main process: */
	  if (fwrite (src, 1, num_bytes, ofp) != num_bytes)
	    {
	      perror ("fwrite: short write");
	      return 1;
	    }
	}
      fflush (ofp);
    }
  assert (SANE_FALSE);		/* not reached */
  DBG (5, "reader_process: exit\n");
  return 1;
}

static SANE_Status
attach (const char *devname, QC_Device ** devp)
{
  int i, n1, n2, s1, s2, cmd, port, force_unidir;
  SANE_Status result, status;
  QC_Device *q;
  char *endp;

  DBG (3, "attach: enter\n");
  errno = 0;
  force_unidir = 0;
  if (devname[0] == 'u')
    {
      force_unidir = 1;
      ++devname;
    }
  port = strtol (devname, &endp, 0);
  if (endp == devname || errno == ERANGE)
    {
      DBG (1, "attach: invalid port address `%s'\n", devname);
      return SANE_STATUS_INVAL;
    }

  for (q = first_dev; q; q = q->next)
    if (port == q->port)
      {
	if (devp)
	  *devp = q;
	return SANE_STATUS_GOOD;
      }

  q = malloc (sizeof (*q));
  if (!q)
    return SANE_STATUS_NO_MEM;

  memset (q, 0, sizeof (*q));
  q->port = port;
  q->lock_fd = -1;

  result = enable_ports (q);
  if (result != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: cannot enable ports (%s)\n", strerror (errno));
      free (q);
      return SANE_STATUS_INVAL;
    }

  /* lock camera while we determine its version: */
  qc_lock (q);

  qc_reset (q);

  write_lpdata (q, QC_SEND_VERSION);
  qc_wait (q);
  write_lpcontrol (q, Autofeed | Reset_N);	/* make PCAck inactive */
  qc_wait (q);

  for (i = 0; (i < 1000) && !(s1 = (n1 = read_lpstatus (q)) & CamRdy1); i++);
  if (!s1)
    {
      DBG (2, "attach: failed to get CamRdy1 at port 0x%x\n", q->port);
      goto unlock_and_fail;
    }

  write_lpcontrol (q, Autofeed | Reset_N | PCAck);
  qc_wait (q);

  for (i = 0; (i < 1000) && (s2 = (n2 = read_lpstatus (q)) & CamRdy1); i++);
  if (s2)
    {
      DBG (2, "attach: CamRdy1 failed to clear at port 0x%x\n", q->port);
      goto unlock_and_fail;
    }

  cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);

  if (cmd != QC_SEND_VERSION)
    {
      DBG (2, "attach: got 0x%02x instead of 0x%02x\n", cmd, QC_SEND_VERSION);
      goto unlock_and_fail;
    }

  q->version = qc_readparam (q);
  DBG (1, "attach: found QuickCam version 0x%02x\n", q->version);

  q->port_mode = QC_UNIDIR;
  if (!force_unidir)
    {
      write_lpcontrol (q, BiDir);
      write_lpdata (q, 0x75);
      if (read_lpdata (q) != 0x75)
	q->port_mode = QC_BIDIR;
    }

  /* For some reason the color quickcam needs two set-black commands
     after a reset.  Thus, we now set the black-level to some
     reasonable value (0) so that the next set-black level command
     will really go through.  */
  if (q->version == QC_COLOR)
    {
      qc_send (q, QC_SET_BLACK);
      qc_send (q, 0);

      DBG (3, "attach: resetting black_level\n");

      /* wait for set black level command to finish: */
      while (qc_getstatus (q) & (CameraNotReady | BlackBalanceInProgress))
	usleep (10000);
    }

  status = qc_unlock (q);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: status qc_unlock NOK\n");
      /* status = SANE_STATUS_GOOD;  */
    }
  q->sane.name = strdup (devname);
  q->sane.vendor = "Connectix";
  q->sane.model =
    (q->version == QC_COLOR) ? "Color QuickCam" : "B&W QuickCam";
  q->sane.type = "video camera";

  ++num_devices;
  q->next = first_dev;
  first_dev = q;

  if (devp)
    *devp = q;
  DBG (3, "attach: exit status OK\n");
  status = SANE_STATUS_GOOD;
  return status;


unlock_and_fail:
  status = qc_unlock (q);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: unlock_and_fail status qc_unlock NOK\n");
    }
  free (q);
  DBG (3, "attach: exit status NOK\n");
  status = SANE_STATUS_INVAL;
  return status;
}

static SANE_Status
init_options (QC_Scanner * s)
{
  int i;

  DBG (3, "init_options: enter\n");
  
  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
		       | SANE_CAP_ALWAYS_SETTABLE);
    }

  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;

  /* 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_STRING;
  s->opt[OPT_RESOLUTION].size = 5;	/* sizeof("High") */
  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_NONE;
  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_RESOLUTION].constraint.string_list = resolution_list;
  s->val[OPT_RESOLUTION].s = strdup (resolution_list[QC_RES_LOW]);

  /* bit-depth */
  s->opt[OPT_DEPTH].name = SANE_NAME_BIT_DEPTH;
  s->opt[OPT_DEPTH].title = "Pixel depth";
  s->opt[OPT_DEPTH].desc = "Number of bits per pixel.";
  s->opt[OPT_DEPTH].type = SANE_TYPE_INT;
  s->opt[OPT_DEPTH].unit = SANE_UNIT_BIT;
  s->opt[OPT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->opt[OPT_DEPTH].constraint.word_list = color_depth_list;
  s->val[OPT_DEPTH].w = color_depth_list[NELEMS (color_depth_list) - 1];

  /* test */
  s->opt[OPT_TEST].name = "test-image";
  s->opt[OPT_TEST].title = "Force test image";
  s->opt[OPT_TEST].desc =
    "Acquire a test-image instead of the image seen by the camera. "
    "The test image consists of red, green, and blue squares of "
    "32x32 pixels each.  Use this to find out whether the "
    "camera is connected properly.";
  s->opt[OPT_TEST].type = SANE_TYPE_BOOL;
  s->val[OPT_TEST].w = SANE_FALSE;

  /* "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;

  /* 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_INT;
  s->opt[OPT_TL_X].unit = SANE_UNIT_PIXEL;
  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_X].constraint.range = &x_range[QC_RES_LOW];
  s->val[OPT_TL_X].w = 10;

  /* 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_INT;
  s->opt[OPT_TL_Y].unit = SANE_UNIT_PIXEL;
  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_Y].constraint.range = &y_range[QC_RES_LOW];
  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_INT;
  s->opt[OPT_BR_X].unit = SANE_UNIT_PIXEL;
  s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BR_X].constraint.range = &odd_x_range[QC_RES_LOW];
  s->val[OPT_BR_X].w = 339;

  /* 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_INT;
  s->opt[OPT_BR_Y].unit = SANE_UNIT_PIXEL;
  s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BR_Y].constraint.range = &odd_y_range[QC_RES_LOW];
  s->val[OPT_BR_Y].w = 245;

  /* xfer-scale */
  s->opt[OPT_XFER_SCALE].name = "transfer-scale";
  s->opt[OPT_XFER_SCALE].title = "Transfer scale";
  s->opt[OPT_XFER_SCALE].desc =
    "The transferscale determines how many of the acquired pixels actually "
    "get sent to the computer.  For example, a transfer scale of 2 would "
    "request that every other acquired pixel would be omitted.  That is, "
    "when scanning a 200 pixel wide and 100 pixel tall area, the resulting "
    "image would be only 100x50 pixels large.  Using a large transfer scale "
    "improves acquisition speed, but reduces resolution.";
  s->opt[OPT_XFER_SCALE].type = SANE_TYPE_INT;
  s->opt[OPT_XFER_SCALE].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->opt[OPT_XFER_SCALE].constraint.word_list = xfer_scale_list;
  s->val[OPT_XFER_SCALE].w = xfer_scale_list[1];

  /* despeckle */
  s->opt[OPT_DESPECKLE].name = "despeckle";
  s->opt[OPT_DESPECKLE].title = "Speckle filter";
  s->opt[OPT_DESPECKLE].desc = "Turning on this filter will remove the "
    "christmas lights that are typically present in dark images.";
  s->opt[OPT_DESPECKLE].type = SANE_TYPE_BOOL;
  s->opt[OPT_DESPECKLE].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_DESPECKLE].w = 0;


  /* "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
    "  In a conventional camera, this control corresponds to the "
    "exposure time.";
  s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_AUTOMATIC;
  s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  s->val[OPT_BRIGHTNESS].w = 135;

  /* 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].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST].constraint.range = &u8_range;
  s->val[OPT_CONTRAST].w = 104;

  /* black-level */
  s->opt[OPT_BLACK_LEVEL].name = SANE_NAME_BLACK_LEVEL;
  s->opt[OPT_BLACK_LEVEL].title = SANE_TITLE_BLACK_LEVEL;
  s->opt[OPT_BLACK_LEVEL].desc = SANE_DESC_BLACK_LEVEL
    " This value should be selected so that black areas just start "
    "to look really black (not gray).";
  s->opt[OPT_BLACK_LEVEL].type = SANE_TYPE_INT;
  s->opt[OPT_BLACK_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BLACK_LEVEL].constraint.range = &u8_range;
  s->val[OPT_BLACK_LEVEL].w = 0;

  /* white-level */
  s->opt[OPT_WHITE_LEVEL].name = SANE_NAME_WHITE_LEVEL;
  s->opt[OPT_WHITE_LEVEL].title = SANE_TITLE_WHITE_LEVEL;
  s->opt[OPT_WHITE_LEVEL].desc = SANE_DESC_WHITE_LEVEL
    " This value should be selected so that white areas just start "
    "to look really white (not gray).";
  s->opt[OPT_WHITE_LEVEL].type = SANE_TYPE_INT;
  s->opt[OPT_WHITE_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_WHITE_LEVEL].constraint.range = &u8_range;
  s->val[OPT_WHITE_LEVEL].w = 150;

  /* hue */
  s->opt[OPT_HUE].name = SANE_NAME_HUE;
  s->opt[OPT_HUE].title = SANE_TITLE_HUE;
  s->opt[OPT_HUE].desc = SANE_DESC_HUE;
  s->opt[OPT_HUE].type = SANE_TYPE_INT;
  s->opt[OPT_HUE].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_HUE].constraint.range = &u8_range;
  s->val[OPT_HUE].w = 128;

  /* saturation */
  s->opt[OPT_SATURATION].name = SANE_NAME_SATURATION;
  s->opt[OPT_SATURATION].title = SANE_TITLE_SATURATION;
  s->opt[OPT_SATURATION].desc = SANE_DESC_SATURATION;
  s->opt[OPT_SATURATION].type = SANE_TYPE_INT;
  s->opt[OPT_SATURATION].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_SATURATION].constraint.range = &u8_range;
  s->val[OPT_SATURATION].w = 100;

  DBG (3, "init_options: exit\n");
  
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  char dev_name[PATH_MAX], *str;
  size_t len;
  FILE *fp;
  authorize = authorize;	/* silence compilation warnings */

  DBG_INIT ();

  DBG (1, "sane_init: enter\n");

  if (version_code)
    *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);

  fp = sanei_config_open (QCAM_CONFIG_FILE);
  if (!fp)
    {
      DBG (1, "sane_init: file `%s' not accessible\n", QCAM_CONFIG_FILE);
      return SANE_STATUS_INVAL;
    }

  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 */

      for (str = dev_name; *str && !isspace (*str) && *str != '#'; ++str);
      *str = '\0';

      attach (dev_name, 0);
    }
  fclose (fp);

  DBG (1, "sane_init: exit\n");
  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  QC_Device *dev, *next;
  static const SANE_Device **devlist;
  
  DBG (5, "sane_exit: enter\n");
  
  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free ((void *) dev->sane.name);
      disable_ports (dev);
      free (dev);
    }
  if (devlist) {
	  free (devlist);
          devlist = NULL;
  }		  
  DBG (5, "sane_exit: exit\n");
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  static const SANE_Device **devlist = 0;
  QC_Device *dev;
  int i;

  DBG (5, "sane_get_devices: enter\n");

  local_only = local_only;	/* silence compilation warnings */

  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;
  
  DBG (5, "sane_get_devices: exit\n");
  
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  SANE_Status status;
  QC_Device *dev;
  QC_Scanner *s;

  DBG (5, "sane_open: enter: (devicename = %s)\n", devicename);
  if (devicename[0])
    {
      status = attach (devicename, &dev);
      if (status != SANE_STATUS_GOOD)
	return status;
    }
  else
    /* empty devicname -> 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->user_corner = 0;
  s->hw = dev;
  s->value_changed = ~0;	/* ensure all options get updated */
  s->reader_pid = -1;
  s->to_child = -1;
  s->from_child = -1;
  s->read_fd = -1;

  init_options (s);

  /* The contrast option seems to have an effect for b&w cameras only,
     so don't give the user the impression that this is a useful thing
     to set... */
  if (s->hw->version == QC_COLOR)
    s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
  else
    {
      /* Black level, Hue and Saturation are things the b&w cameras
         know nothing about.  Despeckle might be useful, but this code
         seems to work for color cameras only right now. The framesize
         seems to work better in these ranges.  */
      s->opt[OPT_DESPECKLE].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_BLACK_LEVEL].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_HUE].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_SATURATION].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_RESOLUTION].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_TEST].cap |= SANE_CAP_INACTIVE;

      s->opt[OPT_DEPTH].constraint.word_list = mono_depth_list;
      s->val[OPT_DEPTH].w = mono_depth_list[NELEMS (mono_depth_list) - 1];
      s->opt[OPT_TL_X].constraint.range = &bw_x_range;
      s->val[OPT_TL_X].w = 14;
      s->opt[OPT_TL_Y].constraint.range = &bw_y_range;
      s->val[OPT_TL_Y].w = 0;
      s->opt[OPT_BR_X].constraint.range = &odd_bw_x_range;
      s->val[OPT_BR_X].w = 333;
      s->opt[OPT_BR_Y].constraint.range = &odd_bw_y_range;
      s->val[OPT_BR_Y].w = 239;

      s->val[OPT_BRIGHTNESS].w = 170;
      s->val[OPT_CONTRAST].w = 150;
      s->val[OPT_WHITE_LEVEL].w = 150;
    }

  /* insert newly opened handle into list of open handles: */
  s->next = first_handle;
  first_handle = s;

  *handle = s;

  DBG (5, "sane_open: exit\n");

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  QC_Scanner *prev, *s;

  DBG (5, "sane_close: enter\n");
  
  /* 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, "sane_close: bad handle %p\n", handle);
      return;			/* oops, not a handle we know about */
    }
  if (prev)
    prev->next = s->next;
  else
    first_handle = s->next;

  if (s->scanning)
    sane_cancel (handle);

  if (s->reader_pid >= 0)
    {
      kill (s->reader_pid, SIGTERM);
      waitpid (s->reader_pid, 0, 0);
      s->reader_pid = 0;
    }
  if (s->to_child >= 0)
    close (s->to_child);
  if (s->from_child >= 0)
    close (s->from_child);
  if (s->read_fd >= 0)
    close (s->read_fd);

  free (s);
  
  DBG (5, "sane_close: exit\n");
	  
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  QC_Scanner *s = handle;

  DBG (5, "sane_get_option_descriptor: enter\n");
  
  if ((unsigned) option >= NUM_OPTIONS)
    return 0;

  DBG (5, "sane_get_option_descriptor: exit\n");

  return s->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  QC_Scanner *s = handle;
  QC_Resolution old_res;
  SANE_Status status;
  SANE_Word cap;
  char *old_val;
  int i;

  DBG (5, "sane_control_option: enter\n");
  
  if (info)
    *info = 0;

  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_NUM_OPTS:
	case OPT_DEPTH:
	case OPT_DESPECKLE:
	case OPT_TEST:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_XFER_SCALE:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL:
	case OPT_HUE:
	case OPT_SATURATION:
	  *(SANE_Word *) val = s->val[option].w;
	  return SANE_STATUS_GOOD;

	  /* string options: */
	case OPT_RESOLUTION:
	  strcpy (val, s->val[option].s);
	  return SANE_STATUS_GOOD;

	default:
	  DBG (1, "control_option: option %d unknown\n", option);
	}
    }
  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;

      if (option >= OPT_TL_X && option <= OPT_BR_Y)
	s->user_corner |= 1 << (option - OPT_TL_X);

      assert (option <= 31);
      s->value_changed |= 1 << option;

      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_XFER_SCALE:
	case OPT_DEPTH:
	  if (!s->scanning && info && s->val[option].w != *(SANE_Word *) val)
	    /* only signal the reload params if we're not scanning---no point
	       in creating the frontend useless work */
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  /* fall through */
	case OPT_NUM_OPTS:
	case OPT_TEST:
	case OPT_DESPECKLE:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL:
	case OPT_HUE:
	case OPT_SATURATION:
	  s->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* options with side-effects: */
	case OPT_RESOLUTION:
	  old_val = s->val[OPT_RESOLUTION].s;

	  if (strcmp (old_val, val) != 0)
	    return SANE_STATUS_GOOD;	/* no change */

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS;
	      if (!s->scanning)
		*info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  free (old_val);
	  s->val[OPT_RESOLUTION].s = strdup (val);

	  /* low-resolution mode: */
	  old_res = s->resolution;
	  s->resolution = QC_RES_LOW;
	  if (strcmp (val, resolution_list[QC_RES_HIGH]) == 0)
	    /* high-resolution mode: */
	    s->resolution = QC_RES_HIGH;
	  s->opt[OPT_TL_X].constraint.range = &x_range[s->resolution];
	  s->opt[OPT_BR_X].constraint.range = &odd_x_range[s->resolution];
	  s->opt[OPT_TL_Y].constraint.range = &y_range[s->resolution];
	  s->opt[OPT_BR_Y].constraint.range = &odd_y_range[s->resolution];

	  if (old_res == QC_RES_LOW && s->resolution == QC_RES_HIGH)
	    {
	      for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
		s->val[i].w *= 2;
	      s->val[OPT_BR_X].w += 1;
	      s->val[OPT_BR_Y].w += 1;
	      s->opt[OPT_TEST].cap |= SANE_CAP_INACTIVE;
	    }
	  else if (old_res == QC_RES_HIGH && s->resolution == QC_RES_LOW)
	    {
	      for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
		s->val[i].w /= 2;
	      s->opt[OPT_TEST].cap &= ~SANE_CAP_INACTIVE;
	    }

	  if (!(s->user_corner & 0x4))
	    s->val[OPT_BR_X].w = odd_x_range[s->resolution].max;
	  if (!(s->user_corner & 0x8))
	    s->val[OPT_BR_Y].w = odd_y_range[s->resolution].max - 4;

	  /* make sure the affected options have valid values: */
	  for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
	    if (s->val[i].w > s->opt[i].constraint.range->max)
	      s->val[i].w = s->opt[i].constraint.range->max;

          DBG (5, "sane_control_option: exit\n");
	  return SANE_STATUS_GOOD;
	}
    }
  else if (action == SANE_ACTION_SET_AUTO)
    {
      switch (option)
	{
	case OPT_BRIGHTNESS:
	  /* not implemented yet */
          DBG (5, "sane_control_option: exit\n");
	  return SANE_STATUS_GOOD;

	default:
	  break;
	}
    }

  DBG (5, "sane_control_option: NOK exit\n");
  return SANE_STATUS_INVAL;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  QC_Scanner *s = handle;
  QC_Device *q = s->hw;
  int xfer_scale;
  size_t Bpp = 3;		/* # of bytes per pixel */

  DBG (5, "sane_get_parameters: enter\n");
  
  if (!s->scanning)
    {
      /* Only compute new parameters when not scanning---allows
         changing width/height etc while scan is in progress.  */
      xfer_scale = s->val[OPT_XFER_SCALE].w;

      s->params.format = SANE_FRAME_RGB;
      if (q->version != QC_COLOR)
	{
	  s->params.format = SANE_FRAME_GRAY;
	  Bpp = 1;
	}
      s->params.last_frame = SANE_TRUE;

      s->params.pixels_per_line = s->val[OPT_BR_X].w - s->val[OPT_TL_X].w + 1;
      s->params.pixels_per_line /= xfer_scale;
      s->params.pixels_per_line &= ~1UL;	/* ensure it's even */
      if (s->params.pixels_per_line < 2)
	s->params.pixels_per_line = 2;

      s->params.lines = s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w + 1;
      s->params.lines /= xfer_scale;
      if (s->params.lines < 1)
	s->params.lines = 1;

      s->params.bytes_per_line = Bpp * s->params.pixels_per_line;
      s->params.depth = 8;
    }
  if (params)
    *params = s->params;

  DBG (5, "sane_get_parameters: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  int top, left, width, height, undecimated_width, undecimated_height;
  QC_Scanner *s = handle;
  QC_Device *q = s->hw;
  QC_Scan_Request req;

  DBG (5, "sane_start: enter\n");
  
  if (s->scanning)
    return SANE_STATUS_DEVICE_BUSY;

  if (s->reader_pid < 0)
    {
      int p2c_pipe[2];		/* parent->child pipe */
      int c2p_pipe[2];		/* child->parent pipe */

      if (pipe (p2c_pipe) < 0 || pipe (c2p_pipe) < 0)
	{
	  DBG (3, "start: failed to create pipes\n");
	  return SANE_STATUS_IO_ERROR;
	}

      s->reader_pid = fork ();
      if (s->reader_pid == 0)
	{
	  /* this is the child */
	  signal (SIGHUP, SIG_DFL);
	  signal (SIGINT, SIG_DFL);
	  signal (SIGPIPE, SIG_DFL);
	  signal (SIGTERM, SIG_DFL);
	  _exit (reader_process (s, p2c_pipe[0], c2p_pipe[1]));
	}
      close (p2c_pipe[0]);
      close (c2p_pipe[1]);
      s->to_child = p2c_pipe[1];
      s->from_child = c2p_pipe[0];
    }

  s->read_fd = dup (s->from_child);
  sane_get_parameters (s, 0);	/* ensure uptodate parameters */

  qc_lock (q);
  s->holding_lock = SANE_TRUE;

  if (q->version == QC_COLOR)
    {
      qc_send (q, QC_SET_SPEED);
      qc_send (q, 2);

      /* wait for camera to become ready: */
      while (qc_getstatus (q) & CameraNotReady)
	usleep (10000);

      /* Only send black_level if necessary; this optimization may
         fail if two applications access the camera in an interleaved
         fashion; but the black-level command is slow enough that it
         cannot be issued for every image acquisition.  */
      if (s->value_changed & (1 << OPT_BLACK_LEVEL))
	{
	  s->value_changed &= ~(1 << OPT_BLACK_LEVEL);

	  qc_send (q, QC_SET_BLACK);
	  qc_send (q, s->val[OPT_BLACK_LEVEL].w);

	  DBG (3, "start: black_level=%d\n", s->val[OPT_BLACK_LEVEL].w);

	  /* wait for set black level command to finish: */
	  while (qc_getstatus (q) & (CameraNotReady | BlackBalanceInProgress))
	    usleep (10000);
	}

      if (s->value_changed & (1 << OPT_HUE))
	{
	  s->value_changed &= ~(1 << OPT_HUE);
	  qc_send (q, QC_COL_SET_HUE);
	  qc_send (q, s->val[OPT_HUE].w);
	}

      if (s->value_changed & (1 << OPT_SATURATION))
	{
	  s->value_changed &= ~(1 << OPT_SATURATION);
	  qc_send (q, QC_SET_SATURATION);
	  qc_send (q, s->val[OPT_SATURATION].w);
	}
    }

  if (q->version != QC_COLOR)
    qc_reset (q);

  if (s->value_changed & (1 << OPT_CONTRAST))
    {
      s->value_changed &= ~(1 << OPT_CONTRAST);
      qc_send (q, ((q->version == QC_COLOR)
		   ? QC_COL_SET_CONTRAST : QC_MONO_SET_CONTRAST));
      qc_send (q, s->val[OPT_CONTRAST].w);
    }

  if (s->value_changed & (1 << OPT_BRIGHTNESS))
    {
      s->value_changed &= ~(1 << OPT_BRIGHTNESS);
      qc_send (q, QC_SET_BRIGHTNESS);
      qc_send (q, s->val[OPT_BRIGHTNESS].w);
    }

  width = s->params.pixels_per_line;
  height = s->params.lines;
  if (s->resolution == QC_RES_HIGH)
    {
      width /= 2;		/* the expansion occurs through oversampling */
      height /= 2;		/* we acquire only half the lines that we generate */
    }
  undecimated_width = width * s->val[OPT_XFER_SCALE].w;
  undecimated_height = height * s->val[OPT_XFER_SCALE].w;

  s->num_bytes = 0;
  s->bytes_per_frame = s->params.lines * s->params.bytes_per_line;

  qc_send (q, QC_SET_NUM_V);
  qc_send (q, undecimated_height);

  if (q->version == QC_COLOR)
    {
      qc_send (q, QC_SET_NUM_H);
      qc_send (q, undecimated_width / 2);
    }
  else
    {
      int val, val2;

      if (q->port_mode == QC_UNIDIR && s->val[OPT_DEPTH].w == 6)
	{
	  val = undecimated_width;
	  val2 = s->val[OPT_XFER_SCALE].w * 4;
	}
      else
	{
	  val = undecimated_width * s->val[OPT_DEPTH].w;
	  val2 =
	    ((q->port_mode == QC_BIDIR) ? 24 : 8) * s->val[OPT_XFER_SCALE].w;
	}
      val = (val + val2 - 1) / val2;
      qc_send (q, QC_SET_NUM_H);
      qc_send (q, val);
    }

  left = s->val[OPT_TL_X].w / 2;
  top = s->val[OPT_TL_Y].w;
  if (s->resolution == QC_RES_HIGH)
    {
      left /= 2;
      top /= 2;
    }

  DBG (3, "sane_start: top=%d, left=%d, white=%d, bright=%d, contr=%d\n",
       top, left, s->val[OPT_WHITE_LEVEL].w, s->val[OPT_BRIGHTNESS].w,
       s->val[OPT_CONTRAST].w);

  qc_send (q, QC_SET_LEFT);
  qc_send (q, left);

  qc_send (q, QC_SET_TOP);
  qc_send (q, top + 1);		/* not sure why this is so... ;-( */

  if (s->value_changed & (1 << OPT_WHITE_LEVEL))
    {
      s->value_changed &= ~(1 << OPT_WHITE_LEVEL);
      qc_send (q, QC_SET_WHITE);
      qc_send (q, s->val[OPT_WHITE_LEVEL].w);
    }

  DBG (2, "start: %s %d lines of %d pixels each (%ld bytes) => %dx%d\n",
       (q->port_mode == QC_BIDIR) ? "bidir" : "unidir",
       height, width, (long) s->bytes_per_frame,
       s->params.pixels_per_line, s->params.lines);

  /* send scan request to reader process: */
  qc_setscanmode (s, &req.mode);
  req.num_bytes = width * height;
  if (q->version == QC_COLOR)
    {
      if (s->resolution == QC_RES_LOW)
	req.num_bytes *= 3;
      else
	req.num_bytes *= 4;
    }
  req.resolution = s->resolution;
  req.params = s->params;
  req.despeckle = s->val[OPT_DESPECKLE].w;
  write (s->to_child, &req, sizeof (req));

  s->scanning = SANE_TRUE;
  s->deliver_eof = 0;

  DBG (5, "sane_start: exit\n");
  
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * lenp)
{
  SANE_Status status;
  QC_Scanner *s = handle;
  QC_Device *q = s->hw;
  ssize_t nread;
  size_t len;

  DBG (5, "sane_read: enter\n");
  
  *lenp = 0;

  if (s->deliver_eof)
    {
      s->deliver_eof = 0;
      return SANE_STATUS_EOF;
    }

  if (!s->scanning)
    return SANE_STATUS_CANCELLED;

  len = max_len;
  if (s->num_bytes + len > s->bytes_per_frame)
    len = s->bytes_per_frame - s->num_bytes;

  DBG (8, "read(buf=%p,num_bytes=%ld,max_len=%d,len=%ld)\n",
       buf, (long) s->num_bytes, max_len, (long) len);

  nread = read (s->read_fd, buf, len);
  if (nread <= 0)
    {
      if (nread == 0 || errno == EAGAIN)
	{
	  DBG (3, "read: no more data available\n");
	  return SANE_STATUS_GOOD;
	}
      DBG (3, "read: short read (%s)\n", strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  if (nread > 0 && s->holding_lock)
    {
      status = qc_unlock (q);	/* now we can unlock the camera */
      if (status != SANE_STATUS_GOOD)
	      DBG(3, "sane_read: qc_unlock error\n");
      s->holding_lock = SANE_FALSE;
    }

  s->num_bytes += nread;
  if (s->num_bytes >= s->bytes_per_frame)
    {
      s->scanning = SANE_FALSE;
      close (s->read_fd);
      s->read_fd = -1;
      s->deliver_eof = 1;
    }

  if (lenp)
    *lenp = nread;

  DBG (5, "sane_read: exit, read got %d bytes\n", *lenp);
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  QC_Scanner *s = handle;
  SANE_Bool was_scanning;
  SANE_Status status;

  DBG (5, "sane_cancel: enter\n");
  
  was_scanning = s->scanning;
  s->scanning = SANE_FALSE;
  s->deliver_eof = 0;
  if (s->read_fd >= 0)
    {
      close (s->read_fd);
      s->read_fd = -1;
    }

  if (s->reader_pid >= 0 && was_scanning)
    {
      char buf[1024];
      ssize_t nread;
      int flags;

      DBG (1, "cancel: cancelling read request\n");

      kill (s->reader_pid, SIGINT);	/* tell reader to stop reading */

      /* save non-blocking i/o flags: */
      flags = fcntl (s->from_child, F_GETFL, 0);

      /* block until we read at least one byte: */
      read (s->from_child, buf, 1);

      /* put descriptor in non-blocking i/o: */
      fcntl (s->from_child, F_SETFL, O_NONBLOCK);

      /* read what's left over in the pipe/file buffer: */
      do
	{
	  while ((nread = read (s->from_child, buf, sizeof (buf))) > 0);
	  usleep (100000);
	  nread = read (s->from_child, buf, sizeof (buf));
	}
      while (nread > 0);

      /* now restore non-blocking i/o flag: */
      fcntl (s->from_child, F_SETFL, flags & O_NONBLOCK);

      waitpid (s->reader_pid, 0, 0);
      s->reader_pid = 0;

      DBG (1, "cancel: cancellation completed\n");
    }
  if (s->holding_lock)
    {
      status = qc_unlock (s->hw);
      if (status != SANE_STATUS_GOOD)
	      DBG(3, "sane_cancel: qc_unlock error\n");
      s->holding_lock = SANE_FALSE;
    }
  DBG (5, "sane_cancel: exit\n");
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  QC_Scanner *s = handle;

  DBG (5, "sane_set_io_mode: enter\n");
  
  if (!s->scanning)
    return SANE_STATUS_INVAL;

  if (fcntl (s->read_fd, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
    return SANE_STATUS_IO_ERROR;
  DBG (5, "sane_set_io_mode: exit\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  QC_Scanner *s = handle;

  DBG (5, "sane_get_select_fd: enter\n");
  if (!s->scanning)
    return SANE_STATUS_INVAL;

  *fd = s->read_fd;
  DBG (5, "sane_get_select_fd: exit\n");
  return SANE_STATUS_GOOD;
}
