/*++ Copyright (C) Microsoft Corporation, 1995 - 1999 All rights reserved. Module Name: detect.cxx Abstract: PnP printer autodetection. Author: Lazar Ivanov (LazarI) May-06-1999 Revision History: May-06-1999 - Created. Larry Zhu (LZhu) Mar-12-2000 - Rewrote PnP detection code. --*/ #include "precomp.hxx" #pragma hdrstop #include "detect.hxx" #include #include // for GUID_PARALLEL_DEVICE #include #if DBG #define DBGCHKMSG(bShowMessage, MsgAndArgs) \ { \ if (bShowMessage) \ { \ DBGMSG(DBG_ERROR, MsgAndArgs); \ } \ } \ #else // DBG #define DBGCHKMSG(bShowMessage, MsgAndArgs) #endif // DBG /******************************************************************** PnP private stuff ********************************************************************/ // // current 1394 printers are enumerated under LPTENUM // #define szParallelClassEnumerator TEXT("LPTENUM") #define szParallelDot4PrintClassEnumerator TEXT("DOT4PRT") #define szUsbPrintClassEnumerator TEXT("USBPRINT") #define szInfraRedPrintClassEnumerator TEXT("IRENUM") // This timeout is per recommendation of the PnP guys // 1 second should be enough #define DELAY_KICKOFF_TIMEOUT 1000 extern "C" { // // config mgr privates // DWORD CMP_WaitNoPendingInstallEvents( IN DWORD dwTimeout ); } /******************************************************************** Global functions ********************************************************************/ BOOL PnP_Enum_KickOff( VOID ) /*++ Routine Description: Kicks off the PNP enumeration event. Arguments: None Return Value: TRUE - on success FALSE - if a failure occurs Notes: --*/ { DWORD crStatus = CR_DEFAULT; DEVINST devRoot = {0}; crStatus = CM_Locate_DevNode(&devRoot, NULL, CM_LOCATE_DEVNODE_NORMAL); DBGCHKMSG((CR_SUCCESS != crStatus), ("CM_Locate_Devnode failed with 0x%x\n", crStatus)); if (CR_SUCCESS == crStatus) { crStatus = CM_Reenumerate_DevNode(devRoot, CM_REENUMERATE_RETRY_INSTALLATION); DBGCHKMSG((CR_SUCCESS != crStatus), ("CM_Reenumerate_DevNode failed with 0x%x\n", crStatus)); } // // There is no point to return CR_XXX code // SetLastError(CR_SUCCESS == crStatus ? ERROR_SUCCESS : ERROR_INVALID_DATA); return CR_SUCCESS == crStatus; } VOID PnP_Enumerate_Sync( VOID ) /*++ Routine Description: Enumerates the LPT devices sync. Arguments: None Return Value: None Notes: --*/ { // // Kick off PnP enumeration // if( PnP_Enum_KickOff( ) ) { for( ;; ) { // // We must wait about 1 sec (DELAY_KICKOFF_TIMEOUT) to make sure the enumeration // event has been kicked off completely, so we can wait successfully use the // CMP_WaitNoPendingInstallEvents() private API. This is by LonnyM recommendation. // This delay also solves the problem with multiple installs like DOT4 printers. // Sleep( DELAY_KICKOFF_TIMEOUT ); // // Check to see if there are pending install events // if( WAIT_TIMEOUT != CMP_WaitNoPendingInstallEvents( 0 ) ) break; // // Wait untill no more pending install events // CMP_WaitNoPendingInstallEvents( INFINITE ); } } } /******************************************************************** TPnPDetect - PnP printer detector class ********************************************************************/ TPnPDetect:: TPnPDetect( VOID ) : _bDetectionInProgress( FALSE ), _pInfo4Before( NULL ), _cInfo4Before( 0 ), _hEventDone( NULL ) /*++ Routine Description: TPnPDetect constructor Arguments: None Return Value: None Notes: --*/ { } TPnPDetect:: ~TPnPDetect( VOID ) /*++ Routine Description: TPnPDetect destructor Arguments: None Return Value: None Notes: --*/ { // // Check to free memory // Reset( ); } typedef bool PI4_less_type(const PRINTER_INFO_4 p1, const PRINTER_INFO_4 p2); static bool PI4_less(const PRINTER_INFO_4 p1, const PRINTER_INFO_4 p2) { return (lstrcmp(p1.pPrinterName, p2.pPrinterName) < 0); } BOOL TPnPDetect:: bKickOffPnPEnumeration( VOID ) /*++ Routine Description: Kicks off PnP enumeration event on the LPT ports. Arguments: None Return Value: TRUE - on success FALSE - if a failure occurs Notes: --*/ { DWORD cbInfo4Before = 0; TStatusB bStatus; bStatus DBGNOCHK = FALSE; // // Check to free memory // Reset( ); // // Enumerate the printers before PnP enumeration // bStatus DBGCHK = VDataRefresh::bEnumPrinters( PRINTER_ENUM_LOCAL, NULL, 4, reinterpret_cast( &_pInfo4Before ), &cbInfo4Before, &_cInfo4Before ); if( bStatus ) { // // Sort out the PRINTER_INFO_4 structures here // std::sort( _pInfo4Before, _pInfo4Before + _cInfo4Before, PI4_less); // // Kick off the PnP enumeration // _hEventDone = CreateEvent( NULL, TRUE, FALSE, NULL ); if( _hEventDone ) { HANDLE hEventOut = NULL; const HANDLE hCurrentProcess = GetCurrentProcess(); bStatus DBGCHK = DuplicateHandle( hCurrentProcess, _hEventDone, hCurrentProcess, &hEventOut, 0, TRUE, DUPLICATE_SAME_ACCESS ); if( bStatus ) { // // Kick off the enumeration thread. // DWORD dwThreadId; HANDLE hThread = TSafeThread::Create( NULL, 0, (LPTHREAD_START_ROUTINE)TPnPDetect::EnumThreadProc, hEventOut, 0, &dwThreadId ); if( hThread ) { // // We don't need the thread handle any more. // CloseHandle( hThread ); // // Detection initiated successfully. // _bDetectionInProgress = TRUE; } else { // // Failed to create the thread. Close the event. // CloseHandle( hEventOut ); } } } // // Check to free the allocated resources if something has failed. // if( !_bDetectionInProgress ) { Reset(); } } return bStatus; } BOOL TPnPDetect:: bDetectionInProgress( VOID ) /*++ Routine Description: Checks whether there is pending detection/installation process. Arguments: None Return Value: TRUE - yes there is pending detect/install process. FALSE - otherwise. Notes: --*/ { return _bDetectionInProgress; } BOOL TPnPDetect:: bFinished( DWORD dwTimeout ) /*++ Routine Description: Checks whether there is no more pending detect/install events after the last PnP enumeration event. Arguments: dwTimeout - Time to wait. By default is zero (no wait - just ping) Return Value: TRUE - on success FALSE - if a failure occurs Notes: --*/ { // // Check to see if the enum process has been kicked off at all. // if( _bDetectionInProgress && _pInfo4Before && _hEventDone ) { // // Ping to see whether the PnP detect/install process has finished // if( WAIT_OBJECT_0 == WaitForSingleObject( _hEventDone, 0 ) ) { _bDetectionInProgress = FALSE; } } // // We must check for all the three conditions below as _bDetectionInProgress // may be FALSE, but the enum process has never kicked off. We want to make // sure we are in the case _bDetectionInProgress is set to FALSE after the enum // process is finished. // return ( !_bDetectionInProgress && _pInfo4Before && _hEventDone ); } BOOL TPnPDetect:: bGetDetectedPrinterName( TString *pstrPrinterName ) /*++ Routine Description: Enum the printers after the PnP enumeration has finished to check whether new local (LPT) printers have been detected. Arguments: None Return Value: TRUE - on success FALSE - if a failure occurs Notes: --*/ { TStatusB bStatus; bStatus DBGNOCHK = FALSE; if( pstrPrinterName && !_bDetectionInProgress && _pInfo4Before ) { // // The detection is done here. Enum the printers after the PnP // detect/install to see whether new printer has been detected. // PRINTER_INFO_4 *pInfo4After = NULL; DWORD cInfo4After = 0; DWORD cbInfo4After = 0; bStatus DBGCHK = VDataRefresh::bEnumPrinters( PRINTER_ENUM_LOCAL, NULL, 4, reinterpret_cast( &pInfo4After ), &cbInfo4After, &cInfo4After ); if( bStatus && cInfo4After > _cInfo4Before ) { for( UINT uAfter = 0; uAfter < cInfo4After; uAfter++ ) { if( !std::binary_search( _pInfo4Before, _pInfo4Before + _cInfo4Before, pInfo4After[uAfter], PI4_less) ) { // // This printer hasn't been found in the before list // so it must be the new printer detected. I know this is partial // solution because we are not considering the case we detect more // than one local PnP printer, but it is not our intention for now. // bStatus DBGCHK = pstrPrinterName->bUpdate( pInfo4After[uAfter].pPrinterName ); break; } } } else { bStatus DBGNOCHK = FALSE; } FreeMem( pInfo4After ); } return bStatus; } DWORD WINAPI TPnPDetect:: EnumThreadProc( LPVOID lpParameter ) /*++ Routine Description: Invokes the PnP enumeration routine Arguments: lpParameter - Standard (see MSDN) Return Value: Standard (see MSDN) Notes: --*/ { DWORD dwStatus = EXIT_FAILURE; HANDLE hEventDone = (HANDLE )lpParameter; dwStatus = hEventDone ? ERROR_SUCCESS : EXIT_FAILURE; // // If there is a NULL driver (meaning no driver) associated with a devnode, // we want to set CONFIGFLAG_FINISH_INSTALL flag so that PnP manager will // try to reinstall a driver for this device. // if (ERROR_SUCCESS == dwStatus) { dwStatus = ProcessDevNodesWithNullDriversAll(); } // // Invokes the enumeration routine. It executes syncroniously. // if (ERROR_SUCCESS == dwStatus) { (void)PnP_Enumerate_Sync(); } // // Notify the client we are done. // if (hEventDone) { SetEvent( hEventDone ); // // We own the event handle, so we must close it now. // CloseHandle( hEventDone ); } return dwStatus; } DWORD WINAPI TPnPDetect:: ProcessDevNodesWithNullDriversForOneEnumerator( IN PCTSTR pszEnumerator ) /*++ Routine Description: This routine enumerates devnodes of one enumerator and it sets CONFIGFLAG_FINISH_INSTALL for devnode that has no driver (or NULL driver) so that this device will be re-detected by PnP manager. Arguments: pszEnumerator - The enumerator for a particular bus, this can be "LPTENUM", "USBPRINT", "DOT4PRT" etc. Return Value: Standard (see MSDN) Notes: */ { DWORD dwStatus = ERROR_SUCCESS; HDEVINFO hDevInfoSet = INVALID_HANDLE_VALUE; TCHAR buffer[MAX_PATH] = {0}; DWORD dwDataType = REG_NONE; SP_DEVINFO_DATA devInfoData = {0}; DWORD cbRequiredSize = 0; DWORD dwConfigFlags = 0; hDevInfoSet = SetupDiGetClassDevs(NULL, pszEnumerator, NULL, DIGCF_ALLCLASSES); dwStatus = (INVALID_HANDLE_VALUE != hDevInfoSet) ? ERROR_SUCCESS : GetLastError(); if (ERROR_SUCCESS == dwStatus) { devInfoData.cbSize = sizeof(devInfoData); for (DWORD cDevIndex = 0; ERROR_SUCCESS == dwStatus; cDevIndex++) { dwStatus = SetupDiEnumDeviceInfo(hDevInfoSet, cDevIndex, &devInfoData) ? ERROR_SUCCESS : GetLastError(); // // When SPDRP_DRIVER is not present, this devnode is associated // with no driver or, in the other word, NULL driver // // For devnodes with NULL drivers, we will set CONFIGFLAG_FINISH_INSTALL // so that the device will be re-detected // // Notes on the error code: internally SetupDiGetDeviceRegistryProperty // first returns CR_NO_SUCH_VALUE, but this error code is remapped // to ERROR_INVALID_DATA by setupapi // if (ERROR_SUCCESS == dwStatus) { dwStatus = SetupDiGetDeviceRegistryProperty(hDevInfoSet, &devInfoData, SPDRP_DRIVER, &dwDataType, reinterpret_cast(buffer), sizeof(buffer), &cbRequiredSize) ? ERROR_SUCCESS : GetLastError(); if (ERROR_INVALID_DATA == dwStatus) { dwConfigFlags = 0; dwStatus = SetupDiGetDeviceRegistryProperty(hDevInfoSet, &devInfoData, SPDRP_CONFIGFLAGS, &dwDataType, reinterpret_cast(&dwConfigFlags), sizeof(dwConfigFlags), &cbRequiredSize) ? (REG_DWORD == dwDataType ? ERROR_SUCCESS : ERROR_INVALID_PARAMETER) : GetLastError(); if ((ERROR_SUCCESS == dwStatus) && (!(dwConfigFlags & CONFIGFLAG_FINISH_INSTALL))) { dwConfigFlags |= CONFIGFLAG_FINISH_INSTALL; dwStatus = SetupDiSetDeviceRegistryProperty(hDevInfoSet, &devInfoData, SPDRP_CONFIGFLAGS, reinterpret_cast(&dwConfigFlags), sizeof(dwConfigFlags)) ? ERROR_SUCCESS : GetLastError(); } } } } dwStatus = ERROR_NO_MORE_ITEMS == dwStatus ? ERROR_SUCCESS : dwStatus; } else { dwStatus = ERROR_INVALID_DATA == dwStatus ? ERROR_SUCCESS : dwStatus; } if (INVALID_HANDLE_VALUE != hDevInfoSet) { SetupDiDestroyDeviceInfoList(hDevInfoSet); } return dwStatus; } DWORD WINAPI TPnPDetect:: ProcessDevNodesWithNullDriversAll( VOID ) /*++ Routine Description: This routine enumerates a subset of devnodes and it sets CONFIGFLAG_FINISH_INSTALL for devnodes that have no driver (or NULL driver) so that these devices will be re-detected by PnP manager. Arguments: None Return Value: Standard (see MSDN) Notes: */ { DWORD dwStatus = ERROR_SUCCESS; // // The following enumerators can have printers attached // static PCTSTR acszEnumerators[] = { szParallelClassEnumerator, szParallelDot4PrintClassEnumerator, szUsbPrintClassEnumerator, szInfraRedPrintClassEnumerator, }; for (UINT i = 0; (ERROR_SUCCESS == dwStatus) && (i < COUNTOF(acszEnumerators)); i++) { dwStatus = ProcessDevNodesWithNullDriversForOneEnumerator(acszEnumerators[i]); } return dwStatus; } VOID TPnPDetect:: Reset( VOID ) /*++ Routine Description: Resets the PnP detector class, so you can kick off the PnP enumeration event again. Release the memory allocated from calling EnumPrinters() before kicking off PnP enum. Arguments: None Return Value: None Notes: --*/ { if( _hEventDone ) { CloseHandle( _hEventDone ); _hEventDone = NULL; } if( _pInfo4Before ) { FreeMem( _pInfo4Before ); _pInfo4Before = NULL; } _bDetectionInProgress = FALSE; }