kopia lustrzana https://gitlab.com/sane-project/backends
1516 wiersze
34 KiB
C
1516 wiersze
34 KiB
C
/*
|
|
* epsonds.c - Epson ESC/I-2 driver.
|
|
*
|
|
* Copyright (C) 2015 Tower Technologies
|
|
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
|
*
|
|
* 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, version 2.
|
|
*/
|
|
|
|
#define EPSONDS_VERSION 1
|
|
#define EPSONDS_REVISION 1
|
|
#define EPSONDS_BUILD 0
|
|
|
|
/* debugging levels:
|
|
*
|
|
* 32 eds_send
|
|
* 30 eds_recv
|
|
* 20 sane_read and related
|
|
* 18 sane_read and related
|
|
* 17 setvalue, getvalue, control_option
|
|
* 16
|
|
* 15 esci2_img
|
|
* 13 image_cb
|
|
* 12 eds_control
|
|
* 11 all received params
|
|
* 10 some received params
|
|
* 9
|
|
* 8 esci2_xxx
|
|
* 7 open/close/attach
|
|
* 6 print_params
|
|
* 5 basic functions
|
|
* 3 JPEG decompressor
|
|
* 1 scanner info and capabilities
|
|
* 0 errors
|
|
*/
|
|
|
|
#include "sane/config.h"
|
|
|
|
#include <ctype.h>
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
#endif
|
|
#include <unistd.h>
|
|
|
|
#include "sane/saneopts.h"
|
|
#include "sane/sanei_config.h"
|
|
#include "sane/sanei_tcp.h"
|
|
#include "sane/sanei_udp.h"
|
|
|
|
#include "epsonds.h"
|
|
#include "epsonds-usb.h"
|
|
#include "epsonds-io.h"
|
|
#include "epsonds-cmd.h"
|
|
#include "epsonds-ops.h"
|
|
#include "epsonds-jpeg.h"
|
|
#include "epsonds-net.h"
|
|
|
|
|
|
/*
|
|
* Definition of the mode_param struct, that is used to
|
|
* specify the valid parameters for the different scan modes.
|
|
*
|
|
* The depth variable gets updated when the bit depth is modified.
|
|
*/
|
|
|
|
struct mode_param mode_params[] = {
|
|
{0, 0x00, 0x30, 1},
|
|
{0, 0x00, 0x30, 8},
|
|
{1, 0x02, 0x00, 8},
|
|
{0, 0x00, 0x30, 1}
|
|
};
|
|
|
|
static SANE_String_Const mode_list[] = {
|
|
SANE_VALUE_SCAN_MODE_LINEART,
|
|
SANE_VALUE_SCAN_MODE_GRAY,
|
|
SANE_VALUE_SCAN_MODE_COLOR,
|
|
NULL
|
|
};
|
|
|
|
static const SANE_String_Const adf_mode_list[] = {
|
|
SANE_I18N("Simplex"),
|
|
SANE_I18N("Duplex"),
|
|
NULL
|
|
};
|
|
|
|
/* Define the different scan sources */
|
|
|
|
#define FBF_STR SANE_I18N("Flatbed")
|
|
#define ADF_STR SANE_I18N("Automatic Document Feeder")
|
|
|
|
/* order will be fixed: fb, adf, tpu */
|
|
SANE_String_Const source_list[] = {
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* List of pointers to devices - will be dynamically allocated depending
|
|
* on the number of devices found.
|
|
*/
|
|
static const SANE_Device **devlist;
|
|
|
|
/* Some utility functions */
|
|
|
|
static size_t
|
|
max_string_size(const SANE_String_Const strings[])
|
|
{
|
|
size_t size, max_size = 0;
|
|
int i;
|
|
|
|
for (i = 0; strings[i]; i++) {
|
|
size = strlen(strings[i]) + 1;
|
|
if (size > max_size)
|
|
max_size = size;
|
|
}
|
|
return max_size;
|
|
}
|
|
|
|
static SANE_Status attach_one_usb(SANE_String_Const devname);
|
|
static SANE_Status attach_one_net(SANE_String_Const devname);
|
|
|
|
static void
|
|
print_params(const SANE_Parameters params)
|
|
{
|
|
DBG(6, "params.format = %d\n", params.format);
|
|
DBG(6, "params.last_frame = %d\n", params.last_frame);
|
|
DBG(6, "params.bytes_per_line = %d\n", params.bytes_per_line);
|
|
DBG(6, "params.pixels_per_line = %d\n", params.pixels_per_line);
|
|
DBG(6, "params.lines = %d\n", params.lines);
|
|
DBG(6, "params.depth = %d\n", params.depth);
|
|
}
|
|
|
|
static void
|
|
close_scanner(epsonds_scanner *s)
|
|
{
|
|
DBG(7, "%s: fd = %d\n", __func__, s->fd);
|
|
|
|
if (s->fd == -1)
|
|
goto free;
|
|
|
|
if (s->locked) {
|
|
DBG(7, " unlocking scanner\n");
|
|
esci2_fin(s);
|
|
}
|
|
|
|
if (s->hw->connection == SANE_EPSONDS_NET) {
|
|
epsonds_net_unlock(s);
|
|
sanei_tcp_close(s->fd);
|
|
} else if (s->hw->connection == SANE_EPSONDS_USB) {
|
|
sanei_usb_close(s->fd);
|
|
}
|
|
|
|
free:
|
|
|
|
free(s->front.ring);
|
|
free(s->back.ring);
|
|
free(s->line_buffer);
|
|
free(s);
|
|
|
|
DBG(7, "%s: ZZZ\n", __func__);
|
|
}
|
|
|
|
static void
|
|
e2_network_discovery(void)
|
|
{
|
|
fd_set rfds;
|
|
int fd, len;
|
|
SANE_Status status;
|
|
|
|
char *ip, *query = "EPSONP\x00\xff\x00\x00\x00\x00\x00\x00\x00";
|
|
unsigned char buf[76];
|
|
|
|
struct timeval to;
|
|
|
|
status = sanei_udp_open_broadcast(&fd);
|
|
if (status != SANE_STATUS_GOOD)
|
|
return;
|
|
|
|
sanei_udp_write_broadcast(fd, 3289, (unsigned char *) query, 15);
|
|
|
|
DBG(5, "%s, sent discovery packet\n", __func__);
|
|
|
|
to.tv_sec = 1;
|
|
to.tv_usec = 0;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
|
|
sanei_udp_set_nonblock(fd, SANE_TRUE);
|
|
while (select(fd + 1, &rfds, NULL, NULL, &to) > 0) {
|
|
if ((len = sanei_udp_recvfrom(fd, buf, 76, &ip)) == 76) {
|
|
DBG(5, " response from %s\n", ip);
|
|
|
|
/* minimal check, protocol unknown */
|
|
if (strncmp((char *) buf, "EPSON", 5) == 0)
|
|
attach_one_net(ip);
|
|
}
|
|
}
|
|
|
|
DBG(5, "%s, end\n", __func__);
|
|
|
|
sanei_udp_close(fd);
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
open_scanner(epsonds_scanner *s)
|
|
{
|
|
SANE_Status status = SANE_STATUS_INVAL;
|
|
|
|
DBG(7, "%s: %s\n", __func__, s->hw->sane.name);
|
|
|
|
if (s->fd != -1) {
|
|
DBG(5, "scanner is already open: fd = %d\n", s->fd);
|
|
return SANE_STATUS_GOOD; /* no need to open the scanner */
|
|
}
|
|
|
|
if (s->hw->connection == SANE_EPSONDS_NET) {
|
|
unsigned char buf[5];
|
|
|
|
/* device name has the form net:ipaddr */
|
|
status = sanei_tcp_open(&s->hw->sane.name[4], 1865, &s->fd);
|
|
if (status == SANE_STATUS_GOOD) {
|
|
|
|
ssize_t read;
|
|
struct timeval tv;
|
|
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
|
|
setsockopt(s->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
|
|
|
s->netlen = 0;
|
|
|
|
DBG(32, "awaiting welcome message\n");
|
|
|
|
/* the scanner sends a kind of welcome msg */
|
|
// XXX check command type, answer to connect is 0x80
|
|
read = eds_recv(s, buf, 3, &status);
|
|
if (read != 3) {
|
|
sanei_tcp_close(s->fd);
|
|
s->fd = -1;
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
DBG(32, "welcome message received, locking the scanner...\n");
|
|
|
|
/* lock the scanner for use by sane */
|
|
status = epsonds_net_lock(s);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
DBG(1, "%s cannot lock scanner: %s\n", s->hw->sane.name,
|
|
sane_strstatus(status));
|
|
|
|
sanei_tcp_close(s->fd);
|
|
s->fd = -1;
|
|
|
|
return status;
|
|
}
|
|
|
|
DBG(32, "scanner locked\n");
|
|
}
|
|
|
|
} else if (s->hw->connection == SANE_EPSONDS_USB) {
|
|
|
|
status = sanei_usb_open(s->hw->sane.name, &s->fd);
|
|
|
|
if (status == SANE_STATUS_GOOD) {
|
|
sanei_usb_set_timeout(USB_TIMEOUT);
|
|
sanei_usb_clear_halt(s->fd);
|
|
}
|
|
|
|
} else {
|
|
DBG(1, "unknown connection type: %d\n", s->hw->connection);
|
|
}
|
|
|
|
if (status == SANE_STATUS_ACCESS_DENIED) {
|
|
DBG(1, "please check that you have permissions on the device.\n");
|
|
DBG(1, "if this is a multi-function device with a printer,\n");
|
|
DBG(1, "disable any conflicting driver (like usblp).\n");
|
|
}
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG(1, "%s open failed: %s\n",
|
|
s->hw->sane.name,
|
|
sane_strstatus(status));
|
|
else
|
|
DBG(5, " opened correctly\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
static int num_devices; /* number of scanners attached to backend */
|
|
static epsonds_device *first_dev; /* first EPSON scanner in list */
|
|
|
|
static struct epsonds_scanner *
|
|
scanner_create(struct epsonds_device *dev, SANE_Status *status)
|
|
{
|
|
struct epsonds_scanner *s;
|
|
|
|
s = malloc(sizeof(struct epsonds_scanner));
|
|
if (s == NULL) {
|
|
*status = SANE_STATUS_NO_MEM;
|
|
return NULL;
|
|
}
|
|
|
|
/* clear verything */
|
|
memset(s, 0x00, sizeof(struct epsonds_scanner));
|
|
|
|
s->fd = -1;
|
|
s->hw = dev;
|
|
|
|
return s;
|
|
}
|
|
|
|
static struct epsonds_scanner *
|
|
device_detect(const char *name, int type, SANE_Status *status)
|
|
{
|
|
struct epsonds_scanner *s;
|
|
struct epsonds_device *dev;
|
|
|
|
DBG(1, "%s, %s, type: %d\n", __func__, name, type);
|
|
|
|
/* try to find the device in our list */
|
|
for (dev = first_dev; dev; dev = dev->next) {
|
|
|
|
if (strcmp(dev->sane.name, name) == 0) {
|
|
|
|
DBG(1, " found cached device\n");
|
|
|
|
// the device might have been just probed, sleep a bit.
|
|
if (dev->connection == SANE_EPSONDS_NET) {
|
|
sleep(1);
|
|
}
|
|
|
|
return scanner_create(dev, status);
|
|
}
|
|
}
|
|
|
|
/* not found, create new if valid */
|
|
if (type == SANE_EPSONDS_NODEV) {
|
|
*status = SANE_STATUS_INVAL;
|
|
return NULL;
|
|
}
|
|
|
|
/* alloc and clear our device structure */
|
|
dev = malloc(sizeof(*dev));
|
|
if (!dev) {
|
|
*status = SANE_STATUS_NO_MEM;
|
|
return NULL;
|
|
}
|
|
memset(dev, 0x00, sizeof(struct epsonds_device));
|
|
|
|
s = scanner_create(dev, status);
|
|
if (s == NULL)
|
|
return NULL;
|
|
|
|
dev->connection = type;
|
|
dev->model = strdup("(undetermined)");
|
|
dev->name = strdup(name);
|
|
|
|
dev->sane.name = dev->name;
|
|
dev->sane.vendor = "Epson";
|
|
dev->sane.model = dev->model;
|
|
dev->sane.type = "ESC/I-2";
|
|
|
|
*status = open_scanner(s);
|
|
if (*status != SANE_STATUS_GOOD) {
|
|
free(s);
|
|
return NULL;
|
|
}
|
|
|
|
eds_dev_init(dev);
|
|
|
|
/* lock scanner */
|
|
*status = eds_lock(s);
|
|
if (*status != SANE_STATUS_GOOD) {
|
|
goto close;
|
|
}
|
|
|
|
/* discover capabilities */
|
|
*status = esci2_info(s);
|
|
if (*status != SANE_STATUS_GOOD)
|
|
goto close;
|
|
|
|
*status = esci2_capa(s);
|
|
if (*status != SANE_STATUS_GOOD)
|
|
goto close;
|
|
|
|
*status = esci2_resa(s);
|
|
if (*status != SANE_STATUS_GOOD)
|
|
goto close;
|
|
|
|
// assume 1 and 8 bit are always supported
|
|
eds_add_depth(s->hw, 1);
|
|
eds_add_depth(s->hw, 8);
|
|
|
|
// setup area according to available options
|
|
if (s->hw->has_fb) {
|
|
|
|
dev->x_range = &dev->fbf_x_range;
|
|
dev->y_range = &dev->fbf_y_range;
|
|
dev->alignment = dev->fbf_alignment;
|
|
|
|
} else if (s->hw->has_adf) {
|
|
|
|
dev->x_range = &dev->adf_x_range;
|
|
dev->y_range = &dev->adf_y_range;
|
|
dev->alignment = dev->adf_alignment;
|
|
|
|
} else {
|
|
DBG(0, "unable to lay on the flatbed or feed the feeder. is that a scanner??\n");
|
|
}
|
|
|
|
*status = eds_dev_post_init(dev);
|
|
if (*status != SANE_STATUS_GOOD)
|
|
goto close;
|
|
|
|
DBG(1, "scanner model: %s\n", dev->model);
|
|
|
|
/* add this scanner to the device list */
|
|
|
|
num_devices++;
|
|
dev->next = first_dev;
|
|
first_dev = dev;
|
|
|
|
return s;
|
|
|
|
close:
|
|
DBG(1, " failed\n");
|
|
|
|
close_scanner(s);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
attach(const char *name, int type)
|
|
{
|
|
SANE_Status status;
|
|
epsonds_scanner * s;
|
|
|
|
DBG(7, "%s: devname = %s, type = %d\n", __func__, name, type);
|
|
|
|
s = device_detect(name, type, &status);
|
|
if (s == NULL)
|
|
return status;
|
|
|
|
close_scanner(s);
|
|
return status;
|
|
}
|
|
|
|
SANE_Status
|
|
attach_one_usb(const char *dev)
|
|
{
|
|
DBG(7, "%s: dev = %s\n", __func__, dev);
|
|
return attach(dev, SANE_EPSONDS_USB);
|
|
}
|
|
|
|
static SANE_Status
|
|
attach_one_net(const char *dev)
|
|
{
|
|
char name[39 + 4];
|
|
|
|
DBG(7, "%s: dev = %s\n", __func__, dev);
|
|
|
|
strcpy(name, "net:");
|
|
strcat(name, dev);
|
|
return attach(name, SANE_EPSONDS_NET);
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
attach_one_config(SANEI_Config __sane_unused__ *config, const char *line)
|
|
{
|
|
int vendor, product;
|
|
|
|
int len = strlen(line);
|
|
|
|
DBG(7, "%s: len = %d, line = %s\n", __func__, len, line);
|
|
|
|
if (sscanf(line, "usb %i %i", &vendor, &product) == 2) {
|
|
|
|
DBG(7, " user configured device\n");
|
|
|
|
if (vendor != SANE_EPSONDS_VENDOR_ID)
|
|
return SANE_STATUS_INVAL; /* this is not an Epson device */
|
|
|
|
sanei_usb_attach_matching_devices(line, attach_one_usb);
|
|
|
|
} else if (strncmp(line, "usb", 3) == 0 && len == 3) {
|
|
|
|
int i, numIds;
|
|
|
|
DBG(7, " probing usb devices\n");
|
|
|
|
numIds = epsonds_get_number_of_ids();
|
|
|
|
for (i = 0; i < numIds; i++) {
|
|
sanei_usb_find_devices(SANE_EPSONDS_VENDOR_ID,
|
|
epsonds_usb_product_ids[i], attach_one_usb);
|
|
}
|
|
|
|
} else if (strncmp(line, "net", 3) == 0) {
|
|
|
|
/* remove the "net" sub string */
|
|
const char *name = sanei_config_skip_whitespace(line + 3);
|
|
|
|
if (strncmp(name, "autodiscovery", 13) == 0)
|
|
e2_network_discovery();
|
|
else
|
|
attach_one_net(name);
|
|
|
|
} else {
|
|
DBG(0, "unable to parse config line: %s\n", line);
|
|
}
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static void
|
|
free_devices(void)
|
|
{
|
|
epsonds_device *dev, *next;
|
|
|
|
for (dev = first_dev; dev; dev = next) {
|
|
next = dev->next;
|
|
free(dev->name);
|
|
free(dev->model);
|
|
free(dev);
|
|
}
|
|
|
|
free(devlist);
|
|
first_dev = NULL;
|
|
}
|
|
|
|
static void
|
|
probe_devices(void)
|
|
{
|
|
DBG(5, "%s\n", __func__);
|
|
|
|
free_devices();
|
|
sanei_configure_attach(EPSONDS_CONFIG_FILE, NULL, attach_one_config);
|
|
}
|
|
|
|
/**** SANE API ****/
|
|
|
|
SANE_Status
|
|
sane_init(SANE_Int *version_code, SANE_Auth_Callback __sane_unused__ authorize)
|
|
{
|
|
DBG_INIT();
|
|
DBG(2, "%s: " PACKAGE " " VERSION "\n", __func__);
|
|
|
|
DBG(1, "epsonds backend, version %i.%i.%i\n",
|
|
EPSONDS_VERSION, EPSONDS_REVISION, EPSONDS_BUILD);
|
|
|
|
if (version_code != NULL)
|
|
*version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR, V_MINOR,
|
|
EPSONDS_BUILD);
|
|
|
|
sanei_usb_init();
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
void
|
|
sane_exit(void)
|
|
{
|
|
DBG(5, "** %s\n", __func__);
|
|
free_devices();
|
|
}
|
|
|
|
SANE_Status
|
|
sane_get_devices(const SANE_Device ***device_list, SANE_Bool __sane_unused__ local_only)
|
|
{
|
|
int i;
|
|
epsonds_device *dev;
|
|
|
|
DBG(5, "** %s\n", __func__);
|
|
|
|
probe_devices();
|
|
|
|
devlist = malloc((num_devices + 1) * sizeof(devlist[0]));
|
|
if (!devlist) {
|
|
DBG(1, "out of memory (line %d)\n", __LINE__);
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
DBG(5, "%s - results:\n", __func__);
|
|
|
|
for (i = 0, dev = first_dev; i < num_devices && dev; dev = dev->next, i++) {
|
|
DBG(1, " %d (%d): %s\n", i, dev->connection, dev->model);
|
|
devlist[i] = &dev->sane;
|
|
}
|
|
|
|
devlist[i] = NULL;
|
|
|
|
*device_list = devlist;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static SANE_Status
|
|
init_options(epsonds_scanner *s)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_OPTIONS; i++) {
|
|
s->opt[i].size = sizeof(SANE_Word);
|
|
s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
|
|
}
|
|
|
|
s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
|
|
s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
|
|
s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
|
|
s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
|
|
s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
|
|
|
|
/* "Scan Mode" group: */
|
|
|
|
s->opt[OPT_MODE_GROUP].title = SANE_I18N("Scan Mode");
|
|
s->opt[OPT_MODE_GROUP].desc = "";
|
|
s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
|
|
s->opt[OPT_MODE_GROUP].cap = 0;
|
|
|
|
/* scan mode */
|
|
s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
|
|
s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
|
|
s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
|
|
s->opt[OPT_MODE].type = SANE_TYPE_STRING;
|
|
s->opt[OPT_MODE].size = max_string_size(mode_list);
|
|
s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
|
|
s->opt[OPT_MODE].constraint.string_list = mode_list;
|
|
s->val[OPT_MODE].w = 0; /* Lineart */
|
|
|
|
/* bit depth */
|
|
s->opt[OPT_DEPTH].name = SANE_NAME_BIT_DEPTH;
|
|
s->opt[OPT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
|
|
s->opt[OPT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
|
|
s->opt[OPT_DEPTH].type = SANE_TYPE_INT;
|
|
s->opt[OPT_DEPTH].unit = SANE_UNIT_BIT;
|
|
s->opt[OPT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
|
|
s->opt[OPT_DEPTH].constraint.word_list = s->hw->depth_list;
|
|
s->val[OPT_DEPTH].w = s->hw->depth_list[1]; /* the first "real" element is the default */
|
|
|
|
/* default is Lineart, disable depth selection */
|
|
s->opt[OPT_DEPTH].cap |= SANE_CAP_INACTIVE;
|
|
|
|
/* resolution */
|
|
s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
|
|
s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
|
|
s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
|
|
|
|
s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
|
|
s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
|
|
|
|
/* range */
|
|
if (s->hw->dpi_range.quant) {
|
|
s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range;
|
|
s->val[OPT_RESOLUTION].w = s->hw->dpi_range.min;
|
|
} else { /* list */
|
|
s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
|
|
s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->res_list;
|
|
s->val[OPT_RESOLUTION].w = s->hw->res_list[1];
|
|
}
|
|
|
|
/* "Geometry" group: */
|
|
s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N("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;
|
|
|
|
/* top-left x */
|
|
s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
|
|
s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
|
|
s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
|
|
s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
|
|
s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
|
|
s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
s->opt[OPT_TL_X].constraint.range = s->hw->x_range;
|
|
s->val[OPT_TL_X].w = 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_FIXED;
|
|
s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
|
|
s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
s->opt[OPT_TL_Y].constraint.range = s->hw->y_range;
|
|
s->val[OPT_TL_Y].w = 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_FIXED;
|
|
s->opt[OPT_BR_X].unit = SANE_UNIT_MM;
|
|
s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
s->opt[OPT_BR_X].constraint.range = s->hw->x_range;
|
|
s->val[OPT_BR_X].w = s->hw->x_range->max;
|
|
|
|
/* bottom-right y */
|
|
s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
|
|
s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
|
|
s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
|
|
|
|
s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
|
|
s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
|
|
s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
s->opt[OPT_BR_Y].constraint.range = s->hw->y_range;
|
|
s->val[OPT_BR_Y].w = s->hw->y_range->max;
|
|
|
|
/* "Optional equipment" group: */
|
|
s->opt[OPT_EQU_GROUP].title = SANE_I18N("Optional equipment");
|
|
s->opt[OPT_EQU_GROUP].desc = "";
|
|
s->opt[OPT_EQU_GROUP].type = SANE_TYPE_GROUP;
|
|
s->opt[OPT_EQU_GROUP].cap = SANE_CAP_ADVANCED;
|
|
|
|
/* source */
|
|
s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
|
|
s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
|
|
s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
|
|
s->opt[OPT_SOURCE].type = SANE_TYPE_STRING;
|
|
s->opt[OPT_SOURCE].size = max_string_size(source_list);
|
|
s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
|
|
s->opt[OPT_SOURCE].constraint.string_list = source_list;
|
|
s->val[OPT_SOURCE].w = 0;
|
|
|
|
s->opt[OPT_EJECT].name = "eject";
|
|
s->opt[OPT_EJECT].title = SANE_I18N("Eject");
|
|
s->opt[OPT_EJECT].desc = SANE_I18N("Eject the sheet in the ADF");
|
|
s->opt[OPT_EJECT].type = SANE_TYPE_BUTTON;
|
|
|
|
if (!s->hw->adf_has_eject)
|
|
s->opt[OPT_EJECT].cap |= SANE_CAP_INACTIVE;
|
|
|
|
s->opt[OPT_LOAD].name = "load";
|
|
s->opt[OPT_LOAD].title = SANE_I18N("Load");
|
|
s->opt[OPT_LOAD].desc = SANE_I18N("Load a sheet in the ADF");
|
|
s->opt[OPT_LOAD].type = SANE_TYPE_BUTTON;
|
|
|
|
if (!s->hw->adf_has_load)
|
|
s->opt[OPT_LOAD].cap |= SANE_CAP_INACTIVE;
|
|
|
|
s->opt[OPT_ADF_MODE].name = "adf-mode";
|
|
s->opt[OPT_ADF_MODE].title = SANE_I18N("ADF Mode");
|
|
s->opt[OPT_ADF_MODE].desc =
|
|
SANE_I18N("Selects the ADF mode (simplex/duplex)");
|
|
s->opt[OPT_ADF_MODE].type = SANE_TYPE_STRING;
|
|
s->opt[OPT_ADF_MODE].size = max_string_size(adf_mode_list);
|
|
s->opt[OPT_ADF_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
|
|
s->opt[OPT_ADF_MODE].constraint.string_list = adf_mode_list;
|
|
s->val[OPT_ADF_MODE].w = 0; /* simplex */
|
|
|
|
if (!s->hw->adf_is_duplex)
|
|
s->opt[OPT_ADF_MODE].cap |= SANE_CAP_INACTIVE;
|
|
|
|
s->opt[OPT_ADF_SKEW].name = "adf-skew";
|
|
s->opt[OPT_ADF_SKEW].title = SANE_I18N("ADF Skew Correction");
|
|
s->opt[OPT_ADF_SKEW].desc =
|
|
SANE_I18N("Enables ADF skew correction");
|
|
s->opt[OPT_ADF_SKEW].type = SANE_TYPE_BOOL;
|
|
s->val[OPT_ADF_SKEW].w = 0;
|
|
|
|
if (!s->hw->adf_has_skew)
|
|
s->opt[OPT_ADF_SKEW].cap |= SANE_CAP_INACTIVE;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_open(SANE_String_Const name, SANE_Handle *handle)
|
|
{
|
|
SANE_Status status;
|
|
epsonds_scanner *s = NULL;
|
|
|
|
DBG(7, "** %s: name = '%s'\n", __func__, name);
|
|
|
|
/* probe if empty device name provided */
|
|
if (name[0] == '\0') {
|
|
|
|
probe_devices();
|
|
|
|
if (first_dev == NULL) {
|
|
DBG(1, "no devices detected\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
s = device_detect(first_dev->sane.name, first_dev->connection,
|
|
&status);
|
|
if (s == NULL) {
|
|
DBG(1, "cannot open a perfectly valid device (%s),"
|
|
" please report to the authors\n", name);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (strncmp(name, "net:", 4) == 0) {
|
|
s = device_detect(name, SANE_EPSONDS_NET, &status);
|
|
if (s == NULL)
|
|
return status;
|
|
} else if (strncmp(name, "libusb:", 7) == 0) {
|
|
s = device_detect(name, SANE_EPSONDS_USB, &status);
|
|
if (s == NULL)
|
|
return status;
|
|
} else {
|
|
DBG(1, "invalid device name: %s\n", name);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
}
|
|
|
|
/* s is always valid here */
|
|
|
|
DBG(5, "%s: handle obtained\n", __func__);
|
|
|
|
init_options(s);
|
|
|
|
*handle = (SANE_Handle)s;
|
|
|
|
status = open_scanner(s);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
free(s);
|
|
return status;
|
|
}
|
|
|
|
/* lock scanner if required */
|
|
if (!s->locked) {
|
|
status = eds_lock(s);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void
|
|
sane_close(SANE_Handle handle)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *)handle;
|
|
|
|
DBG(1, "** %s\n", __func__);
|
|
|
|
close_scanner(s);
|
|
}
|
|
|
|
const SANE_Option_Descriptor *
|
|
sane_get_option_descriptor(SANE_Handle handle, SANE_Int option)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *) handle;
|
|
|
|
if (option < 0 || option >= NUM_OPTIONS)
|
|
return NULL;
|
|
|
|
return s->opt + option;
|
|
}
|
|
|
|
static const SANE_String_Const *
|
|
search_string_list(const SANE_String_Const *list, SANE_String value)
|
|
{
|
|
while (*list != NULL && strcmp(value, *list) != 0)
|
|
list++;
|
|
|
|
return ((*list == NULL) ? NULL : list);
|
|
}
|
|
|
|
static void
|
|
activateOption(epsonds_scanner *s, SANE_Int option, SANE_Bool *change)
|
|
{
|
|
if (!SANE_OPTION_IS_ACTIVE(s->opt[option].cap)) {
|
|
s->opt[option].cap &= ~SANE_CAP_INACTIVE;
|
|
*change = SANE_TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
deactivateOption(epsonds_scanner *s, SANE_Int option, SANE_Bool *change)
|
|
{
|
|
if (SANE_OPTION_IS_ACTIVE(s->opt[option].cap)) {
|
|
s->opt[option].cap |= SANE_CAP_INACTIVE;
|
|
*change = SANE_TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handles setting the source (flatbed, transparency adapter (TPU),
|
|
* or auto document feeder (ADF)).
|
|
*
|
|
* For newer scanners it also sets the focus according to the
|
|
* glass / TPU settings.
|
|
*/
|
|
|
|
static void
|
|
change_source(epsonds_scanner *s, SANE_Int optindex, char *value)
|
|
{
|
|
int force_max = SANE_FALSE;
|
|
SANE_Bool dummy;
|
|
|
|
DBG(1, "%s: optindex = %d, source = '%s'\n", __func__, optindex,
|
|
value);
|
|
|
|
s->val[OPT_SOURCE].w = optindex;
|
|
|
|
/* if current selected area is the maximum available,
|
|
* keep this setting on the new source.
|
|
*/
|
|
if (s->val[OPT_TL_X].w == s->hw->x_range->min
|
|
&& s->val[OPT_TL_Y].w == s->hw->y_range->min
|
|
&& s->val[OPT_BR_X].w == s->hw->x_range->max
|
|
&& s->val[OPT_BR_Y].w == s->hw->y_range->max) {
|
|
force_max = SANE_TRUE;
|
|
}
|
|
|
|
if (strcmp(ADF_STR, value) == 0) {
|
|
|
|
s->hw->x_range = &s->hw->adf_x_range;
|
|
s->hw->y_range = &s->hw->adf_y_range;
|
|
s->hw->alignment = s->hw->adf_alignment;
|
|
|
|
if (s->hw->adf_is_duplex) {
|
|
activateOption(s, OPT_ADF_MODE, &dummy);
|
|
} else {
|
|
deactivateOption(s, OPT_ADF_MODE, &dummy);
|
|
s->val[OPT_ADF_MODE].w = 0;
|
|
}
|
|
|
|
} else if (strcmp(TPU_STR, value) == 0) {
|
|
|
|
s->hw->x_range = &s->hw->tpu_x_range;
|
|
s->hw->y_range = &s->hw->tpu_y_range;
|
|
|
|
deactivateOption(s, OPT_ADF_MODE, &dummy);
|
|
|
|
} else {
|
|
|
|
/* neither ADF nor TPU active, assume FB */
|
|
s->hw->x_range = &s->hw->fbf_x_range;
|
|
s->hw->y_range = &s->hw->fbf_y_range;
|
|
s->hw->alignment = s->hw->fbf_alignment;
|
|
}
|
|
|
|
s->opt[OPT_BR_X].constraint.range = s->hw->x_range;
|
|
s->opt[OPT_BR_Y].constraint.range = s->hw->y_range;
|
|
|
|
if (s->val[OPT_TL_X].w < s->hw->x_range->min || force_max)
|
|
s->val[OPT_TL_X].w = s->hw->x_range->min;
|
|
|
|
if (s->val[OPT_TL_Y].w < s->hw->y_range->min || force_max)
|
|
s->val[OPT_TL_Y].w = s->hw->y_range->min;
|
|
|
|
if (s->val[OPT_BR_X].w > s->hw->x_range->max || force_max)
|
|
s->val[OPT_BR_X].w = s->hw->x_range->max;
|
|
|
|
if (s->val[OPT_BR_Y].w > s->hw->y_range->max || force_max)
|
|
s->val[OPT_BR_Y].w = s->hw->y_range->max;
|
|
}
|
|
|
|
static SANE_Status
|
|
getvalue(SANE_Handle handle, SANE_Int option, void *value)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *)handle;
|
|
SANE_Option_Descriptor *sopt = &(s->opt[option]);
|
|
Option_Value *sval = &(s->val[option]);
|
|
|
|
DBG(17, "%s: option = %d\n", __func__, option);
|
|
|
|
switch (option) {
|
|
|
|
case OPT_NUM_OPTS:
|
|
case OPT_RESOLUTION:
|
|
case OPT_TL_X:
|
|
case OPT_TL_Y:
|
|
case OPT_BR_X:
|
|
case OPT_BR_Y:
|
|
case OPT_DEPTH:
|
|
case OPT_ADF_SKEW:
|
|
*((SANE_Word *) value) = sval->w;
|
|
break;
|
|
|
|
case OPT_MODE:
|
|
case OPT_ADF_MODE:
|
|
case OPT_SOURCE:
|
|
strcpy((char *) value, sopt->constraint.string_list[sval->w]);
|
|
break;
|
|
|
|
default:
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static SANE_Status
|
|
setvalue(SANE_Handle handle, SANE_Int option, void *value, SANE_Int *info)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *) handle;
|
|
SANE_Option_Descriptor *sopt = &(s->opt[option]);
|
|
Option_Value *sval = &(s->val[option]);
|
|
|
|
SANE_Status status;
|
|
const SANE_String_Const *optval = NULL;
|
|
int optindex = 0;
|
|
SANE_Bool reload = SANE_FALSE;
|
|
|
|
DBG(17, "** %s: option = %d, value = %p\n", __func__, option, value);
|
|
|
|
status = sanei_constrain_value(sopt, value, info);
|
|
if (status != SANE_STATUS_GOOD)
|
|
return status;
|
|
|
|
if (info && value && (*info & SANE_INFO_INEXACT)
|
|
&& sopt->type == SANE_TYPE_INT)
|
|
DBG(17, " constrained val = %d\n", *(SANE_Word *) value);
|
|
|
|
if (sopt->constraint_type == SANE_CONSTRAINT_STRING_LIST) {
|
|
optval = search_string_list(sopt->constraint.string_list,
|
|
(char *) value);
|
|
if (optval == NULL)
|
|
return SANE_STATUS_INVAL;
|
|
optindex = optval - sopt->constraint.string_list;
|
|
}
|
|
|
|
/* block faulty frontends */
|
|
if (sopt->cap & SANE_CAP_INACTIVE) {
|
|
DBG(1, " tried to modify a disabled parameter");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
switch (option) {
|
|
|
|
case OPT_ADF_MODE: /* simple lists */
|
|
sval->w = optindex;
|
|
break;
|
|
|
|
case OPT_ADF_SKEW:
|
|
case OPT_RESOLUTION:
|
|
sval->w = *((SANE_Word *) value);
|
|
reload = SANE_TRUE;
|
|
break;
|
|
|
|
case OPT_BR_X:
|
|
case OPT_BR_Y:
|
|
if (SANE_UNFIX(*((SANE_Word *) value)) == 0) {
|
|
DBG(17, " invalid br-x or br-y\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
// fall through
|
|
case OPT_TL_X:
|
|
case OPT_TL_Y:
|
|
sval->w = *((SANE_Word *) value);
|
|
if (NULL != info)
|
|
*info |= SANE_INFO_RELOAD_PARAMS;
|
|
break;
|
|
|
|
case OPT_SOURCE:
|
|
change_source(s, optindex, (char *) value);
|
|
reload = SANE_TRUE;
|
|
break;
|
|
|
|
case OPT_MODE:
|
|
{
|
|
/* use JPEG mode if RAW is not available when bpp > 1 */
|
|
if (optindex > 0 && !s->hw->has_raw) {
|
|
s->mode_jpeg = 1;
|
|
} else {
|
|
s->mode_jpeg = 0;
|
|
}
|
|
|
|
sval->w = optindex;
|
|
|
|
/* if binary, then disable the bit depth selection */
|
|
if (optindex == 0) {
|
|
s->opt[OPT_DEPTH].cap |= SANE_CAP_INACTIVE;
|
|
} else {
|
|
if (s->hw->depth_list[0] == 1)
|
|
s->opt[OPT_DEPTH].cap |= SANE_CAP_INACTIVE;
|
|
else {
|
|
s->opt[OPT_DEPTH].cap &= ~SANE_CAP_INACTIVE;
|
|
s->val[OPT_DEPTH].w =
|
|
mode_params[optindex].depth;
|
|
}
|
|
}
|
|
|
|
reload = SANE_TRUE;
|
|
break;
|
|
}
|
|
|
|
case OPT_DEPTH:
|
|
sval->w = *((SANE_Word *) value);
|
|
mode_params[s->val[OPT_MODE].w].depth = sval->w;
|
|
reload = SANE_TRUE;
|
|
break;
|
|
|
|
case OPT_LOAD:
|
|
esci2_mech(s, "#ADFLOAD");
|
|
break;
|
|
|
|
case OPT_EJECT:
|
|
esci2_mech(s, "#ADFEJCT");
|
|
break;
|
|
|
|
default:
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
if (reload && info != NULL)
|
|
*info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_control_option(SANE_Handle handle, SANE_Int option, SANE_Action action,
|
|
void *value, SANE_Int *info)
|
|
{
|
|
DBG(17, "** %s: action = %x, option = %d\n", __func__, action, option);
|
|
|
|
if (option < 0 || option >= NUM_OPTIONS)
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if (info != NULL)
|
|
*info = 0;
|
|
|
|
switch (action) {
|
|
case SANE_ACTION_GET_VALUE:
|
|
return getvalue(handle, option, value);
|
|
|
|
case SANE_ACTION_SET_VALUE:
|
|
return setvalue(handle, option, value, info);
|
|
|
|
default:
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_get_parameters(SANE_Handle handle, SANE_Parameters *params)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *)handle;
|
|
|
|
DBG(5, "** %s\n", __func__);
|
|
|
|
if (params == NULL)
|
|
DBG(1, "%s: params is NULL\n", __func__);
|
|
|
|
/*
|
|
* If sane_start was already called, then just retrieve the parameters
|
|
* from the scanner data structure
|
|
*/
|
|
if (s->scanning) {
|
|
DBG(5, "scan in progress, returning saved params structure\n");
|
|
} else {
|
|
/* otherwise initialize the params structure */
|
|
eds_init_parameters(s);
|
|
}
|
|
|
|
if (params != NULL)
|
|
*params = s->params;
|
|
|
|
print_params(s->params);
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/*
|
|
* This function is part of the SANE API and gets called from the front end to
|
|
* start the scan process.
|
|
*/
|
|
|
|
SANE_Status
|
|
sane_start(SANE_Handle handle)
|
|
{
|
|
epsonds_scanner *s = (epsonds_scanner *)handle;
|
|
char buf[65]; /* add one more byte to correct buffer overflow issue */
|
|
char cmd[100]; /* take care not to overflow */
|
|
SANE_Status status = 0;
|
|
|
|
s->pages++;
|
|
|
|
DBG(5, "** %s, pages = %d, scanning = %d, backside = %d, front fill: %d, back fill: %d\n",
|
|
__func__, s->pages, s->scanning, s->backside,
|
|
eds_ring_avail(&s->front),
|
|
eds_ring_avail(&s->back));
|
|
|
|
s->eof = 0;
|
|
s->canceling = 0;
|
|
|
|
if ((s->pages % 2) == 1) {
|
|
s->current = &s->front;
|
|
eds_ring_flush(s->current);
|
|
} else if (eds_ring_avail(&s->back)) {
|
|
DBG(5, "back side\n");
|
|
s->current = &s->back;
|
|
}
|
|
|
|
/* prepare the JPEG decompressor */
|
|
if (s->mode_jpeg) {
|
|
status = eds_jpeg_start(s);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
goto end;
|
|
} }
|
|
|
|
/* scan already in progress? (one pass adf) */
|
|
if (s->scanning) {
|
|
DBG(5, " scan in progress, returning early\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* calc scanning parameters */
|
|
status = eds_init_parameters(s);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
DBG(1, " parameters initialization failed\n");
|
|
return status;
|
|
}
|
|
|
|
/* allocate line buffer */
|
|
s->line_buffer = realloc(s->line_buffer, s->params.bytes_per_line);
|
|
if (s->line_buffer == NULL)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
/* transfer buffer size, bsz */
|
|
/* XXX read value from scanner */
|
|
s->bsz = (65536 * 4);
|
|
|
|
/* ring buffer for front page */
|
|
status = eds_ring_init(&s->front, s->bsz * 2);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
return status;
|
|
}
|
|
|
|
/* transfer buffer */
|
|
s->buf = realloc(s->buf, s->bsz);
|
|
if (s->buf == NULL)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
print_params(s->params);
|
|
|
|
/* set scanning parameters */
|
|
|
|
/* document source */
|
|
if (strcmp(source_list[s->val[OPT_SOURCE].w], ADF_STR) == 0) {
|
|
|
|
sprintf(buf, "#ADF%s%s",
|
|
s->val[OPT_ADF_MODE].w ? "DPLX" : "",
|
|
s->val[OPT_ADF_SKEW].w ? "SKEW" : "");
|
|
|
|
/* it seems that DFL only works in duplex mode, but it's
|
|
* also required to be enabled or duplex will be rejected.
|
|
*/
|
|
|
|
if (s->val[OPT_ADF_MODE].w) {
|
|
|
|
if (s->hw->adf_has_dfd == 2) {
|
|
strcat(buf, "DFL2");
|
|
} else if (s->hw->adf_has_dfd == 1) {
|
|
strcat(buf, "DFL1");
|
|
}
|
|
}
|
|
|
|
} else if (strcmp(source_list[s->val[OPT_SOURCE].w], FBF_STR) == 0) {
|
|
|
|
strcpy(buf, "#FB ");
|
|
|
|
} else {
|
|
/* XXX */
|
|
}
|
|
|
|
strcpy(cmd, buf);
|
|
|
|
if (s->params.format == SANE_FRAME_GRAY) {
|
|
sprintf(buf, "#COLM%03d", s->params.depth);
|
|
} else if (s->params.format == SANE_FRAME_RGB) {
|
|
sprintf(buf, "#COLC%03d", s->params.depth * 3);
|
|
}
|
|
|
|
strcat(cmd, buf);
|
|
|
|
/* image transfer format */
|
|
if (!s->mode_jpeg) {
|
|
if (s->params.depth > 1 || s->hw->has_raw) {
|
|
strcat(cmd, "#FMTRAW ");
|
|
}
|
|
} else {
|
|
strcat(cmd, "#FMTJPG #JPGd090");
|
|
}
|
|
|
|
/* resolution (RSMi not always supported) */
|
|
|
|
if (s->val[OPT_RESOLUTION].w > 999) {
|
|
sprintf(buf, "#RSMi%07d#RSSi%07d", s->val[OPT_RESOLUTION].w, s->val[OPT_RESOLUTION].w);
|
|
} else {
|
|
sprintf(buf, "#RSMd%03d#RSSd%03d", s->val[OPT_RESOLUTION].w, s->val[OPT_RESOLUTION].w);
|
|
}
|
|
|
|
strcat(cmd, buf);
|
|
|
|
/* scanning area */
|
|
sprintf(buf, "#ACQi%07di%07di%07di%07d",
|
|
s->left, s->top, s->params.pixels_per_line, s->params.lines);
|
|
|
|
strcat(cmd, buf);
|
|
|
|
status = esci2_para(s, cmd);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
goto end;
|
|
}
|
|
|
|
/* start scanning */
|
|
DBG(1, "%s: scanning...\n", __func__);
|
|
|
|
/* switch to data state */
|
|
status = esci2_trdt(s);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
goto end;
|
|
}
|
|
|
|
/* first page is page 1 */
|
|
s->pages = 1;
|
|
s->scanning = 1;
|
|
|
|
end:
|
|
if (status != SANE_STATUS_GOOD) {
|
|
DBG(1, "%s: start failed: %s\n", __func__, sane_strstatus(status));
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/* this moves data from our buffers to SANE */
|
|
|
|
SANE_Status
|
|
sane_read(SANE_Handle handle, SANE_Byte *data, SANE_Int max_length,
|
|
SANE_Int *length)
|
|
{
|
|
SANE_Int read = 0, tries = 3;
|
|
SANE_Int available;
|
|
SANE_Status status = 0;
|
|
epsonds_scanner *s = (epsonds_scanner *)handle;
|
|
|
|
*length = read = 0;
|
|
|
|
DBG(20, "** %s: backside = %d\n", __func__, s->backside);
|
|
|
|
/* sane_read called before sane_start? */
|
|
if (s->current == NULL) {
|
|
DBG(0, "%s: buffer is NULL", __func__);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
/* anything in the buffer? pass it to the frontend */
|
|
available = eds_ring_avail(s->current);
|
|
if (available) {
|
|
|
|
DBG(18, "reading from ring buffer, %d left\n", available);
|
|
|
|
if (s->mode_jpeg && !s->jpeg_header_seen) {
|
|
|
|
status = eds_jpeg_read_header(s);
|
|
if (status != SANE_STATUS_GOOD && --tries) {
|
|
goto read_again;
|
|
}
|
|
}
|
|
|
|
if (s->mode_jpeg) {
|
|
eds_jpeg_read(handle, data, max_length, &read);
|
|
} else {
|
|
eds_copy_image_from_ring(s, data, max_length, &read);
|
|
}
|
|
|
|
if (read == 0) {
|
|
goto read_again;
|
|
}
|
|
|
|
*length = read;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
|
|
} else if (s->current == &s->back) {
|
|
|
|
/* finished reading the back page, next
|
|
* command should give us the EOF
|
|
*/
|
|
DBG(18, "back side ring buffer empty\n");
|
|
}
|
|
|
|
/* read until data or error */
|
|
|
|
read_again:
|
|
|
|
status = esci2_img(s, &read);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
DBG(20, "read: %d, eof: %d, backside: %d, status: %d\n", read, s->eof, s->backside, status);
|
|
}
|
|
|
|
/* just got a back side page, alloc ring buffer if necessary
|
|
* we didn't before because dummy was not known
|
|
*/
|
|
if (s->backside) {
|
|
|
|
int required = s->params.lines * (s->params.bytes_per_line + s->dummy);
|
|
|
|
if (s->back.size < required) {
|
|
|
|
DBG(20, "allocating buffer for the back side\n");
|
|
|
|
status = eds_ring_init(&s->back, required);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* abort scanning when appropriate */
|
|
if (status == SANE_STATUS_CANCELLED) {
|
|
esci2_can(s);
|
|
return status;
|
|
}
|
|
|
|
if (s->eof && s->backside) {
|
|
DBG(18, "back side scan finished\n");
|
|
}
|
|
|
|
/* read again if no error and no data */
|
|
if (read == 0 && status == SANE_STATUS_GOOD) {
|
|
goto read_again;
|
|
}
|
|
|
|
/* got something, write to ring */
|
|
if (read) {
|
|
|
|
DBG(20, " %d bytes read, %d lines, eof: %d, canceling: %d, status: %d, backside: %d\n",
|
|
read, read / (s->params.bytes_per_line + s->dummy),
|
|
s->canceling, s->eof, status, s->backside);
|
|
|
|
/* move data to the appropriate ring */
|
|
status = eds_ring_write(s->backside ? &s->back : &s->front, s->buf, read);
|
|
|
|
if (0 && s->mode_jpeg && !s->jpeg_header_seen
|
|
&& status == SANE_STATUS_GOOD) {
|
|
|
|
status = eds_jpeg_read_header(s);
|
|
if (status != SANE_STATUS_GOOD && --tries) {
|
|
goto read_again;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* continue reading if appropriate */
|
|
if (status == SANE_STATUS_GOOD)
|
|
return status;
|
|
|
|
/* cleanup */
|
|
DBG(5, "** %s: cleaning up\n", __func__);
|
|
|
|
if (s->mode_jpeg) {
|
|
eds_jpeg_finish(s);
|
|
}
|
|
|
|
eds_ring_flush(s->current);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* void sane_cancel(SANE_Handle handle)
|
|
*
|
|
* Set the cancel flag to true. The next time the backend requests data
|
|
* from the scanner the CAN message will be sent.
|
|
*/
|
|
|
|
void
|
|
sane_cancel(SANE_Handle handle)
|
|
{
|
|
DBG(1, "** %s\n", __func__);
|
|
((epsonds_scanner *)handle)->canceling = SANE_TRUE;
|
|
}
|
|
|
|
/*
|
|
* SANE_Status sane_set_io_mode()
|
|
*
|
|
* not supported - for asynchronous I/O
|
|
*/
|
|
|
|
SANE_Status
|
|
sane_set_io_mode(SANE_Handle __sane_unused__ handle,
|
|
SANE_Bool __sane_unused__ non_blocking)
|
|
{
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
/*
|
|
* SANE_Status sane_get_select_fd()
|
|
*
|
|
* not supported - for asynchronous I/O
|
|
*/
|
|
|
|
SANE_Status
|
|
sane_get_select_fd(SANE_Handle __sane_unused__ handle,
|
|
SANE_Int __sane_unused__ *fd)
|
|
{
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|