diff options
| author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 | 
|---|---|---|
| committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 | 
| commit | ee770c2346eb37e0dcb8b6cf3eaacf3d8efd6bbc (patch) | |
| tree | 58f05092be1a17a939e861f8cadcda1b6ca2ecef /backend/escl | |
| parent | 0da9e21872802cfc6e975b1ebaf9efb9e5934d84 (diff) | |
| parent | fef76e17ed4c607ea73b81279f9ef1d7121be900 (diff) | |
Merge branch 'release/experimental/1.0.29-1_experimental1'experimental/1.0.29-1_experimental1
Diffstat (limited to 'backend/escl')
| -rw-r--r-- | backend/escl/escl.c | 777 | ||||
| -rw-r--r-- | backend/escl/escl.h | 171 | ||||
| -rw-r--r-- | backend/escl/escl_capabilities.c | 377 | ||||
| -rw-r--r-- | backend/escl/escl_devices.c | 185 | ||||
| -rw-r--r-- | backend/escl/escl_jpeg.c | 230 | ||||
| -rw-r--r-- | backend/escl/escl_newjob.c | 241 | ||||
| -rw-r--r-- | backend/escl/escl_png.c | 193 | ||||
| -rw-r--r-- | backend/escl/escl_reset.c | 75 | ||||
| -rw-r--r-- | backend/escl/escl_scan.c | 99 | ||||
| -rw-r--r-- | backend/escl/escl_status.c | 176 | ||||
| -rw-r--r-- | backend/escl/escl_tiff.c | 119 | 
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 | 
