// Printer.cpp: implementation of the CPrinter class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Printer.h" #include "wininet.h" static const DWORD BIT_IGNORED_JOB = (JOB_STATUS_PAUSED | JOB_STATUS_PRINTED | JOB_STATUS_DELETING | JOB_STATUS_OFFLINE | JOB_STATUS_SPOOLING); static const DWORD BIT_ERROR_JOB = (JOB_STATUS_ERROR | JOB_STATUS_PAPEROUT); static const DWORD CHAR_PER_PAGE = 4800; static const DWORD PAGEPERJOB = 1; static const DWORD BYTEPERJOB = 2; #ifdef WIN9X #error This code requires DRIVER_INFO_6 which is not availabe under Win9X #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CPrinter::CPrinter(): m_hPrinter(NULL), m_pInfo2(NULL), m_pInfo4(NULL), m_pDriverInfo6(NULL), m_bCalcJobETA(NULL), m_pszUrlBuffer(NULL), m_pszOemUrl (NULL), m_pszManufacturer (NULL) { } CPrinter::~CPrinter() { if (m_pInfo2) { LocalFree (m_pInfo2); } if (m_pInfo4) { LocalFree (m_pInfo4); } if (m_pDriverInfo6) { LocalFree (m_pDriverInfo6); } if (m_hPrinter) { ClosePrinter (m_hPrinter); } if (m_pszUrlBuffer) { LocalFree (m_pszUrlBuffer); } if (m_pszOemUrl) { LocalFree (m_pszOemUrl); } if (m_pszManufacturer) { LocalFree (m_pszManufacturer); } } BOOL CPrinter::Open(LPTSTR pPrinterName, LPHANDLE phPrinter) { PRINTER_DEFAULTS pd = {NULL, NULL, PRINTER_ACCESS_USE}; if (m_hPrinter != NULL) return FALSE; if (! (OpenPrinter(pPrinterName, &m_hPrinter, &pd))) return FALSE; if (phPrinter) { *phPrinter = m_hPrinter; } return TRUE; } // This is a compiler safe way of checking that an unsigned integer is // negative and returning zero if it is or the integer if it is not // Note: Tval can be any size but must be unsigned template inline T ZeroIfNegative(T Tval) { if (Tval & (T(1) << (sizeof(Tval) * 8 - 1))) return 0; else return Tval; } BOOL CPrinter::CalJobEta() { DWORD dwPrintRate = DWERROR; PJOB_INFO_2 pJobInfo = NULL; PJOB_INFO_2 pJob; DWORD dwNumJobs; DWORD dwNeeded = 0; DWORD i; float fFactor = 1; float fTotal = 0; DWORD dwNumJobsReqested; BOOL bRet = FALSE; const DWORD cdwLimit = 256; if (! AllocGetPrinterInfo2()) return NULL; if (m_pInfo2->cJobs > 0) { if (m_pInfo2->cJobs > cdwLimit) { fFactor = (float) m_pInfo2->cJobs / cdwLimit; dwNumJobsReqested = cdwLimit; } else dwNumJobsReqested = m_pInfo2->cJobs; EnumJobs (m_hPrinter, 0, dwNumJobsReqested, 2, NULL, 0, &dwNeeded, &dwNumJobs); if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (NULL == (pJobInfo = (PJOB_INFO_2)LocalAlloc(LPTR, dwNeeded))) || (!EnumJobs (m_hPrinter, 0, dwNumJobsReqested, 2, (LPBYTE) pJobInfo, dwNeeded, &dwNeeded, &dwNumJobs))) { goto Cleanup; } // Get the average Job size // Find out if we can use page as the unit for (pJob = pJobInfo, i = 0; i < dwNumJobs; i++, pJob++) { if (pJob->Status & BIT_IGNORED_JOB) continue; if (pJob->Size > 0 && pJob->TotalPages == 0) break; } m_dwPendingJobCount = 0; if (i == dwNumJobs) { // All the jobs have the page information. Use page as the unit m_dwAvgJobSizeUnit = PAGEPERJOB; for (pJob = pJobInfo, i = 0; i < dwNumJobs; i++, pJob++) { if (pJob->Status & BIT_IGNORED_JOB) continue; m_dwPendingJobCount++; fTotal += pJob->TotalPages; } } else { // Use byte as the unit m_dwAvgJobSizeUnit = BYTEPERJOB; for (pJob = pJobInfo, i = 0; i < dwNumJobs; i++, pJob++) { if (pJob->Status & BIT_IGNORED_JOB) continue; m_dwPendingJobCount++; fTotal += ZeroIfNegative(pJob->Size); } } // Calculate the averate job size if (m_dwPendingJobCount) { m_dwAvgJobSize = DWORD ((fTotal) / (float) m_dwPendingJobCount); dwPrintRate = GetPPM(); if (dwPrintRate != DWERROR) m_dwJobCompletionMinute = (DWORD) (fFactor * GetWaitingMinutes (dwPrintRate, pJobInfo, dwNumJobs)); } else { m_dwAvgJobSize = 0; m_dwJobCompletionMinute = 0; } m_dwPendingJobCount = (DWORD) (m_dwPendingJobCount * fFactor); } else { m_dwPendingJobCount = 0; m_dwAvgJobSize = 0; m_dwJobCompletionMinute = 0; m_dwAvgJobSizeUnit = PAGEPERJOB; } m_bCalcJobETA = TRUE; // Set the last error to ERROR_INVALID_DATA if the printer rate is not available for the current printer // or if the printer status is not suitable to display the summary information // if (dwPrintRate == DWERROR || m_pInfo2->Status & ( PRINTER_STATUS_PAUSED | PRINTER_STATUS_ERROR | PRINTER_STATUS_PENDING_DELETION | PRINTER_STATUS_PAPER_JAM | PRINTER_STATUS_PAPER_OUT | PRINTER_STATUS_OFFLINE )) { SetLastError (ERROR_INVALID_DATA); m_dwJobCompletionMinute = DWERROR; bRet = FALSE; } else bRet = TRUE; Cleanup: if (pJobInfo) LocalFree(pJobInfo); return bRet; } DWORD CPrinter::GetWaitingMinutes(DWORD dwPPM, PJOB_INFO_2 pJobInfo, DWORD dwNumJob) { DWORD dwWaitingTime = 0; DWORD dwTotalPages = 0; if (dwNumJob == 0) return 0; if (dwPPM == 0) return DWERROR; for (DWORD i = 0; i < dwNumJob; i++, pJobInfo++) { if (pJobInfo->Status & BIT_IGNORED_JOB) continue; if (pJobInfo->Status & BIT_ERROR_JOB) return DWERROR; if (pJobInfo->TotalPages > 0) { dwTotalPages += pJobInfo->TotalPages; } else { if (pJobInfo->Size) { dwTotalPages += 1 + ZeroIfNegative(pJobInfo->Size) / CHAR_PER_PAGE; } } } if (dwTotalPages) dwWaitingTime = 1 + dwTotalPages / dwPPM; return dwWaitingTime; } DWORD CPrinter::GetPPM() { DWORD dwPrintRate; DWORD dwPrintRateUnit; // Get PrintRate dwPrintRate = MyDeviceCapabilities(m_pInfo2->pPrinterName, NULL, DC_PRINTRATE, NULL, NULL); if (dwPrintRate == DWERROR ) { return dwPrintRate; } dwPrintRateUnit = MyDeviceCapabilities(m_pInfo2->pPrinterName, NULL, DC_PRINTRATEUNIT, NULL, NULL); switch (dwPrintRateUnit) { case PRINTRATEUNIT_PPM: // PagesPerMinute break; case PRINTRATEUNIT_CPS: // CharactersPerSecond dwPrintRate = CPS2PPM (dwPrintRate); break; case PRINTRATEUNIT_LPM: // LinesPerMinute dwPrintRate = LPM2PPM (dwPrintRate); break; case PRINTRATEUNIT_IPM: // InchesPerMinute dwPrintRate = DWERROR; break; default: // Unknown dwPrintRate = DWERROR; break; } return dwPrintRate ; } BOOL CPrinter::AllocGetPrinterInfo2() { DWORD dwNeeded = 0; PPRINTER_INFO_2 pPrinterInfo = NULL; LPTSTR pszTmp; if (m_hPrinter == NULL) { SetLastError (ERROR_INVALID_HANDLE); return FALSE; } // Get a PRINTER_INFO_2 structure filled up if (GetPrinter(m_hPrinter, 2, NULL, 0, &dwNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (NULL == (pPrinterInfo = (PPRINTER_INFO_2)LocalAlloc(LPTR, dwNeeded))) || (!GetPrinter(m_hPrinter, 2, (byte *)pPrinterInfo, dwNeeded, &dwNeeded))) { if (pPrinterInfo) LocalFree(pPrinterInfo); if (! GetLastError()) SetLastError (ERROR_INVALID_DATA); return FALSE; } // Mark the offline status if the attribute says offline if (pPrinterInfo->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) { pPrinterInfo->Status |= PRINTER_STATUS_OFFLINE; } // Extract the first port for the case of pooled printing. i.e. we don't support pooled printing. if ( pPrinterInfo->pPortName) { if( pszTmp = _tcschr( pPrinterInfo->pPortName, TEXT(','))) *pszTmp = TEXT('\0'); } if (m_pInfo2) { LocalFree (m_pInfo2); } m_pInfo2 = pPrinterInfo; return TRUE; } BOOL CPrinter::AllocGetPrinterInfo4() { DWORD dwNeeded = 0; PPRINTER_INFO_4 pPrinterInfo = NULL; if (m_hPrinter == NULL) { SetLastError (ERROR_INVALID_HANDLE); return FALSE; } // Get a PRINTER_INFO_4 structure filled up if (GetPrinter(m_hPrinter, 4, NULL, 0, &dwNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (NULL == (pPrinterInfo = (PPRINTER_INFO_4)LocalAlloc(LPTR, dwNeeded))) || (!GetPrinter(m_hPrinter, 4, (byte *)pPrinterInfo, dwNeeded, &dwNeeded))) { if (pPrinterInfo) LocalFree(pPrinterInfo); if (! (GetLastError())) SetLastError (ERROR_INVALID_DATA); return FALSE; } if (m_pInfo4) { LocalFree (m_pInfo4); } m_pInfo4 = pPrinterInfo; return TRUE; } BOOL CPrinter::AllocGetDriverInfo6() { DWORD dwNeeded = 0; PDRIVER_INFO_6 pDriverInfo = NULL; if (m_hPrinter == NULL) { SetLastError (ERROR_INVALID_HANDLE); return FALSE; } // Get a DRIVER_INFO_6 structure filled up if (GetPrinterDriver(m_hPrinter, NULL, 6, NULL, 0, &dwNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (NULL == (pDriverInfo = (PDRIVER_INFO_6)LocalAlloc(LPTR, dwNeeded))) || (!GetPrinterDriver(m_hPrinter, NULL, 6, (LPBYTE)pDriverInfo, dwNeeded, &dwNeeded))) { if (pDriverInfo) LocalFree(pDriverInfo); if (! (GetLastError())) SetLastError (ERROR_INVALID_DATA); return FALSE; } if (m_pDriverInfo6) { LocalFree (m_pDriverInfo6); } m_pDriverInfo6 = pDriverInfo; return TRUE; } PPRINTER_INFO_2 CPrinter::GetPrinterInfo2() { if (m_pInfo2 == NULL) { if (! AllocGetPrinterInfo2()) return NULL; } return m_pInfo2; } PDRIVER_INFO_6 CPrinter::GetDriverInfo6() { if (m_pDriverInfo6 == NULL) { if (! AllocGetDriverInfo6()) { return NULL; } } return m_pDriverInfo6; } DWORD CPrinter::GetWaitingTime() { if (!m_bCalcJobETA) { SetLastError (ERROR_INVALID_DATA); return FALSE; } return m_dwJobCompletionMinute; } BOOL CPrinter::GetJobEtaData (DWORD & dwWaitingTime, DWORD &dwJobCount, DWORD &dwJobSize, DWORD &dwJob) { if (!m_bCalcJobETA) { SetLastError (ERROR_INVALID_DATA); return FALSE; } dwJobCount = m_dwPendingJobCount; dwJobSize = m_dwAvgJobSize; dwWaitingTime = m_dwJobCompletionMinute; dwJob = m_dwAvgJobSizeUnit; return TRUE; } LPTSTR CPrinter::GetPrinterWebUrl(void) { static const TCHAR c_szHttp[] = TEXT("http://"); static const TCHAR c_szHttps[] = TEXT("https://"); BOOL bReturn = FALSE; DWORD dwLastError = ERROR_SUCCESS; DWORD dwLen = 0; TCHAR szBuffer[MAX_COMPUTERNAME_LENGTH+1]; DWORD dwBufferSize; DWORD dwAttributes; LPCTSTR pszServerName; LPTSTR pszShareName; LPTSTR pszSplServerName; LPTSTR pszSplPrinterName; LPTSTR pszSplShareName; // // Get printer info 4 to fetch the attribute. // bReturn = AllocGetPrinterInfo4(); if (!bReturn) { if (GetLastError () == ERROR_INVALID_LEVEL ) { // // Most likely it is a remote printers folder, no support for level4, so // we have to use level 2 instead. // if (! AllocGetPrinterInfo2()) goto Cleanup; } else { // // The call fails with other reasons // goto Cleanup; } } else{ if (m_pInfo4->Attributes & PRINTER_ATTRIBUTE_LOCAL) { // Check if the local flag is on. If so, try to get printer info2 for more information if (! AllocGetPrinterInfo2()) goto Cleanup; } } // // Assume failure // bReturn = FALSE; if (m_pInfo2) { dwAttributes = m_pInfo2->Attributes; pszSplServerName = m_pInfo2->pServerName; pszSplPrinterName = m_pInfo2->pPrinterName; pszSplShareName = m_pInfo2->pShareName; } else { dwAttributes = m_pInfo4->Attributes; pszSplServerName = m_pInfo4->pServerName; pszSplPrinterName = m_pInfo4->pPrinterName; pszSplShareName = NULL; } // // Check if it is a printer connected to an http port // then the port name is the url. // if( m_pInfo2 ) { if( m_pInfo2->pPortName ) { // // Compare the port name prefex to see if it is an HTTP port. // if( !_tcsnicmp( m_pInfo2->pPortName, c_szHttp, _tcslen( c_szHttp ) ) || !_tcsnicmp( m_pInfo2->pPortName, c_szHttps, _tcslen( c_szHttps ) ) ) { // // We always use portname as the URL // dwLen = 1 + lstrlen( m_pInfo2->pPortName ); if (! (m_pszUrlBuffer = (LPTSTR) LocalAlloc (LPTR, dwLen * sizeof (TCHAR)))) { goto Cleanup; } lstrcpy( m_pszUrlBuffer, m_pInfo2->pPortName ); bReturn = TRUE; goto Cleanup; } } } // // If it is an unshared printer, return false // if ( !(dwAttributes & PRINTER_ATTRIBUTE_SHARED) ) { goto Cleanup; } // // Check if it is a connection or a local printer or a masq printer // which is not connected over http, then build the url // from the \\server name\share name. // if( !pszSplServerName ) { dwBufferSize = COUNTOF( szBuffer ); if( !GetComputerName( szBuffer, &dwBufferSize ) ) { goto Cleanup; } pszServerName = szBuffer; } // // Server name was provided then set our pointer to just // after the two leading wacks. // else { if( pszSplServerName[0] == TEXT('\\') && pszSplServerName[1] == TEXT('\\') ) { pszServerName = pszSplServerName + 2; } else { goto Cleanup; } } if ( !IsWebServerInstalled(pszSplServerName) ) { dwLastError = ERROR_NO_BROWSER_SERVERS_FOUND; goto Cleanup; } // // Build the URL - http://server/printers/ipp_0004.asp?printer=ShareName // if (pszSplShareName) { pszShareName = pszSplShareName; } else { // // Parse the sharename/printername from the printer name // if(pszSplPrinterName) { if (pszSplPrinterName[0] == TEXT ('\\') && pszSplPrinterName[1] == TEXT ('\\') ) { pszShareName = _tcschr (pszSplPrinterName + 2, TEXT ('\\')); pszShareName++; } else pszShareName = pszSplPrinterName; } else { goto Cleanup; } } GetWebUIUrl (pszServerName, pszShareName, NULL, &dwLen); if (GetLastError () != ERROR_INSUFFICIENT_BUFFER ) { goto Cleanup; } if (! (m_pszUrlBuffer = (LPTSTR) LocalAlloc (LPTR, dwLen * sizeof (TCHAR)))) { goto Cleanup; } if (!GetWebUIUrl (pszServerName, pszShareName, m_pszUrlBuffer, &dwLen)) { goto Cleanup; } // // Indicate success. // bReturn = TRUE; // // Clean any opened or allocated resources. // Cleanup: // // If this routine failed then set the last error. // if( !bReturn ) { // // If the last error was not set then a called routine may // have set the last error. We don't want to clear the // last error. // if( dwLastError != ERROR_SUCCESS ) { SetLastError( dwLastError ); } } return m_pszUrlBuffer; } BOOL CPrinter::GetDriverData( DriverData dwDriverData, LPTSTR &pszData) { static const ULONG_PTR ulpOffset[LastDriverData] = { // This has the offsets into the DRIVER_INFO_6 structure..... ULOFFSET( PDRIVER_INFO_6, pszOEMUrl ) , ULOFFSET( PDRIVER_INFO_6, pszHardwareID ) , ULOFFSET( PDRIVER_INFO_6, pszMfgName) }; LPTSTR pszDataString = NULL; BOOL bRet = FALSE; DWORD dwSize; ASSERT( (int)dwDriverData >= 0 && (int)dwDriverData < LastDriverData ); if (! GetDriverInfo6() ) goto Cleanup; pszDataString = *(LPTSTR *)(((ULONG_PTR) m_pDriverInfo6) + ulpOffset[dwDriverData] ); if (pszDataString == NULL || *pszDataString == NULL) goto Cleanup; dwSize = sizeof(TCHAR) * (lstrlen( pszDataString ) + 1); if (! (pszData = (LPTSTR)LocalAlloc(LPTR, dwSize))) goto Cleanup; lstrcpy( pszData, pszDataString); bRet = TRUE; Cleanup: return bRet; } BOOL CPrinter::ParseUrlPattern( LPTSTR pSrc, LPTSTR pDest, DWORD &dwDestLen) { const dwMaxMacroLen = 255; enum { NORMALTEXT, STARTMACRO } URL_PATTERN_STATE; BOOL bRet = FALSE; DWORD dwLen = 0; DWORD dwMacroLen = 0; DWORD dwAvailbleSize; DWORD dwState = NORMALTEXT; int i; TCHAR ch; TCHAR szMacroName [dwMaxMacroLen + 1]; LPTSTR pMacroValue = NULL; LPTSTR pszMacroList[] = { TEXT ("MODEL"), TEXT ("HARDWAREID"), }; while (ch = *pSrc++) { switch (dwState) { case NORMALTEXT: if (ch == TEXT ('%')) { // Start a macro dwState = STARTMACRO; dwMacroLen = 0; szMacroName[0] = 0; } else { if (dwLen >= dwDestLen) { dwLen ++; } else { pDest[dwLen++] = ch; } } break; case STARTMACRO: if (ch == TEXT ('%')) { szMacroName[dwMacroLen] = 0; // Replace Macro for (int i = 0; i < sizeof (pszMacroList) / sizeof (pszMacroList[0]); i++) { if (!lstrcmpi (szMacroName, pszMacroList[i])) { pMacroValue = 0; switch (i) { case 0: AssignString (pMacroValue, m_pInfo2->pDriverName); break; case 1: GetDriverData (HardwareID , pMacroValue); break; default: break; } if (pMacroValue) { if (dwDestLen > dwLen) dwAvailbleSize = dwDestLen - dwLen; else dwAvailbleSize = 0; TCHAR szPlaceHolder[1]; DWORD dwBufSize = sizeof (szPlaceHolder) / sizeof (TCHAR); if (!InternetCanonicalizeUrl (pMacroValue, szPlaceHolder, &dwBufSize, 0)) { if (dwBufSize < dwAvailbleSize ) { if (!InternetCanonicalizeUrl (pMacroValue, pDest + dwLen, &dwBufSize, 0)) { LocalFree (pMacroValue); return bRet; } else { dwLen = lstrlen (pDest); } } else { dwLen += dwBufSize; } } LocalFree (pMacroValue); } break; } } dwState = NORMALTEXT; } else { if (dwMacroLen < dwMaxMacroLen) { szMacroName[dwMacroLen ++] = ch; } } break; } } if (dwState == STARTMACRO) { SetLastError ( ERROR_INVALID_DATA ); } else { if (dwLen >= dwDestLen) { SetLastError (ERROR_INSUFFICIENT_BUFFER); dwDestLen = dwLen + 1; } else { pDest[dwLen] = 0; bRet = TRUE; } } return bRet; } LPTSTR CPrinter::GetOemUrl( LPTSTR & pszManufacturer) { LPTSTR pszOemUrlPattern = NULL; DWORD dwLen = 0; LPTSTR pszUrl = NULL; if (!GetPrinterInfo2 () ) goto Cleanup; if (GetDriverData (OEMUrlPattern, pszOemUrlPattern)) { if (! ParseUrlPattern (pszOemUrlPattern, NULL, dwLen) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { m_pszOemUrl = (LPTSTR) LocalAlloc (LPTR, sizeof (TCHAR) * dwLen); if (m_pszOemUrl) { if (!ParseUrlPattern (pszOemUrlPattern, m_pszOemUrl, dwLen)) goto Cleanup; } else goto Cleanup; } } if (GetDriverData (Manufacturer, m_pszManufacturer)) { pszManufacturer = m_pszManufacturer; pszUrl = m_pszOemUrl; } Cleanup: if (pszOemUrlPattern) { LocalFree ( pszOemUrlPattern); } return pszUrl; }