/* sane - Scanner Access Now Easy. Copyright (C) 2019 Touboul Nathane Copyright (C) 2019 Thierry HUCHARD This file is part of the SANE package. SANE 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 3 of the License, or (at your option) any later version. SANE 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 sane; see the file COPYING. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. This file implements a SANE backend for eSCL scanners. */ #include "escl.h" #include #include #include #include #include #include "../include/sane/saneopts.h" #include "../include/sane/sanei.h" #include "../include/sane/sanei_backend.h" #include "../include/sane/sanei_config.h" #define min(A,B) (((A)<(B)) ? (A) : (B)) #define max(A,B) (((A)>(B)) ? (A) : (B)) #define INPUT_BUFFER_SIZE 4096 static const SANE_Device **devlist = NULL; static ESCL_Device *list_devices_primary = NULL; static int num_devices = 0; typedef struct Handled { struct Handled *next; ESCL_Device *device; char *result; ESCL_ScanParam param; SANE_Option_Descriptor opt[NUM_OPTIONS]; Option_Value val[NUM_OPTIONS]; capabilities_t *scanner; SANE_Range x_range1; SANE_Range x_range2; SANE_Range y_range1; SANE_Range y_range2; SANE_Bool cancel; SANE_Bool write_scan_data; SANE_Bool decompress_scan_data; SANE_Bool end_read; SANE_Parameters ps; } escl_sane_t; static ESCL_Device * escl_free_device(ESCL_Device *current) { if (!current) return NULL; free((void*)current->ip_address); free((void*)current->model_name); free((void*)current->type); free(current->unix_socket); free(current); return NULL; } void escl_free_handler(escl_sane_t *handler) { if (handler == NULL) return; escl_free_device(handler->device); free(handler); } SANE_Status escl_parse_name(SANE_String_Const name, ESCL_Device *device); static SANE_Status escl_check_and_add_device(ESCL_Device *current) { if(!current) { DBG (10, "ESCL_Device *current us null.\n"); return (SANE_STATUS_NO_MEM); } if (!current->ip_address) { DBG (10, "Ip Address allocation failure.\n"); return (SANE_STATUS_NO_MEM); } if (current->port_nb == 0) { DBG (10, "No port defined.\n"); return (SANE_STATUS_NO_MEM); } if (!current->model_name) { DBG (10, "Modele Name allocation failure.\n"); return (SANE_STATUS_NO_MEM); } if (!current->type) { DBG (10, "Scanner Type allocation failure.\n"); return (SANE_STATUS_NO_MEM); } ++num_devices; current->next = list_devices_primary; list_devices_primary = current; return (SANE_STATUS_GOOD); } /** * \fn static SANE_Status escl_add_in_list(ESCL_Device *current) * \brief Function that adds all the element needed to my list : * the port number, the model name, the ip address, and the type of url (http/https). * Moreover, this function counts the number of devices found. * * \return SANE_STATUS_GOOD if everything is OK. */ static SANE_Status escl_add_in_list(ESCL_Device *current) { if(!current) { DBG (10, "ESCL_Device *current us null.\n"); return (SANE_STATUS_NO_MEM); } if (SANE_STATUS_GOOD == escl_check_and_add_device(current)) { list_devices_primary = current; return (SANE_STATUS_GOOD); } current = escl_free_device(current); return (SANE_STATUS_NO_MEM); } /** * \fn SANE_Status escl_device_add(int port_nb, const char *model_name, char *ip_address, char *type) * \brief Function that browses my list ('for' loop) and returns the "escl_add_in_list" function to * adds all the element needed to my list : * the port number, the model name, the ip address and the type of the url (http / https). * * \return escl_add_in_list(current) */ SANE_Status escl_device_add(int port_nb, const char *model_name, char *ip_address, char *type) { char tmp[PATH_MAX] = { 0 }; char *model = NULL; ESCL_Device *current = NULL; DBG (10, "escl_device_add\n"); for (current = list_devices_primary; current; current = current->next) { if (strcmp(current->ip_address, ip_address) == 0) { if (strcmp(current->type, type)) { if(!strcmp(type, "_uscans._tcp") || !strcmp(type, "https")) { free (current->type); current->type = strdup(type); current->port_nb = port_nb; current->https = SANE_TRUE; } return (SANE_STATUS_GOOD); } else if (current->port_nb == port_nb) return (SANE_STATUS_GOOD); } } current = (ESCL_Device*)calloc(1, sizeof(*current)); if (current == NULL) { DBG (10, "New device allocation failure.\n"); return (SANE_STATUS_NO_MEM); } current->port_nb = port_nb; if (strcmp(type, "_uscan._tcp") != 0 && strcmp(type, "http") != 0) { snprintf(tmp, sizeof(tmp), "%s SSL", model_name); current->https = SANE_TRUE; } else { current->https = SANE_FALSE; } model = (char*)(tmp[0] != 0 ? tmp : model_name); current->model_name = strdup(model); current->ip_address = strdup(ip_address); current->type = strdup(type); return escl_add_in_list(current); } /** * \fn static inline size_t max_string_size(const SANE_String_Const strings[]) * \brief Function that browses the string ('for' loop) and counts the number of character in the string. * --> this allows to know the maximum size of the string. * * \return max_size + 1 (the size max) */ static inline size_t max_string_size(const SANE_String_Const strings[]) { size_t max_size = 0; int i = 0; for (i = 0; strings[i]; ++i) { size_t size = strlen (strings[i]); if (size > max_size) max_size = size; } return (max_size + 1); } static char * get_vendor(char *search) { if(strcasestr(search, "Epson")) return strdup("Epson"); else if(strcasestr(search, "Fujitsu")) return strdup("Fujitsu"); else if(strcasestr(search, "HP")) return strdup("HP"); else if(strcasestr(search, "Canon")) return strdup("Canon"); else if(strcasestr(search, "Lexmark")) return strdup("Lexmark"); else if(strcasestr(search, "Samsung")) return strdup("Samsung"); else if(strcasestr(search, "Xerox")) return strdup("Xerox"); else if(strcasestr(search, "OKI")) return strdup("OKI"); else if(strcasestr(search, "Hewlett Packard")) return strdup("Hewlett Packard"); else if(strcasestr(search, "IBM")) return strdup("IBM"); else if(strcasestr(search, "Mustek")) return strdup("Mustek"); else if(strcasestr(search, "Ricoh")) return strdup("Ricoh"); else if(strcasestr(search, "Sharp")) return strdup("Sharp"); else if(strcasestr(search, "UMAX")) return strdup("UMAX"); else if(strcasestr(search, "PINT")) return strdup("PINT"); else if(strcasestr(search, "Brother")) return strdup("Brother"); return NULL; } /** * \fn static SANE_Device *convertFromESCLDev(ESCL_Device *cdev) * \brief Function that checks if the url of the received scanner is secured or not (http / https). * --> if the url is not secured, our own url will be composed like "http://'ip':'port'". * --> else, our own url will be composed like "https://'ip':'port'". * AND, it's in this function that we gather all the informations of the url (that were in our list) : * the model_name, the port, the ip, and the type of url. * SO, leaving this function, we have in memory the complete url. * * \return sdev (structure that contains the elements of the url) */ static SANE_Device * convertFromESCLDev(ESCL_Device *cdev) { char *tmp; int len, lv = 0; char unix_path[PATH_MAX+7] = { 0 }; SANE_Device *sdev = (SANE_Device*) calloc(1, sizeof(SANE_Device)); if (!sdev) { DBG (10, "Sane_Device allocation failure.\n"); return NULL; } if (cdev->unix_socket && strlen(cdev->unix_socket)) { snprintf(unix_path, sizeof(unix_path), "unix:%s:", cdev->unix_socket); } len = snprintf(NULL, 0, "%shttp%s://%s:%d", unix_path, cdev->https ? "s" : "", cdev->ip_address, cdev->port_nb); len++; tmp = (char *)malloc(len); if (!tmp) { DBG (10, "Name allocation failure.\n"); goto freedev; } snprintf(tmp, len, "%shttp%s://%s:%d", unix_path, cdev->https ? "s" : "", cdev->ip_address, cdev->port_nb); sdev->name = tmp; DBG( 1, "Escl add device : %s\n", tmp); sdev->vendor = get_vendor(cdev->model_name); if (!sdev->vendor) sdev->vendor = strdup("ESCL"); else lv = strlen(sdev->vendor) + 1; if (!sdev->vendor) { DBG (10, "Vendor allocation failure.\n"); goto freemodel; } sdev->model = strdup(lv + cdev->model_name); if (!sdev->model) { DBG (10, "Model allocation failure.\n"); goto freename; } sdev->type = strdup("flatbed scanner"); if (!sdev->type) { DBG (10, "Scanner Type allocation failure.\n"); goto freevendor; } return (sdev); freevendor: free((void*)sdev->vendor); freemodel: free((void*)sdev->model); freename: free((void*)sdev->name); freedev: free((void*)sdev); return NULL; } /** * \fn SANE_Status sane_init(SANE_Int *version_code, SANE_Auth_Callback authorize) * \brief Function that's called before any other SANE function ; it's the first SANE function called. * --> this function checks the SANE config. and can check the authentication of the user if * 'authorize' value is more than SANE_TRUE. * In this case, it will be necessary to define an authentication method. * * \return SANE_STATUS_GOOD (everything is OK) */ SANE_Status sane_init(SANE_Int *version_code, SANE_Auth_Callback __sane_unused__ authorize) { DBG_INIT(); DBG (10, "escl sane_init\n"); SANE_Status status = SANE_STATUS_GOOD; curl_global_init(CURL_GLOBAL_ALL); if (version_code != NULL) *version_code = SANE_VERSION_CODE(1, 0, 0); if (status != SANE_STATUS_GOOD) return (status); return (SANE_STATUS_GOOD); } /** * \fn void sane_exit(void) * \brief Function that must be called to terminate use of a backend. * This function will first close all device handles that still might be open. * --> by freeing all the elements of my list. * After this function, no function other than 'sane_init' may be called. */ void sane_exit(void) { DBG (10, "escl sane_exit\n"); ESCL_Device *next = NULL; while (list_devices_primary != NULL) { next = list_devices_primary->next; free(list_devices_primary); list_devices_primary = next; } if (devlist) free (devlist); list_devices_primary = NULL; devlist = NULL; curl_global_cleanup(); } /** * \fn static SANE_Status attach_one_config(SANEI_Config *config, const char *line) * \brief Function that implements a configuration file to the user : * if the user can't detect some devices, he will be able to force their detection with this config' file to use them. * Thus, this function parses the config' file to use the device of the user with the information below : * the type of protocol (http/https), the ip, the port number, and the model name. * * \return escl_add_in_list(escl_device) if the parsing worked, SANE_STATUS_GOOD otherwise. */ static SANE_Status attach_one_config(SANEI_Config __sane_unused__ *config, const char *line) { int port = 0; SANE_Status status; static ESCL_Device *escl_device = NULL; if (strncmp(line, "device", 6) == 0) { char *name_str = NULL; char *opt_model = NULL; line = sanei_config_get_string(line + 6, &name_str); DBG (10, "New Escl_Device URL [%s].\n", (name_str ? name_str : "VIDE")); if (!name_str || !*name_str) { DBG (1, "Escl_Device URL missing.\n"); return SANE_STATUS_INVAL; } if (*line) { line = sanei_config_get_string(line, &opt_model); DBG (10, "New Escl_Device model [%s].\n", opt_model); } escl_free_device(escl_device); escl_device = (ESCL_Device*)calloc(1, sizeof(ESCL_Device)); if (!escl_device) { DBG (10, "New Escl_Device allocation failure.\n"); free(name_str); return (SANE_STATUS_NO_MEM); } status = escl_parse_name(name_str, escl_device); free(name_str); if (status != SANE_STATUS_GOOD) { escl_free_device(escl_device); escl_device = NULL; return status; } escl_device->model_name = opt_model ? opt_model : strdup("Unknown model"); escl_device->type = strdup("flatbed scanner"); } if (strncmp(line, "[device]", 8) == 0) { escl_device = escl_free_device(escl_device); escl_device = (ESCL_Device*)calloc(1, sizeof(ESCL_Device)); if (!escl_device) { DBG (10, "New Escl_Device allocation failure."); return (SANE_STATUS_NO_MEM); } } if (strncmp(line, "ip", 2) == 0) { const char *ip_space = sanei_config_skip_whitespace(line + 2); DBG (10, "New Escl_Device IP [%s].", (ip_space ? ip_space : "VIDE")); if (escl_device != NULL && ip_space != NULL) { DBG (10, "New Escl_Device IP Affected."); escl_device->ip_address = strdup(ip_space); } } if (sscanf(line, "port %i", &port) == 1 && port != 0) { DBG (10, "New Escl_Device PORT [%d].", port); if (escl_device != NULL) { DBG (10, "New Escl_Device PORT Affected."); escl_device->port_nb = port; } } if (strncmp(line, "model", 5) == 0) { const char *model_space = sanei_config_skip_whitespace(line + 5); DBG (10, "New Escl_Device MODEL [%s].", (model_space ? model_space : "VIDE")); if (escl_device != NULL && model_space != NULL) { DBG (10, "New Escl_Device MODEL Affected."); escl_device->model_name = strdup(model_space); } } if (strncmp(line, "type", 4) == 0) { const char *type_space = sanei_config_skip_whitespace(line + 4); DBG (10, "New Escl_Device TYPE [%s].", (type_space ? type_space : "VIDE")); if (escl_device != NULL && type_space != NULL) { DBG (10, "New Escl_Device TYPE Affected."); escl_device->type = strdup(type_space); } } status = escl_check_and_add_device(escl_device); if (status == SANE_STATUS_GOOD) escl_device = NULL; return status; } /** * \fn SANE_Status sane_get_devices(const SANE_Device ***device_list, SANE_Bool local_only) * \brief Function that searches for connected devices and places them in our 'device_list'. ('for' loop) * If the attribute 'local_only' is worth SANE_FALSE, we only returns the connected devices locally. * * \return SANE_STATUS_GOOD if devlist != NULL ; SANE_STATUS_NO_MEM otherwise. */ SANE_Status sane_get_devices(const SANE_Device ***device_list, SANE_Bool local_only) { if (local_only) /* eSCL is a network-only protocol */ return (device_list ? SANE_STATUS_GOOD : SANE_STATUS_INVAL); DBG (10, "escl sane_get_devices\n"); ESCL_Device *dev = NULL; static const SANE_Device **devlist = 0; SANE_Status status; if (device_list == NULL) return (SANE_STATUS_INVAL); status = sanei_configure_attach(ESCL_CONFIG_FILE, NULL, attach_one_config); if (status != SANE_STATUS_GOOD) return (status); escl_devices(&status); if (status != SANE_STATUS_GOOD) return (status); if (devlist) free(devlist); devlist = (const SANE_Device **) calloc (num_devices + 1, sizeof (devlist[0])); if (devlist == NULL) return (SANE_STATUS_NO_MEM); int i = 0; for (dev = list_devices_primary; i < num_devices; dev = dev->next) { SANE_Device *s_dev = convertFromESCLDev(dev); devlist[i] = s_dev; i++; } devlist[i] = 0; *device_list = devlist; return (devlist) ? SANE_STATUS_GOOD : SANE_STATUS_NO_MEM; } /* Returns the length of the longest string, including the terminating * character. */ static size_t _source_size_max (SANE_String_Const * sources) { size_t size = 0; while(*sources) { size_t t = strlen (*sources) + 1; if (t > size) size = t; sources++; } return size; } /** * \fn static SANE_Status init_options(SANE_String_Const name, escl_sane_t *s) * \brief Function thzt initializes all the needed options of the received scanner * (the resolution / the color / the margins) thanks to the informations received with * the 'escl_capabilities' function, called just before. * * \return status (if everything is OK, status = SANE_STATUS_GOOD) */ static SANE_Status init_options(SANE_String_Const name_source, escl_sane_t *s) { DBG (10, "escl init_options\n"); SANE_Status status = SANE_STATUS_GOOD; int i = 0; if (!s->scanner) return SANE_STATUS_INVAL; if (name_source) { int source = s->scanner->source; DBG (10, "escl init_options name [%s]\n", name_source); if (!strcmp(name_source, SANE_I18N ("ADF Duplex"))) s->scanner->source = ADFDUPLEX; else if (!strncmp(name_source, "A", 1) || !strcmp(name_source, SANE_I18N ("ADF"))) s->scanner->source = ADFSIMPLEX; else s->scanner->source = PLATEN; if (source == s->scanner->source) return status; } else s->scanner->source = PLATEN; 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->x_range1.min = 0; s->x_range1.max = PIXEL_TO_MM((s->scanner->caps[s->scanner->source].MaxWidth - s->scanner->caps[s->scanner->source].MinWidth), 300.0); s->x_range1.quant = 0; s->x_range2.min = PIXEL_TO_MM(s->scanner->caps[s->scanner->source].MinWidth, 300.0); s->x_range2.max = PIXEL_TO_MM(s->scanner->caps[s->scanner->source].MaxWidth, 300.0); s->x_range2.quant = 0; s->y_range1.min = 0; s->y_range1.max = PIXEL_TO_MM((s->scanner->caps[s->scanner->source].MaxHeight - s->scanner->caps[s->scanner->source].MinHeight), 300.0); s->y_range1.quant = 0; s->y_range2.min = PIXEL_TO_MM(s->scanner->caps[s->scanner->source].MinHeight, 300.0); s->y_range2.max = PIXEL_TO_MM(s->scanner->caps[s->scanner->source].MaxHeight, 300.0); s->y_range2.quant = 0; s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; s->val[OPT_NUM_OPTS].w = NUM_OPTIONS; s->opt[OPT_MODE_GROUP].title = SANE_TITLE_SCAN_MODE; s->opt[OPT_MODE_GROUP].desc = ""; s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; s->opt[OPT_MODE_GROUP].cap = 0; s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; s->opt[OPT_MODE].type = SANE_TYPE_STRING; s->opt[OPT_MODE].unit = SANE_UNIT_NONE; s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_MODE].constraint.string_list = s->scanner->caps[s->scanner->source].ColorModes; s->val[OPT_MODE].s = (char *)strdup(s->scanner->caps[s->scanner->source].ColorModes[0]); if (!s->val[OPT_MODE].s) { DBG (10, "Color Mode Default allocation failure.\n"); return (SANE_STATUS_NO_MEM); } s->opt[OPT_MODE].size = max_string_size(s->scanner->caps[s->scanner->source].ColorModes); s->scanner->caps[s->scanner->source].default_color = (char *)strdup(s->scanner->caps[s->scanner->source].ColorModes[0]); if (!s->scanner->caps[s->scanner->source].default_color) { DBG (10, "Color Mode Default allocation failure.\n"); return (SANE_STATUS_NO_MEM); } 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->scanner->caps[s->scanner->source].SupportedResolutions; s->val[OPT_RESOLUTION].w = s->scanner->caps[s->scanner->source].SupportedResolutions[1]; s->scanner->caps[s->scanner->source].default_resolution = s->scanner->caps[s->scanner->source].SupportedResolutions[1]; 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].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT; s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; s->val[OPT_PREVIEW].w = SANE_FALSE; s->opt[OPT_GRAY_PREVIEW].name = SANE_NAME_GRAY_PREVIEW; s->opt[OPT_GRAY_PREVIEW].title = SANE_TITLE_GRAY_PREVIEW; s->opt[OPT_GRAY_PREVIEW].desc = SANE_DESC_GRAY_PREVIEW; s->opt[OPT_GRAY_PREVIEW].type = SANE_TYPE_BOOL; s->val[OPT_GRAY_PREVIEW].w = SANE_FALSE; s->opt[OPT_GEOMETRY_GROUP].title = SANE_TITLE_GEOMETRY; s->opt[OPT_GEOMETRY_GROUP].desc = SANE_DESC_GEOMETRY; s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; 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].size = sizeof(SANE_Fixed); s->opt[OPT_TL_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; 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->x_range1; s->val[OPT_TL_X].w = s->x_range1.min; 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].size = sizeof(SANE_Fixed); s->opt[OPT_TL_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; 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->y_range1; s->val[OPT_TL_Y].w = s->y_range1.min; 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].size = sizeof(SANE_Fixed); s->opt[OPT_BR_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; 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->x_range2; s->val[OPT_BR_X].w = s->x_range2.max; 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].size = sizeof(SANE_Fixed); s->opt[OPT_BR_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; 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->y_range2; s->val[OPT_BR_Y].w = s->y_range2.max; /* OPT_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 = _source_size_max(s->scanner->Sources); s->opt[OPT_SCAN_SOURCE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; s->opt[OPT_SCAN_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_SCAN_SOURCE].constraint.string_list = s->scanner->Sources; if (s->val[OPT_SCAN_SOURCE].s) free (s->val[OPT_SCAN_SOURCE].s); s->val[OPT_SCAN_SOURCE].s = strdup (s->scanner->Sources[s->scanner->source]); return (status); } SANE_Status escl_parse_name(SANE_String_Const name, ESCL_Device *device) { SANE_String_Const host = NULL; SANE_String_Const port_str = NULL; DBG(10, "escl_parse_name\n"); if (name == NULL || device == NULL) { return SANE_STATUS_INVAL; } if (strncmp(name, "unix:", 5) == 0) { SANE_String_Const socket = name + 5; name = strchr(socket, ':'); if (name == NULL) return SANE_STATUS_INVAL; device->unix_socket = strndup(socket, name - socket); name++; } if (strncmp(name, "https://", 8) == 0) { device->https = SANE_TRUE; host = name + 8; } else if (strncmp(name, "http://", 7) == 0) { device->https = SANE_FALSE; host = name + 7; } else { DBG(1, "Unknown URL scheme in %s", name); return SANE_STATUS_INVAL; } port_str = strchr(host, ':'); if (port_str == NULL) { DBG(1, "Port missing from URL: %s", name); return SANE_STATUS_INVAL; } port_str++; device->port_nb = atoi(port_str); if (device->port_nb < 1 || device->port_nb > 65535) { DBG(1, "Invalid port number in URL: %s", name); return SANE_STATUS_INVAL; } device->ip_address = strndup(host, port_str - host - 1); return SANE_STATUS_GOOD; } /** * \fn SANE_Status sane_open(SANE_String_Const name, SANE_Handle *h) * \brief Function that establishes a connection with the device named by 'name', * and returns a 'handler' using 'SANE_Handle *h', representing it. * Thus, it's this function that calls the 'escl_status' function firstly, * then the 'escl_capabilities' function, and, after, the 'init_options' function. * * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL) */ SANE_Status sane_open(SANE_String_Const name, SANE_Handle *h) { DBG (10, "escl sane_open\n"); SANE_Status status; escl_sane_t *handler = NULL; if (name == NULL) return (SANE_STATUS_INVAL); ESCL_Device *device = calloc(1, sizeof(ESCL_Device)); if (device == NULL) { DBG (10, "Handle device allocation failure.\n"); return SANE_STATUS_NO_MEM; } status = escl_parse_name(name, device); if (status != SANE_STATUS_GOOD) { escl_free_device(device); return status; } handler = (escl_sane_t *)calloc(1, sizeof(escl_sane_t)); if (handler == NULL) { escl_free_device(device); return (SANE_STATUS_NO_MEM); } handler->device = device; // Handler owns device now. handler->scanner = escl_capabilities(device, &status); if (status != SANE_STATUS_GOOD) { escl_free_handler(handler); return (status); } status = init_options(NULL, handler); if (status != SANE_STATUS_GOOD) { escl_free_handler(handler); return (status); } handler->ps.depth = 8; handler->ps.last_frame = SANE_TRUE; handler->ps.format = SANE_FRAME_RGB; handler->ps.pixels_per_line = MM_TO_PIXEL(handler->val[OPT_BR_X].w, 300.0); handler->ps.lines = MM_TO_PIXEL(handler->val[OPT_BR_Y].w, 300.0); handler->ps.bytes_per_line = handler->ps.pixels_per_line * 3; status = sane_get_parameters(handler, 0); if (status != SANE_STATUS_GOOD) { escl_free_handler(handler); return (status); } handler->cancel = SANE_FALSE; handler->write_scan_data = SANE_FALSE; handler->decompress_scan_data = SANE_FALSE; handler->end_read = SANE_FALSE; *h = handler; return (status); } /** * \fn void sane_cancel(SANE_Handle h) * \brief Function that's used to, immediately or as quickly as possible, cancel the currently * pending operation of the device represented by 'SANE_Handle h'. * This functions calls the 'escl_scanner' functions, that resets the scan operations. */ void sane_cancel(SANE_Handle h) { DBG (10, "escl sane_cancel\n"); escl_sane_t *handler = h; if (handler->scanner->tmp) { fclose(handler->scanner->tmp); handler->scanner->tmp = NULL; } handler->scanner->work = SANE_FALSE; handler->cancel = SANE_TRUE; escl_scanner(handler->device, handler->result); free(handler->result); handler->result = NULL; } /** * \fn void sane_close(SANE_Handle h) * \brief Function that closes the communication with the device represented by 'SANE_Handle h'. * This function must release the resources that were allocated to the opening of 'h'. */ void sane_close(SANE_Handle h) { DBG (10, "escl sane_close\n"); if (h != NULL) { escl_free_handler(h); h = NULL; } } /** * \fn const SANE_Option_Descriptor *sane_get_option_descriptor(SANE_Handle h, SANE_Int n) * \brief Function that retrieves a descriptor from the n number option of the scanner * represented by 'h'. * The descriptor remains valid until the machine is closed. * * \return s->opt + n */ const SANE_Option_Descriptor * sane_get_option_descriptor(SANE_Handle h, SANE_Int n) { DBG (10, "escl sane_get_option_descriptor\n"); escl_sane_t *s = h; if ((unsigned) n >= NUM_OPTIONS || n < 0) return (0); return (&s->opt[n]); } /** * \fn SANE_Status sane_control_option(SANE_Handle h, SANE_Int n, SANE_Action a, void *v, SANE_Int *i) * \brief Function that defines the actions to perform for the 'n' option of the machine, * represented by 'h', if the action is 'a'. * There are 3 types of possible actions : * --> SANE_ACTION_GET_VALUE: 'v' must be used to provide the value of the option. * --> SANE_ACTION_SET_VALUE: The option must take the 'v' value. * --> SANE_ACTION_SET_AUTO: The backend or machine must affect the option with an appropriate value. * Moreover, the parameter 'i' is used to provide additional information about the state of * 'n' option if SANE_ACTION_SET_VALUE has been performed. * * \return SANE_STATUS_GOOD if everything is OK, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL */ SANE_Status sane_control_option(SANE_Handle h, SANE_Int n, SANE_Action a, void *v, SANE_Int *i) { DBG (10, "escl sane_control_option\n"); escl_sane_t *handler = h; if (i) *i = 0; if (n >= NUM_OPTIONS || n < 0) return (SANE_STATUS_INVAL); if (a == SANE_ACTION_GET_VALUE) { switch (n) { case OPT_TL_X: case OPT_TL_Y: case OPT_BR_X: case OPT_BR_Y: case OPT_NUM_OPTS: case OPT_RESOLUTION: case OPT_PREVIEW: case OPT_GRAY_PREVIEW: *(SANE_Word *) v = handler->val[n].w; break; case OPT_SCAN_SOURCE: case OPT_MODE: strcpy (v, handler->val[n].s); break; case OPT_MODE_GROUP: default: break; } return (SANE_STATUS_GOOD); } if (a == SANE_ACTION_SET_VALUE) { switch (n) { case OPT_TL_X: case OPT_TL_Y: case OPT_BR_X: case OPT_BR_Y: case OPT_NUM_OPTS: case OPT_RESOLUTION: case OPT_PREVIEW: case OPT_GRAY_PREVIEW: handler->val[n].w = *(SANE_Word *) v; if (i) *i |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT; break; case OPT_SCAN_SOURCE: DBG(10, "SET OPT_SCAN_SOURCE(%s)\n", (SANE_String_Const)v); init_options((SANE_String_Const)v, handler); if (i) *i |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT; break; case OPT_MODE: if (handler->val[n].s) free (handler->val[n].s); handler->val[n].s = strdup (v); if (!handler->val[n].s) { DBG (10, "OPT_MODE allocation failure.\n"); return (SANE_STATUS_NO_MEM); } if (i) *i |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT; break; default: break; } } return (SANE_STATUS_GOOD); } static SANE_Bool _go_next_page(SANE_Status status, SANE_Status job) { // Thank's Alexander Pevzner (pzz@apevzner.com) SANE_Status st = SANE_STATUS_NO_DOCS; switch (status) { case SANE_STATUS_GOOD: case SANE_STATUS_UNSUPPORTED: case SANE_STATUS_DEVICE_BUSY: { DBG(10, "eSCL : Test next page\n"); if (job != SANE_STATUS_GOOD) { DBG(10, "eSCL : Go next page\n"); st = SANE_STATUS_GOOD; } break; } default: DBG(10, "eSCL : No next page\n"); } return st; } /** * \fn SANE_Status sane_start(SANE_Handle h) * \brief Function that initiates aquisition of an image from the device represented by handle 'h'. * This function calls the "escl_newjob" function and the "escl_scan" function. * * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL) */ SANE_Status sane_start(SANE_Handle h) { DBG (10, "escl sane_start\n"); SANE_Status status = SANE_STATUS_GOOD; escl_sane_t *handler = h; int w = 0; int he = 0; int bps = 0; if (handler->device == NULL) { DBG(1, "Missing handler device.\n"); return (SANE_STATUS_INVAL); } handler->cancel = SANE_FALSE; handler->write_scan_data = SANE_FALSE; handler->decompress_scan_data = SANE_FALSE; handler->end_read = SANE_FALSE; if (handler->scanner->work == SANE_FALSE) { SANE_Status st = escl_status(handler->device, handler->scanner->source, NULL, NULL); if (st != SANE_STATUS_GOOD) return st; if(handler->scanner->caps[handler->scanner->source].default_color) free(handler->scanner->caps[handler->scanner->source].default_color); if (handler->val[OPT_PREVIEW].w == SANE_TRUE) { int i = 0, val = 9999;; if (handler->val[OPT_GRAY_PREVIEW].w == SANE_TRUE || !strcasecmp(handler->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY)) handler->scanner->caps[handler->scanner->source].default_color = strdup("Grayscale8"); else handler->scanner->caps[handler->scanner->source].default_color = strdup("RGB24"); if (!handler->scanner->caps[handler->scanner->source].default_color) { DBG (10, "Default Color allocation failure.\n"); return (SANE_STATUS_NO_MEM); } for (i = 1; i < handler->scanner->caps[handler->scanner->source].SupportedResolutionsSize; i++) { if (val > handler->scanner->caps[handler->scanner->source].SupportedResolutions[i]) val = handler->scanner->caps[handler->scanner->source].SupportedResolutions[i]; } handler->scanner->caps[handler->scanner->source].default_resolution = val; } else { handler->scanner->caps[handler->scanner->source].default_resolution = handler->val[OPT_RESOLUTION].w; if (!strcasecmp(handler->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY)) handler->scanner->caps[handler->scanner->source].default_color = strdup("Grayscale8"); else if (!strcasecmp(handler->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_LINEART)) handler->scanner->caps[handler->scanner->source].default_color = strdup("BlackAndWhite1"); else handler->scanner->caps[handler->scanner->source].default_color = strdup("RGB24"); } handler->scanner->caps[handler->scanner->source].height = MM_TO_PIXEL(handler->val[OPT_BR_Y].w, 300.0); handler->scanner->caps[handler->scanner->source].width = MM_TO_PIXEL(handler->val[OPT_BR_X].w, 300.0);; if (handler->x_range1.min == handler->val[OPT_TL_X].w) handler->scanner->caps[handler->scanner->source].pos_x = 0; else handler->scanner->caps[handler->scanner->source].pos_x = MM_TO_PIXEL((handler->val[OPT_TL_X].w - handler->x_range1.min), 300.0); if (handler->y_range1.min == handler->val[OPT_TL_X].w) handler->scanner->caps[handler->scanner->source].pos_y = 0; else handler->scanner->caps[handler->scanner->source].pos_y = MM_TO_PIXEL((handler->val[OPT_TL_Y].w - handler->y_range1.min), 300.0); DBG(10, "Calculate Size Image [%dx%d|%dx%d]\n", handler->scanner->caps[handler->scanner->source].pos_x, handler->scanner->caps[handler->scanner->source].pos_y, handler->scanner->caps[handler->scanner->source].width, handler->scanner->caps[handler->scanner->source].height); if (!handler->scanner->caps[handler->scanner->source].default_color) { DBG (10, "Default Color allocation failure.\n"); return (SANE_STATUS_NO_MEM); } handler->result = escl_newjob(handler->scanner, handler->device, &status); if (status != SANE_STATUS_GOOD) return (status); } else { SANE_Status job = SANE_STATUS_UNSUPPORTED; SANE_Status st = escl_status(handler->device, handler->scanner->source, handler->result, &job); DBG(10, "eSCL : command returned status %s\n", sane_strstatus(st)); if (_go_next_page(st, job) != SANE_STATUS_GOOD) { handler->scanner->work = SANE_FALSE; return SANE_STATUS_NO_DOCS; } } status = escl_scan(handler->scanner, handler->device, handler->result); if (status != SANE_STATUS_GOOD) return (status); if (!strcmp(handler->scanner->caps[handler->scanner->source].default_format, "image/jpeg")) { status = get_JPEG_data(handler->scanner, &w, &he, &bps); } else if (!strcmp(handler->scanner->caps[handler->scanner->source].default_format, "image/png")) { status = get_PNG_data(handler->scanner, &w, &he, &bps); } else if (!strcmp(handler->scanner->caps[handler->scanner->source].default_format, "image/tiff")) { status = get_TIFF_data(handler->scanner, &w, &he, &bps); } else if (!strcmp(handler->scanner->caps[handler->scanner->source].default_format, "application/pdf")) { status = get_PDF_data(handler->scanner, &w, &he, &bps); } else { DBG(10, "Unknow image format\n"); return SANE_STATUS_INVAL; } DBG(10, "2-Size Image (%ld)[%dx%d|%dx%d]\n", handler->scanner->img_size, 0, 0, w, he); if (status != SANE_STATUS_GOOD) return (status); handler->ps.depth = 8; handler->ps.pixels_per_line = w; handler->ps.lines = he; handler->ps.bytes_per_line = w * bps; handler->ps.last_frame = SANE_TRUE; handler->ps.format = SANE_FRAME_RGB; handler->scanner->work = SANE_FALSE; // DBG(10, "NEXT Frame [%s]\n", (handler->ps.last_frame ? "Non" : "Oui")); DBG(10, "Real Size Image [%dx%d|%dx%d]\n", 0, 0, w, he); return (status); } /** * \fn SANE_Status sane_get_parameters(SANE_Handle h, SANE_Parameters *p) * \brief Function that retrieves the device parameters represented by 'h' and stores them in 'p'. * This function is normally used after "sane_start". * It's in this function that we choose to assign the default color. (Color or Monochrome) * * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL) */ SANE_Status sane_get_parameters(SANE_Handle h, SANE_Parameters *p) { DBG (10, "escl sane_get_parameters\n"); SANE_Status status = SANE_STATUS_GOOD; escl_sane_t *handler = h; if (status != SANE_STATUS_GOOD) return (status); if (p != NULL) { p->depth = 8; p->last_frame = handler->ps.last_frame; p->format = SANE_FRAME_RGB; p->pixels_per_line = handler->ps.pixels_per_line; p->lines = handler->ps.lines; p->bytes_per_line = handler->ps.bytes_per_line; } return (status); } /** * \fn SANE_Status sane_read(SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *len) * \brief Function that's used to read image data from the device represented by handle 'h'. * The argument 'buf' is a pointer to a memory area that is at least 'maxlen' bytes long. * The number of bytes returned is stored in '*len'. * --> When the call succeeds, the number of bytes returned can be anywhere in the range from 0 to 'maxlen' bytes. * * \return SANE_STATUS_GOOD (if everything is OK, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL) */ SANE_Status sane_read(SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *len) { DBG (10, "escl sane_read\n"); escl_sane_t *handler = h; SANE_Status status = SANE_STATUS_GOOD; long readbyte; if (!handler | !buf | !len) return (SANE_STATUS_INVAL); if (handler->cancel) return (SANE_STATUS_CANCELLED); if (!handler->write_scan_data) handler->write_scan_data = SANE_TRUE; if (!handler->decompress_scan_data) { if (status != SANE_STATUS_GOOD) return (status); handler->decompress_scan_data = SANE_TRUE; } if (handler->scanner->img_data == NULL) return (SANE_STATUS_INVAL); if (!handler->end_read) { readbyte = min((handler->scanner->img_size - handler->scanner->img_read), maxlen); memcpy(buf, handler->scanner->img_data + handler->scanner->img_read, readbyte); handler->scanner->img_read = handler->scanner->img_read + readbyte; *len = readbyte; if (handler->scanner->img_read == handler->scanner->img_size) handler->end_read = SANE_TRUE; else if (handler->scanner->img_read > handler->scanner->img_size) { *len = 0; handler->end_read = SANE_TRUE; free(handler->scanner->img_data); handler->scanner->img_data = NULL; return (SANE_STATUS_INVAL); } } else { SANE_Status job = SANE_STATUS_UNSUPPORTED; *len = 0; free(handler->scanner->img_data); handler->scanner->img_data = NULL; if (handler->scanner->source != PLATEN) { SANE_Bool next_page = SANE_FALSE; SANE_Status st = escl_status(handler->device, handler->scanner->source, handler->result, &job); DBG(10, "eSCL : command returned status %s\n", sane_strstatus(st)); if (_go_next_page(st, job) == SANE_STATUS_GOOD) next_page = SANE_TRUE; handler->scanner->work = SANE_TRUE; handler->ps.last_frame = !next_page; } return SANE_STATUS_EOF; } return (SANE_STATUS_GOOD); } SANE_Status sane_get_select_fd(SANE_Handle __sane_unused__ h, SANE_Int __sane_unused__ *fd) { return (SANE_STATUS_UNSUPPORTED); } SANE_Status sane_set_io_mode(SANE_Handle __sane_unused__ handle, SANE_Bool __sane_unused__ non_blocking) { return (SANE_STATUS_UNSUPPORTED); } /** * \fn void escl_curl_url(CURL *handle, const ESCL_Device *device, SANE_String_Const path) * \brief Uses the device info in 'device' and the path from 'path' to construct * a full URL. Sets this URL and any necessary connection options into * 'handle'. */ void escl_curl_url(CURL *handle, const ESCL_Device *device, SANE_String_Const path) { int url_len; char *url; url_len = snprintf(NULL, 0, "%s://%s:%d%s", (device->https ? "https" : "http"), device->ip_address, device->port_nb, path); url_len++; url = (char *)malloc(url_len); snprintf(url, url_len, "%s://%s:%d%s", (device->https ? "https" : "http"), device->ip_address, device->port_nb, path); DBG( 1, "escl_curl_url: URL: %s\n", url ); curl_easy_setopt(handle, CURLOPT_URL, url); free(url); if (device->https) { DBG( 1, "Ignoring safety certificates, use https\n"); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); } if (device->unix_socket != NULL) { DBG( 1, "Using local socket %s\n", device->unix_socket ); curl_easy_setopt(handle, CURLOPT_UNIX_SOCKET_PATH, device->unix_socket); } }