From 663f405c1b64aa4adc09112b2c27cf90ebe366e2 Mon Sep 17 00:00:00 2001 From: Peter Fales Date: Fri, 7 Sep 2001 02:40:21 +0000 Subject: [PATCH] Initial version of gphoto2 backend --- backend/gphoto2.c | 1460 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1460 insertions(+) create mode 100644 backend/gphoto2.c diff --git a/backend/gphoto2.c b/backend/gphoto2.c new file mode 100644 index 000000000..077c7f558 --- /dev/null +++ b/backend/gphoto2.c @@ -0,0 +1,1460 @@ +/*************************************************************************** + * _S_A_N_E - Scanner Access Now Easy. + + gphoto2.c + + 03/12/01 - Peter Fales + + Based on the dc210 driver, (C) 1998 Brian J. Murrell (which is + based on dc25 driver (C) 1998 by Peter Fales) + + This file (C) 2001 by Peter Fales + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + + *************************************************************************** + + This file implements a SANE backend for the Kodak DC-240 + digital camera. THIS IS EXTREMELY ALPHA CODE! USE AT YOUR OWN RISK!! + + (feedback to: peter@fales.com + + This backend is based somewhat on the dc25 backend included in this + package by Peter Fales, and the dc210 backend by Brian J. Murrell + + ***************************************************************************/ + +#include "sane/config.h" + +#include +#include +#include +#include +#include +#include +#include "cdjpeg.h" +#include + +#include "sane/sane.h" +#include "sane/sanei.h" +#include "sane/saneopts.h" + +#define BACKEND_NAME gphoto2 +#include "sane/sanei_backend.h" + +#include "gphoto2.h" + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +#define MAGIC (void *)0xab730324 +#define GPHOTO2_CONFIG_FILE "gphoto2.conf" + +# define DEFAULT_TTY "usb:" + +static SANE_Bool is_open = 0; + +static SANE_Bool gphoto2_opt_thumbnails; +static SANE_Bool gphoto2_opt_snap; +static SANE_Bool gphoto2_opt_lowres; +static SANE_Bool gphoto2_opt_erase; +static SANE_Bool gphoto2_opt_autoinc; +static SANE_Bool dumpinquiry; + +static struct jpeg_decompress_struct cinfo; +static djpeg_dest_ptr dest_mgr = NULL; + +static SANE_Int highres_height = 960, highres_width = 1280; +static SANE_Int lowres_height = 480, lowres_width = 640; +static SANE_Int thumb_height = 120, thumb_width = 160; +static SANE_String TopFolder; +static SANE_String Gphoto2Path = "gphoto2"; + +static GPHOTO2 Camera; + +static SANE_Range image_range = { + 0, + 0, + 0 +}; + +static SANE_String **folder_list; +static SANE_Int current_folder = 0; + +static SANE_Option_Descriptor sod[] = { + { + SANE_NAME_NUM_OPTIONS, + SANE_TITLE_NUM_OPTIONS, + SANE_DESC_NUM_OPTIONS, + SANE_TYPE_INT, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_IMAGE_SELECTION 1 + { + "", + "Image Selection", + "Selection of the image to load.", + SANE_TYPE_GROUP, + SANE_UNIT_NONE, + 0, + 0, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_FOLDER 2 + { + "folder", + "Folder", + "Select folder within camera", + SANE_TYPE_STRING, + SANE_UNIT_NONE, + 256, + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_STRING_LIST, + {NULL} + } + , + +#define GPHOTO2_OPT_IMAGE_NUMBER 3 + { + "image", + "Image Number", + "Select Image Number to load from camera", + SANE_TYPE_INT, + SANE_UNIT_NONE, + 4, + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_RANGE, + {(SANE_String_Const *) & image_range} /* this is ANSI conformant! */ + } + , + +#define GPHOTO2_OPT_THUMBS 4 + { + "thumbs", + "Load Thumbnail", + "Load the image as thumbnail.", + SANE_TYPE_BOOL, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_SNAP 5 + { + "snap", + "Snap new picture", + "Take new picture and download it", + SANE_TYPE_BOOL, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT /* | SANE_CAP_ADVANCED */ , + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_LOWRES 6 + { + "lowres", + "Low Resolution", + "Resolution of new picture or selected image (must be manually specified)", + SANE_TYPE_BOOL, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT + /* | SANE_CAP_ADVANCED */ , + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_ERASE 7 + { + "erase", + "Erase", + "Erase the picture after downloading", + SANE_TYPE_BOOL, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_DEFAULT 8 + { + "default-enhancements", + "Defaults", + "Set default values for enhancement controls.", + SANE_TYPE_BUTTON, + SANE_UNIT_NONE, + 0, + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_INIT_GPHOTO2 9 + { + "camera-init", + "Re-establish Communications", + "Re-establish communications with camera (in case of timeout, etc.)", + SANE_TYPE_BUTTON, + SANE_UNIT_NONE, + 0, + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + +#define GPHOTO2_OPT_AUTOINC 10 + { + "autoinc", + "Auto Increment", + "Increment image number after each scan", + SANE_TYPE_BOOL, + SANE_UNIT_NONE, + sizeof (SANE_Word), + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED, + SANE_CONSTRAINT_NONE, + {NULL} + } + , + + +}; + +static SANE_Parameters parms = { + SANE_FRAME_RGB, + 0, + 0, /* Number of bytes returned per scan line: */ + 0, /* Number of pixels per scan line. */ + 0, /* Number of lines for the current scan. */ + 8, /* Number of bits per sample. */ +}; + +static struct cam_dirlist *dir_head = NULL; + +static SANE_Byte *linebuffer = NULL; +static SANE_Int linebuffer_size = 0; +static SANE_Int linebuffer_index = 0; + +static SANE_Byte tmpstr[256]; +static FILE *cmdpipe; + +static SANE_Byte name_buf[60]; + +#include +#include + +static SANE_Int +init_gphoto2 (void) +{ + SANE_Char cmdbuf[256]; + SANE_Int entries, n; + + DBG (1, "GPHOTO2 Backend 05/16/01\n"); + + sprintf ((char *) cmdbuf, "%s --camera \"%s\" --port %s --list-folders -q", + (char *) Gphoto2Path, (char *) Camera.camera_name, + (char *) Camera.tty_name); + + cmdpipe = popen ((char *) cmdbuf, "r"); + if (cmdpipe == NULL) + { + DBG (0, "%s: error: couldn't open gphoto2", "init_gphoto2"); + exit (1); + } + + n = fscanf (cmdpipe, "%d", &entries); + if (n == 0) + { + return -1; + } + fclose (cmdpipe); + + return SANE_STATUS_GOOD; +} + +static void +close_gphoto2 (SANE_Int fd) +{ + /* + * Put the camera back to 9600 baud + */ + + if (close (fd) == -1) + { + DBG (1, "close_gphoto2: error: could not close device\n"); + } +} + +int +get_info (void) +{ + + SANE_Int n; + struct cam_dirlist *e; + + if (Camera.pic_taken == 0) + { + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap |= SANE_CAP_INACTIVE; + image_range.min = 0; + image_range.max = 0; + } + else + { + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE; + image_range.min = 1; + image_range.max = Camera.pic_taken; + } + + n = read_dir (TopFolder, 0); + + /* If we've already got a folder_list, free it up before starting + * the new one + */ + if (folder_list != NULL) + { + int tmp; + for (tmp = 0; folder_list[tmp]; tmp++) + { + free (folder_list[tmp]); + } + free (folder_list); + } + + folder_list = (SANE_String * *)malloc ((n + 1) * sizeof (SANE_String *)); + for (e = dir_head, n = 0; e; e = e->next, n++) + { + folder_list[n] = (SANE_String *) strdup (e->name); + if (strchr ((char *) folder_list[n], ' ')) + { + *strchr ((char *) folder_list[n], ' ') = '\0'; + } + } + if (n == 0) + { + folder_list[n++] = (SANE_String *) strdup (""); + } + + folder_list[n] = NULL; + sod[GPHOTO2_OPT_FOLDER].constraint.string_list = + (SANE_String_Const *) folder_list; + + Camera.pic_taken = 0; + Camera.pic_left = 1; /* Just a guess! */ + + return 0; + +} + +static SANE_Int +erase (void) +{ + return -1; +} + +static SANE_Int +change_res (SANE_Byte res) +{ + + return 0; + +} + +SANE_Status +sane_init (SANE_Int * version_code, SANE_Auth_Callback UNUSEDARG authorize) +{ + + SANE_Char f[] = "sane_init"; + SANE_Char dev_name[PATH_MAX], *p; + SANE_Char buf[256]; + size_t len; + FILE *fp; + + DBG_INIT (); + + if (version_code) + *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0); + + fp = sanei_config_open (GPHOTO2_CONFIG_FILE); + + /* defaults */ + Camera.tty_name = DEFAULT_TTY; + + if (!fp) + { + /* default to /dev/whatever instead of insisting on config file */ + DBG (1, "%s: missing config file '%s'\n", f, GPHOTO2_CONFIG_FILE); + } + else + { + while (sanei_config_read (dev_name, sizeof (dev_name), fp)) + { + dev_name[sizeof (dev_name) - 1] = '\0'; + DBG (20, "%s: config- %s\n", f, dev_name); + + if (dev_name[0] == '#') + continue; /* ignore line comments */ + len = strlen (dev_name); + if (!len) + continue; /* ignore empty lines */ + if (strncmp (dev_name, "port=", 5) == 0) + { + p = dev_name + 5; + if (p) + Camera.tty_name = strdup (p); + DBG (20, "Config file port=%s\n", Camera.tty_name); + } + else if (strncmp (dev_name, "camera=", 7) == 0) + { + Camera.camera_name = strdup (dev_name + 7); + DBG (20, "Config file camera=%s\n", Camera.camera_name); + sprintf (buf, "Image selection - %s", Camera.camera_name); + sod[GPHOTO2_OPT_IMAGE_SELECTION].title = strdup (buf); + } + else if (strcmp (dev_name, "dumpinquiry") == 0) + { + dumpinquiry = SANE_TRUE; + } + else if (strncmp (dev_name, "high_resolution=", 16) == 0) + { + sscanf (&dev_name[16], "%dx%d", &highres_width, + &highres_height); + DBG (20, "Config file high_resolution=%ux%u\n", highres_width, + highres_height); + } + else if (strncmp (dev_name, "low_resolution=", 15) == 0) + { + sscanf (&dev_name[15], "%dx%d", &lowres_width, &lowres_height); + DBG (20, "Config file low_resolution=%ux%u\n", lowres_width, + lowres_height); + } + else if (strncmp (dev_name, "thumb_resolution=", 17) == 0) + { + sscanf (&dev_name[17], "%dx%d", &thumb_width, &thumb_height); + DBG (20, "Config file thumb_resolution=%ux%u\n", thumb_width, + thumb_height); + } + else if (strncmp (dev_name, "topfolder=", 10) == 0) + { + TopFolder = strdup (&dev_name[10]); + DBG (20, "Config file topfolder=%s\n", TopFolder); + } + else if (strncmp (dev_name, "gphoto2path=", 12) == 0) + { + Gphoto2Path = strdup (&dev_name[12]); + DBG (20, "Config file gphoto2path=%s\n", Gphoto2Path); + } + } + fclose (fp); + } + + if (init_gphoto2 () == -1) + return SANE_STATUS_INVAL; + + if (get_info () == -1) + { + DBG (1, "error: could not get info\n"); + close_gphoto2 (Camera.fd); + return SANE_STATUS_INVAL; + } + + /* load the current images array */ + get_pictures_info (); + + if (Camera.pic_taken == 0) + { + Camera.current_picture_number = 0; + parms.bytes_per_line = 0; + parms.pixels_per_line = 0; + parms.lines = 0; + } + else + { + Camera.current_picture_number = 1; + set_res (Camera.Pictures[Camera.current_picture_number - 1].low_res); + } + + if (dumpinquiry) + { + DBG (0, "\nCamera information:\n~~~~~~~~~~~~~~~~~\n\n"); + DBG (0, "Model...........: DC%s\n", "240"); + DBG (0, "Firmware version: %d.%d\n", Camera.ver_major, + Camera.ver_minor); + DBG (0, "Pictures........: %d/%d\n", Camera.pic_taken, + Camera.pic_taken + Camera.pic_left); + DBG (0, "Battery state...: %s\n", + Camera.flags.low_batt == 0 ? "good" : (Camera.flags.low_batt == + 1 ? "weak" : "empty")); + } + + return SANE_STATUS_GOOD; +} + +void +sane_exit (void) +{ +} + +/* Device select/open/close */ + +static const SANE_Device dev[] = { + { + "0", + "Kodak", + "DC-240", + "still camera"}, +}; + +static const SANE_Device *devlist[] = { + dev + 0, 0 +}; + +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool + UNUSEDARG local_only) +{ + + DBG (127, "sane_get_devices called\n"); + + *device_list = devlist; + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_open (SANE_String_Const devicename, SANE_Handle * handle) +{ + SANE_Int i; + + DBG (127, "sane_open for device %s\n", devicename); + if (!devicename[0]) + { + i = 0; + } + else + { + for (i = 0; i < NELEMS (dev); ++i) + { + if (strcmp (devicename, dev[i].name) == 0) + { + break; + } + } + } + + if (i >= NELEMS (dev)) + { + return SANE_STATUS_INVAL; + } + + if (is_open) + { + return SANE_STATUS_DEVICE_BUSY; + } + + is_open = 1; + *handle = MAGIC; + + DBG (4, "sane_open: pictures taken=%d\n", Camera.pic_taken); + + return SANE_STATUS_GOOD; +} + +void +sane_close (SANE_Handle handle) +{ + DBG (127, "sane_close called\n"); + if (handle == MAGIC) + is_open = 0; + + DBG (127, "sane_close returning\n"); +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + if (handle != MAGIC || !is_open) + return NULL; /* wrong device */ + if (option < 0 || option >= NELEMS (sod)) + return NULL; + return &sod[option]; +} + +static SANE_Int myinfo = 0; + +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, + SANE_Action action, void *value, SANE_Int * info) +{ + SANE_Status status; + + DBG (127, "control_option(handle=%p,opt=%s,act=%s,val=%p,info=%p)\n", + handle, sod[option].title, + (action == + SANE_ACTION_SET_VALUE ? "SET" : (action == + SANE_ACTION_GET_VALUE ? "GET" : + "SETAUTO")), value, info); + + if (handle != MAGIC || !is_open) + return SANE_STATUS_INVAL; /* Unknown handle ... */ + + if (option < 0 || option >= NELEMS (sod)) + return SANE_STATUS_INVAL; /* Unknown option ... */ + + switch (action) + { + case SANE_ACTION_SET_VALUE: + status = sanei_constrain_value (sod + option, value, &myinfo); + if (status != SANE_STATUS_GOOD) + { + DBG (2, "Constraint error in control_option\n"); + return status; + } + + switch (option) + { + case GPHOTO2_OPT_IMAGE_NUMBER: + Camera.current_picture_number = *(SANE_Word *) value; + myinfo |= SANE_INFO_RELOAD_PARAMS; + + /* get the image's resolution, unless the camera has no + * pictures yet + */ + if (Camera.pic_taken != 0) + { + set_res (Camera. + Pictures[Camera.current_picture_number - 1].low_res); + } + break; + + case GPHOTO2_OPT_THUMBS: + gphoto2_opt_thumbnails = !!*(SANE_Word *) value; + myinfo |= SANE_INFO_RELOAD_PARAMS; + + if (Camera.pic_taken != 0) + { + set_res (Camera. + Pictures[Camera.current_picture_number - 1].low_res); + } + break; + + case GPHOTO2_OPT_SNAP: + gphoto2_opt_snap = !!*(SANE_Word *) value; + myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + /* if we are snapping a new one */ + if (gphoto2_opt_snap) + { + /* activate the resolution setting */ + sod[GPHOTO2_OPT_LOWRES].cap &= ~SANE_CAP_INACTIVE; + /* and de-activate the image number selector */ + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap |= SANE_CAP_INACTIVE; + } + else + { + /* deactivate the resolution setting */ + sod[GPHOTO2_OPT_LOWRES].cap |= SANE_CAP_INACTIVE; + /* and activate the image number selector */ + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE; + } + /* set params according to resolution settings */ + set_res (gphoto2_opt_lowres); + + break; + + case GPHOTO2_OPT_LOWRES: + gphoto2_opt_lowres = !!*(SANE_Word *) value; + myinfo |= SANE_INFO_RELOAD_PARAMS; + +/* XXX - change the number of pictures left depending on resolution + perhaps just call get_info again? +*/ + set_res (gphoto2_opt_lowres); + + break; + + case GPHOTO2_OPT_ERASE: + gphoto2_opt_erase = !!*(SANE_Word *) value; + break; + + case GPHOTO2_OPT_AUTOINC: + gphoto2_opt_autoinc = !!*(SANE_Word *) value; + break; + + case GPHOTO2_OPT_FOLDER: + printf ("FIXME set folder not implemented yet\n"); + break; + + case GPHOTO2_OPT_DEFAULT: + gphoto2_opt_thumbnails = 0; + gphoto2_opt_snap = 0; + + /* deactivate the resolution setting */ +/* sod[GPHOTO2_OPT_LOWRES].cap |= SANE_CAP_INACTIVE; */ + /* and activate the image number selector */ + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE; + + myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + + DBG (1, "Fixme: Set all defaults here!\n"); + break; + + case GPHOTO2_OPT_INIT_GPHOTO2: + if ((Camera.fd = init_gphoto2 ()) == -1) + { + return SANE_STATUS_INVAL; + } + if (get_info () == -1) + { + DBG (1, "error: could not get info\n"); + close_gphoto2 (Camera.fd); + return SANE_STATUS_INVAL; + } + + /* load the current images array */ + get_pictures_info (); + + myinfo |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS; + break; + + default: + return SANE_STATUS_INVAL; + } + break; + + case SANE_ACTION_GET_VALUE: + switch (option) + { + case 0: + *(SANE_Word *) value = NELEMS (sod); + break; + + case GPHOTO2_OPT_IMAGE_NUMBER: + *(SANE_Word *) value = Camera.current_picture_number; + break; + + case GPHOTO2_OPT_THUMBS: + *(SANE_Word *) value = gphoto2_opt_thumbnails; + break; + + case GPHOTO2_OPT_SNAP: + *(SANE_Word *) value = gphoto2_opt_snap; + break; + + case GPHOTO2_OPT_LOWRES: + *(SANE_Word *) value = gphoto2_opt_lowres; + break; + + case GPHOTO2_OPT_ERASE: + *(SANE_Word *) value = gphoto2_opt_erase; + break; + + case GPHOTO2_OPT_AUTOINC: + *(SANE_Word *) value = gphoto2_opt_autoinc; + break; + + case GPHOTO2_OPT_FOLDER: + if (folder_list == NULL) + { + return SANE_STATUS_INVAL; + } + strncpy ((char *) value, (char *) folder_list[current_folder], 256); + break; + + + default: + return SANE_STATUS_INVAL; + } + break; + + case SANE_ACTION_SET_AUTO: + switch (option) + { + default: + return SANE_STATUS_UNSUPPORTED; /* We are DUMB */ + } + } + + if (info) + { + *info = myinfo; + myinfo = 0; + } + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters * params) +{ + SANE_Int rc = SANE_STATUS_GOOD; + + DBG (127, "sane_get_params called, wid=%d,height=%d\n", + parms.pixels_per_line, parms.lines); + + if (handle != MAGIC || !is_open) + rc = SANE_STATUS_INVAL; /* Unknown handle ... */ + + parms.last_frame = SANE_TRUE; /* Have no idea what this does */ + *params = parms; + DBG (127, "sane_get_params return %d\n", rc); + return rc; +} + +typedef struct +{ + struct jpeg_source_mgr pub; + JOCTET *buffer; +} +my_source_mgr; +typedef my_source_mgr *my_src_ptr; + +METHODDEF (void) +jpeg_init_source (j_decompress_ptr UNUSEDARG cinfo) +{ + /* nothing to do */ +} + +METHODDEF (boolean) jpeg_fill_input_buffer (j_decompress_ptr cinfo) +{ + int n; + + my_src_ptr src = (my_src_ptr) cinfo->src; + + if ((n = fread (src->buffer, 1, 512, cmdpipe)) == -1) + { + DBG (5, "sane_start: read_data failed\n"); + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + return FALSE; + } + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = n; + + return TRUE; +} + +METHODDEF (void) jpeg_skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + + my_src_ptr src = (my_src_ptr) cinfo->src; + + if (num_bytes > 0) + { + while (num_bytes > (long) src->pub.bytes_in_buffer) + { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) jpeg_fill_input_buffer (cinfo); + } + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; +} + +METHODDEF (void) +jpeg_term_source (j_decompress_ptr UNUSEDARG cinfo) +{ + /* no work necessary here */ +} + +SANE_Status +sane_start (SANE_Handle handle) +{ + my_src_ptr src; + + struct jpeg_error_mgr jerr; + SANE_Int row_stride; + SANE_Char cmdbuf[256]; + + DBG (127, "sane_start called\n"); + if (handle != MAGIC || !is_open || + (Camera.current_picture_number == 0 && gphoto2_opt_snap == SANE_FALSE)) + return SANE_STATUS_INVAL; /* Unknown handle ... */ + + if (Camera.scanning) + return SANE_STATUS_EOF; + +/* + * This shouldn't normally happen, but we allow it as a special case + * when batch/autoinc are in effect. The first illegal picture number + * terminates the scan + */ + if (Camera.current_picture_number > Camera.pic_taken) + { + return SANE_STATUS_INVAL; + } + + if (gphoto2_opt_snap) + { + /* + * Don't allow picture unless there is room in the + * camera. + */ + if (Camera.pic_left == 0) + { + DBG (3, "No room to store new picture\n"); + return SANE_STATUS_INVAL; + } + + + if (snap_pic () == SANE_STATUS_INVAL) + { + DBG (1, "Failed to snap new picture\n"); + return SANE_STATUS_INVAL; + } + } + + sprintf ((char *) cmdbuf, + "%s --camera \"%s\" --port %s --folder %s/%s --stdout -%s %d", + (char *) Gphoto2Path, (char *) Camera.camera_name, + (char *) Camera.tty_name, (char *) TopFolder, + (char *) folder_list[current_folder], + gphoto2_opt_thumbnails ? "t" : "p", Camera.current_picture_number); + cmdpipe = popen ((char *) cmdbuf, "r"); + + if (cmdpipe == NULL) + { + return SANE_STATUS_INVAL; + } + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_decompress (&cinfo); + + cinfo.src = + (struct jpeg_source_mgr *) (*cinfo.mem-> + alloc_small) ((j_common_ptr) & cinfo, + JPOOL_PERMANENT, + sizeof (my_source_mgr)); + src = (my_src_ptr) cinfo.src; + + src->buffer = (JOCTET *) (*cinfo.mem->alloc_small) ((j_common_ptr) & + cinfo, + JPOOL_PERMANENT, + 1024 * sizeof (JOCTET)); + src->pub.init_source = jpeg_init_source; + src->pub.fill_input_buffer = jpeg_fill_input_buffer; + src->pub.skip_input_data = jpeg_skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* default */ + src->pub.term_source = jpeg_term_source; + src->pub.bytes_in_buffer = 0; + src->pub.next_input_byte = NULL; + + (void) jpeg_read_header (&cinfo, TRUE); + dest_mgr = sanei_jpeg_jinit_write_ppm (&cinfo); + (void) jpeg_start_decompress (&cinfo); + row_stride = cinfo.output_width * cinfo.output_components; + + + /* Check if a linebuffer has been allocated. Assumes that highres_width + * is also large enough to hold a lowres or thumbnail image + */ + if (linebuffer == NULL) + { + linebuffer = malloc (highres_width * 3); + } + if (linebuffer == NULL) + { + return SANE_STATUS_INVAL; + } + + Camera.scanning = SANE_TRUE; /* don't overlap scan requests */ + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_read (SANE_Handle UNUSEDARG handle, SANE_Byte * data, + SANE_Int max_length, SANE_Int * length) +{ + SANE_Int lines = 0; + + /* If there is anything in the buffer, satisfy the read from there */ + if (linebuffer_size && linebuffer_index < linebuffer_size) + { + *length = linebuffer_size - linebuffer_index; + + if (*length > max_length) + { + *length = max_length; + } + memcpy (data, linebuffer + linebuffer_index, *length); + linebuffer_index += *length; + + return SANE_STATUS_GOOD; + } + + if (cinfo.output_scanline >= cinfo.output_height) + { + fclose (cmdpipe); + + if (gphoto2_opt_erase) + { + DBG (127, "sane_read bp%d, erase image\n", __LINE__); + if (erase () == -1) + { + DBG (1, "Failed to erase memory\n"); + return SANE_STATUS_INVAL; + } + Camera.pic_taken--; + Camera.pic_left++; + Camera.current_picture_number = Camera.pic_taken; + image_range.max--; + + myinfo |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS; + dir_delete ((SANE_String) & name_buf[1]); + + } + if (gphoto2_opt_autoinc) + { + if (Camera.current_picture_number <= Camera.pic_taken) + { + Camera.current_picture_number++; + + myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + + /* get the image's resolution */ + set_res (Camera.Pictures[Camera.current_picture_number - 1]. + low_res); + } + DBG (4, "Increment count to %d (total %d)\n", + Camera.current_picture_number, Camera.pic_taken); + } + return SANE_STATUS_EOF; + } + +/* XXX - we should read more than 1 line at a time here */ + lines = 1; + (void) jpeg_read_scanlines (&cinfo, dest_mgr->buffer, lines); + (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, lines, (char *) linebuffer); + + *length = cinfo.output_width * cinfo.output_components * lines; + linebuffer_size = *length; + linebuffer_index = 0; + + if (*length > max_length) + { + *length = max_length; + } + memcpy (data, linebuffer + linebuffer_index, *length); + linebuffer_index += *length; + + return SANE_STATUS_GOOD; +} + +void +sane_cancel (SANE_Handle UNUSEDARG handle) +{ + if (Camera.scanning) + { + Camera.scanning = SANE_FALSE; /* done with scan */ + } + else + DBG (4, "sane_cancel: not scanning - nothing to do\n"); +} + +SANE_Status +sane_set_io_mode (SANE_Handle UNUSEDARG handle, SANE_Bool + UNUSEDARG non_blocking) +{ + return SANE_STATUS_UNSUPPORTED; +} + +SANE_Status +sane_get_select_fd (SANE_Handle UNUSEDARG handle, SANE_Int * UNUSEDARG fd) +{ + return SANE_STATUS_UNSUPPORTED; +} + +/* + * get_pictures_info - load information about all pictures currently in + * camera: Mainly the mapping of picture number + * to picture name, and the resolution of each picture. + */ +static PictureInfo * +get_pictures_info (void) +{ + SANE_Char f[] = "get_pictures_info"; + SANE_Char path[256]; + SANE_Int num_pictures; + SANE_Int p; + PictureInfo *pics; + + if (Camera.Pictures) + { + free (Camera.Pictures); + Camera.Pictures = NULL; + } + + strcpy (path, TopFolder); + if (folder_list[current_folder] != NULL) + { + strcat (path, "/"); + strcat (path, (char *) folder_list[current_folder]); + } + + num_pictures = read_dir (path, 1); + Camera.pic_taken = num_pictures; + if (num_pictures > 0) + { + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE; + image_range.min = 1; + image_range.max = num_pictures; + } + + if ((pics = (PictureInfo *) malloc (Camera.pic_taken * + sizeof (PictureInfo))) == NULL) + { + DBG (1, "%s: error: allocate memory for pictures array\n", f); + return NULL; + } + + for (p = 0; p < Camera.pic_taken; p++) + { + if (get_picture_info (pics + p, p) == -1) + { + free (pics); + return NULL; + } + } + + Camera.Pictures = pics; + return pics; +} + +static SANE_Int +get_picture_info (PictureInfo * pic, SANE_Int p) +{ + + SANE_Char f[] = "get_picture_info"; + SANE_Int n; + struct cam_dirlist *e; + + DBG (4, "%s: info for pic #%d\n", f, p); + + for (n = 0, e = dir_head; e && n < p; n++, e = e->next) + ; + + DBG (4, "Name is %s\n", e->name); + + read_info (e->name); + + pic->low_res = SANE_FALSE; + + return 0; +} + +/* + * snap_pic - take a picture (and call get_pictures_info to re-create + * the directory related data structures) + */ +static SANE_Status +snap_pic (void) +{ + SANE_Bool found = SANE_FALSE; + SANE_Char cmdbuf[256]; + SANE_Char f[] = "snap_pic"; + SANE_Int linecount = 0; + + /* make sure camera is set to our settings state */ + if (change_res (gphoto2_opt_lowres) == -1) + { + DBG (1, "%s: Failed to set resolution\n", f); + return SANE_STATUS_INVAL; + } + + sprintf ((char *) cmdbuf, + "%s --camera \"%s\" --port %s --folder %s/%s -q --capture-image", + (char *) Gphoto2Path, (char *) Camera.camera_name, + (char *) Camera.tty_name, (char *) TopFolder, + (char *) folder_list[current_folder]); + + cmdpipe = popen ((char *) cmdbuf, "r"); + + if (cmdpipe == NULL) + { + return SANE_STATUS_INVAL; + } + + while (fgets ((char *) cmdbuf, sizeof (cmdbuf), cmdpipe) + && linecount++ < 100) + { + if (strncmp (cmdbuf, "/DCIM/", 6) == 0) + { + found = SANE_TRUE; + } + } + fclose (cmdpipe); + + if (!found) + { + return SANE_STATUS_INVAL; + } + + /* Can't just increment picture count, because if the camera has + * zero pictures we may not know the folder name. Start over + * with get_info and get_pictures_info + */ + if (get_info () == -1) + { + DBG (1, "error: could not get info\n"); + close_gphoto2 (Camera.fd); + return SANE_STATUS_INVAL; + } + + if (get_pictures_info () == NULL) + { + DBG (1, "%s: Failed to get new picture info\n", f); + /* XXX - I guess we should try to erase the image here */ + return SANE_STATUS_INVAL; + } + + + sod[GPHOTO2_OPT_IMAGE_NUMBER].cap |= SANE_CAP_INACTIVE; + Camera.current_picture_number = Camera.pic_taken; + myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + + return SANE_STATUS_GOOD; +} + +/* + * read_dir - read a list of file names from the specified directory + * and create a linked list of file name entries in + * alphabetical order. The first entry in the list will + * be "picture #1", etc. + */ +static SANE_Int +read_dir (SANE_String dir, SANE_Bool read_files) +{ + SANE_Int retval = 0; + SANE_Byte buf[256]; + SANE_Int i, entries; + SANE_Char f[] = "read_dir"; + struct cam_dirlist *e, *next; + + /* Free up current list */ + for (e = dir_head; e; e = next) + { + DBG (127, "%s: free entry %s\n", f, e->name); + next = e->next; + free (e); + } + dir_head = NULL; + + if (read_files) + { + sprintf ((char *) buf, + "%s --camera \"%s\" --port %s --folder %s --list-files --stdout", + (char *) Gphoto2Path, (char *) Camera.camera_name, + (char *) Camera.tty_name, dir); + } + else + { + sprintf ((char *) buf, + "%s --camera \"%s\" --port %s --folder %s --list-folders -q", + (char *) Gphoto2Path, (char *) Camera.camera_name, + (char *) Camera.tty_name, dir); + } + + cmdpipe = popen ((char *) buf, "r"); + if (cmdpipe == NULL) + { + DBG (0, "%s: error: couldn't open gphoto2", f); + exit (1); + } + + fgets ((char *) tmpstr, sizeof (tmpstr), cmdpipe); + sscanf ((char *) tmpstr, "%d", &entries); + + for (i = 0; i < entries; i++) + + { + fgets ((char *) tmpstr, sizeof (tmpstr), cmdpipe); + *strrchr ((char *) tmpstr, '\"') = '\0'; + + if (dir_insert ((char *) (tmpstr + 1))) + { + DBG (1, "%s: error: failed to insert dir entry\n"); + return -1; + } + retval++; + } + fclose (cmdpipe); + + return retval; +} + +/* + * read_info - read the info block from camera for the specified file + */ +static SANE_Int +read_info (SANE_String fname) +{ + SANE_Char path[256]; + + strcpy (path, "\\PCCARD\\DCIM\\"); + strcat (path, (char *) folder_list[current_folder]); + strcat (path, "\\"); + strcat (path, fname); + + + + return 0; +} + +/* + * dir_insert - Add (in alphabetical order) a directory entry to the + * current list of entries. + */ +static SANE_Int +dir_insert (SANE_String s) +{ + struct cam_dirlist *cur, *e; + + cur = (struct cam_dirlist *) malloc (sizeof (struct cam_dirlist)); + if (cur == NULL) + { + DBG (1, "dir_insert: error: could not malloc entry\n"); + return -1; + } + + strcpy (cur->name, s); + DBG (127, "dir_insert: name is %s\n", cur->name); + + cur->next = NULL; + + if (dir_head == NULL) + { + dir_head = cur; + } + else if (strcmp (cur->name, dir_head->name) < 0) + { + cur->next = dir_head; + dir_head = cur; + return 0; + } + else + { + for (e = dir_head; e->next; e = e->next) + { + if (strcmp (e->next->name, cur->name) > 0) + { + cur->next = e->next; + e->next = cur; + return 0; + } + } + e->next = cur; + } + return 0; +} + +/* + * dir_delete - Delete a directory entry from the linked list of file + * names + */ +static SANE_Int +dir_delete (SANE_String fname) +{ + struct cam_dirlist *cur, *e; + + DBG (127, "dir_delete: %s\n", fname); + + if (strcmp (fname, dir_head->name) == 0) + { + cur = dir_head; + dir_head = dir_head->next; + free (cur); + return 0; + } + + for (e = dir_head; e->next; e = e->next) + { + + if (strcmp (fname, e->next->name) == 0) + { + cur = e->next; + e->next = e->next->next; + free (cur); + return (0); + } + } + DBG (1, "dir_delete: Couldn't find entry %s in dir list\n", fname); + return -1; +} + +/* + * set_res - set picture size depending on resolution settings + */ +static void +set_res (SANE_Int lowres) +{ + if (gphoto2_opt_thumbnails) + { + parms.bytes_per_line = THUMB_WIDTH * 3; + parms.pixels_per_line = THUMB_WIDTH; + parms.lines = THUMB_HEIGHT; + } + else if (lowres) + { + parms.bytes_per_line = LOWRES_WIDTH * 3; + parms.pixels_per_line = LOWRES_WIDTH; + parms.lines = LOWRES_HEIGHT; + } + else + { + parms.bytes_per_line = HIGHRES_WIDTH * 3; + parms.pixels_per_line = HIGHRES_WIDTH; + parms.lines = HIGHRES_HEIGHT; + } +}