Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package sane-airscan for openSUSE:Factory checked in at 2024-11-19 22:24:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/sane-airscan (Old) and /work/SRC/openSUSE:Factory/.sane-airscan.new.28523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "sane-airscan" Tue Nov 19 22:24:31 2024 rev:3 rq:1225159 version:0.99.30 Changes: -------- --- /work/SRC/openSUSE:Factory/sane-airscan/sane-airscan.changes 2024-06-04 12:53:01.523950106 +0200 +++ /work/SRC/openSUSE:Factory/.sane-airscan.new.28523/sane-airscan.changes 2024-11-19 22:24:59.488638921 +0100 @@ -1,0 +2,37 @@ +Tue Nov 19 19:30:44 UTC 2024 - Richard Rahl <rra...@opensuse.org> + +- update to 0.99.30: + * WSD: sca:ScannerDescription requested in sca:GetScannerElementsRequest + * WSD: Ricoh Aficio MP 201: fixed detection of "ADF empty" state + * HTTP: logged "end of input" event + * test-decode: more informative usage when invoked without args + * test-devcaps: stub implementation + * test-devcaps: works for WSD + * WSD: fixed ADF duplex on Epson Workforce WF-3520 + * test-devcaps: works for eSCL too + * Device model name propagated from zeroconf to proto handlers, for quirks + * WSD: fix for ADF scan on RICOH Aficio MP 201 + * WSD: more information requested in sca:GetScannerElementsRequest + * Some devices don't behave if sca:ImagesToTransfer isn't set as expected. + * README: fixed OKI supported table entries + * OKI-MB471/OKI-MC332dn/OKI-MC362dn marked as not supporting eSCL + * Dell E514dw added to the list + * Kyocera TASKalfa 3051ci added to the list + * doc: add Epson ET-2650 series + * Add EPSON WF-2760 Series + * WSD: Add content type selection + * eSCL: Add scan intent selection + * Fixed logging of supported/chosen scan intent + * eSCL: fixed parsing of the supported scan intents in the device capabilities + * ID_SCANINTENT better documented + * Tweaked a textual description of the scan-intent option + * SANE name for ID_SCANINTENT_DOCUMENT now "Document" (was "Text") + * Added ID_SCANINTENT_UNSET value for the 'sane-intent' + * Setting "scan-intent" now requires a precise match. + * eSCL: delay between subsequent loads made Brother-specific + * WSD: cosmetic + * WSD: workaround for ADF Duplex on Brother MFC-9370CDW + * The "sane-intent" option cannot be SANE_CAP_INACTIVE +- fix obsoleting itself + +------------------------------------------------------------------- Old: ---- sane-airscan-0.99.29.tar.zst New: ---- sane-airscan-0.99.30.tar.zst ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ sane-airscan.spec ++++++ --- /var/tmp/diff_new_pack.WUxjxZ/_old 2024-11-19 22:25:01.052704047 +0100 +++ /var/tmp/diff_new_pack.WUxjxZ/_new 2024-11-19 22:25:01.064704546 +0100 @@ -17,7 +17,7 @@ Name: sane-airscan -Version: 0.99.29 +Version: 0.99.30 Release: 0 Summary: Universal driver for Apple AirScan (eSCL) and WSD License: SUSE-GPL-2.0+-with-sane-exception @@ -35,9 +35,9 @@ BuildRequires: pkgconfig(libxml-2.0) BuildRequires: pkgconfig(sane-backends) Provides: lib%{name}1 = %{version} -Obsoletes: lib%{name}1 <= %{version} +Obsoletes: lib%{name}1 < %{version} Provides: %{name}-devel = %{version} -Obsoletes: %{name}-devel <= %{version} +Obsoletes: %{name}-devel < %{version} %description This package contains a SANE backend for MFP and document scanners that @@ -62,7 +62,7 @@ %config %{_sysconfdir}/sane.d/airscan.conf %config %{_sysconfdir}/sane.d/dll.d/airscan %{_libdir}/sane/libsane-airscan.so.1 -%{_mandir}/man?/{sane-airscan,airscan-discover}.?.gz +%{_mandir}/man?/{sane-airscan,airscan-discover}.?%{?ext_man} %if 0%{?suse_version} == 1500 %dir %{_sysconfdir}/sane.d/dll.d %endif ++++++ _service ++++++ --- /var/tmp/diff_new_pack.WUxjxZ/_old 2024-11-19 22:25:01.324715373 +0100 +++ /var/tmp/diff_new_pack.WUxjxZ/_new 2024-11-19 22:25:01.364717038 +0100 @@ -2,7 +2,7 @@ <service name="tar_scm" mode="manual"> <param name="url">https://github.com/alexpevzner/sane-airscan.git</param> <param name="scm">git</param> - <param name="revision">0.99.29</param> + <param name="revision">0.99.30</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">disable</param> </service> ++++++ sane-airscan-0.99.29.tar.zst -> sane-airscan-0.99.30.tar.zst ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/Makefile new/sane-airscan-0.99.30/Makefile --- old/sane-airscan-0.99.29/Makefile 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/Makefile 2024-11-19 09:47:27.000000000 +0100 @@ -101,9 +101,9 @@ .PHONY: all clean install man -all: tags $(BACKEND) $(DISCOVER) test test-decode test-multipart test-zeroconf test-uri +all: tags $(BACKEND) $(DISCOVER) test test-decode test-devcaps test-multipart test-zeroconf test-uri -tags: $(SRC) airscan.h test.c test-decode.c test-multipart.c test-zeroconf.c test-uri.c +tags: $(SRC) airscan.h test.c test-decode.c test-devcaps.c test-multipart.c test-zeroconf.c test-uri.c -ctags -R . $(BACKEND): $(OBJDIR)airscan.o $(LIBAIRSCAN) airscan.sym @@ -132,7 +132,7 @@ [ "$(COMPRESS)" = "" ] || $(COMPRESS) -f $(DESTDIR)/$(mandir)/man5/$(MAN_BACKEND) clean: - rm -f test test-decode test-multipart test-zeroconf test-uri $(BACKEND) tags + rm -f test test-decode test-devcaps test-multipart test-zeroconf test-uri $(BACKEND) tags rm -rf $(OBJDIR) uninstall: @@ -160,6 +160,9 @@ test-decode: test-decode.c $(LIBAIRSCAN) $(CC) -o test-decode test-decode.c $(CPPFLAGS) $(common_CFLAGS) $(LIBAIRSCAN) $(tests_LDFLAGS) +test-devcaps: test-devcaps.c $(LIBAIRSCAN) + $(CC) -o test-devcaps test-devcaps.c $(CPPFLAGS) $(common_CFLAGS) $(LIBAIRSCAN) $(tests_LDFLAGS) + test-multipart: test-multipart.c $(LIBAIRSCAN) $(CC) -o test-multipart test-multipart.c $(CPPFLAGS) $(common_CFLAGS) $(LIBAIRSCAN) $(tests_LDFLAGS) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/README.md new/sane-airscan-0.99.30/README.md --- old/sane-airscan-0.99.29/README.md 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/README.md 2024-11-19 09:47:27.000000000 +0100 @@ -129,6 +129,8 @@ | Dell C1765nfw Color MFP | No | Yes | | Dell C2665dnf Color Laser Printer | No | Yes | | Dell C3765dnf Color MFP | No | Yes | +| Dell E514dw | No | Yes | +| EPSON ET-2650 Series | Yes | Yes | EPSON ET-2710 Series | No | Yes | | EPSON ET-2750 Series | Yes | | | EPSON ET-2760 Series | Yes | | @@ -140,6 +142,7 @@ | EPSON ET-M2170 Series | Yes | | | EPSON L6570 Series | Yes | Yes | | EPSON Stylus SX535WD | No | Yes | +| EPSON WF-2760 Series | | Yes | | EPSON WF-3620 Series | No | Yes | | EPSON WF-7710 Series | No | Yes | | EPSON XP-2100 Series | No | Yes | @@ -215,11 +218,15 @@ | Kyocera ECOSYS M5521cdw | Yes | Yes<sup>[5](#note5)</sup> | | Kyocera ECOSYS M5526cdw | Yes | | | Kyocera FS-1028MFP | No | Yes<sup>[5](#note5)</sup> | +| Kyocera TASKalfa 3051ci | | Yes<sup>[5](#note5)</sup> | | Lexmark CX317dn | Yes<sup>[6](#note6)</sup> | Yes<sup>[6](#note6)</sup> | | Lexmark MB2236adw | Yes | | | Lexmark MC2535adwe | Yes | | | Lexmark MC3224adwe | Yes | | | Lexmark MC3326adwe | Yes | | +| OKI-MB471 | No | Yes | +| OKI-MC332dn | No | Yes | +| OKI-MC362dn | No | Yes | | OKI-MC853 | Yes | | | Panasonic KV-S1058Y | No | Yes | | Pantum BM5100ADW series | Yes | Yes | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-devcaps.c new/sane-airscan-0.99.30/airscan-devcaps.c --- old/sane-airscan-0.99.29/airscan-devcaps.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-devcaps.c 2024-11-19 09:47:27.000000000 +0100 @@ -156,22 +156,28 @@ } /* Dump device capabilities, for debugging + * + * The 3rd parameter, 'trace' configures the debug level + * (log_debug vs log_trace) of the generated output */ void -devcaps_dump (log_ctx *log, devcaps *caps) +devcaps_dump (log_ctx *log, devcaps *caps, bool trace) { int i; char *buf = str_new(); + void (*log_func) (log_ctx *log, const char *fmt, ...); - log_trace(log, "===== device capabilities ====="); - log_trace(log, " Size units: %d DPI", caps->units); - log_trace(log, " Protocol: %s", caps->protocol); + log_func = trace ? log_trace : log_debug; + + log_func(log, "===== device capabilities ====="); + log_func(log, " Size units: %d DPI", caps->units); + log_func(log, " Protocol: %s", caps->protocol); if (caps->compression_ok) { - log_trace(log, " Compression min: %d", caps->compression_range.min); - log_trace(log, " Compression max: %d", caps->compression_range.max); - log_trace(log, " Compression step: %d", caps->compression_range.quant); - log_trace(log, " Compression norm: %d", caps->compression_norm); + log_func(log, " Compression min: %d", caps->compression_range.min); + log_func(log, " Compression max: %d", caps->compression_range.max); + log_func(log, " Compression step: %d", caps->compression_range.quant); + log_func(log, " Compression norm: %d", caps->compression_norm); } str_trunc(buf); @@ -184,7 +190,7 @@ } } - log_trace(log, " Sources: %s", buf); + log_func(log, " Sources: %s", buf); ID_SOURCE id_src; for (id_src = (ID_SOURCE) 0; id_src < NUM_ID_SOURCE; id_src ++) { @@ -195,19 +201,19 @@ continue; } - log_trace(log, ""); - log_trace(log, " %s:", id_source_sane_name(id_src)); + log_func(log, ""); + log_func(log, " %s:", id_source_sane_name(id_src)); math_fmt_mm(math_px2mm_res(src->min_wid_px, caps->units), xbuf); math_fmt_mm(math_px2mm_res(src->min_hei_px, caps->units), ybuf); - log_trace(log, " Min window: %dx%d px, %sx%s mm", + log_func(log, " Min window: %dx%d px, %sx%s mm", src->min_wid_px, src->min_hei_px, xbuf, ybuf); math_fmt_mm(math_px2mm_res(src->max_wid_px, caps->units), xbuf); math_fmt_mm(math_px2mm_res(src->max_hei_px, caps->units), ybuf); - log_trace(log, " Max window: %dx%d px, %sx%s mm", + log_func(log, " Max window: %dx%d px, %sx%s mm", src->max_wid_px, src->max_hei_px, xbuf, ybuf); if (src->flags & DEVCAPS_SOURCE_RES_DISCRETE) { @@ -219,7 +225,7 @@ buf = str_append_printf(buf, "%d", src->resolutions[i+1]); } - log_trace(log, " Resolutions: %s", buf); + log_func(log, " Resolutions: %s", buf); } str_trunc(buf); @@ -233,7 +239,7 @@ } } - log_trace(log, " Color modes: %s", buf); + log_func(log, " Color modes: %s", buf); str_trunc(buf); @@ -246,11 +252,24 @@ } } - log_trace(log, " Formats: %s", buf); + log_func(log, " Formats: %s", buf); + + str_trunc(buf); + + for (i = 0; i < NUM_ID_SCANINTENT; i ++) { + if ((src->scanintents & (1 << i)) != 0) { + if (buf[0] != '\0') { + buf = str_append(buf, ", "); + } + buf = str_append(buf, id_scanintent_sane_name(i)); + } + } + + log_func(log, " Intents: %s", buf); } mem_free(buf); - log_trace(log, ""); + log_func(log, ""); } /* vim:ts=8:sw=4:et diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-device.c new/sane-airscan-0.99.30/airscan-device.c --- old/sane-airscan-0.99.29/airscan-device.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-device.c 2024-11-19 09:47:27.000000000 +0100 @@ -215,6 +215,7 @@ log_debug(dev->log, "device created"); dev->proto_ctx.log = dev->log; + dev->proto_ctx.devinfo = dev->devinfo; dev->proto_ctx.devcaps = &dev->opt.caps; devopt_init(&dev->opt); @@ -609,7 +610,7 @@ goto DONE; } - devcaps_dump(dev->log, &dev->opt.caps); + devcaps_dump(dev->log, &dev->opt.caps, true); devopt_set_defaults(&dev->opt); /* Update endpoint address in case of HTTP redirection */ @@ -1039,6 +1040,7 @@ params->y_res = y_resolution; params->src = dev->opt.src; params->colormode = dev->opt.colormode_real; + params->scanintent = dev->opt.scanintent; params->format = device_choose_format(dev, src); /* Dump parameters */ @@ -1050,6 +1052,8 @@ id_colormode_sane_name(dev->opt.colormode_emul)); log_trace(dev->log, " colormode_real: %s", id_colormode_sane_name(params->colormode)); + log_trace(dev->log, " scanintent: %s", + id_scanintent_sane_name(params->scanintent)); log_trace(dev->log, " tl_x: %s mm", math_fmt_mm(dev->opt.tl_x, buf)); log_trace(dev->log, " tl_y: %s mm", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-devops.c new/sane-airscan-0.99.30/airscan-devops.c --- old/sane-airscan-0.99.29/airscan-devops.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-devops.c 2024-11-19 09:47:27.000000000 +0100 @@ -41,6 +41,7 @@ opt->resolution = CONFIG_DEFAULT_RESOLUTION; opt->sane_sources = sane_string_array_new(); opt->sane_colormodes = sane_string_array_new(); + opt->sane_scanintents = sane_string_array_new(); } /* Cleanup device options @@ -50,6 +51,7 @@ { sane_string_array_free(opt->sane_sources); sane_string_array_free(opt->sane_colormodes); + sane_string_array_free(opt->sane_scanintents); devcaps_cleanup(&opt->caps); } @@ -168,6 +170,7 @@ SANE_Option_Descriptor *desc; devcaps_source *src = opt->caps.src[opt->src]; unsigned int colormodes = devopt_available_colormodes(src); + unsigned int scanintents = src->scanintents; int i; const char *s; @@ -175,6 +178,7 @@ sane_string_array_reset(opt->sane_sources); sane_string_array_reset(opt->sane_colormodes); + sane_string_array_reset(opt->sane_scanintents); for (i = 0; i < NUM_ID_SOURCE; i ++) { if (opt->caps.src[i] != NULL) { @@ -191,6 +195,15 @@ } } + scanintents |= 1 << ID_SCANINTENT_UNSET; /* Always implicitly supported */ + for (i = 0; i < NUM_ID_SCANINTENT; i ++) { + if ((scanintents & (1 << i)) != 0) { + opt->sane_scanintents = + sane_string_array_append( + opt->sane_scanintents, (SANE_String) id_scanintent_sane_name(i)); + } + } + /* OPT_NUM_OPTIONS */ desc = &opt->desc[OPT_NUM_OPTIONS]; desc->name = SANE_NAME_NUM_OPTIONS; @@ -236,6 +249,17 @@ desc->constraint_type = SANE_CONSTRAINT_STRING_LIST; desc->constraint.string_list = (SANE_String_Const*) opt->sane_colormodes; + /* OPT_SCAN_INTENT */ + desc = &opt->desc[OPT_SCAN_INTENT]; + desc->name = "scan-intent"; + desc->title = "Scan intent"; + desc->desc = "Optimize scan for Text/Photo/etc."; + desc->type = SANE_TYPE_STRING; + desc->size = sane_string_array_max_strlen(opt->sane_scanintents) + 1; + desc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + desc->constraint_type = SANE_CONSTRAINT_STRING_LIST; + desc->constraint.string_list = (SANE_String_Const*) opt->sane_scanintents; + /* OPT_SCAN_SOURCE */ desc = &opt->desc[OPT_SCAN_SOURCE]; desc->name = SANE_NAME_SCAN_SOURCE; @@ -525,6 +549,24 @@ return SANE_STATUS_GOOD; } +/* Set scan intent. + */ +static SANE_Status +devopt_set_scanintent (devopt *opt, ID_SCANINTENT intent) +{ + devcaps_source *src = opt->caps.src[opt->src]; + unsigned int scanintents = src->scanintents; + + scanintents |= 1 << ID_SCANINTENT_UNSET; /* Always implicitly supported */ + + if ((scanintents & (1 << intent)) == 0) { + return SANE_STATUS_INVAL; + } + + opt->scanintent = intent; + return SANE_STATUS_GOOD; +} + /* Set geometry option */ static SANE_Status @@ -632,6 +674,7 @@ opt->colormode_emul = devopt_choose_colormode(opt, ID_COLORMODE_UNKNOWN); opt->colormode_real = devopt_real_colormode(opt->colormode_emul, src); + opt->scanintent = ID_SCANINTENT_UNSET; opt->resolution = devopt_choose_resolution(opt, CONFIG_DEFAULT_RESOLUTION); opt->tl_x = 0; @@ -657,6 +700,7 @@ SANE_Status status = SANE_STATUS_GOOD; ID_SOURCE id_src; ID_COLORMODE id_colormode; + ID_SCANINTENT id_scanintent; /* Simplify life of options handlers by ensuring info != NULL */ if (info == NULL) { @@ -681,6 +725,15 @@ } break; + case OPT_SCAN_INTENT: + id_scanintent = id_scanintent_by_sane_name(value); + if (id_scanintent == ID_SCANINTENT_UNKNOWN) { + status = SANE_STATUS_INVAL; + } else { + status = devopt_set_scanintent(opt, id_scanintent); + } + break; + case OPT_SCAN_SOURCE: id_src = id_source_by_sane_name(value); if (id_src == ID_SOURCE_UNKNOWN) { @@ -746,6 +799,10 @@ strcpy(value, id_colormode_sane_name(opt->colormode_emul)); break; + case OPT_SCAN_INTENT: + strcpy(value, id_scanintent_sane_name(opt->scanintent)); + break; + case OPT_SCAN_SOURCE: strcpy(value, id_source_sane_name(opt->src)); break; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-escl.c new/sane-airscan-0.99.30/airscan-escl.c --- old/sane-airscan-0.99.29/airscan-escl.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-escl.c 2024-11-19 09:47:27.000000000 +0100 @@ -54,6 +54,7 @@ bool quirk_localhost; /* Set Host: localhost in ScanJobs rq */ bool quirk_canon_mf410_series; /* Canon MF410 Series */ bool quirk_port_in_host; /* Always set port in Host: header */ + bool quirk_next_load_delay; /* Use ESCL_NEXT_LOAD_DELAY */ } proto_handler_escl; /* XML namespace for XML writer @@ -341,6 +342,39 @@ return err; } +/* Parse supported intents (photo/document etc). + */ +static error +escl_devcaps_source_parse_supported_intents (xml_rd *xml, devcaps_source *src) +{ + error err = NULL; + + xml_rd_enter(xml); + for (; !xml_rd_end(xml); xml_rd_next(xml)) { + if(xml_rd_node_name_match(xml, "scan:SupportedIntent")) { + const char *v = xml_rd_node_value(xml); + if (!strcmp(v, "Document")) { + src->scanintents |= 1 << ID_SCANINTENT_DOCUMENT; + } else if (!strcmp(v, "TextAndGraphic")) { + src->scanintents |= 1 << ID_SCANINTENT_TEXTANDGRAPHIC; + } else if (!strcmp(v, "Photo")) { + src->scanintents |= 1 << ID_SCANINTENT_PHOTO; + } else if (!strcmp(v, "Preview")) { + src->scanintents |= 1 << ID_SCANINTENT_PREVIEW; + } else if (!strcmp(v, "Object")) { + src->scanintents |= 1 << ID_SCANINTENT_OBJECT; + } else if (!strcmp(v, "BusinessCard")) { + src->scanintents |= 1 << ID_SCANINTENT_BUSINESSCARD; + } else { + log_debug(NULL, "unknown intent: %s", v); + } + } + } + xml_rd_leave(xml); + + return err; +} + /* Parse ADF justification */ @@ -396,6 +430,8 @@ err = xml_rd_node_value_uint(xml, &src->max_hei_px); } else if (xml_rd_node_name_match(xml, "scan:SettingProfiles")) { err = escl_devcaps_source_parse_setting_profiles(xml, src); + } else if (xml_rd_node_name_match(xml, "scan:SupportedIntents")) { + err = escl_devcaps_source_parse_supported_intents(xml, src); } } xml_rd_leave(xml); @@ -496,6 +532,11 @@ ID_SOURCE id_src; bool src_ok = false; + /* Fill "constant" part of device capabilities */ + caps->units = 300; + caps->protocol = escl->proto.name; + caps->justification_x = caps->justification_y = ID_JUSTIFICATION_UNKNOWN; + /* Parse capabilities XML */ err = xml_rd_begin(&xml, xml_text, xml_len, NULL); if (err != NULL) { @@ -522,6 +563,8 @@ escl->quirk_canon_mf410_series = true; } else if (!strncasecmp(m, "EPSON ", 6)) { escl->quirk_port_in_host = true; + } else if (!strncasecmp(m, "Brother ", 8)) { + escl->quirk_next_load_delay = true; } } else if (xml_rd_node_name_match(xml, "scan:Manufacturer")) { const char *m = xml_rd_node_value(xml); @@ -624,10 +667,6 @@ http_data *data = http_query_get_response_data(ctx->query); const char *s; - caps->units = 300; - caps->protocol = ctx->proto->name; - caps->justification_x = caps->justification_y = ID_JUSTIFICATION_UNKNOWN; - /* Most of devices that have Server: HP_Compact_Server * in their HTTP response header, require this quirk * (see #116) @@ -731,6 +770,7 @@ const proto_scan_params *params = &ctx->params; const char *source = NULL; const char *colormode = NULL; + const char *scanintent = NULL; const char *mime = id_format_mime_name(ctx->params.format); const devcaps_source *src = ctx->devcaps->src[params->src]; bool duplex = false; @@ -755,11 +795,28 @@ log_internal_error(ctx->log); } + switch (params->scanintent) { + case ID_SCANINTENT_UNSET: break; + case ID_SCANINTENT_DOCUMENT: scanintent = "Document"; break; + case ID_SCANINTENT_TEXTANDGRAPHIC: scanintent = "TextAndGraphic"; break; + case ID_SCANINTENT_PHOTO: scanintent = "Photo"; break; + case ID_SCANINTENT_PREVIEW: scanintent = "Preview"; break; + case ID_SCANINTENT_OBJECT: scanintent = "Object"; break; + case ID_SCANINTENT_BUSINESSCARD: scanintent = "BusinessCard"; break; + + default: + log_internal_error(ctx->log); + } + /* Build scan request */ xml_wr *xml = xml_wr_begin("scan:ScanSettings", escl_xml_wr_ns); xml_wr_add_text(xml, "pwg:Version", "2.0"); + if (scanintent) { + xml_wr_add_text(xml, "scan:Intent", scanintent); + } + xml_wr_enter(xml, "pwg:ScanRegions"); xml_wr_enter(xml, "pwg:ScanRegion"); xml_wr_add_text(xml, "pwg:ContentRegionUnits", @@ -905,9 +962,10 @@ static proto_result escl_load_decode (const proto_ctx *ctx) { - proto_result result = {0}; - error err = NULL; - timestamp t = 0; + proto_handler_escl *escl = (proto_handler_escl*) ctx->proto; + proto_result result = {0}; + error err = NULL; + timestamp t = 0; /* Check HTTP status */ err = http_query_error(ctx->query); @@ -923,7 +981,7 @@ } /* Compute delay until next load */ - if (ctx->params.src != ID_SOURCE_PLATEN) { + if (escl->quirk_next_load_delay && ctx->params.src != ID_SOURCE_PLATEN) { t = timestamp_now() - http_query_timestamp(ctx->query); t *= ESCL_NEXT_LOAD_DELAY_MAX; @@ -1158,6 +1216,19 @@ return escl_http_query(ctx, ctx->location, "DELETE", NULL); } +/******************** Test interfaces ********************/ +/* Test interface: decode device capabilities + */ +static error +escl_test_decode_devcaps (proto_handler *proto, + const void *xml_text, size_t xms_size, + devcaps *caps) +{ + proto_handler_escl *escl = (proto_handler_escl*) proto; + + return escl_devcaps_parse(escl, caps, xml_text, xms_size); +} + /******************** Constructor/destructor ********************/ /* Free ESCL protocol handler */ @@ -1195,6 +1266,8 @@ escl->proto.cleanup_query = escl_cancel_query; escl->proto.cancel_query = escl_cancel_query; + escl->proto.test_decode_devcaps = escl_test_decode_devcaps; + return &escl->proto; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-http.c new/sane-airscan-0.99.30/airscan-http.c --- old/sane-airscan-0.99.29/airscan-http.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-http.c 2024-11-19 09:47:27.000000000 +0100 @@ -2755,6 +2755,10 @@ return; } + if (rc == 0) { + log_debug(q->client->log, "HTTP end of input"); + } + http_parser_execute(&q->http_parser, &http_query_callbacks, io_buf, rc); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-id.c new/sane-airscan-0.99.30/airscan-id.c --- old/sane-airscan-0.99.29/airscan-id.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-id.c 2024-11-19 09:47:27.000000000 +0100 @@ -177,6 +177,41 @@ return name ? name : mime; } +/******************** ID_SCANINTENT ********************/ +/* id_scanintent_sane_name_table represents ID_SCANINTENT to + * SANE name mapping + */ +static id_name_table id_scanintent_sane_name_table[] = { + {ID_SCANINTENT_UNSET, "*unset*"}, + {ID_SCANINTENT_AUTO, "Auto"}, + {ID_SCANINTENT_DOCUMENT, "Document"}, + {ID_SCANINTENT_TEXTANDGRAPHIC, "Text and Graphic"}, + {ID_SCANINTENT_PHOTO, "Photo"}, + {ID_SCANINTENT_PREVIEW, "Preview"}, + {ID_SCANINTENT_OBJECT, "3D Object"}, + {ID_SCANINTENT_BUSINESSCARD, "Business Card"}, + {ID_SCANINTENT_HALFTONE, "Halftone"}, + {-1, NULL} +}; + +/* id_scanintent_sane_name returns SANE name for the scan intent + * For unknown ID returns NULL + */ +const char* +id_scanintent_sane_name (ID_SCANINTENT id) +{ + return id_name(id, id_scanintent_sane_name_table); +} + +/* id_scanintent_by_sane_name returns ID_SCANINTENT by its SANE name + * For unknown name returns ID_SCANINTENT_UNKNOWN + */ +ID_SCANINTENT +id_scanintent_by_sane_name (const char *name) +{ + return id_by_name(name, strcasecmp, id_scanintent_sane_name_table); +} + /******************** ID_JUSTIFICATION ********************/ /* id_justification_sane_name_table represents ID_JUSTIFICATION to diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-wsd.c new/sane-airscan-0.99.30/airscan-wsd.c --- old/sane-airscan-0.99.29/airscan-wsd.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-wsd.c 2024-11-19 09:47:27.000000000 +0100 @@ -86,6 +86,18 @@ bool pdf_a; bool png; bool dib; + + /* Quirks */ + + /* Scanner doesn't handle sca:ImagesToTransfer if set to "0" + * (which means "scan until ADF is empty"). + * + * This is the Ricoh Aficio MP 201 case. + * + * The workaround is to set sca:ImagesToTransfer to some big + * arbitrary number. + */ + bool quirk_broken_ImagesToTransfer; } proto_handler_wsd; /* Forward declarations */ @@ -147,10 +159,20 @@ xml_wr_enter(xml, "soap:Body"); xml_wr_enter(xml, "sca:GetScannerElementsRequest"); xml_wr_enter(xml, "sca:RequestedElements"); - //xml_wr_add_text(xml, "sca:Name", "sca:ScannerDescription"); + + /* sca:ScannerConfiguration response defines scanner capabilities, + * such as document formats support, available sources, avaliable + * resolutions, color modes etc. + */ xml_wr_add_text(xml, "sca:Name", "sca:ScannerConfiguration"); - //xml_wr_add_text(xml, "sca:Name", "sca:DefaultScanTicket"); - //xml_wr_add_text(xml, "sca:Name", "sca:ScannerStatus"); + + /* These elements are only requested for logging, to provide some + * device information for troubleshooting purposes + */ + xml_wr_add_text(xml, "sca:Name", "sca:ScannerDescription"); + xml_wr_add_text(xml, "sca:Name", "sca:DefaultScanTicket"); + xml_wr_add_text(xml, "sca:Name", "sca:ScannerStatus"); + xml_wr_leave(xml); xml_wr_leave(xml); xml_wr_leave(xml); @@ -239,6 +261,49 @@ return err; } +/* Parse supported content types and map them to scan intents + */ +static error +wsd_devcaps_parse_content_types (devcaps *caps, xml_rd *xml, + unsigned int *scanintents_out) +{ + error err = NULL; + unsigned int level = xml_rd_depth(xml); + size_t prefixlen = strlen(xml_rd_node_path(xml)); + unsigned int scanintents = 0; + + (void) caps; + + /* Decode supported content types */ + while (!xml_rd_end(xml)) { + const char *path = xml_rd_node_path(xml) + prefixlen; + + if (!strcmp(path, "/scan:ContentTypeValue")) { + const char *v = xml_rd_node_value(xml); + + if (!strcmp(v, "Auto")) { + scanintents |= 1 << ID_SCANINTENT_AUTO; + } else if (!strcmp(v, "Text")) { + scanintents |= 1 << ID_SCANINTENT_DOCUMENT; + } else if (!strcmp(v, "Photo")) { + scanintents |= 1 << ID_SCANINTENT_PHOTO; + } else if (!strcmp(v, "Halftone")) { + scanintents |= 1 << ID_SCANINTENT_HALFTONE; + } else if (!strcmp(v, "Mixed")) { + scanintents |= 1 << ID_SCANINTENT_TEXTANDGRAPHIC; + } else { + log_debug(NULL, "unknown content type: %s", v); + } + } + + xml_rd_deep_next(xml, level); + } + + *scanintents_out = scanintents; + + return err; +} + /* Parse size */ static error @@ -387,18 +452,6 @@ min_hei = tmp; } - /* Workaround for yet another Kyocera bug. This device doesn't - * honor scan region settings. I.e., it understands it, - * properly mirrors in DocumentFinalParameters, but completely - * ignores when generating the image. - * - * So we can't rely on device's ability to clip the image and - * must implement clipping in software. It can be enforced - * in our backend by the following two lines: - */ - min_wid = max_wid; - min_hei = max_hei; - /* Save min/max width and height */ src->min_wid_px = min_wid; src->max_wid_px = max_wid; @@ -432,6 +485,7 @@ size_t prefixlen = strlen(xml_rd_node_path(xml)); bool adf = false, duplex = false; unsigned int formats = 0; + unsigned int scanintents = 0; int i; /* Parse configuration */ @@ -440,6 +494,8 @@ if (!strcmp(path, "/scan:DeviceSettings/scan:FormatsSupported")) { err = wsd_devcaps_parse_formats(wsd, caps, xml, &formats); + } else if (!strcmp(path, "/scan:DeviceSettings/scan:ContentTypesSupported")) { + err = wsd_devcaps_parse_content_types(caps, xml, &scanintents); } else if (!strcmp(path, "/scan:Platen")) { err = wsd_devcaps_parse_source(caps, xml, ID_SOURCE_PLATEN); } else if (!strcmp(path, "/scan:ADF/scan:ADFFront")) { @@ -467,23 +523,59 @@ if (src != NULL) { src->formats = formats; + src->scanintents = scanintents; + + /* Note, as we can clip in software, we indicate + * minimal scan region size for SANE as 0x0. But + * maximal size is defined by hardware + */ src->win_x_range_mm.min = src->win_y_range_mm.min = 0; src->win_x_range_mm.max = math_px2mm_res(src->max_wid_px, 1000); src->win_y_range_mm.max = math_px2mm_res(src->max_hei_px, 1000); } } - /* Note, WSD uses slightly unusual model: instead of providing - * source configurations for simplex and duplex modes, it provides - * source configuration for ADF front, which is required (when ADF - * is supported by device) and for ADF back, which is optional + /* Workaround for Brother MFC-9340CDW + * + * This device reports ADFSupportsDuplex as 0, but returns both + * ADFFront and ADFBack elements. + * + * As a workaround, we assume that if both ADFFront and ADFBack are + * present (temporary saved under the ID_SOURCE_ADF_SIMPLEX and + * ID_SOURCE_ADF_DUPLEX slots), scanner supports duplex mode, + * regardless of the ADFSupportsDuplex value it returns + */ + if (adf && caps->src[ID_SOURCE_ADF_DUPLEX] != NULL) { + duplex = true; + } + + /* Please note that the standard model for SANE and for our implementation + * involves having two separate configurations for the duplex ADF: one for + * simplex mode and another for duplex mode. In duplex mode, it is assumed + * that the front and back page scanning will have the same + * characteristics. + * + * However, WSD employs a slightly different model. Instead of providing + * separate source configurations for simplex and duplex modes, it offers a + * source configuration for the ADF front, which is required when the ADF + * is supported by the device, and an optional configuration for the ADF + * back. + * + * According to the specification, the ADF back configuration is optional. + * If the scanner indicates duplex support (via the ADFSupportsDuplex) but + * does not provide a separate ADFBack element, the ADFBack should be + * assumed to be the same as ADFFront. * - * So we assume, that ADF front applies to both simplex and duplex - * modes, while ADF back applies only to duplex mode + * During the decoding process, we temporarily store the ADF front + * information under the ID_SOURCE_ADF_SIMPLEX and the ADF back information + * under the ID_SOURCE_ADF_DUPLEX slots, and then make adjustments. * - * So if duplex is supported, we either merge front and back - * configurations, if both are present, or simply copy front - * to back, if back is missed + * When adjusting, we assume that the ADF front applies to both simplex and + * duplex modes, while the ADF back applies only to duplex mode. + * + * Therefore, if duplex is supported, we either merge the front and back + * configurations if both are present or simply copy the front + * configuration to the back if the back configuration is missing. */ if (adf && duplex) { log_assert(NULL, caps->src[ID_SOURCE_ADF_SIMPLEX] != NULL); @@ -502,6 +594,25 @@ caps->src[ID_SOURCE_ADF_DUPLEX] = NULL; } + /* Workaround for yet another Kyocera bug. This device doesn't + * honor scan region settings. I.e., it understands it, + * properly mirrors in DocumentFinalParameters, but completely + * ignores when generating the image. + * + * So we can't rely on device's ability to clip the image and + * must implement clipping in software. It can be enforced + * in our backend by setting minimum image size equal to + * max size. + */ + for (i = 0; i < NUM_ID_SOURCE; i ++) { + devcaps_source *src = caps->src[i]; + + if (src != NULL) { + src->min_wid_px = src->max_wid_px; + src->min_hei_px = src->max_hei_px; + } + } + /* Check that we have at least one source */ ID_SOURCE id_src; bool src_ok = false; @@ -529,6 +640,11 @@ xml_rd *xml; bool found_configuration = false; + /* Fill "constant" part of device capabilities */ + caps->units = 1000; + caps->protocol = wsd->proto.name; + caps->justification_x = caps->justification_y = ID_JUSTIFICATION_UNKNOWN; + /* Parse capabilities XML */ err = xml_rd_begin(&xml, xml_text, xml_len, wsd_ns_rd); if (err != NULL) { @@ -576,10 +692,12 @@ http_data *data = http_query_get_response_data(ctx->query); error err; - caps->units = 1000; - caps->protocol = ctx->proto->name; - caps->justification_x = caps->justification_y = ID_JUSTIFICATION_UNKNOWN; + /* Setup quirks */ + if (!strcmp(ctx->devinfo->model, "RICOH Aficio MP 201")) { + wsd->quirk_broken_ImagesToTransfer = true; + } + /* Parse device capabilities response */ err = wsd_devcaps_parse(wsd, caps, data->bytes, data->size); return err; @@ -633,7 +751,7 @@ /* Decode fault response */ static proto_result -wsd_fault_decode (const proto_ctx *ctx) +wsd_fault_decode (const proto_ctx *ctx, bool cleanup) { proto_handler_wsd *wsd = (proto_handler_wsd*) ctx->proto; proto_result result = {0}; @@ -643,7 +761,7 @@ /* Parse XML */ result.err = xml_rd_begin(&xml, data->bytes, data->size, wsd_ns_rd); if (result.err != NULL) { - result.next = PROTO_OP_FINISH; + result.next = cleanup ? PROTO_OP_CLEANUP : PROTO_OP_FINISH; result.status = SANE_STATUS_IO_ERROR; return result; } @@ -709,6 +827,7 @@ xml_wr *xml = xml_wr_begin("soap:Envelope", wsd_ns_wr); const char *source = NULL; const char *colormode = NULL; + const char *contenttype = NULL; const char *format = NULL; static const char *sides_simplex[] = {"sca:MediaFront", NULL}; static const char *sides_duplex[] = {"sca:MediaFront", "sca:MediaBack", NULL}; @@ -736,6 +855,18 @@ log_internal_error(ctx->log); } + switch (params->scanintent) { + case ID_SCANINTENT_UNSET: break; + case ID_SCANINTENT_AUTO: contenttype = "Auto"; break; + case ID_SCANINTENT_DOCUMENT: contenttype = "Text"; break; + case ID_SCANINTENT_PHOTO: contenttype = "Photo"; break; + case ID_SCANINTENT_HALFTONE: contenttype = "Halftone"; break; + case ID_SCANINTENT_TEXTANDGRAPHIC: contenttype = "Mixed"; break; + + default: + log_internal_error(ctx->log); + } + /* Create scan request */ wsd_make_request_header(ctx, xml, WSD_ACTION_CREATE_SCAN_JOB); @@ -804,7 +935,28 @@ log_assert(ctx->log, format != NULL); xml_wr_add_text(xml, "sca:Format", format); - xml_wr_add_text(xml, "sca:ImagesToTransfer", "0"); + /* WS-Scan specification says unspecified scan amount should be 0 + * ( unknown amount, check for more ) and for Flatbed that is 1. + */ + switch (params->src) { + case ID_SOURCE_PLATEN: + xml_wr_add_text(xml, "sca:ImagesToTransfer", "1"); + break; + case ID_SOURCE_ADF_SIMPLEX: + case ID_SOURCE_ADF_DUPLEX: + if (wsd->quirk_broken_ImagesToTransfer) { + xml_wr_add_text(xml, "sca:ImagesToTransfer", "100"); + } else { + xml_wr_add_text(xml, "sca:ImagesToTransfer", "0"); + } + break; + default: + log_internal_error(ctx->log); + } + + if (contenttype) { + xml_wr_add_text(xml, "sca:ContentType", contenttype); + } xml_wr_enter(xml, "sca:InputSize"); xml_wr_enter(xml, "sca:InputMediaSize"); @@ -863,7 +1015,7 @@ /* Decode error, if any */ if (wsd_fault_check(ctx)) { - return wsd_fault_decode(ctx); + return wsd_fault_decode(ctx, false); } /* Decode CreateScanJobResponse */ @@ -961,13 +1113,13 @@ /* Check HTTP status */ if (wsd_fault_check(ctx)) { - return wsd_fault_decode(ctx); + return wsd_fault_decode(ctx, true); } /* We expect multipart message with attached image */ data = http_query_get_mp_response_data(ctx->query, 1); if (data == NULL) { - result.next = PROTO_OP_FINISH; + result.next = PROTO_OP_CLEANUP; result.err = ERROR("RetrieveImageRequest: invalid response"); return result; } @@ -1031,6 +1183,13 @@ result.status = SANE_STATUS_NO_DOCS; return result; } + + /* Ricoh Aficio MP 201 reports "ADF empty" status this strange way + */ + if (!strcmp(wsd->fault_code, "ClientErrorJobIdNotFound")) { + result.status = SANE_STATUS_NO_DOCS; + return result; + } } /* Parse XML */ @@ -1150,6 +1309,18 @@ return wsd_http_post(ctx, xml_wr_finish_compact(xml)); } +/* Test interface: decode device capabilities + */ +static error +wsd_test_decode_devcaps (proto_handler *proto, + const void *xml_text, size_t xms_size, + devcaps *caps) +{ + proto_handler_wsd *wsd = (proto_handler_wsd*) proto; + + return wsd_devcaps_parse(wsd, caps, xml_text, xms_size); +} + /* proto_handler_wsd_new creates new WSD protocol handler */ proto_handler* @@ -1175,8 +1346,11 @@ wsd->proto.status_query = wsd_status_query; wsd->proto.status_decode = wsd_status_decode; + wsd->proto.cleanup_query = wsd_cancel_query; wsd->proto.cancel_query = wsd_cancel_query; + wsd->proto.test_decode_devcaps = wsd_test_decode_devcaps; + return &wsd->proto; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan-zeroconf.c new/sane-airscan-0.99.30/airscan-zeroconf.c --- old/sane-airscan-0.99.29/airscan-zeroconf.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan-zeroconf.c 2024-11-19 09:47:27.000000000 +0100 @@ -1304,7 +1304,9 @@ devinfo = mem_new(zeroconf_devinfo, 1); devinfo->ident = str_dup(ident); devinfo->name = str_dup(name); + devinfo->model = str_dup(""); devinfo->endpoints = zeroconf_endpoint_new(proto, uri); + return devinfo; } @@ -1349,9 +1351,11 @@ if (dev_conf != NULL) { http_uri *uri = http_uri_clone(dev_conf->uri); devinfo->name = str_dup(dev_conf->name); + devinfo->model = str_dup(""); devinfo->endpoints = zeroconf_endpoint_new(dev_conf->proto, uri); } else { devinfo->name = str_dup(zeroconf_device_name(device)); + devinfo->model = str_dup(device->model ? device->model : ""); devinfo->endpoints = zeroconf_device_endpoints(device, proto); } @@ -1365,6 +1369,7 @@ { mem_free((char*) devinfo->ident); mem_free((char*) devinfo->name); + mem_free((char*) devinfo->model); zeroconf_endpoint_list_free(devinfo->endpoints); mem_free(devinfo); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/airscan.h new/sane-airscan-0.99.30/airscan.h --- old/sane-airscan-0.99.29/airscan.h 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/airscan.h 2024-11-19 09:47:27.000000000 +0100 @@ -794,6 +794,48 @@ const char* id_format_short_name (ID_FORMAT id); +/* ID_SCANINTENT represents scan intent + * + * Intent hints scanner on a purpose of requested scan, which may + * imply carious parameters tweaks depending on that purpose. + * + * Intent maps to the eSCL Intent (see Mopria eSCL Technical Specification, 5) + * and WSD ContentType. The semantics of these two parameters looks very + * similar. + * + * Please note, eSCL defines also the ContentType parameter, but after + * some thinking and discussion we came to conclusion that Intent better + * maps our need. + * + * Dee discussion at: https://github.com/alexpevzner/sane-airscan/pull/351 + */ +typedef enum { + ID_SCANINTENT_UNKNOWN = -1, + ID_SCANINTENT_UNSET, /* Intent is not set */ + ID_SCANINTENT_AUTO, /* WSD: Auto */ + ID_SCANINTENT_DOCUMENT, /* eSCL: Docoment, WSD: Text */ + ID_SCANINTENT_TEXTANDGRAPHIC, /* eSCL: TextAndGraphic, WSD: Mixed */ + ID_SCANINTENT_PHOTO, /* eSCL: Photo, WSD: Photo */ + ID_SCANINTENT_PREVIEW, /* eSCL: Preview */ + ID_SCANINTENT_OBJECT, /* eSCL: Objects (3d scan) */ + ID_SCANINTENT_BUSINESSCARD, /* eSCL: BusinessCard */ + ID_SCANINTENT_HALFTONE, /* WSD: Halftone */ + + NUM_ID_SCANINTENT +} ID_SCANINTENT; + +/* id_scanintent_sane_name returns SANE name for the scan intents + * For unknown ID returns NULL + */ +const char* +id_scanintent_sane_name (ID_SCANINTENT id); + +/* id_scanintent_by_sane_name returns ID_SCANINTENT by its SANE name + * For unknown name returns ID_SCANINTENT_UNKNOWN + */ +ID_SCANINTENT +id_scanintent_by_sane_name (const char *name); + /******************** Device ID ********************/ /* Allocate unique device ID */ @@ -2535,6 +2577,7 @@ OPT_GROUP_STANDARD, OPT_SCAN_RESOLUTION, OPT_SCAN_COLORMODE, /* I.e. color/grayscale etc */ + OPT_SCAN_INTENT, /* Document/Photo etc */ OPT_SCAN_SOURCE, /* Platem/ADF/ADF Duplex */ /* Geometry options group */ @@ -2648,6 +2691,7 @@ unsigned int flags; /* Source flags */ unsigned int colormodes; /* Set of 1 << ID_COLORMODE */ unsigned int formats; /* Set of 1 << ID_FORMAT */ + unsigned int scanintents; /* Set of 1 << ID_SCANINTENT */ SANE_Word min_wid_px, max_wid_px; /* Min/max width, in pixels */ SANE_Word min_hei_px, max_hei_px; /* Min/max height, in pixels */ SANE_Word *resolutions; /* Discrete resolutions, in DPI */ @@ -2716,9 +2760,12 @@ devcaps_reset (devcaps *caps); /* Dump device capabilities, for debugging + * + * The 3rd parameter, 'trace' configures the debug level + * (log_debug vs log_trace) of the generated output */ void -devcaps_dump (log_ctx *log, devcaps *caps); +devcaps_dump (log_ctx *log, devcaps *caps, bool trace); /******************** Device options ********************/ /* Scan options @@ -2729,12 +2776,14 @@ ID_SOURCE src; /* Current source */ ID_COLORMODE colormode_emul; /* Current "emulated" color mode*/ ID_COLORMODE colormode_real; /* Current real color mode*/ + ID_SCANINTENT scanintent; /* Current scan intent */ SANE_Word resolution; /* Current resolution */ SANE_Fixed tl_x, tl_y; /* Top-left x/y */ SANE_Fixed br_x, br_y; /* Bottom-right x/y */ SANE_Parameters params; /* Scan parameters */ SANE_String *sane_sources; /* Sources, in SANE format */ SANE_String *sane_colormodes; /* Color modes in SANE format */ + SANE_String *sane_scanintents; /* Scan intents in SANE format */ SANE_Fixed brightness; /* -100.0 ... +100.0 */ SANE_Fixed contrast; /* -100.0 ... +100.0 */ SANE_Fixed shadow; /* 0.0 ... +100.0 */ @@ -2889,6 +2938,7 @@ typedef struct { const char *ident; /* Unique ident */ const char *name; /* Human-friendly name */ + const char *model; /* Model name, for quirks. "" if unknown */ zeroconf_endpoint *endpoints; /* Device endpoints */ } zeroconf_devinfo; @@ -3236,6 +3286,7 @@ int x_res, y_res; /* X/Y resolution */ ID_SOURCE src; /* Desired source */ ID_COLORMODE colormode; /* Desired color mode */ + ID_SCANINTENT scanintent; /* Desired scan intent */ ID_FORMAT format; /* Desired image format */ } proto_scan_params; @@ -3245,6 +3296,7 @@ /* Common context */ log_ctx *log; /* Logging context */ struct proto_handler *proto; /* Link to proto_handler */ + const zeroconf_devinfo *devinfo; /* Device info, from zeroconf */ const devcaps *devcaps; /* Device capabilities */ PROTO_OP op; /* Current operation */ http_client *http; /* HTTP client for sending requests */ @@ -3326,6 +3378,12 @@ /* Cancel scan in progress */ http_query* (*cancel_query) (const proto_ctx *ctx); + + /* Test interfaces. Not for regular use! + */ + error (*test_decode_devcaps) (proto_handler *proto, + const void *xml_text, size_t xms_size, + devcaps *caps); }; /* proto_handler_escl_new creates new eSCL protocol handler @@ -3353,6 +3411,16 @@ } } +/* proto_handler_free destroys protocol handler, previously + * created by proto_handler_new/proto_handler_escl_new/ + * proto_handler_wsd_new functions + */ +static inline void +proto_handler_free (proto_handler *proto) +{ + proto->free(proto); +} + /******************** Image decoding ********************/ /* The window withing the image * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/test-decode.c new/sane-airscan-0.99.30/test-decode.c --- old/sane-airscan-0.99.29/test-decode.c 2024-02-23 12:43:52.000000000 +0100 +++ new/sane-airscan-0.99.30/test-decode.c 2024-11-19 09:47:27.000000000 +0100 @@ -146,7 +146,10 @@ /* Parse command-line arguments */ if (argc != 2) { - die("usage: %s file", argv[0]); + die( + "test-decode - decodes image file and writes result to decoded.png\n" + "usage: %s image.file", argv[0] + ); } file = argv[1]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sane-airscan-0.99.29/test-devcaps.c new/sane-airscan-0.99.30/test-devcaps.c --- old/sane-airscan-0.99.29/test-devcaps.c 1970-01-01 01:00:00.000000000 +0100 +++ new/sane-airscan-0.99.30/test-devcaps.c 2024-11-19 09:47:27.000000000 +0100 @@ -0,0 +1,115 @@ +/* sane-airscan device capabilities parser test + * + * Copyright (C) 2019 and up by Alexander Pevzner (p...@apevzner.com) + * See LICENSE for license terms and conditions + */ + +#include "airscan.h" + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Print error message and exit + */ +void __attribute__((noreturn)) +die (const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vprintf(format, ap); + printf("\n"); + va_end(ap); + + exit(1); +} + +/* The main function + */ +int +main (int argc, char **argv) +{ + char *mode, *file; + FILE *fp; + void *data; + long size; + proto_handler *proto; + int rc; + error err; + devcaps caps; + + /* Parse command-line arguments */ + if (argc != 3) { + die( + "test-devcaps - decode and print device capabilities\n" + "usage: %s [-escl|-wsd] file.xml", argv[0] + ); + } + + mode = argv[1]; + file = argv[2]; + + if (!strcmp(mode, "-escl")) { + proto = proto_handler_escl_new(); + } else if (!strcmp(mode, "-wsd")) { + proto = proto_handler_wsd_new(); + } else { + die("%s: unknown protocol", mode); + } + + /* Load the file */ + fp = fopen(file, "rb"); + if (fp == NULL) { + die("%s: %s", file, strerror(errno)); + } + + rc = fseek(fp, 0, SEEK_END); + if (rc < 0) { + die("%s: %s", file, strerror(errno)); + } + + size = ftell(fp); + if (size < 0) { + die("%s: %s", file, strerror(errno)); + } + + rc = fseek(fp, 0, SEEK_SET); + if (rc < 0) { + die("%s: %s", file, strerror(errno)); + } + + data = mem_new(char, size); + if ((size_t) size != fread(data, 1, size, fp)) { + die("%s: read error", file); + } + + fclose(fp); + + /* Initialize logging */ + conf.dbg_enabled = true; + log_init(); + log_configure(); + + /* Decode device capabilities */ + memset(&caps, 0, sizeof(caps)); + devcaps_init(&caps); + + err = proto->test_decode_devcaps(proto, data, size, &caps); + if (err != NULL) { + die("error: %s", ESTRING(err)); + } + + devcaps_dump(NULL, &caps, false); + + /* Cleanup and exit */ + proto_handler_free(proto); + mem_free(data); + + return 0; +} + +/* vim:ts=8:sw=4:et + */