summaryrefslogtreecommitdiff
path: root/backend/escl
diff options
context:
space:
mode:
Diffstat (limited to 'backend/escl')
-rw-r--r--backend/escl/escl.c777
-rw-r--r--backend/escl/escl.h171
-rw-r--r--backend/escl/escl_capabilities.c377
-rw-r--r--backend/escl/escl_devices.c185
-rw-r--r--backend/escl/escl_jpeg.c230
-rw-r--r--backend/escl/escl_newjob.c241
-rw-r--r--backend/escl/escl_png.c193
-rw-r--r--backend/escl/escl_reset.c75
-rw-r--r--backend/escl/escl_scan.c99
-rw-r--r--backend/escl/escl_status.c176
-rw-r--r--backend/escl/escl_tiff.c119
11 files changed, 2643 insertions, 0 deletions
diff --git a/backend/escl/escl.c b/backend/escl/escl.c
new file mode 100644
index 0000000..8df6c5c
--- /dev/null
+++ b/backend/escl/escl.c
@@ -0,0 +1,777 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <setjmp.h>
+
+#include <curl/curl.h>
+
+#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;
+ SANE_String_Const name;
+ char *result;
+ ESCL_ScanParam param;
+ SANE_Option_Descriptor opt[NUM_OPTIONS];
+ Option_Value val[NUM_OPTIONS];
+ capabilities_t *scanner;
+ SANE_Range x_range;
+ SANE_Range y_range;
+ SANE_Bool cancel;
+ SANE_Bool write_scan_data;
+ SANE_Bool decompress_scan_data;
+ SANE_Bool end_read;
+ SANE_Parameters ps;
+} escl_sane_t;
+
+/**
+ * \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)
+{
+ ++num_devices;
+ current->next = list_devices_primary;
+ list_devices_primary = current;
+ return (SANE_STATUS_GOOD);
+}
+
+/**
+ * \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)
+{
+ 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 && current->port_nb == port_nb
+ && strcmp(current->type, type) == 0)
+ return (SANE_STATUS_GOOD);
+ }
+ current = malloc(sizeof(*current));
+ if (current == NULL)
+ return (SANE_STATUS_NO_MEM);
+ memset(current, 0, sizeof(*current));
+ current->port_nb = port_nb;
+ current->model_name = strdup(model_name);
+ 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);
+}
+
+/**
+ * \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)
+{
+ SANE_Device *sdev = (SANE_Device*) calloc(1, sizeof(SANE_Device));
+ char tmp[PATH_MAX] = { 0 };
+
+ if (strcmp(cdev->type, "_uscan._tcp") == 0 || strcmp(cdev->type, "http") == 0)
+ snprintf(tmp, sizeof(tmp), "http://%s:%d", cdev->ip_address, cdev->port_nb);
+ else
+ snprintf(tmp, sizeof(tmp), "https://%s:%d", cdev->ip_address, cdev->port_nb);
+ DBG( 1, "Escl add device : %s\n", tmp);
+ sdev->name = strdup(tmp);
+ sdev->model = strdup(cdev->model_name);
+ sdev->vendor = strdup("ESCL");
+ sdev->type = strdup("flatbed scanner");
+ return (sdev);
+}
+
+/**
+ * \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;
+ static int count = 0;
+ static ESCL_Device *escl_device = NULL;
+
+ if (strncmp(line, "[device]", 8) == 0) {
+ count = 0;
+ escl_device = (ESCL_Device*)calloc(1, sizeof(ESCL_Device));
+ }
+ if (strncmp(line, "ip", 2) == 0) {
+ const char *ip_space = sanei_config_skip_whitespace(line + 2);
+ if (escl_device != NULL && ip_space != NULL) {
+ count++;
+ escl_device->ip_address = strdup(ip_space);
+ }
+ }
+ if (sscanf(line, "port %i", &port) == 1 && port != 0) {
+ const char *port_space = sanei_config_skip_whitespace(line + 4);
+ if (escl_device != NULL && port_space != NULL) {
+ count++;
+ escl_device->port_nb = port;
+ }
+ }
+ if (strncmp(line, "model", 5) == 0) {
+ const char *model_space = sanei_config_skip_whitespace(line + 5);
+ if (escl_device != NULL && model_space != NULL) {
+ count++;
+ escl_device->model_name = strdup(model_space);
+ }
+ }
+ if (strncmp(line, "type", 4) == 0) {
+ const char *type_space = sanei_config_skip_whitespace(line + 4);
+ if (escl_device != NULL && type_space != NULL) {
+ count++;
+ escl_device->type = strdup(type_space);
+ }
+ }
+ if (count == 4)
+ return (escl_add_in_list(escl_device));
+ return (SANE_STATUS_GOOD);
+}
+
+/**
+ * \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;
+}
+
+/**
+ * \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, escl_sane_t *s)
+{
+ DBG (10, "escl init_options\n");
+ SANE_Status status = SANE_STATUS_GOOD;
+ int i = 0;
+
+ if (name == NULL)
+ return (SANE_STATUS_INVAL);
+ 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_range.min = 0;
+ s->x_range.max = s->scanner->MaxWidth - s->scanner->MinWidth;
+ s->x_range.quant = 1;
+ s->y_range.min = 0;
+ s->y_range.max = s->scanner->MaxHeight - s->scanner->MinHeight;
+ s->y_range.quant = 1;
+ 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->ColorModes;
+ s->val[OPT_MODE].s = (char *)strdup(s->scanner->ColorModes[0]);
+ s->opt[OPT_MODE].size = max_string_size(s->scanner->ColorModes);
+ s->scanner->default_color = (char *)strdup(s->scanner->ColorModes[0]);
+
+ 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->SupportedResolutions;
+ s->val[OPT_RESOLUTION].w = s->scanner->SupportedResolutions[1];
+ s->scanner->default_resolution = s->scanner->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 = "";
+ 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].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_X].constraint.range = &s->x_range;
+ s->val[OPT_TL_X].w = s->scanner->RiskyLeftMargin;
+
+ 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_PIXEL;
+ s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_Y].constraint.range = &s->y_range;
+ s->val[OPT_TL_Y].w = s->scanner->RiskyTopMargin;
+
+ 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_PIXEL;
+ s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_X].constraint.range = &s->x_range;
+ s->val[OPT_BR_X].w = s->scanner->MaxWidth;
+
+ 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_PIXEL;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_Y].constraint.range = &s->y_range;
+ s->val[OPT_BR_Y].w = s->scanner->MaxHeight;
+ return (status);
+}
+
+/**
+ * \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);
+ status = escl_status(name);
+ if (status != SANE_STATUS_GOOD)
+ return (status);
+ handler = (escl_sane_t *)calloc(1, sizeof(escl_sane_t));
+ if (handler == NULL)
+ return (SANE_STATUS_NO_MEM);
+ handler->name = strdup(name);
+ handler->scanner = escl_capabilities(name, &status);
+ if (status != SANE_STATUS_GOOD)
+ return (status);
+ status = init_options(name, handler);
+ if (status != SANE_STATUS_GOOD)
+ return (status);
+ handler->ps.depth = 8;
+ handler->ps.last_frame = SANE_TRUE;
+ handler->ps.format = SANE_FRAME_RGB;
+ handler->ps.pixels_per_line = handler->val[OPT_BR_X].w;
+ handler->ps.lines = handler->val[OPT_BR_Y].w;
+ handler->ps.bytes_per_line = handler->ps.pixels_per_line * 3;
+ status = sane_get_parameters(handler, 0);
+ if (status != SANE_STATUS_GOOD)
+ 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->cancel = SANE_TRUE;
+ escl_scanner(handler->name, handler->result);
+}
+
+/**
+ * \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) {
+ free(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_NUM_OPTS:
+ case OPT_RESOLUTION:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_PREVIEW:
+ case OPT_GRAY_PREVIEW:
+ *(SANE_Word *) v = handler->val[n].w;
+ break;
+ 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_PREVIEW:
+ case OPT_GRAY_PREVIEW:
+ handler->val[n].w = *(SANE_Word *) v;
+ if (i && handler->val[n].w != *(SANE_Word *) v)
+ *i |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT;
+ handler->val[n].w = *(SANE_Word *) v;
+ break;
+ case OPT_RESOLUTION:
+ handler->val[n].w = *(SANE_Word *) v;
+ 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 (i)
+ *i |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT;
+ break;
+ default:
+ break;
+ }
+ }
+ return (SANE_STATUS_GOOD);
+}
+
+/**
+ * \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->name == NULL)
+ 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;
+ handler->scanner->height = handler->val[OPT_BR_Y].w;
+ handler->scanner->width = handler->val[OPT_BR_X].w;
+ handler->scanner->pos_x = handler->val[OPT_TL_X].w;
+ handler->scanner->pos_y = handler->val[OPT_TL_Y].w;
+ if(handler->scanner->default_color)
+ free(handler->scanner->default_color);
+ if (handler->val[OPT_PREVIEW].w == SANE_TRUE)
+ {
+ int i = 0, val = 9999;;
+ if (handler->val[OPT_GRAY_PREVIEW].w == SANE_TRUE ||
+ !strncasecmp(handler->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY, 3))
+ handler->scanner->default_color = strdup("Grayscale8");
+ else
+ handler->scanner->default_color = strdup("RGB24");
+ for (i = 1; i < handler->scanner->SupportedResolutionsSize; i++)
+ {
+ if (val > handler->scanner->SupportedResolutions[i])
+ val = handler->scanner->SupportedResolutions[i];
+ }
+ handler->scanner->default_resolution = val;
+ }
+ else
+ {
+ handler->scanner->default_resolution = handler->val[OPT_RESOLUTION].w;
+ if (!strncasecmp(handler->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY, 3))
+ handler->scanner->default_color = strdup("Grayscale8");
+ else
+ handler->scanner->default_color = strdup("RGB24");
+ }
+ handler->result = escl_newjob(handler->scanner, handler->name, &status);
+ if (status != SANE_STATUS_GOOD)
+ return (status);
+ status = escl_scan(handler->scanner, handler->name, handler->result);
+ if (status != SANE_STATUS_GOOD)
+ return (status);
+ if (!strcmp(handler->scanner->default_format, "image/jpeg"))
+ {
+ status = get_JPEG_data(handler->scanner, &w, &he, &bps);
+ }
+ else if (!strcmp(handler->scanner->default_format, "image/png"))
+ {
+ status = get_PNG_data(handler->scanner, &w, &he, &bps);
+ }
+ else if (!strcmp(handler->scanner->default_format, "image/tiff"))
+ {
+ status = get_TIFF_data(handler->scanner, &w, &he, &bps);
+ }
+ else
+ return SANE_STATUS_INVAL;
+
+ 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;
+ 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 = SANE_TRUE;
+ 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 {
+ *len = 0;
+ free(handler->scanner->img_data);
+ handler->scanner->img_data = NULL;
+ 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);
+}
diff --git a/backend/escl/escl.h b/backend/escl/escl.h
new file mode 100644
index 0000000..82910bd
--- /dev/null
+++ b/backend/escl/escl.h
@@ -0,0 +1,171 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+
+#ifndef __ESCL_H__
+#define __ESCL_H__
+
+#include "../include/sane/config.h"
+
+#if !(HAVE_LIBCURL && defined(WITH_AVAHI) && defined(HAVE_LIBXML2))
+#error "The escl backend requires libcurl, libavahi and libxml2"
+#endif
+
+#ifndef HAVE_LIBJPEG
+/* FIXME: Make JPEG support optional.
+ Support for PNG and PDF is to be added later but currently only
+ JPEG is supported. Absence of JPEG support makes the backend a
+ no-op at present.
+ */
+#error "The escl backend currently requires libjpeg"
+#endif
+
+#include "../include/sane/sane.h"
+
+#include <stdio.h>
+
+#ifndef BACKEND_NAME
+#define BACKEND_NAME escl
+#endif
+
+#define DEBUG_NOT_STATIC
+#include "../include/sane/sanei_debug.h"
+
+#ifndef DBG_LEVEL
+#define DBG_LEVEL PASTE(sanei_debug_, BACKEND_NAME)
+#endif
+#ifndef NDEBUG
+# define DBGDUMP(level, buf, size) \
+ do { if (DBG_LEVEL >= (level)) sanei_escl_dbgdump(buf, size); } while (0)
+#else
+# define DBGDUMP(level, buf, size)
+#endif
+
+#define ESCL_CONFIG_FILE "escl.conf"
+
+typedef struct {
+ int p1_0;
+ int p2_0;
+ int p3_3;
+ int DocumentType;
+ int p4_0;
+ int p5_0;
+ int p6_1;
+ int reserve[11];
+} ESCL_SCANOPTS;
+
+
+typedef struct ESCL_Device {
+ struct ESCL_Device *next;
+
+ char *model_name;
+ int port_nb;
+ char *ip_address;
+ char *type;
+} ESCL_Device;
+
+typedef struct capabilities
+{
+ int height;
+ int width;
+ int pos_x;
+ int pos_y;
+ SANE_String default_color;
+ SANE_String default_format;
+ SANE_Int default_resolution;
+ int MinWidth;
+ int MaxWidth;
+ int MinHeight;
+ int MaxHeight;
+ int MaxScanRegions;
+ SANE_String_Const *ColorModes;
+ int ColorModesSize;
+ SANE_String_Const *ContentTypes;
+ int ContentTypesSize;
+ SANE_String_Const *DocumentFormats;
+ int DocumentFormatsSize;
+ SANE_Int *SupportedResolutions;
+ int SupportedResolutionsSize;
+ SANE_String_Const *SupportedIntents;
+ int SupportedIntentsSize;
+ SANE_String_Const SupportedIntentDefault;
+ int MaxOpticalXResolution;
+ int RiskyLeftMargin;
+ int RiskyRightMargin;
+ int RiskyTopMargin;
+ int RiskyBottomMargin;
+ FILE *tmp;
+ unsigned char *img_data;
+ long img_size;
+ long img_read;
+ int format_ext;
+} capabilities_t;
+
+typedef struct {
+ int XRes;
+ int YRes;
+ int Left;
+ int Top;
+ int Right;
+ int Bottom;
+ int ScanMode;
+ int ScanMethod;
+ ESCL_SCANOPTS opts;
+} ESCL_ScanParam;
+
+
+enum
+{
+ OPT_NUM_OPTS = 0,
+ OPT_MODE_GROUP,
+ OPT_MODE,
+ OPT_RESOLUTION,
+ OPT_PREVIEW,
+ OPT_GRAY_PREVIEW,
+
+ OPT_GEOMETRY_GROUP,
+ OPT_TL_X,
+ OPT_TL_Y,
+ OPT_BR_X,
+ OPT_BR_Y,
+ NUM_OPTIONS
+};
+
+ESCL_Device *escl_devices(SANE_Status *status);
+SANE_Status escl_device_add(int port_nb, const char *model_name, char *ip_address, char *type);
+SANE_Status escl_status(SANE_String_Const name);
+capabilities_t *escl_capabilities(SANE_String_Const name, SANE_Status *status);
+char *escl_newjob(capabilities_t *scanner, SANE_String_Const name, SANE_Status *status);
+SANE_Status escl_scan(capabilities_t *scanner, SANE_String_Const name, char *result);
+void escl_scanner(SANE_String_Const name, char *result);
+
+// JPEG
+SANE_Status get_JPEG_data(capabilities_t *scanner, int *w, int *h, int *bps);
+
+// PNG
+SANE_Status get_PNG_data(capabilities_t *scanner, int *w, int *h, int *bps);
+
+// TIFF
+SANE_Status get_TIFF_data(capabilities_t *scanner, int *w, int *h, int *bps);
+
+#endif
diff --git a/backend/escl/escl_capabilities.c b/backend/escl/escl_capabilities.c
new file mode 100644
index 0000000..690ff1e
--- /dev/null
+++ b/backend/escl/escl_capabilities.c
@@ -0,0 +1,377 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+
+#include "../include/sane/saneopts.h"
+
+struct cap
+{
+ char *memory;
+ size_t size;
+};
+
+/**
+ * \fn static SANE_String_Const convert_elements(SANE_String_Const str)
+ * \brief Function that converts the 'color modes' of the scanner (color/gray) to be understood by SANE.
+ *
+ * \return SANE_VALUE_SCAN_MODE_GRAY / SANE_VALUE_SCAN_MODE_COLOR ; NULL otherwise
+ */
+static SANE_String_Const
+convert_elements(SANE_String_Const str)
+{
+ if (strcmp(str, "Grayscale8") == 0)
+ return (SANE_VALUE_SCAN_MODE_GRAY);
+ else if (strcmp(str, "RGB24") == 0)
+ return (SANE_VALUE_SCAN_MODE_COLOR);
+ return (NULL);
+}
+
+/**
+ * \fn static SANE_String_Const *char_to_array(SANE_String_Const *tab, int *tabsize, SANE_String_Const mode, int good_array)
+ * \brief Function that creates the character arrays to put inside :
+ * the 'color modes', the 'content types', the 'document formats' and the 'supported intents'.
+ *
+ * \return board (the allocated array)
+ */
+static SANE_String_Const *
+char_to_array(SANE_String_Const *tab, int *tabsize, SANE_String_Const mode, int good_array)
+{
+ SANE_String_Const *board = NULL;
+ int i = 0;
+ SANE_String_Const convert = NULL;
+
+ if (mode == NULL)
+ return (tab);
+ if (good_array != 0) {
+ convert = convert_elements(mode);
+ if (convert == NULL)
+ return (tab);
+ }
+ else
+ convert = mode;
+ for (i = 0; i < (*tabsize); i++) {
+ if (strcmp(tab[i], convert) == 0)
+ return (tab);
+ }
+ (*tabsize)++;
+ if (*tabsize == 1)
+ board = (SANE_String_Const *)malloc(sizeof(SANE_String_Const) * (*tabsize) + 1);
+ else
+ board = (SANE_String_Const *)realloc(tab, sizeof(SANE_String_Const) * (*tabsize) + 1);
+ board[*tabsize - 1] = (SANE_String_Const)strdup(convert);
+ board[*tabsize] = NULL;
+ return (board);
+}
+
+/**
+ * \fn static SANE_Int *int_to_array(SANE_Int *tab, int *tabsize, int cont)
+ * \brief Function that creates the integer array to put inside the 'supported resolutions'.
+ *
+ * \return board (the allocated array)
+ */
+static SANE_Int *
+int_to_array(SANE_Int *tab, int *tabsize, int cont)
+{
+ SANE_Int *board = NULL;
+ int i = 0;
+
+ for (i = 0; i < (*tabsize); i++) {
+ if (tab[i] == cont)
+ return (tab);
+ }
+ (*tabsize)++;
+ if (*tabsize == 1) {
+ (*tabsize)++;
+ board = malloc(sizeof(SANE_Int *) * (*tabsize) + 1);
+ }
+ else
+ board = realloc(tab, sizeof(SANE_Int *) * (*tabsize) + 1);
+ board[0] = *tabsize - 1;
+ board[*tabsize - 1] = cont;
+ board[*tabsize] = -1;
+ return (board);
+}
+
+/**
+ * \fn static size_t memory_callback_c(void *contents, size_t size, size_t nmemb, void *userp)
+ * \brief Callback function that stocks in memory the content of the scanner capabilities.
+ *
+ * \return realsize (size of the content needed -> the scanner capabilities)
+ */
+static size_t
+memory_callback_c(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct cap *mem = (struct cap *)userp;
+
+ char *str = realloc(mem->memory, mem->size + realsize + 1);
+ if (str == NULL) {
+ fprintf(stderr, "not enough memory (realloc returned NULL)\n");
+ return (0);
+ }
+ mem->memory = str;
+ memcpy(&(mem->memory[mem->size]), contents, realsize);
+ mem->size = mem->size + realsize;
+ mem->memory[mem->size] = 0;
+ return (realsize);
+}
+
+/**
+ * \fn static int find_nodes_c(xmlNode *node)
+ * \brief Function that browses the xml file and parses it, to find the xml children node.
+ * --> to recover the scanner capabilities.
+ *
+ * \return 0 if a xml child node is found, 1 otherwise
+ */
+static int
+find_nodes_c(xmlNode *node)
+{
+ xmlNode *child = node->children;
+
+ while (child) {
+ if (child->type == XML_ELEMENT_NODE)
+ return (0);
+ child = child->next;
+ }
+ return (1);
+}
+
+/**
+ * \fn static int find_valor_of_array_variables(xmlNode *node, capabilities_t *scanner)
+ * \brief Function that searchs in the xml file if a scanner capabilitie stocked
+ * in one of the created array (character/integer array) is found.
+ *
+ * \return 0
+ */
+static int
+find_valor_of_array_variables(xmlNode *node, capabilities_t *scanner)
+{
+ const char *name = (const char *)node->name;
+ if (strcmp(name, "ColorMode") == 0)
+ scanner->ColorModes = char_to_array(scanner->ColorModes, &scanner->ColorModesSize, (SANE_String_Const)xmlNodeGetContent(node), 1);
+ else if (strcmp(name, "ContentType") == 0)
+ scanner->ContentTypes = char_to_array(scanner->ContentTypes, &scanner->ContentTypesSize, (SANE_String_Const)xmlNodeGetContent(node), 0);
+ else if (strcmp(name, "DocumentFormat") == 0)
+ {
+ int i = 0;
+ scanner->DocumentFormats = char_to_array(scanner->DocumentFormats, &scanner->DocumentFormatsSize, (SANE_String_Const)xmlNodeGetContent(node), 0);
+ for(; i < scanner->DocumentFormatsSize; i++)
+ {
+ if (scanner->default_format == NULL && !strcmp(scanner->DocumentFormats[i], "image/jpeg"))
+ {
+ scanner->default_format = strdup("image/jpeg");
+ }
+#if(defined HAVE_LIBPNG)
+ else if(!strcmp(scanner->DocumentFormats[i], "image/png") && (scanner->default_format == NULL || strcmp(scanner->default_format, "image/tiff")))
+ {
+ if (scanner->default_format)
+ free(scanner->default_format);
+ scanner->default_format = strdup("image/png");
+ }
+#endif
+#if(defined HAVE_TIFFIO_H)
+ else if(!strcmp(scanner->DocumentFormats[i], "image/tiff"))
+ {
+ if (scanner->default_format)
+ free(scanner->default_format);
+ scanner->default_format = strdup("image/tiff");
+ }
+#endif
+ }
+ fprintf(stderr, "Capability : [%s]\n", scanner->default_format);
+ }
+ else if (strcmp(name, "DocumentFormatExt") == 0)
+ scanner->format_ext = 1;
+ else if (strcmp(name, "Intent") == 0)
+ scanner->SupportedIntents = char_to_array(scanner->SupportedIntents, &scanner->SupportedIntentsSize, (SANE_String_Const)xmlNodeGetContent(node), 0);
+ else if (strcmp(name, "XResolution") == 0)
+ scanner->SupportedResolutions = int_to_array(scanner->SupportedResolutions, &scanner->SupportedResolutionsSize, atoi((const char *)xmlNodeGetContent(node)));
+ return (0);
+}
+
+/**
+ * \fn static int find_value_of_int_variables(xmlNode *node, capabilities_t *scanner)
+ * \brief Function that searchs in the xml file if a integer scanner capabilitie is found.
+ * The integer scanner capabilities that are interesting are :
+ * MinWidth, MaxWidth, MaxHeight, MinHeight, MaxScanRegions, MaxOpticalXResolution,
+ * RiskyLeftMargin, RiskyRightMargin, RiskyTopMargin, RiskyBottomMargin.
+ *
+ * \return 0
+ */
+static int
+find_value_of_int_variables(xmlNode *node, capabilities_t *scanner)
+{
+ int MaxWidth = 0;
+ int MaxHeight = 0;
+ const char *name = (const char *)node->name;
+
+ if (strcmp(name, "MinWidth") == 0)
+ scanner->MinWidth = atoi((const char*)xmlNodeGetContent(node));
+ else if (strcmp(name, "MaxWidth") == 0) {
+ MaxWidth = atoi((const char*)xmlNodeGetContent(node));
+ if (scanner->MaxWidth == 0 || MaxWidth < scanner->MaxWidth)
+ scanner->MaxWidth = atoi((const char *)xmlNodeGetContent(node));
+ }
+ else if (strcmp(name, "MinHeight") == 0)
+ scanner->MinHeight = atoi((const char*)xmlNodeGetContent(node));
+ else if (strcmp(name, "MaxHeight") == 0) {
+ MaxHeight = atoi((const char*)xmlNodeGetContent(node));
+ if (scanner->MaxHeight == 0 || MaxHeight < scanner->MaxHeight)
+ scanner->MaxHeight = atoi((const char *)xmlNodeGetContent(node));
+ }
+ else if (strcmp(name, "MaxScanRegions") == 0)
+ scanner->MaxScanRegions = atoi((const char *)xmlNodeGetContent(node));
+ else if (strcmp(name, "MaxOpticalXResolution") == 0)
+ scanner->MaxOpticalXResolution = atoi((const char *)xmlNodeGetContent(node));
+ else if (strcmp(name, "RiskyLeftMargin") == 0)
+ scanner->RiskyLeftMargin = atoi((const char *)xmlNodeGetContent(node));
+ else if (strcmp(name, "RiskyRightMargin") == 0)
+ scanner->RiskyRightMargin = atoi((const char *)xmlNodeGetContent(node));
+ else if (strcmp(name, "RiskyTopMargin") == 0)
+ scanner->RiskyTopMargin = atoi((const char *)xmlNodeGetContent(node));
+ else if (strcmp(name, "RiskyBottomMargin") == 0)
+ scanner->RiskyBottomMargin = atoi((const char *)xmlNodeGetContent(node));
+ find_valor_of_array_variables(node, scanner);
+ return (0);
+}
+
+/**
+ * \fn static int find_true_variables(xmlNode *node, capabilities_t *scanner)
+ * \brief Function that searchs in the xml file if we find a scanner capabilitie stocked
+ * in one of the created array (character/integer array),
+ * or, if we find a integer scanner capabilitie.
+ *
+ * \return 0
+ */
+static int
+find_true_variables(xmlNode *node, capabilities_t *scanner)
+{
+ const char *name = (const char *)node->name;
+ if (strcmp(name, "MinWidth") == 0 ||
+ strcmp(name, "MaxWidth") == 0 ||
+ strcmp(name, "MinHeight") == 0 ||
+ strcmp(name, "MaxHeight") == 0 ||
+ strcmp(name, "MaxScanRegions") == 0 ||
+ strcmp(name, "ColorMode") == 0 ||
+ strcmp(name, "ContentType") == 0 ||
+ strcmp(name, "DocumentFormat") == 0 ||
+ strcmp(name, "XResolution") == 0 ||
+ strcmp(name, "Intent") == 0 ||
+ strcmp(name, "MaxOpticalXResolution") == 0 ||
+ strcmp(name, "RiskyLeftMargin") == 0 ||
+ strcmp(name, "RiskyRightMargin") == 0 ||
+ strcmp(name, "RiskyTopMargin") == 0 ||
+ strcmp(name, "RiskyBottomMargin") == 0 ||
+ strcmp(name, "DocumentFormatExt") == 0)
+ find_value_of_int_variables(node, scanner);
+ return (0);
+}
+
+/**
+ * \fn static int print_xml_c(xmlNode *node, capabilities_t *scanner)
+ * \brief Function that browses the xml file, node by node.
+ *
+ * \return 0
+ */
+static int
+print_xml_c(xmlNode *node, capabilities_t *scanner)
+{
+ while (node) {
+ if (node->type == XML_ELEMENT_NODE) {
+ if (find_nodes_c(node))
+ find_true_variables(node, scanner);
+ }
+ print_xml_c(node->children, scanner);
+ node = node->next;
+ }
+ return (0);
+}
+
+/**
+ * \fn capabilities_t *escl_capabilities(SANE_String_Const name, SANE_Status *status)
+ * \brief Function that finally recovers all the capabilities of the scanner, using curl.
+ * This function is called in the 'sane_open' function and it's the equivalent of
+ * the following curl command : "curl http(s)://'ip':'port'/eSCL/ScannerCapabilities".
+ *
+ * \return scanner (the structure that stocks all the capabilities elements)
+ */
+capabilities_t *
+escl_capabilities(SANE_String_Const name, SANE_Status *status)
+{
+ capabilities_t *scanner = (capabilities_t*)calloc(1, sizeof(capabilities_t));
+ CURL *curl_handle = NULL;
+ struct cap *var = NULL;
+ xmlDoc *data = NULL;
+ xmlNode *node = NULL;
+ const char *scanner_capabilities = "/eSCL/ScannerCapabilities";
+ char tmp[PATH_MAX] = { 0 };
+
+ *status = SANE_STATUS_GOOD;
+ if (name == NULL)
+ *status = SANE_STATUS_NO_MEM;
+ var = (struct cap *)calloc(1, sizeof(struct cap));
+ if (var == NULL)
+ *status = SANE_STATUS_NO_MEM;
+ var->memory = malloc(1);
+ var->size = 0;
+ curl_handle = curl_easy_init();
+ strcpy(tmp, name);
+ strcat(tmp, scanner_capabilities);
+ DBG( 1, "Get Capabilities : %s\n", tmp);
+ curl_easy_setopt(curl_handle, CURLOPT_URL, tmp);
+ if (strncmp(name, "https", 5) == 0) {
+ DBG( 1, "Ignoring safety certificates, use https\n");
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memory_callback_c);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)var);
+ if (curl_easy_perform(curl_handle) != CURLE_OK) {
+ DBG( 1, "The scanner didn't respond.\n");
+ *status = SANE_STATUS_INVAL;
+ }
+ data = xmlReadMemory(var->memory, var->size, "file.xml", NULL, 0);
+ if (data == NULL)
+ *status = SANE_STATUS_NO_MEM;
+ node = xmlDocGetRootElement(data);
+ if (node == NULL)
+ *status = SANE_STATUS_NO_MEM;
+ print_xml_c(node, scanner);
+ xmlFreeDoc(data);
+ xmlCleanupParser();
+ xmlMemoryDump();
+ curl_easy_cleanup(curl_handle);
+ free(var->memory);
+ return (scanner);
+}
diff --git a/backend/escl/escl_devices.c b/backend/escl/escl_devices.c
new file mode 100644
index 0000000..7ecbe31
--- /dev/null
+++ b/backend/escl/escl_devices.c
@@ -0,0 +1,185 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avahi-client/lookup.h>
+#include <avahi-common/error.h>
+#include <avahi-common/simple-watch.h>
+
+#include "../include/sane/sanei.h"
+
+static AvahiSimplePoll *simple_poll = NULL;
+
+/**
+ * \fn static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED
+ * AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
+ * AvahiResolverEvent event, const char *name,
+ * const char *type, const char *domain, const char *host_name,
+ * const AvahiAddress *address, uint16_t port,
+ * AvahiStringList *txt, AvahiLookupResultFlags flags,
+ * void *userdata)
+ * \brief Callback function that will check if the selected scanner follows the escl
+ * protocol or not.
+ */
+static void
+resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
+ AVAHI_GCC_UNUSED AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name,
+ const char __sane_unused__ *type,
+ const char __sane_unused__ *domain,
+ const char __sane_unused__ *host_name,
+ const AvahiAddress *address, uint16_t port, AvahiStringList *txt,
+ AvahiLookupResultFlags __sane_unused__ flags,
+ void __sane_unused__ *userdata)
+{
+ char a[AVAHI_ADDRESS_STR_MAX], *t;
+ assert(r);
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ break;
+ case AVAHI_RESOLVER_FOUND:
+ avahi_address_snprint(a, sizeof(a), address);
+ t = avahi_string_list_to_string(txt);
+ if (strstr(t, "\"rs=eSCL\"") || strstr(t, "\"rs=/eSCL\""))
+ escl_device_add(port, name, a, (char*)type);
+ }
+}
+
+/**
+ * \fn static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
+ * AvahiProtocol protocol, AvahiBrowserEvent event, const char *name,
+ * const char *type, const char *domain,
+ * AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata)
+ * \brief Callback function that will browse tanks to 'avahi' the scanners
+ * connected in network.
+ */
+static void
+browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
+ AvahiProtocol protocol, AvahiBrowserEvent event,
+ const char *name, const char *type,
+ const char *domain,
+ AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void* userdata)
+{
+ AvahiClient *c = userdata;
+ assert(b);
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+ avahi_simple_poll_quit(simple_poll);
+ return;
+ case AVAHI_BROWSER_NEW:
+ if (!(avahi_service_resolver_new(c, interface, protocol, name,
+ type, domain,
+ AVAHI_PROTO_UNSPEC, 0,
+ resolve_callback, c)))
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ if (event != AVAHI_BROWSER_CACHE_EXHAUSTED)
+ avahi_simple_poll_quit(simple_poll);
+ break;
+ }
+}
+
+/**
+ * \fn static void client_callback(AvahiClient *c, AvahiClientState state,
+ * AVAHI_GCC_UNUSED void *userdata)
+ * \brief Callback Function that quit if it doesn't find a connected scanner,
+ * possible thanks the "Hello Protocol".
+ * --> Waiting for a answer by the scanner to continue the avahi process.
+ */
+static void
+client_callback(AvahiClient *c, AvahiClientState state,
+ AVAHI_GCC_UNUSED void *userdata)
+{
+ assert(c);
+ if (state == AVAHI_CLIENT_FAILURE)
+ avahi_simple_poll_quit(simple_poll);
+}
+
+/**
+ * \fn ESCL_Device *escl_devices(SANE_Status *status)
+ * \brief Function that calls all the avahi functions and then, recovers the
+ * connected eSCL devices.
+ * This function is called in the 'sane_get_devices' function.
+ *
+ * \return NULL (the eSCL devices found)
+ */
+ESCL_Device *
+escl_devices(SANE_Status *status)
+{
+ AvahiClient *client = NULL;
+ AvahiServiceBrowser *sb = NULL;
+ int error;
+
+ *status = SANE_STATUS_GOOD;
+ if (!(simple_poll = avahi_simple_poll_new())) {
+ DBG( 1, "Failed to create simple poll object.\n");
+ *status = SANE_STATUS_INVAL;
+ goto fail;
+ }
+ client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0,
+ client_callback, NULL, &error);
+ if (!client) {
+ DBG( 1, "Failed to create client: %s\n", avahi_strerror(error));
+ *status = SANE_STATUS_INVAL;
+ goto fail;
+ }
+ if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, "_uscan._tcp",
+ NULL, 0, browse_callback, client))) {
+ DBG( 1, "Failed to create service browser: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
+ *status = SANE_STATUS_INVAL;
+ goto fail;
+ }
+ if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_uscans._tcp", NULL, 0,
+ browse_callback, client))) {
+ DBG( 1, "Failed to create service browser: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
+ *status = SANE_STATUS_INVAL;
+ goto fail;
+ }
+ avahi_simple_poll_loop(simple_poll);
+fail:
+ if (sb)
+ avahi_service_browser_free(sb);
+ if (client)
+ avahi_client_free(client);
+ if (simple_poll)
+ avahi_simple_poll_free(simple_poll);
+ return (NULL);
+}
diff --git a/backend/escl/escl_jpeg.c b/backend/escl/escl_jpeg.c
new file mode 100644
index 0000000..d6287ef
--- /dev/null
+++ b/backend/escl/escl_jpeg.c
@@ -0,0 +1,230 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include "../include/sane/sanei.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if(defined HAVE_LIBJPEG)
+# include <jpeglib.h>
+#endif
+
+#include <setjmp.h>
+
+#define INPUT_BUFFER_SIZE 4096
+
+#if(defined HAVE_LIBJPEG)
+struct my_error_mgr
+{
+ struct jpeg_error_mgr errmgr;
+ jmp_buf escape;
+};
+
+typedef struct
+{
+ struct jpeg_source_mgr pub;
+ FILE *ctx;
+ unsigned char buffer[INPUT_BUFFER_SIZE];
+} my_source_mgr;
+
+/**
+ * \fn static boolean fill_input_buffer(j_decompress_ptr cinfo)
+ * \brief Called in the "skip_input_data" function.
+ *
+ * \return TRUE (everything is OK)
+ */
+static boolean
+fill_input_buffer(j_decompress_ptr cinfo)
+{
+ my_source_mgr *src = (my_source_mgr *) cinfo->src;
+ int nbytes = 0;
+
+ nbytes = fread(src->buffer, 1, INPUT_BUFFER_SIZE, src->ctx);
+ if (nbytes <= 0) {
+ src->buffer[0] = (unsigned char) 0xFF;
+ src->buffer[1] = (unsigned char) JPEG_EOI;
+ nbytes = 2;
+ }
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = nbytes;
+ return (TRUE);
+}
+
+/**
+ * \fn static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+ * \brief Called in the "jpeg_RW_src" function.
+ */
+static void
+skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+ my_source_mgr *src = (my_source_mgr *) cinfo->src;
+
+ if (num_bytes > 0) {
+ while (num_bytes > (long) src->pub.bytes_in_buffer) {
+ num_bytes -= (long) src->pub.bytes_in_buffer;
+ (void) src->pub.fill_input_buffer(cinfo);
+ }
+ src->pub.next_input_byte += (size_t) num_bytes;
+ src->pub.bytes_in_buffer -= (size_t) num_bytes;
+ }
+}
+
+static void
+term_source(j_decompress_ptr __sane_unused__ cinfo)
+{
+ return;
+}
+
+static void
+init_source(j_decompress_ptr __sane_unused__ cinfo)
+{
+ return;
+}
+
+/**
+ * \fn static void jpeg_RW_src(j_decompress_ptr cinfo, FILE *ctx)
+ * \brief Called in the "escl_sane_decompressor" function.
+ */
+static void
+jpeg_RW_src(j_decompress_ptr cinfo, FILE *ctx)
+{
+ my_source_mgr *src;
+
+ if (cinfo->src == NULL) {
+ cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_source_mgr));
+ src = (my_source_mgr *) cinfo->src;
+ }
+ src = (my_source_mgr *) cinfo->src;
+ src->pub.init_source = init_source;
+ src->pub.fill_input_buffer = fill_input_buffer;
+ src->pub.skip_input_data = skip_input_data;
+ src->pub.resync_to_restart = jpeg_resync_to_restart;
+ src->pub.term_source = term_source;
+ src->ctx = ctx;
+ src->pub.bytes_in_buffer = 0;
+ src->pub.next_input_byte = NULL;
+}
+
+static void
+my_error_exit(j_common_ptr cinfo)
+{
+ struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
+
+ longjmp(err->escape, 1);
+}
+
+static void
+output_no_message(j_common_ptr __sane_unused__ cinfo)
+{
+}
+
+/**
+ * \fn SANE_Status escl_sane_decompressor(escl_sane_t *handler)
+ * \brief Function that aims to decompress the jpeg image to SANE be able to read the image.
+ * This function is called in the "sane_read" function.
+ *
+ * \return SANE_STATUS_GOOD (if everything is OK, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
+ */
+SANE_Status
+get_JPEG_data(capabilities_t *scanner, int *w, int *h, int *bps)
+{
+ int start = 0;
+ struct jpeg_decompress_struct cinfo;
+ JSAMPROW rowptr[1];
+ unsigned char *surface = NULL;
+ struct my_error_mgr jerr;
+ int lineSize = 0;
+
+ if (scanner->tmp == NULL)
+ return (SANE_STATUS_INVAL);
+ fseek(scanner->tmp, SEEK_SET, 0);
+ start = ftell(scanner->tmp);
+ cinfo.err = jpeg_std_error(&jerr.errmgr);
+ jerr.errmgr.error_exit = my_error_exit;
+ jerr.errmgr.output_message = output_no_message;
+ if (setjmp(jerr.escape)) {
+ jpeg_destroy_decompress(&cinfo);
+ if (surface != NULL)
+ free(surface);
+ DBG( 1, "Escl Jpeg : Error reading jpeg\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ jpeg_create_decompress(&cinfo);
+ jpeg_RW_src(&cinfo, scanner->tmp);
+ jpeg_read_header(&cinfo, TRUE);
+ cinfo.out_color_space = JCS_RGB;
+ cinfo.quantize_colors = FALSE;
+ jpeg_calc_output_dimensions(&cinfo);
+ surface = malloc(cinfo.output_width * cinfo.output_height * cinfo.output_components);
+ if (surface == NULL) {
+ jpeg_destroy_decompress(&cinfo);
+ fseek(scanner->tmp, start, SEEK_SET);
+ DBG( 1, "Escl Jpeg : Memory allocation problem\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_NO_MEM);
+ }
+ lineSize = cinfo.output_width * cinfo.output_components;
+ jpeg_start_decompress(&cinfo);
+ while (cinfo.output_scanline < cinfo.output_height) {
+ rowptr[0] = (JSAMPROW)surface + (lineSize * cinfo.output_scanline);
+ jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
+ }
+ scanner->img_data = surface;
+ scanner->img_size = lineSize * cinfo.output_height;
+ scanner->img_read = 0;
+ *w = cinfo.output_width;
+ *h = cinfo.output_height;
+ *bps = cinfo.output_components;
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ return (SANE_STATUS_GOOD);
+}
+#else
+
+SANE_Status
+get_JPEG_data(capabilities_t __sane_unused__ *scanner,
+ int __sane_unused__ *w,
+ int __sane_unused__ *h,
+ int __sane_unused__ *bps)
+{
+ return (SANE_STATUS_INVAL);
+}
+
+#endif
diff --git a/backend/escl/escl_newjob.c b/backend/escl/escl_newjob.c
new file mode 100644
index 0000000..279b9df
--- /dev/null
+++ b/backend/escl/escl_newjob.c
@@ -0,0 +1,241 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#ifdef PATH_MAX
+# undef PATH_MAX
+#endif
+
+#define PATH_MAX 4096
+
+struct uploading
+{
+ const char *read_data;
+ size_t size;
+};
+
+struct downloading
+{
+ char *memory;
+ size_t size;
+};
+
+static const char settings[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
+ "<scan:ScanSettings xmlns:pwg=\"http://www.pwg.org/schemas/2010/12/sm\" xmlns:scan=\"http://schemas.hp.com/imaging/escl/2011/05/03\">" \
+ " <pwg:Version>2.0</pwg:Version>" \
+ " <pwg:ScanRegions>" \
+ " <pwg:ScanRegion>" \
+ " <pwg:ContentRegionUnits>escl:ThreeHundredthsOfInches</pwg:ContentRegionUnits>" \
+ " <pwg:Height>%d</pwg:Height>" \
+ " <pwg:Width>%d</pwg:Width>" \
+ " <pwg:XOffset>%d</pwg:XOffset>" \
+ " <pwg:YOffset>%d</pwg:YOffset>" \
+ " </pwg:ScanRegion>" \
+ " </pwg:ScanRegions>" \
+ " <pwg:DocumentFormat>%s</pwg:DocumentFormat>" \
+ "%s" \
+ " <scan:ColorMode>%s</scan:ColorMode>" \
+ " <scan:XResolution>%d</scan:XResolution>" \
+ " <scan:YResolution>%d</scan:YResolution>" \
+ " <pwg:InputSource>Platen</pwg:InputSource>" \
+ "</scan:ScanSettings>";
+
+static char formatExtJPEG[] =
+ " <scan:DocumentFormatExt>image/jpeg</scan:DocumentFormatExt>";
+
+static char formatExtPNG[] =
+ " <scan:DocumentFormatExt>image/png</scan:DocumentFormatExt>";
+
+static char formatExtTIFF[] =
+ " <scan:DocumentFormatExt>image/tiff</scan:DocumentFormatExt>";
+
+/**
+ * \fn static size_t download_callback(void *str, size_t size, size_t nmemb, void *userp)
+ * \brief Callback function that stocks in memory the content of the 'job'. Example below :
+ * "Trying 192.168.14.150...
+ * TCP_NODELAY set
+ * Connected to 192.168.14.150 (192.168.14.150) port 80
+ * POST /eSCL/ScanJobs HTTP/1.1
+ * Host: 192.168.14.150
+ * User-Agent: curl/7.55.1
+ * Accept: /
+ * Content-Length: 605
+ * Content-Type: application/x-www-form-urlencoded
+ * upload completely sent off: 605 out of 605 bytes
+ * < HTTP/1.1 201 Created
+ * < MIME-Version: 1.0
+ * < Location: http://192.168.14.150/eSCL/ScanJobs/22b54fd0-027b-1000-9bd0-f4a99726e2fa
+ * < Content-Length: 0
+ * < Connection: close
+ * <
+ * Closing connection 0"
+ *
+ * \return realsize (size of the content needed -> the 'job')
+ */
+static size_t
+download_callback(void *str, size_t size, size_t nmemb, void *userp)
+{
+ struct downloading *download = (struct downloading *)userp;
+ size_t realsize = size * nmemb;
+ char *content = realloc(download->memory, download->size + realsize + 1);
+
+ if (content == NULL) {
+ DBG( 1, "Not enough memory (realloc returned NULL)\n");
+ return (0);
+ }
+ download->memory = content;
+ memcpy(&(download->memory[download->size]), str, realsize);
+ download->size = download->size + realsize;
+ download->memory[download->size] = 0;
+ return (realsize);
+}
+
+/**
+ * \fn char *escl_newjob (capabilities_t *scanner, SANE_String_Const name, SANE_Status *status)
+ * \brief Function that, using curl, uploads the data (composed by the scanner capabilities) to the
+ * server to download the 'job' and recover the 'new job' (char *result), in LOCATION.
+ * This function is called in the 'sane_start' function and it's the equivalent of the
+ * following curl command : "curl -v POST -d cap.xml http(s)://'ip':'port'/eSCL/ScanJobs".
+ *
+ * \return result (the 'new job', situated in LOCATION)
+ */
+char *
+escl_newjob (capabilities_t *scanner, SANE_String_Const name, SANE_Status *status)
+{
+ CURL *curl_handle = NULL;
+ struct uploading *upload = NULL;
+ struct downloading *download = NULL;
+ const char *scan_jobs = "/eSCL/ScanJobs";
+ char cap_data[PATH_MAX] = { 0 };
+ char job_cmd[PATH_MAX] = { 0 };
+ char *location = NULL;
+ char *result = NULL;
+ char *temporary = NULL;
+ char *f_ext = "";
+ char *format_ext = NULL;
+
+ *status = SANE_STATUS_GOOD;
+ if (name == NULL || scanner == NULL) {
+ *status = SANE_STATUS_NO_MEM;
+ DBG( 1, "Create NewJob : the name or the scan are invalid.\n");
+ return (NULL);
+ }
+ upload = (struct uploading *)calloc(1, sizeof(struct uploading));
+ if (upload == NULL) {
+ *status = SANE_STATUS_NO_MEM;
+ DBG( 1, "Create NewJob : memory allocation failure\n");
+ return (NULL);
+ }
+ download = (struct downloading *)calloc(1, sizeof(struct downloading));
+ if (download == NULL) {
+ free(upload);
+ DBG( 1, "Create NewJob : memory allocation failure\n");
+ *status = SANE_STATUS_NO_MEM;
+ return (NULL);
+ }
+ curl_handle = curl_easy_init();
+ if (scanner->format_ext == 1)
+ {
+ if (!strcmp(scanner->default_format, "image/jpeg"))
+ format_ext = formatExtJPEG;
+ else if (!strcmp(scanner->default_format, "image/png"))
+ format_ext = formatExtPNG;
+ else if (!strcmp(scanner->default_format, "image/tiff"))
+ format_ext = formatExtTIFF;
+ else
+ format_ext = f_ext;
+ }
+ else
+ format_ext = f_ext;
+ DBG( 1, "Create NewJob : %s\n", scanner->default_format);
+ if (curl_handle != NULL) {
+ snprintf(cap_data, sizeof(cap_data), settings, scanner->height, scanner->width, 0, 0, scanner->default_format,
+ format_ext,
+ scanner->default_color, scanner->default_resolution, scanner->default_resolution);
+ DBG( 1, "Create NewJob : %s\n", cap_data);
+ upload->read_data = strdup(cap_data);
+ upload->size = strlen(cap_data);
+ download->memory = malloc(1);
+ download->size = 0;
+ strcpy(job_cmd, name);
+ strcat(job_cmd, scan_jobs);
+ curl_easy_setopt(curl_handle, CURLOPT_URL, job_cmd);
+ if (strncmp(name, "https", 5) == 0) {
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, upload->read_data);
+ curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, upload->size);
+ curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, download_callback);
+ curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *)download);
+ if (curl_easy_perform(curl_handle) != CURLE_OK) {
+ DBG( 1, "Create NewJob : the scanner responded incorrectly.\n");
+ *status = SANE_STATUS_INVAL;
+ }
+ else {
+ if (download->memory != NULL) {
+ if (strstr(download->memory, "Location:")) {
+ temporary = strrchr(download->memory, '/');
+ if (temporary != NULL) {
+ location = strchr(temporary, '\r');
+ if (location == NULL)
+ location = strchr(temporary, '\n');
+ else {
+ *location = '\0';
+ result = strdup(temporary);
+ }
+ DBG( 1, "Create NewJob : %s\n", result);
+ }
+ free(download->memory);
+ }
+ else {
+ DBG( 1, "Create NewJob : The creation of the failed job\n");
+ *status = SANE_STATUS_INVAL;
+ }
+ }
+ else {
+ *status = SANE_STATUS_NO_MEM;
+ DBG( 1, "Create NewJob : The creation of the failed job\n");
+ return (NULL);
+ }
+ }
+ curl_easy_cleanup(curl_handle);
+ }
+ if (upload != NULL)
+ free(upload);
+ if (download != NULL)
+ free(download);
+ return (result);
+}
diff --git a/backend/escl/escl_png.c b/backend/escl/escl_png.c
new file mode 100644
index 0000000..18f6f35
--- /dev/null
+++ b/backend/escl/escl_png.c
@@ -0,0 +1,193 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include "../include/sane/sanei.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if(defined HAVE_LIBPNG)
+#include <png.h>
+#endif
+
+#include <setjmp.h>
+
+
+#if(defined HAVE_LIBPNG)
+
+/**
+ * \fn SANE_Status escl_sane_decompressor(escl_sane_t *handler)
+ * \brief Function that aims to decompress the png image to SANE be able to read the image.
+ * This function is called in the "sane_read" function.
+ *
+ * \return SANE_STATUS_GOOD (if everything is OK, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
+ */
+SANE_Status
+get_PNG_data(capabilities_t *scanner, int *w, int *h, int *components)
+{
+ unsigned int width = 0; /* largeur */
+ unsigned int height = 0; /* hauteur */
+ int bps = 3; /* composantes d'un texel */
+ unsigned char *texels = NULL; /* données de l'image */
+ unsigned int i = 0;
+ png_byte magic[8];
+
+ // read magic number
+ fread (magic, 1, sizeof (magic), scanner->tmp);
+ // check for valid magic number
+ if (!png_check_sig (magic, sizeof (magic)))
+ {
+ DBG( 1, "Escl Png : PNG error is not a valid PNG image!\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ // create a png read struct
+ png_structp png_ptr = png_create_read_struct
+ (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ {
+ DBG( 1, "Escl Png : PNG error create a png read struct\n");
+ if (scanner->tmp)
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ // create a png info struct
+ png_infop info_ptr = png_create_info_struct (png_ptr);
+ if (!info_ptr)
+ {
+ DBG( 1, "Escl Png : PNG error create a png info struct\n");
+ png_destroy_read_struct (&png_ptr, NULL, NULL);
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ // initialize the setjmp for returning properly after a libpng
+ // error occured
+ if (setjmp (png_jmpbuf (png_ptr)))
+ {
+ png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
+ if (texels)
+ free (texels);
+ fprintf(stderr,"PNG read error.\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ DBG( 1, "Escl Png : PNG read error.\n");
+ return (SANE_STATUS_INVAL);
+ }
+ // setup libpng for using standard C fread() function
+ // with our FILE pointer
+ png_init_io (png_ptr, scanner->tmp);
+ // tell libpng that we have already read the magic number
+ png_set_sig_bytes (png_ptr, sizeof (magic));
+
+ // read png info
+ png_read_info (png_ptr, info_ptr);
+
+ int bit_depth, color_type;
+ // get some usefull information from header
+ bit_depth = png_get_bit_depth (png_ptr, info_ptr);
+ color_type = png_get_color_type (png_ptr, info_ptr);
+ // convert index color images to RGB images
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb (png_ptr);
+ else if (color_type != PNG_COLOR_TYPE_RGB && color_type != PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ fprintf(stderr,"PNG format not supported.\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ bps = 4;
+ else
+ bps = 3;
+ if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_tRNS_to_alpha (png_ptr);
+ if (bit_depth == 16)
+ png_set_strip_16 (png_ptr);
+ else if (bit_depth < 8)
+ png_set_packing (png_ptr);
+ // update info structure to apply transformations
+ png_read_update_info (png_ptr, info_ptr);
+ // retrieve updated information
+ png_get_IHDR (png_ptr, info_ptr,
+ (png_uint_32*)(&width),
+ (png_uint_32*)(&height),
+ &bit_depth, &color_type,
+ NULL, NULL, NULL);
+
+ *w = (int)width;
+ *h = (int)height;
+ *components = bps;
+ // we can now allocate memory for storing pixel data
+ texels = (unsigned char *)malloc (sizeof (unsigned char) * width
+ * height * bps);
+ png_bytep *row_pointers;
+ // setup a pointer array. Each one points at the begening of a row.
+ row_pointers = (png_bytep *)malloc (sizeof (png_bytep) * height);
+ for (i = 0; i < height; ++i)
+ {
+ row_pointers[i] = (png_bytep)(texels +
+ ((height - (i + 1)) * width * bps));
+ }
+ // read pixel data using row pointers
+ png_read_image (png_ptr, row_pointers);
+ // we don't need row pointers anymore
+ scanner->img_data = texels;
+ scanner->img_size = (int)(width * height * bps);
+ scanner->img_read = 0;
+ free (row_pointers);
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ return (SANE_STATUS_GOOD);
+}
+#else
+
+SANE_Status
+get_PNG_data(capabilities_t __sane_unused__ *scanner,
+ int __sane_unused__ *w,
+ int __sane_unused__ *h,
+ int __sane_unused__ *bps)
+{
+ return (SANE_STATUS_INVAL);
+}
+
+#endif
diff --git a/backend/escl/escl_reset.c b/backend/escl/escl_reset.c
new file mode 100644
index 0000000..7722d89
--- /dev/null
+++ b/backend/escl/escl_reset.c
@@ -0,0 +1,75 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+/**
+ * \fn void escl_scanner(SANE_String_Const name, char *result)
+ * \brief Function that resets the scanner after each scan, using curl.
+ * This function is called in the 'sane_cancel' function.
+ */
+void
+escl_scanner(SANE_String_Const name, char *result)
+{
+ CURL *curl_handle = NULL;
+ const char *scan_jobs = "/eSCL/ScanJobs";
+ const char *scanner_start = "/NextDocument";
+ char scan_cmd[PATH_MAX] = { 0 };
+ int i = 0;
+ long answer = 0;
+
+ if (name == NULL || result == NULL)
+ return;
+CURL_CALL:
+ curl_handle = curl_easy_init();
+ if (curl_handle != NULL) {
+ strcpy(scan_cmd, name);
+ strcat(scan_cmd, scan_jobs);
+ strcat(scan_cmd, result);
+ strcat(scan_cmd, scanner_start);
+ curl_easy_setopt(curl_handle, CURLOPT_URL, scan_cmd);
+ DBG( 1, "Reset Job : %s.\n", scan_cmd);
+ if (strncmp(name, "https", 5) == 0) {
+ DBG( 1, "Ignoring safety certificates, use https\n");
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ if (curl_easy_perform(curl_handle) == CURLE_OK) {
+ curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &answer);
+ if (i < 3 && answer == 503) {
+ curl_easy_cleanup(curl_handle);
+ i++;
+ goto CURL_CALL;
+ }
+ }
+ curl_easy_cleanup(curl_handle);
+ }
+}
diff --git a/backend/escl/escl_scan.c b/backend/escl/escl_scan.c
new file mode 100644
index 0000000..8f077a1
--- /dev/null
+++ b/backend/escl/escl_scan.c
@@ -0,0 +1,99 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#include "../include/sane/sanei.h"
+
+/**
+ * \fn static size_t write_callback(void *str, size_t size, size_t nmemb, void *userp)
+ * \brief Callback function that writes the image scanned into the temporary file.
+ *
+ * \return to_write (the result of the fwrite fonction)
+ */
+static size_t
+write_callback(void *str, size_t size, size_t nmemb, void *userp)
+{
+ size_t to_write = fwrite(str, size, nmemb, (FILE *)userp);
+
+ return (to_write);
+}
+
+/**
+ * \fn SANE_Status escl_scan(capabilities_t *scanner, SANE_String_Const name, char *result)
+ * \brief Function that, after recovering the 'new job', scans the image writed in the
+ * temporary file, using curl.
+ * This function is called in the 'sane_start' function and it's the equivalent of
+ * the following curl command : "curl -s http(s)://'ip:'port'/eSCL/ScanJobs/'new job'/NextDocument > image.jpg".
+ *
+ * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
+ */
+SANE_Status
+escl_scan(capabilities_t __sane_unused__ *scanner, SANE_String_Const name, char *result)
+{
+ CURL *curl_handle = NULL;
+ const char *scan_jobs = "/eSCL/ScanJobs";
+ const char *scanner_start = "/NextDocument";
+ char scan_cmd[PATH_MAX] = { 0 };
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (name == NULL)
+ return (SANE_STATUS_NO_MEM);
+ curl_handle = curl_easy_init();
+ if (curl_handle != NULL) {
+ strcpy(scan_cmd, name);
+ strcat(scan_cmd, scan_jobs);
+ strcat(scan_cmd, result);
+ strcat(scan_cmd, scanner_start);
+ curl_easy_setopt(curl_handle, CURLOPT_URL, scan_cmd);
+ DBG( 1, "Scan : %s.\n", scan_cmd);
+ if (strncmp(name, "https", 5) == 0) {
+ DBG( 1, "Ignoring safety certificates, use https\n");
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback);
+ scanner->tmp = tmpfile();
+ if (scanner->tmp != NULL) {
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, scanner->tmp);
+ if (curl_easy_perform(curl_handle) != CURLE_OK) {
+ status = SANE_STATUS_INVAL;
+ }
+ else
+ curl_easy_cleanup(curl_handle);
+ fseek(scanner->tmp, 0, SEEK_SET);
+ }
+ else
+ status = SANE_STATUS_NO_MEM;
+ }
+ return (status);
+}
diff --git a/backend/escl/escl_status.c b/backend/escl/escl_status.c
new file mode 100644
index 0000000..68b51dc
--- /dev/null
+++ b/backend/escl/escl_status.c
@@ -0,0 +1,176 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+
+struct idle
+{
+ char *memory;
+ size_t size;
+};
+
+/**
+ * \fn static size_t memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp)
+ * \brief Callback function that stocks in memory the content of the scanner status.
+ *
+ * \return realsize (size of the content needed -> the scanner status)
+ */
+static size_t
+memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct idle *mem = (struct idle *)userp;
+
+ char *str = realloc(mem->memory, mem->size + realsize + 1);
+ if (str == NULL) {
+ DBG(1, "not enough memory (realloc returned NULL)\n");
+ return (0);
+ }
+ mem->memory = str;
+ memcpy(&(mem->memory[mem->size]), contents, realsize);
+ mem->size = mem->size + realsize;
+ mem->memory[mem->size] = 0;
+ return (realsize);
+}
+
+/**
+ * \fn static int find_nodes_s(xmlNode *node)
+ * \brief Function that browses the xml file and parses it, to find the xml children node.
+ * --> to recover the scanner status.
+ *
+ * \return 0 if a xml child node is found, 1 otherwise
+ */
+static int
+find_nodes_s(xmlNode *node)
+{
+ xmlNode *child = node->children;
+
+ while (child) {
+ if (child->type == XML_ELEMENT_NODE)
+ return (0);
+ child = child->next;
+ }
+ return (1);
+}
+
+/**
+ * \fn static void print_xml_s(xmlNode *node, SANE_Status *status)
+ * \brief Function that browses the xml file, node by node.
+ * If the node 'State' is found, we are expecting to found in this node the 'Idle'
+ * content (if the scanner is ready to use) and then 'status' = SANE_STATUS_GOOD.
+ * Otherwise, this means that the scanner isn't ready to use.
+ */
+static void
+print_xml_s(xmlNode *node, SANE_Status *status)
+{
+ int x = 0;
+
+ while (node) {
+ if (node->type == XML_ELEMENT_NODE) {
+ if (find_nodes_s(node)) {
+ if (strcmp((const char *)node->name, "State") == 0)
+ x = 1;
+ }
+ if (x == 1 && strcmp((const char *)xmlNodeGetContent(node), "Idle") == 0)
+ *status = SANE_STATUS_GOOD;
+ }
+ print_xml_s(node->children, status);
+ node = node->next;
+ }
+}
+
+/**
+ * \fn SANE_Status escl_status(SANE_String_Const name)
+ * \brief Function that finally recovers the scanner status ('Idle', or not), using curl.
+ * This function is called in the 'sane_open' function and it's the equivalent of
+ * the following curl command : "curl http(s)://'ip':'port'/eSCL/ScannerStatus".
+ *
+ * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
+ */
+SANE_Status
+escl_status(SANE_String_Const name)
+{
+ SANE_Status status;
+ CURL *curl_handle = NULL;
+ struct idle *var = NULL;
+ xmlDoc *data = NULL;
+ xmlNode *node = NULL;
+ const char *scanner_status = "/eSCL/ScannerStatus";
+ char tmp[PATH_MAX] = { 0 };
+
+ if (name == NULL)
+ return (SANE_STATUS_NO_MEM);
+ var = (struct idle*)calloc(1, sizeof(struct idle));
+ if (var == NULL)
+ return (SANE_STATUS_NO_MEM);
+ var->memory = malloc(1);
+ var->size = 0;
+ curl_handle = curl_easy_init();
+ strcpy(tmp, name);
+ strcat(tmp, scanner_status);
+ curl_easy_setopt(curl_handle, CURLOPT_URL, tmp);
+ DBG( 1, "Get Status : %s.\n", tmp);
+ if (strncmp(name, "https", 5) == 0) {
+ DBG( 1, "Ignoring safety certificates, use https\n");
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memory_callback_s);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)var);
+ if (curl_easy_perform(curl_handle) != CURLE_OK) {
+ DBG( 1, "The scanner didn't respond.\n");
+ status = SANE_STATUS_INVAL;
+ goto clean_data;
+ }
+ data = xmlReadMemory(var->memory, var->size, "file.xml", NULL, 0);
+ if (data == NULL) {
+ status = SANE_STATUS_NO_MEM;
+ goto clean_data;
+ }
+ node = xmlDocGetRootElement(data);
+ if (node == NULL) {
+ status = SANE_STATUS_NO_MEM;
+ goto clean;
+ }
+ status = SANE_STATUS_DEVICE_BUSY;
+ print_xml_s(node, &status);
+clean:
+ xmlFreeDoc(data);
+clean_data:
+ xmlCleanupParser();
+ xmlMemoryDump();
+ curl_easy_cleanup(curl_handle);
+ free(var->memory);
+ free(var);
+ return (status);
+}
diff --git a/backend/escl/escl_tiff.c b/backend/escl/escl_tiff.c
new file mode 100644
index 0000000..52aec20
--- /dev/null
+++ b/backend/escl/escl_tiff.c
@@ -0,0 +1,119 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Touboul Nathane
+ Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
+
+ 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. */
+
+#define DEBUG_DECLARE_ONLY
+#include "../include/sane/config.h"
+
+#include "escl.h"
+
+#include "../include/sane/sanei.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if(defined HAVE_TIFFIO_H)
+#include <tiffio.h>
+#endif
+
+#include <setjmp.h>
+
+
+#if(defined HAVE_TIFFIO_H)
+
+/**
+ * \fn SANE_Status escl_sane_decompressor(escl_sane_t *handler)
+ * \brief Function that aims to decompress the png image to SANE be able to read the image.
+ * This function is called in the "sane_read" function.
+ *
+ * \return SANE_STATUS_GOOD (if everything is OK, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
+ */
+SANE_Status
+get_TIFF_data(capabilities_t *scanner, int *w, int *h, int *components)
+{
+ TIFF* tif = NULL;
+ uint32 width = 0; /* largeur */
+ uint32 height = 0; /* hauteur */
+ unsigned char *raster = NULL; /* données de l'image */
+ int bps = 4;
+ uint32 npixels = 0;
+
+ lseek(fileno(scanner->tmp), 0, SEEK_SET);
+ tif = TIFFFdOpen(fileno(scanner->tmp), "temp", "r");
+ if (!tif) {
+ DBG( 1, "Escl Tiff : Can not open, or not a TIFF file.\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+
+ TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
+ TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
+ npixels = width * height;
+ raster = (unsigned char*) malloc(npixels * sizeof (uint32));
+ if (raster != NULL)
+ {
+ DBG( 1, "Escl Tiff : Memory allocation problem.\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+
+ if (!TIFFReadRGBAImage(tif, width, height, (uint32 *)raster, 0))
+ {
+ DBG( 1, "Escl Tiff : Problem reading image data.\n");
+ if (scanner->tmp) {
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ }
+ return (SANE_STATUS_INVAL);
+ }
+ *w = (int)width;
+ *h = (int)height;
+ *components = bps;
+ // we don't need row pointers anymore
+ scanner->img_data = raster;
+ scanner->img_size = (int)(width * height * bps);
+ scanner->img_read = 0;
+ TIFFClose(tif);
+ fclose(scanner->tmp);
+ scanner->tmp = NULL;
+ return (SANE_STATUS_GOOD);
+}
+#else
+
+SANE_Status
+get_TIFF_data(capabilities_t __sane_unused__ *scanner,
+ int __sane_unused__ *w,
+ int __sane_unused__ *h,
+ int __sane_unused__ *bps)
+{
+ return (SANE_STATUS_INVAL);
+}
+
+#endif