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
+ */

Reply via email to