diff options
Diffstat (limited to 'backend/bh.c')
-rw-r--r-- | backend/bh.c | 3883 |
1 files changed, 3883 insertions, 0 deletions
diff --git a/backend/bh.c b/backend/bh.c new file mode 100644 index 0000000..58c8caa --- /dev/null +++ b/backend/bh.c @@ -0,0 +1,3883 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1999,2000 Tom Martone + This file is part of a SANE backend for Bell and Howell Copiscan II + Scanners using the Remote SCSI Controller(RSC). + + 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. + +*/ +#include "../include/sane/config.h" +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include "../include/sane/sane.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_scsi.h" +#include "../include/sane/sanei_config.h" + +#define BACKEND_NAME bh +#include "../include/sane/sanei_backend.h" +#define BUILD 4 + +#include "bh.h" + +#define MIN(x,y) ((x)<(y) ? (x) : (y)) +#define MAX(x,y) ((x)>(y) ? (x) : (y)) + +static int num_devices = 0; +static BH_Device *first_dev = NULL; +static BH_Scanner *first_handle = NULL; +static SANE_Char inquiry_data[255] = "Bell+Howell scanner"; +static SANE_Int disable_optional_frames = 0; +static SANE_Int fake_inquiry = 0; + +static int allblank(const char *s) +{ + while (s && *s) + if (!isspace(*s++)) + return 0; + + return 1; +} + +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 void +trim_spaces(char *s, size_t n) +{ + for (s += (n-1); n > 0; n--, s--) + { + if (*s && !isspace(*s)) + break; + *s = '\0'; + } +} + +static SANE_String_Const +print_devtype (SANE_Byte devtype) +{ + static SANE_String devtypes[] = + { + "disk", + "tape", + "printer", + "processor", + "CD-writer", + "CD-drive", + "scanner", + "optical-drive", + "jukebox", + "communicator" + }; + + return (devtype > 0 && devtype < NELEMS(devtypes)) ? + devtypes[devtype] : + "unknown-device"; +} + +static SANE_String_Const +print_barcodetype (SANE_Int i) +{ + return (i > 0 && i < NELEMS(barcode_search_bar_list)) ? + barcode_search_bar_list[i] : + (SANE_String_Const) "unknown"; +} + +static SANE_String_Const +print_orientation (SANE_Int i) +{ + switch(i) + { + case 0: + case 7: + return "vertical upwards"; + case 1: + case 2: + return "horizontal right"; + case 3: + case 4: + return "vertical downwards"; + case 5: + case 6: + return "horizontal left"; + default: + return "unknown"; + } +} + +static SANE_String_Const +print_read_type (SANE_Int i) +{ + static char buf[32]; + SANE_Int n; + + /* translate BH_SCSI_READ_TYPE_ codes to a human-readable string */ + if (i == BH_SCSI_READ_TYPE_FRONT) + { + strcpy(buf, "front page"); + } + else if (i == BH_SCSI_READ_TYPE_BACK) + { + strcpy(buf, "back page"); + } + else if (i > BH_SCSI_READ_TYPE_FRONT && + i <= BH_SCSI_READ_TYPE_FRONT + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_FRONT; + sprintf(buf, "front section %d", n); + } + else if (i > BH_SCSI_READ_TYPE_BACK && + i <= BH_SCSI_READ_TYPE_BACK + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_BACK; + sprintf(buf, "back section %d", n); + } + else if (i == BH_SCSI_READ_TYPE_FRONT_BARCODE) + { + strcpy(buf, "front page barcode"); + } + else if (i == BH_SCSI_READ_TYPE_BACK_BARCODE) + { + strcpy(buf, "back page barcode"); + } + else if (i > BH_SCSI_READ_TYPE_FRONT_BARCODE && + i <= BH_SCSI_READ_TYPE_FRONT_BARCODE + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_FRONT_BARCODE; + sprintf(buf, "front barcode section %d", n); + } + else if (i > BH_SCSI_READ_TYPE_BACK_BARCODE && + i <= BH_SCSI_READ_TYPE_BACK_BARCODE + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_BACK_BARCODE; + sprintf(buf, "back barcode section %d", n); + } + else if (i == BH_SCSI_READ_TYPE_FRONT_PATCHCODE) + { + strcpy(buf, "front page patchcode"); + } + else if (i == BH_SCSI_READ_TYPE_BACK_PATCHCODE) + { + strcpy(buf, "back page patchcode"); + } + else if (i > BH_SCSI_READ_TYPE_FRONT_PATCHCODE && + i <= BH_SCSI_READ_TYPE_FRONT_PATCHCODE + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_FRONT_PATCHCODE; + sprintf(buf, "front patchcode section %d", n); + } + else if (i > BH_SCSI_READ_TYPE_BACK_PATCHCODE && + i <= BH_SCSI_READ_TYPE_BACK_PATCHCODE + NUM_SECTIONS) + { + n = i - BH_SCSI_READ_TYPE_BACK_PATCHCODE; + sprintf(buf, "back patchcode section %d", n); + } + else if (i == BH_SCSI_READ_TYPE_FRONT_ICON) + { + strcpy(buf, "front page icon"); + } + else if (i == BH_SCSI_READ_TYPE_BACK_ICON) + { + strcpy(buf, "back page icon"); + } + else if (i == BH_SCSI_READ_TYPE_SENDBARFILE) + { + strcpy(buf, "transmit bar/patch codes"); + } + else + { + strcpy(buf, "unknown"); + } + + return buf; +} + +static SANE_Int +get_rotation_id(char *s) +{ + SANE_Int i; + + for (i = 0; rotation_list[i]; i++) + if (strcmp(s, rotation_list[i]) == 0) + break; + + /* unknown strings are treated as '0' */ + return rotation_list[i] ? i : 0; +} + +static SANE_Int +get_compression_id(char *s) +{ + SANE_Int i; + + for (i = 0; compression_list[i]; i++) + if (strcmp(s, compression_list[i]) == 0) + break; + + /* unknown strings are treated as 'none' */ + return compression_list[i] ? i : 0; +} + +static SANE_Int +get_barcode_id(char *s) +{ + SANE_Int i; + + for (i = 0; barcode_search_bar_list[i]; i++) + if (strcmp(s, barcode_search_bar_list[i]) == 0) + break; + + /* unknown strings are treated as 'none' */ + return barcode_search_bar_list[i] ? i : 0; +} + +static SANE_Int +get_scan_mode_id(char *s) +{ + SANE_Int i; + + for (i = 0; scan_mode_list[i]; i++) + if (strcmp(s, scan_mode_list[i]) == 0) + break; + + /* unknown strings are treated as 'lineart' */ + return scan_mode_list[i] ? i : 0; +} + +static SANE_Int +get_paper_id(char *s) +{ + SANE_Int i; + + for (i = 0; paper_list[i]; i++) + if (strcmp(s, paper_list[i]) == 0) + break; + + /* unknown strings are treated as 'custom' */ + return paper_list[i] ? i : 0; +} + +static SANE_Int +get_barcode_search_mode(char *s) +{ + SANE_Int i; + + if (strcmp(s, "horizontal") == 0) + { + i = 1; + } + else if (strcmp(s, "vertical") == 0) + { + i = 2; + } + else if (strcmp(s, "vert-horiz") == 0) + { + i = 6; + } + else if (strcmp(s, "horiz-vert") == 0) + { + i = 9; + } + else + { + /* unknown strings are treated as 'horiz-vert' */ + DBG(1, "get_barcode_search_mode: unrecognized string `%s'\n", s); + i = 9; + } + + return i; +} + +static void +appendStdList(BH_Info *sc, SANE_Int res) +{ + /* append entry to resolution list - a SANE_WORD_LIST */ + sc->resStdList[sc->resStdList[0]+1] = res; + sc->resStdList[0]++; +} + +static void +ScannerDump(BH_Scanner *s) +{ + int i; + BH_Info *info; + SANE_Device *sdev; + + info = &s->hw->info; + sdev = &s->hw->sane; + + DBG (1, "SANE Device: '%s' Vendor: '%s' Model: '%s' Type: '%s'\n", + sdev->name, + sdev->vendor, + sdev->model, + sdev->type); + + DBG (1, "Type: '%s' Vendor: '%s' Product: '%s' Revision: '%s'\n", + print_devtype(info->devtype), + info->vendor, + info->product, + info->revision); + + DBG (1, "Automatic Document Feeder:%s\n", + info->canADF ? " <Installed>" : " <Not Installed>"); + + DBG (1, "Colors:%s%s\n", info->colorBandW ? " <Black and White>" : "", + info->colorHalftone ? " <Halftone>" : ""); + + DBG (1, "Data processing:%s%s%s%s%s%s\n", + info->canWhiteFrame ? " <White Frame>" : "", + info->canBlackFrame ? " <Black Frame>" : "", + info->canEdgeExtract ? " <Edge Extraction>" : "", + info->canNoiseFilter ? " <Noise Filter>" : "", + info->canSmooth ? " <Smooth>" : "", + info->canLineBold ? " <Line Bolding>" : ""); + + DBG (1, "Compression:%s%s%s\n", + info->comprG3_1D ? " <Group 3, 1D>" : "", + info->comprG3_2D ? " <Group 3, 2D>" : "", + info->comprG4 ? " <Group 4>" : ""); + + DBG (1, "Optional Features:%s%s%s%s\n", + info->canBorderRecog ? " <Border Recognition>" : "", + info->canBarCode ? " <BarCode Decoding>" : "", + info->canIcon ? " <Icon Generation>" : "", + info->canSection ? " <Section Support>" : ""); + + DBG (1, "Max bytes per scan-line: %d (%d pixels)\n", + info->lineMaxBytes, + info->lineMaxBytes * 8); + + DBG (1, "Basic resolution (X/Y): %d/%d\n", + info->resBasicX, + info->resBasicY); + + DBG (1, "Maximum resolution (X/Y): %d/%d\n", + info->resMaxX, + info->resMaxY); + + DBG (1, "Minimum resolution (X/Y): %d/%d\n", + info->resMinX, + info->resMinY); + + DBG (1, "Standard Resolutions:\n"); + for (i = 0; i < info->resStdList[0]; i++) + DBG (1, " %d\n", info->resStdList[i+1]); + + DBG (1, "Window Width/Height (in basic res) %d/%d (%.2f/%.2f inches)\n", + info->winWidth, + info->winHeight, + (info->resBasicX != 0) ? ((float) info->winWidth) / info->resBasicX : 0.0, + (info->resBasicY) ? ((float) info->winHeight) / info->resBasicY : 0.0); + + DBG (1, "Summary:%s%s%s\n", + info->canDuplex ? "Duplex Scanner" : "Simplex Scanner", + info->canACE ? " (ACE capable)" : "", + info->canCheckADF ? " (ADF Paper Sensor capable)" : ""); + + sprintf(inquiry_data, "Vendor: %s Product: %s Rev: %s %s%s%s\n", + info->vendor, + info->product, + info->revision, + info->canDuplex ? "Duplex Scanner" : "Simplex Scanner", + info->canACE ? " (ACE capable)" : "", + info->canCheckADF ? " (ADF Paper Sensor capable)" : ""); + + DBG (5, "autoborder_default=%d\n", info->autoborder_default); + DBG (5, "batch_default=%d\n", info->batch_default); + DBG (5, "deskew_default=%d\n", info->deskew_default); + DBG (5, "check_adf_default=%d\n", info->check_adf_default); + DBG (5, "duplex_default=%d\n", info->duplex_default); + DBG (5, "timeout_adf_default=%d\n", info->timeout_adf_default); + DBG (5, "timeout_manual_default=%d\n", info->timeout_manual_default); + DBG (5, "control_panel_default=%d\n", info->control_panel_default); + +} + +static SANE_Status +test_unit_ready (int fd) +{ + static SANE_Byte cmd[6]; + SANE_Status status; + DBG (3, "test_unit_ready called\n"); + + cmd[0] = BH_SCSI_TEST_UNIT_READY; + memset (cmd, 0, sizeof (cmd)); + status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0); + + return status; +} + +static SANE_Status +object_position (BH_Scanner *s) +{ + static SANE_Byte cmd[10]; + SANE_Status status; + DBG (3, "object_position called\n"); + + memset (cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_OBJECT_POSITION; + cmd[1] = 0x01; + status = sanei_scsi_cmd (s->fd, cmd, sizeof (cmd), 0, 0); + + return status; +} + +static SANE_Status +read_barcode_data (BH_Scanner *s, FILE *fp) +{ + static SANE_Byte cmd[10]; + SANE_Status status; + SANE_Int num_found = 0; + double w, l, x, y, res; + struct barcode_data buf; + size_t buf_size = sizeof(buf); + DBG (3, "read_barcode_data called\n"); + + memset (&cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_READ_SCANNED_DATA; + cmd[2] = s->readlist[s->readptr]; + _lto3b(buf_size, &cmd[6]); /* transfer length */ + + s->barcode_not_found = SANE_FALSE; + do { + memset (&buf, 0, sizeof(buf)); + status = sanei_scsi_cmd (s->fd, &cmd, sizeof (cmd), &buf, &buf_size); + if (status != SANE_STATUS_GOOD) + break; + if (s->barcode_not_found == SANE_TRUE) + break; + + num_found++; + + buf.barcodedata[sizeof(buf.barcodedata)-1] = '\0'; + + /* calculate the bounding rectangle */ + x = MIN((int) _2btol(buf.posxb), (int) _2btol(buf.posxa)); + y = MIN((int) _2btol(buf.posyb), (int) _2btol(buf.posyd)); + w = MAX((int) _2btol(buf.posxd), (int) _2btol(buf.posxd)) - x; + l = MAX((int) _2btol(buf.posya), (int) _2btol(buf.posyc)) - y; + /* convert from pixels to mm */ + res = _OPT_VAL_WORD(s, OPT_RESOLUTION); + if (res <= 0.0) + { + /* avoid divide by zero */ + DBG(1, "read_barcode_data: warning: " + "encountered bad resolution value '%f', replacing with '%f'\n", + res, 200.0); + res = 200.0; + } + x = x * MM_PER_INCH / res; + y = y * MM_PER_INCH / res; + w = w * MM_PER_INCH / res; + l = l * MM_PER_INCH / res; + /* add a bit of a border around the edges */ + x = MAX(0.0, x - BH_DECODE_FUDGE); + y = MAX(0.0, y - BH_DECODE_FUDGE); + w += (BH_DECODE_FUDGE * 4); + l += (BH_DECODE_FUDGE * 4); + + /* write the decoded barcode data into the file */ + fprintf(fp, "<barcode>\n <section>%s</section>\n", + print_read_type((int) s->readlist[s->readptr])); + fprintf(fp, " <type>%s</type>\n <status-flag>%d</status-flag>\n", + print_barcodetype((int) _2btol(buf.barcodetype)), + (int) _2btol(buf.statusflag)); + fprintf(fp, " <orientation>%s</orientation>\n", + print_orientation((int) _2btol(buf.barcodeorientation))); + fprintf(fp, " <location>\n <tl><x>%d</x><y>%d</y></tl>\n", + (int) _2btol(buf.posxb), (int) _2btol(buf.posyb)); + fprintf(fp, " <tr><x>%d</x><y>%d</y></tr>\n", + (int) _2btol(buf.posxd), (int) _2btol(buf.posyd)); + fprintf(fp, " <bl><x>%d</x><y>%d</y></bl>\n", + (int) _2btol(buf.posxa), (int) _2btol(buf.posya)); + fprintf(fp, " <br><x>%d</x><y>%d</y></br>\n </location>\n", + (int) _2btol(buf.posxc), (int) _2btol(buf.posyc)); + fprintf(fp, " <rectangle>%.2fx%.2f+%.2f+%.2f</rectangle>\n", + w, l, x, y); + fprintf(fp, " <search-time>%d</search-time>\n <length>%d</length>\n", + (int) _2btol(buf.barcodesearchtime), + (int) buf.barcodelen); + fprintf(fp, " <data>%s</data>\n</barcode>\n", + buf.barcodedata); + } while (num_found <= BH_DECODE_TRIES); + + DBG (3, "read_barcode_data: found %d barcodes, returning %s\n", + num_found, sane_strstatus(status)); + + return status; +} + +static SANE_Status +read_icon_data (BH_Scanner *s) +{ + static SANE_Byte cmd[10]; + SANE_Status status; + struct icon_data buf; + size_t buf_size = sizeof(buf); + DBG (3, "read_icon_data called\n"); + + memset (&cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_READ_SCANNED_DATA; + cmd[2] = s->readlist[s->readptr]; + _lto3b(buf_size, &cmd[6]); /* transfer length */ + + memset (&buf, 0, sizeof(buf)); + + status = sanei_scsi_cmd (s->fd, &cmd, sizeof (cmd), &buf, &buf_size); + + /* set the fields in the scanner handle for later reference */ + s->iconwidth = _4btol(buf.iconwidth); + s->iconlength = _4btol(buf.iconlength); + + DBG(3, "read_icon_data: windowwidth:%lu, windowlength:%lu\n", + _4btol(buf.windowwidth), + _4btol(buf.windowlength)); + DBG(3, "read_icon_data: iconwidth:%lu, iconlength:%lu, iconwidth(bytes):%lu\n", + _4btol(buf.iconwidth), + _4btol(buf.iconlength), + _4btol(buf.iconwidthbytes)); + DBG(3, "read_icon_data: bitordering:%02x, icondatalen:%lu\n", + buf.bitordering, + _4btol(buf.icondatalen)); + + DBG (3, "read_icon_data returning %d\n", status); + + return status; +} + +static SANE_Status +read_barfile (BH_Scanner *s, void *buf, size_t *buf_size) +{ + SANE_Status status = SANE_STATUS_GOOD; + size_t nread; + DBG (3, "read_barfile called (%lu bytes)\n", (u_long) *buf_size); + + if (s->barf != NULL) + { + /* this function needs to set InvalidBytes so it looks + * like a B&H scsi EOF + */ + if ((nread = fread(buf, 1, *buf_size, s->barf)) < *buf_size) + { + /* set InvalidBytes */ + s->InvalidBytes = *buf_size - nread; + + if (ferror(s->barf)) + { + status = SANE_STATUS_IO_ERROR; + fclose(s->barf); + s->barf = NULL; + unlink(s->barfname); + } + else if (feof(s->barf)) + { + /* it also needs to close the file and delete it when EOF is + * reached. + */ + fclose(s->barf); + s->barf = NULL; + unlink(s->barfname); + } + } + } + else + { + /* set InvalidBytes */ + s->InvalidBytes = *buf_size; + } + + return status; +} + +static SANE_Status +read_data (BH_Scanner *s, void *buf, size_t *buf_size) +{ + static SANE_Byte cmd[10]; + SANE_Status status; + DBG (3, "read_data called (%lu bytes)\n", (u_long) *buf_size); + + if (s->readlist[s->readptr] == BH_SCSI_READ_TYPE_SENDBARFILE) + { + /* call special barcode data read function. */ + status = read_barfile(s, buf, buf_size); + } + else + { + memset (&cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_READ_SCANNED_DATA; + cmd[2] = s->readlist[s->readptr]; + _lto3b(*buf_size, &cmd[6]); /* transfer length */ + + status = sanei_scsi_cmd (s->fd, &cmd, sizeof (cmd), buf, buf_size); + } + + return status; +} + +static SANE_Status +mode_select_measurement (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_03 mp; + } select_cmd; + SANE_Status status; + + DBG (3, "mode_select_measurement called (bmu:%d mud:%d)\n", + s->bmu, s->mud); + + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.pagecode = BH_MODE_MEASUREMENT_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + select_cmd.mp.bmu = s->bmu; + _lto2b(s->mud, select_cmd.mp.mud); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + + return status; +} + +static SANE_Status +mode_select_timeout (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_20 mp; + } select_cmd; + SANE_Status status; + + DBG (3, "mode_select_timeout called\n"); + + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.pagecode = BH_MODE_TIMEOUT_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + select_cmd.mp.timeoutmanual = _OPT_VAL_WORD(s, OPT_TIMEOUT_MANUAL); + select_cmd.mp.timeoutadf = _OPT_VAL_WORD(s, OPT_TIMEOUT_ADF); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + + return status; +} + +static SANE_Status +mode_select_icon (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_21 mp; + } select_cmd; + SANE_Status status; + + DBG (3, "mode_select_icon called\n"); + + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.pagecode = BH_MODE_ICON_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + _lto2b(_OPT_VAL_WORD(s, OPT_ICON_WIDTH), select_cmd.mp.iconwidth); + _lto2b(_OPT_VAL_WORD(s, OPT_ICON_LENGTH), select_cmd.mp.iconlength); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + + return status; +} + +static SANE_Status +mode_select_barcode_priority (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_30 mp; + } select_cmd; + SANE_Status status; + int i; + + DBG (3, "mode_select_barcode_priority called\n"); + + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.pagecode = BH_MODE_BARCODE_PRIORITY_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + + for (i = 0; i < NUM_SEARCH_BARS; i++) + { + /* anything after a 'none' is ignored */ + if ((select_cmd.mp.priority[i] = s->search_bars[i]) == 0) break; + } + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + + return status; +} + +static SANE_Status +mode_select_barcode_param1 (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_31 mp; + } select_cmd; + SANE_Status status; + + DBG (3, "mode_select_barcode_param1 called\n"); + + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.pagecode = BH_MODE_BARCODE_PARAM1_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + + _lto2b((SANE_Int)_OPT_VAL_WORD_THOUSANDTHS(s, OPT_BARCODE_HMIN), select_cmd.mp.minbarheight); + select_cmd.mp.searchcount = _OPT_VAL_WORD(s, OPT_BARCODE_SEARCH_COUNT); + select_cmd.mp.searchmode = + get_barcode_search_mode(_OPT_VAL_STRING(s, OPT_BARCODE_SEARCH_MODE)); + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_SEARCH_TIMEOUT), select_cmd.mp.searchtimeout); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + + return status; +} + +static SANE_Status +mode_select_barcode_param2 (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_32 mp; + } select_cmd; + SANE_Status status; + size_t len; + + DBG (3, "mode_select_barcode_param2 called\n"); + + /* first we'll do a mode sense, then we'll overwrite with + * our new values, and then do a mode select + */ + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SENSE; + select_cmd.cmd[2] = BH_MODE_BARCODE_PARAM2_PAGE_CODE; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + len = sizeof(select_cmd.mp); + status = sanei_scsi_cmd (s->fd, &select_cmd.cmd, sizeof (select_cmd.cmd), + &select_cmd.mp, &len); + + if (status == SANE_STATUS_GOOD) + { + DBG(8, "mode_select_barcode_param2: sensed values: relmax:%d barmin:%d barmax:%d\n", + (int) _2btol(select_cmd.mp.relmax), + (int) _2btol(select_cmd.mp.barmin), + (int) _2btol(select_cmd.mp.barmax)); + + memset (&select_cmd.cmd, 0, sizeof (select_cmd.cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.modedatalen = 0x00; + select_cmd.mp.mediumtype = 0x00; + select_cmd.mp.devicespecificparam = 0x00; + select_cmd.mp.blockdescriptorlen = 0x00; + + select_cmd.mp.pagecode = BH_MODE_BARCODE_PARAM2_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + + /* only overwrite the default values if the option is non-zero */ + if (_OPT_VAL_WORD(s, OPT_BARCODE_RELMAX) != 0) + { + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_RELMAX), select_cmd.mp.relmax); + } + if (_OPT_VAL_WORD(s, OPT_BARCODE_BARMIN) != 0) + { + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_BARMIN), select_cmd.mp.barmin); + } + if (_OPT_VAL_WORD(s, OPT_BARCODE_BARMAX) != 0) + { + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_BARMAX), select_cmd.mp.barmax); + } + + DBG(8, "mode_select_barcode_param2: param values: relmax:%d barmin:%d barmax:%d\n", + (int) _OPT_VAL_WORD(s, OPT_BARCODE_RELMAX), + (int) _OPT_VAL_WORD(s, OPT_BARCODE_BARMIN), + (int) _OPT_VAL_WORD(s, OPT_BARCODE_BARMAX)); + + DBG(8, "mode_select_barcode_param2: select values: relmax:%d barmin:%d barmax:%d\n", + (int) _2btol(select_cmd.mp.relmax), + (int) _2btol(select_cmd.mp.barmin), + (int) _2btol(select_cmd.mp.barmax)); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + } + + return status; +} + +static SANE_Status +mode_select_barcode_param3 (BH_Scanner *s) +{ + static struct { + SANE_Byte cmd[6]; + struct mode_page_33 mp; + } select_cmd; + SANE_Status status; + size_t len; + + DBG (3, "mode_select_barcode_param3 called\n"); + + /* first we'll do a mode sense, then we'll overwrite with + * our new values, and then do a mode select + */ + memset (&select_cmd, 0, sizeof (select_cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SENSE; + select_cmd.cmd[2] = BH_MODE_BARCODE_PARAM3_PAGE_CODE; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + len = sizeof(select_cmd.mp); + status = sanei_scsi_cmd (s->fd, &select_cmd.cmd, sizeof (select_cmd.cmd), + &select_cmd.mp, &len); + + if (status == SANE_STATUS_GOOD) + { + DBG(8, "mode_select_barcode_param3: sensed values: contrast:%d patchmode:%d\n", + (int) _2btol(select_cmd.mp.barcodecontrast), + (int) _2btol(select_cmd.mp.patchmode)); + + memset (&select_cmd.cmd, 0, sizeof (select_cmd.cmd)); + select_cmd.cmd[0] = BH_SCSI_MODE_SELECT; + select_cmd.cmd[1] = 0x10; + select_cmd.cmd[4] = sizeof(select_cmd.mp); + + select_cmd.mp.modedatalen = 0x00; + select_cmd.mp.mediumtype = 0x00; + select_cmd.mp.devicespecificparam = 0x00; + select_cmd.mp.blockdescriptorlen = 0x00; + + select_cmd.mp.pagecode = BH_MODE_BARCODE_PARAM3_PAGE_CODE; + select_cmd.mp.paramlen = 0x06; + + /* only overwrite the default values if the option is non-zero */ + if (_OPT_VAL_WORD(s, OPT_BARCODE_CONTRAST) != 0) + { + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_CONTRAST), select_cmd.mp.barcodecontrast); + } + if (_OPT_VAL_WORD(s, OPT_BARCODE_PATCHMODE) != 0) + { + _lto2b(_OPT_VAL_WORD(s, OPT_BARCODE_PATCHMODE), select_cmd.mp.patchmode); + } + + DBG(8, "mode_select_barcode_param3: param values: contrast:%d patchmode:%d\n", + (int) _OPT_VAL_WORD(s, OPT_BARCODE_CONTRAST), + (int) _OPT_VAL_WORD(s, OPT_BARCODE_PATCHMODE)); + + DBG(8, "mode_select_barcode_param3: select values: contrast:%d patchmode:%d\n", + (int) _2btol(select_cmd.mp.barcodecontrast), + (int) _2btol(select_cmd.mp.patchmode)); + + status = sanei_scsi_cmd (s->fd, &select_cmd, sizeof (select_cmd), 0, 0); + } + + return status; +} + +static SANE_Status +inquiry (int fd, void *buf, size_t *buf_size, SANE_Byte evpd, SANE_Byte page_code) +{ + static SANE_Byte cmd[6]; + SANE_Status status; + DBG (3, "inquiry called\n"); + + memset (cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_INQUIRY; + cmd[1] = evpd; + cmd[2] = page_code; + cmd[4] = *buf_size; + + status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, buf_size); + + return status; +} + +static SANE_Status +set_window (BH_Scanner *s, SANE_Byte batchmode) +{ + static struct { + SANE_Byte cmd[10]; + SANE_Byte hdr[8]; + struct window_data window; + } set_window_cmd; + SANE_Status status; + SANE_Int width, length, i, format, rotation, deskew ; + + DBG (3, "set_window called\n"); + + /* set to thousandths for set_window */ + s->bmu = BH_UNIT_INCH; + s->mud = 1000; + status = mode_select_measurement(s); + if (status != SANE_STATUS_GOOD) + return status; + + memset (&set_window_cmd, 0, sizeof (set_window_cmd)); + set_window_cmd.cmd[0] = BH_SCSI_SET_WINDOW; + DBG(3, "set_window: sizeof(hdr) %d, sizeof(window): %d\n", + (int)sizeof(set_window_cmd.hdr), (int)sizeof(set_window_cmd.window)); + + _lto3b(sizeof(set_window_cmd.hdr) + sizeof(set_window_cmd.window), + &set_window_cmd.cmd[6]); + + _lto2b(256, &set_window_cmd.hdr[6]); + + set_window_cmd.window.windowid = 0; + set_window_cmd.window.autoborder = _OPT_VAL_WORD(s, OPT_AUTOBORDER); + DBG (5, "autoborder set to=%d\n", set_window_cmd.window.autoborder); + _lto2b(_OPT_VAL_WORD(s, OPT_RESOLUTION), set_window_cmd.window.xres); + _lto2b(_OPT_VAL_WORD(s, OPT_RESOLUTION), set_window_cmd.window.yres); + _lto4b((int) _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_X), set_window_cmd.window.ulx); + _lto4b((int) _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_Y), set_window_cmd.window.uly); + + width = (SANE_Int) (_OPT_VAL_WORD_THOUSANDTHS(s, OPT_BR_X) - + _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_X)); + length = (SANE_Int) (_OPT_VAL_WORD_THOUSANDTHS(s, OPT_BR_Y) - + _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_Y)); + + _lto4b(width, set_window_cmd.window.windowwidth); + _lto4b(length, set_window_cmd.window.windowlength); + + /* brightness (1-255) 0 is default, aka 128. Ignored with ACE scanners */ + set_window_cmd.window.brightness = _OPT_VAL_WORD(s, OPT_BRIGHTNESS); + /* threshold (1-255) 0 is default, aka 128. Ignored with ACE scanners */ + set_window_cmd.window.threshold = _OPT_VAL_WORD(s, OPT_THRESHOLD); + /*!!! contrast (not used) */ + /*!!! set_window_cmd.window.contrast = _OPT_VAL_WORD(s, OPT_CONTRAST); */ + /* imagecomposition 0x00 lineart, 0x01 dithered/halftone, 0x02 grayscale*/ + set_window_cmd.window.imagecomposition = + get_scan_mode_id(_OPT_VAL_STRING(s, OPT_SCAN_MODE)); + + set_window_cmd.window.bitsperpixel = 0x01; + /*!!! halftone code (not used) */ + /*!!! halftone id (not used) */ + + set_window_cmd.window.paddingtype = 0x03; /* truncate byte */ + if (_OPT_VAL_WORD(s, OPT_NEGATIVE) == SANE_TRUE) { + /* reverse image format (valid when bitsperpixel=1) + * 0x00 normal, 0x01 reversed. This is bit 7 of paddingtype. + */ + set_window_cmd.window.paddingtype |= 0x80; + } + + set_window_cmd.window.bitordering[0] = 0x00; + + /* we must always sent plain gray data in preview mode */ + format = (_OPT_VAL_WORD(s, OPT_PREVIEW)) ? + BH_COMP_NONE : + get_compression_id(_OPT_VAL_STRING(s, OPT_COMPRESSION)); + + switch (format) + { + case BH_COMP_G31D: + set_window_cmd.window.compressiontype = 0x01; + set_window_cmd.window.compressionarg = 0x00; + set_window_cmd.window.bitordering[1] = 0x01; /* Bit ordering LSB */ + break; + case BH_COMP_G32D: + set_window_cmd.window.compressiontype = 0x02; + set_window_cmd.window.compressionarg = 0x04; + set_window_cmd.window.bitordering[1] = 0x01; /* Bit ordering LSB */ + break; + case BH_COMP_G42D: + set_window_cmd.window.compressiontype = 0x03; + set_window_cmd.window.compressionarg = 0x00; + set_window_cmd.window.bitordering[1] = 0x01; /* Bit ordering LSB */ + break; + case BH_COMP_NONE: + default: + set_window_cmd.window.compressiontype = 0x00; + set_window_cmd.window.compressionarg = 0x00; + set_window_cmd.window.bitordering[1] = 0x00; /* n/a */ + break; + } + + /* rotation and deskew settings, if autoborder is turned on */ + if(set_window_cmd.window.autoborder){ /*--- setting byte 46 of the window descriptor block only works with autoborder */ + rotation = get_rotation_id(_OPT_VAL_STRING(s, OPT_ROTATION)); + if (_OPT_VAL_WORD(s, OPT_DESKEW) == SANE_TRUE) deskew = BH_DESKEW_ENABLE; + else deskew = BH_DESKEW_DISABLE; + set_window_cmd.window.border_rotation = ( rotation | deskew ); /*--- deskew assumes autoborder */ + } + + /* remote - 0x00 ACE set in window; 0x01 ACE set by control panel */ + set_window_cmd.window.remote = _OPT_VAL_WORD(s, OPT_CONTROL_PANEL); + if (set_window_cmd.window.remote == 0x00) { + /* acefunction (ignored on non-ACE scanners) */ + set_window_cmd.window.acefunction = _OPT_VAL_WORD(s, OPT_ACE_FUNCTION); + /* acesensitivity (ignored on non-ACE scanners) */ + set_window_cmd.window.acesensitivity = _OPT_VAL_WORD(s, OPT_ACE_SENSITIVITY); + } + + set_window_cmd.window.batchmode = batchmode; + + /* fill in the section descriptor blocks */ + for (i = 0; i < s->num_sections; i++) + { + BH_SectionBlock *b; + + b = &set_window_cmd.window.sectionblock[i]; + + _lto4b(s->sections[i].left, b->ul_x); + _lto4b(s->sections[i].top, b->ul_y); + _lto4b(s->sections[i].width, b->width); + _lto4b(s->sections[i].length, b->length); + b->compressiontype = s->sections[i].compressiontype; + b->compressionarg = s->sections[i].compressionarg; + } + + status = sanei_scsi_cmd (s->fd, &set_window_cmd, sizeof (set_window_cmd), 0, 0); + DBG (5, "sanei_scsi_cmd executed, status=%d\n", status ); + if (status != SANE_STATUS_GOOD) + return status; + + /* set to points for reading */ + s->bmu = BH_UNIT_POINT; + s->mud = 1; + status = mode_select_measurement(s); + + return status; +} + +static SANE_Status +get_window (BH_Scanner *s, SANE_Int *w, SANE_Int *h, SANE_Bool backpage) +{ + SANE_Byte cmd[10]; + static struct { + SANE_Byte hdr[8]; + struct window_data window; + } get_window_data; + SANE_Status status; + SANE_Int x, y, i = 0, get_window_delay = 1; + SANE_Bool autoborder; + size_t len; + + DBG (3, "get_window called\n"); + + autoborder = _OPT_VAL_WORD(s, OPT_AUTOBORDER) == 1; + + while (1) + { + i++; + memset (&cmd, 0, sizeof (cmd)); + memset (&get_window_data, 0, sizeof (get_window_data)); + + cmd[0] = BH_SCSI_GET_WINDOW; + _lto3b(sizeof(get_window_data), &cmd[6]); + + _lto2b(256, &get_window_data.hdr[6]); + + get_window_data.window.windowid = (backpage == SANE_TRUE) ? 1 : 0; + + len = sizeof(get_window_data); + status = sanei_scsi_cmd (s->fd, &cmd, sizeof (cmd), + &get_window_data, &len); + if (status == SANE_STATUS_GOOD) + { + x =_4btol(get_window_data.window.ulx); + y =_4btol(get_window_data.window.uly); + *w =_4btol(get_window_data.window.windowwidth); + *h =_4btol(get_window_data.window.windowlength); + + if (autoborder) + { + /* we try repeatedly until we get the autoborder bit set */ + if (get_window_data.window.autoborder != 1 && + i < BH_AUTOBORDER_TRIES) + { + DBG (5, "waiting %d second[s], try: %d\n",get_window_delay,i); + sleep(get_window_delay); /*--- page 4-5 of B&H Copiscan 8000 ESC OEM Tech Manual */ + /*--- requires at least 50ms wait between each GET WINDOW command */ + /*--- experience shows that this can take 3 to 4 seconds */ + continue; + } + if (get_window_data.window.autoborder != 1) + { + DBG(1, "Automatic Border Detection not done within %d tries\n", + BH_AUTOBORDER_TRIES); + status = SANE_STATUS_IO_ERROR; + } + DBG (0, "page dimension: wide:%d high:%d \n",*w,*h); + } + DBG (3, "*** Window size: %dx%d+%d+%d\n", *w, *h, x, y); + DBG (5, "*** get_window found autoborder=%02xh\n", get_window_data.window.autoborder); + DBG (5, "*** get_window found border_rotation=%02xh\n", get_window_data.window.border_rotation); + } + + /* we are 'outta here' */ + break; + } + + return status; +} + +static SANE_Status +get_parameters (SANE_Handle handle, SANE_Parameters *params) +{ + BH_Scanner *s = handle; + SANE_Int width, length, res, comp; + double br_x, tl_x, br_y, tl_y; + SANE_Frame format; + + DBG(3, "get_parameters called\n"); + + memset (&s->params, 0, sizeof (s->params)); + + res = _OPT_VAL_WORD(s, OPT_RESOLUTION); + + /* make best-effort guess at what parameters will look like once + the scan starts. */ + + br_x = _OPT_VAL_WORD_THOUSANDTHS(s, OPT_BR_X); + br_y = _OPT_VAL_WORD_THOUSANDTHS(s, OPT_BR_Y); + tl_x = _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_X); + tl_y = _OPT_VAL_WORD_THOUSANDTHS(s, OPT_TL_Y); + + width = (br_x - tl_x + 1) * res / 1000.0; + length = (br_y - tl_y + 1) * res / 1000.0; + + /* figure out the default image format for front/back pages */ + comp = get_compression_id(_OPT_VAL_STRING(s, OPT_COMPRESSION)); + switch (comp) + { + case BH_COMP_G31D: + format = SANE_FRAME_G31D; + break; + case BH_COMP_G32D: + format = SANE_FRAME_G32D; + break; + case BH_COMP_G42D: + format = SANE_FRAME_G42D; + break; + case BH_COMP_NONE: + default: + format = SANE_FRAME_GRAY; + break; + } + + if (s->scanning) + { + SANE_Int w, l, status; + SANE_Byte itemtype; + + itemtype = s->readlist[s->readptr]; + /* update parameters based on the current item */ + + status = SANE_STATUS_GOOD; + if (itemtype == BH_SCSI_READ_TYPE_FRONT) + { + DBG (3, "get_parameters: sending GET WINDOW (front)\n"); + status = get_window (s, &w, &l, SANE_FALSE); + if (status == SANE_STATUS_GOOD) + { + width = w; + length = l; + } + } + else if (itemtype == BH_SCSI_READ_TYPE_BACK) + { + DBG (3, "get_parameters: sending GET WINDOW (back)\n"); + status = get_window (s, &w, &l, SANE_TRUE); + if (status == SANE_STATUS_GOOD) + { + width = w; + length = l; + } + } + else if (itemtype == BH_SCSI_READ_TYPE_FRONT_ICON || + itemtype == BH_SCSI_READ_TYPE_BACK_ICON) + { + /* the icon is never compressed */ + format = SANE_FRAME_GRAY; + width = s->iconwidth; + length = s->iconlength; + } + else if (itemtype > BH_SCSI_READ_TYPE_FRONT && + itemtype <= (BH_SCSI_READ_TYPE_FRONT + NUM_SECTIONS)) + { + /* a front section */ + SANE_Int sectnum = itemtype - BH_SCSI_READ_TYPE_FRONT; + + format = s->sections[sectnum - 1].format; + /* convert from thousandths to pixels */ + width = s->sections[sectnum - 1].width * res / 1000.0; + length = s->sections[sectnum - 1].length * res / 1000.0; + } + else if (itemtype > BH_SCSI_READ_TYPE_BACK && + itemtype <= (BH_SCSI_READ_TYPE_BACK + NUM_SECTIONS)) + { + /* a back section */ + SANE_Int sectnum = itemtype - BH_SCSI_READ_TYPE_BACK; + + format = s->sections[sectnum - 1].format; + /* convert from thousandths to pixels */ + width = s->sections[sectnum - 1].width * res / 1000.0; + length = s->sections[sectnum - 1].length * res / 1000.0; + } + else if ( (itemtype >= BH_SCSI_READ_TYPE_BACK_BARCODE && + itemtype <= (BH_SCSI_READ_TYPE_BACK_BARCODE + NUM_SECTIONS)) || + (itemtype >= BH_SCSI_READ_TYPE_FRONT_BARCODE && + itemtype <= (BH_SCSI_READ_TYPE_FRONT_BARCODE + NUM_SECTIONS)) ) + { + /* decoded barcode data */ + format = SANE_FRAME_TEXT; + width = 8; + length = -1; + } + else if (itemtype == BH_SCSI_READ_TYPE_SENDBARFILE) + { + /* decoded barcode data file */ + format = SANE_FRAME_TEXT; + width = 8; + length = -1; + } + else + { + format = SANE_FRAME_GRAY; + width = 8; + length = -1; + DBG(1, "get_parameters: unrecognized read itemtype: %d\n", + itemtype); + } + + if (status != SANE_STATUS_GOOD) + { + DBG(1, "get_parameters: failed\n"); + return status; + } + } + + if (res <= 0 || width <= 0) + { + DBG(1, "get_parameters:illegal parameters res=%d, width=%d, length=%d\n", + res, width, length); + return SANE_STATUS_INVAL; + } + + /* we disable our compression/barcode formats in preview as well + * as with the disable_optional_frames configuration option. NOTE: + * we may still be delivering 'wierd' data and lying about it being _GRAY! + */ + if (format != SANE_FRAME_GRAY && + (_OPT_VAL_WORD(s, OPT_PREVIEW) || disable_optional_frames)) + { + DBG(1, "get_parameters: warning: delivering %s data as gray", + sane_strframe(format)); + format = SANE_FRAME_GRAY; + } + + s->params.format = format; + s->params.depth = 1; + s->params.last_frame = SANE_TRUE; + s->params.pixels_per_line = width; + s->params.lines = length; + s->params.bytes_per_line = (s->params.pixels_per_line + 7) / 8; + /* The Bell and Howell truncates to the byte */ + s->params.pixels_per_line = s->params.bytes_per_line * 8; + + if (params) + *params = s->params; + + DBG (1, "get_parameters: format=%d, pixels/line=%d, bytes/line=%d, " + "lines=%d, dpi=%d\n", + (int) s->params.format, + s->params.pixels_per_line, + s->params.bytes_per_line, + s->params.lines, + res); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +section_parse(const char *val, BH_Section *sect, SANE_Int res, SANE_Int comp) +{ + SANE_Status status = SANE_STATUS_INVAL; + char buf[255+1], *x, *y, *w, *l, *f, *ep; + const char *seps = "x+:"; + double mm, fpixels; + u_long pixels; + + DBG(3, "section_parse called\n"); + + /* a section option looks something like this: + * <width>x<length>+<tl-x>+<tl-y>:<functioncodes> + * Example: + * 76.2x25.4+50.8+0:frontbar:back:front + * the width, length, tl-x, and tl-y are in mm. + * the function codes are one or more of: + * front, back, frontbar, backbar, frontpatch, backpatch + */ + if (strlen(val) > sizeof(buf) - 1) + { + DBG(1, "section_parse: option string too long\n"); + status = SANE_STATUS_INVAL; + } + else + { + do { + strcpy(buf, val); + + x = y = w = l = f = NULL; + w = strtok(buf, seps); + if (w) l = strtok(NULL, seps); + if (l) x = strtok(NULL, seps); + if (x) y = strtok(NULL, seps); + if (y) f = strtok(NULL, seps); + if (!x || !y || !w || !l) break; + + mm = strtod(x, &ep); + if (*ep != '\0' || errno == ERANGE || mm < 0.0) break; + sect->left = mm * 1000.0 / MM_PER_INCH; + + mm = strtod(y, &ep); + if (*ep != '\0' || errno == ERANGE || mm < 0.0) break; + sect->top = mm * 1000.0 / MM_PER_INCH; + + mm = strtod(w, &ep); + if (*ep != '\0' || errno == ERANGE || mm < 0.0) break; + sect->width = mm * 1000.0 / MM_PER_INCH; + /* the window width must be truncated to 16 bit points */ + fpixels = sect->width * res / 1000.0; + pixels = fpixels / 16; + sect->width = pixels * 16 * 1000 / res; + + mm = strtod(l, &ep); + if (*ep != '\0' || errno == ERANGE || mm < 0.0) break; + sect->length = mm * 1000.0 / MM_PER_INCH; + + status = SANE_STATUS_GOOD; + while (f) + { + /* parse the function modifiers and set flags */ + if (strcmp(f, "front") == 0) + sect->flags |= BH_SECTION_FRONT_IMAGE; + else if (strcmp(f, "frontbar") == 0) + sect->flags |= BH_SECTION_FRONT_BAR; + else if (strcmp(f, "frontpatch") == 0) + sect->flags |= BH_SECTION_FRONT_PATCH; + else if (strcmp(f, "back") == 0) + sect->flags |= BH_SECTION_BACK_IMAGE; + else if (strcmp(f, "backbar") == 0) + sect->flags |= BH_SECTION_BACK_BAR; + else if (strcmp(f, "backpatch") == 0) + sect->flags |= BH_SECTION_BACK_PATCH; + else if (strcmp(f, "g42d") == 0) + comp = BH_COMP_G42D; + else if (strcmp(f, "g32d") == 0) + comp = BH_COMP_G32D; + else if (strcmp(f, "g31d") == 0) + comp = BH_COMP_G31D; + else if (strcmp(f, "none") == 0) + comp = BH_COMP_NONE; + else + DBG(1, "section_parse: ignoring unrecognized function " + "code '%s'\n", f); + + f = strtok(NULL, seps); + } + + switch (comp) + { + case BH_COMP_G31D: + sect->compressiontype = 0x01; + sect->compressionarg = 0x00; + sect->format = SANE_FRAME_G31D; + break; + case BH_COMP_G32D: + sect->compressiontype = 0x02; + sect->compressionarg = 0x04; + sect->format = SANE_FRAME_G32D; + break; + case BH_COMP_G42D: + sect->compressiontype = 0x03; + sect->compressionarg = 0x00; + sect->format = SANE_FRAME_G42D; + break; + case BH_COMP_NONE: + default: + sect->compressiontype = 0x00; + sect->compressionarg = 0x00; + sect->format = SANE_FRAME_GRAY; + break; + } + + DBG(3, "section_parse: converted '%s' (mm) to " + "%ldx%ld+%ld+%ld (thousandths) " + "flags=%02x compression=[%d,%d] frame=%s\n", + val, + sect->width, sect->length, sect->left, sect->top, + sect->flags, + sect->compressiontype, sect->compressionarg, + sane_strframe(sect->format)); + + } while (0); /* perform 'loop' once */ + } + + return status; +} + +static SANE_Status +setup_sections (BH_Scanner *s, const char *val) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Int sectnum = 0; + char buf[255+1], *section; + + DBG(3, "setup_sections called\n"); + + memset(s->sections, '\0', sizeof(s->sections)); + if (strlen(val) > sizeof(buf) - 1) + { + DBG(1, "setup_sections: option string too long\n"); + status = SANE_STATUS_INVAL; + } + else + { + strcpy(buf, val); + + section = strtok(buf, ","); + while (section != NULL && sectnum < NUM_SECTIONS) + { + if (!allblank(section)) + { + SANE_Int res = _OPT_VAL_WORD(s, OPT_RESOLUTION); + SANE_Int format = + get_compression_id(_OPT_VAL_STRING(s, OPT_COMPRESSION)); + + status = section_parse(section, &s->sections[sectnum], + res, format); + if (status != SANE_STATUS_GOOD) + { + DBG(1, + "setup_sections: error parsing section `%s'\n", + section); + break; + } + + sectnum++; + } + section += strlen(section) + 1; + if (section > buf + strlen(val)) break; + + section = strtok(section, ","); + } + } + s->num_sections = sectnum; + + return status; +} + +static SANE_Status +start_setup (BH_Scanner *s) +{ + SANE_Status status; + SANE_Bool duplex; + SANE_Int i, imagecnt; + SANE_Byte batchmode; + + DBG(3, "start_setup called\n"); + + duplex = _OPT_VAL_WORD(s, OPT_DUPLEX); + + /* get the _SECTION option, parse it and fill in the sections */ + status = setup_sections(s, _OPT_VAL_STRING(s, OPT_SECTION)); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: setup_sections failed: %s\n", + sane_strstatus(status)); + return status; + } + + /* see whether we'll be decoding barcodes and + * set the barcodes flag appropriately + */ + if (s->search_bars[0] == 0) + { + s->barcodes = SANE_FALSE; + } + else + { + s->barcodes = SANE_TRUE; + } + + /* see whether we'll be handling icons (thumbnails) + * set the icons flag appropriately + */ + if (_OPT_VAL_WORD(s, OPT_ICON_WIDTH) >= 8 && + _OPT_VAL_WORD(s, OPT_ICON_LENGTH) >= 8) + { + s->icons = SANE_TRUE; + } + else + { + s->icons = SANE_FALSE; + } + + + /* calculate a new readlist for this 'batch' */ + s->readptr = s->readcnt = 0; + + /* always read the front image */ + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_FRONT; + + /* read back page only if duplex is true */ + if (duplex == SANE_TRUE) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_BACK; + } + + /* add image section reads to the readlist */ + for (i = 0; i < s->num_sections; i++) + { + SANE_Word flags = s->sections[i].flags; + + if (flags & BH_SECTION_FRONT_IMAGE) + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_FRONT + i + 1; + if (flags & BH_SECTION_BACK_IMAGE) + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_BACK + i + 1; + } + + + /* icons (thumbnails) */ + if (s->icons) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_FRONT_ICON; + /* read back icon only if duplex is true */ + if (duplex == SANE_TRUE) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_BACK_ICON; + } + } + + /* NOTE: It is important that all of the image data comes before + * the barcode/patchcode data. + */ + /* barcodes */ + imagecnt = s->readcnt; + if (s->barcodes) + { + if (s->num_sections == 0) + { + /* we only decode the entire page(s) if there are no + * sections defined + */ + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_FRONT_BARCODE; + /* read back barcode only if duplex is true */ + if (duplex == SANE_TRUE) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_BACK_BARCODE; + } + } + else + { + /* add barcode section reads to the readlist */ + for (i = 0; i < s->num_sections; i++) + { + SANE_Word flags = s->sections[i].flags; + + if (flags & BH_SECTION_FRONT_BAR) + s->readlist[s->readcnt++] = + BH_SCSI_READ_TYPE_FRONT_BARCODE + i + 1; + if (flags & BH_SECTION_BACK_BAR) + s->readlist[s->readcnt++] = + BH_SCSI_READ_TYPE_BACK_BARCODE + i + 1; + } + } + } + + /* patchcodes */ + if (s->patchcodes) + { + if (s->num_sections == 0) + { + /* we only decode the entire page(s) if there are no + * sections defined + */ + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_FRONT_PATCHCODE; + /* read back patchcode only if duplex is true */ + if (duplex == SANE_TRUE) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_BACK_PATCHCODE; + } + } + else + { + /* add patchcode section reads to the readlist */ + for (i = 0; i < s->num_sections; i++) + { + SANE_Word flags = s->sections[i].flags; + + if (flags & BH_SECTION_FRONT_PATCH) + s->readlist[s->readcnt++] = + BH_SCSI_READ_TYPE_FRONT_PATCHCODE + i + 1; + if (flags & BH_SECTION_BACK_PATCH) + s->readlist[s->readcnt++] = + BH_SCSI_READ_TYPE_BACK_PATCHCODE + i + 1; + } + } + } + + /* add the special item to the read list which transfers the barcode + * file that's built as a result of processing barcode and patchcode + * readitems. NOTE: this one must be last! + */ + if (s->readcnt > imagecnt) + { + s->readlist[s->readcnt++] = BH_SCSI_READ_TYPE_SENDBARFILE; + } + + if (_OPT_VAL_WORD(s, OPT_BATCH) == SANE_TRUE) + { + /* if batchmode is enabled, then call set_window to + * abort the batch (even though there might not (and probably + * isn't) a batch in progress). This avoids a batch start error + * in the case where a previous batch was not aborted. + */ + DBG(5, "start_setup: calling set_window to abort batch\n"); + set_window(s, BH_BATCH_ABORT); + + batchmode = BH_BATCH_ENABLE; + } + else + { + batchmode = BH_BATCH_DISABLE; + } + + DBG(5, "start_setup: duplex=%s, barcodes=%s, patchcodes=%s, " + "icons=%s, batch=%s\n", + (duplex == SANE_TRUE) ? "yes" : "no", + (s->barcodes == SANE_TRUE) ? "yes" : "no", + (s->patchcodes == SANE_TRUE) ? "yes" : "no", + (s->icons == SANE_TRUE) ? "yes" : "no", + (batchmode == BH_BATCH_ENABLE) ? "yes" : "no"); + DBG(5, "start_setup: sections=%d\n", s->num_sections); + for (i = 0; i < s->num_sections; i++) + { + DBG(5, "start_setup: " + "[%d] %lux%lu+%lu+%lu flags=%02x compression=[%d,%d]\n", + i+1, + s->sections[i].width, s->sections[i].length, + s->sections[i].left, s->sections[i].top, + s->sections[i].flags, + s->sections[i].compressiontype, s->sections[i].compressionarg); + } + DBG(5, "start_setup: read list length=%d\n", s->readcnt); + for (i = 0; i < s->readcnt; i++) + { + DBG(5, "start_setup: [%d] %s\n", i+1, print_read_type(s->readlist[i])); + } + + DBG(5, "start_setup: sending SET WINDOW\n"); + status = set_window(s, batchmode); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: SET WINDOW failed: %s\n", + sane_strstatus(status)); + return status; + } + + DBG(5, "start_setup: sending mode_select_timeout\n"); + status = mode_select_timeout(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_timeout failed: %s\n", + sane_strstatus(status)); + return status; + } + + if (s->icons == SANE_TRUE) + { + DBG(5, "start_setup: sending mode_select_icon\n"); + status = mode_select_icon(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_icon failed: %s\n", + sane_strstatus(status)); + return status; + } + } + + if (s->barcodes == SANE_TRUE) + { + DBG(5, "start_setup: sending mode_select_barcode_priority\n"); + status = mode_select_barcode_priority(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_barcode_priority failed: %s\n", + sane_strstatus(status)); + return status; + } + + DBG(5, "start_setup: sending mode_select_barcode_param1\n"); + status = mode_select_barcode_param1(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_barcode_param1 failed: %s\n", + sane_strstatus(status)); + return status; + } + + DBG(5, "start_setup: sending mode_select_barcode_param2\n"); + status = mode_select_barcode_param2(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_barcode_param2 failed: %s\n", + sane_strstatus(status)); + return status; + } + + DBG(5, "start_setup: sending mode_select_barcode_param3\n"); + status = mode_select_barcode_param3(s); + if (status != SANE_STATUS_GOOD) + { + DBG(1, "start_setup: mode_select_barcode_param3 failed: %s\n", + sane_strstatus(status)); + return status; + } + } + + return status; +} + +static SANE_Status +start_scan (BH_Scanner *s) +{ + static SANE_Byte cmd[8]; + SANE_Status status = SANE_STATUS_GOOD; + SANE_Bool check_adf, duplex; + DBG (3, "start_scan called\n"); + + /* SANE front ends will call this function between 'FRAMES'. + * A single scan on the B&H may result in up to 56 different + * things to read (20 are SANE image frames, 36 are non-SANE + * data - decoded bar/patch codes). + */ + + if (s->readcnt > 1 && s->scanning == SANE_TRUE) + { + DBG(3, "start_scan: any more items in the readlist?\n"); + /* we've been reading data from this scan, so we just + * move on to the next item in the readlist without + * starting a new scan. + */ + s->readptr++; + if (s->readptr < s->readcnt) + { + SANE_Byte itemtype; + + for (; s->readptr < s->readcnt; s->readptr++) + { + + itemtype = s->readlist[s->readptr]; + + DBG(3, "start_scan: advance readlist(%d, %d)\n", + s->readptr, + (int) itemtype); + + /* 'dance' by the non-SANE data streams + * like bar/patch code data + */ + if (!BH_HAS_IMAGE_DATA(itemtype)) + { + int fd; + FILE *fp; + + strncpy(s->barfname, "/tmp/bhXXXXXX", sizeof(s->barfname)); + s->barfname[sizeof(s->barfname)-1] = '\0'; + + if ((mktemp(s->barfname) == NULL) && + ((fd = open(s->barfname, O_CREAT | O_EXCL | O_WRONLY, 0600)) != -1) && + ((fp = fdopen(fd, "w")) != NULL)) + { + fprintf(fp, "<xml-stream>\n"); + + for (; + s->readptr < s->readcnt && + status == SANE_STATUS_GOOD; + s->readptr++) + { + if (s->readlist[s->readptr] == + BH_SCSI_READ_TYPE_SENDBARFILE) { + break; + } + status = read_barcode_data(s, fp); + if (status != SANE_STATUS_GOOD) break; + } + + fprintf(fp, "</xml-stream>\n"); + + /* close file; re-open for read(setting s->barfd) */ + fclose(fp); + if ((s->barf = fopen(s->barfname, "r")) == NULL) + { + DBG(1, "sane_start: error opening barfile `%s'\n", + s->barfname); + status = SANE_STATUS_IO_ERROR; + } + } + else + { + DBG(1, "sane_start: error opening barfile `%s'\n", + s->barfname); + status = SANE_STATUS_IO_ERROR; + } + } + else if (itemtype == BH_SCSI_READ_TYPE_FRONT_ICON || + itemtype == BH_SCSI_READ_TYPE_BACK_ICON) + { + /* read the icon header setting the iconwidth and iconlength + * to the actual values so get_parameters will have them. + * Subsequent calls to sane_read will get pure image data + * since the icon header has been consumed. + */ + + status = read_icon_data(s); + } + + if (status == SANE_STATUS_GOOD) + { + /* update our parameters to reflect the new item */ + status = get_parameters (s, 0); + } + + if (status != SANE_STATUS_GOOD) s->scanning = SANE_FALSE; + + return status; + } + /* if we reach here, we're finished with the readlist and + * will drop through to start a new scan + */ + } + } + + s->readptr = 0; + + check_adf = _OPT_VAL_WORD(s, OPT_CHECK_ADF); + duplex = _OPT_VAL_WORD(s, OPT_DUPLEX); + + memset (&cmd, 0, sizeof (cmd)); + cmd[0] = BH_SCSI_START_SCAN; + cmd[4] = (duplex == SANE_TRUE) ? 2 : 1; + + cmd[6] = 0; + cmd[7] = 1; + + if (check_adf) + { + status = object_position(s); + if (status != SANE_STATUS_GOOD) + { + DBG(3, "object_position: returned %d\n", status); + return status; + } + } + + status = sanei_scsi_cmd (s->fd, &cmd, sizeof (cmd), 0, 0); + if (status == SANE_STATUS_GOOD) + { + s->scanning = SANE_TRUE; + + /* update our parameters, + * now that we're scanning we'll do a GET_WINDOW + */ + status = get_parameters (s, 0); + if (status != SANE_STATUS_GOOD) + { + s->scanning = SANE_FALSE; + } + } + + return status; +} + +/* a sensible sense handler, courtesy of Franck; + arg is a pointer to the associated BH_Scanner structure */ +static SANE_Status +sense_handler (int scsi_fd, u_char *result, void *arg) +{ + BH_Scanner *s = (BH_Scanner *) arg; + u_char sense, asc, ascq, EOM, ILI, ErrorCode, ValidData; + u_long InvalidBytes; + char *sense_str = "", *as_str = ""; + SANE_Int i; + SANE_Status status = SANE_STATUS_INVAL; + SANE_Char print_sense[(16 * 3) + 1]; + + scsi_fd = scsi_fd; /* get rid of compiler warning */ + ErrorCode = result[0] & 0x7F; + ValidData = (result[0] & 0x80) != 0; + sense = result[2] & 0x0f; /* Key */ + asc = result[12]; /* Code */ + ascq = result[13]; /* Qual */ + EOM = (result[2] & 0x40) != 0; /* End Of Media */ + ILI = (result[2] & 0x20) != 0; /* Invalid Length Indicator */ + InvalidBytes = ValidData ? _4btol(&result[3]) : 0; + + DBG(3, "sense_handler: result=%x, sense=%x, asc=%x, ascq=%x\n", + result[0], sense, asc, ascq); + DBG(3, "sense_handler: ErrorCode %02x ValidData: %d " + "EOM: %d ILI: %d InvalidBytes: %lu\n", + ErrorCode, ValidData, EOM, ILI, InvalidBytes); + + memset(print_sense, '\0', sizeof(print_sense)); + for (i = 0; i < 16; i++) + { + sprintf(print_sense + strlen(print_sense), "%02x ", result[i]); + } + DBG(5, "sense_handler: sense=%s\n", print_sense); + + if (ErrorCode != 0x70 && ErrorCode != 0x71) + { + DBG (3, "sense_handler: error code is invalid.\n"); + return SANE_STATUS_IO_ERROR; /* error code is invalid */ + } + + /* handle each sense key; + * RSC supports 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0B + */ + switch (sense) + { + case 0x00: + /* no sense */ + sense_str = "No sense."; + status = SANE_STATUS_GOOD; + if (ILI && asc == 0x00 && ascq == 0x05) + { + /* from read_data function */ + as_str = "ILI bit is set."; + if (s != NULL) + { + s->InvalidBytes = InvalidBytes; + } + status = SANE_STATUS_GOOD; + } + else if (EOM && asc == 0x00 && ascq == 0x02) + { + /* from adfStatus or startScan function */ + as_str = "Out of paper in the hopper."; + status = SANE_STATUS_NO_DOCS; + } + else if (EOM) + { + /* from adfStatus or startScan function */ + as_str = "Out of paper in the hopper."; + status = SANE_STATUS_NO_DOCS; + } + break; + case 0x01: + /* recovered error */ + sense_str = "Recovered error."; + status = SANE_STATUS_GOOD; + break; + case 0x02: + /* not ready */ + sense_str = "Not ready."; + status = SANE_STATUS_DEVICE_BUSY; + if (asc == 0x40 && ascq == 0x01) + { + as_str = "P.O.D. error: Scanner not found."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x40 && ascq == 0x02) + { + as_str = "P.O.D. error: Scanner not ready(paper in transport)."; + status = SANE_STATUS_DEVICE_BUSY; + } + else if (asc == 0x40 && ascq == 0x03) + { + as_str = "P.O.D. error: Unknown scanner."; + status = SANE_STATUS_INVAL; + } + break; + case 0x03: + /* medium error */ + sense_str = "Medium error."; + status = SANE_STATUS_IO_ERROR; + if (asc == 0x00 && ascq == 0x00) + { + as_str = "Scanner error: paper jam detected."; + status = SANE_STATUS_JAMMED; + } + break; + case 0x04: + /* hardware error */ + sense_str = "Hardware error."; + status = SANE_STATUS_IO_ERROR; + if (asc == 0x60 && ascq == 0x00) + { + as_str = "Scanner error: illumination lamps failure."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x03) + { + as_str = "Communication error between RSC and scanner."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x06) + { + as_str = "Scanner error: page detected but lamps are off."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x07) + { + as_str = "Scanner error: camera white level problem."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x08) + { + /* could be caught from start_scan or read_data */ + /* stop button pressed */ + as_str = "Scanner error: operator pressed the Stop key."; + status = SANE_STATUS_NO_DOCS; + } + else if (asc == 0x80 && ascq == 0x12) + { + as_str = "Scanner error: transport motor failure."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x15) + { + as_str = "Scanner error: device / page sensor(s) bouncing."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x16) + { + as_str = "Scanner error: feeder is not attached."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x18) + { + as_str = "Scanner error: logic system general failure."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x34) + { + as_str = "Scanner error: no dual logic communication."; + status = SANE_STATUS_IO_ERROR; + } + break; + case 0x05: + /* illegal request */ + sense_str = "Illegal request."; + status = SANE_STATUS_INVAL; + if (asc == 0x1a && ascq == 0x00) + { + as_str = "Parameter list length error."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x20 && ascq == 0x00) + { + as_str = "Invalid command operation code."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x24 && ascq == 0x00) + { + /* caught from object_position (via reverse engineering) */ + /* Not supported? */ + as_str = "Invalid field in CDB."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x25 && ascq == 0x00) + { + as_str = "Unsupported LUN."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x26 && ascq == 0x00) + { + /* caught from mode_select (as well as others) */ + /* Bar/Patch code detection support not installed */ + /* See Appendix A, Section A.5 */ + as_str = "Invalid field in parameter list."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x2c && ascq == 0x00) + { + /* we were getting this in read_data during the time + that the ADF was misbehaving. Hopefully we will + not see it anymore. + */ + as_str = "Command out of sequence."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x2c && ascq == 0x01) + { + as_str = "Too many windows defined."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x2c && ascq == 0x02) + { + as_str = "Batch start error."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x2c && ascq == 0x03) + { + as_str = "Batch abort error."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x3d && ascq == 0x00) + { + as_str = "Invalid bits in IDENTIFY message."; + status = SANE_STATUS_INVAL; + } + break; + case 0x06: + /* unit attention */ + sense_str = "Unit attention."; + status = SANE_STATUS_IO_ERROR; + if (asc == 0x04 && ascq == 0x01) + { + as_str = "Reset detected, LUN is becoming ready."; + status = SANE_STATUS_DEVICE_BUSY; + } + break; + case 0x07: + /* data protect */ + sense_str = "Data protect."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x08: + /* blank check */ + sense_str = "Blank check."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x09: + /* vendor specific */ + sense_str = "Vendor specific."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x0A: + /* copy aborted */ + sense_str = "Copy aborted."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x0B: + /* aborted command */ + sense_str = "Aborted command."; + status = SANE_STATUS_IO_ERROR; + if (asc == 0x00 && ascq == 0x00) + { + as_str = "Aborted command (unspecified error)."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x08 && ascq == 0x01) + { + /* caught from start_scan */ + /* manual feed timeout */ + as_str = "SCSI Time-out, paper Time-out (SCAN command)."; + status = SANE_STATUS_NO_DOCS; + } + else if (asc == 0x47 && ascq == 0x00) + { + as_str = "SCSI parity error."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x00) + { + as_str = "Aborted command due to memory error."; + status = SANE_STATUS_IO_ERROR; + } + else if (asc == 0x80 && ascq == 0x01) + { + /* caught from read_data */ + /* section border error; border is outside the main window */ + /* See Appendix A, Section A.4 */ + as_str = "Section Read error (out of border)."; + status = SANE_STATUS_INVAL; + } + else if (asc == 0x80 && ascq == 0x02) + { + /* caught from read_data */ + /* No code found; no barcode data is found */ + /* See Appendix A, Section A.5 */ + s->barcode_not_found = SANE_TRUE; + as_str = "No Bar/Patch Code found."; + status = SANE_STATUS_GOOD; + } + else if (asc == 0x80 && ascq == 0x03) + { + as_str = "Icon Read error (out of border)."; + status = SANE_STATUS_INVAL; + } + break; + case 0x0C: + /* equal */ + sense_str = "Equal."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x0D: + /* volume overflow */ + sense_str = "Volume overflow."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x0E: + /* miscompare */ + sense_str = "Miscompare."; + status = SANE_STATUS_IO_ERROR; + break; + case 0x0F: + /* reserved */ + sense_str = "Reserved."; + status = SANE_STATUS_IO_ERROR; + break; + default: + sense_str = "Unhandled case."; + status = SANE_STATUS_IO_ERROR; + break; + } + + DBG(3, "sense_handler: '%s' '%s' return:%d\n", + sense_str, as_str, status); + + return status; +} + +static SANE_Status +init_options (BH_Scanner * s) +{ + int i; + DBG (3, "init_options called\n"); + + memset (s->opt, 0, sizeof (s->opt)); + memset (s->val, 0, sizeof (s->val)); + + for (i = 0; i < NUM_OPTIONS; ++i) + { + s->opt[i].size = sizeof (SANE_Word); + s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + 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].cap = SANE_CAP_SOFT_DETECT; + s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; + s->val[OPT_NUM_OPTS].w = NUM_OPTIONS; + + /* "Scan Mode" group: */ + s->opt[OPT_MODE_GROUP].name = ""; + s->opt[OPT_MODE_GROUP].title = SANE_TITLE_SCAN_MODE_GROUP; + s->opt[OPT_MODE_GROUP].desc = ""; + s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_MODE_GROUP].cap = 0; + s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* Preview: */ + s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; + s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; + s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_PREVIEW].w = 0; + + /* Inquiry */ + s->opt[OPT_INQUIRY].name = SANE_NAME_INQUIRY; + s->opt[OPT_INQUIRY].title = SANE_TITLE_INQUIRY; + s->opt[OPT_INQUIRY].desc = SANE_DESC_INQUIRY; + s->opt[OPT_INQUIRY].type = SANE_TYPE_STRING; + s->opt[OPT_INQUIRY].size = sizeof(inquiry_data); + s->opt[OPT_INQUIRY].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_INQUIRY].s = strdup(inquiry_data); + s->opt[OPT_INQUIRY].cap = SANE_CAP_SOFT_DETECT; + + /* scan mode */ + s->opt[OPT_SCAN_MODE].name = SANE_NAME_SCAN_MODE; + s->opt[OPT_SCAN_MODE].title = SANE_TITLE_SCAN_MODE; + s->opt[OPT_SCAN_MODE].desc = SANE_DESC_SCAN_MODE; + s->opt[OPT_SCAN_MODE].type = SANE_TYPE_STRING; + s->opt[OPT_SCAN_MODE].size = max_string_size (scan_mode_list); + s->opt[OPT_SCAN_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_SCAN_MODE].constraint.string_list = scan_mode_list; + s->val[OPT_SCAN_MODE].s = strdup (scan_mode_list[0]); + + /* Standard resolutions */ + 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; + s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->info.resStdList; + s->val[OPT_RESOLUTION].w = s->hw->info.res_default; + + /* compression */ + s->opt[OPT_COMPRESSION].name = SANE_NAME_COMPRESSION; + s->opt[OPT_COMPRESSION].title = SANE_TITLE_COMPRESSION; + s->opt[OPT_COMPRESSION].desc = SANE_DESC_COMPRESSION; + s->opt[OPT_COMPRESSION].type = SANE_TYPE_STRING; + s->opt[OPT_COMPRESSION].size = max_string_size (compression_list); + s->opt[OPT_COMPRESSION].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_COMPRESSION].constraint.string_list = compression_list; + s->val[OPT_COMPRESSION].s = strdup (compression_list[0]); + + if (s->hw->info.colorHalftone == SANE_FALSE) + { + s->opt[OPT_SCAN_MODE].size = max_string_size (scan_mode_min_list); + s->opt[OPT_SCAN_MODE].constraint.string_list = scan_mode_min_list; + } + + if (s->hw->info.comprG3_1D == SANE_FALSE || + s->hw->info.comprG3_2D == SANE_FALSE || + s->hw->info.comprG4 == SANE_FALSE) + { + s->opt[OPT_COMPRESSION].cap |= SANE_CAP_INACTIVE; + } + + /* "Geometry" group: */ + s->opt[OPT_GEOMETRY_GROUP].name = ""; + s->opt[OPT_GEOMETRY_GROUP].title = SANE_TITLE_GEOMETRY_GROUP; + s->opt[OPT_GEOMETRY_GROUP].desc = ""; + s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_GEOMETRY_GROUP].cap = 0; + s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* Autoborder: */ + s->opt[OPT_AUTOBORDER].name = SANE_NAME_AUTOBORDER; + s->opt[OPT_AUTOBORDER].title = SANE_TITLE_AUTOBORDER; + s->opt[OPT_AUTOBORDER].desc = SANE_DESC_AUTOBORDER; + s->opt[OPT_AUTOBORDER].type = SANE_TYPE_BOOL; + s->opt[OPT_AUTOBORDER].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_AUTOBORDER].w = s->hw->info.autoborder_default; + + /* Paper Size */ + s->opt[OPT_PAPER_SIZE].name = SANE_NAME_PAPER_SIZE; + s->opt[OPT_PAPER_SIZE].title = SANE_TITLE_PAPER_SIZE; + s->opt[OPT_PAPER_SIZE].desc = SANE_DESC_PAPER_SIZE; + s->opt[OPT_PAPER_SIZE].type = SANE_TYPE_STRING; + s->opt[OPT_PAPER_SIZE].size = max_string_size (paper_list); + s->opt[OPT_PAPER_SIZE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_PAPER_SIZE].constraint.string_list = paper_list; + s->val[OPT_PAPER_SIZE].s = strdup (paper_list[0]); + + /* rotation */ + s->opt[OPT_ROTATION].name = SANE_NAME_ROTATION; + s->opt[OPT_ROTATION].title = SANE_TITLE_ROTATION; + s->opt[OPT_ROTATION].desc = SANE_DESC_ROTATION; + s->opt[OPT_ROTATION].type = SANE_TYPE_STRING; + s->opt[OPT_ROTATION].size = max_string_size (rotation_list); + s->opt[OPT_ROTATION].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_ROTATION].constraint.string_list = rotation_list; + s->val[OPT_ROTATION].s = strdup (rotation_list[0]); + + /* Deskew: */ + s->opt[OPT_DESKEW].name = SANE_NAME_DESKEW; + s->opt[OPT_DESKEW].title = SANE_TITLE_DESKEW; + s->opt[OPT_DESKEW].desc = SANE_DESC_DESKEW; + s->opt[OPT_DESKEW].type = SANE_TYPE_BOOL; + s->opt[OPT_DESKEW].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_DESKEW].w = s->hw->info.deskew_default; + + /* 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->info.x_range); + s->val[OPT_TL_X].w = SANE_FIX(0.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->info.y_range); + s->val[OPT_TL_Y].w = SANE_FIX(0.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->info.x_range); + s->val[OPT_BR_X].w = s->hw->info.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->info.y_range); + s->val[OPT_BR_Y].w = s->hw->info.y_range.max; + + if (s->hw->info.canBorderRecog == SANE_FALSE) + { + s->opt[OPT_AUTOBORDER].cap |= SANE_CAP_INACTIVE; + } + + /* "Feeder" group: */ + s->opt[OPT_FEEDER_GROUP].name = ""; + s->opt[OPT_FEEDER_GROUP].title = SANE_TITLE_FEEDER_GROUP; + s->opt[OPT_FEEDER_GROUP].desc = ""; + s->opt[OPT_FEEDER_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_FEEDER_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_FEEDER_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* scan source */ + s->opt[OPT_SCAN_SOURCE].name = SANE_NAME_SCAN_SOURCE; + s->opt[OPT_SCAN_SOURCE].title = SANE_TITLE_SCAN_SOURCE; + s->opt[OPT_SCAN_SOURCE].desc = SANE_DESC_SCAN_SOURCE; + s->opt[OPT_SCAN_SOURCE].type = SANE_TYPE_STRING; + s->opt[OPT_SCAN_SOURCE].size = max_string_size (scan_source_list); + s->opt[OPT_SCAN_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_SCAN_SOURCE].constraint.string_list = scan_source_list; + s->val[OPT_SCAN_SOURCE].s = strdup (scan_source_list[0]); + + /* Batch: */ + s->opt[OPT_BATCH].name = SANE_NAME_BATCH; + s->opt[OPT_BATCH].title = SANE_TITLE_BATCH; + s->opt[OPT_BATCH].desc = SANE_DESC_BATCH; + s->opt[OPT_BATCH].type = SANE_TYPE_BOOL; + s->opt[OPT_BATCH].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_BATCH].w = s->hw->info.batch_default; + + /* Check ADF: */ + s->opt[OPT_CHECK_ADF].name = SANE_NAME_CHECK_ADF; + s->opt[OPT_CHECK_ADF].title = SANE_TITLE_CHECK_ADF; + s->opt[OPT_CHECK_ADF].desc = SANE_DESC_CHECK_ADF; + s->opt[OPT_CHECK_ADF].type = SANE_TYPE_BOOL; + s->opt[OPT_CHECK_ADF].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_CHECK_ADF].w = s->hw->info.check_adf_default; + + /* Duplex: */ + s->opt[OPT_DUPLEX].name = SANE_NAME_DUPLEX; + s->opt[OPT_DUPLEX].title = SANE_TITLE_DUPLEX; + s->opt[OPT_DUPLEX].desc = SANE_DESC_DUPLEX; + s->opt[OPT_DUPLEX].type = SANE_TYPE_BOOL; + s->opt[OPT_DUPLEX].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_DUPLEX].w = s->hw->info.duplex_default; + + /* timeout adf */ + s->opt[OPT_TIMEOUT_ADF].name = SANE_NAME_TIMEOUT_ADF; + s->opt[OPT_TIMEOUT_ADF].title = SANE_TITLE_TIMEOUT_ADF; + s->opt[OPT_TIMEOUT_ADF].desc = SANE_DESC_TIMEOUT_ADF; + s->opt[OPT_TIMEOUT_ADF].type = SANE_TYPE_INT; + s->opt[OPT_TIMEOUT_ADF].unit = SANE_UNIT_NONE; + s->opt[OPT_TIMEOUT_ADF].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TIMEOUT_ADF].constraint.range = &u8_range; + s->val[OPT_TIMEOUT_ADF].w = s->hw->info.timeout_adf_default; + + /* timeout manual */ + s->opt[OPT_TIMEOUT_MANUAL].name = SANE_NAME_TIMEOUT_MANUAL; + s->opt[OPT_TIMEOUT_MANUAL].title = SANE_TITLE_TIMEOUT_MANUAL; + s->opt[OPT_TIMEOUT_MANUAL].desc = SANE_DESC_TIMEOUT_MANUAL; + s->opt[OPT_TIMEOUT_MANUAL].type = SANE_TYPE_INT; + s->opt[OPT_TIMEOUT_MANUAL].unit = SANE_UNIT_NONE; + s->opt[OPT_TIMEOUT_MANUAL].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TIMEOUT_MANUAL].constraint.range = &u8_range; + s->val[OPT_TIMEOUT_MANUAL].w = s->hw->info.timeout_manual_default; + + if (s->hw->info.canCheckADF == SANE_FALSE) + { + s->opt[OPT_CHECK_ADF].cap |= SANE_CAP_INACTIVE; + } + + if (s->hw->info.canDuplex == SANE_FALSE) + { + s->opt[OPT_DUPLEX].cap |= SANE_CAP_INACTIVE; + } + + if (s->hw->info.canADF == SANE_FALSE) + { + s->opt[OPT_TIMEOUT_ADF].cap |= SANE_CAP_INACTIVE; + } + + /* "Enhancement" group: */ + s->opt[OPT_ENHANCEMENT_GROUP].name = ""; + s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_TITLE_ENHANCEMENT_GROUP; + s->opt[OPT_ENHANCEMENT_GROUP].desc = ""; + s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* Control Panel: */ + s->opt[OPT_CONTROL_PANEL].name = SANE_NAME_CONTROL_PANEL; + s->opt[OPT_CONTROL_PANEL].title = SANE_TITLE_CONTROL_PANEL; + s->opt[OPT_CONTROL_PANEL].desc = SANE_DESC_CONTROL_PANEL; + s->opt[OPT_CONTROL_PANEL].type = SANE_TYPE_BOOL; + s->opt[OPT_CONTROL_PANEL].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_CONTROL_PANEL].w = s->hw->info.control_panel_default; + + /* Ace_Function */ + s->opt[OPT_ACE_FUNCTION].name = SANE_NAME_ACE_FUNCTION; + s->opt[OPT_ACE_FUNCTION].title = SANE_TITLE_ACE_FUNCTION; + s->opt[OPT_ACE_FUNCTION].desc = SANE_DESC_ACE_FUNCTION; + s->opt[OPT_ACE_FUNCTION].type = SANE_TYPE_INT; + s->opt[OPT_ACE_FUNCTION].unit = SANE_UNIT_NONE; + s->opt[OPT_ACE_FUNCTION].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_ACE_FUNCTION].constraint.range = &ace_function_range; + s->val[OPT_ACE_FUNCTION].w = 0; + + /* Ace_Sensitivity */ + s->opt[OPT_ACE_SENSITIVITY].name = SANE_NAME_ACE_SENSITIVITY; + s->opt[OPT_ACE_SENSITIVITY].title = SANE_TITLE_ACE_SENSITIVITY; + s->opt[OPT_ACE_SENSITIVITY].desc = SANE_DESC_ACE_SENSITIVITY; + s->opt[OPT_ACE_SENSITIVITY].type = SANE_TYPE_INT; + s->opt[OPT_ACE_SENSITIVITY].unit = SANE_UNIT_NONE; + s->opt[OPT_ACE_SENSITIVITY].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_ACE_SENSITIVITY].constraint.range = &ace_sensitivity_range; + s->val[OPT_ACE_SENSITIVITY].w = 4; + + /* Brightness */ + s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT; + s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE; + s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BRIGHTNESS].constraint.range = &u8_range; + s->val[OPT_BRIGHTNESS].w = 0; + + /* Threshold */ + s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD; + s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD; + s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD; + s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT; + s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE; + s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_THRESHOLD].constraint.range = &u8_range; + s->val[OPT_THRESHOLD].w = 0; + + /* Contrast */ + s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; + s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; + s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; + s->opt[OPT_CONTRAST].type = SANE_TYPE_INT; + s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE; + s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CONTRAST].constraint.range = &u8_range; + s->val[OPT_CONTRAST].w = 0; + + /* Negative: */ + s->opt[OPT_NEGATIVE].name = SANE_NAME_NEGATIVE; + s->opt[OPT_NEGATIVE].title = SANE_TITLE_NEGATIVE; + s->opt[OPT_NEGATIVE].desc = SANE_DESC_NEGATIVE; + s->opt[OPT_NEGATIVE].type = SANE_TYPE_BOOL; + s->opt[OPT_NEGATIVE].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_NEGATIVE].w = SANE_FALSE; + + /* Contrast is not used in any case; why did we add it? */ + s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE; + if (s->hw->info.control_panel_default == SANE_TRUE) + { + s->opt[OPT_ACE_FUNCTION].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ACE_SENSITIVITY].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE; + } + else if (s->hw->info.canACE == SANE_FALSE) + { + s->opt[OPT_ACE_FUNCTION].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ACE_SENSITIVITY].cap |= SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE; + } + + /* "ICON" group: */ + s->opt[OPT_ICON_GROUP].name = ""; + s->opt[OPT_ICON_GROUP].title = SANE_TITLE_ICON_GROUP; + s->opt[OPT_ICON_GROUP].desc = ""; + s->opt[OPT_ICON_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_ICON_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_ICON_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* Icon_Width */ + s->opt[OPT_ICON_WIDTH].name = SANE_NAME_ICON_WIDTH; + s->opt[OPT_ICON_WIDTH].title = SANE_TITLE_ICON_WIDTH; + s->opt[OPT_ICON_WIDTH].desc = SANE_DESC_ICON_WIDTH; + s->opt[OPT_ICON_WIDTH].type = SANE_TYPE_INT; + s->opt[OPT_ICON_WIDTH].unit = SANE_UNIT_PIXEL; + s->opt[OPT_ICON_WIDTH].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_ICON_WIDTH].constraint.range = &icon_range; + s->val[OPT_ICON_WIDTH].w = 0; + + /* Icon_Length */ + s->opt[OPT_ICON_LENGTH].name = SANE_NAME_ICON_LENGTH; + s->opt[OPT_ICON_LENGTH].title = SANE_TITLE_ICON_LENGTH; + s->opt[OPT_ICON_LENGTH].desc = SANE_DESC_ICON_LENGTH; + s->opt[OPT_ICON_LENGTH].type = SANE_TYPE_INT; + s->opt[OPT_ICON_LENGTH].unit = SANE_UNIT_PIXEL; + s->opt[OPT_ICON_LENGTH].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_ICON_LENGTH].constraint.range = &icon_range; + s->val[OPT_ICON_LENGTH].w = 0; + + if (s->hw->info.canIcon == SANE_FALSE) + { + s->opt[OPT_ICON_GROUP].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ICON_WIDTH].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ICON_LENGTH].cap |= SANE_CAP_INACTIVE; + } + + /* "Barcode" group: */ + s->opt[OPT_BARCODE_GROUP].name = ""; + s->opt[OPT_BARCODE_GROUP].title = SANE_TITLE_BARCODE_GROUP; + s->opt[OPT_BARCODE_GROUP].desc = ""; + s->opt[OPT_BARCODE_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_BARCODE_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_BARCODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* Add <name> to barcode search priority. */ + s->opt[OPT_BARCODE_SEARCH_BAR].name = SANE_NAME_BARCODE_SEARCH_BAR; + s->opt[OPT_BARCODE_SEARCH_BAR].title = SANE_TITLE_BARCODE_SEARCH_BAR; + s->opt[OPT_BARCODE_SEARCH_BAR].desc = SANE_DESC_BARCODE_SEARCH_BAR; + s->opt[OPT_BARCODE_SEARCH_BAR].type = SANE_TYPE_STRING; + s->opt[OPT_BARCODE_SEARCH_BAR].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_SEARCH_BAR].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_BARCODE_SEARCH_BAR].constraint.string_list = barcode_search_bar_list; + s->opt[OPT_BARCODE_SEARCH_BAR].size = max_string_size (barcode_search_bar_list); + s->val[OPT_BARCODE_SEARCH_BAR].s = strdup (barcode_search_bar_list[0]); + + /* Barcode search count (1-7, default 1). */ + s->opt[OPT_BARCODE_SEARCH_COUNT].name = SANE_NAME_BARCODE_SEARCH_COUNT; + s->opt[OPT_BARCODE_SEARCH_COUNT].title = SANE_TITLE_BARCODE_SEARCH_COUNT; + s->opt[OPT_BARCODE_SEARCH_COUNT].desc = SANE_DESC_BARCODE_SEARCH_COUNT; + s->opt[OPT_BARCODE_SEARCH_COUNT].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_SEARCH_COUNT].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_SEARCH_COUNT].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_SEARCH_COUNT].constraint.range = &barcode_search_count_range; + s->val[OPT_BARCODE_SEARCH_COUNT].w = 3; + + /* Barcode search mode. horiz-vert, horizontal, vertical, vert-horiz */ + s->opt[OPT_BARCODE_SEARCH_MODE].name = SANE_NAME_BARCODE_SEARCH_MODE; + s->opt[OPT_BARCODE_SEARCH_MODE].title = SANE_TITLE_BARCODE_SEARCH_MODE; + s->opt[OPT_BARCODE_SEARCH_MODE].desc = SANE_DESC_BARCODE_SEARCH_MODE; + s->opt[OPT_BARCODE_SEARCH_MODE].type = SANE_TYPE_STRING; + s->opt[OPT_BARCODE_SEARCH_MODE].size = max_string_size (barcode_search_mode_list); + s->opt[OPT_BARCODE_SEARCH_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_BARCODE_SEARCH_MODE].constraint.string_list = barcode_search_mode_list; + s->val[OPT_BARCODE_SEARCH_MODE].s = strdup(barcode_search_mode_list[0]); + + /* Patch code min height (def=5mm) */ + s->opt[OPT_BARCODE_HMIN].name = SANE_NAME_BARCODE_HMIN; + s->opt[OPT_BARCODE_HMIN].title = SANE_TITLE_BARCODE_HMIN; + s->opt[OPT_BARCODE_HMIN].desc = SANE_DESC_BARCODE_HMIN; + s->opt[OPT_BARCODE_HMIN].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_HMIN].unit = SANE_UNIT_MM; + s->opt[OPT_BARCODE_HMIN].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_HMIN].constraint.range = &barcode_hmin_range; + s->val[OPT_BARCODE_HMIN].w = 5; + + /* Barcode search timeout in ms (20-65535,default is 10000). */ + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].name = SANE_NAME_BARCODE_SEARCH_TIMEOUT; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].title = SANE_TITLE_BARCODE_SEARCH_TIMEOUT; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].desc = SANE_DESC_BARCODE_SEARCH_TIMEOUT; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].unit = SANE_UNIT_MICROSECOND; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].constraint.range = &barcode_search_timeout_range; + s->val[OPT_BARCODE_SEARCH_TIMEOUT].w = 10000; + + /* Specify image sections and functions */ + s->opt[OPT_SECTION].name = SANE_NAME_SECTION; + s->opt[OPT_SECTION].title = SANE_TITLE_SECTION; + s->opt[OPT_SECTION].desc = SANE_DESC_SECTION; + s->opt[OPT_SECTION].type = SANE_TYPE_STRING; + s->opt[OPT_SECTION].unit = SANE_UNIT_NONE; + s->opt[OPT_SECTION].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_SECTION].size = 255; + s->val[OPT_SECTION].s = strdup (""); + + /* Barcode_Relmax */ + s->opt[OPT_BARCODE_RELMAX].name = SANE_NAME_BARCODE_RELMAX; + s->opt[OPT_BARCODE_RELMAX].title = SANE_TITLE_BARCODE_RELMAX; + s->opt[OPT_BARCODE_RELMAX].desc = SANE_DESC_BARCODE_RELMAX; + s->opt[OPT_BARCODE_RELMAX].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_RELMAX].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_RELMAX].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_RELMAX].constraint.range = &u8_range; + s->val[OPT_BARCODE_RELMAX].w = 0; + + /* Barcode_Barmin */ + s->opt[OPT_BARCODE_BARMIN].name = SANE_NAME_BARCODE_BARMIN; + s->opt[OPT_BARCODE_BARMIN].title = SANE_TITLE_BARCODE_BARMIN; + s->opt[OPT_BARCODE_BARMIN].desc = SANE_DESC_BARCODE_BARMIN; + s->opt[OPT_BARCODE_BARMIN].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_BARMIN].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_BARMIN].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_BARMIN].constraint.range = &u8_range; + s->val[OPT_BARCODE_BARMIN].w = 0; + + /* Barcode_Barmax */ + s->opt[OPT_BARCODE_BARMAX].name = SANE_NAME_BARCODE_BARMAX; + s->opt[OPT_BARCODE_BARMAX].title = SANE_TITLE_BARCODE_BARMAX; + s->opt[OPT_BARCODE_BARMAX].desc = SANE_DESC_BARCODE_BARMAX; + s->opt[OPT_BARCODE_BARMAX].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_BARMAX].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_BARMAX].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_BARMAX].constraint.range = &u8_range; + s->val[OPT_BARCODE_BARMAX].w = 0; + + /* Barcode_Contrast */ + s->opt[OPT_BARCODE_CONTRAST].name = SANE_NAME_BARCODE_CONTRAST; + s->opt[OPT_BARCODE_CONTRAST].title = SANE_TITLE_BARCODE_CONTRAST; + s->opt[OPT_BARCODE_CONTRAST].desc = SANE_DESC_BARCODE_CONTRAST; + s->opt[OPT_BARCODE_CONTRAST].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_CONTRAST].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_CONTRAST].constraint.range = &barcode_contrast_range; + s->val[OPT_BARCODE_CONTRAST].w = 3; + + /* Barcode_Patchmode */ + s->opt[OPT_BARCODE_PATCHMODE].name = SANE_NAME_BARCODE_PATCHMODE; + s->opt[OPT_BARCODE_PATCHMODE].title = SANE_TITLE_BARCODE_PATCHMODE; + s->opt[OPT_BARCODE_PATCHMODE].desc = SANE_DESC_BARCODE_PATCHMODE; + s->opt[OPT_BARCODE_PATCHMODE].type = SANE_TYPE_INT; + s->opt[OPT_BARCODE_PATCHMODE].unit = SANE_UNIT_NONE; + s->opt[OPT_BARCODE_PATCHMODE].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BARCODE_PATCHMODE].constraint.range = &barcode_patchmode_range; + s->val[OPT_BARCODE_PATCHMODE].w = 0; + + if (s->hw->info.canSection == SANE_FALSE) + { + s->opt[OPT_SECTION].cap |= SANE_CAP_INACTIVE; + } + + if (s->hw->info.canBarCode == SANE_FALSE) + { + s->opt[OPT_BARCODE_GROUP].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_SEARCH_BAR].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_SEARCH_COUNT].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_SEARCH_MODE].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_HMIN].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_SEARCH_TIMEOUT].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_RELMAX].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_BARMIN].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_BARMAX].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_CONTRAST].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BARCODE_PATCHMODE].cap |= SANE_CAP_INACTIVE; + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +attach (const char *devnam, BH_Device ** devp) +{ + SANE_Status status; + BH_Device *dev; + struct inquiry_standard_data ibuf; + struct inquiry_vpd_data vbuf; + struct inquiry_jis_data jbuf; + size_t buf_size; + int fd = -1; + double mm; + + DBG (3, "attach called\n"); + + for (dev = first_dev; dev; dev = dev->next) + { + if (strcmp (dev->sane.name, devnam) == 0) + { + if (devp) + *devp = dev; + return SANE_STATUS_GOOD; + } + } + +#ifdef FAKE_INQUIRY + if (fake_inquiry) + { + DBG (3, "attach: faking inquiry of %s\n", devnam); + + memset (&ibuf, 0, sizeof (ibuf)); + ibuf.devtype = 6; + memcpy(ibuf.vendor, "**FAKE**", 8); + memcpy(ibuf.product, "COPISCAN II 6338", 16); + memcpy(ibuf.revision, "0016", 4); + + DBG (1, "attach: reported devtype='%d', vendor='%.8s', " + "product='%.16s', revision='%.4s'\n", + ibuf.devtype, ibuf.vendor, + ibuf.product, ibuf.revision); + + memset (&vbuf, 0, sizeof (vbuf)); + memset (&jbuf, 0, sizeof (jbuf)); + } + else +#endif + { + DBG (3, "attach: opening %s\n", devnam); + status = sanei_scsi_open (devnam, &fd, sense_handler, NULL); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "attach: open failed: %s\n", sane_strstatus (status)); + return status; + } + + DBG (3, "attach: sending TEST_UNIT_READY\n"); + status = test_unit_ready (fd); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "attach: test unit ready failed (%s)\n", + sane_strstatus (status)); + sanei_scsi_close (fd); + return status; + } + + DBG (3, "attach: sending INQUIRY (standard data)\n"); + memset (&ibuf, 0, sizeof (ibuf)); + buf_size = sizeof(ibuf); + status = inquiry (fd, &ibuf, &buf_size, 0, + BH_INQUIRY_STANDARD_PAGE_CODE); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "attach: inquiry (standard data) failed: %s\n", + sane_strstatus (status)); + sanei_scsi_close (fd); + return status; + } + + DBG (1, "attach: reported devtype='%d', vendor='%.8s', " + "product='%.16s', revision='%.4s'\n", + ibuf.devtype, ibuf.vendor, + ibuf.product, ibuf.revision); + + if (ibuf.devtype != 6 + || strncmp ((char *)ibuf.vendor, "B&H SCSI", 8) != 0 + || strncmp ((char *)ibuf.product, "COPISCAN ", 9) != 0) + { + DBG (1, + "attach: device is not a recognized Bell and Howell scanner\n"); + sanei_scsi_close (fd); + return SANE_STATUS_INVAL; + } + + DBG (3, "attach: sending INQUIRY (vpd data)\n"); + memset (&vbuf, 0, sizeof (vbuf)); + buf_size = sizeof(vbuf); + status = inquiry (fd, &vbuf, &buf_size, 1, + BH_INQUIRY_VPD_PAGE_CODE); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "attach: inquiry (vpd data) failed: %s\n", + sane_strstatus (status)); + sanei_scsi_close (fd); + return status; + } + + DBG (3, "attach: sending INQUIRY (jis data)\n"); + memset (&jbuf, 0, sizeof (jbuf)); + buf_size = sizeof(jbuf); + status = inquiry (fd, &jbuf, &buf_size, 1, + BH_INQUIRY_JIS_PAGE_CODE); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "attach: inquiry (jis data) failed: %s\n", + sane_strstatus (status)); + sanei_scsi_close (fd); + return status; + } + + sanei_scsi_close (fd); + } + + dev = malloc (sizeof (*dev)); + if (!dev) + return SANE_STATUS_NO_MEM; + memset (dev, 0, sizeof (*dev)); + + + dev->info.devtype = ibuf.devtype; + sprintf(dev->info.vendor, "%.8s", ibuf.vendor); + trim_spaces(dev->info.vendor, sizeof(dev->info.vendor)); + sprintf(dev->info.product, "%.16s", ibuf.product); + trim_spaces(dev->info.product, sizeof(dev->info.product)); + sprintf(dev->info.revision, "%.4s", ibuf.revision); + trim_spaces(dev->info.revision, sizeof(dev->info.revision)); + + dev->sane.name = strdup (devnam); + dev->sane.vendor = strdup(dev->info.vendor); + dev->sane.model = strdup(dev->info.product);; + dev->sane.type = strdup(print_devtype(dev->info.devtype)); + + /* set capabilities from vpd */ + dev->info.canADF = vbuf.adf & 0x01; + dev->info.colorBandW = vbuf.imagecomposition & 0x01; + dev->info.colorHalftone = vbuf.imagecomposition & 0x02; + dev->info.canWhiteFrame = vbuf.imagedataprocessing[1] & 0x01; + dev->info.canBlackFrame = vbuf.imagedataprocessing[1] & 0x02; + dev->info.canEdgeExtract = vbuf.imagedataprocessing[1] & 0x04; + dev->info.canNoiseFilter = vbuf.imagedataprocessing[1] & 0x08; + dev->info.canSmooth = vbuf.imagedataprocessing[1] & 0x10; + dev->info.canLineBold = vbuf.imagedataprocessing[1] & 0x20; + dev->info.comprG3_1D = vbuf.compression & 0x01; + dev->info.comprG3_2D = vbuf.compression & 0x02; + dev->info.comprG4 = vbuf.compression & 0x04; + dev->info.canBorderRecog = vbuf.sizerecognition & 0x01; + dev->info.canBarCode = vbuf.optionalfeatures & 0x01; + dev->info.canIcon = vbuf.optionalfeatures & 0x02; + dev->info.canSection = vbuf.optionalfeatures & 0x04; + dev->info.lineMaxBytes = _2btol(vbuf.xmaxoutputbytes); + +#ifdef FAKE_INQUIRY + if (fake_inquiry) + { + dev->info.canADF = SANE_FALSE; + dev->info.colorBandW = SANE_TRUE; + dev->info.colorHalftone = SANE_TRUE; + dev->info.canWhiteFrame = SANE_TRUE; + dev->info.canBlackFrame = SANE_TRUE; + dev->info.canEdgeExtract = SANE_TRUE; + dev->info.canNoiseFilter = SANE_TRUE; + dev->info.canSmooth = SANE_TRUE; + dev->info.canLineBold = SANE_TRUE; + dev->info.comprG3_1D = SANE_TRUE; + dev->info.comprG3_2D = SANE_TRUE; + dev->info.comprG4 = SANE_TRUE; + dev->info.canBorderRecog = SANE_TRUE; + dev->info.canBarCode = SANE_TRUE; + dev->info.canIcon = SANE_TRUE; + dev->info.canSection = SANE_TRUE; + dev->info.lineMaxBytes = 450; + } +#endif + + /* set capabilities from jis */ + dev->info.resBasicX = _2btol(jbuf.basicxres); + dev->info.resBasicY = _2btol(jbuf.basicyres); + dev->info.resMaxX = _2btol(jbuf.maxxres); + dev->info.resMaxY = _2btol(jbuf.maxyres); + dev->info.resMinX = _2btol(jbuf.minxres); + dev->info.resMinY = _2btol(jbuf.minyres); + + /* set the length of the list to zero first, then append standard resolutions */ + dev->info.resStdList[0] = 0; + if (jbuf.standardres[0] & 0x80) appendStdList(&dev->info, 60); + if (jbuf.standardres[0] & 0x40) appendStdList(&dev->info, 75); + if (jbuf.standardres[0] & 0x20) appendStdList(&dev->info, 100); + if (jbuf.standardres[0] & 0x10) appendStdList(&dev->info, 120); + if (jbuf.standardres[0] & 0x08) appendStdList(&dev->info, 150); + if (jbuf.standardres[0] & 0x04) appendStdList(&dev->info, 160); + if (jbuf.standardres[0] & 0x02) appendStdList(&dev->info, 180); + if (jbuf.standardres[0] & 0x01) appendStdList(&dev->info, 200); + if (jbuf.standardres[1] & 0x80) appendStdList(&dev->info, 240); + if (jbuf.standardres[1] & 0x40) appendStdList(&dev->info, 300); + if (jbuf.standardres[1] & 0x20) appendStdList(&dev->info, 320); + if (jbuf.standardres[1] & 0x10) appendStdList(&dev->info, 400); + if (jbuf.standardres[1] & 0x08) appendStdList(&dev->info, 480); + if (jbuf.standardres[1] & 0x04) appendStdList(&dev->info, 600); + if (jbuf.standardres[1] & 0x02) appendStdList(&dev->info, 800); + if (jbuf.standardres[1] & 0x01) appendStdList(&dev->info, 1200); + if (dev->info.resStdList[0] == 0) + { + /* make a default standard resolutions for 200 and 300dpi */ + DBG(1, "attach: no standard resolutions reported\n"); + dev->info.resStdList[0] = 2; + dev->info.resStdList[1] = 200; + dev->info.resStdList[2] = 300; + dev->info.resBasicX = dev->info.resBasicY = 300; + } + + dev->info.winWidth = _4btol(jbuf.windowwidth); + dev->info.winHeight = _4btol(jbuf.windowlength); + + if (dev->info.winWidth <= 0) + { + dev->info.winWidth = (SANE_Int) (dev->info.resBasicX * 8.5); + DBG(1, "attach: invalid window width reported, using %d\n", dev->info.winWidth); + } + if (dev->info.winHeight <= 0) + { + dev->info.winHeight = dev->info.resBasicY * 14; + DBG(1, "attach: invalid window height reported, using %d\n", dev->info.winHeight); + } + + mm = (dev->info.resBasicX > 0) ? + ((double) dev->info.winWidth / (double) dev->info.resBasicX * MM_PER_INCH) : + 0.0; + dev->info.x_range.min = SANE_FIX(0.0); + dev->info.x_range.max = SANE_FIX(mm); + dev->info.x_range.quant = SANE_FIX(0.0); + + mm = (dev->info.resBasicY > 0) ? + ((double) dev->info.winHeight / (double) dev->info.resBasicY * MM_PER_INCH) : + 0.0; + dev->info.y_range.min = SANE_FIX(0.0); + dev->info.y_range.max = SANE_FIX(mm); + dev->info.y_range.quant = SANE_FIX(0.0); + + /* set additional discovered/guessed capabilities */ + + /* if all of the ACE capabilities are present, declare it ACE capable */ + dev->info.canACE = dev->info.canEdgeExtract && + dev->info.canNoiseFilter && + dev->info.canSmooth && + dev->info.canLineBold; + + /* if the model is known to be a duplex, declare it duplex capable */ + if (strcmp(dev->info.product, "COPISCAN II 6338") == 0) + { + dev->info.canDuplex = SANE_TRUE; + } + else + { + dev->info.canDuplex = SANE_FALSE; + } + + /* the paper sensor requires RSC revision 1.4 or higher and an + * installed feeder. NOTE: It also requires SW-4 on and the + * AccufeedPlus feeder, but we cannot discover that. + */ + if (strcmp(dev->info.revision, "0014") >= 0) + { + dev->info.canCheckADF = dev->info.canADF; + } + else + { + dev->info.canCheckADF = SANE_FALSE; + } + + /* set option defaults based on inquiry information */ + dev->info.res_default = dev->info.resBasicX; + dev->info.autoborder_default = dev->info.canBorderRecog; + dev->info.batch_default = SANE_FALSE; + dev->info.deskew_default = SANE_FALSE; + dev->info.check_adf_default = SANE_FALSE; + dev->info.duplex_default = SANE_FALSE; + dev->info.timeout_adf_default = 0; + dev->info.timeout_manual_default = 0; + dev->info.control_panel_default = dev->info.canACE; + + ++num_devices; + dev->next = first_dev; + first_dev = dev; + + if (devp) + *devp = dev; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +attach_one(const char *devnam) +{ + attach (devnam, NULL); + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_init (SANE_Int *version_code, SANE_Auth_Callback authorize) +{ + char devnam[PATH_MAX] = "/dev/scanner"; + FILE *fp; + + authorize = authorize; /* get rid of compiler warning */ + + DBG_INIT(); + DBG(3, "sane_init called\n"); + DBG(1, "Bell+Howell SANE backend %d.%d build %d %s endian\n", + SANE_CURRENT_MAJOR, V_MINOR, BUILD, + _is_host_little_endian() ? "little" : "big"); + + if (version_code) + *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD); + + fp = sanei_config_open(BH_CONFIG_FILE); + if (fp) + { + char line[PATH_MAX]; + const char *lp; + size_t len; + + /* read config file */ + while (sanei_config_read (line, sizeof (line), fp)) + { + if (line[0] == '#') /* ignore line comments */ + continue; + len = strlen (line); + + if (!len) + continue; /* ignore empty lines */ + + lp = sanei_config_skip_whitespace (line); + + DBG(16, + "sane_init: processing config file line '%s'\n", + line); + if (strncmp(lp, "option", 6) == 0 && + (isspace (lp[6]) || lp[6] == '\0')) + { + lp += 6; + lp = sanei_config_skip_whitespace (lp); + + if (strncmp(lp, "disable-optional-frames", 23) == 0) + { + DBG(1, "sane_init: configuration option " + "'disable-optional-frames' set\n"); + disable_optional_frames = 1; + } + else if (strncmp(lp, "fake-inquiry", 12) == 0) + { + DBG(1, "sane_init: configuration option " + "'fake-inquiry' set\n"); + fake_inquiry = 1; + } + else + { + DBG(1, "sane_init: ignoring unknown " + "configuration option '%s'\n", + lp); + } + } + else + { + DBG(16, + "sane_init: found a device: line '%s'\n", + lp); + strncpy (devnam, lp, sizeof(devnam)); + devnam[sizeof(devnam)-1] = '\0'; + + sanei_config_attach_matching_devices(devnam, + attach_one); + } + } + fclose (fp); + } + else + { + /* configure the /dev/scanner device in the absence of config file */ + sanei_config_attach_matching_devices ("/dev/scanner", attach_one); + } + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_get_devices (const SANE_Device ***device_list, SANE_Bool local) +{ + static const SANE_Device **devlist = 0; + BH_Device *dev; + int i; + DBG(3, "sane_get_devices called\n"); + + local = local; /* get rid of compiler warning */ + if (devlist) + free (devlist); + devlist = malloc ((num_devices + 1) * sizeof (devlist[0])); + if (!devlist) + return SANE_STATUS_NO_MEM; + + i = 0; + for (dev = first_dev; dev; dev = dev->next) + devlist[i++] = &dev->sane; + devlist[i++] = 0; + + *device_list = devlist; + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_open (SANE_String_Const devnam, SANE_Handle *handle) +{ + SANE_Status status; + BH_Device *dev; + BH_Scanner *s; + DBG(3, "sane_open called\n"); + + if (devnam[0] != '\0') + { + for (dev = first_dev; dev; dev = dev->next) + { + if (strcmp (dev->sane.name, devnam) == 0) + break; + } + + if (!dev) + { + status = attach (devnam, &dev); + if (status != SANE_STATUS_GOOD) + return status; + } + } + else + { + dev = first_dev; + } + + if (!dev) + return SANE_STATUS_INVAL; + + s = malloc (sizeof (*s)); + if (!s) + return SANE_STATUS_NO_MEM; + memset (s, 0, sizeof (*s)); + + s->fd = -1; + s->hw = dev; + + s->bmu = BH_UNIT_POINT; + s->mud = 1; + + ScannerDump(s); + + init_options (s); + + s->next = first_handle; + first_handle = s; + + /* initialize our parameters */ + get_parameters(s, 0); + + *handle = s; + +#ifdef FAKE_INQUIRY + if (fake_inquiry) + { + DBG (1, "sane_open: faking open of %s\n", + s->hw->sane.name); + } + else +#endif + { + status = sanei_scsi_open (s->hw->sane.name, &s->fd, sense_handler, s); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_open: open of %s failed: %s\n", + s->hw->sane.name, sane_strstatus (status)); + return status; + } + } + + return SANE_STATUS_GOOD; +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + BH_Scanner *s = handle; + DBG(3, "sane_get_option_descriptor called (option:%d)\n", option); + + if ((unsigned) option >= NUM_OPTIONS) + return 0; + + return (s->opt + option); +} + +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action, + void *val, SANE_Word *info) +{ + BH_Scanner *s = handle; + SANE_Status status; + SANE_Word cap; + SANE_String_Const name; + + DBG(3, "sane_control_option called\n"); + + name = s->opt[option].name ? s->opt[option].name : "(nil)"; + + if (info) + *info = 0; + + if (s->scanning && action == SANE_ACTION_SET_VALUE) + return SANE_STATUS_DEVICE_BUSY; + if (option >= NUM_OPTIONS) + return SANE_STATUS_INVAL; + + cap = s->opt[option].cap; + if (!SANE_OPTION_IS_ACTIVE (cap)) + return SANE_STATUS_INVAL; + + if (action == SANE_ACTION_GET_VALUE) + { + DBG(16, "sane_control_option: get_value %s [#%d]\n", name, option); + switch (option) + { + /* word options: */ + case OPT_RESOLUTION: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + case OPT_TIMEOUT_ADF: + case OPT_TIMEOUT_MANUAL: + case OPT_ACE_FUNCTION: + case OPT_ACE_SENSITIVITY: + case OPT_BRIGHTNESS: + case OPT_THRESHOLD: + case OPT_CONTRAST: + case OPT_ICON_WIDTH: + case OPT_ICON_LENGTH: + case OPT_BARCODE_SEARCH_COUNT: + case OPT_BARCODE_HMIN: + case OPT_BARCODE_SEARCH_TIMEOUT: + case OPT_BARCODE_RELMAX: + case OPT_BARCODE_BARMIN: + case OPT_BARCODE_BARMAX: + case OPT_BARCODE_CONTRAST: + case OPT_BARCODE_PATCHMODE: + case OPT_NUM_OPTS: + *(SANE_Word *) val = s->val[option].w; + return SANE_STATUS_GOOD; + + /* string options: */ + case OPT_INQUIRY: + case OPT_SCAN_SOURCE: + case OPT_SCAN_MODE: + case OPT_COMPRESSION: + case OPT_PAPER_SIZE: + case OPT_ROTATION: + case OPT_BARCODE_SEARCH_BAR: + case OPT_BARCODE_SEARCH_MODE: + case OPT_SECTION: + strcpy (val, s->val[option].s); + return SANE_STATUS_GOOD; + + /* boolean options: */ + case OPT_PREVIEW: + case OPT_AUTOBORDER: + case OPT_DESKEW: + case OPT_BATCH: + case OPT_CHECK_ADF: + case OPT_DUPLEX: + case OPT_CONTROL_PANEL: + case OPT_NEGATIVE: + *(SANE_Word *) val = s->val[option].w; + return SANE_STATUS_GOOD; + + default: + DBG(1, "sane_control_option:invalid option number %d\n", option); + return SANE_STATUS_INVAL; + } + } + else if (action == SANE_ACTION_SET_VALUE) + { + switch (s->opt[option].type) + { + case SANE_TYPE_BOOL: + case SANE_TYPE_INT: + DBG(16, "sane_control_option: set_value %s [#%d] to %d\n", + name, option, *(SANE_Word *) val); + break; + + case SANE_TYPE_FIXED: + DBG(16, "sane_control_option: set_value %s [#%d] to %f\n", + name, option, SANE_UNFIX(*(SANE_Word *) val)); + break; + + case SANE_TYPE_STRING: + DBG(16, "sane_control_option: set_value %s [#%d] to %s\n", + name, option, (char *) val); + break; + + default: + DBG(16, "sane_control_option: set_value %s [#%d]\n", + name, option); + } + + if (!SANE_OPTION_IS_SETTABLE (cap)) + return SANE_STATUS_INVAL; + + status = sanei_constrain_value (s->opt + option, val, info); + if (status != SANE_STATUS_GOOD) + return status; + + switch (option) + { + /* (mostly) side-effect-free word options: */ + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + /* make sure that paper-size is set to custom */ + if (s->val[option].w != *(SANE_Word *) val) + { + if (info) *info |= SANE_INFO_RELOAD_PARAMS; + + if (get_paper_id(_OPT_VAL_STRING(s, OPT_PAPER_SIZE)) != 0) + { + if (info) *info |= SANE_INFO_RELOAD_OPTIONS; + + /* set paper size to 'custom' */ + free (s->val[OPT_PAPER_SIZE].s); + s->val[OPT_PAPER_SIZE].s = strdup(paper_list[0]); + } + } + /* fall through */ + case OPT_RESOLUTION: + if (info && s->val[option].w != *(SANE_Word *) val) + *info |= SANE_INFO_RELOAD_PARAMS; + /* fall through */ + case OPT_TIMEOUT_ADF: + case OPT_TIMEOUT_MANUAL: + case OPT_ACE_FUNCTION: + case OPT_ACE_SENSITIVITY: + case OPT_BRIGHTNESS: + case OPT_THRESHOLD: + case OPT_CONTRAST: + case OPT_ICON_WIDTH: + case OPT_ICON_LENGTH: + case OPT_BARCODE_SEARCH_COUNT: + case OPT_BARCODE_HMIN: + case OPT_BARCODE_SEARCH_TIMEOUT: + case OPT_BARCODE_RELMAX: + case OPT_BARCODE_BARMIN: + case OPT_BARCODE_BARMAX: + case OPT_BARCODE_CONTRAST: + case OPT_BARCODE_PATCHMODE: + case OPT_NUM_OPTS: + s->val[option].w = *(SANE_Word *) val; + return SANE_STATUS_GOOD; + + /* string options */ + case OPT_BARCODE_SEARCH_BAR: + /*!!! we're supporting only a single barcode type via the option */ + s->search_bars[0] = get_barcode_id(val); + /* fall through */ + case OPT_SCAN_SOURCE: + case OPT_COMPRESSION: + case OPT_ROTATION: + case OPT_BARCODE_SEARCH_MODE: + case OPT_SECTION: + if (s->val[option].s) + free (s->val[option].s); + s->val[option].s = strdup (val); + return SANE_STATUS_GOOD; + + /* boolean options: */ + case OPT_AUTOBORDER: + /*!!! autoborder true disables geometry controls + * and sets them to defaults? + */ + /* fall through */ + case OPT_PREVIEW: + case OPT_BATCH: + case OPT_DESKEW: + case OPT_CHECK_ADF: + case OPT_DUPLEX: + case OPT_NEGATIVE: + s->val[option].w = *(SANE_Word *) val; + return SANE_STATUS_GOOD; + + /* options with side effects */ + case OPT_CONTROL_PANEL: + /* a boolean option */ + /* control-panel true enables/disables some enhancement controls */ + if (s->val[option].w != *(SANE_Word *) val) + { + if (info) *info |= SANE_INFO_RELOAD_OPTIONS; + + s->val[option].w = *(SANE_Word *) val; + + if (*(SANE_Word *) val == SANE_TRUE) + { + if (s->hw->info.canACE == SANE_TRUE) + { + s->opt[OPT_ACE_FUNCTION].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ACE_SENSITIVITY].cap |= SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE; + } + } + else + { + if (s->hw->info.canACE == SANE_TRUE) + { + s->opt[OPT_ACE_FUNCTION].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_ACE_SENSITIVITY].cap &= ~SANE_CAP_INACTIVE; + } + else + { + s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE; + } + } + } + return SANE_STATUS_GOOD; + + case OPT_SCAN_MODE: + /* a string option */ + /* scan mode != lineart disables compression, setting it to + * 'none' + */ + if (strcmp (s->val[option].s, (SANE_String) val)) + { + if (info) *info |= SANE_INFO_RELOAD_OPTIONS; + if (get_scan_mode_id((SANE_String) val) != 0) + { + /* scan mode is not lineart, disable compression + * and set compression to 'none' + */ + s->opt[OPT_COMPRESSION].cap |= SANE_CAP_INACTIVE; + if (s->val[OPT_COMPRESSION].s && + get_compression_id(s->val[OPT_COMPRESSION].s) != 0) + { + free (s->val[OPT_COMPRESSION].s); + s->val[OPT_COMPRESSION].s = strdup(compression_list[0]); + } + } + else + { + /* scan mode is lineart, enable compression */ + s->opt[OPT_COMPRESSION].cap &= ~SANE_CAP_INACTIVE; + } + free (s->val[option].s); + s->val[option].s = strdup (val); + } + return SANE_STATUS_GOOD; + + case OPT_PAPER_SIZE: + /* a string option */ + /* changes geometry options, therefore _RELOAD_PARAMS and _RELOAD_OPTIONS */ + if (strcmp (s->val[option].s, (SANE_String) val)) + { + SANE_Int paper_id = get_paper_id((SANE_String) val); + + /* paper_id 0 is a special case (custom) that + * disables the paper size control of geometry + */ + if (paper_id != 0) + { + double left, x_max, y_max, x, y; + + x_max = SANE_UNFIX(s->hw->info.x_range.max); + y_max = SANE_UNFIX(s->hw->info.y_range.max); + /* a dimension of 0.0 (or less) is replaced with the max value */ + x = (paper_sizes[paper_id].width <= 0.0) ? x_max : + paper_sizes[paper_id].width; + y = (paper_sizes[paper_id].length <= 0.0) ? y_max : + paper_sizes[paper_id].length; + + if (info) *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + + /* set geometry options based on paper size */ + /* set geometry options based on paper size */ + if (s->hw->info.canADF) + { + /* when the feeder is used the paper is centered in the + * hopper; with the manual feed it is aligned left. + */ + left = (x_max - x) / 2.0; + if (left < 0.0) left = 0.0; + } + else + { + left = 0.0; + } + + s->val[OPT_TL_X].w = SANE_FIX(left); + s->val[OPT_TL_Y].w = SANE_FIX(0.0); + s->val[OPT_BR_X].w = SANE_FIX(MIN(x + left, x_max)); + s->val[OPT_BR_Y].w = SANE_FIX(MIN(y, y_max)); + } + free (s->val[option].s); + s->val[option].s = strdup (val); + } + return SANE_STATUS_GOOD; + + default: + DBG(1, "sane_control_option:invalid option number %d\n", option); + return SANE_STATUS_INVAL; + } + } + + return SANE_STATUS_INVAL; +} + +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters *params) +{ + BH_Scanner *s = handle; + SANE_Int status = SANE_STATUS_GOOD; + + DBG(3, "sane_get_parameters called\n"); + + if (params) + { + SANE_Int res; + + if (!s->scanning) + { + /* update our parameters ONLY if we're not scanning */ + status = get_parameters(s, 0); + } + + *params = s->params; + + res = _OPT_VAL_WORD(s, OPT_RESOLUTION); + + DBG (1, "get_parameters: format=%d, pixels/line=%d, bytes/line=%d, " + "lines=%d, dpi=%d\n", + (int) s->params.format, + s->params.pixels_per_line, + s->params.bytes_per_line, + s->params.lines, + res); + } + + return status; +} + +SANE_Status +sane_start (SANE_Handle handle) +{ + BH_Scanner *s = handle; + SANE_Status status; + + DBG(3, "sane_start called\n"); + s->cancelled = SANE_FALSE; + + if (s->scanning == SANE_FALSE) + { + /* get preliminary parameters */ + status = get_parameters (s, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_start: get_parameters failed: %s\n", + sane_strstatus (status)); + return status; + } + + /* Do the setup once per 'batch'. The SANE standard requires the + * frontend to call sane_cancel once all desired frames have been + * acquired. That is when scanning is set back to SANE_FALSE and + * the 'batch' is considered done. + */ + status = start_setup (s); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_start: start_setup failed: %s\n", + sane_strstatus (status)); + return status; + } + } + + status = start_scan (s); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_start: start_scan failed: %s\n", + sane_strstatus (status)); + return status; + } + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *len) +{ + BH_Scanner *s = handle; + SANE_Status status; + size_t nread; + + DBG(3, "sane_read called\n"); + + *len = 0; + + if (s->cancelled) { + DBG (3, "sane_read: cancelled!\n"); + return SANE_STATUS_CANCELLED; + } + + if (!s->scanning) { + DBG (3, "sane_read: scanning is false!\n"); + sane_cancel(s); + return SANE_STATUS_CANCELLED; + } + + nread = maxlen; + + DBG (3, "sane_read: request %lu bytes\n", (u_long) nread); + /* set InvalidBytes to 0 before read; sense_handler will set it + * to non-zero if we do the last partial read. + */ + s->InvalidBytes = 0; + status = read_data (s, buf, &nread); + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_read: read_data failed %s\n", + sane_strstatus(status)); + sane_cancel (s); + return status; + } + nread = maxlen - s->InvalidBytes; + DBG (3, "sane_read: got %lu bytes\n", (u_long) nread); + *len = nread; + + return (maxlen != 0 && nread == 0) ? SANE_STATUS_EOF : SANE_STATUS_GOOD; +} + +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ +#ifdef NONBLOCKSUPPORTED + BH_Scanner *s = handle; +#endif + DBG(3, "sane_set_io_mode called: non_blocking=%d\n", non_blocking); + +#ifdef NONBLOCKSUPPORTED + if (s->fd < 0) + { + return SANE_STATUS_INVAL; + } + + if (fcntl (s->fd, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0) + { + DBG(1, "sane_set_io_mode: error setting io mode\n"); + return SANE_STATUS_IO_ERROR; + } + + return SANE_STATUS_GOOD; +#else + handle = handle; /* get rid of compiler warning */ + return (non_blocking == 1) ? SANE_STATUS_UNSUPPORTED : SANE_STATUS_GOOD; +#endif +} + +SANE_Status +sane_get_select_fd (SANE_Handle handle, SANE_Int *fd) +{ +#ifdef NONBLOCKSUPPORTED + BH_Scanner *s = handle; +#endif + DBG(3, "sane_get_select_fd called\n"); + +#ifdef NONBLOCKSUPPORTED + if (s->fd < 0) + { + return SANE_STATUS_INVAL; + } + *fd = s->fd; + + return SANE_STATUS_GOOD; +#else + handle = handle; fd = fd; /* get rid of compiler warning */ + return SANE_STATUS_UNSUPPORTED; +#endif +} + +void +sane_cancel (SANE_Handle handle) +{ + BH_Scanner *s = (BH_Scanner *) handle; + DBG(3, "sane_cancel called\n"); + if (s->scanning) + { + /* if batchmode is enabled, then call set_window to + * abort the batch + */ + if (_OPT_VAL_WORD(s, OPT_BATCH) == SANE_TRUE) + { + DBG(5, "sane_cancel: calling set_window to abort batch\n"); + set_window(s, BH_BATCH_ABORT); + } + } + s->scanning = SANE_FALSE; + s->cancelled = SANE_TRUE; +} + +void +sane_close (SANE_Handle handle) +{ + BH_Scanner *s = (BH_Scanner *) handle; + DBG(3, "sane_close called\n"); + + if (s->fd != -1) + sanei_scsi_close (s->fd); + s->fd = -1; + free (s); +} + +void +sane_exit (void) +{ + BH_Device *dev, *next; + DBG(3, "sane_exit called\n"); + + for (dev = first_dev; dev; dev = next) + { + next = dev->next; + free (dev); + } +} + |