sane.ds: Implement support for ICAP_XRESOLUTION and ICAP_YRESOLUTION.
diff --git a/dlls/sane.ds/capability.c b/dlls/sane.ds/capability.c
index ad2d7ba..1d574e3 100644
--- a/dlls/sane.ds/capability.c
+++ b/dlls/sane.ds/capability.c
@@ -122,11 +122,40 @@
return TWCC_SUCCESS;
}
+#ifdef SONAME_LIBSANE
+static TW_UINT16 msg_get_range(pTW_CAPABILITY pCapability, TW_UINT16 type,
+ TW_UINT32 minval, TW_UINT32 maxval, TW_UINT32 step, TW_UINT32 def, TW_UINT32 current)
+{
+ TW_RANGE *range = NULL;
+
+ pCapability->ConType = TWON_RANGE;
+ pCapability->hContainer = 0;
+
+ pCapability->hContainer = GlobalAlloc (0, sizeof(*range));
+ if (pCapability->hContainer)
+ range = GlobalLock(pCapability->hContainer);
+
+ if (! range)
+ return TWCC_LOWMEMORY;
+
+ range->ItemType = type;
+ range->MinValue = minval;
+ range->MaxValue = maxval;
+ range->StepSize = step;
+ range->DefaultValue = def;
+ range->CurrentValue = current;
+
+ GlobalUnlock(pCapability->hContainer);
+ return TWCC_SUCCESS;
+}
+#endif
+
static TW_UINT16 TWAIN_GetSupportedCaps(pTW_CAPABILITY pCapability)
{
TW_ARRAY *a;
static const UINT16 supported_caps[] = { CAP_SUPPORTEDCAPS, CAP_XFERCOUNT, CAP_UICONTROLLABLE,
- ICAP_XFERMECH, ICAP_PIXELTYPE, ICAP_COMPRESSION, ICAP_PIXELFLAVOR };
+ ICAP_XFERMECH, ICAP_PIXELTYPE, ICAP_COMPRESSION, ICAP_PIXELFLAVOR,
+ ICAP_XRESOLUTION, ICAP_YRESOLUTION };
pCapability->hContainer = GlobalAlloc (0, FIELD_OFFSET( TW_ARRAY, ItemList[sizeof(supported_caps)] ));
pCapability->ConType = TWON_ARRAY;
@@ -345,6 +374,108 @@
return twCC;
}
+/* ICAP_XRESOLUTION, ICAP_YRESOLUTION */
+static TW_UINT16 SANE_ICAPResolution (pTW_CAPABILITY pCapability, TW_UINT16 action, TW_UINT16 cap)
+{
+ TW_UINT16 twCC = TWCC_BADCAP;
+#ifdef SONAME_LIBSANE
+ TW_UINT32 val;
+ SANE_Int current_resolution;
+ TW_FIX32 *default_res;
+ const char *best_option_name;
+ SANE_Int minval, maxval, quantval;
+ SANE_Status sane_rc;
+ SANE_Int set_status;
+
+ TRACE("ICAP_%cRESOLUTION\n", cap == ICAP_XRESOLUTION ? 'X' : 'Y');
+
+ /* Some scanners support 'x-resolution', most seem to just support 'resolution' */
+ if (cap == ICAP_XRESOLUTION)
+ {
+ best_option_name = "x-resolution";
+ default_res = &activeDS.defaultXResolution;
+ }
+ else
+ {
+ best_option_name = "y-resolution";
+ default_res = &activeDS.defaultYResolution;
+ }
+ if (sane_option_get_int(activeDS.deviceHandle, best_option_name, ¤t_resolution) != SANE_STATUS_GOOD)
+ {
+ best_option_name = "resolution";
+ if (sane_option_get_int(activeDS.deviceHandle, best_option_name, ¤t_resolution) != SANE_STATUS_GOOD)
+ return TWCC_BADCAP;
+ }
+
+ /* Sane does not support a concept of 'default' resolution, so we have to
+ * cache the resolution the very first time we load the scanner, and use that
+ * as the default */
+ if (cap == ICAP_XRESOLUTION && ! activeDS.XResolutionSet)
+ {
+ default_res->Whole = current_resolution;
+ default_res->Frac = 0;
+ activeDS.XResolutionSet = TRUE;
+ }
+
+ if (cap == ICAP_YRESOLUTION && ! activeDS.YResolutionSet)
+ {
+ default_res->Whole = current_resolution;
+ default_res->Frac = 0;
+ activeDS.YResolutionSet = TRUE;
+ }
+
+ switch (action)
+ {
+ case MSG_QUERYSUPPORT:
+ twCC = set_onevalue(pCapability, TWTY_INT32,
+ TWQC_GET | TWQC_SET | TWQC_GETDEFAULT | TWQC_GETCURRENT | TWQC_RESET );
+ break;
+
+ case MSG_GET:
+ sane_rc = sane_option_probe_resolution(activeDS.deviceHandle, best_option_name, &minval, &maxval, &quantval);
+ if (sane_rc != SANE_STATUS_GOOD)
+ twCC = TWCC_BADCAP;
+ else
+ twCC = msg_get_range(pCapability, TWTY_FIX32,
+ minval, maxval, quantval == 0 ? 1 : quantval, default_res->Whole, current_resolution);
+ break;
+
+ case MSG_SET:
+ twCC = msg_set(pCapability, &val);
+ if (twCC == TWCC_SUCCESS)
+ {
+ TW_FIX32 f32;
+ memcpy(&f32, &val, sizeof(f32));
+ sane_rc = sane_option_set_int(activeDS.deviceHandle, best_option_name, f32.Whole, &set_status);
+ if (sane_rc != SANE_STATUS_GOOD)
+ {
+ FIXME("Status of %d not expected or handled\n", sane_rc);
+ twCC = TWCC_BADCAP;
+ }
+ else if (set_status == SANE_INFO_INEXACT)
+ twCC = TWCC_CHECKSTATUS;
+ }
+ break;
+
+ case MSG_GETDEFAULT:
+ twCC = set_onevalue(pCapability, TWTY_FIX32, default_res->Whole);
+ break;
+
+ case MSG_RESET:
+ sane_rc = sane_option_set_int(activeDS.deviceHandle, best_option_name, default_res->Whole, NULL);
+ if (sane_rc != SANE_STATUS_GOOD)
+ return TWCC_BADCAP;
+
+ /* .. fall through intentional .. */
+
+ case MSG_GETCURRENT:
+ twCC = set_onevalue(pCapability, TWTY_FIX32, current_resolution);
+ break;
+ }
+#endif
+ return twCC;
+}
+
/* ICAP_PIXELFLAVOR */
static TW_UINT16 SANE_ICAPPixelFlavor (pTW_CAPABILITY pCapability, TW_UINT16 action)
{
@@ -427,6 +558,14 @@
case ICAP_COMPRESSION:
twCC = SANE_ICAPCompression(pCapability, action);
break;
+
+ case ICAP_XRESOLUTION:
+ twCC = SANE_ICAPResolution(pCapability, action, pCapability->Cap);
+ break;
+
+ case ICAP_YRESOLUTION:
+ twCC = SANE_ICAPResolution(pCapability, action, pCapability->Cap);
+ break;
}
/* Twain specifies that you should return a 0 in response to QUERYSUPPORT,
diff --git a/dlls/sane.ds/ds_ctrl.c b/dlls/sane.ds/ds_ctrl.c
index 69ec633..f204a71 100644
--- a/dlls/sane.ds/ds_ctrl.c
+++ b/dlls/sane.ds/ds_ctrl.c
@@ -162,7 +162,13 @@
else
{
twCC = SANE_SaneCapability (pCapability, MSG_SET);
- twRC = (twCC == TWCC_SUCCESS)?TWRC_SUCCESS:TWRC_FAILURE;
+ if (twCC == TWCC_CHECKSTATUS)
+ {
+ twCC = TWCC_SUCCESS;
+ twRC = TWRC_CHECKSTATUS;
+ }
+ else
+ twRC = (twCC == TWCC_SUCCESS)?TWRC_SUCCESS:TWRC_FAILURE;
activeDS.twCC = twCC;
}
return twRC;
diff --git a/dlls/sane.ds/options.c b/dlls/sane.ds/options.c
index 0ac5c45..9924746 100644
--- a/dlls/sane.ds/options.c
+++ b/dlls/sane.ds/options.c
@@ -26,7 +26,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(twain);
#ifdef SONAME_LIBSANE
-SANE_Status sane_option_get_int(SANE_Handle h, const char *option_name, SANE_Int *val)
+static SANE_Status sane_find_option(SANE_Handle h, const char *option_name,
+ const SANE_Option_Descriptor **opt_p, int *optno, SANE_Value_Type type)
{
SANE_Status rc;
SANE_Int optcount;
@@ -41,9 +42,59 @@
{
opt = psane_get_option_descriptor(h, i);
if (opt && (opt->name && strcmp(opt->name, option_name) == 0) &&
- opt->type == SANE_TYPE_INT )
- return psane_control_option(h, i, SANE_ACTION_GET_VALUE, val, NULL);
+ opt->type == type)
+ {
+ *opt_p = opt;
+ *optno = i;
+ return SANE_STATUS_GOOD;
+ }
}
return SANE_STATUS_EOF;
}
+
+SANE_Status sane_option_get_int(SANE_Handle h, const char *option_name, SANE_Int *val)
+{
+ SANE_Status rc;
+ int optno;
+ const SANE_Option_Descriptor *opt;
+
+ rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
+ if (rc != SANE_STATUS_GOOD)
+ return rc;
+
+ return psane_control_option(h, optno, SANE_ACTION_GET_VALUE, val, NULL);
+}
+
+SANE_Status sane_option_set_int(SANE_Handle h, const char *option_name, SANE_Int val, SANE_Int *status)
+{
+ SANE_Status rc;
+ int optno;
+ const SANE_Option_Descriptor *opt;
+
+ rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
+ if (rc != SANE_STATUS_GOOD)
+ return rc;
+
+ return psane_control_option(h, optno, SANE_ACTION_SET_VALUE, (void *) &val, status);
+}
+
+SANE_Status sane_option_probe_resolution(SANE_Handle h, const char *option_name, SANE_Int *minval, SANE_Int *maxval, SANE_Int *quant)
+{
+ SANE_Status rc;
+ int optno;
+ const SANE_Option_Descriptor *opt;
+
+ rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
+ if (rc != SANE_STATUS_GOOD)
+ return rc;
+
+ if (opt->constraint_type != SANE_CONSTRAINT_RANGE)
+ return SANE_STATUS_UNSUPPORTED;
+
+ *minval = opt->constraint.range->min;
+ *maxval = opt->constraint.range->max;
+ *quant = opt->constraint.range->quant;
+
+ return rc;
+}
#endif
diff --git a/dlls/sane.ds/sane_i.h b/dlls/sane.ds/sane_i.h
index 7e2f117..79e2cce 100644
--- a/dlls/sane.ds/sane_i.h
+++ b/dlls/sane.ds/sane_i.h
@@ -53,6 +53,8 @@
extern HINSTANCE SANE_instance;
+#define TWCC_CHECKSTATUS (TWCC_CUSTOMBASE + 1)
+
/* internal information about an active data source */
struct tagActiveDS
{
@@ -73,6 +75,10 @@
/* Capabilities */
TW_UINT16 capXferMech; /* ICAP_XFERMECH */
TW_UINT16 capPixelType; /* ICAP_PIXELTYPE */
+ BOOL XResolutionSet;
+ TW_FIX32 defaultXResolution;
+ BOOL YResolutionSet;
+ TW_FIX32 defaultYResolution;
} activeDS;
/* Helper functions */
@@ -214,6 +220,8 @@
/* Option functions */
#ifdef SONAME_LIBSANE
SANE_Status sane_option_get_int(SANE_Handle h, const char *option_name, SANE_Int *val);
+SANE_Status sane_option_set_int(SANE_Handle h, const char *option_name, SANE_Int val, SANE_Int *status);
+SANE_Status sane_option_probe_resolution(SANE_Handle h, const char *option_name, SANE_Int *minval, SANE_Int *maxval, SANE_Int *quant);
#endif
diff --git a/dlls/twain_32/tests/dsm.c b/dlls/twain_32/tests/dsm.c
index 1c38824..54641c1 100644
--- a/dlls/twain_32/tests/dsm.c
+++ b/dlls/twain_32/tests/dsm.c
@@ -267,6 +267,127 @@
}
}
+static void test_resolution(TW_IDENTITY *appid, TW_IDENTITY *source, TW_UINT16 captype, TW_INT32 minimum_support)
+{
+ TW_UINT16 rc;
+ TW_STATUS status;
+ TW_CAPABILITY cap;
+ TW_UINT32 val;
+ TW_UINT16 type;
+ TW_INT32 actual_support;
+ TW_FIX32 orig_value = { 0, 0 };
+ TW_UINT32 new_value = 0;
+ TW_FIX32 default_value = { 0, 0 };
+
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_DONTCARE16;
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_QUERYSUPPORT, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_QUERYSUPPORT for type 0x%x\n", rc, status.ConditionCode, captype);
+ if (rc != TWRC_SUCCESS)
+ return;
+ ok(get_onevalue(cap.hContainer, (TW_UINT32 *) &actual_support, NULL), "Returned cap.hContainer invalid for QuerySupport on type 0x%x\n", captype);
+ ok((actual_support & minimum_support) == minimum_support,
+ "Error: minimum support 0x%x for type 0x%x, got 0x%x\n", minimum_support,
+ captype, actual_support);
+
+
+ if (actual_support & TWQC_GETCURRENT)
+ {
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_DONTCARE16;
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_GETCURRENT, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_GETCURRENT for type 0x%x\n", rc, status.ConditionCode, captype);
+ if (rc == TWRC_SUCCESS)
+ {
+ get_onevalue(cap.hContainer, &val, &type);
+ ok(type == TWTY_FIX32, "GETCURRENT for RESOLUTION is not type FIX32, is type %d\n", type);
+ memcpy(&orig_value, &val, sizeof(orig_value));
+ GlobalFree(cap.hContainer);
+ }
+ }
+
+ if (actual_support & TWQC_GETDEFAULT)
+ {
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_DONTCARE16;
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_GETDEFAULT, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_GETDEFAULT for type 0x%x\n", rc, status.ConditionCode, captype);
+ if (rc == TWRC_SUCCESS)
+ {
+ ok(type == TWTY_FIX32, "GETDEFAULT for RESOLUTION is not type FIX32, is type %d\n", type);
+ memcpy(&default_value, &val, sizeof(default_value));
+ GlobalFree(cap.hContainer);
+ }
+ }
+
+ if (actual_support & TWQC_GET)
+ {
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_DONTCARE16;
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_GET, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_GET for type 0x%x\n", rc, status.ConditionCode, captype);
+ if (rc == TWRC_SUCCESS)
+ {
+ TW_RANGE *range;
+ ok(cap.ConType == TWON_RANGE, "MSG_GET for ICAP_[XY]RESOLUTION did not return TWON_RANGE, but %d\n", cap.ConType);
+ range = GlobalLock(cap.hContainer);
+ trace("MSG_GET of 0x%x returned [ItemType %d|MinValue %d|MaxValue %d|StepSize %d|DefaultValue %d|CurrentValue %d]:\n",
+ cap.Cap, range->ItemType, range->MinValue, range->MaxValue, range->StepSize,
+ range->DefaultValue, range->CurrentValue);
+ for (new_value = range->MinValue; new_value < range->MaxValue; new_value += range->StepSize)
+ if (new_value != range->CurrentValue)
+ break;
+ GlobalUnlock(cap.hContainer);
+ GlobalFree(cap.hContainer);
+ }
+ }
+
+ if (actual_support & TWQC_SET)
+ {
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_ONEVALUE;
+ cap.hContainer = alloc_and_set_onevalue(new_value, TWTY_FIX32);
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_SET for type 0x%x\n", rc, status.ConditionCode, captype);
+ GlobalFree(cap.hContainer);
+
+ }
+
+ if (actual_support & TWQC_RESET)
+ {
+ memset(&cap, 0, sizeof(cap));
+ cap.Cap = captype;
+ cap.ConType = TWON_DONTCARE16;
+
+ rc = pDSM_Entry(appid, source, DG_CONTROL, DAT_CAPABILITY, MSG_RESET, &cap);
+ get_condition_code(appid, source, &status);
+ ok(rc == TWRC_SUCCESS && status.ConditionCode == TWCC_SUCCESS,
+ "Error [rc %d|cc %d] doing MSG_RESET for type 0x%x\n", rc, status.ConditionCode, captype);
+ if (rc == TWRC_SUCCESS)
+ GlobalFree(cap.hContainer);
+ }
+}
+
static void test_single_source(TW_IDENTITY *appid, TW_IDENTITY *source)
{
@@ -356,10 +477,14 @@
if (capabilities[ICAP_XFERMECH])
test_onevalue_cap(appid, source, ICAP_XFERMECH, TWTY_UINT16,
TWQC_GET | TWQC_SET | TWQC_GETDEFAULT | TWQC_GETCURRENT | TWQC_RESET);
- todo_wine
ok(capabilities[ICAP_XRESOLUTION], "ICAP_XRESOLUTION not supported\n");
- todo_wine
+ if (capabilities[ICAP_XRESOLUTION])
+ test_resolution(appid, source, ICAP_XRESOLUTION,
+ TWQC_GET | TWQC_SET | TWQC_GETDEFAULT | TWQC_GETCURRENT | TWQC_RESET);
ok(capabilities[ICAP_YRESOLUTION], "ICAP_YRESOLUTION not supported\n");
+ if (capabilities[ICAP_YRESOLUTION])
+ test_resolution(appid, source, ICAP_YRESOLUTION,
+ TWQC_GET | TWQC_SET | TWQC_GETDEFAULT | TWQC_GETCURRENT | TWQC_RESET);
}
}