summaryrefslogtreecommitdiff
path: root/backend/v4l.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/v4l.c')
-rw-r--r--backend/v4l.c1137
1 files changed, 1137 insertions, 0 deletions
diff --git a/backend/v4l.c b/backend/v4l.c
new file mode 100644
index 0000000..38595ed
--- /dev/null
+++ b/backend/v4l.c
@@ -0,0 +1,1137 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1999 Juergen G. Schimmer
+ Updates and bugfixes (C) 2002 - 2004 Henning Meier-Geinitz
+
+ 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 v4l-Devices.
+*/
+
+#define BUILD 5
+
+#include "../include/sane/config.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 <sys/mman.h>
+
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+#include <sys/ioctl.h>
+#include <asm/types.h> /* XXX glibc */
+
+#define BACKEND_NAME v4l
+#include "../include/sane/sanei_backend.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#include "../include/sane/sanei_config.h"
+#define V4L_CONFIG_FILE "v4l.conf"
+
+#include <libv4l1.h>
+#include "v4l.h"
+
+static const SANE_Device **devlist = NULL;
+static int num_devices;
+static V4L_Device *first_dev;
+static V4L_Scanner *first_handle;
+static char *buffer;
+
+static const SANE_String_Const mode_list[] = {
+ SANE_VALUE_SCAN_MODE_GRAY, SANE_VALUE_SCAN_MODE_COLOR,
+ 0
+};
+
+static const SANE_Range u8_range = {
+ /* min, max, quantization */
+ 0, 255, 0
+};
+
+static SANE_Range x_range = { 0, 338, 2 };
+
+static SANE_Range odd_x_range = { 1, 339, 2 };
+
+static SANE_Range y_range = { 0, 249, 1 };
+
+static SANE_Range odd_y_range = { 1, 250, 1 };
+
+
+static SANE_Parameters parms = {
+ SANE_FRAME_RGB,
+ 1, /* 1 = Last Frame , 0 = More Frames to come */
+ 0, /* Number of bytes returned per scan line: */
+ 0, /* Number of pixels per scan line. */
+ 0, /* Number of lines for the current scan. */
+ 8, /* Number of bits per sample. */
+};
+
+static SANE_Status
+attach (const char *devname, V4L_Device ** devp)
+{
+ V4L_Device *dev;
+ static int v4lfd;
+ static struct video_capability capability;
+
+ errno = 0;
+
+ for (dev = first_dev; dev; dev = dev->next)
+ if (strcmp (dev->sane.name, devname) == 0)
+ {
+ if (devp)
+ *devp = dev;
+ DBG (5, "attach: device %s is already known\n", devname);
+ return SANE_STATUS_GOOD;
+ }
+
+ DBG (3, "attach: trying to open %s\n", devname);
+ v4lfd = v4l1_open (devname, O_RDWR);
+ if (v4lfd != -1)
+ {
+ if (v4l1_ioctl (v4lfd, VIDIOCGCAP, &capability) == -1)
+ {
+ DBG (1,
+ "attach: ioctl (%d, VIDIOCGCAP,..) failed on `%s': %s\n",
+ v4lfd, devname, strerror (errno));
+ v4l1_close (v4lfd);
+ return SANE_STATUS_INVAL;
+ }
+ if (!(VID_TYPE_CAPTURE & capability.type))
+ {
+ DBG (1, "attach: device %s can't capture to memory -- exiting\n",
+ devname);
+ v4l1_close (v4lfd);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ DBG (2, "attach: found videodev `%s' on `%s'\n", capability.name,
+ devname);
+ v4l1_close (v4lfd);
+ }
+ else
+ {
+ DBG (1, "attach: failed to open device `%s': %s\n", devname,
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ dev = malloc (sizeof (*dev));
+ if (!dev)
+ return SANE_STATUS_NO_MEM;
+
+ memset (dev, 0, sizeof (*dev));
+
+ dev->sane.name = strdup (devname);
+ if (!dev->sane.name)
+ return SANE_STATUS_NO_MEM;
+ dev->sane.vendor = "Noname";
+ dev->sane.model = strdup (capability.name);
+ if (!dev->sane.model)
+ return SANE_STATUS_NO_MEM;
+ dev->sane.type = "virtual device";
+
+ ++num_devices;
+ dev->next = first_dev;
+ first_dev = dev;
+
+ if (devp)
+ *devp = dev;
+ return SANE_STATUS_GOOD;
+}
+
+static void
+update_parameters (V4L_Scanner * s)
+{
+ /* ??? should be per-device */
+ x_range.min = 0;
+ x_range.max = s->capability.maxwidth - s->capability.minwidth;
+ x_range.quant = 1;
+
+ y_range.min = 0;
+ y_range.max = s->capability.maxheight - s->capability.minheight;
+ y_range.quant = 1;
+
+ odd_x_range.min = s->capability.minwidth;
+ odd_x_range.max = s->capability.maxwidth;
+ if (odd_x_range.max > 767)
+ {
+ odd_x_range.max = 767;
+ x_range.max = 767 - s->capability.minwidth;
+ };
+ odd_x_range.quant = 1;
+
+ odd_y_range.min = s->capability.minheight;
+ odd_y_range.max = s->capability.maxheight;
+ if (odd_y_range.max > 511)
+ {
+ odd_y_range.max = 511;
+ y_range.max = 511 - s->capability.minheight;
+ };
+ odd_y_range.quant = 1;
+
+ parms.lines = s->window.height;
+ parms.pixels_per_line = s->window.width;
+
+ switch (s->pict.palette)
+ {
+ case VIDEO_PALETTE_GREY: /* Linear greyscale */
+ {
+ parms.format = SANE_FRAME_GRAY;
+ parms.depth = 8;
+ parms.bytes_per_line = s->window.width;
+ break;
+ }
+ case VIDEO_PALETTE_RGB24: /* 24bit RGB */
+ {
+ parms.format = SANE_FRAME_RGB;
+ parms.depth = 8;
+ parms.bytes_per_line = s->window.width * 3;
+ break;
+ }
+ default:
+ {
+ parms.format = SANE_FRAME_GRAY;
+ parms.bytes_per_line = s->window.width;
+ break;
+ }
+ }
+}
+
+static SANE_Status
+init_options (V4L_Scanner * s)
+{
+ int i;
+
+ 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);
+ }
+
+ /* Number of options */
+ 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;
+
+ /* mode */
+ s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
+ s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
+ s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
+ s->opt[OPT_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_MODE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_MODE].constraint.string_list = mode_list;
+ s->val[OPT_MODE].s = strdup (mode_list[0]);
+ if (!s->val[OPT_MODE].s)
+ return SANE_STATUS_NO_MEM;
+
+ /* channel */
+ s->opt[OPT_CHANNEL].name = "channel";
+ s->opt[OPT_CHANNEL].title = "Channel";
+ s->opt[OPT_CHANNEL].desc =
+ "Selects the channel of the v4l device (e.g. television " "or video-in.";
+ s->opt[OPT_CHANNEL].type = SANE_TYPE_STRING;
+ s->opt[OPT_CHANNEL].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CHANNEL].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_CHANNEL].constraint.string_list = s->channel;
+ s->val[OPT_CHANNEL].s = strdup (s->channel[0]);
+ if (!s->val[OPT_CHANNEL].s)
+ return SANE_STATUS_NO_MEM;
+ if (s->channel[0] == 0 || s->channel[1] == 0)
+ s->opt[OPT_CHANNEL].cap |= SANE_CAP_INACTIVE;
+
+ /* "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 *//* ??? first check if window is settable at all */
+ 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].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_X].constraint.range = &x_range;
+ s->val[OPT_TL_X].w = 0;
+
+ /* top-left y */
+ s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].type = SANE_TYPE_INT;
+ s->opt[OPT_TL_Y].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_TL_Y].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_Y].constraint.range = &y_range;
+ s->val[OPT_TL_Y].w = 0;
+
+ /* bottom-right x */
+ s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
+ s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
+ s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
+ s->opt[OPT_BR_X].type = SANE_TYPE_INT;
+ s->opt[OPT_BR_X].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_BR_X].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_X].constraint.range = &odd_x_range;
+ s->val[OPT_BR_X].w = s->capability.maxwidth;
+ if (s->val[OPT_BR_X].w > 767)
+ s->val[OPT_BR_X].w = 767;
+
+ /* 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].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_Y].constraint.range = &odd_y_range;
+ s->val[OPT_BR_Y].w = s->capability.maxheight;
+ if (s->val[OPT_BR_Y].w > 511)
+ s->val[OPT_BR_Y].w = 511;
+
+ /* "Enhancement" group: */
+ s->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement";
+ s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
+ s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
+ s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* brightness */
+ s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &u8_range;
+ s->val[OPT_BRIGHTNESS].w = s->pict.brightness / 256;
+
+ /* 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 = s->pict.hue / 256;
+
+ /* colour */
+ s->opt[OPT_COLOR].name = "color";
+ s->opt[OPT_COLOR].title = "Picture color";
+ s->opt[OPT_COLOR].desc = "Sets the picture's color.";
+ s->opt[OPT_COLOR].type = SANE_TYPE_INT;
+ s->opt[OPT_COLOR].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_COLOR].constraint.range = &u8_range;
+ s->val[OPT_COLOR].w = s->pict.colour / 256;
+
+ /* 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 = s->pict.contrast / 256;
+
+ /* whiteness */
+ 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;
+ 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 = s->pict.whiteness / 256;
+
+ 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; /* stop gcc from complaining */
+ DBG_INIT ();
+
+ DBG (2, "SANE v4l backend version %d.%d build %d from %s\n", SANE_CURRENT_MAJOR,
+ V_MINOR, BUILD, PACKAGE_STRING);
+
+ if (version_code)
+ *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD);
+
+ fp = sanei_config_open (V4L_CONFIG_FILE);
+ if (!fp)
+ {
+ DBG (2,
+ "sane_init: file `%s' not accessible (%s), trying /dev/video0\n",
+ V4L_CONFIG_FILE, strerror (errno));
+
+ return attach ("/dev/video0", 0);
+ }
+
+ 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 */
+
+ /* Remove trailing space and trailing comments */
+ for (str = dev_name; *str && !isspace (*str) && *str != '#'; ++str);
+ attach (dev_name, 0);
+ }
+ fclose (fp);
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+ V4L_Device *dev, *next;
+
+ for (dev = first_dev; dev; dev = next)
+ {
+ next = dev->next;
+ free ((void *) dev->sane.name);
+ free ((void *) dev->sane.model);
+ free (dev);
+ }
+
+ if (NULL != devlist)
+ {
+ free (devlist);
+ devlist = NULL;
+ }
+ DBG (5, "sane_exit: all devices freed\n");
+}
+
+SANE_Status
+sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
+{
+ V4L_Device *dev;
+ int i;
+
+ DBG (5, "sane_get_devices\n");
+ local_only = SANE_TRUE; /* Avoid compile warning */
+
+ if (devlist)
+ free (devlist);
+
+ devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
+ if (!devlist)
+ return SANE_STATUS_NO_MEM;
+
+ i = 0;
+ for (dev = first_dev; i < num_devices; dev = dev->next)
+ devlist[i++] = &dev->sane;
+ devlist[i++] = 0;
+
+ *device_list = devlist;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_open (SANE_String_Const devname, SANE_Handle * handle)
+{
+ V4L_Device *dev;
+ V4L_Scanner *s;
+ static int v4lfd;
+ int i;
+ struct video_channel channel;
+ SANE_Status status;
+ int max_channels = MAX_CHANNELS;
+
+ if (!devname)
+ {
+ DBG (1, "sane_open: devname == 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ for (dev = first_dev; dev; dev = dev->next)
+ if (strcmp (dev->sane.name, devname) == 0)
+ {
+ DBG (5, "sane_open: device %s found in devlist\n", devname);
+ break;
+ }
+ if (!devname[0])
+ dev = first_dev;
+ if (!dev)
+ {
+ DBG (1, "sane_open: device %s doesn't seem to be a v4l "
+ "device\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ v4lfd = v4l1_open (devname, O_RDWR);
+ if (v4lfd == -1)
+ {
+ DBG (1, "sane_open: can't open %s (%s)\n", devname, strerror (errno));
+ 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->devicename = devname;
+ s->fd = v4lfd;
+
+ if (v4l1_ioctl (s->fd, VIDIOCGCAP, &s->capability) == -1)
+ {
+ DBG (1, "sane_open: ioctl (%d, VIDIOCGCAP,..) failed on `%s': %s\n",
+ s->fd, devname, strerror (errno));
+ v4l1_close (s->fd);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sane_open: %d channels, %d audio devices\n",
+ s->capability.channels, s->capability.audios);
+ DBG (5, "sane_open: minwidth=%d, minheight=%d, maxwidth=%d, "
+ "maxheight=%d\n", s->capability.minwidth, s->capability.minheight,
+ s->capability.maxwidth, s->capability.maxheight);
+ if (VID_TYPE_CAPTURE & s->capability.type)
+ DBG (5, "sane_open: V4L device can capture to memory\n");
+ if (VID_TYPE_TUNER & s->capability.type)
+ DBG (5, "sane_open: V4L device has a tuner of some form\n");
+ if (VID_TYPE_TELETEXT & s->capability.type)
+ DBG (5, "sane_open: V4L device supports teletext\n");
+ if (VID_TYPE_OVERLAY & s->capability.type)
+ DBG (5, "sane_open: V4L device can overlay its image onto the frame "
+ "buffer\n");
+ if (VID_TYPE_CHROMAKEY & s->capability.type)
+ DBG (5, "sane_open: V4L device uses chromakey on overlay\n");
+ if (VID_TYPE_CLIPPING & s->capability.type)
+ DBG (5, "sane_open: V4L device supports overlay clipping\n");
+ if (VID_TYPE_FRAMERAM & s->capability.type)
+ DBG (5, "sane_open: V4L device overwrites frame buffer memory\n");
+ if (VID_TYPE_SCALES & s->capability.type)
+ DBG (5, "sane_open: V4L device supports hardware scaling\n");
+ if (VID_TYPE_MONOCHROME & s->capability.type)
+ DBG (5, "sane_open: V4L device is grey scale only\n");
+ if (VID_TYPE_SUBCAPTURE & s->capability.type)
+ DBG (5, "sane_open: V4L device can capture parts of the image\n");
+
+ if (s->capability.channels < max_channels)
+ max_channels = s->capability.channels;
+ for (i = 0; i < max_channels; i++)
+ {
+ channel.channel = i;
+ if (-1 == v4l1_ioctl (v4lfd, VIDIOCGCHAN, &channel))
+ {
+ DBG (1, "sane_open: can't ioctl VIDIOCGCHAN %s: %s\n", devname,
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (5, "sane_open: channel %d (%s), tuners=%d, flags=0x%x, "
+ "type=%d, norm=%d\n", channel.channel, channel.name,
+ channel.tuners, channel.flags, channel.type, channel.norm);
+ if (VIDEO_VC_TUNER & channel.flags)
+ DBG (5, "sane_open: channel has tuner(s)\n");
+ if (VIDEO_VC_AUDIO & channel.flags)
+ DBG (5, "sane_open: channel has audio\n");
+ if (VIDEO_TYPE_TV == channel.type)
+ DBG (5, "sane_open: input is TV input\n");
+ if (VIDEO_TYPE_CAMERA == channel.type)
+ DBG (5, "sane_open: input is camera input\n");
+ s->channel[i] = strdup (channel.name);
+ if (!s->channel[i])
+ return SANE_STATUS_NO_MEM;
+ }
+ s->channel[i] = 0;
+ if (-1 == v4l1_ioctl (v4lfd, VIDIOCGPICT, &s->pict))
+ {
+ DBG (1, "sane_open: can't ioctl VIDIOCGPICT %s: %s\n", devname,
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (5, "sane_open: brightness=%d, hue=%d, colour=%d, contrast=%d\n",
+ s->pict.brightness, s->pict.hue, s->pict.colour, s->pict.contrast);
+ DBG (5, "sane_open: whiteness=%d, depth=%d, palette=%d\n",
+ s->pict.whiteness, s->pict.depth, s->pict.palette);
+
+ /* ??? */
+ s->pict.palette = VIDEO_PALETTE_GREY;
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCSPICT, &s->pict))
+ {
+ DBG (1, "sane_open: ioctl VIDIOCSPICT failed (%s)\n", strerror (errno));
+ }
+
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGWIN, &s->window))
+ {
+ DBG (1, "sane_open: can't ioctl VIDIOCGWIN %s: %s\n", devname,
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (5, "sane_open: x=%d, y=%d, width=%d, height=%d\n",
+ s->window.x, s->window.y, s->window.width, s->window.height);
+
+ /* already done in sane_start
+ if (-1 == v4l1_ioctl (v4lfd, VIDIOCGMBUF, &mbuf))
+ DBG (1, "sane_open: can't ioctl VIDIOCGMBUF (no Fbuffer?)\n");
+ */
+
+ status = init_options (s);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+ update_parameters (s);
+
+ /* insert newly opened handle into list of open handles: */
+ s->next = first_handle;
+ first_handle = s;
+
+ *handle = s;
+
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ V4L_Scanner *prev, *s;
+
+ DBG (2, "sane_close: trying to close handle %p\n", (void *) handle);
+ /* remove handle from list of open handles: */
+ prev = 0;
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ prev = s;
+ }
+ if (!s)
+ {
+ 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);
+ v4l1_close (s->fd);
+ free (s);
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ V4L_Scanner *s = handle;
+
+ if ((unsigned) option >= NUM_OPTIONS || option < 0)
+ return 0;
+ DBG (4, "sane_get_option_descriptor: option %d (%s)\n", option,
+ s->opt[option].name ? s->opt[option].name : s->opt[option].title);
+ return s->opt + option;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *val, SANE_Int * info)
+{
+ V4L_Scanner *s = handle;
+ SANE_Status status;
+ SANE_Word cap;
+
+ if (info)
+ *info = 0;
+
+ if (option >= NUM_OPTIONS || option < 0)
+ return SANE_STATUS_INVAL;
+
+ DBG (4, "sane_control_option: %s option %d (%s)\n",
+ action == SANE_ACTION_GET_VALUE ? "get" :
+ action == SANE_ACTION_SET_VALUE ? "set" :
+ action == SANE_ACTION_SET_AUTO ? "auto set" :
+ "(unknow action with)", option,
+ s->opt[option].name ? s->opt[option].name : s->opt[option].title);
+
+ cap = s->opt[option].cap;
+
+ if (!SANE_OPTION_IS_ACTIVE (cap))
+ {
+ DBG (1, "sane_control option: option is inactive\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (action == SANE_ACTION_GET_VALUE)
+ {
+ switch (option)
+ {
+ /* word options: */
+ case OPT_NUM_OPTS:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_BRIGHTNESS:
+ case OPT_HUE:
+ case OPT_COLOR:
+ case OPT_CONTRAST:
+ case OPT_WHITE_LEVEL:
+ *(SANE_Word *) val = s->val[option].w;
+ return SANE_STATUS_GOOD;
+ case OPT_CHANNEL: /* string list options */
+ case OPT_MODE:
+ strcpy (val, s->val[option].s);
+ return SANE_STATUS_GOOD;
+ default:
+ DBG (1, "sane_control_option: option %d unknown\n", option);
+ }
+ }
+ else if (action == SANE_ACTION_SET_VALUE)
+ {
+ if (!SANE_OPTION_IS_SETTABLE (cap))
+ {
+ DBG (1, "sane_control_option: option is not settable\n");
+ return SANE_STATUS_INVAL;
+ }
+ status = sanei_constrain_value (s->opt + option, val, info);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_control_option: sanei_constarin_value failed: %s\n",
+ sane_strstatus (status));
+ return status;
+ }
+ if (option >= OPT_TL_X && option <= OPT_BR_Y)
+ {
+ s->user_corner |= 1 << (option - OPT_TL_X);
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGWIN, &s->window))
+ {
+ DBG (1, "sane_control_option: ioctl VIDIOCGWIN failed "
+ "(can not get window geometry)\n");
+ return SANE_STATUS_INVAL;
+ }
+ s->window.clipcount = 0;
+ s->window.clips = 0;
+ s->window.height = parms.lines;
+ s->window.width = parms.pixels_per_line;
+ }
+
+
+ switch (option)
+ {
+ /* (mostly) side-effect-free word options: */
+ case OPT_TL_X:
+ break;
+ case OPT_TL_Y:
+ break;
+ case OPT_BR_X:
+ s->window.width = *(SANE_Word *) val;
+ parms.pixels_per_line = *(SANE_Word *) val;
+ if (info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_BR_Y:
+ s->window.height = *(SANE_Word *) val;
+ parms.lines = *(SANE_Word *) val;
+ if (info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_MODE:
+ if (info)
+ *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ s->val[option].s = strdup (val);
+ if (!s->val[option].s)
+ return SANE_STATUS_NO_MEM;
+ if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_GRAY) == 0)
+ s->pict.palette = VIDEO_PALETTE_GREY;
+ else
+ s->pict.palette = VIDEO_PALETTE_RGB24;
+ update_parameters (s);
+ break;
+ case OPT_BRIGHTNESS:
+ s->pict.brightness = *(SANE_Word *) val *256;
+ s->val[option].w = *(SANE_Word *) val;
+ break;
+ case OPT_HUE:
+ s->pict.hue = *(SANE_Word *) val *256;
+ s->val[option].w = *(SANE_Word *) val;
+ break;
+ case OPT_COLOR:
+ s->pict.colour = *(SANE_Word *) val *256;
+ s->val[option].w = *(SANE_Word *) val;
+ break;
+ case OPT_CONTRAST:
+ s->pict.contrast = *(SANE_Word *) val *256;
+ s->val[option].w = *(SANE_Word *) val;
+ break;
+ case OPT_WHITE_LEVEL:
+ s->pict.whiteness = *(SANE_Word *) val *256;
+ s->val[option].w = *(SANE_Word *) val;
+ break;
+ case OPT_CHANNEL:
+ {
+ int i;
+ struct video_channel channel;
+
+ s->val[option].s = strdup (val);
+ if (!s->val[option].s)
+ return SANE_STATUS_NO_MEM;
+ for (i = 0; i < MAX_CHANNELS; i++)
+ {
+ if (strcmp (s->channel[i], val) == 0)
+ {
+ channel.channel = i;
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGCHAN, &channel))
+ {
+ DBG (1, "sane_open: can't ioctl VIDIOCGCHAN %s: %s\n",
+ s->devicename, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCSCHAN, &channel))
+ {
+ DBG (1, "sane_open: can't ioctl VIDIOCSCHAN %s: %s\n",
+ s->devicename, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ break;
+ }
+ }
+ return SANE_STATUS_GOOD;
+ break;
+ }
+ default:
+ DBG (1, "sane_control_option: option %d unknown\n", option);
+ return SANE_STATUS_INVAL;
+ }
+ if (option >= OPT_TL_X && option <= OPT_BR_Y)
+ {
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCSWIN, &s->window))
+ {
+ DBG (1, "sane_control_option: ioctl VIDIOCSWIN failed (%s)\n",
+ strerror (errno));
+ /* return SANE_STATUS_INVAL; */
+ }
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGWIN, &s->window))
+ {
+ DBG (1, "sane_control_option: ioctl VIDIOCGWIN failed (%s)\n",
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ }
+ if (option >= OPT_BRIGHTNESS && option <= OPT_WHITE_LEVEL)
+ {
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCSPICT, &s->pict))
+ {
+ DBG (1, "sane_control_option: ioctl VIDIOCSPICT failed (%s)\n",
+ strerror (errno));
+ /* return SANE_STATUS_INVAL; */
+ }
+ }
+ return SANE_STATUS_GOOD;
+ }
+ else if (action == SANE_ACTION_SET_AUTO)
+ {
+ if (!(cap & SANE_CAP_AUTOMATIC))
+ {
+ DBG (1, "sane_control_option: option can't be set automatically\n");
+ return SANE_STATUS_INVAL;
+ }
+ switch (option)
+ {
+ case OPT_BRIGHTNESS:
+ /* not implemented yet */
+ return SANE_STATUS_GOOD;
+
+ default:
+ break;
+ }
+ }
+ return SANE_STATUS_INVAL;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
+{
+ V4L_Scanner *s = handle;
+
+ DBG (4, "sane_get_parameters\n");
+ update_parameters (s);
+ if (params == 0)
+ {
+ DBG (1, "sane_get_parameters: params == 0\n");
+ return SANE_STATUS_INVAL;
+ }
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGWIN, &s->window))
+ {
+ DBG (1, "sane_control_option: ioctl VIDIOCGWIN failed "
+ "(can not get window geometry)\n");
+ return SANE_STATUS_INVAL;
+ }
+ parms.pixels_per_line = s->window.width;
+ parms.bytes_per_line = s->window.width;
+ if (parms.format == SANE_FRAME_RGB)
+ parms.bytes_per_line = s->window.width * 3;
+ parms.lines = s->window.height;
+ *params = parms;
+ return SANE_STATUS_GOOD;
+
+}
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ int len, loop;
+ V4L_Scanner *s;
+ char data;
+
+ DBG (2, "sane_start\n");
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ }
+ if (!s)
+ {
+ DBG (1, "sane_start: bad handle %p\n", handle);
+ return SANE_STATUS_INVAL; /* oops, not a handle we know about */
+ }
+ len = v4l1_ioctl (s->fd, VIDIOCGCAP, &s->capability);
+ if (-1 == len)
+ {
+ DBG (1, "sane_start: can not get capabilities\n");
+ return SANE_STATUS_INVAL;
+ }
+ s->buffercount = 0;
+ if (-1 == v4l1_ioctl (s->fd, VIDIOCGMBUF, &s->mbuf))
+ {
+ s->is_mmap = SANE_FALSE;
+ buffer =
+ malloc (s->capability.maxwidth * s->capability.maxheight *
+ s->pict.depth);
+ if (0 == buffer)
+ return SANE_STATUS_NO_MEM;
+ DBG (3, "sane_start: V4L trying to read frame\n");
+ len = v4l1_read (s->fd, buffer, parms.bytes_per_line * parms.lines);
+ DBG (3, "sane_start: %d bytes read\n", len);
+ }
+ else
+ {
+ s->is_mmap = SANE_TRUE;
+ DBG (3,
+ "sane_start: mmap frame, buffersize: %d bytes, buffers: %d, offset 0 %d\n",
+ s->mbuf.size, s->mbuf.frames, s->mbuf.offsets[0]);
+ buffer =
+ v4l1_mmap (0, s->mbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0);
+ if (buffer == (void *)-1)
+ {
+ DBG (1, "sane_start: mmap failed: %s\n", strerror (errno));
+ buffer = NULL;
+ return SANE_STATUS_IO_ERROR;
+ }
+ DBG (3, "sane_start: mmapped frame, capture 1 pict into %p\n", buffer);
+ s->mmap.frame = 0;
+ s->mmap.width = s->window.width;
+ /* s->mmap.width = parms.pixels_per_line; ??? huh? */
+ s->mmap.height = s->window.height;
+ /* s->mmap.height = parms.lines; ??? huh? */
+ s->mmap.format = s->pict.palette;
+ DBG (2, "sane_start: mmapped frame %d x %d with palette %d\n",
+ s->mmap.width, s->mmap.height, s->mmap.format);
+
+ /* We need to loop here to empty the read buffers, so we don't
+ get a stale image */
+ for (loop = 0; loop <= s->mbuf.frames; loop++)
+ {
+ len = v4l1_ioctl (s->fd, VIDIOCMCAPTURE, &s->mmap);
+ if (len == -1)
+ {
+ DBG (1, "sane_start: ioctl VIDIOCMCAPTURE failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (3, "sane_start: waiting for frame %x, loop %d\n", s->mmap.frame, loop);
+ len = v4l1_ioctl (s->fd, VIDIOCSYNC, &(s->mmap.frame));
+ if (-1 == len)
+ {
+ DBG (1, "sane_start: call to ioctl(%d, VIDIOCSYNC, ..) failed\n",
+ s->fd);
+ return SANE_STATUS_INVAL;
+ }
+ }
+ DBG (3, "sane_start: frame %x done\n", s->mmap.frame);
+ }
+
+ /* v4l1 actually returns BGR when we ask for RGB, so convert it */
+ if (s->pict.palette == VIDEO_PALETTE_RGB24)
+ {
+ DBG (3, "sane_start: converting from BGR to RGB\n");
+ for (loop = 0; loop < (s->window.width * s->window.height * 3); loop += 3)
+ {
+ data = *(buffer + loop);
+ *(buffer + loop) = *(buffer + loop + 2);
+ *(buffer + loop + 2) = data;
+ }
+ }
+
+ DBG (3, "sane_start: done\n");
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
+ SANE_Int * lenp)
+{
+ int i, min;
+ V4L_Scanner *s = handle;
+
+ DBG (4, "sane_read: max_len = %d\n", max_len);
+ if (!lenp)
+ {
+ DBG (1, "sane_read: lenp == 0\n");
+ return SANE_STATUS_INVAL;
+ }
+ if ((s->buffercount + 1) > (parms.lines * parms.bytes_per_line))
+ {
+ *lenp = 0;
+ return SANE_STATUS_EOF;
+ };
+ min = parms.lines * parms.bytes_per_line;
+ if (min > (max_len + s->buffercount))
+ min = (max_len + s->buffercount);
+ if (s->is_mmap == SANE_FALSE)
+ {
+ for (i = s->buffercount; i < (min + 0); i++)
+ {
+ *(buf + i - s->buffercount) = *(buffer + i);
+ };
+ *lenp = (parms.lines * parms.bytes_per_line - s->buffercount);
+ if (max_len < *lenp)
+ *lenp = max_len;
+ DBG (3, "sane_read: transferred %d bytes (from %d to %d)\n", *lenp,
+ s->buffercount, i);
+ s->buffercount = i;
+ }
+ else
+ {
+ for (i = s->buffercount; i < (min + 0); i++)
+ {
+ *(buf + i - s->buffercount) = *(buffer + i);
+ };
+ *lenp = (parms.lines * parms.bytes_per_line - s->buffercount);
+ if ((i - s->buffercount) < *lenp)
+ *lenp = (i - s->buffercount);
+ DBG (3, "sane_read: transferred %d bytes (from %d to %d)\n", *lenp,
+ s->buffercount, i);
+ s->buffercount = i;
+ }
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ V4L_Scanner *s = handle;
+
+ DBG (2, "sane_cancel\n");
+
+ /* ??? buffer isn't checked in sane_read? */
+ if (buffer)
+ {
+ if (s->is_mmap)
+ v4l1_munmap(buffer, s->mbuf.size);
+ else
+ free (buffer);
+
+ buffer = NULL;
+ }
+}
+
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ /* Avoid compile warning */
+ handle = 0;
+
+ if (non_blocking == SANE_FALSE)
+ return SANE_STATUS_GOOD;
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
+{
+ /* Avoid compile warning */
+ handle = 0;
+ fd = 0;
+
+ return SANE_STATUS_UNSUPPORTED;
+}