diff options
Diffstat (limited to 'tools/sane-desc.c')
-rw-r--r-- | tools/sane-desc.c | 4056 |
1 files changed, 4056 insertions, 0 deletions
diff --git a/tools/sane-desc.c b/tools/sane-desc.c new file mode 100644 index 0000000..3cc4407 --- /dev/null +++ b/tools/sane-desc.c @@ -0,0 +1,4056 @@ +/* + sane-desc.c -- generate list of supported SANE devices + + Copyright (C) 2002-2006 Henning Meier-Geinitz <henning@meier-geinitz.de> + Copyright (C) 2004 Jose Gato <jgato@gsyc.escet.urjc.es> (XML output) + Copyright (C) 2006 Mattias Ellert <mattias.ellert@tsl.uu.se> (plist output) + Copyright (C) 2009 Dr. Ing. Dieter Jurzitza <dieter.jurzitza@t-online.de> + Copyright (C) 2013 Tom Gundersen <teg@jklm.no> (hwdb output) + + 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. +*/ + +#include <../include/sane/config.h> + +#include "lgetopt.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <limits.h> +#include <ctype.h> +#include <time.h> + +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" +#include "../include/sane/sanei_config.h" + +#define SANE_DESC_VERSION "3.5" + +#define MAN_PAGE_LINK "http://www.sane-project.org/man/%s.5.html" +#define COLOR_MINIMAL "\"#B00000\"" +#define COLOR_BASIC "\"#FF9000\"" +#define COLOR_GOOD "\"#90B000\"" +#define COLOR_COMPLETE "\"#007000\"" +#define COLOR_UNTESTED "\"#0000B0\"" +#define COLOR_UNSUPPORTED "\"#F00000\"" +#define COLOR_NEW "\"#F00000\"" +#define COLOR_UNKNOWN "\"#000000\"" + +#define DEVMODE "0664" +#define DEVOWNER "root" +#define DEVGROUP "scanner" + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +#define DBG_ERR current_debug_level = 0; debug_call +#define DBG_WARN current_debug_level = 1; debug_call +#define DBG_INFO current_debug_level = 2; debug_call +#define DBG_DBG current_debug_level = 3; debug_call + +typedef enum output_mode +{ + output_mode_ascii = 0, + output_mode_xml, + output_mode_html_backends, + output_mode_html_backends_split, + output_mode_html_mfgs, + output_mode_statistics, + output_mode_usermap, + output_mode_db, + output_mode_udev, + output_mode_udevacl, + output_mode_udevhwdb, + output_mode_hwdb, + output_mode_plist, + output_mode_hal, + output_mode_halnew +} +output_mode; + +typedef enum parameter_type +{ + param_none = 0, + param_string, + param_two_strings, + param_three_strings +} +parameter_type; + +typedef enum status_entry +{ + status_unknown, + status_unsupported, + status_untested, + status_minimal, + status_basic, + status_good, + status_complete +} +status_entry; + +typedef enum device_type +{ + type_unknown, + type_scanner, + type_stillcam, + type_vidcam, + type_meta, + type_api +} +device_type; + +typedef enum level +{ + level_backend, + level_mfg, + level_model, + level_desc +} +level; + +typedef struct url_entry +{ + struct url_entry *next; + char *name; +} +url_entry; + +typedef struct model_entry +{ + struct model_entry *next; + char *name; + char *interface; + struct url_entry *url; + char *comment; + enum status_entry status; + char *usb_vendor_id; + char *usb_product_id; + SANE_Bool ignore_usb_id; + char *scsi_vendor_id; + char *scsi_product_id; + SANE_Bool scsi_is_processor; +} +model_entry; + +typedef struct desc_entry +{ + struct desc_entry *next; + char *desc; + struct url_entry *url; + char *comment; +} +desc_entry; + +typedef struct mfg_entry +{ + struct mfg_entry *next; + char *name; + struct url_entry *url; + char *comment; + struct model_entry *model; +} +mfg_entry; + +typedef struct type_entry +{ + struct type_entry *next; + enum device_type type; + struct desc_entry *desc; + struct mfg_entry *mfg; +} +type_entry; + +typedef struct backend_entry +{ + struct backend_entry *next; + char *name; + char *version; + char *manpage; + struct url_entry *url; + char *comment; + struct type_entry *type; + SANE_Bool new; +} +backend_entry; + +typedef struct model_record_entry +{ + struct model_record_entry *next; + char *name; + char *interface; + struct url_entry *url; + char *comment; + enum status_entry status; + char *usb_vendor_id; + char *usb_product_id; + char *scsi_vendor_id; + char *scsi_product_id; + SANE_Bool scsi_is_processor; + struct backend_entry *be; +} +model_record_entry; + +typedef struct mfg_record_entry +{ + struct mfg_record_entry *next; + char *name; + char *comment; + struct url_entry *url; + struct model_record_entry *model_record; +} +mfg_record_entry; + +typedef int statistics_type [status_complete + 1]; + + +typedef struct manufacturer_model_type +{ + struct manufacturer_model_type * next; + char *name; +} +manufacturer_model_type; + +typedef struct usbid_type +{ + struct usbid_type * next; + char *usb_vendor_id; + char *usb_product_id; + struct manufacturer_model_type *name; +} +usbid_type; + +typedef struct scsiid_type +{ + struct scsiid_type * next; + char *scsi_vendor_id; + char *scsi_product_id; + SANE_Bool is_processor; + struct manufacturer_model_type *name; +} +scsiid_type; + +static char *program_name; +static int debug = 0; +static int current_debug_level = 0; +static char *search_dir_spec = 0; +static backend_entry *first_backend = 0; +static enum output_mode mode = output_mode_ascii; +static char *title = 0; +static char *intro = 0; +static SANE_String desc_name = 0; +static const char *status_name[] = + {"Unknown", "Unsupported", "Untested", "Minimal", "Basic", + "Good", "Complete"}; +static const char *device_type_name[] = + {"Unknown", "Scanners", "Still cameras", "Video Cameras", "Meta backends", + "APIs"}; +static const char *device_type_aname[] = + {"UKNOWN", "SCANNERS", "STILL", "VIDEO", "META", + "API"}; +static const char *status_color[] = + {COLOR_UNKNOWN, COLOR_UNSUPPORTED, COLOR_UNTESTED, COLOR_MINIMAL, + COLOR_BASIC, COLOR_GOOD, COLOR_COMPLETE}; + + +static void +debug_call (const char *fmt, ...) +{ + va_list ap; + char *level_txt; + + va_start (ap, fmt); + if (debug >= current_debug_level) + { + /* print to stderr */ + switch (current_debug_level) + { + case 0: + level_txt = "ERROR:"; + break; + case 1: + level_txt = "Warning:"; + break; + case 2: + level_txt = "Info:"; + break; + default: + level_txt = ""; + break; + } + if (desc_name) + fprintf (stderr, "%s: %8s ", desc_name, level_txt); + else + fprintf (stderr, "[%s] %8s ", program_name, level_txt); + vfprintf (stderr, fmt, ap); + } + va_end (ap); +} + +static void +print_usage (char *program_name) +{ + printf ("Usage: %s [-s dir] [-m mode] [-d level] [-h] [-V]\n", + program_name); + printf (" -s|--search-dir dir " + "Specify the directory that contains .desc files \n" + " " + "(multiple directories can be concatenated by \":\")\n"); + printf (" -m|--mode mode " + "Output mode (ascii, html-backends-split, html-mfgs,\n" + " xml, statistics, usermap, db, udev, udev+acl, udev+hwdb, hwdb, plist, hal, hal-new)\n"); + printf (" -t|--title \"title\" The title used for HTML pages\n"); + printf (" -i|--intro \"intro\" A short description of the " + "contents of the page\n"); + printf (" -d|--debug-level level Specify debug level (0-3)\n"); + printf (" -h|--help Print help message\n"); + printf (" -V|--version Print version information\n"); + printf ("Report bugs to <henning@meier-geinitz.de>\n"); +} + +static void +print_version (void) +{ + printf ("sane-desc %s (%s)\n", SANE_DESC_VERSION, PACKAGE_STRING); + printf ("Copyright (C) 2002-2006 Henning Meier-Geinitz " + "<henning@meier-geinitz.de>\n" + "sane-desc comes with NO WARRANTY, to the extent permitted by " + "law.\n" + "You may redistribute copies of sane-desc under the terms of the " + "GNU General\n" + "Public License.\n" + "For more information about these matters, see the file named " + "COPYING.\n"); +} + +static SANE_Bool +get_options (int argc, char **argv) +{ + int longindex; + int opt; + static struct option desc_options[] = { + {"search-dir", required_argument, NULL, 's'}, + {"mode", required_argument, NULL, 'm'}, + {"title", required_argument, NULL, 't'}, + {"intro", required_argument, NULL, 'i'}, + {"debug-level", required_argument, NULL, 'd'}, + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'V'}, + {0, 0, 0, 0} + }; + + while ((opt = getopt_long (argc, argv, "s:m:t:i:d:hV", desc_options, + &longindex)) != -1) + { + switch (opt) + { + case 'h': + print_usage (argv[0]); + exit (0); + case 'V': + print_version (); + exit (0); + case 's': + search_dir_spec = strdup (optarg); + DBG_INFO ("setting search directory to `%s'\n", search_dir_spec); + break; + case 'm': + if (strcmp (optarg, "ascii") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_ascii; + } + else if (strcmp (optarg, "xml") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_xml; + } + else if (strcmp (optarg, "html-backends-split") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_html_backends_split; + } + else if (strcmp (optarg, "html-mfgs") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_html_mfgs; + } + else if (strcmp (optarg, "statistics") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_statistics; + } + else if (strcmp (optarg, "usermap") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_usermap; + } + else if (strcmp (optarg, "db") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_db; + } + else if (strcmp (optarg, "udev") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_udev; + } + else if (strcmp (optarg, "udev+acl") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_udevacl; + } + else if (strcmp (optarg, "udev+hwdb") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_udevhwdb; + } + else if (strcmp (optarg, "hwdb") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_hwdb; + } + else if (strcmp (optarg, "plist") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_plist; + } + else if (strcmp (optarg, "hal") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_hal; + } + else if (strcmp (optarg, "hal-new") == 0) + { + DBG_INFO ("Output mode: %s\n", optarg); + mode = output_mode_halnew; + } + else + { + DBG_ERR ("Unknown output mode: %s\n", optarg); + exit (1); + } + break; + case 't': + title = optarg; + DBG_INFO ("setting title to `%s'\n", optarg); + break; + case 'i': + intro = optarg; + DBG_INFO ("setting intro to `%s'\n", optarg); + break; + case 'd': + debug = atoi (optarg); + DBG_INFO ("setting debug level to %d\n", debug); + break; + case '?': + DBG_ERR ("unknown option (use -h for help)\n"); + return SANE_FALSE; + case ':': + DBG_ERR ("missing parameter (use -h for help)\n"); + return SANE_FALSE; + default: + DBG_ERR ("missing option (use -h for help)\n"); + return SANE_FALSE; + } + } + if (!search_dir_spec) + search_dir_spec = "."; + return SANE_TRUE; +} + +static int +char_compare (char char1, char char2) +{ + char1 = toupper (char1); + char2 = toupper (char2); + + if (char1 < char2) + return -1; + else if (char1 > char2) + return 1; + else + return 0; +} + +static int +num_compare (char *num_string1, char *num_string2) +{ + int num1 = atoi (num_string1); + int num2 = atoi (num_string2); + if (num1 < num2) + return -1; + else if (num1 > num2) + return 1; + else + return 0; +} + +/* Compare two strings, try to sort numbers correctly (600 < 1200) */ +static int +string_compare (char *string1, char *string2) +{ + int count = 0; + int compare = 0; + + if (!string1) + { + if (!string2) + return 0; + else + return 1; + } + else if (!string2) + return -1; + + while (string1[count] && string2[count]) + { + if (isdigit (string1[count]) && isdigit (string2[count])) + compare = num_compare (&string1[count], &string2[count]); + else + compare = char_compare (string1[count], string2[count]); + if (compare != 0) + return compare; + count++; + } + return char_compare (string1[count], string2[count]); +} + +/* Add URLs to the end of the list if they are unique */ +static url_entry * +update_url_list (url_entry * first_url, char *new_url) +{ + url_entry *url = first_url; + SANE_Bool found = SANE_FALSE; + + while (url && url->name) + { + if (string_compare (url->name, new_url) == 0) + found = SANE_TRUE; + url = url->next; + } + if (found) + return first_url; + + url = first_url; + if (url) + { + while (url->next) + url = url->next; + url->next = calloc (1, sizeof (url_entry)); + url = url->next; + } + else + { + first_url = calloc (1, sizeof (url_entry)); + url = first_url; + } + if (!url) + { + DBG_ERR ("update_url_list: couldn't calloc url_entry\n"); + exit (1); + } + url->name = new_url; + return first_url; +} + +/* Get the next token, ignoring escaped quotation marks */ +static const char * +get_token (const char *str, char **string_const) +{ + const char *start; + size_t len; + + str = sanei_config_skip_whitespace (str); + + if (*str == '"') + { + start = ++str; + while (*str && (*str != '"' || *(str - 1) == '\\')) + ++str; + len = str - start; + if (*str == '"') + ++str; + else + start = 0; /* final double quote is missing */ + } + else + { + start = str; + while (*str && !isspace (*str)) + ++str; + len = str - start; + } + if (start) + *string_const = strndup (start, len); + else + *string_const = NULL; + return str; +} + +/* Checks a line for a keyword token and determines keyword/string argument */ +static SANE_Status +read_keyword (SANE_String line, SANE_String keyword_token, + parameter_type p_type, void *argument) +{ + SANE_String_Const cp; + SANE_Char *word; + + word = 0; + + cp = get_token (line, &word); + + if (!word) + { + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + + if (strcmp (word, keyword_token) != 0) + { + free(word); + return SANE_STATUS_INVAL; + } + + free (word); + word = 0; + + switch (p_type) + { + case param_none: + return SANE_STATUS_GOOD; + case param_string: + { + char *pos; + cp = get_token (cp, &word); + if (!word) + { + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + + DBG_DBG ("read_keyword: set entry `%s' to `%s'\n", keyword_token, + word); + *(SANE_String *) argument = strdup (word); + break; + } + case param_two_strings: + { + char *pos; + char **strings = malloc (2 * sizeof (SANE_String)); + + cp = get_token (cp, &word); + if (!word) + { + free(strings); + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + DBG_INFO ("read_keyword: set first entry of `%s' to `%s'\n", keyword_token, + word); + strings[0] = strdup (word); + if (word) + free (word); + + cp = get_token (cp, &word); + if (!word) + { + free(strings); + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + DBG_INFO ("read_keyword: set second entry of `%s' to `%s'\n", keyword_token, + word); + strings[1] = strdup (word); + * (SANE_String **) argument = strings; + break; + } + case param_three_strings: + { + char *pos; + char **strings = malloc (3 * sizeof (SANE_String)); + + cp = get_token (cp, &word); + if (!word) + { + free(strings); + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + DBG_INFO ("read_keyword: set first entry of `%s' to `%s'\n", keyword_token, + word); + strings[0] = strdup (word); + if (word) + free (word); + + cp = get_token (cp, &word); + if (!word) + { + free(strings); + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + DBG_INFO ("read_keyword: set second entry of `%s' to `%s'\n", keyword_token, + word); + strings[1] = strdup (word); + if (word) + free (word); + + cp = get_token (cp, &word); + if (!word) + { + free(strings); + DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); + return SANE_STATUS_INVAL; + } + /* remove escaped quotations */ + while ((pos = strstr (word, "\\\"")) != 0) + *pos = ' '; + DBG_INFO ("read_keyword: set third entry of `%s' to `%s'\n", keyword_token, + word); + strings[2] = strdup (word); + * (SANE_String **) argument = strings; + break; + } + default: + DBG_ERR ("read_keyword: unknown param_type %d\n", p_type); + return SANE_STATUS_INVAL; + } + + if (word) + free (word); + word = 0; + return SANE_STATUS_GOOD; +} + +/* Check for a all-lowercase 4-digit hex number (e.g. 0x1234) */ +static SANE_Bool +check_hex (SANE_String string) +{ + unsigned int i; + + if (strlen (string) != 6) + return SANE_FALSE; + if (strncmp (string, "0x", 2) != 0) + return SANE_FALSE; + for (i = 0; i < strlen (string); i++) + { + if (isupper (string[i])) + return SANE_FALSE; + } + for (i = 2; i < strlen (string); i++) + { + if (!isxdigit (string[i])) + return SANE_FALSE; + } + return SANE_TRUE; +} + +/* Read and interprete the .desc files */ +static SANE_Bool +read_files (void) +{ + struct stat stat_buf; + DIR *dir; + struct dirent *dir_entry; + FILE *fp; + char file_name[PATH_MAX]; + SANE_Char line[4096], *word; + SANE_String_Const cp; + backend_entry *current_backend = 0; + type_entry *current_type = 0; + mfg_entry *current_mfg = 0; + model_entry *current_model = 0; + enum level current_level = level_backend; + char *search_dir = search_dir_spec, *end = 0; + + DBG_INFO ("looking for .desc files in `%s'\n", search_dir_spec); + + while (search_dir && search_dir[0]) + { + end = strchr (search_dir, ':'); + if (end) + end[0] = '\0'; + DBG_INFO ("reading directory `%s'\n", search_dir); + + if (stat (search_dir, &stat_buf) < 0) + { + DBG_ERR ("cannot stat `%s' (%s)\n", search_dir, strerror (errno)); + return SANE_FALSE; + } + if (!S_ISDIR (stat_buf.st_mode)) + { + DBG_ERR ("`%s' is not a directory\n", search_dir); + return SANE_FALSE; + } + if ((dir = opendir (search_dir)) == 0) + { + DBG_ERR ("cannot read directory `%s' (%s)\n", search_dir, + strerror (errno)); + return SANE_FALSE; + } + + while ((dir_entry = readdir (dir)) != NULL) + { + if (strlen (dir_entry->d_name) > 5 && + strcmp (dir_entry->d_name + strlen (dir_entry->d_name) - 5, + ".desc") == 0) + { + if (strlen (search_dir) + + strlen (dir_entry->d_name) + 1 + 1 > PATH_MAX) + { + DBG_ERR ("filename too long\n"); + return SANE_FALSE; + } + sprintf (file_name, "%s/%s", search_dir, dir_entry->d_name); + DBG_INFO ("-> reading desc file: %s\n", file_name); + fp = fopen (file_name, "r"); + if (!fp) + { + DBG_ERR ("can't open desc file: %s (%s)\n", file_name, + strerror (errno)); + return SANE_FALSE; + } + /* now we check if everything is ok with the previous backend + before we read the new one */ + if (current_backend) + { + type_entry *current_type = current_backend->type; + int no_usbids = 0; + int no_interface = 0; + int no_status = 0; + + while (current_type) + { + if (current_type->type == type_scanner || + current_type->type == type_stillcam || + current_type->type == type_vidcam) + { + mfg_entry *current_mfg = current_type->mfg; + + while (current_mfg) + { + model_entry *current_model = current_mfg->model; + + while (current_model) + { + if (current_model->status == status_unknown) + { + DBG_INFO + ("Backend `%s': `%s' `%s' does not have a status\n", + current_backend->name, + current_mfg->name, + current_model->name); + no_status++; + } + if (!current_model->interface) + { + DBG_INFO + ("Backend `%s': `%s' `%s' does not have an interface\n", + current_backend->name, + current_mfg->name, + current_model->name); + no_interface++; + } + else if (strstr (current_model->interface, "USB")) + { + if ((!current_model->usb_vendor_id || !current_model->usb_product_id) + && !current_model->ignore_usb_id) + { + DBG_INFO ("`%s' seems to provide a USB device " + "without :usbid (%s %s)\n", + current_backend->name, + current_mfg->name, + current_model->name); + no_usbids++; + } + } + current_model = current_model->next; + } + current_mfg = current_mfg->next; + } + } + current_type = current_type->next; + } + if (no_status) + { + DBG_WARN ("Backend `%s': %d devices without :status\n", + current_backend->name, no_status); + } + if (no_interface) + { + DBG_WARN ("Backend `%s': %d devices without :interface\n", + current_backend->name, no_interface); + } + if (no_usbids) + { + DBG_WARN ("Backend `%s': %d USB devices without :usbid\n", + current_backend->name, no_usbids); + } + } + desc_name = dir_entry->d_name; + current_backend = 0; + current_type = 0; + current_mfg = 0; + current_model = 0; + while (sanei_config_read (line, sizeof (line), fp)) + { + char *string_entry = 0; + char **two_string_entry; + char **three_string_entry; + word = 0; + + cp = get_token (line, &word); + if (!word || cp == line) + { + DBG_DBG ("ignoring empty line\n"); + if (word) + free (word); + word = 0; + continue; + } + if (word[0] == ';') + { + DBG_DBG ("ignoring comment line\n"); + free (word); + word = 0; + continue; + } + DBG_DBG ("line: %s\n", line); + + if (read_keyword + (line, ":backend", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + backend_entry *be = first_backend, *prev_be = + 0, *new_be = 0; + DBG_INFO ("creating backend entry `%s'\n", + string_entry); + + new_be = calloc (1, sizeof (backend_entry)); + if (!new_be) + { + DBG_ERR ("calloc failed (%s)\n", strerror (errno)); + return SANE_FALSE; + } + new_be->name = string_entry; + new_be->new = SANE_FALSE; + + if (!be) + { + first_backend = new_be; + be = new_be; + } + else + { + while (be) + { + int compare = + string_compare (new_be->name, be->name); + if (compare <= 0) + { + backend_entry *be_tmp = be; + be = new_be; + be->next = be_tmp; + if (!prev_be) + first_backend = be; + else + prev_be->next = be; + break; + } + prev_be = be; + be = be->next; + } + if (!be) /* last entry */ + { + prev_be->next = new_be; + be = prev_be->next; + } + } + current_backend = be; + current_type = 0; + current_mfg = 0; + current_model = 0; + current_level = level_backend; + continue; + } + if (!current_backend) + { + DBG_ERR ("use `:backend' keyword first\n"); + return SANE_FALSE; + } + if (read_keyword + (line, ":version", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + if (current_backend->version) + { + DBG_WARN + ("overwriting version of backend `%s' to `%s'" + "(was: `%s')\n", current_backend->name, + string_entry, current_backend->version, + current_backend->version); + } + + DBG_INFO ("setting version of backend `%s' to `%s'\n", + current_backend->name, string_entry); + current_backend->version = string_entry; + continue; + } + if (read_keyword + (line, ":status", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + switch (current_level) + { + case level_model: + if (current_model->status != status_unknown) + { + DBG_WARN + ("overwriting status of model `%s' (backend `%s')\n", + current_model->name, current_backend->name); + } + if (strcmp (string_entry, ":minimal") == 0) + { + DBG_INFO + ("setting status of model `%s' to `minimal'\n", + current_model->name); + current_model->status = status_minimal; + } + else if (strcmp (string_entry, ":basic") == 0) + { + DBG_INFO + ("setting status of model `%s' to `basic'\n", + current_model->name); + current_model->status = status_basic; + } + else if (strcmp (string_entry, ":good") == 0) + { + DBG_INFO + ("setting status of model `%s' to `good'\n", + current_model->name); + current_model->status = status_good; + } + else if (strcmp (string_entry, ":complete") == 0) + { + DBG_INFO + ("setting status of model `%s' to `complete'\n", + current_model->name); + current_model->status = status_complete; + } + else if (strcmp (string_entry, ":untested") == 0) + { + DBG_INFO + ("setting status of model `%s' to `untested'\n", + current_model->name); + current_model->status = status_untested; + } + else if (strcmp (string_entry, ":unsupported") == 0) + { + DBG_INFO + ("setting status of model `%s' to `unsupported'\n", + current_model->name); + current_model->status = status_unsupported; + } + else + { + DBG_ERR + ("unknown status of model `%s': `%s' (backend `%s')\n", + current_model->name, string_entry, + current_backend->name); + current_model->status = status_untested; + return SANE_FALSE; + } + break; + default: + DBG_ERR + ("level %d not implemented for :status (backend `%s')\n", + current_level, current_backend->name); + return SANE_FALSE; + } + + + continue; + } + if (read_keyword (line, ":new", param_string, &string_entry) + == SANE_STATUS_GOOD) + { + if (strcmp (string_entry, ":yes") == 0) + { + DBG_INFO + ("backend %s is new in this SANE release\n", + current_backend->name); + current_backend->new = SANE_TRUE; + } + else if (strcmp (string_entry, ":no") == 0) + { + DBG_INFO + ("backend %s is NOT new in this SANE release\n", + current_backend->name); + current_backend->new = SANE_FALSE; + } + else + { + DBG_ERR ("unknown :new parameter of backend `%s': " + "`%s'\n", current_backend->name, + string_entry); + current_backend->new = SANE_FALSE; + return SANE_FALSE; + } + continue; + } + if (read_keyword + (line, ":manpage", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + if (current_backend->manpage) + { + DBG_WARN + ("overwriting manpage of backend `%s' to `%s'" + "(was: `%s')\n", current_backend->name, + string_entry, current_backend->manpage); + } + + DBG_INFO ("setting manpage of backend `%s' to `%s'\n", + current_backend->name, string_entry); + current_backend->manpage = string_entry; + continue; + } + if (read_keyword + (line, ":devicetype", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + type_entry *type = 0; + + type = current_backend->type; + + DBG_INFO + ("adding `%s' to list of device types of backend " + "`%s'\n", string_entry, current_backend->name); + + if (type) + { + while (type->next) + type = type->next; + type->next = calloc (1, sizeof (type_entry)); + type = type->next; + } + else + { + current_backend->type = + calloc (1, sizeof (type_entry)); + type = current_backend->type; + } + + type->type = type_unknown; + if (strcmp (string_entry, ":scanner") == 0) + { + DBG_INFO ("setting device type of backend `%s' to " + "scanner\n", current_backend->name); + type->type = type_scanner; + } + else if (strcmp (string_entry, ":stillcam") == 0) + { + DBG_INFO ("setting device type of backend `%s' to " + "still camera\n", current_backend->name); + type->type = type_stillcam; + } + else if (strcmp (string_entry, ":vidcam") == 0) + { + DBG_INFO ("setting device type of backend `%s' to " + "video camera\n", current_backend->name); + type->type = type_vidcam; + } + else if (strcmp (string_entry, ":api") == 0) + { + DBG_INFO ("setting device type of backend `%s' to " + "API\n", current_backend->name); + type->type = type_api; + } + else if (strcmp (string_entry, ":meta") == 0) + { + DBG_INFO ("setting device type of backend `%s' to " + "meta\n", current_backend->name); + type->type = type_meta; + } + else + { + DBG_ERR + ("unknown device type of backend `%s': `%s'\n", + current_backend->name, string_entry); + type->type = type_unknown; + return SANE_FALSE; + } + current_type = type; + current_mfg = 0; + current_model = 0; + continue; + } + if (read_keyword + (line, ":desc", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + if (!current_type) + { + DBG_ERR + ("use `:devicetype' keyword first (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + if (current_type->type < type_meta) + { + DBG_ERR + ("use `:desc' for `:api' and `:meta' only (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + + if (current_type->desc) + { + DBG_WARN + ("overwriting description of device type of " + "backend `%s' to `%s' (was: `%s')\n", + current_backend->name, string_entry, + current_type->desc); + } + + DBG_INFO + ("setting description of backend `%s' to `%s'\n", + current_backend->name, string_entry); + current_type->desc = calloc (1, sizeof (desc_entry)); + if (!current_type->desc) + { + DBG_ERR ("calloc failed (%s)\n", strerror (errno)); + return SANE_FALSE; + } + current_type->desc->desc = string_entry; + current_level = level_desc; + current_mfg = 0; + current_model = 0; + continue; + } + if (read_keyword (line, ":mfg", param_string, &string_entry) + == SANE_STATUS_GOOD) + { + mfg_entry *mfg = 0; + + if (!current_type) + { + DBG_ERR + ("use `:devicetype' keyword first (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + if (current_type->type >= type_meta) + { + DBG_ERR + ("use `:mfg' for hardware devices only (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + + mfg = current_type->mfg; + if (mfg) + { + while (mfg->next) + mfg = mfg->next; + mfg->next = calloc (1, sizeof (mfg_entry)); + mfg = mfg->next; + } + else + { + current_type->mfg = calloc (1, sizeof (mfg_entry)); + mfg = current_type->mfg; + } + + if (!mfg) + { + DBG_ERR ("calloc failed (%s)\n", strerror (errno)); + return SANE_FALSE; + } + mfg->name = string_entry; + DBG_INFO ("adding mfg entry %s to backend `%s'\n", + string_entry, current_backend->name); + current_mfg = mfg; + current_model = 0; + current_level = level_mfg; + continue; + } + if (read_keyword + (line, ":model", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + model_entry *model = 0; + + if (!current_type) + { + DBG_ERR + ("use `:devicetype' keyword first (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + if (current_level != level_mfg + && current_level != level_model) + { + DBG_ERR + ("use `:mfg' keyword first (backend `%s')\n", + current_backend->name); + return SANE_FALSE; + } + model = current_mfg->model; + if (model) + { + while (model->next) + model = model->next; + model->next = calloc (1, sizeof (model_entry)); + model = model->next; + } + else + { + current_mfg->model = + calloc (1, sizeof (model_entry)); + model = current_mfg->model; + } + + if (!model) + { + DBG_ERR ("calloc failed (%s)\n", strerror (errno)); + return SANE_FALSE; + } + model->name = string_entry; + model->status = status_unknown; + DBG_INFO + ("adding model entry %s to manufacturer `%s'\n", + string_entry, current_mfg->name); + current_model = model; + current_level = level_model; + continue; + } + if (read_keyword + (line, ":interface", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + if (!current_model) + { + DBG_WARN + ("ignored `%s' :interface, only allowed for " + "hardware devices\n", current_backend->name); + continue; + } + + if (current_model->interface) + { + DBG_WARN ("overwriting `%s's interface of model " + "`%s' to `%s' (was: `%s')\n", + current_backend->name, + current_model->name, string_entry, + current_model->interface); + } + + DBG_INFO ("setting interface of model `%s' to `%s'\n", + current_model->name, string_entry); + current_model->interface = string_entry; + continue; + } + if (read_keyword + (line, ":scsi", param_three_strings, + &three_string_entry) == SANE_STATUS_GOOD) + { + if (!current_model) + { + DBG_WARN + ("ignored `%s' :scsi, only allowed for " + "hardware devices\n", current_backend->name); + continue; + } + + DBG_INFO ("setting scsi vendor and product ids of model `%s' to `%s/%s'\n", + current_model->name, three_string_entry[0], three_string_entry[1]); + if (strcasecmp (three_string_entry[0], "ignore") == 0) + { + DBG_INFO ("Ignoring `%s's scsi-entries of `%s'\n", + current_backend->name, + current_model->name); + continue; + } + if (strcasecmp (three_string_entry[2], "processor") == 0){ + current_model->scsi_is_processor = SANE_TRUE; + current_model->scsi_vendor_id = three_string_entry[0]; + current_model->scsi_product_id = three_string_entry[1]; + } + else + { + DBG_INFO ("scsi-format info in %s is invalid -> break\n", current_backend->name); + continue; + } + continue; + } + if (read_keyword + (line, ":usbid", param_two_strings, + &two_string_entry) == SANE_STATUS_GOOD) + { + if (!current_model) + { + DBG_WARN + ("ignored `%s' :usbid, only allowed for " + "hardware devices\n", current_backend->name); + continue; + } + if (strcasecmp (two_string_entry[0], "ignore") == 0) + { + DBG_INFO ("Ignoring `%s's USB ids of `%s'\n", + current_backend->name, + current_model->name); + current_model->ignore_usb_id = SANE_TRUE; + continue; + } + if (!check_hex (two_string_entry[0])) + { + DBG_WARN ("`%s's USB vendor id of `%s' is " + "not a lowercase 4-digit hex number: " + "`%s'\n", current_backend->name, + current_model->name, two_string_entry[0]); + continue; + } + if (!check_hex (two_string_entry[1])) + { + DBG_WARN ("`%s's USB product id of `%s' is " + "not a lowercase 4-digit hex number: " + "`%s'\n", current_backend->name, + current_model->name, two_string_entry[1]); + continue; + } + + if (current_model->usb_vendor_id || current_model->usb_product_id) + { + DBG_WARN ("overwriting `%s's USB ids of model " + "`%s' to `%s/%s' (was: `%s/%s')\n", + current_backend->name, + current_model->name, two_string_entry[0], + two_string_entry[1], + current_model->usb_vendor_id, + current_model->usb_product_id); + } + + DBG_INFO ("setting USB vendor and product ids of model `%s' to `%s/%s'\n", + current_model->name, two_string_entry[0], two_string_entry[1]); + current_model->usb_vendor_id = two_string_entry[0]; + current_model->usb_product_id = two_string_entry[1]; + continue; + } + if (read_keyword (line, ":url", param_string, &string_entry) + == SANE_STATUS_GOOD) + { + switch (current_level) + { + case level_backend: + current_backend->url = + update_url_list (current_backend->url, + string_entry); + DBG_INFO ("adding `%s' to list of urls of backend " + "`%s'\n", string_entry, + current_backend->name); + break; + case level_mfg: + current_mfg->url = + update_url_list (current_mfg->url, string_entry); + DBG_INFO ("adding `%s' to list of urls of mfg " + "`%s'\n", string_entry, + current_mfg->name); + break; + case level_desc: + current_type->desc->url = + update_url_list (current_type->desc->url, + string_entry); + DBG_INFO + ("adding `%s' to list of urls of description " + "for backend `%s'\n", string_entry, + current_backend->name); + break; + case level_model: + current_model->url = + update_url_list (current_model->url, + string_entry); + DBG_INFO ("adding `%s' to list of urls of model " + "`%s'\n", string_entry, + current_model->name); + break; + default: + DBG_ERR + ("level %d not implemented for :url (backend `%s')\n", + current_level, current_backend->name); + return SANE_FALSE; + } + continue; + } + if (read_keyword + (line, ":comment", param_string, + &string_entry) == SANE_STATUS_GOOD) + { + switch (current_level) + { + case level_backend: + current_backend->comment = string_entry; + DBG_INFO ("setting comment of backend %s to `%s'\n", + current_backend->name, string_entry); + break; + case level_mfg: + current_mfg->comment = string_entry; + DBG_INFO + ("setting comment of manufacturer %s to `%s'\n", + current_mfg->name, string_entry); + break; + case level_desc: + current_type->desc->comment = string_entry; + DBG_INFO ("setting comment of description for " + "backend %s to `%s'\n", + current_backend->name, string_entry); + break; + case level_model: + current_model->comment = string_entry; + DBG_INFO ("setting comment of model %s to `%s'\n", + current_model->name, string_entry); + break; + default: + DBG_ERR + ("level %d not implemented for `:comment' (backend `%s')\n", + current_level, current_backend->name); + return SANE_FALSE; + } + continue; + } + DBG_ERR + ("unknown keyword token in line `%s' of file `%s'\n", + line, file_name); + return SANE_FALSE; + } /* while (sanei_config_readline) */ + fclose (fp); + } /* if (strlen) */ + } /* while (direntry) */ + if (closedir(dir) != 0) + { + DBG_ERR ("cannot close directory `%s' (%s)\n", search_dir, + strerror (errno)); + return SANE_FALSE; + } + if (end) + search_dir = end + 1; + else + search_dir = (search_dir + strlen (search_dir)); + } + + desc_name = 0; + if (!first_backend) + { + DBG_ERR ("Couldn't find any .desc file\n"); + return SANE_FALSE; + } + return SANE_TRUE; +} + +/* Create a model_record_entry based on a model_entry */ +static model_record_entry * +create_model_record (model_entry * model) +{ + model_record_entry *model_record; + + model_record = calloc (1, sizeof (model_record_entry)); + if (!model_record) + { + DBG_ERR ("create_model_record: couldn't calloc model_record_entry\n"); + exit (1); + } + model_record->name = model->name; + model_record->status = model->status; + model_record->interface = model->interface; + model_record->url = model->url; + model_record->comment = model->comment; + model_record->usb_vendor_id = model->usb_vendor_id; + model_record->usb_product_id = model->usb_product_id; + model_record->scsi_vendor_id = model->scsi_vendor_id; + model_record->scsi_product_id = model->scsi_product_id; + model_record->scsi_is_processor = model->scsi_is_processor; + return model_record; +} + +/* Calculate the priority of statuses: */ +/* minimal, basic, good, complete -> 2, untested -> 1, unsupported -> 0 */ +static int +calc_priority (status_entry status) +{ + switch (status) + { + case status_untested: + return 1; + case status_unsupported: + return 0; + default: + return 2; + } +} + +/* Insert model into list at the alphabetically correct position */ +static model_record_entry * +update_model_record_list (model_record_entry * first_model_record, + model_entry * model, backend_entry * be) +{ + model_record_entry *model_record = first_model_record; + + if (!first_model_record) + { + /* First model for this manufacturer */ + first_model_record = create_model_record (model); + model_record = first_model_record; + } + else + { + model_record_entry *prev_model_record = 0; + + while (model_record) + { + int compare = string_compare (model->name, model_record->name); + if (compare <= 0) + { + model_record_entry *tmp_model_record = model_record; + if ((compare == 0) && + (string_compare (model->interface, model_record->interface) == 0) && + (string_compare (model->usb_vendor_id, model_record->usb_vendor_id) == 0) && + (string_compare (model->usb_product_id, model_record->usb_product_id) == 0)) + { + /* Two entries for the same model */ + int new_priority = calc_priority (model->status); + int old_priority = calc_priority (model_record->status); + if (new_priority < old_priority) + { + DBG_DBG + ("update_model_record_list: model %s ignored, backend %s has " + "higher priority\n", model->name, + model_record->be->name); + return first_model_record; + } + if (new_priority > old_priority) + { + DBG_DBG + ("update_model_record_list: model %s overrides the one from backend %s\n", + model->name, model_record->be->name); + tmp_model_record = model_record->next; + } + } + /* correct position */ + model_record = create_model_record (model); + model_record->next = tmp_model_record; + if (!prev_model_record) + first_model_record = model_record; + else + prev_model_record->next = model_record; + break; + } + prev_model_record = model_record; + model_record = model_record->next; + } + if (!model_record) /* last entry */ + { + prev_model_record->next = create_model_record (model); + model_record = prev_model_record->next; + } + } /* if (first_model_record) */ + model_record->be = be; + DBG_DBG ("update_model_record_list: added model %s\n", model->name); + return first_model_record; +} + + +/* Insert manufacturer into list at the alphabetically correct position, */ +/* create new record if neccessary */ +static mfg_record_entry * +update_mfg_record_list (mfg_record_entry * first_mfg_record, mfg_entry * mfg, + backend_entry * be) +{ + model_entry *model = mfg->model; + mfg_record_entry *mfg_record = first_mfg_record; + + while (mfg_record) + { + if (string_compare (mfg_record->name, mfg->name) == 0) + { + /* Manufacturer already exists */ + url_entry *mfg_url = mfg->url; + + /* Manufacturer comments and (additional) URLs? */ + if (!mfg_record->comment) + mfg_record->comment = mfg->comment; + while (mfg_url && mfg_url->name) + { + mfg_record->url = update_url_list (mfg_record->url, + mfg_url->name); + mfg_url = mfg_url->next; + } + break; + } + mfg_record = mfg_record->next; + } + + if (!mfg_record) + { + /* Manufacturer doesn't exist yet */ + url_entry *url = mfg->url; + + mfg_record = calloc (1, sizeof (mfg_record_entry)); + if (!mfg_record) + { + DBG_ERR ("update_mfg_record_list: couldn't calloc " + "mfg_record_entry\n"); + exit (1); + } + mfg_record->name = mfg->name; + mfg_record->comment = mfg->comment; + while (url) + { + mfg_record->url = update_url_list (mfg_record->url, url->name); + url = url->next; + } + if (first_mfg_record != 0) + { + /* We already have one manufacturer in the list */ + mfg_record_entry *new_mfg_record = mfg_record; + mfg_record_entry *prev_mfg_record = 0; + + mfg_record = first_mfg_record; + + while (mfg_record) + { + int compare = + string_compare (new_mfg_record->name, mfg_record->name); + if (compare <= 0) + { + mfg_record_entry *tmp_mfg_record = mfg_record; + mfg_record = new_mfg_record; + mfg_record->next = tmp_mfg_record; + if (!prev_mfg_record) + first_mfg_record = mfg_record; + else + prev_mfg_record->next = mfg_record; + break; + } + prev_mfg_record = mfg_record; + mfg_record = mfg_record->next; + } + if (!mfg_record) /* last entry */ + { + prev_mfg_record->next = new_mfg_record; + mfg_record = prev_mfg_record->next; + } + } + else + first_mfg_record = mfg_record; + DBG_DBG ("update_mfg_record_list: created mfg %s\n", mfg_record->name); + } /* if (!mfg_record) */ + + /* create model entries */ + while (model) + { + mfg_record->model_record = + update_model_record_list (mfg_record->model_record, model, be); + model = model->next; + } + return first_mfg_record; +} + +/* Create a sorted list of manufacturers based on the backends list */ +static mfg_record_entry * +create_mfg_list (device_type dev_type) +{ + mfg_record_entry *first_mfg_record = 0; + backend_entry *be = first_backend; + + DBG_DBG ("create_mfg_list: start\n"); + while (be) + { + type_entry *type = be->type; + while (type) + { + if (type->type == dev_type) + { + mfg_entry *mfg = type->mfg; + while (mfg) + { + first_mfg_record = + update_mfg_record_list (first_mfg_record, mfg, be); + mfg = mfg->next; + } + } + type = type->next; + } + be = be->next; + } + DBG_DBG ("create_mfg_list: exit\n"); + return first_mfg_record; +} + +/* Print an ASCII list with all the information we have */ +static void +ascii_print_backends (void) +{ + backend_entry *be; + + be = first_backend; + while (be) + { + url_entry *url = be->url; + type_entry *type = be->type; + + if (be->name) + printf ("backend `%s'\n", be->name); + else + printf ("backend *none*\n"); + + if (be->version) + printf (" version `%s'\n", be->version); + else + printf (" version *none*\n"); + + if (be->new) + printf (" NEW!\n"); + + if (be->manpage) + printf (" manpage `%s'\n", be->manpage); + else + printf (" manpage *none*\n"); + + if (url) + while (url) + { + printf (" url `%s'\n", url->name); + url = url->next; + } + else + printf (" url *none*\n"); + + if (be->comment) + printf (" comment `%s'\n", be->comment); + else + printf (" comment *none*\n"); + + if (type) + while (type) + { + switch (type->type) + { + case type_scanner: + printf (" type scanner\n"); + break; + case type_stillcam: + printf (" type stillcam\n"); + break; + case type_vidcam: + printf (" type vidcam\n"); + break; + case type_meta: + printf (" type meta\n"); + break; + case type_api: + printf (" type api\n"); + break; + default: + printf (" type *unknown*\n"); + break; + } + if (type->desc) + { + url_entry *url = type->desc->url; + printf (" desc `%s'\n", type->desc->desc); + if (url) + while (url) + { + printf (" url `%s'\n", url->name); + url = url->next; + } + else + printf (" url *none*\n"); + + if (type->desc->comment) + printf (" comment `%s'\n", type->desc->comment); + else + printf (" comment *none*\n"); + } + else if (type->type >= type_meta) + printf (" desc *none*\n"); + + if (type->mfg) + { + mfg_entry *mfg = type->mfg; + while (mfg) + { + model_entry *model = mfg->model; + url_entry *url = mfg->url; + + printf (" mfg `%s'\n", mfg->name); + if (url) + while (url) + { + printf (" url `%s'\n", url->name); + url = url->next; + } + else + printf (" url *none*\n"); + + if (mfg->comment) + printf (" comment `%s'\n", mfg->comment); + else + printf (" comment *none*\n"); + + if (model) + while (model) + { + url_entry *url = model->url; + printf (" model `%s'\n", model->name); + if (model->interface) + printf (" interface `%s'\n", model->interface); + else + printf (" interface *none*\n"); + + if (model->usb_vendor_id) + printf (" usb-vendor-id `%s'\n", model->usb_vendor_id); + else + printf (" usb-vendor-id *none*\n"); + + if (model->usb_product_id) + printf (" usb-product-id `%s'\n", model->usb_product_id); + else + printf (" usb-product-id *none*\n"); + + switch (model->status) + { + case status_minimal: + printf (" status minimal\n"); + break; + case status_basic: + printf (" status basic\n"); + break; + case status_good: + printf (" status good\n"); + break; + case status_complete: + printf (" status complete\n"); + break; + case status_untested: + printf (" status untested\n"); + break; + case status_unsupported: + printf (" status unsupported\n"); + break; + default: + printf (" status *unknown*\n"); + break; + } + + if (url) + while (url) + { + printf (" url `%s'\n", url->name); + url = url->next; + } + else + printf (" url *none*\n"); + + if (model->comment) + printf (" comment `%s'\n", model->comment); + else + printf (" comment *none*\n"); + + model = model->next; + } + else + printf (" model *none*\n"); + + mfg = mfg->next; + } /* while (mfg) */ + } + else if (type->type < type_meta) + printf (" mfg *none*\n"); + type = type->next; + } /* while (type) */ + else + printf (" type *none*\n"); + be = be->next; + } /* while (be) */ +} + + +static char * +clean_string (char *c) +{ + /* not avoided characters */ + + char *aux; + + aux = malloc (strlen (c) * sizeof (char) * 6); + + *aux = '\0'; + + while (*c != '\0') + { + + /*limit to printable ASCII only*/ + if(*c < 0x20 || *c > 0x7e){ + c++; + continue; + } + + switch (*c) + { + case '<': + aux = strcat (aux, "<"); + break; + case '>': + aux = strcat (aux, ">"); + break; + case '\'': + aux = strcat (aux, "'"); + break; + case '&': + aux = strcat (aux, "&"); + break; + default: + aux = strncat (aux, c, 1); + } + c = c + 1; + } + return aux; +} + +/* Print an XML list with all the information we have */ +static void +xml_print_backends (void) +{ + backend_entry *be; + + be = first_backend; + printf ("<backends>\n"); + while (be) + { + url_entry *url = be->url; + type_entry *type = be->type; + + if (be->name) + printf ("<backend name=\"%s\">\n", clean_string (be->name)); + else + printf ("<backend name=\"*none\">\n"); + + if (be->version) + printf ("<version>%s</version> \n", clean_string (be->version)); + else + printf ("<version>*none*</version>\n"); + + if (be->new) + printf ("<new state=\"yes\"/>\n"); + else + printf ("<new state=\"no\"/>\n"); + + + if (be->manpage) + printf (" <manpage>%s</manpage>\n", clean_string (be->manpage)); + else + printf (" <manpage>*none*</manpage>\n"); + + if (url) + while (url) + { + printf (" <url>%s</url>\n", clean_string (url->name)); + url = url->next; + } + else + printf (" <url>*none*</url>\n"); + + if (be->comment) + printf (" <comment>%s</comment>\n", clean_string (be->comment)); + else + printf (" <comment>*none*</comment>\n"); + + if (type) + while (type) + { + + switch (type->type) + { + case type_scanner: + printf (" <type def=\"scanner\">\n"); + break; + case type_stillcam: + printf (" <type def=\"stillcam\">\n"); + break; + case type_vidcam: + printf (" <type def=\"vidcam\">\n"); + break; + case type_meta: + printf (" <type def=\"meta\">\n"); + break; + case type_api: + printf (" <type def=\"api\">\n"); + break; + default: + printf (" <type def=\"*unknown*\">\n"); + break; + } + if (type->desc) + { + url_entry *url = type->desc->url; + printf (" <desc>%s</desc>\n", + clean_string (type->desc->desc)); + if (url) + while (url) + { + printf (" <url>%s</url>\n", clean_string (url->name)); + url = url->next; + } + else + printf (" <url>*none*</url>\n"); + + if (type->desc->comment) + printf (" <comment>%s</comment>\n", + clean_string (type->desc->comment)); + else + printf (" <comment>*none*</comment>\n"); + } + else if (type->type >= type_meta) + printf (" <desc>*none*</desc>\n"); + + if (type->mfg) + { + mfg_entry *mfg = type->mfg; + while (mfg) + { + model_entry *model = mfg->model; + url_entry *url = mfg->url; + + printf (" <mfg name=\"%s\">\n", clean_string (mfg->name)); + if (url) + while (url) + { + printf (" <url>`%s'</url>\n", + clean_string (url->name)); + url = url->next; + } + else + printf (" <url>*none*</url>\n"); + + if (mfg->comment) + printf (" <comment>%s</comment>\n", + clean_string (mfg->comment)); + else + printf (" <comment>*none*</comment>\n"); + + if (model) + while (model) + { + url_entry *url = model->url; + printf (" <model name=\"%s\">\n", + clean_string (model->name)); + if (model->interface) + printf (" <interface>%s</interface>\n", + clean_string (model->interface)); + else + printf (" <interface>*none*</interface>\n"); + + if (model->usb_vendor_id) + printf (" <usbvendorid>%s</usbvendorid>\n", + clean_string (model->usb_vendor_id)); + else + printf (" <usbvendorid>*none*</usbvendorid>\n"); + if (model->usb_product_id) + printf (" <usbproductid>%s</usbproductid>\n", + clean_string (model->usb_product_id)); + else + printf (" <usbproductid>*none*</usbproductid>\n"); + + switch (model->status) + { + case status_minimal: + printf (" <status>minimal</status>\n"); + break; + case status_basic: + printf (" <status>basic</status>\n"); + break; + case status_good: + printf (" <status>good</status>\n"); + break; + case status_complete: + printf (" <status>complete</status>\n"); + break; + case status_untested: + printf (" <status>untested</status>\n"); + break; + case status_unsupported: + printf (" <status>unsupported</status>\n"); + break; + default: + printf (" <status>*unknown*</status>\n"); + break; + } + + if (url) + while (url) + { + printf (" <url>%s</url>\n", + clean_string (url->name)); + url = url->next; + } + else + printf (" <url>*none*</url>\n"); + + if (model->comment) + printf (" <comment>%s</comment>\n", + clean_string (model->comment)); + else + printf (" <comment>*none*</comment>\n"); + + model = model->next; + printf (" </model>\n"); + } /* while (model) */ + else + printf (" <model name=\"*none*\" />\n"); + + printf (" </mfg>\n"); + mfg = mfg->next; + } /* while (mfg) */ + } + else if (type->type < type_meta) + printf (" <mfg>*none*</mfg>\n"); + type = type->next; + printf (" </type>\n"); + } /* while (type) */ + else + printf (" <type>*none*</type>\n"); + printf ("</backend>\n"); + be = be->next; + + } /* while (be) */ + printf ("</backends>\n"); +} + +/* calculate statistics about supported devices per device type*/ +static void +calculate_statistics_per_type (device_type dev_type, statistics_type num) +{ + backend_entry *be = first_backend; + + while (be) + { + type_entry *type = be->type; + + while (type) + { + if (type->type == dev_type) + { + mfg_entry *mfg = type->mfg; + model_entry *model; + + if (type->desc) + { + num[status_complete]++; + type = type->next; + continue; + } + + if (!mfg) + { + type = type->next; + continue; + } + + mfg = type->mfg; + while (mfg) + { + model = mfg->model; + if (model) + { + while (model) + { + enum status_entry status = model->status; + num[status]++; + model = model->next; + } /* while (model) */ + } /* if (num_models) */ + mfg = mfg->next; + } /* while (mfg) */ + } /* if (type->type) */ + type = type->next; + } /* while (type) */ + be = be->next; + } /* while (be) */ +} + +static void +html_print_statistics_cell (const char * color, int number) +{ + printf ("<td align=center><font color=%s>%d</font></td>\n", + color, number); +} + +static void +html_print_statistics_per_type (device_type dev_type) +{ + statistics_type num = {0, 0, 0, 0, 0, 0, 0}; + status_entry status; + + calculate_statistics_per_type (dev_type, num); + printf ("<tr>\n"); + printf("<td align=center><a href=\"#%s\">%s</a></td>\n", + device_type_aname [dev_type], device_type_name [dev_type]); + + html_print_statistics_cell + (COLOR_UNKNOWN, + num[status_minimal] + num[status_basic] + num[status_good] + + num[status_complete] + num[status_untested] + num[status_unsupported]); + if (dev_type == type_scanner || dev_type == type_stillcam + || dev_type == type_vidcam) + { + html_print_statistics_cell + (COLOR_UNKNOWN, + num[status_minimal] + num[status_basic] + num[status_good] + + num[status_complete]); + for (status = status_complete; status >= status_unsupported; status--) + html_print_statistics_cell (status_color [status], num [status]); + } + else + { + printf ("<td align=center colspan=7>n/a</td>\n"); + } + printf ("</tr>\n"); +} + +/* print html statistcis */ +static void +html_print_summary (void) +{ + device_type dev_type; + status_entry status; + + printf ("<h2>Summary</h2>\n"); + printf ("<table border=1>\n"); + printf ("<tr bgcolor=E0E0FF>\n"); + printf ("<th align=center rowspan=3>Device type</th>\n"); + printf ("<th align=center colspan=8>Number of devices</th>\n"); + printf ("</tr>\n"); + printf ("<tr bgcolor=E0E0FF>\n"); + printf ("<th align=center rowspan=2>Total</th>\n"); + printf ("<th align=center colspan=5>Supported</th>\n"); + printf ("<th align=center rowspan=2><font color=" COLOR_UNTESTED + ">%s</font></th>\n", status_name[status_untested]); + printf ("<th align=center rowspan=2><font color=" COLOR_UNSUPPORTED + ">%s</font></th>\n", status_name[status_unsupported]); + printf ("</tr>\n"); + printf ("<tr bgcolor=E0E0FF>\n"); + printf ("<th align=center>Sum</th>\n"); + for (status = status_complete; status >= status_minimal; status--) + printf ("<th align=center><font color=%s>%s</font></th>\n", + status_color[status], status_name[status]); + printf ("</tr>\n"); + for (dev_type = type_scanner; dev_type <= type_api; dev_type++) + html_print_statistics_per_type (dev_type); + printf ("</table>\n"); +} + + +/* Generate a name used for <a name=...> HTML tags */ +static char * +html_generate_anchor_name (device_type dev_type, char *manufacturer_name) +{ + char *name = malloc (strlen (manufacturer_name) + 1 + 2); + char *pointer = name; + char type_char; + + if (!name) + { + DBG_ERR ("html_generate_anchor_name: couldn't malloc\n"); + return 0; + } + + switch (dev_type) + { + case type_scanner: + type_char = 'S'; + break; + case type_stillcam: + type_char = 'C'; + break; + case type_vidcam: + type_char = 'V'; + break; + case type_meta: + type_char = 'M'; + break; + case type_api: + type_char = 'A'; + break; + default: + type_char = 'Z'; + break; + } + + snprintf (name, strlen (manufacturer_name) + 1 + 2, "%c-%s", + type_char, manufacturer_name); + + while (*pointer) + { + if (!isalnum (*pointer)) + *pointer = '-'; + else + *pointer = toupper (*pointer); + pointer++; + } + return name; +} + + +/* Generate one table per backend of all backends providing models */ +/* of type dev_type */ +static void +html_backends_split_table (device_type dev_type) +{ + backend_entry *be = first_backend; + SANE_Bool first = SANE_TRUE; + + printf ("<p><b>Backends</b>: \n"); + while (be) /* print link list */ + { + type_entry *type = be->type; + SANE_Bool found = SANE_FALSE; + + while (type) + { + if (type->type == dev_type) + found = SANE_TRUE; + type = type->next; + } + if (found) + { + if (!first) + printf (", \n"); + first = SANE_FALSE; + printf ("<a href=\"#%s\">%s</a>", + html_generate_anchor_name (dev_type, be->name), be->name); + } + be = be->next; + } + be = first_backend; + if (first) + printf ("(none)\n"); + + printf ("</p>\n"); + + + while (be) + { + type_entry *type = be->type; + + while (type) + { + if (type->type == dev_type) + { + mfg_entry *mfg = type->mfg; + model_entry *model; + + printf ("<h3><a name=\"%s\">Backend: %s\n", + html_generate_anchor_name (type->type, be->name), + be->name); + + if (be->version || be->new) + { + printf ("("); + if (be->version) + { + printf ("%s", be->version); + if (be->new) + printf (", <font color=" COLOR_NEW ">NEW!</font>"); + } + else + printf ("<font color=" COLOR_NEW ">NEW!</font>"); + printf (")\n"); + } + printf ("</a></h3>\n"); + + printf ("<p>\n"); + + if (be->url && be->url->name) + { + url_entry *url = be->url; + printf ("<b>Link(s):</b> \n"); + while (url) + { + if (url != be->url) + printf (", "); + printf ("<a href=\"%s\">%s</a>", url->name, url->name); + url = url->next; + } + printf ("<br>\n"); + } + if (be->manpage) + printf ("<b>Manual page:</b> <a href=\"" MAN_PAGE_LINK + "\">%s</a><br>\n", be->manpage, be->manpage); + + if (be->comment) + printf ("<b>Comment:</b> %s<br>\n", be->comment); + + + if (type->desc) + { + if (type->desc->desc) + { + if (type->desc->url && type->desc->url->name) + printf ("<b>Description:</b> " + "<a href=\"%s\">%s</a><br>\n", + type->desc->url->name, type->desc->desc); + else + printf ("<b>Description:</b> %s<br>\n", + type->desc->desc); + } + + if (type->desc->comment) + printf ("<b>Comment:</b> %s<br>\n", type->desc->comment); + printf ("</p>\n"); + type = type->next; + continue; + } + printf ("</p>\n"); + + if (!mfg) + { + type = type->next; + continue; + } + + printf ("<table border=1>\n"); + + printf ("<tr bgcolor=E0E0FF>\n"); + printf ("<th align=center>Manufacturer</th>\n"); + printf ("<th align=center>Model</th>\n"); + printf ("<th align=center>Interface</th>\n"); + printf ("<th align=center>USB id</th>\n"); + printf ("<th align=center>Status</th>\n"); + printf ("<th align=center>Comment</th>\n"); + printf ("</tr>\n"); + + mfg = type->mfg; + while (mfg) + { + model = mfg->model; + if (model) + { + int num_models = 0; + + while (model) /* count models for rowspan */ + { + model = model->next; + num_models++; + } + model = mfg->model; + printf ("<tr>\n"); + printf ("<td align=center rowspan=%d>\n", num_models); + if (mfg->url && mfg->url->name) + printf ("<a href=\"%s\">%s</a>\n", mfg->url->name, + mfg->name); + else + printf ("%s\n", mfg->name); + + while (model) + { + enum status_entry status = model->status; + + if (model != mfg->model) + printf ("<tr>\n"); + + if (model->url && model->url->name) + printf + ("<td align=center><a href=\"%s\">%s</a></td>\n", + model->url->name, model->name); + else + printf ("<td align=center>%s</td>\n", + model->name); + + if (model->interface) + printf ("<td align=center>%s</td>\n", + model->interface); + else + printf ("<td align=center>?</td>\n"); + + if (model->usb_vendor_id && model->usb_product_id) + printf ("<td align=center>%s/%s</td>\n", + model->usb_vendor_id, model->usb_product_id); + else + printf ("<td align=center> </td>\n"); + + printf ("<td align=center><font color=%s>%s</font></td>\n", + status_color[status], status_name[status]); + + if (model->comment && model->comment[0] != 0) + printf ("<td>%s</td>\n", model->comment); + else + printf ("<td> </td>\n"); + + model = model->next; + printf ("</tr>\n"); + } /* while (model) */ + } /* if (num_models) */ + mfg = mfg->next; + } /* while (mfg) */ + printf ("</table>\n"); + } /* if (type->type) */ + type = type->next; + } /* while (type) */ + be = be->next; + } /* while (be) */ + /* printf ("</table>\n"); */ +} + +/* Generate one table per manufacturer constructed of all backends */ +/* providing models of type dev_type */ +static void +html_mfgs_table (device_type dev_type) +{ + mfg_record_entry *mfg_record = 0, *first_mfg_record = 0; + + first_mfg_record = create_mfg_list (dev_type); + mfg_record = first_mfg_record; + + printf ("<p><b>Manufacturers</b>: \n"); + while (mfg_record) + { + if (mfg_record != first_mfg_record) + printf (", \n"); + printf ("<a href=\"#%s\">%s</a>", + html_generate_anchor_name (type_unknown, mfg_record->name), + mfg_record->name); + mfg_record = mfg_record->next; + } + mfg_record = first_mfg_record; + if (!mfg_record) + printf ("(none)\n"); + printf ("</p>\n"); + while (mfg_record) + { + model_record_entry *model_record = mfg_record->model_record; + + printf ("<h3><a name=\"%s\">Manufacturer: %s</a></h3>\n", + html_generate_anchor_name (type_unknown, mfg_record->name), + mfg_record->name); + printf ("<p>\n"); + if (mfg_record->url && mfg_record->url->name) + { + url_entry *url = mfg_record->url; + printf ("<b>Link(s):</b> \n"); + while (url) + { + if (url != mfg_record->url) + printf (", "); + printf ("<a href=\"%s\">%s</a>", url->name, url->name); + url = url->next; + } + printf ("<br>\n"); + } + if (mfg_record->comment) + printf ("<b>Comment:</b> %s<br>\n", mfg_record->comment); + printf ("</p>\n"); + if (!model_record) + { + mfg_record = mfg_record->next; + continue; + } + printf ("<table border=1>\n"); + printf ("<tr bgcolor=E0E0FF>\n"); + + printf ("<th align=center>Model</th>\n"); + printf ("<th align=center>Interface</th>\n"); + printf ("<th align=center>USB id</th>\n"); + printf ("<th align=center>Status</th>\n"); + printf ("<th align=center>Comment</th>\n"); + printf ("<th align=center>Backend</th>\n"); + printf ("<th align=center>Manpage</th>\n"); + printf ("</tr>\n"); + + while (model_record) + { + enum status_entry status = model_record->status; + + if (model_record->url && model_record->url->name) + printf ("<tr><td align=center><a " + "href=\"%s\">%s</a></td>\n", + model_record->url->name, model_record->name); + else + printf ("<tr><td align=center>%s</td>\n", model_record->name); + + if (model_record->interface) + printf ("<td align=center>%s</td>\n", model_record->interface); + else + printf ("<td align=center>?</td>\n"); + + if (model_record->usb_vendor_id && model_record->usb_product_id) + printf ("<td align=center>%s/%s</td>\n", + model_record->usb_vendor_id, model_record->usb_product_id); + else + printf ("<td align=center> </td>\n"); + + printf ("<td align=center><font color=%s>%s</font></td>\n", + status_color[status], status_name[status]); + + if (model_record->comment && model_record->comment[0] != 0) + printf ("<td>%s</td>\n", model_record->comment); + else + printf ("<td> </td>\n"); + + printf ("<td align=center>\n"); + if (model_record->be->url && model_record->be->url->name) + printf ("<a href=\"%s\">%s</a>\n", + model_record->be->url->name, model_record->be->name); + else + printf ("%s", model_record->be->name); + + if (model_record->be->version || model_record->be->new) + { + printf ("<br>("); + if (model_record->be->version) + { + printf ("%s", model_record->be->version); + if (model_record->be->new) + printf (", <font color=" COLOR_NEW ">NEW!</font>"); + } + else + printf ("<font color=" COLOR_NEW ">NEW!</font>"); + printf (")\n"); + } + + printf ("</td>\n"); + if (model_record->be->manpage) + printf ("<td align=center><a href=\"" + MAN_PAGE_LINK "\">%s</a></td>\n", + model_record->be->manpage, model_record->be->manpage); + else + printf ("<td align=center>?</td>\n"); + + printf ("</tr>\n"); + model_record = model_record->next; + } /* while model_record */ + printf ("</table>\n"); + mfg_record = mfg_record->next; + } /* while mfg_record */ +} + +/* Print the HTML headers and an introduction */ +static void +html_print_header (void) +{ + printf + ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" + "<html> <head>\n" + "<meta http-equiv=\"Content-Type\" content=\"text/html; " + "charset=iso-8859-1\">\n"); + printf ("<title>%s</title>\n", title); + printf + ("</head>\n" + "<body bgcolor=FFFFFF>\n" + "<div align=center>\n" + "<img src=\"http://www.sane-project.org/images/sane.png\" alt=\"SANE\">\n"); + printf ("<h1>%s</h1>\n", title); + printf ("</div>\n" "<hr>\n"); + printf ("%s\n", intro); + printf + ("<p>This is only a summary!\n" + "Please consult the manpages and the author-supplied webpages\n" + "for more detailed (and usually important) information\n" + "concerning each backend.</p>\n"); + printf + ("<p>If you have new information or corrections, please file a\n" + "<a href=\"http://www.sane-project.org/bugs.html\">bug report</a>\n" + "with as many details as possible. Also please tell us if your scanner \n" + "isn't mentioned in this list at all.</p>\n" + "<p>For an explanation of the tables, see the\n" + "<a href=\"#legend\">legend</a>.\n"); +} + +/* Print the HTML footers and contact information */ +static void +html_print_footer (void) +{ + time_t current_time = time (0); + + printf + ("<hr>\n" + "<a href=\"http://www.sane-project.org/\">SANE homepage</a>\n" + "<address>\n" + "<a href=\"http://www.sane-project.org/imprint.html\"\n" + ">Contact</a>\n" "</address>\n" "<font size=-1>\n"); + printf ("This page was last updated on %s by sane-desc %s from %s\n", + asctime (localtime (¤t_time)), SANE_DESC_VERSION, PACKAGE_STRING); + printf ("</font>\n"); + printf ("</body> </html>\n"); +} + + +/* print parts of the legend */ +static void +html_print_legend_backend (void) +{ + printf + (" <dt><b>Backend:</b></dt>\n" + " <dd>Name of the backend, in parentheses if available:\n" + " Version of backend/driver; newer versions may be\n" + " available from their home sites.<br>" + " <font color=" COLOR_NEW ">NEW!</font> means brand-new to the\n" + " current release of SANE.<br>\n" + " UNMAINTAINED means that nobody maintains that backend. Expect no \n" + " new features or newly supported devices. You are welcome to take over \n" + " maintainership.\n" " </dd>\n"); +} + +static void +html_print_legend_link (void) +{ + printf + (" <dt><b>Link(s):</b></dt>\n" + " <dd>Link(s) to more extensive and\n" + " detailed information, if it exists, or the email address\n" + " of the author or maintainer.\n"); +} + +static void +html_print_legend_manual (void) +{ + printf + (" <dt><b>Manual Page:</b></dt>\n" + " <dd>A link to the man-page online, if it exists.</dd>\n"); +} + +static void +html_print_legend_comment (void) +{ + printf + (" <dt><b>Comment:</b></dt>\n" + " <dd>More information about the backend or model, e.g. the level of " + " support and possible problems.</dd>\n"); +} + +static void +html_print_legend_manufacturer (void) +{ + printf + (" <dt><b>Manufacturer:</b></dt>\n" + " <dd>Manufacturer, vendor or brand name of the device.</dd>\n"); +} + +static void +html_print_legend_model (void) +{ + printf + (" <dt><b>Model:</b></dt>\n" " <dd>Name of the the device.</dd>\n"); +} + +static void +html_print_legend_interface (void) +{ + printf + (" <dt><b>Interface:</b></dt>\n" + " <dd>How the device is connected to the computer.</dd>\n"); +} + +static void +html_print_legend_usbid (void) +{ + printf + (" <dt><b>USB id:</b></dt>\n" + " <dd>The USB vendor and product ids as printed by sane-find-scanner -q (only applicable for USB devices).</dd>\n"); +} + +static void +html_print_legend_status (void) +{ + printf + (" <dt><b>Status</b>:</dt>\n" + " <dd>Indicates how many of the features the device provides \n" + " are supported by SANE.\n" + " <ul><li><font color=" COLOR_UNSUPPORTED ">unsupported</font>" + " means the device is not supported at least by this backend. " + " It may be supported by other backends, however.\n"); + printf + (" <li><font color=" COLOR_UNTESTED ">untested</font> means the " + " device may be supported but couldn't be tested. Be very " + " careful and report success/failure.\n" + " <li><font color=" COLOR_MINIMAL ">minimal</font> means that the\n" + " device is detected and scans at least in one mode. But the quality \n" + " is bad or important features won't work.\n"); + printf + (" <li><font color=" COLOR_BASIC ">basic</font> means it works at \n" + " least in the most important modes but quality is not perfect.\n" + " <li><font color=" COLOR_GOOD + ">good</font> means the device is usable \n" + " for day-to-day work. Some rather exotic features may be missing.\n" + " <li><font color=" COLOR_COMPLETE + ">complete</font> means the backends \n" + " supports everything the device can do.\n" " </ul></dd>\n"); +} + +static void +html_print_legend_description (void) +{ + printf + (" <dt><b>Description</b>:</dt>\n" + " <dd>The scope of application of the backend.\n"); +} + +/* Print the HTML page with one table of models per backend */ +static void +html_print_backends_split (void) +{ + if (!title) + title = "SANE: Backends (Drivers)"; + if (!intro) + intro = "<p> The following table summarizes the backends/drivers " + "distributed with the latest version of sane-backends, and the hardware " + "or software they support. </p>"; + + html_print_header (); + + html_print_summary (); + + printf ("<h2><a name=\"SCANNERS\">Scanners</a></h2>\n"); + html_backends_split_table (type_scanner); + + printf ("<h2><a name=\"STILL\">Still Cameras</a></h2>\n"); + html_backends_split_table (type_stillcam); + + printf ("<h2><a name=\"VIDEO\">Video Cameras</a></h2>\n"); + html_backends_split_table (type_vidcam); + + printf ("<h2><a name=\"API\">APIs</a></h2>\n"); + html_backends_split_table (type_api); + + printf ("<h2><a name=\"META\">Meta Backends</a></h2>\n"); + html_backends_split_table (type_meta); + + printf ("<h3><a name=\"legend\">Legend:</a></h3>\n" "<blockquote><dl>\n"); + + html_print_legend_backend (); + html_print_legend_link (); + html_print_legend_manual (); + html_print_legend_comment (); + html_print_legend_manufacturer (); + html_print_legend_model (); + html_print_legend_interface (); + html_print_legend_usbid (); + html_print_legend_status (); + html_print_legend_description (); + + printf ("</dl></blockquote>\n"); + + html_print_footer (); +} + +/* Print the HTML page with one table of models per manufacturer */ +static void +html_print_mfgs (void) +{ + if (!title) + title = "SANE: Supported Devices"; + + if (!intro) + intro = "<p> The following table summarizes the devices supported " + "by the latest version of sane-backends. </p>"; + + html_print_header (); + + html_print_summary (); + + printf ("<h2><a name=\"SCANNERS\">Scanners</a></h2>\n"); + html_mfgs_table (type_scanner); + + printf ("<h2><a name=\"STILL\">Still Cameras</a></h2>\n"); + html_mfgs_table (type_stillcam); + + printf ("<h2><a name=\"VIDEO\">Video Cameras</a></h2>\n"); + html_mfgs_table (type_vidcam); + + printf ("<h2><a name=\"API\">APIs</a></h2>\n"); + html_backends_split_table (type_api); + + printf ("<h2><a name=\"META\">Meta Backends</a></h2>\n"); + html_backends_split_table (type_meta); + + printf + ("<h3><a name=\"legend\">Legend:</a></h3>\n" "<blockquote>\n" "<dl>\n"); + + html_print_legend_model (); + html_print_legend_interface (); + html_print_legend_usbid (); + html_print_legend_status (); + html_print_legend_comment (); + html_print_legend_backend (); + html_print_legend_manual (); + + html_print_legend_manufacturer (); + html_print_legend_description (); + + printf ("</dl>\n" "</blockquote>\n"); + + html_print_footer (); +} + + +/* print statistics about supported devices */ +static void +print_statistics_per_type (device_type dev_type) +{ + statistics_type num = {0, 0, 0, 0, 0, 0, 0}; + + calculate_statistics_per_type (dev_type, num); + + printf (" Total: %4d\n", + num[status_minimal] + num[status_basic] + num[status_good] + + num[status_complete] + num[status_untested] + num[status_untested] + + num[status_unsupported]); + if (dev_type == type_scanner || dev_type == type_stillcam + || dev_type == type_vidcam) + { + printf (" Supported: %4d (complete: %d, good: %d, basic: %d, " + "minimal: %d)\n", + num[status_minimal] + num[status_basic] + num[status_good] + + num[status_complete], num[status_complete], num[status_good], + num[status_basic], num[status_minimal]); + printf (" Untested: %4d\n", num[status_untested]); + printf (" Unsupported: %4d\n", num[status_unsupported]); + } +} + +static void +print_statistics (void) +{ + printf ("Number of known devices:\n"); + printf ("Scanners:\n"); + print_statistics_per_type (type_scanner); + printf ("Still cameras:\n"); + print_statistics_per_type (type_stillcam); + printf ("Video cameras:\n"); + print_statistics_per_type (type_vidcam); + printf ("Meta backends:\n"); + print_statistics_per_type (type_meta); + printf ("API backends:\n"); + print_statistics_per_type (type_api); +} + +static usbid_type * +create_usbid (char *manufacturer, char *model, + char *usb_vendor_id, char *usb_product_id) +{ + usbid_type * usbid = calloc (1, sizeof (usbid_type)); + + usbid->usb_vendor_id = strdup (usb_vendor_id); + usbid->usb_product_id = strdup (usb_product_id); + usbid->name = calloc (1, sizeof (manufacturer_model_type)); + usbid->name->name = calloc (1, strlen (manufacturer) + strlen (model) + 3); + sprintf (usbid->name->name, "%s %s", manufacturer, model); + usbid->name->next = 0; + usbid->next = 0; + DBG_DBG ("New USB ids: %s/%s (%s %s)\n", usb_vendor_id, usb_product_id, + manufacturer, model); + return usbid; +} + +static scsiid_type * +create_scsiid (char *manufacturer, char *model, + char *scsi_vendor_id, char *scsi_product_id, SANE_Bool is_processor) +{ + scsiid_type * scsiid = calloc (1, sizeof (scsiid_type)); + + scsiid->scsi_vendor_id = strdup (scsi_vendor_id); + scsiid->scsi_product_id = strdup (scsi_product_id); + scsiid->is_processor = is_processor; + scsiid->name = calloc (1, sizeof (manufacturer_model_type)); + scsiid->name->name = calloc (1, strlen (manufacturer) + strlen (model) + 3); + sprintf (scsiid->name->name, "%s %s", manufacturer, model); + scsiid->name->next = 0; + scsiid->next = 0; + DBG_DBG ("New SCSI ids: %s/%s (%s %s)\n", scsi_vendor_id, scsi_product_id, + manufacturer, model); + return scsiid; +} + +static usbid_type * +add_usbid (usbid_type *first_usbid, char *manufacturer, char *model, + char *usb_vendor_id, char *usb_product_id) +{ + usbid_type *usbid = first_usbid; + usbid_type *prev_usbid = 0, *tmp_usbid = 0; + + if (!first_usbid) + first_usbid = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); + else + { + while (usbid) + { + if (strcmp (usb_vendor_id, usbid->usb_vendor_id) == 0 && + strcmp (usb_product_id, usbid->usb_product_id) == 0) + { + manufacturer_model_type *man_mod = usbid->name; + + while (man_mod->next) + man_mod = man_mod->next; + man_mod->next = malloc (sizeof (manufacturer_model_type)); + man_mod->next->name = malloc (strlen (manufacturer) + strlen (model) + 3); + sprintf (man_mod->next->name, "%s %s", manufacturer, model); + man_mod->next->next = 0; + DBG_DBG ("Added manufacturer/model %s %s to USB ids %s/%s\n", manufacturer, model, + usb_vendor_id, usb_product_id); + break; + } + if (strcmp (usb_vendor_id, usbid->usb_vendor_id) < 0 || + (strcmp (usb_vendor_id, usbid->usb_vendor_id) == 0 && + strcmp (usb_product_id, usbid->usb_product_id) < 0)) + { + + tmp_usbid = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); + tmp_usbid->next = usbid; + if (prev_usbid) + prev_usbid->next = tmp_usbid; + else + first_usbid = tmp_usbid; + break; + } + prev_usbid = usbid; + usbid = usbid->next; + } + if (!usbid) + { + prev_usbid->next = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); + usbid = prev_usbid->next; + } + } + return first_usbid; +} + +static scsiid_type * +add_scsiid (scsiid_type *first_scsiid, char *manufacturer, char *model, + char *scsi_vendor_id, char *scsi_product_id, SANE_Bool is_processor) +{ + scsiid_type *scsiid = first_scsiid; + scsiid_type *prev_scsiid = 0, *tmp_scsiid = 0; + + if (!first_scsiid) + first_scsiid = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); + else + { + while (scsiid) + { + if (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) == 0 && + strcmp (scsi_product_id, scsiid->scsi_product_id) == 0) + { + manufacturer_model_type *man_mod = scsiid->name; + + while (man_mod->next) + man_mod = man_mod->next; + man_mod->next = malloc (sizeof (manufacturer_model_type)); + man_mod->next->name = malloc (strlen (manufacturer) + strlen (model) + 3); + sprintf (man_mod->next->name, "%s %s", manufacturer, model); + man_mod->next->next = 0; + DBG_DBG ("Added manufacturer/model %s %s to SCSI ids %s/%s\n", manufacturer, model, + scsi_vendor_id, scsi_product_id); + break; + } + if (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) < 0 || + (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) == 0 && + strcmp (scsi_product_id, scsiid->scsi_product_id) < 0)) + { + + tmp_scsiid = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); + tmp_scsiid->next = scsiid; + if (prev_scsiid) + prev_scsiid->next = tmp_scsiid; + else + first_scsiid = tmp_scsiid; + break; + } + prev_scsiid = scsiid; + scsiid = scsiid->next; + } + if (!scsiid) + { + prev_scsiid->next = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); + scsiid = prev_scsiid->next; + } + } + return first_scsiid; +} + +static usbid_type * +create_usbids_table (void) +{ + backend_entry *be; + usbid_type *first_usbid = NULL; + + if (!first_backend) + return NULL; + + for (be = first_backend; be; be = be->next) + { + type_entry *type; + + if (!be->type) + continue; + + for (type = be->type; type; type = type->next) + { + mfg_entry *mfg; + + if (!type->mfg) + continue; + + for (mfg = type->mfg; mfg; mfg = mfg->next) + { + model_entry *model; + + if (!mfg->model) + continue; + + for (model = mfg->model; model; model = model->next) + { + if ((model->status == status_unsupported) + || (model->status == status_unknown)) + continue; + + if (model->usb_vendor_id && model->usb_product_id) + { + first_usbid = add_usbid (first_usbid, mfg->name, + model->name, + model->usb_vendor_id, + model->usb_product_id); + } + } /* for (model) */ + } /* for (mfg) */ + } /* for (type) */ + } /* for (be) */ + + return first_usbid; +} + +static scsiid_type * +create_scsiids_table (void) +{ + backend_entry *be; + scsiid_type *first_scsiid = NULL; + + if (!first_backend) + return NULL; + + for (be = first_backend; be; be = be->next) + { + type_entry *type; + + if (!be->type) + continue; + + for (type = be->type; type; type = type->next) + { + mfg_entry *mfg; + + if (!type->mfg) + continue; + + for (mfg = type->mfg; mfg; mfg = mfg->next) + { + model_entry *model; + + if (!mfg->model) + continue; + + for (model = mfg->model; model; model = model->next) + { + if ((model->status == status_unsupported) + || (model->status == status_unknown)) + continue; + + if (model->scsi_vendor_id && model->scsi_product_id) + { + first_scsiid = add_scsiid (first_scsiid, mfg->name, + model->name, + model->scsi_vendor_id, + model->scsi_product_id, + model->scsi_is_processor); + } + } /* for (model) */ + } /* for (mfg) */ + } /* for (type) */ + } /* for (be) */ + + return first_scsiid; +} + +/* print USB usermap file to be used by the hotplug tools */ +static void +print_usermap_header (void) +{ + time_t current_time = time (0); + + printf + ("# This file was automatically created based on description files (*.desc)\n" + "# by sane-desc %s from %s on %s" + "#\n" + , + SANE_DESC_VERSION, PACKAGE_STRING, asctime (localtime (¤t_time))); + + printf + ("# The entries below are used to detect a USB device and change owner\n" + "# and permissions on the \"device node\" used by libusb.\n" + "#\n" + "# The 0x0003 match flag means the device is matched by its vendor and\n" + "# product IDs.\n" + "#\n" + "# Sample entry (replace 0xVVVV and 0xPPPP with vendor ID and product ID\n" + "# respectively):\n" + "#\n" + ); + + printf + ("# libusbscanner 0x0003 0xVVVV 0xPPPP 0x0000 0x0000 0x00 0x00 0x00 0x00 " + "0x00 0x00 0x00000000\n" + "# usb module match_flags idVendor idProduct bcdDevice_lo bcdDevice_hi " + "bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass " + "bInterfaceSubClass bInterfaceProtocol driver_info\n" + "#\n" + ); + + printf + ("# If your scanner isn't listed below, you can add it as explained above.\n" + "#\n" + "# If your scanner is supported by some external backend (brother, epkowa,\n" + "# hpaio, etc) please ask the author of the backend to provide proper\n" + "# device detection support for your OS\n" + "#\n" + "# If the scanner is supported by sane-backends, please mail the entry to\n" + "# the sane-devel mailing list (sane-devel@lists.alioth.debian.org).\n" + "#\n" + ); + +} + +static void +print_usermap (void) +{ + usbid_type *usbid = create_usbids_table (); + + print_usermap_header (); + while (usbid) + { + manufacturer_model_type * name = usbid->name; + + printf ("# "); + while (name) + { + if (name != usbid->name) + printf (" | "); + printf ("%s", name->name); + name = name->next; + } + printf ("\n"); + printf ("libusbscanner 0x0003 %s %s ", usbid->usb_vendor_id, + usbid->usb_product_id); + printf ("0x0000 0x0000 0x00 0x00 0x00 0x00 0x00 0x00 0x00000000\n"); + usbid = usbid->next; + } +} + +/* print libsane.db file for hotplug-ng */ +static void +print_db_header (void) +{ + time_t current_time = time (0); + printf ("# This file was automatically created based on description files (*.desc)\n" + "# by sane-desc %s from %s on %s", + SANE_DESC_VERSION, PACKAGE_STRING, asctime (localtime (¤t_time))); + printf + ("#\n" + "# The entries below are used to detect a USB device when it's plugged in\n" + "# and then run a script to change the ownership and\n" + "# permissions on the \"device node\" used by libusb.\n" + "# Sample entry (replace 0xVVVV and 0xPPPP with vendor ID and product ID\n" + "# respectively):\n"); + printf + ("#\n" + "# 0xVVVV<tab>0xPPPP<tab>%s:%s<tab>%s<tab>[/usr/local/bin/foo.sh]\n" + "# Fields:\n" + "# vendor ID\n" + "# product ID\n" + "# ownership (user:group)\n" + "# permissions\n" + "# path of an optional script to run (it can be omitted)\n" + "#\n" + , DEVOWNER, DEVGROUP, DEVMODE); + + printf + ("# If your scanner isn't listed below, you can add it as explained above.\n" + "#\n" + "# If your scanner is supported by some external backend (brother, epkowa,\n" + "# hpaio, etc) please ask the author of the backend to provide proper\n" + "# device detection support for your OS\n" + "#\n" + "# If the scanner is supported by sane-backends, please mail the entry to\n" + "# the sane-devel mailing list (sane-devel@lists.alioth.debian.org).\n" + "#\n" + ); +} + +static void +print_db (void) +{ + usbid_type *usbid = create_usbids_table (); + + print_db_header (); + while (usbid) + { + manufacturer_model_type * name = usbid->name; + + printf ("# "); + while (name) + { + if (name != usbid->name) + printf (" | "); + printf ("%s", name->name); + name = name->next; + } + printf ("\n"); + printf ("%s\t%s\t%s:%s\t%s\t\n", usbid->usb_vendor_id, + usbid->usb_product_id, DEVOWNER, DEVGROUP, DEVMODE); + usbid = usbid->next; + } +} + +/* print libsane.rules for Linux udev */ +static void +print_udev_header (void) +{ + time_t current_time = time (0); + printf ("# This file was automatically created based on description files (*.desc)\n" + "# by sane-desc %s from %s on %s", + SANE_DESC_VERSION, PACKAGE_STRING, asctime (localtime (¤t_time))); + + printf + ("#\n" + "# udev rules file for supported USB and SCSI devices\n" + "#\n" + "# The SCSI device support is very basic and includes only\n" + "# scanners that mark themselves as type \"scanner\" or\n" + "# SCSI-scanners from HP and other vendors that are entitled \"processor\"\n" + "# but are treated accordingly.\n" + "#\n"); + printf + ("# To add a USB device, add a rule to the list below between the\n" + "# LABEL=\"libsane_usb_rules_begin\" and LABEL=\"libsane_usb_rules_end\" lines.\n" + "#\n" + "# To run a script when your device is plugged in, add RUN+=\"/path/to/script\"\n" + "# to the appropriate rule.\n" + "#\n" + ); + printf + ("# If your scanner isn't listed below, you can add it as explained above.\n" + "#\n" + "# If your scanner is supported by some external backend (brother, epkowa,\n" + "# hpaio, etc) please ask the author of the backend to provide proper\n" + "# device detection support for your OS\n" + "#\n" + "# If the scanner is supported by sane-backends, please mail the entry to\n" + "# the sane-devel mailing list (sane-devel@lists.alioth.debian.org).\n" + "#\n" + ); +} + +static void +print_udev (void) +{ + usbid_type *usbid = create_usbids_table (); + scsiid_type *scsiid = create_scsiids_table (); + int i; + + print_udev_header (); + printf("ACTION!=\"add\", GOTO=\"libsane_rules_end\"\n" + "ENV{DEVTYPE}==\"usb_device\", GOTO=\"libsane_create_usb_dev\"\n" + "SUBSYSTEMS==\"scsi\", GOTO=\"libsane_scsi_rules_begin\"\n" + "SUBSYSTEM==\"usb_device\", GOTO=\"libsane_usb_rules_begin\"\n" + "SUBSYSTEM!=\"usb_device\", GOTO=\"libsane_usb_rules_end\"\n" + "\n"); + + printf("# Kernel >= 2.6.22 jumps here\n" + "LABEL=\"libsane_create_usb_dev\"\n" + "\n"); + + printf("# For Linux >= 2.6.22 without CONFIG_USB_DEVICE_CLASS=y\n" + "# If the following rule does not exist on your system yet, uncomment it\n" + "# ENV{DEVTYPE}==\"usb_device\", " + "MODE=\"0664\", OWNER=\"root\", GROUP=\"root\"\n" + "\n"); + + printf("# Kernel < 2.6.22 jumps here\n" + "LABEL=\"libsane_usb_rules_begin\"\n" + "\n"); + + while (usbid) + { + manufacturer_model_type * name = usbid->name; + + i = 0; + printf ("# "); + while (name) + { + if ((name != usbid->name) && (i > 0)) + printf (" | "); + printf ("%s", name->name); + name = name->next; + + i++; + + /* + * Limit the number of model names on the same line to 3, + * as udev cannot handle very long lines and prints a warning + * message while loading the rules files. + */ + if ((i == 3) && (name != NULL)) + { + printf("\n# "); + i = 0; + } + } + printf ("\n"); + + if (mode == output_mode_udevacl) + printf ("ATTRS{idVendor}==\"%s\", ATTRS{idProduct}==\"%s\", ENV{libsane_matched}=\"yes\"\n", + usbid->usb_vendor_id + 2, usbid->usb_product_id + 2); + else + printf ("ATTRS{idVendor}==\"%s\", ATTRS{idProduct}==\"%s\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", + usbid->usb_vendor_id + 2, usbid->usb_product_id + 2, DEVMODE, DEVGROUP); + + usbid = usbid->next; + } + + printf("\n# The following rule will disable USB autosuspend for the device\n"); + printf("ENV{libsane_matched}==\"yes\", RUN+=\"/bin/sh -c 'if test -e /sys/$env{DEVPATH}/power/control; then echo on > /sys/$env{DEVPATH}/power/control; elif test -e /sys/$env{DEVPATH}/power/level; then echo on > /sys/$env{DEVPATH}/power/level; fi'\"\n"); + + printf ("\nLABEL=\"libsane_usb_rules_end\"\n\n"); + + printf ("SUBSYSTEMS!=\"scsi\", GOTO=\"libsane_scsi_rules_end\"\n\n"); + printf ("LABEL=\"libsane_scsi_rules_begin\"\n"); + printf ("# Generic: SCSI device type 6 indicates a scanner\n"); + + if (mode == output_mode_udevacl) + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"6\", ENV{libsane_matched}=\"yes\"\n"); + else + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"6\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", DEVMODE, DEVGROUP); + + + printf ("# Some scanners advertise themselves as SCSI device type 3\n"); + + printf ("# Wildcard: for some Epson SCSI scanners\n"); + if (mode == output_mode_udevacl) + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", ENV{libsane_matched}=\"yes\"\n"); + else + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", + DEVMODE, DEVGROUP); + + while (scsiid) + { + manufacturer_model_type * name = scsiid->name; + + if (!scsiid->is_processor) + { + scsiid = scsiid->next; + continue; + } + + /* Wildcard for Epson scanners: vendor = EPSON, product = SCANNER* */ + if ((strcmp(scsiid->scsi_vendor_id, "EPSON") == 0) + && (strncmp(scsiid->scsi_product_id, "SCANNER", 7) == 0)) + { + scsiid = scsiid->next; + continue; + } + + i = 0; + printf ("# "); + while (name) + { + if ((name != scsiid->name) && (i > 0)) + printf (" | "); + printf ("%s", name->name); + name = name->next; + + i++; + + /* + * Limit the number of model names on the same line to 3, + * as udev cannot handle very long lines and prints a warning + * message while loading the rules files. + */ + if ((i == 3) && (name != NULL)) + { + printf("\n# "); + i = 0; + } + } + printf ("\n"); + + if (mode == output_mode_udevacl) + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", ENV{libsane_matched}=\"yes\"\n", + scsiid->scsi_vendor_id, scsiid->scsi_product_id); + else + printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", + scsiid->scsi_vendor_id, scsiid->scsi_product_id, DEVMODE, DEVGROUP); + + scsiid = scsiid->next; + } + printf ("LABEL=\"libsane_scsi_rules_end\"\n"); + + if (mode == output_mode_udevacl) + printf("\nENV{libsane_matched}==\"yes\", RUN+=\"/bin/setfacl -m g:%s:rw $env{DEVNAME}\"\n", DEVGROUP); + else + printf ("\nENV{libsane_matched}==\"yes\", MODE=\"664\", GROUP=\"scanner\"\n"); + + printf ("\nLABEL=\"libsane_rules_end\"\n"); +} + + +/* print libsane.rules for Linux udev */ +static void +print_udevhwdb_header (void) +{ + time_t current_time = time (0); + printf ("# This file was automatically created based on description files (*.desc)\n" + "# by sane-desc %s from %s on %s", + SANE_DESC_VERSION, PACKAGE_STRING, asctime (localtime (¤t_time))); + + printf + ("#\n" + "# udev rules file for supported USB and SCSI devices\n" + "#\n" + "# For the list of supported USB devices see /usr/lib/udev/hwdb.d/20-sane.hwdb\n" + "#\n" + "# The SCSI device support is very basic and includes only\n" + "# scanners that mark themselves as type \"scanner\" or\n" + "# SCSI-scanners from HP and other vendors that are entitled \"processor\"\n" + "# but are treated accordingly.\n" + "#\n"); + printf + ("# If your SCSI scanner isn't listed below, you can add it to a new rules\n" + "# file under /etc/udev/rules.d/.\n" + "#\n" + "# If your scanner is supported by some external backend (brother, epkowa,\n" + "# hpaio, etc) please ask the author of the backend to provide proper\n" + "# device detection support for your OS\n" + "#\n" + "# If the scanner is supported by sane-backends, please mail the entry to\n" + "# the sane-devel mailing list (sane-devel@lists.alioth.debian.org).\n" + "#\n" + ); +} + +static void +print_udevhwdb (void) +{ + scsiid_type *scsiid = create_scsiids_table (); + int i; + + print_udevhwdb_header (); + printf("ACTION!=\"add\", GOTO=\"libsane_rules_end\"\n\n"); + + printf("# The following rule will disable USB autosuspend for the device\n"); + printf("ENV{DEVTYPE}==\"usb_device\", ENV{libsane_matched}==\"yes\", TEST==\"power/control\", ATTR{power/control}=\"on\"\n\n"); + + printf ("SUBSYSTEMS!=\"scsi\", GOTO=\"libsane_rules_end\"\n"); + printf ("KERNEL!=\"sg[0-9]*\", GOTO=\"libsane_rules_end\"\n\n"); + + printf ("# Generic: SCSI device type 6 indicates a scanner\n"); + printf ("ATTRS{type}==\"6\", ENV{libsane_matched}=\"yes\"\n\n"); + + printf ("# Some scanners advertise themselves as SCSI device type 3\n\n"); + + printf ("# Wildcard: for some Epson SCSI scanners\n"); + printf ("ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", ENV{libsane_matched}=\"yes\"\n\n"); + + while (scsiid) + { + manufacturer_model_type * name = scsiid->name; + + if (!scsiid->is_processor) + { + scsiid = scsiid->next; + continue; + } + + /* Wildcard for Epson scanners: vendor = EPSON, product = SCANNER* */ + if ((strcmp(scsiid->scsi_vendor_id, "EPSON") == 0) + && (strncmp(scsiid->scsi_product_id, "SCANNER", 7) == 0)) + { + scsiid = scsiid->next; + continue; + } + + i = 0; + printf ("# "); + while (name) + { + if ((name != scsiid->name) && (i > 0)) + printf (" | "); + printf ("%s", name->name); + name = name->next; + + i++; + + /* + * Limit the number of model names on the same line to 3, + * as udev cannot handle very long lines and prints a warning + * message while loading the rules files. + */ + if ((i == 3) && (name != NULL)) + { + printf("\n# "); + i = 0; + } + } + printf ("\n"); + + printf ("ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", ENV{libsane_matched}=\"yes\"\n\n", + scsiid->scsi_vendor_id, scsiid->scsi_product_id); + + scsiid = scsiid->next; + } + + printf ("\nLABEL=\"libsane_rules_end\"\n"); +} + +/* print /usr/lib/udev/hwdb.d/20-sane.conf for Linux hwdb */ +static void +print_hwdb_header (void) +{ + time_t current_time = time (0); + printf ("# This file was automatically created based on description files (*.desc)\n" + "# by sane-desc %s from %s on %s", + SANE_DESC_VERSION, PACKAGE_STRING, asctime (localtime (¤t_time))); + + printf + ("#\n" + "# hwdb file for supported USB devices\n" + "#\n"); + printf + ("# If your scanner isn't listed below, you can add it to a new hwdb file\n" + "# under /etc/udev/hwdb.d/.\n" + "#\n" + "# If your scanner is supported by some external backend (brother, epkowa,\n" + "# hpaio, etc) please ask the author of the backend to provide proper\n" + "# device detection support for your OS\n" + "#\n" + "# If the scanner is supported by sane-backends, please mail the entry to\n" + "# the sane-devel mailing list (sane-devel@lists.alioth.debian.org).\n" + "#\n" + ); +} + +static void +print_hwdb (void) +{ + usbid_type *usbid = create_usbids_table (); + char *vendor_id; + char *product_id; + int i,j; + + print_hwdb_header (); + + while (usbid) + { + manufacturer_model_type * name = usbid->name; + + i = 0; + printf ("# "); + while (name) + { + if ((name != usbid->name) && (i > 0)) + printf (" | "); + printf ("%s", name->name); + name = name->next; + + i++; + + /* + * Limit the number of model names on the same line to 3, + * as udev cannot handle very long lines and prints a warning + * message while loading the rules files. + */ + if ((i == 3) && (name != NULL)) + { + printf("\n# "); + i = 0; + } + } + printf ("\n"); + + vendor_id = strdup(usbid->usb_vendor_id + 2); + product_id = strdup(usbid->usb_product_id + 2); + + for(j = 0; j < 4; j++) { + vendor_id[j] = toupper(vendor_id[j]); + product_id[j] = toupper(vendor_id[j]); + } + + printf ("usb:v%sp%s*\n libsane_matched=yes\n\n", + vendor_id, product_id); + + free(vendor_id); + free(product_id); + + usbid = usbid->next; + } +} + +static void +print_plist (void) +{ + usbid_type *usbid = create_usbids_table (); + + printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + printf ("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"); + printf ("<plist version=\"1.0\">\n"); + printf ("<dict>\n"); + printf ("\t<key>device info version</key>\n"); + printf ("\t<string>2.0</string>\n"); + printf ("\t<key>usb</key>\n"); + printf ("\t<dict>\n"); + printf ("\t\t<key>IOUSBDevice</key>\n"); + printf ("\t\t<array>\n"); + while (usbid) + { + printf ("\t\t\t<dict>\n"); + printf ("\t\t\t\t<key>device type</key>\n"); + printf ("\t\t\t\t<string>scanner</string>\n"); + printf ("\t\t\t\t<key>product</key>\n"); + printf ("\t\t\t\t<string>%s</string>\n", usbid->usb_product_id); + printf ("\t\t\t\t<key>vendor</key>\n"); + printf ("\t\t\t\t<string>%s</string>\n", usbid->usb_vendor_id); + printf ("\t\t\t</dict>\n"); + usbid = usbid->next; + } + printf ("\t\t</array>\n"); + printf ("\t</dict>\n"); + printf ("</dict>\n"); + printf ("</plist>\n"); +} + + +static void +print_hal (int new) +{ + int i; + SANE_Bool in_match; + char *last_vendor; + scsiid_type *scsiid = create_scsiids_table (); + usbid_type *usbid = create_usbids_table (); + + printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + printf ("<deviceinfo version=\"0.2\">\n"); + printf (" <device>\n"); + printf (" <!-- SCSI-SUBSYSTEM -->\n"); + printf (" <match key=\"info.category\" string=\"scsi_generic\">\n"); + printf (" <!-- Some SCSI Scanners announce themselves \"processor\" -->\n"); + printf (" <match key=\"@info.parent:scsi.type\" string=\"processor\">\n"); + + last_vendor = ""; + in_match = SANE_FALSE; + while (scsiid) + { + manufacturer_model_type * name = scsiid->name; + + if (!scsiid->is_processor) + { + scsiid = scsiid->next; + continue; + } + + if (strcmp(last_vendor, scsiid->scsi_vendor_id) != 0) + { + if (in_match) + printf (" </match>\n"); + + printf (" <match key=\"@info.parent:scsi.vendor\" string=\"%s\">\n", scsiid->scsi_vendor_id); + last_vendor = scsiid->scsi_vendor_id; + in_match = SANE_TRUE; + } + + printf (" <!-- SCSI Scanner "); + while (name) + { + if (name != scsiid->name) + printf (" | "); + printf ("\"%s\"", name->name); + name = name->next; + } + printf (" -->\n"); + printf (" <match key=\"@info.parent:scsi.model\" string=\"%s\">\n", scsiid->scsi_product_id); + printf (" <append key=\"info.capabilities\" type=\"strlist\">scanner</append>\n"); + printf (" </match>\n"); + + scsiid = scsiid->next; + } + + if (in_match) + printf (" </match>\n"); + + printf (" </match>\n"); + printf (" </match>\n"); + printf (" <!-- USB-SUBSYSTEM -->\n"); + + if (new) + printf (" <match key=\"info.subsystem\" string=\"usb\">\n"); + else + printf (" <match key=\"info.bus\" string=\"usb\">\n"); + + last_vendor = ""; + in_match = SANE_FALSE; + while (usbid) + { + manufacturer_model_type * name = usbid->name; + + if (strcmp(last_vendor, usbid->usb_vendor_id) != 0) + { + if (in_match) + printf (" </match>\n"); + + printf (" <match key=\"usb.vendor_id\" int=\"%s\">\n", usbid->usb_vendor_id); + last_vendor = usbid->usb_vendor_id; + in_match = SANE_TRUE; + } + + i = 0; + printf (" <!-- "); + while (name) + { + if ((name != usbid->name) && (i > 0)) + printf (" | "); + + printf ("%s", name->name); + name = name->next; + i++; + + if ((i == 3) && (name != NULL)) + { + printf("\n "); + i = 0; + } + } + printf (" -->\n"); + printf (" <match key=\"usb.product_id\" int=\"%s\">\n", usbid->usb_product_id); + printf (" <append key=\"info.capabilities\" type=\"strlist\">scanner</append>\n"); + printf (" <merge key=\"scanner.access_method\" type=\"string\">proprietary</merge>\n"); + printf (" </match>\n"); + + usbid = usbid->next; + } + + if (in_match) + printf (" </match>\n"); + + printf (" </match>\n"); + + printf (" </device>\n"); + printf ("</deviceinfo>\n"); +} + +int +main (int argc, char **argv) +{ + program_name = strrchr (argv[0], '/'); + if (program_name) + ++program_name; + else + program_name = argv[0]; + + if (!get_options (argc, argv)) + return 1; + if (!read_files ()) + return 1; + switch (mode) + { + case output_mode_ascii: + ascii_print_backends (); + break; + case output_mode_xml: + xml_print_backends (); + break; + case output_mode_html_backends_split: + html_print_backends_split (); + break; + case output_mode_html_mfgs: + html_print_mfgs (); + break; + case output_mode_statistics: + print_statistics (); + break; + case output_mode_usermap: + print_usermap (); + break; + case output_mode_db: + print_db (); + break; + case output_mode_udev: + case output_mode_udevacl: + print_udev (); + break; + case output_mode_udevhwdb: + print_udevhwdb (); + break; + case output_mode_hwdb: + print_hwdb (); + break; + case output_mode_plist: + print_plist (); + break; + case output_mode_hal: + print_hal (0); + break; + case output_mode_halnew: + print_hal (1); + break; + default: + DBG_ERR ("Unknown output mode\n"); + return 1; + } + + return 0; +} |