/* sane - Scanner Access Now Easy.

   Copyright (C) 2004-2006 Gerard Klaver  <gerard at gkall dot hobby dot nl>
   The teco2 and gl646 backend (Frank Zago) are used as a template for 
   this backend, some parts of the pdc640c. camlib libgphoto2 are used.
   
   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. 
*/

/*  $Id: gt89xx.c,v 1.1 2006/09/02 15:55:17 gerard Exp $

   Grantech gt8911  camera driver Gerard Klaver
   start 26-09-2004 update 02-09-2006
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach vidcams
   . - sane_get_devices() : query list of vidcam devices
   . - sane_open() : open a particular vidcam device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get vidcam fd
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . . (sane_read called multiple times; after sane_read returns EOF, 
   . . loop may continue with sane_start which may return a 2nd page
   . . when doing duplex scans, or load the next page from the ADF)
   . .
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened vidcam device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2004/09/26  update 02-09-2006 */
#define BACKEND_NAME gt89xx
#define GT89XX_CONFIG_FILE "gt89xx.conf"

/* --------------------- SANE INTERNATIONALISATION ------------------------ */

/* must be first include */
#include "../include/sane/config.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.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 "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/lassert.h"

/* for add-text routine  */
#include <time.h>
#include "../include/font_6x11.h"
/*-----------------------*/

#include "gt89xx.h"

#define TIMEOUT 1000

#ifndef PATH_MAX
#define PATH_MAX (1024)
#endif

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  COLOR_RAW_STR,
  COLOR_8_STR,
  COLOR_5COMP_STR,
  COLOR_RGB_TEXT_STR,
  SANE_VALUE_SCAN_MODE_LINEART,
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,

  NULL
};

/*--------------------------------------------------------------------------*/
/*-----------------------------------------minium, maximum, quantization----*/
static const SANE_Range brightness_range = { 0, 255, 1 };

static const SANE_Range contrast_range = { 0, 255, 1 };

static const SANE_Range hue_range = { 0, 255, 1 };

static const SANE_Range saturation_range = { 0, 255, 1 };

static const SANE_Range threshold_range = { 0, 255, 0 };

static const SANE_Range black_level_range = { 0, 255, 1 };

static const SANE_Range red_level_range = { 0, 64, 1 };

static const SANE_Range green_level_range = { 0, 64, 1 };


static const SANE_Range blue_level_range = { 0, 64, 1 };

/*--------------------------------------------------------------------------*/

static const struct dpi_color_adjust gt8911_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B, 0 (-) or 1 (+) color skewing, lines skewing */
  {160, 120, 1, 0, 2, 1, 2},
  {320, 240, 1, 0, 2, 1, 2},
  {640, 480, 1, 0, 2, 1, 2},
  /* must be the last entry */
  {0, 0, 0, 0, 0, 0, 0}
};

static const struct dpi_color_adjust gt89xx_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B, 0 (-) or 1 (+) color skewing, lines skewing */
  {160, 120, 1, 0, 2, 1, 2},
  {176, 144, 1, 0, 2, 1, 2},
  {352, 288, 1, 0, 2, 1, 2},
  /* must be the last entry */
  {0, 0, 0, 0, 0, 0, 0}
};

/* For all vidcams. apear in the gt8911 and gt89xx_dpi_color_adjust list. */
#define DEF_RESOLUTION 320

static const struct vidcam_hardware vidcams[] = {
/* vid, pid, class, name, name, max x and y resolution, res. table name    */
  {0x0797, 0x801c, USB_CLASS_VENDOR_SPEC, "Grandtech", "ScopeCam", 640, 480,
   gt8911_dpi_color_adjust},

  {0x0797, 0x8001, USB_CLASS_VENDOR_SPEC, "Grandtech", "SmartCam", 640, 480,
   gt8911_dpi_color_adjust},

  {0x5da, 0x1006, USB_CLASS_VENDOR_SPEC, "Jenoptik", "JD350 entrance", 320,
   240, gt8911_dpi_color_adjust},

  {0xd96, 0x0000, USB_CLASS_VENDOR_SPEC, "Jenoptik", "JD350 video", 320, 240,
   gt8911_dpi_color_adjust},

  {0x797, 0x8901, USB_CLASS_VENDOR_SPEC, "ScanHex", "SX-35a", 320, 240,
   gt8911_dpi_color_adjust},

  {0x797, 0x8909, USB_CLASS_VENDOR_SPEC, "ScanHex", "SX-35b", 320, 240,
   gt8911_dpi_color_adjust},

  {0x797, 0x8911, USB_CLASS_VENDOR_SPEC, "ScanHex", "SX-35c", 320, 240,
   gt8911_dpi_color_adjust},

  {0x84d, 0x1001, USB_CLASS_VENDOR_SPEC, "ScanHex", "SX-35d", 320, 240,
   gt8911_dpi_color_adjust},

  {0x797, 0x801a, USB_CLASS_VENDOR_SPEC, "Typhoon", "StyloCam", 320, 240,
   gt8911_dpi_color_adjust},

  {0x6d6, 0x002e, USB_CLASS_VENDOR_SPEC, "Trust", "PowerC@m 350FS", 320, 240,
   gt8911_dpi_color_adjust},

  {0x6d6, 0x002d, USB_CLASS_VENDOR_SPEC, "Trust", "PowerC@m 350FT", 320, 240,
   gt8911_dpi_color_adjust},

  {0xd64, 0x1001, USB_CLASS_VENDOR_SPEC, "SiPix", "Stylecam", 320, 240,
   gt8911_dpi_color_adjust},
  /* http://www.umax.de/digicam/AstraPix320S.htm */
  /* reportedly has same ids as SiPix StyleCam and also
   * looks identical. 
   {"UMAX AstraPix 320s", 0xd64, 0x1001, */

  /* http://www.dlink.com/products/usb/dsc350/ */
  /* ids from driver download */
  {0xd64, 0x1021, USB_CLASS_VENDOR_SPEC, "D-Link", "DSC 350F", 352, 288,
   gt89xx_dpi_color_adjust}
};

/* List of vidcams attached. */
static Gt89xx_Vidcam *first_dev = NULL;
static int num_devices = 0;
/* used by sane_get_devices */
static const SANE_Device **devlist = NULL;

/*----------------------------------------------------------- */

/* Local functions. */

/* Display a buffer in the log. Display by lines of 16 bytes. */
static void
hexdump (int level, const char *comment, unsigned char *buf, const int length)
{
  int i;
  char line[128];
  char *ptr;
  char asc_buf[17];
  char *asc_ptr;

  DBG (level, "  %s\n", comment);

  i = 0;
  goto start;

  do
    {
      if (i < length)
	{
	  ptr += sprintf (ptr, " %2.2x", *buf);

	  if (*buf >= 32 && *buf <= 127)
	    {
	      asc_ptr += sprintf (asc_ptr, "%c", *buf);
	    }
	  else
	    {
	      asc_ptr += sprintf (asc_ptr, ".");
	    }
	}
      else
	{
	  /* After the length; do nothing. */
	  ptr += sprintf (ptr, "   ");
	}

      i++;
      buf++;

      if ((i % 16) == 0)
	{
	  /* It's a new line */
	  DBG (level, "  %s    %s\n", line, asc_buf);

	start:
	  ptr = line;
	  *ptr = '\0';
	  asc_ptr = asc_buf;
	  *asc_ptr = '\0';

	  ptr += sprintf (ptr, "  %3.3d:", i);
	}

    }
  while (i < ((length + 15) & ~15));
}

/* Returns the length of the longest string, including the terminating
 * character. */
static size_t
max_string_size (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;
}

/* Lookup a string list from one array and return its index. */
static int
get_string_list_index (SANE_String_Const list[], SANE_String_Const name)
{
  int index;

  index = 0;
  while (list[index] != NULL)
    {
      if (strcmp (list[index], name) == 0)
	{
	  return (index);
	}
      index++;
    }

  DBG (DBG_error, "name %s not found in list\n", name);

  assert (0);			/* bug in backend, core dump */

  return (-1);
}

/* Initialize a vidcam entry. Return an allocated vidcam with some
 *  */
static Gt89xx_Vidcam *
gt89xx_init (void)
{
  Gt89xx_Vidcam *dev;

  DBG (DBG_proc, "gt89xx_init: enter\n");

  /* Allocate a new vidcam entry. */
  dev = calloc (1, sizeof (Gt89xx_Vidcam));
  if (dev == NULL)
    {
      return NULL;
    }
  memset (dev, 0, sizeof (Gt89xx_Vidcam));
/* Allocate the windoww buffer*/
  dev->windoww_size = 0x40;
  dev->windoww = malloc (dev->windoww_size);
  if (dev->windoww == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windowr buffer*/
  dev->windowr_size = 0x40;
  dev->windowr = malloc (dev->windowr_size);
  if (dev->windowr == NULL)
    {
      free (dev);
      free (dev->windoww);
      return NULL;
    }

  dev->fd = -1;

  DBG (DBG_proc, "gt89xx_init: exit\n");

  return (dev);
}

static SANE_Status
gt89xx_init_2 (Gt89xx_Vidcam *dev)
{
  SANE_Status status; 

  DBG (DBG_proc, "gt89xx_init_2: enter\n");

  /* Allocate the buffer used to transfer the USB data */
  /* Check for max. format image size so buffer size can
   * be adjusted, format from camera is bayer 422 */
  if (dev->CIF)
      dev->buffer_size = 356 * 292;
  if (dev->VGA)
      dev->buffer_size = 644 * 484;
  DBG (DBG_proc, "gt89xx_init_2: dev->bufffer = 0x%lx\n", (unsigned long) (size_t) dev->buffer_size);

  dev->buffer = malloc (dev->buffer_size);

  if (dev->buffer == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      return SANE_STATUS_NO_MEM;
    }

  /* Allocate the output buffer used for bayer conversion */
  dev->output_size = dev->buffer_size * 3;

  dev->output = malloc (dev->output_size);
  if (dev->output == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);

      return SANE_STATUS_NO_MEM;
    }
  dev->image_size = dev->buffer_size;
  
  dev->image = malloc (dev->image_size);
  if (dev->image == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);
      free (dev->output);
    
       return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_proc, "gt89xx_init_2: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* Closes an open vidcams. */
static void
gt89xx_close (Gt89xx_Vidcam * dev)
{
  DBG (DBG_proc, "gt89xx_close: enter \n");

  if (dev->fd != -1)
    {
      sanei_usb_close (dev->fd);
      dev->fd = -1;
    }

  DBG (DBG_proc, "gt89xx_close: exit\n");
}

/* Frees the memory used by a vidcam. */
static void
gt89xx_free (Gt89xx_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "gt89xx_free: enter\n");

  if (dev == NULL)
    return;

  gt89xx_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
  if (dev->output)
    {
      free (dev->output);
    }
  if (dev->image)
    {
      free (dev->image);
    }
  if (dev->windoww)
    {
      free (dev->windoww);
    }
  if (dev->windowr)
    {
      free (dev->windowr);
    }

  for (i = 1; i < OPT_NUM_OPTIONS; i++)
    {
      if (dev->opt[i].type == SANE_TYPE_STRING && dev->val[i].s)
	{
	  free (dev->val[i].s);
	}
    }
  if (dev->resolutions_list)
    free (dev->resolutions_list);

  free (dev);

  DBG (DBG_proc, "gt89xx_free: exit\n");
}

static SANE_Status
gt89xx_set_config (Gt89xx_Vidcam * dev, int configuration, int interface,
		   int alternate)
{
  SANE_Status status;
  DBG (DBG_proc, "gt89xx_set_config: open\n");
 /* 
  status = sanei_usb_set_configuration (dev->fd, configuration);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gt89xx_vidcam_init: GT89XX FAILED to set configuration %d\n",
	   configuration);
      return status;
    }
*/
  status = sanei_usb_claim_interface (dev->fd, interface);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gt89xx_vidcam_init: GT89XX FAILED to claim interface\n");
      return status;
    }

  status = sanei_usb_set_altinterface (dev->fd, alternate);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gt89xx_vidcam_init: GT89XX FAILED to set alternate %d\n",
	   alternate);
      return status;
    }
  DBG (DBG_proc,
       "gt89xx_vidcam_init: configuration=%d, interface=%d, alternate=%d\n",
       configuration, interface, alternate);

  DBG (DBG_proc, "gt89xx_set_config: exit\n");
  return status;
}

static SANE_Status
gt89xx_vidcam_init (Gt89xx_Vidcam * dev)
{
  SANE_Byte val;
  int command;
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;
  size_t sizew;
  size_t sizer;
  size_t size;			/* significant size of window */

  SANE_Byte window[0x40];
  DBG (DBG_proc, "gt89xx_vidcam_init: open\n");

  sanei_usb_get_vendor_product (dev->fd, &vendor, &product);
  command = 0;
  i = 0;

  /* size of the whole windows block */
  size = 0x40;
  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  /* Check that window is big enough */
  assert (size <= sizeof (window));

  memset (window, 0, size);

  hexdump (DBG_info2, "windows", window, size);

  /*  configuration = 1, interface = 0, alternate = 0 */
	  status = gt89xx_set_config (dev, 1, 0, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gt89xx_vidcam_init: GT89XX FAILED to set configure\n");
	      return status;
	    }

  /* urb0, reset device  */
/* 	status = sanei_usb_control_msg (dev->fd, 0x00, 0x01, 0x01, 0x00, 0x00, val);
 	if (status != SANE_STATUS_GOOD) goto done; 
 	DBG (DBG_proc, "12h bytes val: %s\n", val);
 	command++; 
*/

  /*      status = !SANE_STATUS_GOOD;
     DBG (DBG_proc, "routine to bypass init part\n");
     if (status !=SANE_STATUS_GOOD) goto done1;
   */
  /* urb 5, first read */
  size = 0x40;
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x10, 0x018c, 0x3800, size, window);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;
  hexdump (DBG_info2, "urb 5", window, size);
 
 usleep (10000);
 
 /* urb 6, read  */
  size = 0x40;
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x10, 0x058a, 0x4200, size, window);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;
  val = 0x0;
  hexdump (DBG_info2, "urb6", window, size);
  status = (SANE_STATUS_GOOD);
  return status;
done:
  if (status)
    {
      DBG (DBG_error, "gt89xx_vidcam_init failed for command %d: %s\n",
	   command, sane_strstatus (status));
    }
  return status;
}

/* Reset vidcam */
static SANE_Status
gt89xx_reset_window (Gt89xx_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "gt89xx_reset_window: enter\n");
  status = SANE_STATUS_GOOD;
  DBG (DBG_proc, "gt89xx_reset_window: leave, status=%d\n", status);

  return status;
}

/* Read the size of the scan, for vidcam ? */
static SANE_Status
gt89xx_get_scan_size (Gt89xx_Vidcam * dev)
{
  size_t size;
  SANE_Status status;
  int bytes_pixel;

  DBG (DBG_proc, "gt89xx_get_scan_size: enter\n");

  switch (dev->scan_mode)
    {
    case GT89XX_LINEART:
      bytes_pixel = 3;
      break;
    case GT89XX_GRAY:
      bytes_pixel = 1;
      break;
    case GT89XX_COLOR_RAW:
      bytes_pixel = 3;
      break;
    case GT89XX_COLOR_8:
      bytes_pixel = 1;
      break;
    case GT89XX_COLOR_5COMP:
      bytes_pixel = 1;
      break;
    case GT89XX_COLOR:
    case GT89XX_COLOR_RGB:
    case GT89XX_COLOR_RGB_TEXT:
      bytes_pixel = 1;
      break;
    default:
      bytes_pixel = 3;
      break;
    }
  switch (dev->val[OPT_RESOLUTION].w)
    {
    case 160:
      dev->params.pixels_per_line = 160;
      dev->params.lines = 120;
      break;
    case 320:
      dev->params.pixels_per_line = 320;
      dev->params.lines = 240;
      break;
    case 640:
      dev->params.pixels_per_line = 640;
      dev->params.lines = 480;
      break;
    }

  dev->params.bytes_per_line = dev->params.pixels_per_line * bytes_pixel;

  DBG (DBG_proc,
       "gt89xx_get_scan_size: , param.bytes_per_line =0x%x bytes_pixel 0x%x\n",
       dev->params.bytes_per_line, bytes_pixel);

  status = SANE_STATUS_GOOD;	/* no correct test yet */

  DBG (DBG_proc, "gt89xx_get_scan_size: exit, status=%d\n", status);

  return (status);
}

/* Wait until the vidcam is ready to send the data. This is the
   almost the same code as gt89xx_get_scan_size(). This function has to
   be called once after the scan has started. */
static SANE_Status
gt89xx_wait_for_data (Gt89xx_Vidcam * dev)
{
  SANE_Status status;
  int i;

  DBG (DBG_proc, "gt89xx_wait_for_data: enter\n");

  for (i = 0; i < 60; i++)
    {

      status = SANE_STATUS_GOOD;

      if (i == 59)
	{
	  return (SANE_STATUS_GOOD);
	}

/*	  sleep (1);  */
    }

  return SANE_STATUS_GOOD;
  DBG (DBG_proc, "gt89xx_wait_for_data: vidcam not ready to send data (%d)\n",
       status);

  return SANE_STATUS_DEVICE_BUSY;
}

static SANE_Status
gt89xx_scan (Gt89xx_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "gt89xx_scan: enter\n");


  status = SANE_STATUS_GOOD;

  DBG (DBG_proc, "gt89xx_scan: exit, status=%d\n", status);

  return status;
}

/* Inquiry a device and returns TRUE if is supported. */
static int
gt89xx_identify_scanner (Gt89xx_Vidcam * dev)
{
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;
  /* char req[64]; */
  SANE_Byte req[0x40];
  size_t sizer;

  DBG (DBG_info, "gt89xx_identify_vidcam: open\n");
  status = sanei_usb_get_vendor_product (dev->fd, &vendor, &product);

  /* Loop through our list to make sure this scanner is supported. */
  for (i = 0; i < NELEMS (vidcams); i++)
    {
      if (vidcams[i].vendor == vendor && vidcams[i].product == product)
	{
	  unsigned char val;

	  DBG (DBG_info,
	       "gt89xx_identify_scanner: scanner %x:%x is in list\n", vendor,
	       product);

	  memset (req, 0, 0x40);
	  sizer = 0x40;
	  req[0] = 0x55;
	  req[1] = 0x66;

	  /*  status = usb_control_msg (handle, 0xc0, 0x10, 0x41, 0x0000, req, 64, TIMEOUT); */
	  status =
	    sanei_usb_control_msg (dev->fd, 0xc0, 0x10, 0x41, 0x0000, sizer,
				   req);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gt89xx_identify_vidcam: this is not a GT8911 (idVendor = 0x%x, bProduct = 0x%x) writing register failed with %s\n",
		   vendor, product, sane_strstatus (status));
	      return SANE_FALSE;
	    }
	  /*         status = usb_control_msg (handle, 0xc0, 0x10, 0x05, 0x0000, req, 64, TIMEOUT);   */
usleep (10000);
          status =
	    sanei_usb_control_msg (dev->fd, 0xc0, 0x10, 0x05, 0x0000, sizer,
				   req);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gt89xx_identify_vidcam: this is not a gt89xx (idVendor = %d, bProduct = %d), read register failed %s\n",
		   vendor, product, sane_strstatus (status));
	      return SANE_FALSE;
	    }
	  /* tested on model hardware version 0xffffffc0, firmware version 0x10)) */
	  DBG (DBG_proc,
	       "gt89xx_identify_vidcam: control message (version: hardware: %0x / firmware: %0x)\n",
	       req[0], req[1]);

	  DBG (DBG_info, "gt89xx_identify_vidcam: vidcam supported\n");
	  dev->hw = &(vidcams[i]);
	  return SANE_TRUE;

	}
    }
  DBG (DBG_error, "gt89xx_identify_vidcam: this is not a GT89XX exit\n");
  return SANE_FALSE;
}

/* Attach a vidcam to this backend. */
static SANE_Status
attach_vidcam (SANE_String_Const devicename, Gt89xx_Vidcam ** devp)
{
  Gt89xx_Vidcam *dev;
  int fd;
  SANE_Status status;

  DBG (DBG_proc, "attach_vidcam: %s\n", devicename);

  if (devp)
    *devp = NULL;

  /* Check if we know this device name. */
  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (DBG_info, "device is already known\n");
	  return SANE_STATUS_GOOD;
	}
    }

  /* Allocate a new vidcam entry. */
  dev = gt89xx_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_info, "attach_vidcam: opening USB device %s\n", devicename);

  if (sanei_usb_open (devicename, &fd) != 0)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: open failed\n");
      gt89xx_free (dev);
      return SANE_STATUS_INVAL;
    }
  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->fd = fd;

  /* Now, check that it is a vidcam we support. */

  if (gt89xx_identify_scanner (dev) == SANE_FALSE)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: vidcam-identification failed\n");
      gt89xx_free (dev);
      return SANE_STATUS_INVAL;
    }

  /* Allocate a buffer memory. */
  status = gt89xx_init_2 (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gt89xx_initi_2, ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  gt89xx_close (dev);

  /* Build list of vidcam supported resolutions. */
  DBG (DBG_proc, "attach_vidcam: build resolution list\n");

  if (dev->hw->color_adjust[0].resolution_x != 0)
    {
      int num_entries;
      int i;
      num_entries = 0;

      while (dev->hw->color_adjust[num_entries].resolution_x != 0)
	num_entries++;

      dev->resolutions_list = malloc (sizeof (SANE_Word) * (num_entries + 1));

      if (dev->resolutions_list == NULL)
	{
	  DBG (DBG_error,
	       "ERROR: attach_vidcam: vidcam resolution list failed\n");
	  gt89xx_free (dev);
	  return SANE_STATUS_NO_MEM;
	}

      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_vidcam: make color resolution table \n");
      for (i = 0; i < num_entries; i++)
	{
	  dev->resolutions_list[i + 1] =
	    dev->hw->color_adjust[i].resolution_x;
	}
    }
  else
    {
      dev->resolutions_list = NULL;
    }

  /* Set the default options for that vidcam. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("vidcam/webcam");

  /* Link the vidcam with the others. */
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

  DBG (DBG_proc, "attach_vidcam: exit\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *dev)
{
  DBG (DBG_proc, "attach_one: open \n");
  attach_vidcam (dev, NULL);
  DBG (DBG_proc, "attach_one: exit \n");
  return SANE_STATUS_GOOD;
}

/* Reset the options for that vidcam. */
static void
gt89xx_init_options (Gt89xx_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "gt89xx_init_options: open\n");

  /* Pre-initialize the options. */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

  for (i = 0; i < OPT_NUM_OPTIONS; ++i)
    {
      dev->opt[i].size = sizeof (SANE_Word);
      dev->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
  /*  Default settings */
  /*
   */
  DBG (DBG_proc, "gt89xx_init_options: done loop opt_num_options=%d, i=%d \n",
       OPT_NUM_OPTIONS, i);
  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = "";
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = OPT_NUM_OPTIONS;

  /* Mode group */
  dev->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  dev->opt[OPT_MODE_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].cap = 0;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Scanner supported modes */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].size = max_string_size (scan_mode_list);
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list = scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup ("");	/* will be set later */

  /* X and Y resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;

  /* brightness   */
  dev->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  dev->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  dev->val[OPT_BRIGHTNESS].w = 80;	/* to get middle value */

  /* contrast     */
  dev->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  dev->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  dev->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  dev->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
  dev->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  dev->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_CONTRAST].constraint.range = &contrast_range;
  dev->val[OPT_CONTRAST].w = 80;	/* to get middle value */

  /* hue          */
  dev->opt[OPT_HUE].name = SANE_NAME_HUE;
  dev->opt[OPT_HUE].title = SANE_TITLE_HUE;
  dev->opt[OPT_HUE].desc = SANE_DESC_HUE;
  dev->opt[OPT_HUE].type = SANE_TYPE_INT;
  dev->opt[OPT_HUE].unit = SANE_UNIT_NONE;
  dev->opt[OPT_HUE].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_HUE].constraint.range = &hue_range;
  dev->val[OPT_HUE].w = 02;	/* to get middle value */

  /* saturation   */
  dev->opt[OPT_SATURATION].name = SANE_NAME_SATURATION;
  dev->opt[OPT_SATURATION].title = SANE_TITLE_SATURATION;
  dev->opt[OPT_SATURATION].desc = SANE_DESC_SATURATION;
  dev->opt[OPT_SATURATION].type = SANE_TYPE_INT;
  dev->opt[OPT_SATURATION].unit = SANE_UNIT_NONE;
  dev->opt[OPT_SATURATION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_SATURATION].constraint.range = &saturation_range;
  dev->val[OPT_SATURATION].w = 80;	/* to get middle value */


  /* Enhancement group */
  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Threshold */
  dev->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
  dev->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  dev->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
  dev->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
  dev->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
  dev->opt[OPT_THRESHOLD].size = sizeof (SANE_Int);
  dev->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_THRESHOLD].constraint.range = &threshold_range;
  dev->val[OPT_THRESHOLD].w = 128;

  /* black level correction */
  dev->opt[OPT_BLACK_LEVEL].name = SANE_NAME_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].title = SANE_TITLE_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].desc = SANE_DESC_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].type = SANE_TYPE_INT;
  dev->opt[OPT_BLACK_LEVEL].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BLACK_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BLACK_LEVEL].constraint.range = &black_level_range;
  dev->val[OPT_BLACK_LEVEL].w = 02;	/* to get middle value */

  /* red level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_R].name = SANE_NAME_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].title = SANE_TITLE_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].desc = SANE_DESC_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint.range = &red_level_range;
  dev->val[OPT_WHITE_LEVEL_R].w = 02;	/* to get middle value */

  /* green level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_G].name = SANE_NAME_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].title = SANE_TITLE_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].desc = SANE_DESC_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint.range = &green_level_range;
  dev->val[OPT_WHITE_LEVEL_G].w = 02;	/* to get middle value */

  /* blue level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_B].name = SANE_NAME_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].title = SANE_TITLE_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].desc = SANE_DESC_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint.range = &blue_level_range;
  dev->val[OPT_WHITE_LEVEL_B].w = 02;	/* to get middle value */

  /* preview */
  dev->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
  dev->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  dev->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
  dev->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
  dev->opt[OPT_PREVIEW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  dev->val[OPT_PREVIEW].w = SANE_FALSE;

  /* Lastly, set the default scan mode. This might change some
   * values previously set here. */

  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String_Const *) scan_mode_list[0], NULL);
  DBG (DBG_proc, "gt89xx_init_options: exit\n");
}

/* 
 * Wait until the vidcam is ready, can be much shorter dan for scanners.
 */
static SANE_Status
gt89xx_wait_vidcam (Gt89xx_Vidcam * dev)
{
  SANE_Status status;
  int timeout;

  DBG (DBG_proc, "gt89xx_wait_vidcam: enter\n");

  /* Set the timeout to 10 seconds. */
  timeout = 10;

  while (timeout > 0)
    {

      /* test unit ready */
/*      status = sanei_scsi_cmd (dev->fd, cdb.data, cdb.len, NULL, NULL);*/
      status = SANE_STATUS_GOOD;
      if (status == SANE_STATUS_GOOD)
	{
	  DBG (DBG_proc, "gt89xx_wait_vidcam: vidcam status good\n");
	  return SANE_STATUS_GOOD;
	}

      sleep (1);
    };

  DBG (DBG_proc, "gt89xx_wait_vidcam: vidcam not ready\n");
  return (SANE_STATUS_IO_ERROR);
}

#define COLOR_0 (color_adjust->z3_color_0)
#define COLOR_1 (color_adjust->z3_color_1)
#define COLOR_2 (color_adjust->z3_color_2)

/* Read the image from the vidcam and fill the temporary buffer with it. */
static SANE_Status
gt89xx_fill_image (Gt89xx_Vidcam * dev)
{
  SANE_Status status;
  size_t bulk_size_read;
  size_t size;
  size_t sizer;
  int command = 100;
  SANE_Byte cmd[8];
  size_t to_read;
  SANE_Int n_bytes;
  SANE_Int p_size;
  SANE_Byte val;
  unsigned char ep;
  SANE_Byte window[0x40];
  size_t iso_size_read;

  /* size of the whole windows block */
  size = 0x40;

  /* Check that window is big enough */
  assert (size <= sizeof (window));

  memset (window, 0, size);

  hexdump (DBG_info2, "windows", window, size);

  DBG (DBG_proc, "gt89xx_fill_image: enter\n");

  assert (dev->image_begin == dev->image_end);
  assert (dev->real_bytes_left > 0);

  dev->image_begin = 0;
  dev->image_end = 0;

usleep (10000);
 /*  configuration = 1, interface = 1 (0 for bulk), alternate = 1 (0 for bulk) */
	  status = gt89xx_set_config (dev, 1, 0, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gt89xx_vidcam_init: GT89XX FAILED to set configure\n");
	      return status;
	    }

  /* urb 7 *, end interface 1, alternate 1*, for bulk 0,0 */
usleep (10000);
  /* urb 8, read  */
  size = 0x40;
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x10, 0x018f, 0x3b00, size, window);
  if (status != SANE_STATUS_GOOD)
   return status;
  
  DBG (DBG_proc, "gt89xx_fill_image: real dev bytes left=0x%lx \n",
       (unsigned long) (size_t) dev->real_bytes_left);
  
  p_size = 0x0380;
  size = dev->bytes_left;
  ep = 0x83;
  usleep (8000);
  iso_size_read = dev->real_bytes_left;
 DBG (DBG_proc, "gt89xxs_fill_image: real dev bytes left=0x%lx \n",
       (unsigned long) (size_t) dev->real_bytes_left);
  bulk_size_read = dev->real_bytes_left;
 
  while (dev->real_bytes_left)
    {
      /* 
       * Try to read the maximum number of bytes.
       */
      DBG (DBG_proc,
	   "gt89xx_fill_image: real dev bytes left, while loop=0x%lx \n",
	   (unsigned long) (size_t) dev->real_bytes_left);

      size = dev->real_bytes_left;

      if (size < bulk_size_read)
	{
	  size = bulk_size_read;
	  DBG (DBG_proc,
	       "gt89xx_fill_image: size = 0x%lx \n", (unsigned long) (size_t) size);
	}
 /* 40000 or 64 test */
         if (size > 0x40)
         {
         size = 0x40;
         DBG (DBG_proc, "gt89xx_fill_image: size > 0x40 = 0x%lx \n", 
	 (unsigned long) (size_t) size);
         }
       
      if (size == 0)
	{
	  /* Probably reached the end of the buffer.  Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}
  
  
  /* DBG (DBG_proc, "gt89xx_fill_image: for first usb_iso_setup\n"); */

  

      DBG (DBG_proc,
	   "gt89xx_fill_image: dev->real_bytes_left: 0x%lx size: 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
      usleep (23000);
      /* urb 44 first read bulk */

      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);
      DBG (DBG_proc,
	   "gt89xx_fill_image: after first bulk read\n");
      usleep (230000);
      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{

      DBG (DBG_proc,
	   "gt89xx_fill_image: dev->real_bytes_left status NOK: 0x%lx size: 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
	  return status;
	}

      DBG (DBG_info,
	   "gt89xx_fill_image: size (read) = 0x%lx bytes (bpl=0x%lx)\n",
	   (long) size, (long) dev->params.bytes_per_line);

      memcpy (dev->image + dev->image_end, dev->buffer, size);

      dev->image_end += size;
      bulk_size_read = size;
      if (dev->real_bytes_left > size)
	dev->real_bytes_left -= size;
      else if (dev->real_bytes_left <= size)	/* last loop */
	dev->real_bytes_left = 0;
      DBG (DBG_info, "gt89xx_fill_image: real bytes left = 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left);
    }  
 
  /* status = sanei_usb_iso_setup (dev->fd, ep, p_size, dev->buffer, &size);
  if (status != SANE_STATUS_GOOD)
    return status;

  DBG (DBG_proc, "gt89xx_fill_image: for first usb_iso_submit\n");
  
  status = sanei_usb_iso_submit (dev->fd, &size);
  if (status != SANE_STATUS_GOOD)
    return status;
*/
      /* Prepare the transfer, not tested, also sanei_usb_read_iso not yet ready. */

      /* Do the transfer */
  /*    n_bytes = dev->bytes_left;
      to_read = dev->bytes_left;
*/
      DBG (DBG_proc,
	   "gt89xx_fill_image: dev->bytes_left: 0x%lx p_size: 0x%lx n_bytes: 0x%lx\n",
	   (unsigned long) (size_t) dev->bytes_left, 
	   (unsigned long) (size_t) p_size, 
	   (unsigned long) (size_t) n_bytes);
/*
      status = sanei_usb_iso_reap (dev->fd, dev->buffer, &size);
      if (status != SANE_STATUS_GOOD)
	return status;
*/
 /*     if (to_read != dev->bytes_left)
	{
	  DBG (DBG_error, "gt89xx_fill_image: read %x bytes instead of %x\n",
	       to_read, dev->bytes_left);
	  status = SANE_STATUS_IO_ERROR;
	}
*/
      
  /*    if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gt89xx_fill_image: cannot read from the vidcam\n");
	  return status;
	}
DBG (DBG_info, "gt89xx_fill_image: to read   = %lx bytes (bpl=%d)\n",
	   (long) size, dev->params.bytes_per_line);
 sizer = 0x40;
      hexdump (DBG_proc, "sanei_usb_iso_reap first 0x40 bytes", dev->buffer, sizer);

      memcpy (dev->image + dev->image_end, dev->buffer, size);
      dev->image_end += size;
      iso_size_read = size;
      dev->real_bytes_left -= size;
      if (dev->real_bytes_left <= 0) */
        /* last loop */
	/*      dev->real_bytes_left =0;
      DBG (DBG_info, "gt89xx_fill_image: real bytes left = %lx\n",
	   (unsigned long) dev->real_bytes_left);
         }
*/
 /*  configuration = 1, interface = 0, alternate = 0 */
/*	  status = gt89xx_set_config (dev, 1, 0, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gt89xx_fill_image: GT89XX FAILED to set configure\n");
	      return status;
	    }
*/
  DBG (DBG_proc, "gt89xx_fill_image: exit\n");

  return (SANE_STATUS_GOOD);	/* unreachable */
}
/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for the pencam2 program.
* gt89xx_add_text was taken from the pencam2 program and changed on
* some points
*
*********************************************************************/

#define MSG_MAXLEN   45
#define CHAR_HEIGHT  11
#define CHAR_WIDTH   6
#define CHAR_START   4

static SANE_Status
gt89xx_add_text (SANE_Byte * image, int width, int height, char *txt)
{
  SANE_Status status;
  time_t t;
  struct tm *tm;
  char line[MSG_MAXLEN + 1];
  SANE_Byte *ptr;
  int i, x, y, f, len;
  char fmtstring[25] = " %Y-%m-%d %H:%M:%S";
  char fmttxt[46];

  DBG (DBG_proc, "gt89xx_add_text: enter\n");
  time (&t);
  tm = localtime (&t);
  if (strlen (txt) > (MSG_MAXLEN - 23))
    strncpy (fmttxt, txt, (MSG_MAXLEN - 23));
  else
    strcpy (fmttxt, txt);
  strcat (fmttxt, fmtstring);

  len = strftime (line, MSG_MAXLEN, fmttxt, tm);

  for (y = 0; y < CHAR_HEIGHT; y++)
    {
      ptr = image + 3 * width * (height - CHAR_HEIGHT - 2 + y) + 12;

      for (x = 0; x < len; x++)
	{
	  f = fontdata[line[x] * CHAR_HEIGHT + y];
	  for (i = CHAR_WIDTH - 1; i >= 0; i--)
	    {
	      if (f & (CHAR_START << i))
		{
		  ptr[0] = 255;
		  ptr[1] = 255;
		  ptr[2] = 255;
		}
	      ptr += 3;
	    }			/* for i */
	}			/* for x */
    }				/* for y */

  DBG (DBG_proc, "gt89xx_add_text: exit vw=%d, vh=%d\n", width, height);
  status = (SANE_STATUS_GOOD);
  return status;

}				/*  end of add_text  */

/* **************************  Video Decoding  *********************  */

static SANE_Status
gt89xx_bayer_unshuffle (Gt89xx_Vidcam * dev, SANE_Byte * buf, size_t * size)
{
  SANE_Status status;
  int x, y;
  int i = 0;
  int RED, GREEN, BLUE;
  int w = dev->cwidth;
  int vw = dev->x_resolution;
  int vh = dev->y_resolution;
  SANE_Byte p = 0;
  int colour = 0, bayer = 0;
  int bright_red;
  int bright_green;
  int bright_blue;
  int count;

  RED = dev->red_s;
  GREEN = dev->green_s;
  BLUE = dev->blue_s;

  DBG (DBG_proc, "gt89xx_bayer_unshuffle: enter\n");

#define AD(x, y, w) (((y)*(w)+(x))*3)

  DBG (DBG_proc,
       "gt89xx_bayer_unshuffle: color read RED=%d, GREEN=%d, BLUE=%d\n",
       RED, GREEN, BLUE);
  
  DBG (DBG_proc, "gt89xx_bayer_unshuffle: w=%d, vw=%d, vh=%d, len=0x%lx\n",
       w, vw, vh, (unsigned long) (size_t) size);

  for (y = 0; y < vh; y++)
    {
      for (x = 0; x < vw; x++)
	{
	  if (x & 1)
	    {
	      p = dev->image[y * w + (x >> 1)];
	    }
	  else
	    {
	      p = dev->image[y * w + (x >> 1) + (w >> 1)];
	    }
	  if (y & 1)
	    bayer = 2;
	  else
	    bayer = 0;
	  if (x & 1)
	    bayer++;

	  switch (bayer)
	    {
	    case 0:
	    case 3:
	      colour = 1;
	      break;
	    case 1:
	      colour = 0;
	      break;
	    case 2:
	      colour = 2;
	      break;
	    }
	  i = (y * vw + x) * 3;
	  *(dev->output + i + colour) = (SANE_Byte) p;
	}			/* for x */

    }				/* for y */

	/****** gamma correction plus hardcoded white balance */
  /* Thanks to Alexander Schwartx <alexander.schwartx@gmx.net> for this code.
     Correction values red[], green[], blue[], are generated by 
     (pow(i/256.0, GAMMA)*255.0)*white balanceRGB where GAMMA=0.55, 1<i<255. 
     White balance (RGB)= 1.0, 1.17, 1.48. Values are calculated as double float and 
     converted to unsigned char. Values are in gt89xx.h  */
  if (dev->scan_mode == GT89XX_COLOR_RGB
      || dev->scan_mode == GT89XX_COLOR_RGB_TEXT)
    {
      for (y = 0; y < vh; y++)
	{
	  for (x = 0; x < vw; x++)
	    {
	      i = (y * vw + x) * 3;
	      *(dev->output + i) =  (SANE_Byte) red_g[*(dev->output + i)];
	      *(dev->output + i + 1) = green_g[*(dev->output + i + 1)];
	      *(dev->output + i + 2) = blue_g[*(dev->output + i + 2)];
	      }
	}
    }
  DBG (DBG_proc, "gt89xx_bayer_unshuffle: gamma correction done\n");

  if (dev->scan_mode != GT89XX_COLOR_RAW)
    {

	/******  bayer demosaic  ******/
      for (y = 1; y < (vh - 1); y++)
	{
	  for (x = 1; x < (vw - 1); x++)
	    {			/* work out pixel type */
	      if (y & 1)
		bayer = 0;
	      else
		bayer = 2;
	      if (!(x & 1))
		bayer++;
	      switch (bayer)
		{
		case 0:	/* green. blue lr, red tb */
		  *(dev->output + AD (x, y, vw) + BLUE) =
		    ((int) *(dev->output + AD (x - 1, y, vw) + BLUE) +
		     (int) *(dev->output + AD (x + 1, y, vw) + BLUE)) >> 1;
		  *(dev->output + AD (x, y, vw) + RED) =
		    ((int) *(dev->output + AD (x, y - 1, vw) + RED) +
		     (int) *(dev->output + AD (x, y + 1, vw) + RED)) >> 1;
		  break;

		case 1:	/* blue. green lrtb, red diagonals */
		  *(dev->output + AD (x, y, vw) + GREEN) =
		    ((int) *(dev->output + AD (x - 1, y, vw) + GREEN) +
		     (int) *(dev->output + AD (x + 1, y, vw) + GREEN) +
		     (int) *(dev->output + AD (x, y - 1, vw) + GREEN) +
		     (int) *(dev->output + AD (x, y + 1, vw) + GREEN)) >> 2;
		  *(dev->output + AD (x, y, vw) + RED) =
		    ((int) *(dev->output + AD (x - 1, y - 1, vw) + RED) +
		     (int) *(dev->output + AD (x - 1, y + 1, vw) + RED) +
		     (int) *(dev->output + AD (x + 1, y - 1, vw) + RED) +
		     (int) *(dev->output + AD (x + 1, y + 1, vw) + RED)) >> 2;
		  break;

		case 2:	/* red. green lrtb, blue diagonals */
		  *(dev->output + AD (x, y, vw) + GREEN) =
		    ((int) *(dev->output + AD (x - 1, y, vw) + GREEN) +
		     (int) *(dev->output + AD (x + 1, y, vw) + GREEN) +
		     (int) *(dev->output + AD (x, y - 1, vw) + GREEN) +
		     (int) *(dev->output + AD (x, y + 1, vw) + GREEN)) >> 2;
		  *(dev->output + AD (x, y, vw) + BLUE) =
		    ((int) *(dev->output + AD (x - 1, y - 1, vw) + BLUE) +
		     (int) *(dev->output + AD (x + 1, y - 1, vw) + BLUE) +
		     (int) *(dev->output + AD (x - 1, y + 1, vw) + BLUE) +
		     (int) *(dev->output + AD (x + 1, y + 1, vw) +
			     BLUE)) >> 2;
		  break;

		case 3:	/* green. red lr, blue tb */
		  *(dev->output + AD (x, y, vw) + RED) =
		    ((int) *(dev->output + AD (x - 1, y, vw) + RED) +
		     (int) *(dev->output + AD (x + 1, y, vw) + RED)) >> 1;
		  *(dev->output + AD (x, y, vw) + BLUE) =
		    ((int) *(dev->output + AD (x, y - 1, vw) + BLUE) +
		     (int) *(dev->output + AD (x, y + 1, vw) + BLUE)) >> 1;
		  break;
		}		/* switch */
	    }			/* for x */
	}			/* for y  - end demosaic  */
    }				/* no bayer demosaic */
  DBG (DBG_proc, "gt89xx_bayer_unshuffle: bayer demosaic done\n");
  
  /* fix top and bottom row, left and right side */
  i = vw * 3;
  memcpy (dev->output, (dev->output + i), i);

  memcpy ((dev->output + (vh * i)), (dev->output + ((vh - 1) * i)), i);


  for (y = 0; y < vh; y++)
    {
      i = y * vw * 3;
      memcpy ((dev->output + i), (dev->output + i + 3), 3);
      memcpy ((dev->output + i + (vw * 3)),
	      (dev->output + i + (vw - 1) * 3), 3);
    }

  /*  process all raw data, then trim to size if necessary */
  if (dev->subsample == 160)
    {
      i = 0;
      for (y = 0; y < vh; y++)
	{
	  if (!(y & 1))
	    {
	      for (x = 0; x < vw; x++)
		{
		  p = (y * vw + x) * 3;
		  if (!(x & 1))
		    {
		      *(dev->output + i) = *(dev->output + p);
		      *(dev->output + i + 1) = *(dev->output + p + 1);
		      *(dev->output + i + 2) = *(dev->output + p + 2);
		      i += 3;
		    }
		}		/* for x */
	    }
	}			/* for y */
    }

  DBG (DBG_proc,
       "gt89xx_bayer_unshuffle: if needed, trim to size 160 done\n");
  /* reset to proper width */
  if ((dev->subsample == 160))
    {
      vw = 160;
      vh = 120;
    }

  /* brightness adjustment */

  count = vw * vh * 3;

  bright_red = (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_R].w);
  bright_green =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_G].w);
  bright_blue =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_B].w);

  for (x = 0; x < count; x++)
    {
      y = x + 1;
      i = x + 2;
      if ((*(dev->output + x) + bright_red) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->output + x) + bright_red) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) = (*(dev->output + x) + bright_red);

      if ((*(dev->output + y) + bright_green) >= 255)
	*(buf + y) = 255;

      else if ((*(dev->output + y) + bright_green) <= 0)
	*(buf + y) = 0;
      else
	*(buf + y) = (*(dev->output + y) + bright_green);

      if ((*(dev->output + i) + bright_blue) >= 255)
	*(buf + i) = 255;

      else if ((*(dev->output + i) + bright_blue) <= 0)
	*(buf + i) = 0;
      else
	*(buf + i) = (*(dev->output + i) + bright_blue);

      x += 2;
    }

  if (dev->scan_mode == GT89XX_COLOR_RGB_TEXT)
    {
/* insert add text routine                 */
/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for this program.
*
*********************************************************************/
      strcpy (dev->picmsg_ps, "STVcam ");

      status = gt89xx_add_text (buf, vw, vh, dev->picmsg_ps);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "gt89xx_bayer_unshuffle status NOK\n");
	  return (status);
	}
    }

/* end of add text routine                 */
 /* size = (size_t) (vw * vh * 3); */
  DBG (DBG_proc, "gt89xx_bayer_unshuffle: exit vw=%d, vh=%d\n", vw, vh);
  status = (SANE_STATUS_GOOD);
  return status;
}

/* end routines from the decoding image routine bayer_unshuffle *********/

/* Stop a scan. */
static SANE_Status
do_cancel (Gt89xx_Vidcam * dev)
{
  DBG (DBG_sane_proc, "do_cancel enter\n");

  if (dev->scanning == SANE_TRUE)
    {

      /* Reset the vidcam */
      gt89xx_reset_window (dev);
      gt89xx_close (dev);
    }

  dev->scanning = SANE_FALSE;

  DBG (DBG_sane_proc, "do_cancel exit\n");

  return SANE_STATUS_CANCELLED;
}

/*--------------------------------------------------------------------------*/

/* Sane entry points */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  FILE *fp;
  char line[PATH_MAX];
  size_t len;

  DBG_INIT ();

  DBG (DBG_sane_init, "sane_init\n");

  authorize = authorize;	/* silence gcc */

  DBG (DBG_error, "This is sane-gt89xx version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2004-2006 by Gerard Klaver\n");

  if (version_code)
    {
      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
    }

  sanei_usb_init ();

  fp = sanei_config_open (GT89XX_CONFIG_FILE);
  if (!fp)
    {
      /* No default vidcam? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   GT89XX_CONFIG_FILE);

      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (line, sizeof (line), fp))
    {
      SANE_Word vendor;
      SANE_Word product;

      if (line[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (line);

      if (!len)
	continue;		/* ignore empty lines */
      if (sscanf (line, "usb %i %i", &vendor, &product) == 2)
	{

	  sanei_usb_attach_matching_devices (line, attach_one);
	}
      else
	{
	  /* Garbage. Ignore. */
	  DBG (DBG_warning, "bad configuration line: \"%s\" - ignoring.\n",
	       line);
	}

    }

  fclose (fp);

  DBG (DBG_proc, "sane_init: leave\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  Gt89xx_Vidcam *dev;
  int i;

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

  local_only = local_only;	/* silence gcc */

  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 (DBG_proc, "sane_get_devices: exit\n");

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Gt89xx_Vidcam *dev;
  SANE_Status status;

  DBG (DBG_proc, "sane_open: enter\n");

  /* search for devicename */
  if (devicename[0])
    {
      DBG (DBG_info, "sane_open: devicename=%s\n", devicename);

      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devicename) == 0)
	    {
	      break;
	    }
	}

      if (!dev)
	{
	  status = attach_vidcam (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      DBG (DBG_sane_info, "sane_open: no devicename, opening first device\n");
      dev = first_dev;		/* empty devicename -> use first device */
    }

  if (!dev)
    {
      DBG (DBG_error, "No vidcam found\n");

      return SANE_STATUS_INVAL;
    }

  gt89xx_init_options (dev);

  *handle = dev;

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

  return SANE_STATUS_GOOD;
}


const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Gt89xx_Vidcam *dev = handle;

  DBG (DBG_proc, "sane_get_option_descriptor: enter, option %d\n", option);

  if ((unsigned) option >= OPT_NUM_OPTIONS)
    {
      return NULL;
    }

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

  return dev->opt + option;
}


SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Gt89xx_Vidcam *dev = handle;
  SANE_Status status;
  SANE_Word cap;

  DBG (DBG_proc, "sane_control_option: enter, option %d, action %d\n",
       option, action);

  if (info)
    {
      *info = 0;
    }

  if (dev->scanning)
    {
      return SANE_STATUS_DEVICE_BUSY;
    }

  if (option < 0 || option >= OPT_NUM_OPTIONS)
    {
      return SANE_STATUS_INVAL;
    }

  cap = dev->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_RESOLUTION:
	case OPT_PREVIEW:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_SATURATION:
	case OPT_THRESHOLD:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;
	case OPT_MODE:
	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (DBG_error, "could not set option, not settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "could not set option, invalid value\n");
	  return status;
	}

      switch (option)
	{

	  /* Numeric side-effect options */
	case OPT_THRESHOLD:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_SATURATION:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* Numeric side-effect free options */
	case OPT_PREVIEW:
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* String side-effect options */
	case OPT_MODE:
	  if (strcmp (dev->val[option].s, val) == 0)
	    return SANE_STATUS_GOOD;

	  free (dev->val[OPT_MODE].s);
	  dev->val[OPT_MODE].s = (SANE_Char *) strdup (val);

	  dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_BLACK_LEVEL].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;




	  if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_LINEART) ==
	      0)
	    {
	      dev->scan_mode = GT89XX_LINEART;
	      dev->depth = 8;
	      dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY) ==
		   0)
	    {
	      dev->scan_mode = GT89XX_GRAY;
	      dev->depth = 8;

	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR)
		   == 0)
	    {
	      dev->scan_mode = GT89XX_COLOR;
	      dev->depth = 8;

	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;


	    }


	  /* The gt8911  supports only a handful of resolution. */
	  /* This the default resolution range for the GT89XX_532 */

	  if (dev->resolutions_list != NULL)
	    {
	      int i;

	      dev->opt[OPT_RESOLUTION].constraint_type =
		SANE_CONSTRAINT_WORD_LIST;
	      dev->opt[OPT_RESOLUTION].constraint.word_list =
		dev->resolutions_list;

	      /* If the resolution isn't in the list, set a default. */
	      for (i = 1; i <= dev->resolutions_list[0]; i++)
		{
		  if (dev->resolutions_list[i] >= dev->val[OPT_RESOLUTION].w)
		    break;
		}
	      if (i > dev->resolutions_list[0])
		{
		  /* Too big. Take default. */
		  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;
		}
	      else
		{
		  /* Take immediate superioir value. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[i];
		}
	    }

	  /* String side-effect options */


	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }

  DBG (DBG_proc, "sane_control_option: exit, bad\n");

  return SANE_STATUS_UNSUPPORTED;
}


SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Gt89xx_Vidcam *dev = handle;
  int bytes_pixel;

  DBG (DBG_proc, "sane_get_parameters: enter\n");

  if (!(dev->scanning))
    {

      /* Setup the parameters for the scan. These values will be re-used
       * in the SET WINDOWS command. */
      if (dev->val[OPT_PREVIEW].w == SANE_TRUE)
	{

	  dev->x_resolution = 320;
	  dev->y_resolution = 240;
	}
      else
	{
	  dev->x_resolution = dev->val[OPT_RESOLUTION].w;
	  dev->y_resolution = dev->val[OPT_RESOLUTION].w;

	}

      switch (dev->scan_mode)
	{
	case GT89XX_LINEART:
	  bytes_pixel = 1;
	  break;
	case GT89XX_GRAY:
	  bytes_pixel = 1;
	  break;
	case GT89XX_COLOR_RAW:
	  bytes_pixel = 3;
	  break;
	case GT89XX_COLOR_8:
	  bytes_pixel = 1;
	  break;
	case GT89XX_COLOR_5COMP:
	  bytes_pixel = 1;
	  break;
	case GT89XX_COLOR:
	case GT89XX_COLOR_RGB:
	case GT89XX_COLOR_RGB_TEXT:
	  bytes_pixel = 3;
	  break;
	}
      /* Prepare the parameters for the caller. */
      memset (&dev->params, 0, sizeof (SANE_Parameters));

      dev->params.last_frame = SANE_TRUE;

      dev->params.format = SANE_FRAME_RGB;
      dev->params.pixels_per_line = dev->x_resolution;
      dev->params.bytes_per_line = dev->params.pixels_per_line * bytes_pixel;
      dev->params.depth = 8;

      dev->params.lines = dev->y_resolution;
    }

  /* Return the current values. */
  if (params)
    {
      *params = (dev->params);
    }

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

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_start (SANE_Handle handle)
{
  Gt89xx_Vidcam *dev = handle;
  SANE_Status status;

  DBG (DBG_proc, "sane_start: enter\n");

  if (!(dev->scanning))
    {
      sane_get_parameters (dev, NULL);

      /* Open again the vidcam  */
      if (sanei_usb_open (dev->devicename, &(dev->fd)) != 0)
	{
	  DBG (DBG_error, "ERROR: sane_start: open failed\n");
	  return SANE_STATUS_INVAL;
	}

      /* Initialize the vidcam. */
      status = gt89xx_vidcam_init (dev);
      if (status)
	{
	  DBG (DBG_error, "ERROR: failed to init the vidcam\n");
	  gt89xx_close (dev);
	  return status;
	}

      /* The vidcam must be ready. */
      status = gt89xx_wait_vidcam (dev);
      if (status)
	{
	  gt89xx_close (dev);
	  return status;
	}

      status = gt89xx_get_scan_size (dev);
      if (status)
	{
	  gt89xx_close (dev);
	  return status;
	}

      dev->image_size = dev->buffer_size;
      dev->image = malloc (dev->image_size);
      if (dev->image == NULL)
	{
	  return SANE_STATUS_NO_MEM;
	}

      status = gt89xx_scan (dev);
      if (status)
	{
	  gt89xx_close (dev);
	  return status;
	}

      status = gt89xx_wait_for_data (dev);
      if (status)
	{
	  gt89xx_close (dev);
	  return status;
	}
    }

  dev->image_end = 0;
  dev->image_begin = 0;

  dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;
  dev->real_bytes_left = dev->params.bytes_per_line * dev->params.lines;

  dev->scanning = SANE_TRUE;

  DBG (DBG_proc, "sane_start: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SANE_Status status;
  Gt89xx_Vidcam *dev = handle;
  size_t size;
  int buf_offset;		/* offset into buf */

  DBG (DBG_proc, "sane_read: enter\n");

  *len = 0;

  if (!(dev->scanning))
    {
      DBG (DBG_proc, "sane_read: before do_cancel enter\n");
      /* OOPS, not scanning */
      return do_cancel (dev);
    }

  if (dev->bytes_left <= 0)
    {
      return (SANE_STATUS_EOF);
    }

  buf_offset = 0;

      if (dev->image_begin == dev->image_end)
	{
	  /* Fill image */
	  status = gt89xx_fill_image (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return (status);
	    }
	}

      /* Something must have been read */
      if (dev->image_begin == dev->image_end)
	{
	  DBG (DBG_info, "sane_read: nothing read\n");
	  return SANE_STATUS_IO_ERROR;
	}

size = dev->bytes_left;
  if (((unsigned int) max_len) < size)
    {
      DBG (DBG_error, "sane_read: max_len < size\n");
      return (SANE_FALSE);
    }
  if ((dev->image_end - dev->image_begin) > size)
    {
      size = dev->image_end - dev->image_begin;
      DBG (DBG_proc, "sane_read: size < dev->image_end - dev->image_begin\n");
    }
  /* diff between size an dev->bytes_left because of 356/352 and 292/288 */
  DBG (DBG_info, "sane_read: size =0x%lx bytes, max_len=0x%lx bytes\n",
       (unsigned long) (size_t) size, (unsigned long) (size_t) max_len);
  
  *len = dev->bytes_left;	/* needed */
  size = dev->bytes_left;
  dev->bytes_left = 0;	        /* needed for frontend or ? */

  if (dev->scan_mode != GT89XX_COLOR_RAW)
    {
      /* do bayer unshuffle  after complete frame is read */
      status = gt89xx_bayer_unshuffle (dev, buf, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sane_read: gt89xx_bayer_unshuffle status NOK\n");
	  return (status);
	}
    }
  else
    {
      /* Copy the raw data to the frontend buffer. */
      memcpy (buf, dev->image, size);
      DBG (DBG_info, "sane_read: raw mode\n");
    }
  DBG (DBG_info, "sane_read: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{

  DBG (DBG_proc, "sane_set_io_mode: enter\n");

  handle = handle;		/* silence gcc */
  non_blocking = non_blocking;	/* silence gcc */


  DBG (DBG_proc, "sane_set_io_mode: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (DBG_proc, "sane_get_select_fd: enter\n");

  handle = handle;		/* silence gcc */
  fd = fd;			/* silence gcc */

  DBG (DBG_proc, "sane_get_select_fd: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Gt89xx_Vidcam *dev = handle;

  DBG (DBG_proc, "sane_cancel: enter\n");

  do_cancel (dev);

  DBG (DBG_proc, "sane_cancel: exit\n");
}

void
sane_close (SANE_Handle handle)
{
  Gt89xx_Vidcam *dev = handle;
  Gt89xx_Vidcam *dev_tmp;

  DBG (DBG_proc, "sane_close: enter\n");

  do_cancel (dev);
  gt89xx_close (dev);

  /* Unlink dev. */
  if (first_dev == dev)
    {
      first_dev = dev->next;
    }
  else
    {
      dev_tmp = first_dev;
      while (dev_tmp->next && dev_tmp->next != dev)
	{
	  dev_tmp = dev_tmp->next;
	}
      if (dev_tmp->next != NULL)
	{
	  dev_tmp->next = dev_tmp->next->next;
	}
    }

  gt89xx_free (dev);
  num_devices--;

  DBG (DBG_proc, "sane_close: exit\n");
}

void
sane_exit (void)
{
  DBG (DBG_proc, "sane_exit: enter\n");

  while (first_dev)
    {
      sane_close (first_dev);
    }

  if (devlist)
    {
      free (devlist);
      devlist = NULL;
    }

  DBG (DBG_proc, "sane_exit: exit\n");
}
