64-Bit Windows applications typically expect the Twain Data Source manager to be named twaindsm.dll.
Send the DG_CONTROL/DAT_ENTRYPOINT/MSG_SET before DG_CONTROL/DAT_IDENTITY/MSG_OPENDS so sane.ds does not need to know the module name to find the DSM entry point.
An explanation for the request to offer the name twaindsm.dll for 64-Bit windows is offered here by the TWAIN organization:
https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc
They cannot update the twain_32.dll installed and maintained by Microsoft, so they choose the new name twaindsm.dll instead. This name is also used at least by Irfanview and oder versions oder LibreOffice (Newer Libreoffice version start a 32-Bit process for 32-Bit TWAIN usage).
This Merge request also changes the twain_32.dll so that it send it's entry points earlier, before opening the data source. This is the same behaviour as the twaindsm.dll from the TWAIN organization from [https://github.com/twain/twain-dsm/%5D(https://github.com/twain/twain-dsm/bl...)
Otherwise sane.ds would in MSG_OPENDS try to find the DSM entry point based on the module-name "twain_32" in sane_main.c:60:
``` if (SANE_dsmentry == NULL) { HMODULE moddsm = GetModuleHandleW(L"twain_32");
if (moddsm) SANE_dsmentry = (void*)GetProcAddress(moddsm, "DSM_Entry");
if (!SANE_dsmentry) { ERR("can't find DSM entry point\n"); return TWRC_FAILURE; } } ```
-- v8: dlls/twaindsm: Recursively search in C:\Windows\twain_xx for installed data sources
From: Bernd Herd codeberg@herdsoft.com
Renamed dlls/twain_32 to dlls/twaindsm thus creating twaindsm.dll as platform independent name for the Twain Data Source Manager as it is used on 64-Bit windows.
Create new dlls/twain_32 as a wrapper for 32-Bit Windows only that forwards all calls to the main implementation in twaindsm.dll.
Send the DG_CONTROL/DAT_ENTRYPOINT/MSG_SET before DG_CONTROL/DAT_IDENTITY/MSG_OPENDS so Twain 2.x aware data sources like sane.ds don't need to know the filename of the Twain DSM ro find the DSM entry point.
On 32-Bit Systems LoadLibrary for the twain_32.dll wrapper when opening the DS and it is not 2.x compliant. This makes sure it will find the DSM_Entry of twain_32.dll if it uses GetModuleHandle("twain_32.dll") to find the DLL. --- MAINTAINERS | 1 + configure | 6 ++- configure.ac | 5 +- dlls/twain_32/Makefile.in | 6 +-- dlls/twain_32/twain_32.c | 50 +++++++++++++++++++ dlls/twain_32/twain_32.spec | 2 +- dlls/twaindsm/Makefile.in | 8 +++ dlls/{twain_32 => twaindsm}/dsm_ctrl.c | 47 +++++++++++++---- dlls/{twain_32 => twaindsm}/resource.h | 0 dlls/{twain_32 => twaindsm}/tests/Makefile.in | 2 +- dlls/{twain_32 => twaindsm}/tests/dsm.c | 4 +- dlls/{twain_32 => twaindsm}/twain.rc | 0 dlls/{twain_32 => twaindsm}/twain32_main.c | 0 dlls/{twain_32 => twaindsm}/twain_i.h | 0 dlls/twaindsm/twaindsm.spec | 1 + 15 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 dlls/twain_32/twain_32.c create mode 100644 dlls/twaindsm/Makefile.in rename dlls/{twain_32 => twaindsm}/dsm_ctrl.c (94%) rename dlls/{twain_32 => twaindsm}/resource.h (100%) rename dlls/{twain_32 => twaindsm}/tests/Makefile.in (64%) rename dlls/{twain_32 => twaindsm}/tests/dsm.c (99%) rename dlls/{twain_32 => twaindsm}/twain.rc (100%) rename dlls/{twain_32 => twaindsm}/twain32_main.c (100%) rename dlls/{twain_32 => twaindsm}/twain_i.h (100%) create mode 100644 dlls/twaindsm/twaindsm.spec
diff --git a/MAINTAINERS b/MAINTAINERS index c34abcecbc3..6167ff4464e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -325,6 +325,7 @@ F: dlls/uxtheme/ TWAIN P: Esme Povirk esme@codeweavers.com @madewokherd F: dlls/twain_32/ +F: dlls/twaindsm/ F: dlls/sane.ds/ F: dlls/gphoto2.ds/
diff --git a/configure b/configure index 7aebd970364..b9570edfe40 100755 --- a/configure +++ b/configure @@ -1461,6 +1461,7 @@ enable_tdi_sys enable_threadpoolwinrt enable_traffic enable_twain_32 +enable_twaindsm enable_twinapi_appcore enable_tzres enable_ucrtbase @@ -22283,6 +22284,8 @@ enable_msiexec=${enable_msiexec:-yes} enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes} +enable_twaindsm=${enable_twaindsm:-yes} +enable_twain_32=${enable_twain_32:-i386}
enable_win16=${enable_win16:-i386} enable_w32skrnl=${enable_w32skrnl:-$enable_win16} @@ -23071,7 +23074,8 @@ wine_fn_config_makefile dlls/toolhelp.dll16 enable_win16 wine_fn_config_makefile dlls/traffic enable_traffic wine_fn_config_makefile dlls/twain.dll16 enable_win16 wine_fn_config_makefile dlls/twain_32 enable_twain_32 -wine_fn_config_makefile dlls/twain_32/tests enable_tests +wine_fn_config_makefile dlls/twaindsm enable_twaindsm +wine_fn_config_makefile dlls/twaindsm/tests enable_tests wine_fn_config_makefile dlls/twinapi.appcore enable_twinapi_appcore wine_fn_config_makefile dlls/twinapi.appcore/tests enable_tests wine_fn_config_makefile dlls/typelib.dll16 enable_win16 diff --git a/configure.ac b/configure.ac index 06a3a820e4c..05e2ba85b91 100644 --- a/configure.ac +++ b/configure.ac @@ -2420,6 +2420,8 @@ enable_msiexec=${enable_msiexec:-yes} enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes} +enable_twaindsm=${enable_twaindsm:-yes} +enable_twain_32=${enable_twain_32:-i386}
dnl Disable Win16-related modules enable_win16=${enable_win16:-i386} @@ -3211,7 +3213,8 @@ WINE_CONFIG_MAKEFILE(dlls/toolhelp.dll16) WINE_CONFIG_MAKEFILE(dlls/traffic) WINE_CONFIG_MAKEFILE(dlls/twain.dll16) WINE_CONFIG_MAKEFILE(dlls/twain_32) -WINE_CONFIG_MAKEFILE(dlls/twain_32/tests) +WINE_CONFIG_MAKEFILE(dlls/twaindsm) +WINE_CONFIG_MAKEFILE(dlls/twaindsm/tests) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore/tests) WINE_CONFIG_MAKEFILE(dlls/typelib.dll16) diff --git a/dlls/twain_32/Makefile.in b/dlls/twain_32/Makefile.in index b4ac1e27729..94225d4cc64 100644 --- a/dlls/twain_32/Makefile.in +++ b/dlls/twain_32/Makefile.in @@ -1,7 +1,5 @@ MODULE = twain_32.dll -IMPORTS = user32 +IMPORTS = user32 twaindsm
SOURCES = \ - dsm_ctrl.c \ - twain.rc \ - twain32_main.c + twain_32.c diff --git a/dlls/twain_32/twain_32.c b/dlls/twain_32/twain_32.c new file mode 100644 index 00000000000..eba57a4578b --- /dev/null +++ b/dlls/twain_32/twain_32.c @@ -0,0 +1,50 @@ +/* + * TWAIN32 functions + * + * Copyright 2025 Bernd Herd codeberg@herdsoft.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * This implements twain_32.dll as a simple wrapper for 32-Bit Intel Windows + * to forward all requests to the platform independent + * main inplementation in twaindsm.dll + */ + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "twain.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(twain); + +/* Main entry point for the TWAIN_32.dll library */ +TW_UINT16 WINAPI +TWAIN_32_DSM_Entry ( + pTW_IDENTITY pOrigin, + pTW_IDENTITY pDest, + TW_UINT32 DG, + TW_UINT16 DAT, + TW_UINT16 MSG, + TW_MEMREF pData) +{ + TRACE("twain_32 (DG=%ld DAT=%d MSG=%d)\n", DG, DAT, MSG); + + /* Just forward the call to the twaindsm.dll */ + return DSM_Entry(pOrigin, pDest, DG, DAT, MSG, pData); +} diff --git a/dlls/twain_32/twain_32.spec b/dlls/twain_32/twain_32.spec index fe79c9f19dc..cc2fc585157 100644 --- a/dlls/twain_32/twain_32.spec +++ b/dlls/twain_32/twain_32.spec @@ -1 +1 @@ -@ stdcall DSM_Entry(ptr ptr long long long ptr) +@ stdcall DSM_Entry(ptr ptr long long long ptr) TWAIN_32_DSM_Entry diff --git a/dlls/twaindsm/Makefile.in b/dlls/twaindsm/Makefile.in new file mode 100644 index 00000000000..ad8053803da --- /dev/null +++ b/dlls/twaindsm/Makefile.in @@ -0,0 +1,8 @@ +MODULE = twaindsm.dll +IMPORTLIB = twaindsm +IMPORTS = user32 + +SOURCES = \ + dsm_ctrl.c \ + twain.rc \ + twain32_main.c diff --git a/dlls/twain_32/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c similarity index 94% rename from dlls/twain_32/dsm_ctrl.c rename to dlls/twaindsm/dsm_ctrl.c index 89e788f979d..c77f4f32b6b 100644 --- a/dlls/twain_32/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -47,6 +47,11 @@ struct all_devices { static int nrdevices = 0; static struct all_devices *devices = NULL;
+#ifndef WIN64 +/* Instance-Handle of the twain_32.dll */ +HINSTANCE hinstTwain_32 = NULL; +#endif // !WIN64 + static void twain_add_onedriver(const WCHAR *dsname) { HMODULE hmod; @@ -276,6 +281,12 @@ TW_UINT16 TWAIN_CloseDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) /* This causes crashes due to still open Windows, so leave out for now. * FreeLibrary (currentDS->hmod); */ +#ifndef WIN64 + if (!(pIdentity->SupportedGroups & DF_DS2)) { + FreeLibrary(hinstTwain_32); + } +#endif // !WIN64 + if (prevDS) prevDS->next = currentDS->next; else @@ -381,8 +392,34 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) } newSource->hmod = hmod; newSource->dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry"); + if (!newSource->dsEntry) { + ERR("Failed to find DS_Entry() in TWAIN DS %s\n", debugstr_w(devices[i].modname)); + DSM_twCC = TWCC_OPERATIONERROR; + HeapFree(GetProcessHeap(), 0, newSource); + return TWRC_FAILURE; + } /* Assign id for the opened data source */ pIdentity->Id = DSM_sourceId ++; + /* Get the Identity of the new DS, so we know the SupportedGroups */ + if (TWRC_SUCCESS != newSource->dsEntry (NULL, DG_CONTROL, DAT_IDENTITY, MSG_GET, pIdentity)) { + DSM_twCC = TWCC_OPERATIONERROR; + HeapFree(GetProcessHeap(), 0, newSource); + DSM_sourceId--; + return TWRC_FAILURE; + } + /* Tell the source our entry points */ + if (pIdentity->SupportedGroups & DF_DS2) { + /* This makes sure that the DS knows the current address of our DSM_Entry + * function so there is no risk that it is using a stale copy. */ + newSource->dsEntry (pOrigin, DG_CONTROL, DAT_ENTRYPOINT, MSG_SET, (TW_ENTRYPOINT *) &_entrypoints); + } +#ifndef WIN64 + else { + /* DS is Version 1.x. Make sure twain_32.dll is loaded in case DS uses GetModuleHandle("twain_32") */ + hinstTwain_32 = LoadLibraryW(L"twain_32.dll"); + } +#endif // !WIN64 + /* Open the data source */ if (TWRC_SUCCESS != newSource->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, pIdentity)) { DSM_twCC = TWCC_OPERATIONERROR; HeapFree(GetProcessHeap(), 0, newSource); @@ -398,16 +435,6 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) newSource->event_window = NULL; activeSources = newSource; DSM_twCC = TWCC_SUCCESS; - - /* Tell the source our entry points */ - if (pIdentity->SupportedGroups & DF_DS2) { - /* This makes sure that the DS knows the current address of our DSM_Entry - * function so there is no risk that it is using a stale copy. - * The other entry points are also set for formal reasons, - * but are currently not used. - */ - newSource->dsEntry (pOrigin, DG_CONTROL, DAT_ENTRYPOINT, MSG_SET, (TW_ENTRYPOINT *) &_entrypoints); - } return TWRC_SUCCESS; }
diff --git a/dlls/twain_32/resource.h b/dlls/twaindsm/resource.h similarity index 100% rename from dlls/twain_32/resource.h rename to dlls/twaindsm/resource.h diff --git a/dlls/twain_32/tests/Makefile.in b/dlls/twaindsm/tests/Makefile.in similarity index 64% rename from dlls/twain_32/tests/Makefile.in rename to dlls/twaindsm/tests/Makefile.in index 13052327d7d..11cc60fef8b 100644 --- a/dlls/twain_32/tests/Makefile.in +++ b/dlls/twaindsm/tests/Makefile.in @@ -1,4 +1,4 @@ -TESTDLL = twain_32.dll +TESTDLL = twaindsm.dll IMPORTS = user32 gdi32
SOURCES = \ diff --git a/dlls/twain_32/tests/dsm.c b/dlls/twaindsm/tests/dsm.c similarity index 99% rename from dlls/twain_32/tests/dsm.c rename to dlls/twaindsm/tests/dsm.c index f12110c664b..85893ed4827 100644 --- a/dlls/twain_32/tests/dsm.c +++ b/dlls/twaindsm/tests/dsm.c @@ -845,10 +845,10 @@ START_TEST(dsm) return; }
- htwain = LoadLibraryA("twain_32.dll"); + htwain = LoadLibraryA("twaindsm.dll"); if (! htwain) { - win_skip("twain_32.dll not available, skipping tests\n"); + win_skip("twaindsm.dll not available, skipping tests\n"); return; } pDSM_Entry = (void*)GetProcAddress(htwain, "DSM_Entry"); diff --git a/dlls/twain_32/twain.rc b/dlls/twaindsm/twain.rc similarity index 100% rename from dlls/twain_32/twain.rc rename to dlls/twaindsm/twain.rc diff --git a/dlls/twain_32/twain32_main.c b/dlls/twaindsm/twain32_main.c similarity index 100% rename from dlls/twain_32/twain32_main.c rename to dlls/twaindsm/twain32_main.c diff --git a/dlls/twain_32/twain_i.h b/dlls/twaindsm/twain_i.h similarity index 100% rename from dlls/twain_32/twain_i.h rename to dlls/twaindsm/twain_i.h diff --git a/dlls/twaindsm/twaindsm.spec b/dlls/twaindsm/twaindsm.spec new file mode 100644 index 00000000000..fe79c9f19dc --- /dev/null +++ b/dlls/twaindsm/twaindsm.spec @@ -0,0 +1 @@ +@ stdcall DSM_Entry(ptr ptr long long long ptr)
From: Bernd Herd codeberg@herdsoft.com
Implement DG_CONTROL/DAT_ENTRYPOINT/MSG_GET as a first step towards implementing Twain 2.0 extensions to the protocol --- dlls/twaindsm/dsm_ctrl.c | 34 ++++++++++++++++++++++++++++++++++ dlls/twaindsm/twain32_main.c | 15 +++++++++++++++ dlls/twaindsm/twain_i.h | 1 + include/twain.h | 2 ++ 4 files changed, 52 insertions(+)
diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index c77f4f32b6b..8addc688188 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -582,6 +582,12 @@ TW_UINT16 TWAIN_OpenDSM (pTW_IDENTITY pOrigin, TW_MEMREF pData) TRACE("DG_CONTROL/DAT_PARENT/MSG_OPENDSM\n"); if (!DSM_initialized) { event_message = RegisterWindowMessageA("WINE TWAIN_32 EVENT"); + + if (pOrigin->SupportedGroups & DF_APP2) + { + pOrigin->SupportedGroups |= DF_DSM2; + } + DSM_currentDevice = 0; DSM_initialized = TRUE; DSM_twCC = TWCC_SUCCESS; @@ -606,3 +612,31 @@ TW_UINT16 TWAIN_GetDSMStatus (pTW_IDENTITY pOrigin, TW_MEMREF pData) DSM_twCC = TWCC_SUCCESS; /* clear the condition code */ return TWRC_SUCCESS; } + + +/* DG_CONTROL/DAT_ENTRYPOINT/MSG_GET */ +TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint) +{ + TW_UINT16 twRC; + + if (!pEntrypoint) + { + ERR("pEntrypoint is null\n"); + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADVALUE; + } + else if (pEntrypoint->Size < sizeof(TW_ENTRYPOINT)) + { + ERR("pEntrypoint->Size=%ld too small\n", pEntrypoint->Size); + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADVALUE; + } + else + { + memcpy(pEntrypoint, &_entrypoints, sizeof(_entrypoints)); + twRC = TWRC_SUCCESS; + DSM_twCC = TWCC_SUCCESS; + } + + return twRC; +} diff --git a/dlls/twaindsm/twain32_main.c b/dlls/twaindsm/twain32_main.c index a8b11a9bbfe..8edbad40327 100644 --- a/dlls/twaindsm/twain32_main.c +++ b/dlls/twaindsm/twain32_main.c @@ -126,6 +126,21 @@ static TW_UINT16 TWAIN_SourceManagerHandler ( } break;
+ case DAT_ENTRYPOINT: + switch (MSG) + { + case MSG_GET: + twRC = TWAIN_GetEntrypoint((TW_ENTRYPOINT *) pData); + break; + + default: + /* Unrecognized operation triplet */ + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADPROTOCOL; + WARN("unrecognized operation triplet\n"); + } + break; + case DAT_STATUS: if (MSG == MSG_GET) { twRC = TWAIN_GetDSMStatus (pOrigin, pData); diff --git a/dlls/twaindsm/twain_i.h b/dlls/twaindsm/twain_i.h index 7ff608d2e95..a4aa37a5dbd 100644 --- a/dlls/twaindsm/twain_i.h +++ b/dlls/twaindsm/twain_i.h @@ -63,5 +63,6 @@ extern TW_UINT16 TWAIN_OpenDSM(pTW_IDENTITY pOrigin, TW_MEMREF pData); extern TW_UINT16 TWAIN_GetDSMStatus(pTW_IDENTITY pOrigin, TW_MEMREF pData); extern TW_UINT16 TWAIN_ControlNull(pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS *pSource, TW_UINT16 MSG, TW_MEMREF pData); extern TW_UINT16 TWAIN_ProcessEvent(pTW_IDENTITY pOrigin, activeDS *pSource, TW_MEMREF pData); +extern TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint);
#endif diff --git a/include/twain.h b/include/twain.h index ec17a4fe519..0dc65fcd8d0 100644 --- a/include/twain.h +++ b/include/twain.h @@ -1845,6 +1845,8 @@ typedef TW_UINT16 (*DSENTRYPROC)(pTW_IDENTITY,
/* Definitions from TWAIN 2.x used by our builtin data sources */ #define DAT_ENTRYPOINT 0x0403 +#define DF_DSM2 0x10000000L +#define DF_APP2 0x20000000L #define DF_DS2 0x40000000
#ifdef __cplusplus
From: Bernd Herd codeberg@herdsoft.com
Since TWAIN version 2.0, applications are encouraged to use callbacks instead of window messages for communication. This commit implements the TRIPLET to set the callbacks and the call to those callbacks.
If an application does not use callbacks, the old PostMessage method is being used as before. --- dlls/twaindsm/dsm_ctrl.c | 54 ++++++++++++++++++++++++++++++++++-- dlls/twaindsm/twain32_main.c | 19 ++++++++++++- dlls/twaindsm/twain_i.h | 2 ++ include/twain.h | 23 +++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-)
diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index 8addc688188..d0a6a13c9a6 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -77,6 +77,7 @@ twain_add_onedriver(const WCHAR *dsname) { do { int i;
+ memset(&sourceId, 0, sizeof(sourceId)); sourceId.Id = DSM_sourceId; sourceId.ProtocolMajor = TWON_PROTOCOLMAJOR; sourceId.ProtocolMinor = TWON_PROTOCOLMINOR; @@ -188,12 +189,43 @@ TW_UINT16 TWAIN_ControlNull (pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS
if (MSG != MSG_CLOSEDSREQ && MSG != MSG_DEVICEEVENT && - MSG != MSG_XFERREADY) + MSG != MSG_XFERREADY && + MSG != MSG_DEVICEEVENT) { DSM_twCC = TWCC_BADPROTOCOL; return TWRC_FAILURE; }
+ if (pSource && + pSource->registered_callback.CallBackProc) + { + static BOOL bProcessingCallback = FALSE; + TRACE("DG_CONTROL/DAT_NULL using callback\n"); + if (!bProcessingCallback) + { + TW_UINT16 twRC; + bProcessingCallback=TRUE; + twRC = ((DSMENTRYPROC) pSource->registered_callback.CallBackProc) + (pOrigin, + pDest, + DG_CONTROL, DAT_NULL, MSG, + (TW_MEMREF) pSource->registered_callback.RefCon); + bProcessingCallback=FALSE; + if (twRC != TWCC_SUCCESS) + { + DSM_twCC = TWCC_BADPROTOCOL; + } + return twRC; + } + else + { + ERR("Nested callback\n"); + DSM_twCC = TWCC_BADPROTOCOL; + return TWRC_FAILURE; + } + + } + message = HeapAlloc(GetProcessHeap(), 0, sizeof(*message)); if (!message) { @@ -377,7 +409,7 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) } /* else use the first device */
/* the source is found in the device list */ - newSource = HeapAlloc (GetProcessHeap(), 0, sizeof (activeDS)); + newSource = HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof (activeDS)); if (!newSource) { DSM_twCC = TWCC_LOWMEMORY; FIXME("Out of memory.\n"); @@ -640,3 +672,21 @@ TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint)
return twRC; } + + + +/** @brief DG_CONTROL/DAT_CALLBACK/MSG_REGISTER_CALLBACK and DG_CONTROL/DAT_CALLBACK2/MSG_REGISTER_CALLBACK + * @param pSource The data source to associate the callback to + * @param CallBackProc Address of a callback procedure defined by the application program + * @param RefCon Reference Constant as defined by the application program + * @return Twain result code, TWRC_SUCCESS on success + */ +TW_UINT16 TWAIN_RegisterCallback(activeDS *pSource, TW_MEMREF *CallBackProc, UINT_PTR RefCon) +{ + TRACE("DG_CONTROL/DAT_CALLBACKx/MSG_REGISTER_CALLBACK\n"); + pSource->registered_callback.CallBackProc=CallBackProc; + pSource->registered_callback.RefCon=RefCon; + + DSM_twCC = TWCC_SUCCESS; + return TWRC_SUCCESS; +} diff --git a/dlls/twaindsm/twain32_main.c b/dlls/twaindsm/twain32_main.c index 8edbad40327..6f727c04747 100644 --- a/dlls/twaindsm/twain32_main.c +++ b/dlls/twaindsm/twain32_main.c @@ -187,7 +187,6 @@ DSM_Entry (pTW_IDENTITY pOrigin,
return TWAIN_ControlNull (pOrigin, pDest, pSource, MSG, pData); } - if (pDest) { activeDS *pSource = TWAIN_LookupSource (pDest); @@ -213,6 +212,24 @@ DSM_Entry (pTW_IDENTITY pOrigin, pSource->ui_window = ((TW_USERINTERFACE*)pData)->hParent; }
+ if (DG == DG_CONTROL && + MSG == MSG_REGISTER_CALLBACK && + (DAT == DAT_CALLBACK || DAT != DAT_CALLBACK2) && + pData != NULL) + { + if (DAT == DAT_CALLBACK) + { + TW_CALLBACK *pCallback = (TW_CALLBACK *) pData; + twRC = TWAIN_RegisterCallback(pSource, pCallback->CallBackProc, pCallback->RefCon); + } + else + { + TW_CALLBACK2 *pCallback = (TW_CALLBACK2 *) pData; + twRC = TWAIN_RegisterCallback(pSource, pCallback->CallBackProc, pCallback->RefCon); + } + return twRC; + } + DSM_twCC = TWCC_SUCCESS; TRACE("Forwarding %ld/%d/%d/%p to DS.\n", DG, DAT, MSG, pData); twRC = pSource->dsEntry(pOrigin, DG, DAT, MSG, pData); diff --git a/dlls/twaindsm/twain_i.h b/dlls/twaindsm/twain_i.h index a4aa37a5dbd..6858aac0908 100644 --- a/dlls/twaindsm/twain_i.h +++ b/dlls/twaindsm/twain_i.h @@ -43,6 +43,7 @@ typedef struct tagActiveDS struct list pending_messages; HWND ui_window; HWND event_window; + TW_CALLBACK2 registered_callback; } activeDS;
extern TW_UINT16 DSM_twCC; /* current condition code of Source Manager */ @@ -64,5 +65,6 @@ extern TW_UINT16 TWAIN_GetDSMStatus(pTW_IDENTITY pOrigin, TW_MEMREF pData); extern TW_UINT16 TWAIN_ControlNull(pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS *pSource, TW_UINT16 MSG, TW_MEMREF pData); extern TW_UINT16 TWAIN_ProcessEvent(pTW_IDENTITY pOrigin, activeDS *pSource, TW_MEMREF pData); extern TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint); +extern TW_UINT16 TWAIN_RegisterCallback(activeDS *pSource, TW_MEMREF *CallBackProc, UINT_PTR RefCon);
#endif diff --git a/include/twain.h b/include/twain.h index 0dc65fcd8d0..bac51357012 100644 --- a/include/twain.h +++ b/include/twain.h @@ -81,6 +81,7 @@ #ifdef _MSWIN_ typedef HANDLE TW_HANDLE; typedef LPVOID TW_MEMREF; + typedef UINT_PTR TW_UINTPTR; typedef BYTE * HPBYTE; typedef void * HPVOID; #endif /* _MSWIN_ */ @@ -496,6 +497,22 @@ typedef struct { TW_INT16 VRefNum; } TW_SETUPAUDIOFILEXFER, FAR * pTW_SETUPAUDIOFILEXFER;
+ +/* Used with DG_CONTROL / DAT_CALLBACK / MSG_REGISTER_CALLBACK */ +typedef struct { + TW_MEMREF CallBackProc; + TW_UINT32 RefCon; + TW_INT16 Message; +} TW_CALLBACK, * pTW_CALLBACK; + +/* Used with DG_CONTROL / DAT_CALLBACK2 / MSG_REGISTER_CALLBACK */ +typedef struct { + TW_MEMREF CallBackProc; + TW_UINTPTR RefCon; + TW_INT16 Message; +} TW_CALLBACK2, * pTW_CALLBACK2; + + /**************************************************************************** * Generic Constants * ****************************************************************************/ @@ -1351,6 +1368,8 @@ typedef struct { #define DAT_DEVICEEVENT 0x000d /* TW_DEVICEEVENT */ #define DAT_FILESYSTEM 0x000e /* TW_FILESYSTEM */ #define DAT_PASSTHRU 0x000f /* TW_PASSTHRU */ +#define DAT_CALLBACK 0x0010 /* TW_CALLBACK */ +#define DAT_CALLBACK2 0x0012 /* TW_CALLBACK2 */
/* Data Argument Types for the DG_IMAGE Data Group. */ #define DAT_IMAGEINFO 0x0101 /* TW_IMAGEINFO */ @@ -1436,6 +1455,10 @@ typedef struct { /* Messages used with a pointer to a DAT_PASSTHRU structure */ #define MSG_PASSTHRU 0x0901
+/* Added 2.x */ +#define MSG_REGISTER_CALLBACK 0x0902 /* Used with DAT_CALLBACK/DAT_CALLBACK2*/ + + /**************************************************************************** * Capabilities * ****************************************************************************/
From: Bernd Herd codeberg@herdsoft.com
Data sources installed with a setup program are storing their data in subdirectiries of C:\Windows\twain_32 or C:\Windows\twain_64. This commit adds recursive search for all .ds files in these directories to add them to the list of available data sources --- dlls/twaindsm/dsm_ctrl.c | 78 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 14 deletions(-)
diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index d0a6a13c9a6..fc026c3528f 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -53,16 +53,13 @@ HINSTANCE hinstTwain_32 = NULL; #endif // !WIN64
static void -twain_add_onedriver(const WCHAR *dsname) { +twain_add_onedriver(const WCHAR *path) { HMODULE hmod; DSENTRYPROC dsEntry; TW_IDENTITY fakeOrigin; TW_IDENTITY sourceId; struct all_devices *new_devices; TW_UINT16 ret; - WCHAR path[MAX_PATH]; - - swprintf( path, MAX_PATH, L"c:\windows\twain_%u\%s", sizeof(void *) * 8, dsname ); hmod = LoadLibraryW(path); if (!hmod) { ERR("Failed to load TWAIN Source %s\n", debugstr_w(path)); @@ -71,6 +68,7 @@ twain_add_onedriver(const WCHAR *dsname) { dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry"); if (!dsEntry) { ERR("Failed to find DS_Entry() in TWAIN DS %s\n", debugstr_w(path)); + FreeLibrary(hmod); return; } /* Loop to do multiple detects, mostly for sane.ds and gphoto2.ds */ @@ -108,20 +106,72 @@ twain_add_onedriver(const WCHAR *dsname) { FreeLibrary (hmod); }
+ +/** + * Recursively search all *.ds Data Source files in the given directory + * and add them to the new_devices list. + * + * We search for file ending with ".ds" for the Data Source drivers + * and fill all subdirectories. + * + * @param dirname Name of the start directory. + */ +static void +twain_autodetect_recurse(const WCHAR *dirname) +{ + HANDLE hfind; + WCHAR szFindPattern[MAX_PATH]; + WCHAR *p; + WIN32_FIND_DATAW ff; + + swprintf(szFindPattern, ARRAY_SIZE(szFindPattern), L"%s\*", dirname); + + hfind = FindFirstFileW(szFindPattern, &ff); + if (hfind != INVALID_HANDLE_VALUE) + { + WCHAR szFullName[MAX_PATH]; + do + { + swprintf(szFullName, ARRAY_SIZE(szFullName), L"%s\%s", dirname, ff.cFileName); + if ((ff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + lstrcmpW(ff.cFileName, L".") && + lstrcmpW(ff.cFileName, L"..")) + { + /* Found a subdirectory. Recursivly search in it. */ + twain_autodetect_recurse(szFullName); + } + else if (NULL != (p=wcsrchr(ff.cFileName, '.')) && + !lstrcmpiW(p, L".ds")) + { + twain_add_onedriver(szFullName); + } + } + while (FindNextFileW(hfind, &ff)); + FindClose(hfind); + } +} + + static BOOL detectionrun = FALSE;
+/** @brief Detect all installed data sources by recursive directory scan. + * + * TWAIN Data Sources install in a subdirectory of c:\windows\twain_<bitdepth> as + * *.ds files. Search all of them and add them to the list. + */ static void -twain_autodetect(void) { +twain_autodetect(void) +{ + WCHAR szWindowsDir[MAX_PATH]; + WCHAR szTwainDir[MAX_PATH]; + if (detectionrun) return; - detectionrun = TRUE; - - twain_add_onedriver(L"sane.ds"); - twain_add_onedriver(L"gphoto2.ds"); -#if 0 - twain_add_onedriver(L"Largan\sp503a.ds"); - twain_add_onedriver(L"vivicam10\vivicam10.ds"); - twain_add_onedriver(L"ws30slim\sp500a.ds"); -#endif + detectionrun = TRUE; + + GetWindowsDirectoryW(szWindowsDir, MAX_PATH); + swprintf( szTwainDir, MAX_PATH, L"%s\twain_%u", szWindowsDir, sizeof(void *) * 8 ); + + twain_autodetect_recurse(szTwainDir); }
/**