/*************************************************************************** * 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; AppIDm_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 }