512 lines
15 KiB
C
512 lines
15 KiB
C
/*
|
|
* TWAIN32 Source Manager
|
|
*
|
|
* Copyright 2000 Corel Corporation
|
|
* Copyright 2006 Marcus Meissner
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winuser.h"
|
|
#include "twain.h"
|
|
#include "twain_i.h"
|
|
#include "resource.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(twain);
|
|
|
|
static TW_UINT16 DSM_initialized; /* whether Source Manager is initialized */
|
|
static TW_UINT32 DSM_sourceId; /* source id generator */
|
|
static TW_UINT16 DSM_currentDevice; /* keep track of device during enumeration */
|
|
static HWND DSM_parent;
|
|
static UINT event_message;
|
|
|
|
struct all_devices {
|
|
WCHAR *modname;
|
|
TW_IDENTITY identity;
|
|
};
|
|
|
|
static int nrdevices = 0;
|
|
static struct all_devices *devices = NULL;
|
|
|
|
static void
|
|
twain_add_onedriver(const WCHAR *dsname) {
|
|
HMODULE hmod;
|
|
DSENTRYPROC dsEntry;
|
|
TW_IDENTITY fakeOrigin;
|
|
TW_IDENTITY sourceId;
|
|
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));
|
|
return;
|
|
}
|
|
dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry");
|
|
if (!dsEntry) {
|
|
ERR("Failed to find DS_Entry() in TWAIN DS %s\n", debugstr_w(path));
|
|
return;
|
|
}
|
|
/* Loop to do multiple detects, mostly for sane.ds and gphoto2.ds */
|
|
do {
|
|
int i;
|
|
|
|
sourceId.Id = DSM_sourceId;
|
|
sourceId.ProtocolMajor = TWON_PROTOCOLMAJOR;
|
|
sourceId.ProtocolMinor = TWON_PROTOCOLMINOR;
|
|
ret = dsEntry (&fakeOrigin, DG_CONTROL, DAT_IDENTITY, MSG_GET, &sourceId);
|
|
if (ret != TWRC_SUCCESS) {
|
|
ERR("Source->(DG_CONTROL,DAT_IDENTITY,MSG_GET) failed!\n");
|
|
break;
|
|
}
|
|
TRACE("Manufacturer: %s\n", debugstr_a(sourceId.Manufacturer));
|
|
TRACE("ProductFamily: %s\n", debugstr_a(sourceId.ProductFamily));
|
|
TRACE("ProductName: %s\n", debugstr_a(sourceId.ProductName));
|
|
|
|
for (i=0;i<nrdevices;i++) {
|
|
if (!strcmp(sourceId.ProductName,devices[i].identity.ProductName))
|
|
break;
|
|
}
|
|
if (i < nrdevices)
|
|
break;
|
|
devices = realloc( devices, sizeof(devices[0])*(nrdevices+1) );
|
|
devices[nrdevices].modname = wcsdup( path );
|
|
devices[nrdevices].identity = sourceId;
|
|
nrdevices++;
|
|
DSM_sourceId++;
|
|
} while (1);
|
|
FreeLibrary (hmod);
|
|
}
|
|
|
|
static BOOL detectionrun = FALSE;
|
|
|
|
static void
|
|
twain_autodetect(void) {
|
|
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
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_NULL/MSG_CLOSEDSREQ|MSG_DEVICEEVENT|MSG_XFERREADY */
|
|
TW_UINT16 TWAIN_ControlNull (pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS *pSource, TW_UINT16 MSG, TW_MEMREF pData)
|
|
{
|
|
struct pending_message *message;
|
|
|
|
TRACE ("DG_CONTROL/DAT_NULL MSG=%i\n", MSG);
|
|
|
|
if (MSG != MSG_CLOSEDSREQ &&
|
|
MSG != MSG_DEVICEEVENT &&
|
|
MSG != MSG_XFERREADY)
|
|
{
|
|
DSM_twCC = TWCC_BADPROTOCOL;
|
|
return TWRC_FAILURE;
|
|
}
|
|
|
|
message = HeapAlloc(GetProcessHeap(), 0, sizeof(*message));
|
|
if (!message)
|
|
{
|
|
DSM_twCC = TWCC_LOWMEMORY;
|
|
return TWRC_FAILURE;
|
|
}
|
|
|
|
message->msg = MSG;
|
|
list_add_tail(&pSource->pending_messages, &message->entry);
|
|
|
|
/* Delphi twain only sends us messages from one window, and it
|
|
doesn't even give us the real handle to that window. Other
|
|
applications might decide to forward messages sent to DSM_parent
|
|
or to the one supplied to ENABLEDS. So let's try very hard to
|
|
find a window that will work. */
|
|
if (DSM_parent)
|
|
PostMessageW(DSM_parent, event_message, 0, 0);
|
|
if (pSource->ui_window && pSource->ui_window != DSM_parent)
|
|
PostMessageW(pSource->ui_window, event_message, 0, 0);
|
|
if (pSource->event_window && pSource->event_window != pSource->ui_window &&
|
|
pSource->event_window != DSM_parent)
|
|
PostMessageW(pSource->event_window, event_message, 0, 0);
|
|
PostMessageW(0, event_message, 0, 0);
|
|
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
/* Filters MSG_PROCESSEVENT messages before reaching the data source */
|
|
TW_UINT16 TWAIN_ProcessEvent (pTW_IDENTITY pOrigin, activeDS *pSource, TW_MEMREF pData)
|
|
{
|
|
TW_EVENT *event = (TW_EVENT*)pData;
|
|
MSG *msg = (MSG*)event->pEvent;
|
|
TW_UINT16 result = TWRC_NOTDSEVENT;
|
|
|
|
TRACE("%x,%x\n", msg->message, event_message);
|
|
|
|
if (msg->message == event_message)
|
|
{
|
|
if (!list_empty (&pSource->pending_messages))
|
|
{
|
|
struct list *entry = list_head (&pSource->pending_messages);
|
|
struct pending_message *message = LIST_ENTRY(entry, struct pending_message, entry);
|
|
event->TWMessage = message->msg;
|
|
list_remove (entry);
|
|
TRACE("<-- %x\n", event->TWMessage);
|
|
}
|
|
else
|
|
event->TWMessage = MSG_NULL;
|
|
result = TWRC_DSEVENT;
|
|
}
|
|
|
|
if (msg->hwnd)
|
|
{
|
|
MSG dummy;
|
|
pSource->event_window = msg->hwnd;
|
|
if (!list_empty (&pSource->pending_messages) &&
|
|
!PeekMessageW(&dummy, msg->hwnd, event_message, event_message, PM_NOREMOVE))
|
|
{
|
|
PostMessageW(msg->hwnd, event_message, 0, 0);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS */
|
|
TW_UINT16 TWAIN_CloseDS (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
TW_UINT16 twRC = TWRC_SUCCESS;
|
|
pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
|
|
activeDS *currentDS = NULL, *prevDS = NULL;
|
|
|
|
TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS\n");
|
|
|
|
for (currentDS = activeSources; currentDS; currentDS = currentDS->next) {
|
|
if (currentDS->identity.Id == pIdentity->Id)
|
|
break;
|
|
prevDS = currentDS;
|
|
}
|
|
if (!currentDS) {
|
|
DSM_twCC = TWCC_NODS;
|
|
return TWRC_FAILURE;
|
|
}
|
|
twRC = currentDS->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, pData);
|
|
/* This causes crashes due to still open Windows, so leave out for now.
|
|
* FreeLibrary (currentDS->hmod);
|
|
*/
|
|
if (prevDS)
|
|
prevDS->next = currentDS->next;
|
|
else
|
|
activeSources = currentDS->next;
|
|
HeapFree (GetProcessHeap(), 0, currentDS);
|
|
if (twRC == TWRC_SUCCESS)
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
else /* FIXME: unclear how to get TWCC */
|
|
DSM_twCC = TWCC_SEQERROR;
|
|
return twRC;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT */
|
|
TW_UINT16 TWAIN_IdentityGetDefault (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
|
|
|
|
TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT\n");
|
|
DSM_twCC = TWCC_NODS;
|
|
twain_autodetect();
|
|
if (!nrdevices)
|
|
return TWRC_FAILURE;
|
|
*pSourceIdentity = devices[0].identity;
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_GETFIRST */
|
|
TW_UINT16 TWAIN_IdentityGetFirst (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
|
|
|
|
TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_GETFIRST\n");
|
|
twain_autodetect();
|
|
if (!nrdevices) {
|
|
TRACE ("no entries found.\n");
|
|
DSM_twCC = TWCC_NODS;
|
|
return TWRC_FAILURE;
|
|
}
|
|
DSM_currentDevice = 0;
|
|
*pSourceIdentity = devices[DSM_currentDevice++].identity;
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_GETNEXT */
|
|
TW_UINT16 TWAIN_IdentityGetNext (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
|
|
|
|
TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETNEXT\n");
|
|
if (!nrdevices || (DSM_currentDevice == nrdevices)) {
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_ENDOFLIST;
|
|
}
|
|
*pSourceIdentity = devices[DSM_currentDevice++].identity;
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_OPENDS */
|
|
TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
TW_UINT16 i = 0;
|
|
pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
|
|
activeDS *newSource;
|
|
HMODULE hmod;
|
|
|
|
TRACE("DG_CONTROL/DAT_IDENTITY/MSG_OPENDS\n");
|
|
TRACE("pIdentity is %s\n", pIdentity->ProductName);
|
|
if (!DSM_initialized) {
|
|
FIXME("seq error\n");
|
|
DSM_twCC = TWCC_SEQERROR;
|
|
return TWRC_FAILURE;
|
|
}
|
|
twain_autodetect();
|
|
if (!nrdevices) {
|
|
FIXME("no devs.\n");
|
|
DSM_twCC = TWCC_NODS;
|
|
return TWRC_FAILURE;
|
|
}
|
|
|
|
if (pIdentity->ProductName[0] != '\0') {
|
|
/* Make sure the source to be opened exists in the device list */
|
|
for (i = 0; i<nrdevices; i++)
|
|
if (!strcmp (devices[i].identity.ProductName, pIdentity->ProductName))
|
|
break;
|
|
if (i == nrdevices)
|
|
i = 0;
|
|
} /* else use the first device */
|
|
|
|
/* the source is found in the device list */
|
|
newSource = HeapAlloc (GetProcessHeap(), 0, sizeof (activeDS));
|
|
if (!newSource) {
|
|
DSM_twCC = TWCC_LOWMEMORY;
|
|
FIXME("Out of memory.\n");
|
|
return TWRC_FAILURE;
|
|
}
|
|
hmod = LoadLibraryW(devices[i].modname);
|
|
if (!hmod) {
|
|
ERR("Failed to load TWAIN Source %s\n", debugstr_w(devices[i].modname));
|
|
DSM_twCC = TWCC_OPERATIONERROR;
|
|
HeapFree(GetProcessHeap(), 0, newSource);
|
|
return TWRC_FAILURE;
|
|
}
|
|
newSource->hmod = hmod;
|
|
newSource->dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry");
|
|
/* Assign id for the opened data source */
|
|
pIdentity->Id = DSM_sourceId ++;
|
|
if (TWRC_SUCCESS != newSource->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, pIdentity)) {
|
|
DSM_twCC = TWCC_OPERATIONERROR;
|
|
HeapFree(GetProcessHeap(), 0, newSource);
|
|
DSM_sourceId--;
|
|
return TWRC_FAILURE;
|
|
}
|
|
/* add the data source to an internal active source list */
|
|
newSource->next = activeSources;
|
|
newSource->identity.Id = pIdentity->Id;
|
|
strcpy (newSource->identity.ProductName, pIdentity->ProductName);
|
|
list_init(&newSource->pending_messages);
|
|
newSource->ui_window = NULL;
|
|
newSource->event_window = NULL;
|
|
activeSources = newSource;
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
typedef struct {
|
|
pTW_IDENTITY origin;
|
|
pTW_IDENTITY result;
|
|
} userselect_data;
|
|
|
|
static INT_PTR CALLBACK userselect_dlgproc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
userselect_data *data = (userselect_data*)lparam;
|
|
int i;
|
|
HWND sourcelist;
|
|
BOOL any_devices = FALSE;
|
|
|
|
SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)data);
|
|
|
|
sourcelist = GetDlgItem(hwnd, IDC_LISTSOURCE);
|
|
|
|
for (i=0; i<nrdevices; i++)
|
|
{
|
|
TW_IDENTITY *id = &devices[i].identity;
|
|
LRESULT index;
|
|
|
|
if ((id->SupportedGroups & data->origin->SupportedGroups) == 0)
|
|
continue;
|
|
|
|
index = SendMessageA(sourcelist, LB_ADDSTRING, 0, (LPARAM)id->ProductName);
|
|
SendMessageW(sourcelist, LB_SETITEMDATA, (WPARAM)index, (LPARAM)i);
|
|
any_devices = TRUE;
|
|
}
|
|
|
|
if (any_devices)
|
|
{
|
|
EnableWindow(GetDlgItem(hwnd, IDOK), TRUE);
|
|
|
|
/* FIXME: Select the supplied product name or default source. */
|
|
SendMessageW(sourcelist, LB_SETCURSEL, 0, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
case WM_CLOSE:
|
|
EndDialog(hwnd, 0);
|
|
return TRUE;
|
|
case WM_COMMAND:
|
|
if (wparam == MAKEWPARAM(IDCANCEL, BN_CLICKED))
|
|
{
|
|
EndDialog(hwnd, 0);
|
|
return TRUE;
|
|
}
|
|
else if (wparam == MAKEWPARAM(IDOK, BN_CLICKED) ||
|
|
wparam == MAKEWPARAM(IDC_LISTSOURCE, LBN_DBLCLK))
|
|
{
|
|
userselect_data *data = (userselect_data*)GetWindowLongPtrW(hwnd, DWLP_USER);
|
|
HWND sourcelist;
|
|
LRESULT index;
|
|
|
|
sourcelist = GetDlgItem(hwnd, IDC_LISTSOURCE);
|
|
|
|
index = SendMessageW(sourcelist, LB_GETCURSEL, 0, 0);
|
|
|
|
if (index == LB_ERR)
|
|
return TRUE;
|
|
|
|
index = SendMessageW(sourcelist, LB_GETITEMDATA, (WPARAM)index, 0);
|
|
|
|
*data->result = devices[index].identity;
|
|
|
|
/* FIXME: Save this as the default source */
|
|
|
|
EndDialog(hwnd, 1);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT */
|
|
TW_UINT16 TWAIN_UserSelect (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
userselect_data param = {pOrigin, pData};
|
|
HWND parent = DSM_parent;
|
|
|
|
TRACE("DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT SupportedGroups=0x%lx ProductName=%s\n",
|
|
pOrigin->SupportedGroups, wine_dbgstr_a(param.result->ProductName));
|
|
|
|
twain_autodetect();
|
|
|
|
if (!IsWindow(parent))
|
|
parent = NULL;
|
|
|
|
if (DialogBoxParamW(DSM_hinstance, MAKEINTRESOURCEW(DLG_USERSELECT),
|
|
parent, userselect_dlgproc, (LPARAM)¶m) == 0)
|
|
{
|
|
TRACE("canceled\n");
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_CANCEL;
|
|
}
|
|
|
|
TRACE("<-- %s\n", wine_dbgstr_a(param.result->ProductName));
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_SUCCESS;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_PARENT/MSG_CLOSEDSM */
|
|
TW_UINT16 TWAIN_CloseDSM (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
activeDS *currentDS = activeSources, *nextDS;
|
|
|
|
TRACE("DG_CONTROL/DAT_PARENT/MSG_CLOSEDSM\n");
|
|
|
|
if (DSM_initialized)
|
|
{
|
|
DSM_initialized = FALSE;
|
|
|
|
/* If there are data sources still open, close them now. */
|
|
while (currentDS != NULL)
|
|
{
|
|
nextDS = currentDS->next;
|
|
currentDS->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, pData);
|
|
HeapFree (GetProcessHeap(), 0, currentDS);
|
|
currentDS = nextDS;
|
|
}
|
|
activeSources = NULL;
|
|
DSM_parent = NULL;
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
return TWRC_SUCCESS;
|
|
} else {
|
|
DSM_twCC = TWCC_SEQERROR;
|
|
return TWRC_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_PARENT/MSG_OPENDSM */
|
|
TW_UINT16 TWAIN_OpenDSM (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
TW_UINT16 twRC = TWRC_SUCCESS;
|
|
|
|
TRACE("DG_CONTROL/DAT_PARENT/MSG_OPENDSM\n");
|
|
if (!DSM_initialized) {
|
|
event_message = RegisterWindowMessageA("WINE TWAIN_32 EVENT");
|
|
DSM_currentDevice = 0;
|
|
DSM_initialized = TRUE;
|
|
DSM_twCC = TWCC_SUCCESS;
|
|
twRC = TWRC_SUCCESS;
|
|
} else {
|
|
/* operation invoked in invalid state */
|
|
DSM_twCC = TWCC_SEQERROR;
|
|
twRC = TWRC_FAILURE;
|
|
}
|
|
DSM_parent = (HWND)pData;
|
|
return twRC;
|
|
}
|
|
|
|
/* DG_CONTROL/DAT_STATUS/MSG_GET */
|
|
TW_UINT16 TWAIN_GetDSMStatus (pTW_IDENTITY pOrigin, TW_MEMREF pData)
|
|
{
|
|
pTW_STATUS pSourceStatus = (pTW_STATUS) pData;
|
|
|
|
TRACE ("DG_CONTROL/DAT_STATUS/MSG_GET\n");
|
|
|
|
pSourceStatus->ConditionCode = DSM_twCC;
|
|
DSM_twCC = TWCC_SUCCESS; /* clear the condition code */
|
|
return TWRC_SUCCESS;
|
|
}
|