code_device/twain/dsm/apps.cpp

1921 lines
59 KiB
C++

/***************************************************************************
* TWAIN Data Source Manager version 2.1
* Manages image acquisition data sources used by a machine.
* Copyright © 2007 TWAIN Working Group:
* Adobe Systems Incorporated,AnyDoc Software Inc., Eastman Kodak Company,
* Fujitsu Computer Products of America, JFL Peripheral Solutions Inc.,
* Ricoh Corporation, and Xerox Corporation.
* All rights reserved.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Contact the TWAIN Working Group by emailing the Technical Subcommittee at
* twainwg@twain.org or mailing us at 13090 Hwy 9, Suite 3, Boulder Creek, CA 95006.
*
***************************************************************************/
/**
* @file apps.cpp
* Support the Application and Driver data for the Data Source Manager.
* @author TWAIN Working Group
* @date March 2007
*/
#include "dsm.h"
/**
* Describes everything we need to know about the Data Source over
* the course of the session...
*/
typedef struct
{
TW_IDENTITY Identity; /**< Identity info for data source */
TW_HANDLE pHandle; /**< returned by LOADLIBRARY(...) */
DSENTRYPROC DS_Entry; /**< function pointer to the DS_Entry function -- set by dlsym(...) */
char szPath[FILENAME_MAX]; /**< location of the DS */
TW_CALLBACK2 twcallback2; /**< callback structure (we're using callback2 because it's 32-bit and 64-bit safe) */
TW_BOOL bCallbackPending; /**< True if an application is old style and a callback was supposed to be made to it */
TW_BOOL bDSProcessingMessage; /**< True if the application is still waiting for the DS to return from processing a message */
TW_BOOL bAppProcessingCallback; /**< True if the application is still waiting for the DS to return from processing a message */
} DS_INFO;
/**
* We have to manage the horror we created for ourselves when we
* defined TW_INT32/TW_UINT32 to be long instead of int. We can
* use the union with any data source on 64-bit Linux and not
* worry about memory problems. We can then examine the data and
* decide if we can handle it. Old data sources must be ignored.
* Anyone who needs to use an old data source has to use the
* libtwaindsm.so.2.3.1 DSM.
*/
typedef struct {
long Id;
TW_VERSION Version;
TW_UINT16 ProtocolMajor;
TW_UINT16 ProtocolMinor;
long SupportedGroups;
TW_STR32 Manufacturer;
TW_STR32 ProductFamily;
TW_STR32 ProductName;
} TW_IDENTITY_LINUX64;
typedef union {
TW_IDENTITY twidentity;
TW_IDENTITY_LINUX64 twidentitylinux64;
} TW_IDENTITY_LINUX64SAFE;
/**
* Structure to hold a list of Data Sources.
*/
typedef struct
{
TW_UINT16 NumFiles; /**< Number of items in list */
DS_INFO DSInfo[MAX_NUM_DS]; /**< array of Data Sources */
} DS_LIST;
/**
* Structure to hold data about a connected application, we use
* DS_LIST so we don't have to allocate memory that we don't
* need, on the theory that few applications will load more than
* one driver at a time...
*/
typedef struct
{
TW_IDENTITY identity; /**< the application's identity. */
TW_INT16 ConditionCode; /**< the apps condition code. */
DSM_State CurrentState; /**< the current state of the DSM for this app. */
DS_LIST *pDSList; /**< Each Application has a list of DS that it discovers each time the app opens the DSM. */
HWND hwnd; /**< the window that will monitor for events on Windows */
} APP_INFO;
/**
* Class CAppList implements dynamic array of APP_INFO
*/
class CAppList
{
private:
APP_INFO *m_pList; /**< pointer to dynamicaly allocated array */
TWID_T m_count; /**< number of elements of the array */
public:
/**
* Default constructor
* allocates element 0. Real data starts from element 1, for backward compatibility
*/
CAppList()
{
m_count=0;
m_pList=NULL;
}
/**
* Default destructor
* frees allocated resources
*/
~CAppList()
{
if(m_pList)
{
free(m_pList);
}
}
/**
* Get number of allocated App slots (Last valid App ID +1)
* @return number of allocated App slots (Last valid App ID +1)
*/
TWID_T size(){return m_count;}
/**
* Get reference to element of the array.
* Allocate memory if the element does not exist.
* @param[in] AppId is Application ID/array element index
* @return reference to the array element
*/
APP_INFO& operator[](TWID_T AppId)
{
if(AppId>=m_count)
{
APP_INFO * pNewList=(APP_INFO *)realloc(m_pList,sizeof(APP_INFO)*(AppId+1));
if(pNewList==NULL)
{
kLOG((kLOGERR,"realloc of m_pList failed AppId = %d",AppId));
return m_pList[0];
}
m_pList = pNewList;
memset(&m_pList[m_count],0,sizeof(APP_INFO)*(AppId+1-m_count));
m_count = AppId+1;
}
return m_pList[AppId];
}
/**
* Erase element from the array
* Unallocate memory if element is last one, else clear CurrentState
* @param[in] AppId is Application ID/array element index
* @return true on success
*/
bool Erase(TWID_T AppId)
{
if((AppId==0) || (AppId>=m_count))
{
kLOG((kLOGERR,"AppId = %d is invalid",AppId));
return false;
}
if(AppId==(m_count-1))
{
m_count--;
for(TWID_T i=m_count-1; i>0;i--)
{
if(m_pList[i].identity.Id)
{
break;
}
m_count--;
}
APP_INFO * pNewList=(APP_INFO *)realloc(m_pList,sizeof(APP_INFO)*m_count);
if(pNewList==NULL)
{
kLOG((kLOGERR,"realloc of m_pList failed AppId = %d",AppId));
return false;
}
m_pList = pNewList;
}
else
{
memset(&m_pList[AppId],0,sizeof(APP_INFO));
}
return true;
}
/**
* Erase all elements of the array
* @return true on success
*/
bool Clear()
{
APP_INFO * pNewList=(APP_INFO *)realloc(m_pList,sizeof(APP_INFO));
if(pNewList==NULL)
{
memset(&m_pList[1],0,sizeof(APP_INFO)*(m_count-1));
kLOG((kLOGERR,"realloc of m_pList failed"));
return false;
}
m_count=1;
m_pList = pNewList;
return true;
}
};
/**
* Impl Class to hold list of connected applications.
* In 32bit enviroments each application will connect to a seperate
* instance of DSM data but with this list it allows ONE application
* to connect several time, as long as it uses a different name with
* each connection. I'm still not sure why you'd want to do that,
* but there it is. This class is intended to hide the gory details
* of how we're storing the data, so an impl is used.
*/
class CTwnDsmAppsImpl
{
public:
/**
* Our CTwnDsmAppsImpl constructor.
*/
CTwnDsmAppsImpl()
{
memset(&pod,0,sizeof(_pod));
}
/**
* Scan for Data Sources.
* Recursively navigate the TWAIN datasource dir looking for data sources.
* Store all valid data sources in _pList upto a maximum of MAX_NUM_DS
* data sources.
* @param[in] _szAbsPath starting directory to begin search.
* @param[out] _pAppId the application requesting scan.
* @return either EXIT_SUCCESS or EXIT_FAILURE.
*/
int scanDSDir(char *_szAbsPath,
TW_IDENTITY *_pAppId);
/**
* Translates the cc passed in into a string and returns it
* @param[in] cc the TWAIN Condition Code to translate
* @return a string that represents the cc
*/
const char *StringFromCC(const TW_UINT16 cc);
/**
* Loads a DS from disk and adds it to a global list of DS's.
* @param[in] _pAppId Origin of message
* @param[in] _pPath The path to the library to open
* @param[in] _DsId the source array index
* @param[in] _boolKeepOpen if set to true keeps DS open after successful load
* @return a valid TWRC_xxxx return code
*/
TW_INT16 LoadDS(TW_IDENTITY *_pAppId,
char *_pPath,
TWID_T _DsId,
bool _boolKeepOpen);
/**
* Set the condition code.
* @param[in] _pAppId Origin of message
* @param[in] _ConditionCode new code to remember
*/
void AppSetConditionCode(TW_IDENTITY *_pAppId,
TW_UINT16 _ConditionCode);
public:
// If you add a class in future, declare it here and not in
// the pod, or the memset in the constructor will ruin your
// day...
/**
* We use a pod (Pieces of Data) system because it help prevents us from
* making dumb initialization mistakes.
*/
struct _pod
{
TW_UINT16 m_conditioncode; /**< we use this if we have no apps. */
} pod; /**< Pieces of data for CTwnDsmAppsImpl*/
CAppList m_AppInfo; /**< list of applications. */
};
/**
* The constructor, where we create our implementation object...
*/
CTwnDsmApps::CTwnDsmApps()
{
m_ptwndsmappsimpl = new CTwnDsmAppsImpl;
if (!m_ptwndsmappsimpl)
{
kLOG((kLOGERR,"new of CTwnDsmAppsImpl failed..."));
}
}
/**
* The destructor, where we destroy our implementation object...
*/
CTwnDsmApps::~CTwnDsmApps()
{
if (m_ptwndsmappsimpl)
{
// We should not have to go through the list of Apps at this point and
// close them. The application should close any open DSs and then Close
// the DSM which should take care of this. But just in case, we will
// clean up any DS left open.
for (TWID_T i = 1; i < m_ptwndsmappsimpl->m_AppInfo.size(); i++)
{
if( m_ptwndsmappsimpl->m_AppInfo[i].identity.Id
&& dsmState_Open != m_ptwndsmappsimpl->m_AppInfo[i].CurrentState )
{
kLOG((kLOGINFO,"The Application, \"%0.32s\", has left the DSM in an open state when it was unloaded!",
m_ptwndsmappsimpl->m_AppInfo[i].identity.ProductName));
RemoveApp(&m_ptwndsmappsimpl->m_AppInfo[i].identity);
}
}
delete m_ptwndsmappsimpl;
m_ptwndsmappsimpl = 0;
}
}
/**
* Add an application.
* Attempt to add an application to our list. Truth be told we only expect
* an application to do this once, but for legacy's sake we support the
* ability to do it more than once, but the Application has to provide a
* unique ProductName in its Identity. If an Application really has to
* support multiple drivers, then it's recommended that it do this through
* seperate processes, since their is no guarantee that any two drivers will
* operator correctly in the same process...
*/
TW_UINT16 CTwnDsmApps::AddApp(TW_IDENTITY *_pAppId,
TW_MEMREF _MemRef)
{
TWID_T ii;
char szDsm[FILENAME_MAX];
// Validate...
if (_pAppId->ProductName[0] == 0)
{
kLOG((kLOGERR,"AppId.ProductName is empty"));
AppSetConditionCode(0,TWCC_BADVALUE);
return TWRC_FAILURE;
}
// Initialize...
ii = 0;
memset(szDsm,0,sizeof(szDsm));
// Log the entry...
kLOG((kLOGINFO,"Application: \"%0.32s\"", _pAppId->Manufacturer));
kLOG((kLOGINFO," \"%0.32s\"", _pAppId->ProductFamily));
kLOG((kLOGINFO," \"%0.32s\" version: %u.%u", _pAppId->ProductName, _pAppId->Version.MajorNum, _pAppId->Version.MinorNum));
kLOG((kLOGINFO," TWAIN %u.%u", _pAppId->ProtocolMajor, _pAppId->ProtocolMinor));
// An application is identified by the name and handle
// Check to see if this app has already been opened, and
// if so, treat it as a sequence error, because this app
// is already open...
for (ii = 1; ii < m_ptwndsmappsimpl->m_AppInfo.size(); ii++)
{
if ( !strncmp((char*)m_ptwndsmappsimpl->m_AppInfo[ii].identity.ProductName,(char*)_pAppId->ProductName,sizeof(TW_STR32))
&& m_ptwndsmappsimpl->m_AppInfo[ii].hwnd == (HWND)(_MemRef?*(HWND*)_MemRef:0) )
{
kLOG((kLOGERR,"A successful MSG_OPENDSM was already done for %s...",_pAppId->ProductName));
AppSetConditionCode(0,TWCC_SEQERROR);
return TWRC_FAILURE;
}
}
//Go through the list and find an empty location
// Already tested that there is enough room to fit
for (ii=1; ii < m_ptwndsmappsimpl->m_AppInfo.size(); ii++)
{
if (!m_ptwndsmappsimpl->m_AppInfo[ii].identity.Id)
{
break;
}
}
// The application ID is equal to array index it resides in.
// We just let the 0-index stay empty...
_pAppId->Id = (TWIDDEST_T)ii;
_pAppId->SupportedGroups |= DF_DSM2;
m_ptwndsmappsimpl->m_AppInfo[ii].identity = *_pAppId;
m_ptwndsmappsimpl->m_AppInfo[ii].hwnd = (HWND)(_MemRef?*(HWND*)_MemRef:0);
m_ptwndsmappsimpl->m_AppInfo[ii].pDSList = (DS_LIST*)calloc(sizeof(DS_LIST)+1,1);
if (!m_ptwndsmappsimpl->m_AppInfo[ii].pDSList)
{
kLOG((kLOGERR,"calloc failed for %s...",_pAppId->ProductName));
AppSetConditionCode(0,TWCC_LOWMEMORY);
return TWRC_FAILURE;
}
// Work out the full path to our drivers (if needed)...
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
(void)::GetWindowsDirectory(szDsm,sizeof(szDsm));
SSTRCAT(szDsm,sizeof(szDsm),"\\");
SSTRCAT(szDsm,sizeof(szDsm),kTWAIN_DS_DIR);
#elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP)
SSTRCPY(szDsm,sizeof(szDsm),kTWAIN_DS_DIR);
#else
#error Sorry, we do not recognize this system...
#endif
// Move DSM to state 3 for this app...
m_ptwndsmappsimpl->m_AppInfo[ii].CurrentState = dsmState_Open;
// Recursively navigate the TWAIN datasource dir looking for data sources.
// Ignor error continue with what we found even if it is nothing
m_ptwndsmappsimpl->scanDSDir(szDsm,_pAppId);
// Maybe one of many DS failed but we still found some.
AppSetConditionCode(_pAppId, TWCC_SUCCESS);
// at this point we can safely add our flag to the caller's
// application id, but don't bother to do it unless they put
// their flag in there first...
if (_pAppId->SupportedGroups & DF_APP2)
{
_pAppId->SupportedGroups |= DF_DSM2;
}
// All done...
return TWRC_SUCCESS;
}
/**
* Remove an application.
* Attempt to remove an application to our list.
*/
TW_UINT16 CTwnDsmApps::RemoveApp(TW_IDENTITY *_pAppId)
{
int nIndex;
DS_INFO *pDSInfo;
TW_PENDINGXFERS twpendingxfers;
TW_USERINTERFACE twuserinterface;
// Validate...
if ( ((TWID_T)_pAppId->Id ==0)
|| ((TWID_T)_pAppId->Id > m_ptwndsmappsimpl->m_AppInfo.size()))
{
kLOG((kLOGERR,"_id is out of range...%d",(int)(TWID_T)_pAppId->Id));
AppSetConditionCode(0,TWCC_BADVALUE);
return TWRC_FAILURE;
}
// To close the DSM we must be open. I don't really like this
// piece of code. To my way of thinking a close should always
// succeed, even if there are mice in the DVD drive and the
// monitor is on fire. The notion of a failure message during
// a close is annoying. But there might be a good reason for
// this I'm not aware of...
if (dsmState_Open != m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].CurrentState)
{
kLOG((kLOGINFO,"%0.32s not open.",(char*)_pAppId->ProductName));
AppSetConditionCode(0,TWCC_SEQERROR);
return TWRC_FAILURE;
}
// Get rid of our list of drivers...
if (m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList)
{
// Check all the driver slots, if we find an open slot, shotgun it
// with the sequence of close out commands and shut it down. This
// really isn't something we should have to do, but it makes us
// more robust...
for (nIndex = 1;
nIndex < m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles;
nIndex++)
{
pDSInfo = &m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[nIndex];
if (pDSInfo->DS_Entry)
{
kLOG((kLOGERR,"MSG_CLOSEDSM called with drivers still open."));
kLOG((kLOGINFO,"The application should not be doing this."));
kLOG((kLOGINFO,"The DSM is going to try to gracefully shutdown the drivers..."));
memset(&twpendingxfers,0,sizeof(twpendingxfers));
memset(&twuserinterface,0,sizeof(twuserinterface));
pDSInfo->DS_Entry(_pAppId,DG_CONTROL,DAT_PENDINGXFERS,MSG_ENDXFER,(TW_MEMREF)&twpendingxfers);
pDSInfo->DS_Entry(_pAppId,DG_CONTROL,DAT_PENDINGXFERS,MSG_RESET,(TW_MEMREF)&twpendingxfers);
pDSInfo->DS_Entry(_pAppId,DG_CONTROL,DAT_USERINTERFACE,MSG_DISABLEDS,(TW_MEMREF)&twuserinterface);
pDSInfo->DS_Entry(_pAppId,DG_CONTROL,DAT_IDENTITY,MSG_CLOSEDS,(TW_MEMREF)&pDSInfo->Identity);
UnloadDS(_pAppId,nIndex);
}
}
// Okay, we can blow away the memory now...
free(m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList);
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList = NULL;
}
//Free AppInfo for this App
m_ptwndsmappsimpl->m_AppInfo.Erase((TWID_T)_pAppId->Id);
// All done...
return TWRC_SUCCESS;
}
/**
* Validate an application's identity.
* Make sure we're dealing with good data...
*/
TW_BOOL CTwnDsmApps::AppValidateId(TW_IDENTITY *_pAppId)
{
if (!_pAppId)
{
kLOG((kLOGERR,"_pAppId is null..."));
return false;
}
else if ((TWID_T)_pAppId->Id >= m_ptwndsmappsimpl->m_AppInfo.size())
{
kLOG((kLOGERR,"invalid App ID...%d",(int)(TWID_T)_pAppId->Id));
return false;
}
return true;
}
/**
* Validate an application's identity and its DS identity.
* Make sure we're dealing with good data...
*/
TW_BOOL CTwnDsmApps::AppValidateIds(TW_IDENTITY *_pAppId, TW_IDENTITY *_pDSId)
{
if(!AppValidateId(_pAppId))
{
return false;
}
else
{
if (!_pDSId)
{
kLOG((kLOGERR,"_pDSId is null..."));
return false;
}
else if ((TWID_T)_pDSId->Id >= MAX_NUM_DS)
{
kLOG((kLOGERR,"invalid DS ID...%d",(int)(TWID_T)_pDSId->Id));
return false;
}
else if (!m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList)
{
kLOG((kLOGERR,"List of DS for app is invalid"));
return false;
}
else if ((TWID_T)_pDSId->Id > m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles)
{
kLOG((kLOGERR,"The DS ID for app is not valid"));
return false;
}
}
return true;
}
/**
* Validate an application's identity.
* Make sure we're dealing with good data...
*/
TW_IDENTITY *CTwnDsmApps::AppGetIdentity(TW_IDENTITY *_pAppId)
{
if (AppValidateId(_pAppId))
{
return &m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].identity;
}
kLOG((kLOGERR,"bad _pAppId..."));
return NULL;
}
/**
* Return the condition code and clear it internally.
* Every open application maintains its own conditioncode, which
* is going to be used in state 3. In state 4 and higher the
* condition code is coming from the driver. In state 2 there is
* no condition code, so we have to use a value that is global to
* this instance of the DSM. The code is cleared internally,
* per the specification...
*/
TW_UINT16 CTwnDsmApps::AppGetConditionCode(TW_IDENTITY *_pAppId)
{
TW_UINT16 conditioncode;
// Return the application specific value...
if (AppValidateId(_pAppId))
{
conditioncode = m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].ConditionCode;
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].ConditionCode = TWCC_SUCCESS;
m_ptwndsmappsimpl->pod.m_conditioncode = TWCC_SUCCESS;
return conditioncode;
}
// Uh-oh, we have no app, so return the global value...
else
{
conditioncode = m_ptwndsmappsimpl->pod.m_conditioncode;
m_ptwndsmappsimpl->pod.m_conditioncode = TWCC_SUCCESS;
return conditioncode;
}
}
/*
* Set the condition code.
* The same rules apply here as they do for AppGetConditionCode.
* This is the interface function...
*/
void CTwnDsmApps::AppSetConditionCode(TW_IDENTITY *_pAppId,
TW_UINT16 _ConditionCode)
{
return m_ptwndsmappsimpl->AppSetConditionCode(_pAppId,_ConditionCode);
}
/**
* Set the condition code.
* The same rules apply here as they do for AppGetConditionCode.
* This is the implemenation function.
*/
void CTwnDsmAppsImpl::AppSetConditionCode(TW_IDENTITY *_pAppId,
TW_UINT16 _ConditionCode)
{
// We have no application identity to work with...
if ( (0 == _pAppId)
|| (0 == (TWID_T)_pAppId->Id)
|| (0 == m_AppInfo[(TWID_T)_pAppId->Id].identity.Id))
{
pod.m_conditioncode = _ConditionCode;
}
// This is where we normally expect to be...
else
{
m_AppInfo[(TWID_T)_pAppId->Id].ConditionCode = _ConditionCode;
}
// Make a note of this in the log...
if (_ConditionCode != TWCC_SUCCESS)
{
kLOG((kLOGINFO,"Condition Code: %s",StringFromCC(_ConditionCode)));
}
// All done...
return;
}
DSM_State CTwnDsmApps::AppGetState()
{
// Initialize to PreSession and update it if we find an application that is further along.
DSM_State CurrentState = dsmState_PreSession;
for (TWID_T AppID = 1; AppID<m_ptwndsmappsimpl->m_AppInfo.size(); AppID++)
{
if(m_ptwndsmappsimpl->m_AppInfo[AppID].CurrentState > CurrentState)
{
CurrentState = m_ptwndsmappsimpl->m_AppInfo[AppID].CurrentState;
}
}
return CurrentState;
}
/**
* Get our current state.
* There are really only two states that the DSM can occupy, state
* 2 where it's loaded, but hasn't had a MSG_OPENDSM done for the
* specified application identity. And state 3, where a MSG_OPENDSM
* has been successfully performed...
*/
DSM_State CTwnDsmApps::AppGetState(TW_IDENTITY *_pAppId)
{
// Return the application specific state...
if (AppValidateId(_pAppId))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].CurrentState;
}
// Otherwise we must be in state 2. Good luck ever getting state 1
// out of the DSM... :)
else
{
return dsmState_Loaded;
}
}
/**
* Get number of allocated App slots (Last valid App ID +1)
* @return number of allocated App slots (Last valid App ID +1)
*/
TWID_T CTwnDsmApps::AppGetNumApp()
{
return m_ptwndsmappsimpl->m_AppInfo.size();
}
/**
* Get our hwnd.
* Windows needs this to help center the user select window...
*/
void *CTwnDsmApps::AppHwnd(TW_IDENTITY *_pAppId)
{
// Return the hwnd...
if (AppValidateId(_pAppId))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].hwnd;
}
// Otherwise return a null...
else
{
return 0;
}
}
/**
* Get the number of drivers found.
* When LoadDS() is called during AddApp() we browse for drivers and
* keep the identity for each one we find. This just tells us how
* many we found...
*/
TWID_T CTwnDsmApps::AppGetNumDs(TW_IDENTITY *_pAppId)
{
// Return the number of drivers we found...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList)
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles;
}
// Not a lot of choice, the value has to be zero...
else
{
return 0;
}
}
/**
* Get the identity for the specified driver.
* When LoadDS() is called during AddApp() we browse for drivers and
* keep the identity for each one we find. This just tells us how
* many we found...
*/
TW_IDENTITY *CTwnDsmApps::DsGetIdentity(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Return a pointer to the driver's identity...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return &m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].Identity;
}
// Something is toasted, so return NULL...
else
{
kLOG((kLOGERR,"Returning NULL from DsGetIdentity..."));
return NULL;
}
}
/**
* Get the DS_Entry function for the specified driver.
* Every driver has to have one of these...
*/
DSENTRYPROC CTwnDsmApps::DsGetEntryProc(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Return a pointer to the driver's identity...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].DS_Entry;
}
// Something is toasted, so return NULL...
else
{
kLOG((kLOGERR,"Returning NULL from DsGetEntryProc..."));
return NULL;
}
}
/**
* Get the full path and filename for the specified driver.
* We use this to uniquely identify each driver, since the ProductName
* is not guaranteed to be unique, though it should be...
*/
char *CTwnDsmApps::DsGetPath(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Return a pointer to the driver's file path and name...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].szPath;
}
// Something is toasted, so return NULL...
else
{
kLOG((kLOGERR,"Returning NULL from DsGetPath..."));
return NULL;
}
}
/**
* Get a point to the TW_CALLBACK2 for the specified driver.
* This is optional for drivers on Windows. On Linux it's the only
* way to use DAT_NULL to let an Application know about messages
* going from the Driver to the Application
*/
TW_CALLBACK2 *CTwnDsmApps::DsCallback2Get(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Return a pointer to the driver's TW_CALLBACK...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return &m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].twcallback2;
}
// Something is toasted, so return NULL...
else
{
kLOG((kLOGERR,"Returning NULL from DsCallbackGet..."));
return NULL;
}
}
/**
* Check if a callback is waiting.
* This allows the DSM to help a driver get its message to an
* application...
*/
TW_BOOL CTwnDsmApps::DsCallbackIsWaiting(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Check the waiting flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bCallbackPending;
}
// Something is toasted, so return FALSE...
else
{
kLOG((kLOGERR,"Returning FALSE from DsCallbackIsWaiting..."));
return FALSE;
}
}
/**
* Set the callback flag.
* This is how we know when we have something for the Application...
*/
void CTwnDsmApps::DsCallbackSetWaiting(TW_IDENTITY *_pAppId,
TWID_T _DsId,
TW_BOOL _Waiting)
{
// Set the waiting flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bCallbackPending = _Waiting;
}
// Something is toasted, so whine about it...
else
{
kLOG((kLOGERR,"Unable to properly handle DsCallbackSetWaiting..."));
}
}
/**
* Check if the DS is still processing last message.
* This allows the DSM to prevent DS from recieving a new message
* when they have not finished the current
*/
TW_BOOL CTwnDsmApps::DsIsProcessingMessage(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Check the waiting flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bDSProcessingMessage;
}
// Something is toasted, so return FALSE...
else
{
kLOG((kLOGERR,"Returning FALSE from DsIsProcessingMessage..."));
return FALSE;
}
}
/**
* Set the ProcessingMessage flag.
* This is how we know the DS is not done processing the previous message
*/
void CTwnDsmApps::DsSetProcessingMessage(TW_IDENTITY *_pAppId,
TWID_T _DsId,
TW_BOOL _Processing)
{
// Set the processing flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bDSProcessingMessage = _Processing;
}
// Something is toasted, so whine about it...
else
{
kLOG((kLOGERR,"Unable to properly handle DsSetProcessingMessage..."));
}
}
/**
* Check if the App is still processing last callback.
* This allows the DSM to prevent the app from calling the DS with a
* message before it returns from recieving the last callback.
*/
TW_BOOL CTwnDsmApps::DsIsAppProcessingCallback(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Check the waiting flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
return m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bAppProcessingCallback;
}
// Something is toasted, so return FALSE...
else
{
kLOG((kLOGERR,"Returning FALSE from DsIsAppProcessingCallback..."));
return FALSE;
}
}
/**
* Set the AppProcessingCallback flag.
* This is how we know the App is not done processing the previous callback
*/
void CTwnDsmApps::DsSetAppProcessingCallback(TW_IDENTITY *_pAppId,
TWID_T _DsId,
TW_BOOL _Processing)
{
// Set the processing flag...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].bAppProcessingCallback = _Processing;
}
// Something is toasted, so whine about it...
else
{
kLOG((kLOGERR,"Unable to properly handle DsSetAppProcessingCallback..."));
}
}
/**
* Turn a TWCC_ condition code into a string...
*/
const char *CTwnDsmAppsImpl::StringFromCC(const TW_UINT16 cc)
{
switch(cc)
{
case TWCC_SUCCESS:
return "TWRC_SUCCESS";
break;
case TWCC_BUMMER:
return "Failure due to unknown causes";
break;
case TWCC_LOWMEMORY:
return "Not enough memory to perform operation";
break;
case TWCC_NODS:
return "No Data Source";
break;
case TWCC_MAXCONNECTIONS:
return "DS is connected to max possible applications";
break;
case TWCC_OPERATIONERROR:
return "DS or DSM reported error, application shouldn't display an error";
break;
case TWCC_BADCAP:
return "Unknown capability";
break;
case TWCC_BADPROTOCOL:
return "Unrecognized MSG DG DAT combination";
break;
case TWCC_BADVALUE:
return "Data parameter out of range";
break;
case TWCC_SEQERROR:
return "DG DAT MSG out of expected sequence";
break;
case TWCC_BADDEST:
return "Unknown destination Application/Source in DSM_Entry";
break;
case TWCC_CAPUNSUPPORTED:
return "Capability not supported by source";
break;
case TWCC_CAPBADOPERATION:
return "Operation not supported by capability";
break;
case TWCC_CAPSEQERROR:
return "Capability has dependancy on other capability";
break;
case TWCC_DENIED:
return "File System operation is denied (file is protected)";
break;
case TWCC_FILEEXISTS:
return "Operation failed because file already exists.";
break;
case TWCC_FILENOTFOUND:
return "File not found";
break;
case TWCC_NOTEMPTY:
return "Operation failed because directory is not empty";
break;
case TWCC_PAPERJAM:
return "The feeder is jammed";
break;
case TWCC_PAPERDOUBLEFEED:
return "The feeder detected multiple pages";
break;
case TWCC_FILEWRITEERROR:
return "Error writing the file (meant for things like disk full conditions)";
break;
case TWCC_CHECKDEVICEONLINE:
return "The device went offline prior to or during this operation";
break;
}
static TW_STR32 hex;
SSNPRINTF((char*)hex, NCHARS(hex), 32, "TWCC 0x%04x", cc);
return (char*)hex;
}
/**
* Find all of the drivers.
* We recursively descend into the driver directory, looking for
* files with a .ds extension, opening them and getting their
* TW_IDENTITY. Which is why it's critical that drivers do as
* little as possible during this operation. It's easy to know
* when it's happening, because the application's TW_IDENTITY is
* empty, which is the hint that the DSM is browsing...
*/
int CTwnDsmAppsImpl::scanDSDir(char *_szAbsPath,
TW_IDENTITY *_pAppId)
{
// Validate...
if ( !_szAbsPath
|| !_pAppId)
{
return EXIT_FAILURE;
}
//
// Take care of VC++...
//
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
WIN32_FIND_DATA FileData; // Data structure describes the file found
HANDLE hSearch; // Search handle returned by FindFirstFile
char szABSFilename[FILENAME_MAX];
BOOL bFinished = FALSE;
char szPrevWorkDir[FILENAME_MAX];
// Start searching for .ds files in the root directory.
SSTRCPY(szABSFilename, NCHARS(szABSFilename), _szAbsPath);
SSTRCAT(szABSFilename, NCHARS(szABSFilename), "\\*.ds");
hSearch = FindFirstFile(szABSFilename,&FileData);
// If we find something, squirrel it away and anything else we find...
if (hSearch != INVALID_HANDLE_VALUE)
{
/* Save the current working directory: */
char *szResult = _getcwd( szPrevWorkDir, sizeof(szPrevWorkDir) );
if (szResult == (char*)NULL)
{
return EXIT_FAILURE;
}
int iResult = _chdir(_szAbsPath);
if (iResult != 0)
{
return EXIT_FAILURE;
}
while (!bFinished)
{
if (SSNPRINTF(szABSFilename, NCHARS(szABSFilename), FILENAME_MAX, "%s\\%s", _szAbsPath, FileData.cFileName) > 0)
{
if (TWRC_SUCCESS == LoadDS(_pAppId,
szABSFilename,
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles+1,
false))
{
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles++;
}
}
if (!FindNextFile(hSearch, &FileData))
{
bFinished = TRUE;
}
}
if (!FindClose (hSearch))
{
(void)_chdir( szPrevWorkDir );
return EXIT_FAILURE;
}
}
// Start searching sub directories.
SSTRCPY(szABSFilename, NCHARS(szABSFilename), _szAbsPath);
SSTRCAT(szABSFilename, NCHARS(szABSFilename), "\\*.*");
hSearch = FindFirstFile(szABSFilename, &FileData);
bFinished = FALSE;
if (hSearch == INVALID_HANDLE_VALUE)
{
(void)_chdir( szPrevWorkDir );
return EXIT_FAILURE;
}
while (!bFinished)
{
if ( (strcmp(".", FileData.cFileName) != 0)
&& (strcmp("..", FileData.cFileName) != 0)
&& (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
{
if (SSNPRINTF(szABSFilename, NCHARS(szABSFilename), FILENAME_MAX, "%s\\%s", _szAbsPath, FileData.cFileName) > 0)
{
scanDSDir(szABSFilename,_pAppId);
}
}
if (!FindNextFile(hSearch, &FileData))
{
bFinished = TRUE;
}
}
(void)_chdir( szPrevWorkDir );
if (!FindClose (hSearch))
{
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
//
// Take care of g++...
//
#elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP)
#if (TWNDSM_OS == TWNDSM_OS_MACOSX)
char szABSFilename[FILENAME_MAX];
DIR *pdir;
// Initialize...
pdir = 0;
memset(szABSFilename,0,sizeof(szABSFilename));
// Open the directory...
if ((pdir=opendir(_szAbsPath)) == 0)
{
perror("opendir");
return EXIT_FAILURE;
}
struct dirent *pfile;
while(errno=0, ((pfile=readdir(pdir)) != 0))
{
if ( (strcmp(".", pfile->d_name) == 0)
|| (strcmp("..", pfile->d_name) == 0) )
{
continue;
}
if (SNPRINTF(szABSFilename,FILENAME_MAX,"%s/%s",_szAbsPath,pfile->d_name) < 0)
{
continue;
}
struct stat st;
if (lstat(szABSFilename, &st) < 0)
{
perror("lstat");
continue;
}
if (S_ISDIR(st.st_mode) && (0 != strstr(pfile->d_name, ".ds")))
{
if (TWRC_SUCCESS == LoadDS(_pAppId,
szABSFilename,
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles+1,
false))
{
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles++;
}
}
}
if (0 != errno)
{
perror("readdir");
}
closedir(pdir);
return EXIT_SUCCESS;
#else
char szABSFilename[FILENAME_MAX];
DIR *pdir;
// Initialize...
pdir = 0;
memset(szABSFilename,0,sizeof(szABSFilename));
// Open the directory...
if ((pdir=opendir(_szAbsPath)) == 0)
{
perror("opendir");
return EXIT_FAILURE;
}
struct dirent *pfile;
while(errno=0, ((pfile=readdir(pdir)) != 0))
{
if ( (strcmp(".", pfile->d_name) == 0)
|| (strcmp("..", pfile->d_name) == 0) )
{
continue;
}
if (SNPRINTF(szABSFilename,FILENAME_MAX,"%s/%s",_szAbsPath,pfile->d_name) < 0)
{
continue;
}
struct stat st;
if (lstat(szABSFilename, &st) < 0)
{
perror("lstat");
continue;
}
if (S_ISDIR(st.st_mode))
{
scanDSDir(szABSFilename,_pAppId);
}
else if (S_ISREG(st.st_mode) && (0 != strstr(pfile->d_name, ".ds")))
{
if (TWRC_SUCCESS == LoadDS(_pAppId,
szABSFilename,
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles+1,
false))
{
m_AppInfo[(TWID_T)_pAppId->Id].pDSList->NumFiles++;
}
}
}
if (0 != errno)
{
perror("readdir");
}
closedir(pdir);
return EXIT_SUCCESS;
#endif
//
// meh!
//
#else
#error Sorry, we do not recognize this system...
#endif
}
/**
* Load a specific driver.
* This is the interface function that's called by CTwnDsm
*/
TW_INT16 CTwnDsmApps::LoadDS(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
// Load the specified driver...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS))
{
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
// Make the DS directory the current directoy while we load the DS so that any DLLs that
// are loaded with the DS can be found.
char *szResult;
char szPrevWorkDir[FILENAME_MAX];
char szWorkDir[FILENAME_MAX];
SSTRCPY(szWorkDir, NCHARS(szWorkDir), m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].szPath);
// strip filename from path
size_t x = strlen(szWorkDir);
while(x > 0)
{
if(PATH_SEPERATOR == szWorkDir[x-1])
{
szWorkDir[x-1] = 0;
break;
}
--x;
}
/* Save the current working directory: */
memset( szPrevWorkDir, 0, NCHARS(szPrevWorkDir) );
szResult = _getcwd( szPrevWorkDir, NCHARS(szPrevWorkDir) );
if (!szResult)
{
kLOG((kLOGERR, "_getcwd failed..."));
}
(void)_chdir( szWorkDir );
#endif
TW_INT16 result = m_ptwndsmappsimpl->LoadDS(_pAppId,
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].szPath,
_DsId,
true);
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
if(0!=strlen(szPrevWorkDir))
{
(void)_chdir( szPrevWorkDir );
}
#endif
return result;
}
// Something is toasted, so return LoadDS...
else
{
kLOG((kLOGERR,"Returning TWRC_FAILURE from LoadDS..."));
return TWRC_FAILURE;
}
}
/**
* Load a driver.
* This is the implementation function. We use this both to browse
* for drivers during MSG_GETFIRST/MSG_GETNEXT and to load a specific
* driver during MSG_OPENDS. Which is why we need the path and the
* keep open flag...
*/
TW_INT16 CTwnDsmAppsImpl::LoadDS(TW_IDENTITY *_pAppId,
char *_pPath,
TWID_T _DsId,
bool _boolKeepOpen)
{
TW_INT16 result = TWRC_SUCCESS;
DS_INFO *pDSInfo;
bool hook;
TW_IDENTITY_LINUX64SAFE twidentitylinux64safe;
char szUseAppid[8];
// Validate...
if ( 0 == _pPath )
{
// bad path
kLOG((kLOGERR,"bad path."));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
if ( _DsId >= MAX_NUM_DS )
{
// too many DS's already open
kLOG((kLOGINFO,"Too many DS's already open."));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
// For Linux we only support the native architecture, so if
// a 32-bit process tries to run on an x86_64 system we expect
// it to fail. However, we can't rule out that somebody
// might try to make this work. So we'll check the file...
#if (TWNDSM_OS == TWNDSM_OS_LINUX)
(void)hook;
bool blSuccess = false;
char szData[2048] = { 0 };
snprintf(szData, sizeof(szData), "file \"%s\"", _pPath);
FILE *pf = popen(szData, "r");
if (pf)
{
szData[0] = 0;
size_t sizet = fread(szData, 1, sizeof(szData), pf);
szData[sizet] = 0;
#if (TWNDSM_OS_64BIT == 1)
blSuccess = strstr(szData, "x86-64") || strstr(szData, "MIPS64") || strstr(szData, "aarch64");
#else
blSuccess = strstr(szData, "Intel 80386");
#endif
pclose(pf);
pf = 0;
}
if (!blSuccess)
{
kLOG((kLOGINFO, "driver doesn't support architecture: %s <%s>", _pPath, szData));
AppSetConditionCode(_pAppId, TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#endif
// Mac has universal binaries which may or may not have what we
// need. For instance, we can't load an image that only has
// i386 content if we're running in an x86_64 process. So we're
// using file(1) to filter content...
#if (TWNDSM_OS == TWNDSM_OS_MACOSX)
bool blSuccess = false;
char szData[2048] = { 0 };
snprintf(szData, sizeof(szData), "file \"%s/Contents/MacOS/$(/usr/libexec/PlistBuddy -c 'Print CFBundleExecutable' '%s/Contents/Info.plist')\"", _pPath, _pPath);
FILE *pf = popen(szData, "r");
if (pf)
{
szData[0] = 0;
size_t sizet = fread(szData, 1, sizeof(szData), pf);
szData[sizet] = 0;
#if (TWNDSM_OS_64BIT == 1)
blSuccess = (strstr(szData, "x86_64") != 0);
#else
blSuccess = (strstr(szData, "i386") != 0);
#endif
pclose(pf);
pf = 0;
}
if (!blSuccess)
{
kLOG((kLOGINFO, "driver doesn't support architecture: %s <%s>", _pPath, szData));
AppSetConditionCode(_pAppId, TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#endif
// Initialize stuff...
pDSInfo = &m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId];
// Only log DS details when processing a MSG_OPENDS message
if(_boolKeepOpen)
{
kLOG((kLOGINFO,"Datasource: \"%0.32s\"", pDSInfo->Identity.Manufacturer));
kLOG((kLOGINFO," \"%0.32s\"", pDSInfo->Identity.ProductFamily));
kLOG((kLOGINFO," \"%0.32s\" version: %u.%u", pDSInfo->Identity.ProductName, pDSInfo->Identity.Version.MajorNum, pDSInfo->Identity.Version.MinorNum));
kLOG((kLOGINFO," TWAIN %u.%u", pDSInfo->Identity.ProtocolMajor, pDSInfo->Identity.ProtocolMinor));
}
// Only hook this driver if we've been asked to keep the driver
// open (meaning we're processing a MSG_OPENDS) and if we see
// that the driver is 1.x...(by checking the absence of DF_DS2)
hook = _boolKeepOpen && !(pDSInfo->Identity.SupportedGroups & DF_DS2);
// Try to load the driver... We load the driver again if we are keeping
// it open. This LoadLibrary is always closed so we dont hook this time.
pDSInfo->pHandle = (TW_HANDLE)LOADLIBRARY(_pPath,false,0);
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
if (0 == pDSInfo->pHandle)
{
kLOG((kLOGERR,"Could not load library: %s",_pPath));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP)
if (0 == pDSInfo->pHandle)
{
// This is a bit skanky, and not the sort of thing I really want
// a user to have to see, but more info is better than less, so
// hopefully someone will be able to sort out what the cryptic
// message means and we can FAQ it...
fprintf(stderr,">>> error loading <%s>\r\n",_pPath);
fprintf(stderr,">>> %s\r\n",dlerror());
fprintf(stderr,">>> please contact your scanner or driver vendor for more\r\n");
fprintf(stderr,">>> help, if that doesn't help then check out the FAQ at\r\n");
fprintf(stderr,">>> http://www.twain.org\r\n");
kLOG((kLOGERR,"Could not load library: %s",_pPath));
kLOG((kLOGERR,dlerror()));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#else
#error Sorry, we do not recognize this system...
#endif
// Try to get the entry point...
pDSInfo->DS_Entry = (DSENTRYPROC)DSM_LoadFunction(pDSInfo->pHandle,"DS_Entry");
if (pDSInfo->DS_Entry == 0)
{
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
// The WIATwain.ds does not have an entry point
if (0 != strstr(_pPath, "wiatwain.ds"))
{
kLOG((kLOGERR, "We're deliberately skipping this file: %s", _pPath));
}
else
{
pDSInfo->DS_Entry = (DSENTRYPROC)GetProcAddress((HMODULE)pDSInfo->pHandle, MAKEINTRESOURCE(1));
if (pDSInfo->DS_Entry == 0)
{
kLOG((kLOGINFO, "Could not find Entry 1 in DS: %s", _pPath));
}
}
#else
kLOG((kLOGERR, "Could not find DS_Entry function in DS: %s", _pPath));
#endif
if (pDSInfo->DS_Entry == 0)
{
(void)UNLOADLIBRARY(pDSInfo->pHandle, false, 0);
pDSInfo->pHandle = NULL;
AppSetConditionCode(_pAppId, TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
}
// Allllrighty then! So the original TWAIN_32.DLL passes in
// a value of NULL for the origin. This is not documented
// anywhere in the TWAIN Spec. It was decided to maintain this
// behavior in TWAINDSM.DLL. All fine and well for Window and
// Linux. But Mac had it's own DSM, and it didn't pass in a
// NULL. So now we have a conundrum.
//
// I'm adding an event variable so that an application can
// override stuff, but the default behavior is going to be:
// Windows - NULL
// Linux - _pAppId
// Mac - _pAppId
memset(&szUseAppid, 0, sizeof(szUseAppid));
SGETENV(szUseAppid, NCHARS(szUseAppid), "TWAINDSM_USEAPPID");
// No data received, set the default based on the platform...
if (szUseAppid[0] != 0)
{
#if (TWNDSM_OS == TWNDSM_OS_WINDOWS)
szUseAppid[0] = '0'; // Windows is NULL
#elif (TWNDSM_OS == TWNDSM_OS_LINUX)
szUseAppid[0] = '1'; // Linux is _pAppId
#elif (TWNDSM_OS == TWNDSM_OS_MACOSX)
szUseAppid[0] = '1'; // Linux is _pAppId
#else
Unsupported...
#endif
}
// Otherwise, force the value to be '0' or '1'...
else if (szUseAppid[0] != '0')
{
szUseAppid[0] = '1';
}
// Report success and squirrel away the index...
kLOG((kLOGINFO, "Loaded library: %s (TWAINDSM_USEAPPID:%c)", _pPath, szUseAppid[0]));
pDSInfo->Identity.Id = (TWIDDEST_T)_DsId;
// Get the source to fill in the identity structure
// This operation should never fail on any DS
//
// We need the NULL to be backwards compatible with the
// older DSM. This is the only way a driver can tell if
// it's being talked to directly by the DSM instead of
// by the application (with the DSM as a passthru).
//
// Okay, this is where we make the actual call. I left
// the original comments in place...
memset(&twidentitylinux64safe, 0, sizeof(twidentitylinux64safe));
twidentitylinux64safe.twidentity.Id = (TWIDDEST_T)_DsId;
if (szUseAppid[0] == '1')
{
// this is what the spec calls for
result = pDSInfo->DS_Entry(_pAppId, DG_CONTROL, DAT_IDENTITY, MSG_GET, (TW_MEMREF)&twidentitylinux64safe);
}
else
{
// this is out of spec, but we need it for Windows
result = pDSInfo->DS_Entry(NULL, DG_CONTROL, DAT_IDENTITY, MSG_GET, (TW_MEMREF)&twidentitylinux64safe);
}
if (result != TWRC_SUCCESS)
{
(void)UNLOADLIBRARY(pDSInfo->pHandle,false,0);
pDSInfo->pHandle = NULL;
pDSInfo->DS_Entry = NULL;
kLOG((kLOGINFO, "DG_CONTROL,DAT_IDENTITY,MSG_GET failed"));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
// We're going to do a sanity check on the data if we
// are running on Linux as a 64-bit process. This is
// because we messed up the definition of TW_INT32 and
// TW_UINT32, making them 64-bit values (based on long)
// rather than 32-bit values (based on int). Starting
// with TWAIN 2.4 this is fixed, but we have to be able
// to handle old drivers. These will be in trouble
// because their Id and SupportedGroups will be 64-bit,
// shifting data in the structure.
//
// This is a heuristic, meaning that it's possible to
// get it wrong. Add as many checks as possible. All
// TWAIN drivers must support DG_CONTROL and DG_IMAGE,
// and we're going to validate a whole mess of protocol
// versions...
#if (TWNDSM_OS == TWNDSM_OS_LINUX) && (TWNDSM_OS_64BIT == 1)
if ( ((twidentitylinux64safe.twidentity.SupportedGroups & (DG_CONTROL | DG_IMAGE)) == (DG_CONTROL | DG_IMAGE))
&& (((twidentitylinux64safe.twidentity.ProtocolMajor >= 3) && (twidentitylinux64safe.twidentity.ProtocolMinor <= 9))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 2) && (twidentitylinux64safe.twidentity.ProtocolMinor >= 4) && (twidentitylinux64safe.twidentity.ProtocolMinor <= 9))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 5))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 6))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 7))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 8))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 9))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 1) && (twidentitylinux64safe.twidentity.ProtocolMinor == 91))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 2) && (twidentitylinux64safe.twidentity.ProtocolMinor == 0))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 2) && (twidentitylinux64safe.twidentity.ProtocolMinor == 1))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 2) && (twidentitylinux64safe.twidentity.ProtocolMinor == 2))
|| ((twidentitylinux64safe.twidentity.ProtocolMajor == 2) && (twidentitylinux64safe.twidentity.ProtocolMinor == 3))))
{
// We're good, keep going...
}
else
{
(void)UNLOADLIBRARY(pDSInfo->pHandle,false,0);
pDSInfo->pHandle = NULL;
pDSInfo->DS_Entry = NULL;
kLOG((kLOGINFO,"DG_CONTROL,DAT_IDENTITY,MSG_GET failed (rejected as old 64-bit TW_INT32/TW_UINT32)"));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#endif
// Okay, we can keep this TW_IDENTITY, so copy it over,
// but be careful to use the TW_IDENTITY size...
memcpy(&pDSInfo->Identity, &twidentitylinux64safe.twidentity, sizeof(pDSInfo->Identity));
// Compare the supported groups. Note that the & is correct
// because we are comparing bits...
// we do not want to compare DG_CONTROL because is it supported by all
if ( !( (_pAppId->SupportedGroups & DG_MASK & ~DG_CONTROL) // app supports
& (pDSInfo->Identity.SupportedGroups & DG_MASK & ~DG_CONTROL) ) ) // source supports
{
(void)UNLOADLIBRARY(pDSInfo->pHandle,false,0);
pDSInfo->pHandle = NULL;
pDSInfo->DS_Entry = NULL;
kLOG((kLOGINFO,"The SupportedGroups do not match."));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
// The DS should not modify the Id even though the spec states
// that the id will not be assigned until DSM sends MSG_OPENDS to DS, and
// by the way...don't do the copy of the src and dst are the same address...
pDSInfo->Identity.Id = (TWIDDEST_T)_DsId;
if (pDSInfo->szPath != _pPath)
{
SSTRNCPY(pDSInfo->szPath, NCHARS(pDSInfo->szPath),_pPath,FILENAME_MAX);
}
// We clear the library to avoid cluttering up the virtual address space, and
// to prevent scary weirdness that can result from multiple drivers being
// loaded (if the application wants to load multiple drivers, that's its risk).
(void)UNLOADLIBRARY(pDSInfo->pHandle,false,0);
pDSInfo->pHandle = NULL;
pDSInfo->DS_Entry = NULL;
// At this point you're probably scratching your head. Here's the deal.
// When the DSM issues DG_CONTROL/DAT_IDENTITY/MSG_GET without an
// AppIdentity structure it alerts the driver that it's being called by
// the DSM and not by the application, most likely to bring up the user
// selection dialog. A driver should use this information to create --
// and more importantly -- to destroy its internal data structures,
// because it will get no other chance to clean itself up.
//
// It's worth interjecting at this point that Microsoft warns against
// any but the most minimal activity in DllMain, so relying on doing
// the create/destroy in there is very risky. The same goes for the
// __attribute(constructor)/__attribute(destructor) with GNU.
//
// The problem is that the DSM issues DG_CONTROL/DAT_IDENTITY/MSG_GET
// just prior to DG_CONTROL/DAT_IDENTITY/MSG_OPEN. If a driver is keyed
// to the AppIdentity being NULL, it'll incorrectly clean itself up.
//
// This means we need to unload and reload the library, to give the
// driver a consistent look.
if (_boolKeepOpen == true)
{
pDSInfo->pHandle = (TW_HANDLE)LOADLIBRARY(_pPath,hook,_DsId);
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
if (0 == pDSInfo->pHandle)
{
kLOG((kLOGERR,"Could not load library: %s",_pPath));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP)
if (0 == pDSInfo->pHandle)
{
// This is a bit skanky, and not the sort of thing I really want
// a user to have to see, but more info is better than less, so
// hopefully someone will be able to sort out what the cryptic
// message means and we can FAQ it...
fprintf(stderr,">>> error loading <%s>\r\n",_pPath);
fprintf(stderr,">>> %s\r\n",dlerror());
fprintf(stderr,">>> please contact your scanner or driver vendor for more\r\n");
fprintf(stderr,">>> help, if that doesn't help then check out the FAQ at\r\n");
fprintf(stderr,">>> http://www.twain.org\r\n");
kLOG((kLOGERR,"Could not load library: %s",_pPath));
kLOG((kLOGERR,dlerror()));
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
#else
#error Sorry, we do not recognize this system...
#endif
// Try to get the entry point...
pDSInfo->DS_Entry = (DSENTRYPROC)DSM_LoadFunction(pDSInfo->pHandle,"DS_Entry");
if (pDSInfo->DS_Entry == 0)
{
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
// The WIATwain.ds does not have an entry point
if(0 != strstr(_pPath, "wiatwain.ds"))
{
kLOG((kLOGERR,"We're deliberately skipping this file: %s",_pPath));
}
else
{
pDSInfo->DS_Entry = (DSENTRYPROC)GetProcAddress((HMODULE)pDSInfo->pHandle, MAKEINTRESOURCE(1));
if (pDSInfo->DS_Entry == 0)
{
kLOG((kLOGINFO,"Could not find Entry 1 in DS: %s",_pPath));
}
}
#else
kLOG((kLOGERR,"Could not find DS_Entry function in DS: %s",_pPath));
#endif
if (pDSInfo->DS_Entry == 0)
{
(void)UNLOADLIBRARY(pDSInfo->pHandle,false,0);
pDSInfo->pHandle = NULL;
AppSetConditionCode(_pAppId,TWCC_OPERATIONERROR);
return TWRC_FAILURE;
}
}
}
// All done...
return result;
}
/**
* Unload a specific driver.
* I don't care if the called sends this function a bouquet of pink
* bunnies, I think that close type functions shouldn't fail (unless,
* of course they're doing something vital, in which case that
* vital activity shouldn't be in the close function in the first
* place -- so there)...
*/
void CTwnDsmApps::UnloadDS(TW_IDENTITY *_pAppId,
TWID_T _DsId)
{
int retval = 0;
// Unload the specified driver...
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList
&& (_DsId < MAX_NUM_DS)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].pHandle)
{
// Unload the library...
retval = UNLOADLIBRARY(m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].pHandle,true,_DsId);
// Log if something bad happens...
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
if(0 == retval)
{
kLOG((kLOGERR,"failed to unload datasource"));
}
#else
if(0 != retval)
{
kLOG((kLOGERR,"dlclose: %s",dlerror()));
}
#endif
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].DS_Entry = 0;
m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].pDSList->DSInfo[_DsId].pHandle = 0;
}
}
/**
* Wakeup an application.
* We need this in Windows when we send DAT_NULL to an application, otherwise
* it'll sit there like a lump on a bog until an event comes along to wake it
* up so it can process the message.
*/
void CTwnDsmApps::AppWakeup(TW_IDENTITY *_pAppId)
{
#if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP)
BOOL boolResult;
if ( AppValidateId(_pAppId)
&& m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].hwnd)
{
// Force the parent to process a message. WM_NULL is
// safe, because it's a no-op...
boolResult = ::PostMessage(m_ptwndsmappsimpl->m_AppInfo[(TWID_T)_pAppId->Id].hwnd,WM_NULL,(WPARAM)0,(LPARAM)0);
if (!boolResult)
{
kLOG((kLOGERR,"PostMessage failed..."));
}
}
#elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP)
kLOG((kLOGERR,"We shouldn't be here in AppWakeup..."));
// We don't support this path on this platform, use
// callbacks instead...
// Make the compiler happy...
void *unused = _pAppId;
unused = 0;
(void)unused;
#else
#error Sorry, we do not recognize this system...
#endif
}