diff --git a/Makefile b/Makefile index b00a41e..3462012 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ +SRC = airscan.c airscan-array.c airscan-xml.c sane_strstatus.c + CFLAGS = -O2 -g -W -Wall -fPIC CFLAGS += `pkg-config --cflags --libs avahi-client` CFLAGS += `pkg-config --cflags --libs avahi-glib` CFLAGS += `pkg-config --cflags --libs libjpeg` CFLAGS += `pkg-config --cflags --libs libsoup-2.4` CFLAGS += `pkg-config --cflags --libs libxml-2.0` +CFLAGS += -Wl,--version-script=airscan.sym # This magic is a workaround for libsoup bug. # @@ -22,9 +25,9 @@ CFLAGS += -Wl,-z,nodelete all: libsane-airscan.so test -libsane-airscan.so: Makefile airscan.c +libsane-airscan.so: Makefile $(SRC) airscan.h airscan.sym @ctags -R . - gcc -o libsane-airscan.so -shared ${CFLAGS} airscan.c + gcc -o libsane-airscan.so -shared ${CFLAGS} $(SRC) test: libsane-airscan.so test.c #gcc -o test test.c -l sane diff --git a/airscan-array.c b/airscan-array.c new file mode 100644 index 0000000..ccccc3e --- /dev/null +++ b/airscan-array.c @@ -0,0 +1,125 @@ +/* AirScan (a.k.a. eSCL) backend for SANE + * + * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com) + * See LICENSE for license terms and conditions + */ + +#include "airscan.h" + +#include + +/******************** Typed Arrays ********************/ +/* Initial capacity of arrays + */ +#define ARRAY_INITIAL_CAPACITY 4 + +/* Initialize array of SANE_Word + */ +void +array_of_word_init (SANE_Word **a) +{ + *a = g_new0(SANE_Word, ARRAY_INITIAL_CAPACITY); +} + +/* Cleanup array of SANE_Word + */ +void +array_of_word_cleanup (SANE_Word **a) +{ + g_free(*a); + *a = NULL; +} + +/* Get length of the SANE_Word array + */ +size_t +array_of_word_len (SANE_Word **a) +{ + return (size_t) (*a)[0]; +} + +/* Append word to array + */ +void +array_of_word_append(SANE_Word **a, SANE_Word w) +{ + size_t sz = array_of_word_len(a) + 1; + + /* If sz reached the power-of-2, reallocate the array, doubling its size */ + if (sz >= ARRAY_INITIAL_CAPACITY && (sz & (sz - 1)) == 0) { + *a = g_renew(SANE_Word, (*a), sz + sz); + } + + (*a)[sz] = w; + (*a)[0] ++; +} + +/* Compare function for array_of_word_sort + */ +int +array_of_word_sort_cmp(const void *p1, const void *p2) +{ + return *(SANE_Word*) p1 - *(SANE_Word*) p2; +} + +/* Sort array of SANE_Word in increasing order + */ +void +array_of_word_sort(SANE_Word **a) +{ + SANE_Word len = (*a)[0]; + + if (len) { + qsort((*a) + 1, len, sizeof(SANE_Word), array_of_word_sort_cmp); + } +} + +/* Initialize array of SANE_String + */ +void +array_of_string_init (SANE_String **a) +{ + *a = g_new0(SANE_String, ARRAY_INITIAL_CAPACITY); +} + +/* Cleanup array of SANE_String + */ +void +array_of_string_cleanup (SANE_String **a) +{ + g_free(*a); + *a = NULL; +} + +/* Get length of the SANE_Word array + */ +size_t +array_of_string_len (SANE_String **a) +{ + size_t sz; + + for (sz = 0; (*a)[sz]; sz ++) + ; + + return sz; +} + +/* Append string to array + */ +void +array_of_string_append(SANE_String **a, SANE_String s) +{ + size_t sz = array_of_string_len(a) + 1; + + /* If sz reached the power-of-2, reallocate the array, doubling its size */ + if (sz >= ARRAY_INITIAL_CAPACITY && (sz & (sz - 1)) == 0) { + *a = g_renew(SANE_String, (*a), sz + sz); + } + + /* Append string */ + (*a)[sz - 1] = s; + (*a)[sz] = NULL; +} + +/* vim:ts=8:sw=4:et + */ diff --git a/airscan-xml.c b/airscan-xml.c new file mode 100644 index 0000000..06b4574 --- /dev/null +++ b/airscan-xml.c @@ -0,0 +1,188 @@ +/* AirScan (a.k.a. eSCL) backend for SANE + * + * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com) + * See LICENSE for license terms and conditions + */ + +#include "airscan.h" + +#include +#include + +/******************** XML utilities ********************/ +/* Static initializer for the XML iterator + */ +#define XML_ITER_INIT {NULL, NULL, NULL, NULL, NULL} + +/* Skip dummy nodes. This is internal function, don't call directly + */ +static void +__xml_iter_skip_dummy (xml_iter *iter) +{ + xmlNode *node = iter->node; + + while (node != NULL && + (node->type == XML_COMMENT_NODE || xmlIsBlankNode (node))) { + node = node->next; + } + + iter->node = node; +} + +/* Invalidate cached data. This is internal function, don't call directly + */ +static void +__xml_iter_invalidate_cache (xml_iter *iter) +{ + g_free((void*) iter->name); + xmlFree((xmlChar*) iter->text); + g_free((void*) iter->err); + iter->name = NULL; + iter->text = NULL; + iter->err = NULL; +} + +/* Initialize iterator to iterate starting from the given node + */ +void +xml_iter_init (xml_iter *iter, xmlNode *node) +{ + iter->node = node; + __xml_iter_skip_dummy(iter); + __xml_iter_invalidate_cache(iter); + iter->parent = iter->node ? iter->node->parent : NULL; +} + + +/* Cleanup XML iterator + */ +void +xml_iter_cleanup (xml_iter *iter) +{ + __xml_iter_invalidate_cache(iter); + iter->node = NULL; + iter->parent = NULL; +} + +/* Check for end-of-document condition + */ +SANE_Bool +xml_iter_end (xml_iter *iter) +{ + return iter->node == NULL; +} + +/* Shift to the next node + */ +void +xml_iter_next (xml_iter *iter) +{ + if (iter->node) { + iter->node = iter->node->next; + __xml_iter_skip_dummy(iter); + __xml_iter_invalidate_cache(iter); + } +} + +/* Enter the current node - iterate its children + */ +void +xml_iter_enter (xml_iter *iter) +{ + if (iter->node) { + iter->parent = iter->node; + iter->node = iter->node->children; + __xml_iter_skip_dummy(iter); + __xml_iter_invalidate_cache(iter); + } +} + +/* Leave the current node - return to its parent + */ +void +xml_iter_leave (xml_iter *iter) +{ + iter->node = iter->parent; + if (iter->node) { + iter->parent = iter->node->parent; + } + __xml_iter_invalidate_cache(iter); +} + +/* Get name of the current node. + * + * The returned string remains valid, until iterator is cleaned up + * or current node is changed (by set/next/enter/leave operations). + * You don't need to free this string explicitly + */ +const char* +xml_iter_node_name (xml_iter *iter) +{ + const char *prefix = NULL; + + if (iter->name == NULL && iter->node != NULL) { + if (iter->node->ns != NULL) { + prefix = (const char*) iter->node->ns->prefix; + } + + if (prefix != NULL) { + iter->name = g_strconcat(prefix, ":", iter->node->name, NULL); + } else { + iter->name = g_strdup((const char*) iter->node->name); + } + } + + return iter->name; +} + +/* Match name of the current node against the pattern + */ +SANE_Bool +xml_iter_node_name_match (xml_iter *iter, const char *pattern) +{ + return !g_strcmp0(xml_iter_node_name(iter), pattern); +} + +/* Get value of the current node as text + * + * The returned string remains valid, until iterator is cleaned up + * or current node is changed (by set/next/enter/leave operations). + * You don't need to free this string explicitly + */ +const char* +xml_iter_node_value (xml_iter *iter) +{ + if (iter->text == NULL && iter->node != NULL) { + iter->text = xmlNodeGetContent(iter->node); + g_strstrip((char*) iter->text); + } + + return (const char*) iter->text; +} + +/* Get value of the current node as unsigned integer + * Returns error string, NULL if OK + */ +const char* +xml_iter_node_value_uint (xml_iter *iter, SANE_Word *val) +{ + const char *s = xml_iter_node_value(iter); + char *end; + unsigned long v; + + g_assert(s != NULL); + + v = strtoul(s, &end, 10); + if (end == s || *end || v != (unsigned long) (SANE_Word) v) { + g_free((char*) iter->err); + iter->err = g_strdup_printf("%s: invalid numerical value", + xml_iter_node_name(iter)); + return iter->err; + } + + *val = (SANE_Word) v; + return NULL; +} + +/* vim:ts=8:sw=4:et + */ diff --git a/airscan.c b/airscan.c index 58276ae..4a740b9 100644 --- a/airscan.c +++ b/airscan.c @@ -4,7 +4,7 @@ * See LICENSE for license terms and conditions */ -#include +#include "airscan.h" #include #include @@ -17,7 +17,6 @@ #include #include -#include /******************** Constants *********************/ /* Service type to look for @@ -43,305 +42,6 @@ /******************** Debugging ********************/ #define DBG(level, msg, args...) printf("airscan: " msg, ##args) -/******************** XML utilities ********************/ -/* XML iterator - */ -typedef struct { - xmlNode *node; - xmlNode *parent; - const char *name; - const xmlChar *text; - const char *err; -} xml_iter; - -/* Static initializer for the XML iterator - */ -#define XML_ITER_INIT {NULL, NULL, NULL, NULL, NULL} - -/* Skip dummy nodes. This is internal function, don't call directly - */ -static void -__xml_iter_skip_dummy (xml_iter *iter) -{ - xmlNode *node = iter->node; - - while (node != NULL && - (node->type == XML_COMMENT_NODE || xmlIsBlankNode (node))) { - node = node->next; - } - - iter->node = node; -} - -/* Invalidate cached data. This is internal function, don't call directly - */ -static void -__xml_iter_invalidate_cache (xml_iter *iter) -{ - g_free((void*) iter->name); - xmlFree((xmlChar*) iter->text); - g_free((void*) iter->err); - iter->name = NULL; - iter->text = NULL; - iter->err = NULL; -} - -/* Set current node to iterate from - */ -static void -xml_iter_set (xml_iter *iter, xmlNode *node) -{ - iter->node = node; - __xml_iter_skip_dummy(iter); - __xml_iter_invalidate_cache(iter); - iter->parent = iter->node ? iter->node->parent : NULL; -} - - -/* Cleanup XML iterator - */ -static void -xml_iter_cleanup (xml_iter *iter) -{ - __xml_iter_invalidate_cache(iter); - iter->node = NULL; - iter->parent = NULL; -} - -/* Check for end-of-document condition - */ -static SANE_Bool -xml_iter_end (xml_iter *iter) -{ - return iter->node == NULL; -} - -/* Shift to the next node - */ -static void -xml_iter_next (xml_iter *iter) -{ - if (iter->node) { - iter->node = iter->node->next; - __xml_iter_skip_dummy(iter); - __xml_iter_invalidate_cache(iter); - } -} - -/* Enter the current node - iterate its children - */ -static void -xml_iter_enter (xml_iter *iter) -{ - if (iter->node) { - iter->parent = iter->node; - iter->node = iter->node->children; - __xml_iter_skip_dummy(iter); - __xml_iter_invalidate_cache(iter); - } -} - -/* Leave the current node - return to its parent - */ -static void -xml_iter_leave (xml_iter *iter) -{ - iter->node = iter->parent; - if (iter->node) { - iter->parent = iter->node->parent; - } - __xml_iter_invalidate_cache(iter); -} - -/* Get name of the current node. - * - * The returned string remains valid, until iterator is cleaned up - * or current node is changed (by set/next/enter/leave operations). - * You don't need to free this string explicitly - */ -static const char* -xml_iter_node_name (xml_iter *iter) -{ - const char *prefix = NULL; - - if (iter->name == NULL && iter->node != NULL) { - if (iter->node->ns != NULL) { - prefix = (const char*) iter->node->ns->prefix; - } - - if (prefix != NULL) { - iter->name = g_strconcat(prefix, ":", iter->node->name, NULL); - } else { - iter->name = g_strdup((const char*) iter->node->name); - } - } - - return iter->name; -} - -/* Match name of the current node against the pattern - */ -static SANE_Bool -xml_iter_node_name_match (xml_iter *iter, const char *pattern) -{ - return !g_strcmp0(xml_iter_node_name(iter), pattern); -} - -/* Get value of the current node as text - * - * The returned string remains valid, until iterator is cleaned up - * or current node is changed (by set/next/enter/leave operations). - * You don't need to free this string explicitly - */ -static const char* -xml_iter_node_value (xml_iter *iter) -{ - if (iter->text == NULL && iter->node != NULL) { - iter->text = xmlNodeGetContent(iter->node); - g_strstrip((char*) iter->text); - } - - return (const char*) iter->text; -} - -/* Get value of the current node as unsigned integer - * Returns error string, NULL if OK - */ -static const char* -xml_iter_node_value_uint (xml_iter *iter, SANE_Word *val) -{ - const char *s = xml_iter_node_value(iter); - char *end; - unsigned long v; - - g_assert(s != NULL); - - v = strtoul(s, &end, 10); - if (end == s || *end || v != (unsigned long) (SANE_Word) v) { - g_free((char*) iter->err); - iter->err = g_strdup_printf("%s: invalid numerical value", - xml_iter_node_name(iter)); - return iter->err; - } - - *val = (SANE_Word) v; - return NULL; -} - - -/******************** Types Arrays ********************/ -/* Initial capacity of arrays - */ -#define ARRAY_INITIAL_CAPACITY 4 - -/* Initialize array of SANE_Word - */ -static void -array_of_word_init (SANE_Word **a) -{ - *a = g_new0(SANE_Word, ARRAY_INITIAL_CAPACITY); -} - -/* Cleanup array of SANE_Word - */ -static void -array_of_word_cleanup (SANE_Word **a) -{ - g_free(*a); - *a = NULL; -} - -/* Get length of the SANE_Word array - */ -static size_t -array_of_word_len (SANE_Word **a) -{ - return (size_t) (*a)[0]; -} - -/* Append word to array - */ -static void -array_of_word_append(SANE_Word **a, SANE_Word w) -{ - size_t sz = array_of_word_len(a) + 1; - - /* If sz reached the power-of-2, reallocate the array, doubling its size */ - if (sz >= ARRAY_INITIAL_CAPACITY && (sz & (sz - 1)) == 0) { - *a = g_renew(SANE_Word, (*a), sz + sz); - } - - (*a)[sz] = w; - (*a)[0] ++; -} - -/* Compare function for array_of_word_sort - */ -static int -array_of_word_sort_cmp(const void *p1, const void *p2) -{ - return *(SANE_Word*) p1 - *(SANE_Word*) p2; -} - -/* Sort array of SANE_Word in increasing order - */ -static void -array_of_word_sort(SANE_Word **a) -{ - SANE_Word len = (*a)[0]; - - if (len) { - qsort((*a) + 1, len, sizeof(SANE_Word), array_of_word_sort_cmp); - } -} - -/* Initialize array of SANE_String - */ -static void -array_of_string_init (SANE_String **a) -{ - *a = g_new0(SANE_String, ARRAY_INITIAL_CAPACITY); -} - -/* Cleanup array of SANE_String - */ -static void -array_of_string_cleanup (SANE_String **a) -{ - g_free(*a); - *a = NULL; -} - -/* Get length of the SANE_Word array - */ -static size_t -array_of_string_len (SANE_String **a) -{ - size_t sz; - - for (sz = 0; (*a)[sz]; sz ++) - ; - - return sz; -} - -/* Append string to array - */ -static void -array_of_string_append(SANE_String **a, SANE_String s) -{ - size_t sz = array_of_string_len(a) + 1; - - /* If sz reached the power-of-2, reallocate the array, doubling its size */ - if (sz >= ARRAY_INITIAL_CAPACITY && (sz & (sz - 1)) == 0) { - *a = g_renew(SANE_String, (*a), sz + sz); - } - - /* Append string */ - (*a)[sz - 1] = s; - (*a)[sz] = NULL; -} - /******************** Device Capabilities ********************/ /* Source flags */ @@ -664,7 +364,7 @@ devcaps_parse (devcaps *caps, xmlDoc *xml) xml_iter iter = XML_ITER_INIT; /* Parse capabilities XML */ - xml_iter_set(&iter, xmlDocGetRootElement(xml)); + xml_iter_init(&iter, xmlDocGetRootElement(xml)); if (!xml_iter_node_name_match(&iter, "scan:ScannerCapabilities")) { err = "XML: missed scan:ScannerCapabilities"; goto DONE; diff --git a/airscan.h b/airscan.h new file mode 100644 index 0000000..35a0eb9 --- /dev/null +++ b/airscan.h @@ -0,0 +1,141 @@ +/* AirScan (a.k.a. eSCL) backend for SANE + * + * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com) + * See LICENSE for license terms and conditions + */ + +#ifndef airscan_h +#define airscan_h + +#include +#include + +/******************** Typed Arrays ********************/ +/* Initialize array of SANE_Word + */ +void +array_of_word_init (SANE_Word **a); + +/* Cleanup array of SANE_Word + */ +void +array_of_word_cleanup (SANE_Word **a); + +/* Get length of the SANE_Word array + */ +size_t +array_of_word_len (SANE_Word **a); + +/* Append word to array + */ +void +array_of_word_append(SANE_Word **a, SANE_Word w); + +/* Compare function for array_of_word_sort + */ +int +array_of_word_sort_cmp(const void *p1, const void *p2); + +/* Sort array of SANE_Word in increasing order + */ +void +array_of_word_sort(SANE_Word **a); + +/* Initialize array of SANE_String + */ +void +array_of_string_init (SANE_String **a); + +/* Cleanup array of SANE_String + */ +void +array_of_string_cleanup (SANE_String **a); + +/* Get length of the SANE_Word array + */ +size_t +array_of_string_len (SANE_String **a); + +/* Append string to array + */ +void +array_of_string_append(SANE_String **a, SANE_String s); + +/******************** XML utilities ********************/ +/* XML iterator + */ +typedef struct { + xmlNode *node; + xmlNode *parent; + const char *name; + const xmlChar *text; + const char *err; +} xml_iter; + +/* Static initializer for the XML iterator + */ +#define XML_ITER_INIT {NULL, NULL, NULL, NULL, NULL} + +/* Initialize iterator to iterate starting from the given node + */ +void +xml_iter_init (xml_iter *iter, xmlNode *node); + +/* Cleanup XML iterator + */ +void +xml_iter_cleanup (xml_iter *iter); + +/* Check for end-of-document condition + */ +SANE_Bool +xml_iter_end (xml_iter *iter); + +/* Shift to the next node + */ +void +xml_iter_next (xml_iter *iter); + +/* Enter the current node - iterate its children + */ +void +xml_iter_enter (xml_iter *iter); + +/* Leave the current node - return to its parent + */ +void +xml_iter_leave (xml_iter *iter); + +/* Get name of the current node. + * + * The returned string remains valid, until iterator is cleaned up + * or current node is changed (by set/next/enter/leave operations). + * You don't need to free this string explicitly + */ +const char* +xml_iter_node_name (xml_iter *iter); + +/* Match name of the current node against the pattern + */ +SANE_Bool +xml_iter_node_name_match (xml_iter *iter, const char *pattern); + +/* Get value of the current node as text + * + * The returned string remains valid, until iterator is cleaned up + * or current node is changed (by set/next/enter/leave operations). + * You don't need to free this string explicitly + */ +const char* +xml_iter_node_value (xml_iter *iter); + +/* Get value of the current node as unsigned integer + * Returns error string, NULL if OK + */ +const char* +xml_iter_node_value_uint (xml_iter *iter, SANE_Word *val); + +#endif + +/* vim:ts=8:sw=4:et + */ diff --git a/airscan.sym b/airscan.sym new file mode 100644 index 0000000..41d2f42 --- /dev/null +++ b/airscan.sym @@ -0,0 +1,33 @@ +{ + global: + sane_airscan_cancel; + sane_airscan_close; + sane_airscan_control_option; + sane_airscan_exit; + sane_airscan_get_devices; + sane_airscan_get_option_descriptor; + sane_airscan_get_parameters; + sane_airscan_get_select_fd; + sane_airscan_init; + sane_airscan_open; + sane_airscan_read; + sane_airscan_set_io_mode; + sane_airscan_start; + sane_cancel; + sane_close; + sane_control_option; + sane_exit; + sane_get_devices; + sane_get_option_descriptor; + sane_get_parameters; + sane_get_select_fd; + sane_init; + sane_open; + sane_read; + sane_set_io_mode; + sane_start; + sane_strstatus; + + local: + *; +}; diff --git a/sane_strstatus.c b/sane_strstatus.c new file mode 100644 index 0000000..b35adef --- /dev/null +++ b/sane_strstatus.c @@ -0,0 +1,109 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1996, 1997 David Mosberger-Tang and Andreas Beck + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + + This file implements the backend-independent parts of SANE. */ + +#include + +#include + +#ifndef SANE_I18N +#define SANE_I18N(text) text +#endif + +SANE_String_Const +sane_strstatus (SANE_Status status) +{ + static char buf[80]; + + switch (status) + { + case SANE_STATUS_GOOD: + return SANE_I18N("Success"); + + case SANE_STATUS_UNSUPPORTED: + return SANE_I18N("Operation not supported"); + + case SANE_STATUS_CANCELLED: + return SANE_I18N("Operation was canceled"); + + case SANE_STATUS_DEVICE_BUSY: + return SANE_I18N("Device busy"); + + case SANE_STATUS_INVAL: + return SANE_I18N("Invalid argument"); + + case SANE_STATUS_EOF: + return SANE_I18N("End of file reached"); + + case SANE_STATUS_JAMMED: + return SANE_I18N("Document feeder jammed"); + + case SANE_STATUS_NO_DOCS: + return SANE_I18N("Document feeder out of documents"); + + case SANE_STATUS_COVER_OPEN: + return SANE_I18N("Scanner cover is open"); + + case SANE_STATUS_IO_ERROR: + return SANE_I18N("Error during device I/O"); + + case SANE_STATUS_NO_MEM: + return SANE_I18N("Out of memory"); + + case SANE_STATUS_ACCESS_DENIED: + return SANE_I18N("Access to resource has been denied"); + +#ifdef SANE_STATUS_WARMING_UP + case SANE_STATUS_WARMING_UP: + return SANE_I18N("Lamp not ready, please retry"); +#endif + +#ifdef SANE_STATUS_HW_LOCKED + case SANE_STATUS_HW_LOCKED: + return SANE_I18N("Scanner mechanism locked for transport"); +#endif + + default: + /* non-reentrant, but better than nothing */ + sprintf (buf, "Unknown SANE status code %d", status); + return buf; + } +}