2002-04-01 22:54:25 +00:00
|
|
|
/* sane - Scanner Access Now Easy.
|
|
|
|
Copyright (C) 2001-2002 Matthew C. Duggan and Simon Krix
|
|
|
|
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.
|
|
|
|
|
|
|
|
-----
|
|
|
|
|
|
|
|
canon_pp.c: $Revision$
|
|
|
|
|
|
|
|
This file is part of the canon_pp backend, supporting Canon FBX30P
|
|
|
|
and NX40P scanners
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef _AIX
|
|
|
|
#include <lalloca.h> /* MUST come first for AIX! */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define VERSION "$Revision$"
|
|
|
|
#define BACKEND_NAME canon_pp
|
|
|
|
|
|
|
|
#define THREE_BITS 0xE0
|
|
|
|
#define TWO_BITS 0xC0
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ieee1284.h>
|
|
|
|
|
|
|
|
#include "../include/sane/sane.h"
|
|
|
|
#include "../include/sane/saneopts.h"
|
|
|
|
|
|
|
|
#include "canon_pp-dev.h"
|
|
|
|
#include "canon_pp-io.h"
|
|
|
|
#include "canon_pp.h"
|
|
|
|
|
|
|
|
/* #include "../include/sane/sanei_pio.h" */
|
|
|
|
#include "../include/sane/sanei_config.h"
|
|
|
|
#include "../include/sane/sanei_backend.h"
|
|
|
|
/* #include "../include/sane/sanei_debug.h" */
|
|
|
|
|
|
|
|
|
|
|
|
/* Prototypes */
|
|
|
|
static SANE_Status init_device(struct parport *pp);
|
|
|
|
|
|
|
|
static void update_ranges(CANONP_Scanner *cs);
|
|
|
|
/* create a calibration file and give it initial values */
|
|
|
|
static int init_cal(char *file);
|
|
|
|
|
|
|
|
static SANE_Status fix_weights_file(CANONP_Scanner *cs);
|
|
|
|
|
|
|
|
static SANE_Status detect_mode(CANONP_Scanner *cs);
|
|
|
|
|
|
|
|
/* Global Variables (ack!) */
|
|
|
|
|
|
|
|
/* The first device in a linked list of devices */
|
|
|
|
static CANONP_Scanner *first_dev = NULL;
|
|
|
|
/* The default scanner to open */
|
|
|
|
static char *def_scanner = NULL;
|
|
|
|
/* The number of devices */
|
|
|
|
static int num_devices = 0;
|
|
|
|
/* ieee1284 parallel ports */
|
|
|
|
struct parport_list pl;
|
|
|
|
/* leftover from the last read */
|
|
|
|
static SANE_Byte *read_leftover = NULL;
|
|
|
|
/* leftover from the last read */
|
|
|
|
static SANE_Bool force_nibble = SANE_FALSE;
|
|
|
|
|
|
|
|
/* Constants */
|
|
|
|
|
|
|
|
/* Colour Modes */
|
|
|
|
static const SANE_String_Const cmodes[] = { "Greyscale", "Colour", NULL };
|
|
|
|
/* bit depths */
|
|
|
|
static const SANE_String_Const depths[] = { "8", "12", NULL };
|
|
|
|
/* resolutions */
|
|
|
|
static const SANE_Int res330[] = {3, 75, 150, 300};
|
|
|
|
static const SANE_Int res630[] = {4, 75, 150, 300, 600};
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_init()
|
|
|
|
*
|
|
|
|
* Initialises data for the list of scanners, stored in canon-p.conf.
|
|
|
|
*
|
|
|
|
* Scanners are not sent any commands until sane_open() is called.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_init (SANE_Int *vc, SANE_Auth_Callback cb)
|
|
|
|
{
|
|
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
|
|
int i, tmp;
|
|
|
|
FILE *fp;
|
|
|
|
char line[80];
|
|
|
|
char *tmp_wf, *tmp_port;
|
|
|
|
CANONP_Scanner *s_tmp;
|
|
|
|
|
|
|
|
|
|
|
|
DBG_INIT();
|
|
|
|
|
|
|
|
#if defined PACKAGE && defined VERSION
|
|
|
|
DBG(2, ">> sane_init(%p, %p): " PACKAGE " " VERSION "\n", vc, cb);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(vc)
|
|
|
|
*vc = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);
|
|
|
|
|
|
|
|
DBG(2,"sane_init: >> ieee1284_find_ports\n");
|
|
|
|
/* Find lp ports */
|
|
|
|
tmp = ieee1284_find_ports(&pl, 0);
|
|
|
|
DBG(2,"sane_init: %d << ieee1284_find_ports\n", tmp);
|
|
|
|
|
|
|
|
if (tmp != E1284_OK)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_init: Error trying to get port list\n");
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pl.portc < 1)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_init: Error, no parallel ports found.\n");
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(10,"sane_init: %i parallel port(s) found.\n", pl.portc);
|
|
|
|
/* Setup data structures for each port */
|
|
|
|
for(i=0; i<pl.portc; i++)
|
|
|
|
{
|
|
|
|
DBG(10,"sane_init: port %s\n", pl.portv[i]->name);
|
|
|
|
status = init_device(pl.portv[i]);
|
|
|
|
/* Now's a good time to quit if we got an error */
|
|
|
|
if (status != SANE_STATUS_GOOD) return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This should never be true here */
|
|
|
|
if (num_devices == 0)
|
|
|
|
status = SANE_STATUS_IO_ERROR;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read information from config file: pixel weight location and default
|
|
|
|
* port.
|
|
|
|
*/
|
|
|
|
if((fp = sanei_config_open(CANONP_CONFIG_FILE)))
|
|
|
|
{
|
|
|
|
while(sanei_config_read(line, sizeof (line), fp))
|
|
|
|
{
|
|
|
|
DBG(100, "sane_init: >%s<\n", line);
|
|
|
|
if(line[0] == '#') /* ignore line comments */
|
|
|
|
continue;
|
|
|
|
if(!strlen(line))
|
|
|
|
continue; /* ignore empty lines */
|
|
|
|
|
|
|
|
if(strncmp(line,"calibrate ", 10) == 0)
|
|
|
|
{
|
|
|
|
/* warning: pointer trickyness ahead
|
|
|
|
* Do not free tmp_port! */
|
|
|
|
DBG(40, "sane_init: calibrate line, %s\n",
|
|
|
|
line);
|
|
|
|
tmp_wf = strdup(line+10);
|
|
|
|
tmp_port = strstr(tmp_wf, " ");
|
|
|
|
if ((tmp_port == tmp_wf) || (tmp_port == NULL))
|
|
|
|
{
|
|
|
|
/* They have used an old style config
|
|
|
|
* file which does not specify scanner
|
|
|
|
* Assume first port */
|
|
|
|
DBG(1, "sane_init: old config line:"
|
|
|
|
"\"%s\". Please add "
|
|
|
|
"a port argument.\n",
|
|
|
|
line);
|
|
|
|
|
|
|
|
/* first_dev should never be null here
|
|
|
|
* because we found at least one
|
|
|
|
* parallel port above */
|
|
|
|
first_dev->weights_file = tmp_wf;
|
|
|
|
DBG(100, "sane_init: Successfully "
|
|
|
|
"parsed cal.\n");
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now find which scanner wants
|
|
|
|
* this calibration file */
|
|
|
|
s_tmp = first_dev;
|
|
|
|
while (s_tmp != NULL)
|
|
|
|
{
|
|
|
|
if (!strcmp(s_tmp->params.port->name,
|
|
|
|
tmp_port))
|
|
|
|
{
|
|
|
|
s_tmp->weights_file = tmp_wf;
|
|
|
|
break;
|
|
|
|
DBG(100, "sane_init: "
|
|
|
|
"Successfully"
|
|
|
|
" parsed "
|
|
|
|
"cal.\n");
|
|
|
|
}
|
|
|
|
s_tmp = s_tmp->next;
|
|
|
|
}
|
|
|
|
if (s_tmp == NULL)
|
|
|
|
{
|
|
|
|
/* we made it all the way
|
|
|
|
* through the list and didn't
|
|
|
|
* find the port */
|
|
|
|
free(tmp_wf);
|
|
|
|
DBG(10, "sane_init: calibrate line is "
|
|
|
|
"for unknown port!\n");
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(strncmp(line,"ieee1284 ", 9) == 0)
|
|
|
|
{
|
|
|
|
DBG(100, "sane_init: Successfully parsed "
|
|
|
|
"default scanner.\n");
|
|
|
|
/* this will be our default scanner */
|
|
|
|
def_scanner = strdup(line+9);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(strncmp(line,"force_nibble", 12) == 0)
|
|
|
|
{
|
|
|
|
DBG(100, "sane_init: force_nibble "
|
|
|
|
"requested.\n");
|
|
|
|
force_nibble = SANE_TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
DBG(1, "sane_init: Unknown configuration command!");
|
|
|
|
|
|
|
|
}
|
|
|
|
fclose (fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* There should now be a LL of ports starting at first_dev */
|
|
|
|
|
|
|
|
for (s_tmp = first_dev; s_tmp != NULL; s_tmp = s_tmp->next)
|
|
|
|
{
|
|
|
|
/* Assume there's no scanner present until proven otherwise */
|
|
|
|
s_tmp->scanner_present = SANE_FALSE;
|
|
|
|
|
|
|
|
/* Try to detect if there's a scanner there, and if so,
|
|
|
|
* what sort of scanner it is */
|
|
|
|
status = detect_mode(s_tmp);
|
|
|
|
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
|
|
{
|
|
|
|
DBG(10,"sane_init: Error detecting port mode on %s!\n",
|
|
|
|
s_tmp->params.port->name);
|
|
|
|
s_tmp->scanner_present = SANE_FALSE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* detect_mode suceeded, so the port is open. This beholdens
|
|
|
|
* us to call ieee1284_close in any of the remaining error
|
|
|
|
* cases in this loop. */
|
2002-04-07 11:20:46 +00:00
|
|
|
#if 0
|
2002-04-01 22:54:25 +00:00
|
|
|
tmp = sanei_canon_pp_detect(s_tmp->params.port);
|
2002-04-07 11:20:46 +00:00
|
|
|
|
|
|
|
|
2002-04-01 22:54:25 +00:00
|
|
|
if (tmp && (s_tmp->ieee1284_mode != M1284_NIBBLE))
|
|
|
|
{
|
|
|
|
/* A failure, try again in nibble mode... */
|
|
|
|
DBG(1, "sane_init: Failed on ECP mode, falling "
|
|
|
|
"back to nibble mode\n");
|
|
|
|
|
|
|
|
s_tmp->ieee1284_mode = M1284_NIBBLE;
|
|
|
|
sanei_canon_pp_set_ieee1284_mode(s_tmp->ieee1284_mode);
|
|
|
|
tmp = sanei_canon_pp_detect(s_tmp->params.port);
|
|
|
|
}
|
|
|
|
/* still no go? */
|
|
|
|
if (tmp)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_init: couldn't find a scanner on port "
|
|
|
|
"%s\n", s_tmp->params.port->name);
|
|
|
|
|
|
|
|
ieee1284_close(s_tmp->params.port);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2002-04-07 11:20:46 +00:00
|
|
|
#endif
|
2002-04-01 22:54:25 +00:00
|
|
|
/* all signs point to yes, try it out */
|
|
|
|
if (ieee1284_claim(s_tmp->params.port) != E1284_OK) {
|
|
|
|
DBG(10, "sane_init: Couldn't claim port %s.\n",
|
|
|
|
s_tmp->params.port->name);
|
|
|
|
|
|
|
|
ieee1284_close(s_tmp->params.port);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(2, "sane_init: >> initialise\n");
|
|
|
|
tmp = sanei_canon_pp_initialise(&(s_tmp->params));
|
|
|
|
DBG(2, "sane_init: << %d initialise\n", tmp);
|
2002-04-07 11:20:46 +00:00
|
|
|
/* put it back to sleep until we're ready to
|
|
|
|
* open for business again */
|
|
|
|
sanei_canon_pp_sleep_scanner(s_tmp->params.port);
|
2002-04-01 22:54:25 +00:00
|
|
|
/* leave the port open but not claimed - this is regardless
|
|
|
|
* of the return value of initialise */
|
|
|
|
ieee1284_release(s_tmp->params.port);
|
2002-04-07 11:20:46 +00:00
|
|
|
if (tmp && (s_tmp->ieee1284_mode != M1284_NIBBLE))
|
|
|
|
{
|
|
|
|
/* A failure, try again in nibble mode... */
|
|
|
|
DBG(1, "sane_init: Failed on ECP mode, falling "
|
|
|
|
"back to nibble mode\n");
|
|
|
|
|
|
|
|
s_tmp->ieee1284_mode = M1284_NIBBLE;
|
|
|
|
sanei_canon_pp_set_ieee1284_mode(s_tmp->ieee1284_mode);
|
|
|
|
tmp = sanei_canon_pp_initialise(&(s_tmp->params));
|
|
|
|
}
|
2002-04-01 22:54:25 +00:00
|
|
|
if (tmp) {
|
|
|
|
DBG(10, "sane_init: Couldn't contact scanner on port "
|
|
|
|
"%s. Maybe it's not a scanner?\n",
|
|
|
|
s_tmp->params.port->name);
|
|
|
|
ieee1284_close(s_tmp->params.port);
|
|
|
|
s_tmp->scanner_present = SANE_FALSE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Finally, we're sure there's a scanner there! Now we
|
|
|
|
* just have to load the weights file...*/
|
|
|
|
if (fix_weights_file(s_tmp) != SANE_STATUS_GOOD) {
|
|
|
|
DBG(1, "sane_init: Eeek! fix_weights_file failed for "
|
|
|
|
"scanner on port %s!\n",
|
|
|
|
s_tmp->params.port->name);
|
|
|
|
|
|
|
|
ieee1284_close(s_tmp->params.port);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cocked, locked and ready to rock */
|
2002-04-07 11:20:46 +00:00
|
|
|
|
2002-04-01 22:54:25 +00:00
|
|
|
s_tmp->hw.model = s_tmp->params.name;
|
|
|
|
s_tmp->scanner_present = SANE_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(2, "<< sane_init\n");
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_get_devices()
|
|
|
|
*
|
|
|
|
* Gives a list of devices avaialable. In our case, that's the linked
|
|
|
|
* list produced by sane_init.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_get_devices (const SANE_Device ***dl, SANE_Bool local)
|
|
|
|
{
|
|
|
|
static const SANE_Device **devlist;
|
|
|
|
CANONP_Scanner *dev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
DBG(2, ">> sane_get_devices (%p, %d)\n", dl, local);
|
|
|
|
|
|
|
|
if (dl == NULL)
|
|
|
|
{
|
|
|
|
DBG(1, "sane_get_devices: ERROR: devlist pointer is NULL!");
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (devlist != NULL)
|
|
|
|
{
|
|
|
|
/* this has been called already */
|
|
|
|
*dl = devlist;
|
2002-04-07 11:20:46 +00:00
|
|
|
return SANE_STATUS_GOOD;
|
2002-04-01 22:54:25 +00:00
|
|
|
}
|
|
|
|
devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
|
|
|
|
if (devlist == NULL)
|
|
|
|
return (SANE_STATUS_NO_MEM);
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
for (dev = first_dev; dev != NULL; dev = dev->next)
|
|
|
|
{
|
|
|
|
if (dev->scanner_present == SANE_TRUE)
|
|
|
|
{
|
|
|
|
devlist[i] = &(dev->hw);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
devlist[i] = NULL;
|
|
|
|
|
|
|
|
*dl = devlist;
|
|
|
|
|
|
|
|
DBG(2, "<< sane_get_devices\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_open()
|
|
|
|
*
|
|
|
|
* Open the scanner described by name. Ask libieee1284 to claim the port
|
|
|
|
* and call Simon's init code. Also configure data structures.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_open (SANE_String_Const name, SANE_Handle *h)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *cs;
|
|
|
|
SANE_Range *tmp_range;
|
|
|
|
int tmp;
|
|
|
|
|
|
|
|
DBG(2, ">> sane_open (h=%p, name=\"%s\")\n", h, name);
|
|
|
|
|
|
|
|
if ((h == NULL) || (name == NULL))
|
|
|
|
{
|
|
|
|
DBG(2,"sane_open: Null pointer received!\n");
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strlen(name))
|
|
|
|
{
|
|
|
|
DBG(10,"sane_open: Empty name given, assuming first/"
|
|
|
|
"default scanner\n");
|
|
|
|
if (def_scanner == NULL)
|
|
|
|
name = first_dev->params.port->name;
|
|
|
|
else
|
|
|
|
name = def_scanner;
|
|
|
|
|
|
|
|
/* we don't _have_ to fit this name, so _don't_ fail if it's
|
|
|
|
* not there */
|
|
|
|
|
|
|
|
cs = first_dev;
|
|
|
|
while((cs != NULL) && strcmp(cs->params.port->name, name))
|
|
|
|
cs = cs->next;
|
|
|
|
|
|
|
|
/* if we didn't find the port they want, or there's no scanner
|
|
|
|
* there, we just want to find _any_ scanner */
|
|
|
|
if ((cs == NULL) || (cs->scanner_present != SANE_TRUE))
|
|
|
|
{
|
|
|
|
cs = first_dev;
|
|
|
|
while((cs != NULL) &&
|
|
|
|
(cs->scanner_present == SANE_FALSE))
|
|
|
|
cs = cs->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* they're dead keen for this name, so _do_ fail if it's
|
|
|
|
* not there */
|
|
|
|
cs = first_dev;
|
|
|
|
while((cs != NULL) && strcmp(cs->params.port->name, name))
|
|
|
|
cs = cs->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (cs == NULL)
|
|
|
|
{
|
|
|
|
DBG(2,"sane_open: No scanner found or requested port "
|
|
|
|
"doesn't exist (%s)\n", name);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
if (cs->scanner_present == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_open: Request to open port with no scanner "
|
|
|
|
"(%s)\n", name);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
if (cs->opened == SANE_TRUE)
|
|
|
|
{
|
|
|
|
DBG(2,"sane_open; Oi!, That scanner's already open.\n");
|
|
|
|
return SANE_STATUS_DEVICE_BUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the scanner has already been opened once, we don't have to do
|
|
|
|
* this setup again */
|
|
|
|
if (cs->setup == SANE_TRUE)
|
|
|
|
{
|
|
|
|
cs->opened = SANE_TRUE;
|
|
|
|
*h = (SANE_Handle)cs;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = ieee1284_claim(cs->params.port);
|
|
|
|
if (tmp != E1284_OK) {
|
|
|
|
DBG(1, "sane_open: Could not claim port!\n");
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
2002-04-07 11:20:46 +00:00
|
|
|
/* I put the scanner to sleep before, better wake it back up */
|
2002-04-01 22:54:25 +00:00
|
|
|
|
|
|
|
DBG(2, "sane_open: >> initialise\n");
|
|
|
|
tmp = sanei_canon_pp_initialise(&(cs->params));
|
|
|
|
DBG(2, "sane_open: << %d initialise\n", tmp);
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1, "sane_open: initialise returned %d, something is "
|
|
|
|
"wrong with the scanner!\n", tmp);
|
|
|
|
|
|
|
|
DBG(1, "sane_open: Can't contact scanner. Try power "
|
|
|
|
"cycling scanner, and unplug any "
|
|
|
|
"printers\n");
|
2002-04-07 11:20:46 +00:00
|
|
|
ieee1284_release(cs->params.port);
|
2002-04-01 22:54:25 +00:00
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cs->weights_file != NULL)
|
|
|
|
DBG(2, "sane_open: >> load_weights(%s, %p)\n",
|
|
|
|
cs->weights_file, &(cs->params));
|
|
|
|
else
|
|
|
|
DBG(2, "sane_open: >> load_weights(NULL, %p)\n", &(cs->params));
|
|
|
|
tmp = sanei_canon_pp_load_weights(cs->weights_file, &(cs->params));
|
|
|
|
DBG(2, "sane_open: << %d load_weights\n", tmp);
|
|
|
|
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1, "sane_open: WARNING: Error on load_weights: "
|
|
|
|
"returned %d. This could be due to a corrupt "
|
|
|
|
"calibration file. Try recalibrating and if "
|
|
|
|
"problems persist, please report the problem "
|
|
|
|
"to the canon_pp maintainer\n", tmp);
|
|
|
|
cs->cal_valid = SANE_FALSE;
|
|
|
|
} else {
|
|
|
|
cs->cal_valid = SANE_TRUE;
|
|
|
|
DBG(10, "sane_open: loadweights successful, uploading gamma"
|
|
|
|
" profile...\n");
|
|
|
|
tmp = sanei_canon_pp_adjust_gamma(&(cs->params));
|
|
|
|
if (tmp != 0)
|
|
|
|
DBG(1, "sane_open: WARNING: adjust_gamma returned "
|
|
|
|
"%d!\n", tmp);
|
|
|
|
|
|
|
|
DBG(10, "sane_open: after adjust_gamma Status = %i\n",
|
|
|
|
sanei_canon_pp_check_status(cs->params.port));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Configure ranges etc */
|
|
|
|
|
|
|
|
/* Resolution - determined by magic number */
|
|
|
|
|
|
|
|
if (cs->params.scanheadwidth == 2552)
|
|
|
|
cs->opt[OPT_RESOLUTION].constraint.word_list = res330;
|
|
|
|
else
|
|
|
|
cs->opt[OPT_RESOLUTION].constraint.word_list = res630;
|
|
|
|
|
|
|
|
|
|
|
|
/* TL-X */
|
|
|
|
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
(*tmp_range).min = 0;
|
|
|
|
/* max is set later */
|
|
|
|
cs->opt[OPT_TL_X].constraint.range = tmp_range;
|
|
|
|
|
|
|
|
/* TL-Y */
|
|
|
|
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
(*tmp_range).min = 0;
|
|
|
|
/* max is set later */
|
|
|
|
cs->opt[OPT_TL_Y].constraint.range = tmp_range;
|
|
|
|
|
|
|
|
/* BR-X */
|
|
|
|
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
(*tmp_range).min = 64;
|
|
|
|
/* max is set later */
|
|
|
|
cs->opt[OPT_BR_X].constraint.range = tmp_range;
|
|
|
|
|
|
|
|
/* BR-Y */
|
|
|
|
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
(*tmp_range).min = 1;
|
|
|
|
/* max is set later */
|
|
|
|
cs->opt[OPT_BR_Y].constraint.range = tmp_range;
|
|
|
|
|
|
|
|
/* set the max for all those ranges */
|
|
|
|
update_ranges(cs);
|
|
|
|
|
|
|
|
cs->opened = SANE_TRUE;
|
|
|
|
cs->setup = SANE_TRUE;
|
|
|
|
|
|
|
|
*h = (SANE_Handle)cs;
|
|
|
|
|
|
|
|
DBG(2, "<< sane_open\n");
|
|
|
|
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_get_option_descriptor()
|
|
|
|
*
|
|
|
|
* Return the structure for option number opt.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
const SANE_Option_Descriptor *
|
|
|
|
sane_get_option_descriptor (SANE_Handle h, SANE_Int opt)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
/*DBG(2, ">> sane_get_option_descriptor (h=%p, opt=%d)\n", h, opt);*/
|
|
|
|
|
|
|
|
if (h == NULL) {
|
|
|
|
DBG(10,"sane_get_option_descriptor: WARNING: h==NULL!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((unsigned)opt >= NUM_OPTIONS) {
|
|
|
|
DBG(10,"sane_get_option_descriptor: Note: opt >= "
|
|
|
|
"NUM_OPTIONS!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cs->opened == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_get_option_descriptor: That scanner (%p) ain't "
|
|
|
|
"open yet\n", cs);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*DBG(2, "<< sane_get_option_descriptor\n");*/
|
|
|
|
|
|
|
|
return (cs->opt + opt);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_control_option()
|
|
|
|
*
|
|
|
|
* Set a value for one of the options provided.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_control_option (SANE_Handle h, SANE_Int opt, SANE_Action act,
|
|
|
|
void *val, SANE_Word *info)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
int i = 0, tmp, maxresi;
|
|
|
|
|
|
|
|
DBG(2, ">> sane_control_option (h=%p, opt=%d, act=%d)\n",
|
|
|
|
h,opt,act);
|
|
|
|
|
|
|
|
/* Do some sanity checks on the parameters
|
|
|
|
* note that val can be null for buttons */
|
|
|
|
if ((h == NULL) || ((val == NULL) && (opt != OPT_CAL)))
|
|
|
|
/* || (info == NULL)) - Don't check this any more..
|
|
|
|
* frontends seem to like passing a null */
|
|
|
|
{
|
|
|
|
DBG(1,"sane_control_option: Frontend passed me a null! "
|
|
|
|
"(h=%p,val=%p,info=%p)\n",h,val,info);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (((unsigned)opt) >= NUM_OPTIONS)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_control_option: I don't do option %d.\n", opt);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cs->opened == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_control_option: That scanner (%p) ain't "
|
|
|
|
"open yet\n", cs);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cs->scanning == SANE_TRUE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_control_option: That scanner (%p) is scanning!\n",
|
|
|
|
cs);
|
|
|
|
return SANE_STATUS_DEVICE_BUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(act)
|
|
|
|
{
|
|
|
|
case SANE_ACTION_GET_VALUE:
|
|
|
|
switch (opt)
|
|
|
|
{
|
|
|
|
case OPT_COLOUR_MODE:
|
|
|
|
strcpy((char *)val,
|
|
|
|
cmodes[cs->vals[opt]]);
|
|
|
|
break;
|
|
|
|
case OPT_DEPTH:
|
|
|
|
strcpy((char *)val,
|
|
|
|
depths[cs->vals[opt]]);
|
|
|
|
break;
|
|
|
|
case OPT_RESOLUTION:
|
|
|
|
*((int *)val) = res630[cs->vals[opt]];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
*((int *)val) = cs->vals[opt];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SANE_ACTION_SET_VALUE:
|
2002-04-07 11:20:46 +00:00
|
|
|
/* val has been checked for NULL if opt != OPT_CAL */
|
|
|
|
if (opt != OPT_CAL) i = *((int *)val);
|
2002-04-01 22:54:25 +00:00
|
|
|
if (info != NULL) *info = 0;
|
|
|
|
switch (opt) {
|
|
|
|
case OPT_NUM_OPTIONS:
|
|
|
|
/* you can't set that! */
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
case OPT_RESOLUTION:
|
|
|
|
i = cs->vals[opt];
|
|
|
|
cs->vals[opt] = 1;
|
|
|
|
maxresi = cs->opt[OPT_RESOLUTION].
|
|
|
|
constraint.word_list[0];
|
|
|
|
|
|
|
|
while ((cs->vals[opt] <= maxresi) &&
|
|
|
|
(res630[cs->vals[opt]]
|
|
|
|
< *((int *)val)))
|
|
|
|
{
|
|
|
|
cs->vals[opt] += 1;
|
|
|
|
}
|
|
|
|
if (cs->vals[opt] != i)
|
|
|
|
{
|
|
|
|
update_ranges(cs);
|
2002-04-07 11:20:46 +00:00
|
|
|
/* shouldn't have to reload
|
|
|
|
* params too, but xsane
|
|
|
|
* seems a bit buggy in that
|
|
|
|
* respect. it's ok for us
|
|
|
|
* not to change them */
|
2002-04-01 22:54:25 +00:00
|
|
|
if (info != NULL) *info |=
|
2002-04-07 11:20:46 +00:00
|
|
|
SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
|
2002-04-01 22:54:25 +00:00
|
|
|
}
|
|
|
|
if (res630[cs->vals[opt]] !=
|
|
|
|
*((int *)val))
|
|
|
|
{
|
|
|
|
if (info != NULL) *info |=
|
|
|
|
SANE_INFO_INEXACT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case OPT_COLOUR_MODE:
|
|
|
|
cs->vals[opt] = 0;
|
|
|
|
while ((cmodes[cs->vals[opt]] != NULL)
|
|
|
|
&& strcmp(cmodes[cs->vals[opt]],
|
|
|
|
(char *)val))
|
|
|
|
{
|
|
|
|
cs->vals[opt] += 1;
|
|
|
|
}
|
|
|
|
if (info != NULL) *info |=
|
|
|
|
SANE_INFO_RELOAD_PARAMS;
|
|
|
|
break;
|
|
|
|
case OPT_DEPTH:
|
|
|
|
cs->vals[opt] = 0;
|
|
|
|
while ((depths[cs->vals[opt]] != NULL)
|
|
|
|
&& strcmp(depths[cs->vals[opt]],
|
|
|
|
(char *)val))
|
|
|
|
{
|
|
|
|
cs->vals[opt] += 1;
|
|
|
|
}
|
|
|
|
if (info != NULL) *info |=
|
|
|
|
SANE_INFO_RELOAD_PARAMS;
|
|
|
|
break;
|
|
|
|
case OPT_TL_X:
|
|
|
|
case OPT_BR_X:
|
|
|
|
if ((i<cs->opt[opt].constraint.range->min) || (i>cs->opt[opt].constraint.range->max))
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
/* Rnd up to the next multiple of 4 */
|
|
|
|
if (i % 4) {
|
|
|
|
DBG(100, "Inexact: X requested"
|
|
|
|
": %d,",i);
|
|
|
|
if (opt == OPT_TL_X)
|
|
|
|
i -= (i % 4);
|
|
|
|
else
|
|
|
|
i += 4 - (i % 4);
|
|
|
|
|
|
|
|
DBG(100, " X provided: %d\n",i);
|
|
|
|
if (info != NULL) *info |=
|
|
|
|
SANE_INFO_INEXACT;
|
|
|
|
}
|
|
|
|
cs->vals[opt] = i;
|
|
|
|
break;
|
|
|
|
case OPT_TL_Y:
|
|
|
|
case OPT_BR_Y:
|
|
|
|
if ((i<cs->opt[opt].constraint.range->min) || (i>cs->opt[opt].constraint.range->max))
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
cs->vals[opt] = i;
|
|
|
|
break;
|
|
|
|
case OPT_CAL:
|
|
|
|
/* Call the calibration code */
|
|
|
|
if ((cs->weights_file==NULL) ||
|
|
|
|
cs->cal_readonly
|
|
|
|
)
|
|
|
|
DBG(2, ">> calibrate(%p, "
|
|
|
|
"NULL)\n",
|
|
|
|
&(cs->params));
|
|
|
|
else
|
|
|
|
DBG(2, ">> calibrate(%p,"
|
|
|
|
"%s)\n",
|
|
|
|
&(cs->params),
|
|
|
|
cs->weights_file);
|
|
|
|
|
|
|
|
if (cs->cal_readonly) tmp =
|
|
|
|
sanei_canon_pp_calibrate(
|
|
|
|
&(cs->params),
|
|
|
|
NULL);
|
|
|
|
else tmp = sanei_canon_pp_calibrate(
|
|
|
|
&(cs->params),
|
|
|
|
cs->weights_file);
|
|
|
|
|
|
|
|
DBG(2, "<< %d calibrate\n",
|
|
|
|
tmp);
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1, "sane_control_option: "
|
|
|
|
"WARNING: "
|
|
|
|
"calibrate "
|
|
|
|
"returned %d!",
|
|
|
|
tmp);
|
|
|
|
cs->cal_valid =
|
|
|
|
SANE_FALSE;
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
} else {
|
|
|
|
cs->cal_valid =
|
|
|
|
SANE_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
/*case OPT_PREVIEW:
|
|
|
|
if (i) cs->vals[opt] = 1;
|
|
|
|
else cs->vals[opt] = 0;
|
|
|
|
break;*/
|
|
|
|
default:
|
|
|
|
/* Should never happen */
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SANE_ACTION_SET_AUTO:
|
|
|
|
DBG(2, "sane_control_option: attempt at "
|
|
|
|
"automatic control! (unsupported)\n");
|
|
|
|
/* Auto? are they mad? I'm not that smart! */
|
|
|
|
/* fall through. */
|
|
|
|
default:
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBG(2, "<< sane_control_option\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_get_parameters()
|
|
|
|
*
|
|
|
|
* Get information about the next packet. If a scan hasn't started, results
|
|
|
|
* only have to be best guesses.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_get_parameters (SANE_Handle h, SANE_Parameters *params)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
SANE_Int total_lines = 0;
|
|
|
|
DBG(2, ">> sane_get_parameters (h=%p, params=%p)\n", h, params);
|
|
|
|
|
|
|
|
if (h == NULL) return SANE_STATUS_INVAL;
|
|
|
|
|
|
|
|
if (cs->opened == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_get_parameters: That scanner (%p) ain't "
|
|
|
|
"open yet\n", cs);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* These don't change whether we're scanning or not
|
|
|
|
* NOTE: Assumes options don't change after scanning commences
|
|
|
|
*/
|
|
|
|
total_lines = cs->vals[OPT_BR_Y] - cs->vals[OPT_TL_Y];
|
|
|
|
|
|
|
|
params->depth = cs->vals[OPT_DEPTH] ? 16 : 8;
|
|
|
|
|
|
|
|
switch (cs->vals[OPT_COLOUR_MODE])
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
params->format = SANE_FRAME_GRAY;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
params->format = SANE_FRAME_RGB;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* shouldn't happen */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
params->pixels_per_line = cs->vals[OPT_BR_X] - cs->vals[OPT_TL_X];
|
|
|
|
|
|
|
|
if (!(params->pixels_per_line)) {
|
|
|
|
params->last_frame = SANE_TRUE;
|
|
|
|
params->lines = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Always assume next packet will be the last
|
|
|
|
* - frontends seem to like it that way */
|
|
|
|
params->last_frame = SANE_TRUE;
|
|
|
|
params->lines = total_lines - cs->lines_scanned;
|
|
|
|
|
|
|
|
switch (cs->vals[OPT_COLOUR_MODE])
|
|
|
|
{
|
|
|
|
case 0: /* Grey */
|
|
|
|
params->bytes_per_line =
|
|
|
|
params->pixels_per_line * (params->depth/8);
|
|
|
|
break;
|
|
|
|
case 1: /* Colour */
|
|
|
|
params->bytes_per_line =
|
|
|
|
params->pixels_per_line * (params->depth/8) * 3;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* shouldn't happen */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Do we need to account for the scanner's max buffer here? */
|
|
|
|
|
|
|
|
DBG(2, "<< sane_get_parameters\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_start()
|
|
|
|
*
|
|
|
|
* Starts scanning an image.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_start (SANE_Handle h)
|
|
|
|
{
|
|
|
|
int i,tmp;
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
DBG(2, ">> sane_start (h=%p)\n", h);
|
|
|
|
|
|
|
|
if (h == NULL) return SANE_STATUS_INVAL;
|
|
|
|
|
|
|
|
if (cs->scanning) return SANE_STATUS_DEVICE_BUSY;
|
|
|
|
if (cs->opened == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_start: That scanner (%p) ain't "
|
|
|
|
"open yet\n", cs);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy the options stored in the vals into the scaninfo */
|
|
|
|
cs->scan.width = cs->vals[OPT_BR_X] - cs->vals[OPT_TL_X];
|
|
|
|
cs->scan.height = cs->vals[OPT_BR_Y] - cs->vals[OPT_TL_Y];
|
|
|
|
cs->scan.xoffset = cs->vals[OPT_TL_X];
|
|
|
|
cs->scan.yoffset = cs->vals[OPT_TL_Y];
|
|
|
|
|
|
|
|
if (((cs->vals[OPT_BR_Y] - cs->vals[OPT_TL_Y]) <= 0) ||
|
|
|
|
((cs->vals[OPT_BR_X] - cs->vals[OPT_TL_X]) <= 0))
|
|
|
|
{
|
|
|
|
DBG(1,"sane_start: height = %d, Width = %d. "
|
|
|
|
"Can't scan void range!",
|
|
|
|
cs->scan.height, cs->scan.width);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We use 630 res list here because the 330 res list is just a shorter
|
|
|
|
* version, so this will always work. */
|
|
|
|
tmp = res630[cs->vals[OPT_RESOLUTION]];
|
|
|
|
|
|
|
|
DBG(10, "sane_start: val=%d, tmp=%d\n",
|
|
|
|
cs->vals[OPT_RESOLUTION], tmp);
|
|
|
|
|
|
|
|
/* We pass a value to init_scan which is the power of 2 that 75
|
|
|
|
* is multiplied for the resolution. ie:
|
|
|
|
* 75 -> 0
|
|
|
|
* 150 -> 1
|
|
|
|
* 300 -> 2
|
|
|
|
* 600 -> 4
|
|
|
|
*
|
|
|
|
* This rather strange parameter is a result of the way the scanner
|
|
|
|
* takes its resolution argument
|
|
|
|
*/
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (tmp > 75)
|
|
|
|
{
|
|
|
|
i++;
|
|
|
|
tmp = tmp >> 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME? xres == yres for now. */
|
|
|
|
cs->scan.xresolution = i;
|
|
|
|
cs->scan.yresolution = i;
|
|
|
|
|
|
|
|
cs->scan.mode = cs->vals[OPT_COLOUR_MODE];
|
|
|
|
|
|
|
|
DBG(10, ">> init_scan(%p, %p)\n", &(cs->params), &(cs->scan));
|
|
|
|
tmp = sanei_canon_pp_init_scan(&(cs->params), &(cs->scan));
|
|
|
|
DBG(10, "<< %d init_scan\n", tmp);
|
|
|
|
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1,"sane_start: WARNING: init_scan returned %d!", tmp);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
cs->scanning = SANE_TRUE;
|
|
|
|
cs->cancelled = SANE_FALSE;
|
|
|
|
cs->sent_eof = SANE_FALSE;
|
|
|
|
|
|
|
|
/* init_scan doesn't return a value yet... but Simon might get keen
|
|
|
|
* one day
|
|
|
|
if (!(init_scan(&(cs->params), &(cs->scan))))
|
|
|
|
{
|
|
|
|
cs->scanning = SANE_TRUE;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cs->scanning = SANE_FALSE;
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
DBG(2, "<< sane_start\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_read()
|
|
|
|
*
|
|
|
|
* Reads some information from the buffer.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_read (SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenp)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
image_segment *is;
|
|
|
|
unsigned int lines, bytes, bpl;
|
|
|
|
unsigned int i;
|
|
|
|
short *shortptr;
|
|
|
|
SANE_Byte *charptr;
|
|
|
|
int max_buf, tmp;
|
|
|
|
|
|
|
|
static SANE_Byte *lbuf;
|
|
|
|
static unsigned int bytesleft;
|
|
|
|
|
|
|
|
DBG(2, ">> sane_read (h=%p, buf=%p, maxlen=%d)\n", h, buf, maxlen);
|
|
|
|
|
|
|
|
/* default to returning 0 - for errors */
|
|
|
|
*lenp = 0;
|
|
|
|
|
|
|
|
if ((h == NULL) || (buf == NULL) || (lenp == NULL))
|
|
|
|
{
|
|
|
|
DBG(1, "sane_read: This frontend's passing me dodgy gear! "
|
|
|
|
"(h=%p, buf=%p, lenp=%p)\n",
|
|
|
|
h, buf, lenp);
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now we have to see if we have some leftover from last time */
|
|
|
|
|
|
|
|
if (read_leftover != NULL) {
|
|
|
|
/* feed some more data in until we've run out - don't care
|
|
|
|
* whether or not we _think_ the scanner is scanning now,
|
|
|
|
* because we may still have data left over to send */
|
|
|
|
|
|
|
|
/* Now feed it some data from lbuf */
|
|
|
|
if (bytesleft <= (unsigned int)maxlen)
|
|
|
|
{
|
|
|
|
/* enough buffer to send the lot */
|
|
|
|
memcpy(buf, lbuf, bytesleft);
|
|
|
|
free(lbuf);
|
|
|
|
*lenp = bytesleft;
|
|
|
|
lbuf = NULL;
|
|
|
|
read_leftover = NULL;
|
|
|
|
bytesleft = 0;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* only enough to send maxlen */
|
|
|
|
memcpy(buf, lbuf, maxlen);
|
|
|
|
read_leftover = lbuf + maxlen;
|
|
|
|
bytesleft -= maxlen;
|
|
|
|
*lenp = maxlen;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* fix potential memory leak if cancelling at the very end
|
|
|
|
* of a scan */
|
|
|
|
if (lbuf != NULL) free(lbuf);
|
|
|
|
lbuf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Has the last scan ended? */
|
|
|
|
if (((unsigned)cs->scan.height == (unsigned)cs->lines_scanned)
|
|
|
|
|| !(cs->scanning))
|
|
|
|
{
|
|
|
|
if (cs->cancelled)
|
|
|
|
return SANE_STATUS_CANCELLED;
|
|
|
|
|
|
|
|
if (cs->sent_eof)
|
|
|
|
{
|
|
|
|
/* It's over mate, get over it */
|
|
|
|
DBG(10, "sane_read: Already sent EOF!\n");
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
} else {
|
|
|
|
cs->sent_eof = SANE_TRUE;
|
|
|
|
return SANE_STATUS_EOF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* At this point we have to read more data from the scanner */
|
|
|
|
|
|
|
|
|
|
|
|
/* Decide how many lines we can fit into this buffer */
|
|
|
|
if (cs->vals[OPT_DEPTH] == 0)
|
|
|
|
bpl = cs->scan.width * (cs->vals[OPT_COLOUR_MODE] ? 3 : 1);
|
|
|
|
else
|
|
|
|
bpl = cs->scan.width * (cs->vals[OPT_COLOUR_MODE] ? 6 : 2);
|
|
|
|
|
|
|
|
/* We will have as many lines as possible, or the size of the scan.
|
|
|
|
* Each pixel is 5/4 bytes per colour, so we multiply the buffer
|
|
|
|
* size by 4/5 to get the number of lines. */
|
|
|
|
if ((int)maxlen > (BUF_MAX * 4 / 5))
|
|
|
|
max_buf = (BUF_MAX * 4 / 5);
|
|
|
|
else
|
|
|
|
max_buf = (int)maxlen;
|
|
|
|
|
|
|
|
lines = max_buf / (int)bpl;
|
|
|
|
if (lines > cs->scan.height) lines = cs->scan.height;
|
|
|
|
if ((cs->scan.height - cs->lines_scanned) < lines)
|
|
|
|
lines = cs->scan.height - cs->lines_scanned;
|
|
|
|
|
|
|
|
if (!lines)
|
|
|
|
{
|
|
|
|
/* can't fit a whole line into the buffer */
|
|
|
|
DBG(10, "sane_read: Tighter than a duck's butt (can't fit a "
|
|
|
|
"whole scanline in the buffer)\n");
|
|
|
|
/* return SANE_STATUS_INVAL; */
|
|
|
|
/* Try and deal with this gracefully */
|
|
|
|
lines = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes = lines * bpl;
|
|
|
|
|
|
|
|
/* Allocate a local buffer to hold the data while we play */
|
|
|
|
if ((lbuf = malloc(bytes)) == NULL)
|
|
|
|
{
|
|
|
|
DBG(10, "sane_read: Not enough memory to hold a "
|
|
|
|
"local buffer. You're doomed\n");
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* This call required a lot of debugging information.. */
|
|
|
|
DBG(10, "sane_read: Here's what we're sending read_segment:\n");
|
|
|
|
DBG(10, "scanner setup: shw=%d xres=%d yres=%d %d %d id=%s\n",
|
|
|
|
cs->params.scanheadwidth,
|
|
|
|
cs->params.natural_xresolution,
|
|
|
|
cs->params.natural_yresolution,
|
|
|
|
cs->params.max_xresolution,
|
|
|
|
cs->params.max_yresolution,
|
|
|
|
(cs->params.id_string)+8);
|
|
|
|
DBG(10, "scan_params->: width=%d, height=%d, xoffset=%d, "
|
|
|
|
"yoffset=%d\n\txresolution=%d, yresolution=%d, "
|
|
|
|
"mode=%d\n\n",
|
|
|
|
cs->scan.width, cs->scan.height,
|
|
|
|
cs->scan.xoffset, cs->scan.yoffset,
|
|
|
|
cs->scan.xresolution, cs->scan.yresolution,
|
|
|
|
cs->scan.mode
|
|
|
|
);
|
|
|
|
DBG(10, "lines=%d\n",lines);
|
|
|
|
|
|
|
|
DBG(2, ">> read_segment(%p, %p, %p, %d)\n",
|
|
|
|
&is, &(cs->params), &(cs->scan), lines);
|
|
|
|
tmp = sanei_canon_pp_read_segment(&is, &(cs->params),
|
|
|
|
&(cs->scan), lines, cs->cal_valid);
|
|
|
|
DBG(2, "<< %d read_segment\n", tmp);
|
|
|
|
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1, "sane_read: WARNING: read_segment returned %d!\n", tmp);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(10, "sane_read: bpl=%d, lines=%d, bytes=%d\n", bpl, lines, bytes);
|
|
|
|
|
|
|
|
cs->lines_scanned += lines;
|
|
|
|
|
|
|
|
/* translate data out of buffer */
|
|
|
|
if (cs->vals[OPT_COLOUR_MODE] == 0)
|
|
|
|
{
|
|
|
|
/* fudge because simon's code is slack and returns greyscale as
|
|
|
|
* RGB data, just with the same stuff in R,G,B. */
|
|
|
|
DBG(10, "sane_read: Initialising FUDGE engine 1.0\n");
|
|
|
|
if (cs->vals[OPT_DEPTH] == 0)
|
|
|
|
{
|
|
|
|
/* 8bpp */
|
|
|
|
for(i = 0; i < bytes * 3; i += 3)
|
|
|
|
{
|
|
|
|
*((char *)lbuf + i/3) =
|
|
|
|
*((char *)(is->image_data) + (i*2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* 16bpp */
|
|
|
|
for(i = 0; i < (bytes/2) * 3; i += 3)
|
|
|
|
{
|
|
|
|
/* Convert bigendian data to the local system */
|
|
|
|
*((short *)lbuf + i/3) = MAKE_SHORT(
|
|
|
|
*((char *)(is->image_data)
|
|
|
|
+ (i*2)),
|
|
|
|
*((char *)(is->image_data)
|
|
|
|
+ (i*2)+1)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (cs->vals[OPT_DEPTH] == 0)
|
|
|
|
{
|
|
|
|
/* 8bpp */
|
|
|
|
for(i = 0; i < bytes; i++)
|
|
|
|
{
|
|
|
|
charptr = lbuf + i;
|
|
|
|
if (i % 3 == 0) charptr += 2;
|
|
|
|
if (i % 3 == 2) charptr -= 2;
|
|
|
|
*(charptr) =
|
|
|
|
*((char *)(is->image_data) + (i*2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* 16bpp */
|
|
|
|
for(i = 0; i < (bytes/2); i++)
|
|
|
|
{
|
|
|
|
shortptr = ((short *)lbuf + i);
|
|
|
|
if (i % 3 == 0) shortptr += 2;
|
|
|
|
if (i % 3 == 2) shortptr -= 2;
|
|
|
|
*shortptr = MAKE_SHORT(
|
|
|
|
*((char *)(is->image_data) +
|
|
|
|
(i*2)),
|
|
|
|
*((char *)(is->image_data) +
|
|
|
|
(i*2)+1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now feed it some data from lbuf */
|
|
|
|
if (bytes <= (unsigned int)maxlen)
|
|
|
|
{
|
|
|
|
/* enough buffer to send the lot */
|
|
|
|
memcpy(buf, lbuf, bytes);
|
|
|
|
*lenp = bytes;
|
|
|
|
free(lbuf);
|
|
|
|
lbuf = NULL;
|
|
|
|
read_leftover = NULL;
|
|
|
|
bytesleft = 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* only enough to send maxlen */
|
|
|
|
memcpy(buf, lbuf, maxlen);
|
|
|
|
*lenp = maxlen;
|
|
|
|
read_leftover = lbuf + maxlen;
|
|
|
|
bytesleft = bytes - maxlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((unsigned)cs->lines_scanned >= cs->scan.height)
|
|
|
|
{
|
|
|
|
/* The scan is over! Don't need to call anything in the
|
|
|
|
* hardware, it will sort itself out */
|
|
|
|
DBG(10, "sane_read: Scan is finished.\n");
|
|
|
|
cs->scanning = SANE_FALSE;
|
|
|
|
cs->lines_scanned = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(2, "<< sane_read\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_cancel()
|
|
|
|
*
|
|
|
|
* Cancels a scan in progress
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
void
|
|
|
|
sane_cancel (SANE_Handle h)
|
|
|
|
{
|
|
|
|
int tmp;
|
|
|
|
/* Note: assume handle is valid apart from NULLs */
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
DBG(2, ">> sane_cancel (h=%p)\n", h);
|
|
|
|
if (h == NULL) return;
|
|
|
|
|
|
|
|
if (cs->scanning == SANE_FALSE)
|
|
|
|
{
|
|
|
|
/* ensure we don't try to send old data */
|
|
|
|
read_leftover = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cs->scanning = SANE_FALSE;
|
|
|
|
/* cs->sent_eof = SANE_TRUE; */
|
|
|
|
cs->cancelled = SANE_TRUE;
|
|
|
|
|
|
|
|
cs->lines_scanned = 0;
|
|
|
|
DBG(2, "sane_cancel: >> abort_scan\n");
|
|
|
|
tmp = sanei_canon_pp_abort_scan(&(cs->params), &(cs->scan));
|
|
|
|
DBG(2, "sane_cancel: << abort_scan\n");
|
|
|
|
if (tmp != 0) {
|
|
|
|
DBG(1, "sane_cancel: WARNING: abort_scan returned %d!", tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(2, "<< sane_cancel\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_close()
|
|
|
|
*
|
|
|
|
* Closes a scanner handle. Scanner is assumed to be free after this.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
void
|
|
|
|
sane_close (SANE_Handle h)
|
|
|
|
{
|
|
|
|
/* Note: assume handle is valid apart from NULLs */
|
|
|
|
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
|
|
|
DBG(2, ">> sane_close (h=%p)\n", h);
|
|
|
|
if (h == NULL) return;
|
|
|
|
|
|
|
|
if (cs->opened == SANE_FALSE)
|
|
|
|
{
|
|
|
|
DBG(1,"sane_close: That scanner (%p) ain't "
|
|
|
|
"open yet\n", cs);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Put scanner back in transparent mode */
|
|
|
|
sanei_canon_pp_close_scanner(&(cs->params));
|
|
|
|
|
|
|
|
cs->opened = SANE_FALSE;
|
|
|
|
|
|
|
|
/* if it was scanning, it's not any more */
|
|
|
|
cs->scanning = SANE_FALSE;
|
|
|
|
cs->sent_eof = SANE_TRUE;
|
|
|
|
|
|
|
|
ieee1284_release(cs->params.port);
|
|
|
|
|
|
|
|
DBG(2, "<< sane_close\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* sane_exit()
|
|
|
|
*
|
|
|
|
* Shut it down!
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
void
|
|
|
|
sane_exit (void)
|
|
|
|
{
|
|
|
|
CANONP_Scanner *dev, *next;
|
|
|
|
|
|
|
|
DBG(2, ">> sane_exit\n");
|
|
|
|
|
|
|
|
for (dev = first_dev; dev != NULL; dev = next)
|
|
|
|
{
|
|
|
|
next = dev->next;
|
|
|
|
|
|
|
|
/* These were only created if the scanner has been init'd */
|
|
|
|
|
|
|
|
/* Should normally nullify pointers after freeing, but in
|
|
|
|
* this case we're about to free the whole structure so
|
|
|
|
* theres not a lot of point. */
|
|
|
|
|
|
|
|
/* Constraints (mostly) allocated when the scanner is opened */
|
|
|
|
if(dev->opt[OPT_TL_X].constraint.range)
|
|
|
|
free((void *)(dev->opt[OPT_TL_X].constraint.range));
|
|
|
|
if(dev->opt[OPT_TL_Y].constraint.range)
|
|
|
|
free((void *)(dev->opt[OPT_TL_Y].constraint.range));
|
|
|
|
if(dev->opt[OPT_BR_X].constraint.range)
|
|
|
|
free((void *)(dev->opt[OPT_BR_X].constraint.range));
|
|
|
|
if(dev->opt[OPT_BR_Y].constraint.range)
|
|
|
|
free((void *)(dev->opt[OPT_BR_Y].constraint.range));
|
|
|
|
|
|
|
|
/* Weights file now on a per-scanner basis */
|
|
|
|
if (dev->weights_file != NULL)
|
|
|
|
free(dev->weights_file);
|
|
|
|
|
|
|
|
if (dev->scanner_present)
|
|
|
|
{
|
|
|
|
if (dev->opened == SANE_TRUE)
|
|
|
|
{
|
|
|
|
/* naughty boys, should have closed first */
|
|
|
|
ieee1284_release(dev->params.port);
|
|
|
|
}
|
|
|
|
ieee1284_close(dev->params.port);
|
|
|
|
}
|
|
|
|
|
|
|
|
free (dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
first_dev = NULL;
|
|
|
|
|
2002-04-07 11:20:46 +00:00
|
|
|
/* FIXEDME: this creates a segfault in DLL code. */
|
|
|
|
/* freeing something it shouldn't perhaps? */
|
|
|
|
/* Turns out to be a bug in libieee1284 0.1.4 and only occurs on 2.2
|
|
|
|
* kernels - have sent mail to Tim Waugh, will leave commented out
|
|
|
|
* until I receive a reply */
|
2002-04-01 22:54:25 +00:00
|
|
|
/* ieee1284_free_ports(&pl); */
|
|
|
|
|
|
|
|
DBG(2, "<< sane_exit\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* init_device()
|
|
|
|
*
|
|
|
|
* (Not part of the SANE API)
|
|
|
|
*
|
|
|
|
* Initialises a CANONP_Scanner data structure for a new device.
|
|
|
|
* NOTE: The device is not ready to scan until initialise() has been
|
|
|
|
* called in scan library!
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
static SANE_Status init_device(struct parport *pp)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
static const char *hw_vendor = "CANON";
|
|
|
|
static const char *hw_type = "flatbed scanner";
|
|
|
|
static const char *opt_names[] = {
|
|
|
|
SANE_NAME_NUM_OPTIONS,
|
|
|
|
SANE_NAME_SCAN_RESOLUTION,
|
|
|
|
SANE_NAME_SCAN_MODE,
|
|
|
|
SANE_NAME_BIT_DEPTH,
|
|
|
|
SANE_NAME_SCAN_TL_X,
|
|
|
|
SANE_NAME_SCAN_TL_Y,
|
|
|
|
SANE_NAME_SCAN_BR_X,
|
|
|
|
SANE_NAME_SCAN_BR_Y,
|
|
|
|
SANE_NAME_QUALITY_CAL
|
|
|
|
#if 0
|
|
|
|
SANE_NAME_GAMMA_R,
|
|
|
|
SANE_NAME_GAMMA_G,
|
|
|
|
SANE_NAME_GAMMA_B
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
static const char *opt_titles[] = {
|
|
|
|
SANE_TITLE_NUM_OPTIONS,
|
|
|
|
SANE_TITLE_SCAN_RESOLUTION,
|
|
|
|
SANE_TITLE_SCAN_MODE,
|
|
|
|
SANE_TITLE_BIT_DEPTH,
|
|
|
|
SANE_TITLE_SCAN_TL_X,
|
|
|
|
SANE_TITLE_SCAN_TL_Y,
|
|
|
|
SANE_TITLE_SCAN_BR_X,
|
|
|
|
SANE_TITLE_SCAN_BR_Y,
|
|
|
|
SANE_TITLE_QUALITY_CAL
|
|
|
|
#if 0
|
|
|
|
SANE_TITLE_GAMMA_R,
|
|
|
|
SANE_TITLE_GAMMA_G,
|
|
|
|
SANE_TITLE_GAMMA_B
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
static const char *opt_descs[] = {
|
|
|
|
SANE_DESC_NUM_OPTIONS,
|
|
|
|
SANE_DESC_SCAN_RESOLUTION,
|
|
|
|
SANE_DESC_SCAN_MODE,
|
|
|
|
SANE_DESC_BIT_DEPTH,
|
|
|
|
SANE_DESC_SCAN_TL_X,
|
|
|
|
SANE_DESC_SCAN_TL_Y,
|
|
|
|
SANE_DESC_SCAN_BR_X,
|
|
|
|
SANE_DESC_SCAN_BR_Y,
|
|
|
|
SANE_DESC_QUALITY_CAL
|
|
|
|
#if 0
|
|
|
|
SANE_DESC_GAMMA_R,
|
|
|
|
SANE_DESC_GAMMA_G,
|
|
|
|
SANE_DESC_GAMMA_B
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
CANONP_Scanner *cs = NULL;
|
|
|
|
|
|
|
|
DBG(2, ">> init_device\n");
|
|
|
|
|
|
|
|
cs = malloc (sizeof (CANONP_Scanner));
|
|
|
|
if (cs == NULL)
|
|
|
|
{
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
}
|
|
|
|
memset (cs, 0, sizeof (CANONP_Scanner));
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
if ((cs->params.port = malloc(sizeof(*pp))) == NULL)
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
|
|
|
|
memcpy(cs->params.port, pp, sizeof(*pp));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
cs->params.port = pp;
|
|
|
|
|
|
|
|
/* ensure these are null to start off with, otherwise they might be
|
|
|
|
* erroneously free'd. Note that we set everything to 0 above
|
|
|
|
* but that's not *always* the same thing */
|
|
|
|
cs->params.blackweight = NULL;
|
|
|
|
cs->params.redweight = NULL;
|
|
|
|
cs->params.greenweight = NULL;
|
|
|
|
cs->params.blueweight = NULL;
|
|
|
|
|
|
|
|
/* Set some sensible defaults */
|
|
|
|
cs->hw.name = cs->params.port->name;
|
|
|
|
cs->hw.vendor = hw_vendor;
|
|
|
|
cs->hw.type = hw_type;
|
|
|
|
cs->opened = SANE_FALSE;
|
|
|
|
cs->scanning = SANE_FALSE;
|
|
|
|
cs->cancelled = SANE_FALSE;
|
|
|
|
cs->sent_eof = SANE_TRUE;
|
|
|
|
|
|
|
|
DBG(10, "init_device: [configuring options]\n");
|
|
|
|
|
|
|
|
/* take a punt at each option, then we change it later */
|
|
|
|
for (i = 0; i < NUM_OPTIONS; i++)
|
|
|
|
{
|
|
|
|
cs->opt[i].name = opt_names[i];
|
|
|
|
cs->opt[i].title = opt_titles[i];
|
|
|
|
cs->opt[i].desc = opt_descs[i];
|
|
|
|
cs->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
|
|
|
|
cs->opt[i].type = SANE_TYPE_INT;
|
|
|
|
cs->opt[i].size = sizeof(SANE_Int);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: num_options\n");
|
|
|
|
/* The number of options option */
|
|
|
|
|
|
|
|
cs->opt[OPT_NUM_OPTIONS].unit = SANE_UNIT_NONE;
|
|
|
|
cs->opt[OPT_NUM_OPTIONS].cap = SANE_CAP_SOFT_DETECT;
|
|
|
|
cs->vals[OPT_NUM_OPTIONS] = NUM_OPTIONS;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: resolution\n");
|
|
|
|
|
|
|
|
/* The resolution of scanning (X res == Y res for now)*/
|
|
|
|
cs->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
|
|
|
|
cs->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
|
|
|
|
/* should never point at first element (wordlist size) */
|
|
|
|
cs->vals[OPT_RESOLUTION] = 1;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: colour mode\n");
|
|
|
|
|
|
|
|
/* The colour mode (0=grey 1=rgb) */
|
|
|
|
cs->opt[OPT_COLOUR_MODE].type = SANE_TYPE_STRING;
|
|
|
|
cs->opt[OPT_COLOUR_MODE].size = 20;
|
|
|
|
cs->opt[OPT_COLOUR_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
|
|
|
|
/* Set this one here because it doesn't change by scanner (yet) */
|
|
|
|
cs->opt[OPT_COLOUR_MODE].constraint.string_list = cmodes;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: bit depth\n");
|
|
|
|
|
|
|
|
/* The bit depth */
|
|
|
|
cs->opt[OPT_DEPTH].type = SANE_TYPE_STRING;
|
|
|
|
cs->opt[OPT_DEPTH].size = 20;
|
|
|
|
cs->opt[OPT_DEPTH].cap |= SANE_CAP_EMULATED;
|
|
|
|
cs->opt[OPT_DEPTH].constraint_type = SANE_CONSTRAINT_STRING_LIST;
|
|
|
|
cs->opt[OPT_DEPTH].constraint.string_list = depths;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: tl-x\n");
|
|
|
|
|
|
|
|
/* The top-left-x */
|
|
|
|
cs->opt[OPT_TL_X].unit = SANE_UNIT_PIXEL;
|
|
|
|
cs->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: tl-y\n");
|
|
|
|
|
|
|
|
/* The top-left-y */
|
|
|
|
cs->opt[OPT_TL_Y].unit = SANE_UNIT_PIXEL;
|
|
|
|
cs->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: br-x\n");
|
|
|
|
|
|
|
|
/* The bottom-right-x */
|
|
|
|
cs->opt[OPT_BR_X].unit = SANE_UNIT_PIXEL;
|
|
|
|
cs->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
|
|
/* default scan width */
|
|
|
|
cs->vals[OPT_BR_X] = 100;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: br-y\n");
|
|
|
|
|
|
|
|
/* The bottom-right-y */
|
|
|
|
cs->opt[OPT_BR_Y].unit = SANE_UNIT_PIXEL;
|
|
|
|
cs->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
|
|
|
|
cs->vals[OPT_BR_Y] = 100;
|
|
|
|
|
|
|
|
DBG(100, "init_device: configuring opt: calibrate\n");
|
|
|
|
|
|
|
|
/* The calibration button */
|
|
|
|
cs->opt[OPT_CAL].type = SANE_TYPE_BUTTON;
|
|
|
|
cs->opt[OPT_CAL].constraint_type = SANE_CONSTRAINT_NONE;
|
|
|
|
if (cs->cal_readonly)
|
|
|
|
cs->opt[OPT_CAL].cap |= SANE_CAP_INACTIVE;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/* the gamma values (once we do them) */
|
|
|
|
cs->opt[OPT_GAMMA_R].caps |= SANE_CAP_ADVANCED;
|
|
|
|
cs->opt[OPT_GAMMA_G].caps |= SANE_CAP_ADVANCED;
|
|
|
|
cs->opt[OPT_GAMMA_B].caps |= SANE_CAP_ADVANCED;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE: Ranges and lists are actually set when scanner is opened,
|
|
|
|
* becase that's when we find out what sort of scanner it is
|
|
|
|
*/
|
|
|
|
|
|
|
|
DBG(100, "init_device: done opts\n");
|
|
|
|
|
|
|
|
/* add it to the head of the tree */
|
|
|
|
cs->next = first_dev;
|
|
|
|
first_dev = cs;
|
|
|
|
|
|
|
|
num_devices++;
|
|
|
|
|
|
|
|
DBG(2, "<< init_device\n");
|
|
|
|
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* These two are optional ones... maybe if I get really keen?
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
SANE_Status
|
|
|
|
sane_set_io_mode (SANE_Handle h, SANE_Bool non_blocking)
|
|
|
|
{
|
|
|
|
DBG(2, ">> sane_set_io_mode (%p, %d) (not really supported)\n",
|
|
|
|
h, non_blocking);
|
|
|
|
|
|
|
|
if (non_blocking == SANE_FALSE)
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
|
|
|
|
DBG(2, "<< sane_set_io_mode\n");
|
|
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
SANE_Status
|
|
|
|
sane_get_select_fd (SANE_Handle h, SANE_Int *fdp)
|
|
|
|
{
|
|
|
|
DBG(2, ">> sane_get_select_fd (%p, %p) (not supported)\n", h, fdp);
|
|
|
|
DBG(2, "<< sane_get_select_fd\n");
|
|
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* update_ranges(): when the res is changed, the max scan size in pixels
|
|
|
|
* has to be changed.
|
|
|
|
*
|
|
|
|
*************************************************************************/
|
|
|
|
static void update_ranges(CANONP_Scanner *cs)
|
|
|
|
{
|
|
|
|
int new_width, new_height, max_res;
|
|
|
|
|
|
|
|
/* FIXME: Magic numbers ahead! */
|
|
|
|
|
|
|
|
max_res = cs->params.scanheadwidth == 2552 ? 300 : 600;
|
|
|
|
|
|
|
|
new_width = cs->params.scanheadwidth /
|
|
|
|
(max_res / res630[cs->vals[OPT_RESOLUTION]]);
|
2002-04-07 11:20:46 +00:00
|
|
|
new_height = (cs->params.scanheadwidth == 2552 ? 3508 : 7016) /
|
2002-04-01 22:54:25 +00:00
|
|
|
(max_res / res630[cs->vals[OPT_RESOLUTION]]);
|
|
|
|
|
|
|
|
/* Max width has to be divisible by 4 */
|
|
|
|
new_width -= (new_width%4);
|
|
|
|
|
|
|
|
DBG(10, "update_ranges: new_width = %d, new_height = %d\n",
|
|
|
|
new_width, new_height);
|
|
|
|
|
|
|
|
(*((SANE_Range *)(cs->opt[OPT_TL_X].constraint.range))).max =
|
|
|
|
new_width - 1;
|
|
|
|
(*((SANE_Range *)(cs->opt[OPT_TL_Y].constraint.range))).max =
|
|
|
|
new_height - 1;
|
|
|
|
(*((SANE_Range *)(cs->opt[OPT_BR_X].constraint.range))).max =
|
|
|
|
new_width;
|
|
|
|
(*((SANE_Range *)(cs->opt[OPT_BR_Y].constraint.range))).max =
|
|
|
|
new_height;
|
|
|
|
|
|
|
|
if (cs->vals[OPT_TL_X] > cs->opt[OPT_TL_X].constraint.range->max)
|
|
|
|
cs->vals[OPT_TL_X] = cs->opt[OPT_TL_X].constraint.range->max;
|
|
|
|
if (cs->vals[OPT_BR_X] > cs->opt[OPT_BR_X].constraint.range->max)
|
|
|
|
cs->vals[OPT_BR_X] = cs->opt[OPT_BR_X].constraint.range->max;
|
|
|
|
if (cs->vals[OPT_TL_Y] > cs->opt[OPT_TL_Y].constraint.range->max)
|
|
|
|
cs->vals[OPT_TL_Y] = cs->opt[OPT_TL_Y].constraint.range->max;
|
|
|
|
if (cs->vals[OPT_BR_Y] > cs->opt[OPT_BR_Y].constraint.range->max)
|
|
|
|
cs->vals[OPT_BR_Y] = cs->opt[OPT_BR_Y].constraint.range->max;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* init_cal(): Try to create a calibration file
|
|
|
|
* has to be changed.
|
|
|
|
*
|
|
|
|
************************************************************************/
|
|
|
|
static int init_cal(char *file)
|
|
|
|
{
|
|
|
|
char *tmp, *path;
|
|
|
|
int f, i;
|
|
|
|
|
|
|
|
if ((f = open(file, O_CREAT | O_WRONLY, 0600)) < 0)
|
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
{
|
|
|
|
/* we need to try and make ~/.sane perhaps -
|
|
|
|
* find the last / in the file path, and try
|
|
|
|
* to create it */
|
|
|
|
if ((tmp = strrchr(file, '/')) == NULL)
|
|
|
|
return -1;
|
|
|
|
path = strdup(file);
|
|
|
|
*(path + (tmp-file)) = '\0';
|
|
|
|
i = mkdir(path, 0777);
|
|
|
|
free(path);
|
|
|
|
if (i) return -1;
|
|
|
|
/* Path has been created, now try this again.. */
|
|
|
|
if ((f = open(file, O_CREAT | O_WRONLY, 0600)) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Error is something like access denied - too
|
|
|
|
* hard to fix, so i give up... */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* should probably set defaults here.. */
|
|
|
|
close(f);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* fix_weights_file(): Ensures that the weights_file setting for a given
|
|
|
|
* scanner is valid
|
|
|
|
*
|
|
|
|
************************************************************************/
|
|
|
|
static SANE_Status fix_weights_file(CANONP_Scanner *cs)
|
|
|
|
{
|
|
|
|
char *tmp, *myhome, buf[PATH_MAX];
|
|
|
|
int i;
|
|
|
|
struct stat *f_stat;
|
|
|
|
|
|
|
|
|
|
|
|
if (cs == NULL)
|
|
|
|
{
|
|
|
|
DBG(0, "fix_weights_file: FATAL: NULL passed by my code, "
|
|
|
|
"please report this!\n");
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Assume this is false and then correct it */
|
|
|
|
cs->cal_readonly = SANE_FALSE;
|
|
|
|
|
|
|
|
if (cs->weights_file == NULL)
|
|
|
|
{
|
|
|
|
/* Will be of form canon_pp-calibration-parport0 or -0x378 */
|
|
|
|
sprintf(buf, "~/.sane/canon_pp-calibration-%s",
|
|
|
|
cs->params.port->name);
|
|
|
|
cs->weights_file = strdup(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the user's home dir if they used ~ */
|
|
|
|
if (cs->weights_file[0] == '~')
|
|
|
|
{
|
|
|
|
if ((tmp = malloc(PATH_MAX)) == NULL)
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
if ((myhome = getenv("HOME")) == NULL)
|
|
|
|
{
|
|
|
|
DBG(0,"fix_weights_file: FATAL: ~ used, but $HOME not"
|
|
|
|
" set!\n");
|
|
|
|
free(tmp);
|
|
|
|
tmp = NULL;
|
|
|
|
return SANE_STATUS_INVAL;
|
|
|
|
}
|
|
|
|
strncpy(tmp, myhome, PATH_MAX);
|
|
|
|
strncpy(tmp+strlen(tmp), (cs->weights_file)+1,
|
|
|
|
PATH_MAX-strlen(tmp));
|
|
|
|
|
|
|
|
free(cs->weights_file);
|
|
|
|
cs->weights_file = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((f_stat = malloc(sizeof(struct stat))) == NULL)
|
|
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
|
|
|
|
if(stat(cs->weights_file, f_stat))
|
|
|
|
{
|
|
|
|
/* this non-intuitive if basically is if we got some error that
|
|
|
|
* wasn't no-such-file, or we can't create the file.. */
|
|
|
|
if ((errno != ENOENT) || init_cal(cs->weights_file))
|
|
|
|
{
|
|
|
|
/* Some nasty error returned. Give up. */
|
|
|
|
DBG(2,"fix_weights_file: error stating cal file"
|
|
|
|
" (%s)\n", strerror(errno));
|
|
|
|
DBG(2,"fix_weights_file: Changes to cal data won't"
|
|
|
|
" be saved!\n");
|
|
|
|
free(cs->weights_file);
|
|
|
|
cs->weights_file = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
free(f_stat);
|
|
|
|
f_stat = NULL;
|
|
|
|
|
|
|
|
/* No error returned.. Check read/writability */
|
|
|
|
i = open(cs->weights_file, O_RDWR | O_APPEND);
|
|
|
|
if (i <= 0)
|
|
|
|
{
|
|
|
|
DBG(10,"fix_weighs_file: Note: Changes to cal data "
|
|
|
|
"won't be saved!\n");
|
|
|
|
i = open(cs->weights_file, O_RDONLY);
|
|
|
|
if (i <= 0)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Open failed (do i care why?)
|
|
|
|
*/
|
|
|
|
DBG(2,"fix_weights_file: error opening cal "
|
|
|
|
"(%s)\n", strerror(errno));
|
|
|
|
free(cs->weights_file);
|
|
|
|
cs->weights_file = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DBG(2,"fix_weights_file: file is read-only, "
|
|
|
|
"changes won't be saved\n");
|
|
|
|
cs->cal_readonly = SANE_TRUE;
|
|
|
|
close(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* good! */
|
|
|
|
DBG(10,"fix_weights_file: Calibration file is good "
|
|
|
|
"for opening!\n");
|
|
|
|
close(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* detect_mode
|
|
|
|
* PRE:
|
|
|
|
* cs->params.port is not open
|
|
|
|
* POST:
|
|
|
|
* cs->params.port is left opened iff SANE_STATUS_GOOD returned.
|
|
|
|
*/
|
|
|
|
|
|
|
|
SANE_Status detect_mode(CANONP_Scanner *cs)
|
|
|
|
{
|
|
|
|
|
|
|
|
int capabilities, tmp;
|
|
|
|
|
|
|
|
/* Open then claim parallel port using libieee1284 */
|
|
|
|
DBG(10,"detect_mode: Opening port %s\n", (cs->params.port->name));
|
|
|
|
|
|
|
|
tmp = ieee1284_open(cs->params.port, 0, &capabilities);
|
|
|
|
|
|
|
|
if (tmp != E1284_OK)
|
|
|
|
{
|
|
|
|
switch (tmp)
|
|
|
|
{
|
|
|
|
case E1284_INVALIDPORT:
|
|
|
|
DBG(1, "detect_mode: Invalid port.\n");
|
|
|
|
break;
|
|
|
|
case E1284_SYS:
|
|
|
|
DBG(1, "detect_mode: System error: %s\n",
|
|
|
|
strerror(errno));
|
|
|
|
break;
|
|
|
|
case E1284_INIT:
|
|
|
|
DBG(1, "detect_mode: Initialisation error.\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DBG(1, "detect_mode: Unknown error.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG(10,"detect_mode: Claiming port.\n");
|
|
|
|
|
|
|
|
if (ieee1284_claim(cs->params.port) != E1284_OK)
|
|
|
|
{
|
|
|
|
DBG(1,"detect_mode: Unable to claim port\n");
|
|
|
|
ieee1284_close(cs->params.port);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Check that compatibility-mode (required) is supported */
|
|
|
|
if (!(capabilities & CAP1284_COMPAT))
|
|
|
|
{
|
|
|
|
DBG(0,"detect_mode: Compatibility mode (required) not "
|
|
|
|
"supported.\n");
|
|
|
|
ieee1284_release(cs->params.port);
|
|
|
|
ieee1284_close(cs->params.port);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check capabilities which will enchance speed */
|
|
|
|
if (capabilities & CAP1284_ECP)
|
|
|
|
DBG(2, "detect_mode: Port supports ECP-H.\n");
|
|
|
|
else if (capabilities & CAP1284_ECPSWE)
|
|
|
|
DBG(2, "detect_mode: Port supports ECP-S.\n");
|
|
|
|
if (capabilities & CAP1284_IRQ)
|
|
|
|
DBG(2, "detect_mode: Port supports interrupts.\n");
|
|
|
|
if (capabilities & CAP1284_DMA)
|
|
|
|
DBG(2, "detect_mode: Port supports DMA.\n");
|
|
|
|
|
|
|
|
/* Check whether ECP mode is possible */
|
|
|
|
if (capabilities & CAP1284_ECP)
|
|
|
|
{
|
|
|
|
cs->ieee1284_mode = M1284_ECP;
|
|
|
|
DBG(10, "detect_mode: Using ECP-H Mode\n");
|
|
|
|
}
|
|
|
|
else if (capabilities & CAP1284_ECPSWE)
|
|
|
|
{
|
|
|
|
cs->ieee1284_mode = M1284_ECPSWE;
|
|
|
|
DBG(10, "detect_mode: Using ECP-S Mode\n");
|
|
|
|
}
|
|
|
|
else if (capabilities & CAP1284_NIBBLE)
|
|
|
|
{
|
|
|
|
cs->ieee1284_mode = M1284_NIBBLE;
|
|
|
|
DBG(10, "detect_mode: Using nibble mode\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DBG(0, "detect_mode: No supported parport modes available!\n");
|
|
|
|
ieee1284_release(cs->params.port);
|
|
|
|
ieee1284_close(cs->params.port);
|
|
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check to make sure ECP mode really is supported */
|
|
|
|
if ((cs->ieee1284_mode == M1284_ECP) ||
|
|
|
|
(cs->ieee1284_mode == M1284_ECPSWE))
|
|
|
|
{
|
|
|
|
if (ieee1284_ecp_read_data(cs->params.port, 0, NULL, 0) ==
|
|
|
|
E1284_NOTIMPL)
|
|
|
|
{
|
|
|
|
DBG(10, "detect_mode: Your version of libieee1284 "
|
|
|
|
"doesn't support ECP mode - defaulting"
|
|
|
|
" to nibble mode instead.\n");
|
|
|
|
cs->ieee1284_mode = M1284_NIBBLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (force_nibble == SANE_TRUE) {
|
|
|
|
DBG(10, "detect_mode: Nibble mode force in effect.\n");
|
|
|
|
cs->ieee1284_mode = M1284_NIBBLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
ieee1284_release(cs->params.port);
|
|
|
|
|
|
|
|
sanei_canon_pp_set_ieee1284_mode(cs->ieee1284_mode);
|
|
|
|
|
|
|
|
return SANE_STATUS_GOOD;
|
|
|
|
}
|