/*++ Copyright (c) 1990 - 1995 Microsoft Corporation Module Name: msgbox.c Abstract: This module provides all the public exported APIs relating to Printer management for the Local Print Providor LocalAddPrinterConnection LocalDeletePrinterConnection LocalPrinterMessageBox Author: Dave Snipp (DaveSn) 15-Mar-1991 Revision History: --*/ #define NOMINMAX #include #pragma hdrstop #include "winsta.h" #define WINSTATION_PRINTER_MESSAGE_TIMEOUT (5*60) HANDLE WinStaDllHandle = NULL; PWINSTATION_SEND_MESSAGEW pWinStationSendMessage = NULL; DWORD LocalPrinterMessageBox( HANDLE hPrinter, DWORD Error, HWND hWnd, LPWSTR pText, LPWSTR pCaption, DWORD dwType ) { // // Always fail this call. It's completely bogus and shouldn't be // supported. The router always passes us a bad handle anyway, so // we will always return invalid handle. // SetLastError(ERROR_INVALID_HANDLE); return FALSE; } BOOL UpdateJobStatus( PSPOOL pSpool, DWORD Error ) /*++ Routine Description: Update job status based on Error. Arguments: pSpool - Handle of session. Error - Error returned from port monitor. Return Value: TRUE - Job is still valid. FALSE - Job is pending deletion. --*/ { DWORD dwJobStatus; PINIJOB pIniJob = NULL; if (pSpool->pIniJob) pIniJob = pSpool->pIniJob; else if (pSpool->pIniPort) pIniJob = pSpool->pIniPort->pIniJob; if (pIniJob) { EnterSplSem(); dwJobStatus = pIniJob->Status; switch (Error) { case ERROR_BAD_DEV_TYPE: case ERROR_INVALID_NAME: case ERROR_PRINT_CANCELLED: // // If we have a problem with the port name, we will not find // a WinStation to put the message on. So kill the job to // prevent the spooler from looping. // pSpool->Status |= SPOOL_STATUS_CANCELLED; InterlockedOr((LONG*)&(pIniJob->Status), JOB_PENDING_DELETION); // // Release any thread waiting on LocalSetPort // SetPortErrorEvent(pIniJob->pIniPort); // // Release any thread waiting on SeekPrinter // SeekPrinterSetEvent(pIniJob, NULL, TRUE); SetLastError(ERROR_PRINT_CANCELLED); LeaveSplSem(); SplOutSem(); return FALSE; case ERROR_OUT_OF_PAPER: if( !( pIniJob->Status & JOB_PAPEROUT )){ InterlockedOr((LONG*)&(pIniJob->Status), JOB_PAPEROUT); pIniJob->pIniPrinter->cErrorOutOfPaper++; } break; case ERROR_NOT_READY: if( !( pIniJob->Status & JOB_OFFLINE )){ InterlockedOr((LONG*)&(pIniJob->Status), JOB_OFFLINE); pIniJob->pIniPrinter->cErrorNotReady++; } break; default: if( !( pIniJob->Status & JOB_ERROR )){ InterlockedOr((LONG*)&(pIniJob->Status), JOB_ERROR); pIniJob->pIniPrinter->cJobError++; } pIniJob->pIniPrinter->dwLastError = Error; // Release any thread waiting on SeekPrinter SeekPrinterSetEvent(pIniJob, NULL, TRUE); break; } if( dwJobStatus != pIniJob->Status ){ SetPrinterChange(pIniJob->pIniPrinter, pIniJob, NVJobStatus, PRINTER_CHANGE_SET_JOB, pIniJob->pIniPrinter->pIniSpooler ); } LeaveSplSem(); if(( pIniJob->Status & JOB_REMOTE ) && pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopups) { if (!(pIniJob->Status & JOB_NOTIFICATION_SENT)) { SendJobAlert(pIniJob); InterlockedOr((LONG*)&(pIniJob->Status), JOB_NOTIFICATION_SENT); } MyMessageBeep( MB_ICONEXCLAMATION, pIniJob->pIniPrinter->pIniSpooler ); } } return TRUE; } DWORD MyMessageBox( HWND hWnd, PSPOOL pSpool, DWORD Error, LPWSTR pText, LPWSTR pCaption, DWORD dwType ) { PINIJOB pIniJob = NULL; LPWSTR pErrorString, pDocumentName; HANDLE hToken; WCHAR szUnnamed[80]; DWORD dwJobStatus; DWORD SessionId = DetermineJobSessionId(pSpool); if (pSpool->pIniJob) pIniJob = pSpool->pIniJob; else if (pSpool->pIniPort) pIniJob = pSpool->pIniPort->pIniJob; if (pIniJob) { if (pText) { Error = WinStationMessageBox(SessionId, hWnd, pText, pCaption, dwType); } else { pErrorString = Error == ERROR_NOT_READY || Error == ERROR_OUT_OF_PAPER || Error == ERROR_DEVICE_REINITIALIZATION_NEEDED || Error == ERROR_DEVICE_REQUIRES_CLEANING || Error == ERROR_DEVICE_DOOR_OPEN || Error == ERROR_DEVICE_NOT_CONNECTED ? GetErrorString(Error) : NULL; if (hToken = RevertToPrinterSelf()) { pDocumentName = pIniJob->pDocument; if (!pDocumentName) { *szUnnamed = L'\0'; LoadString( hInst, IDS_UNNAMED, szUnnamed, sizeof szUnnamed / sizeof *szUnnamed ); pDocumentName = szUnnamed; } if (pSpool->pIniPort) { Error = WinStationMessage(SessionId, NULL, MB_ICONSTOP | MB_RETRYCANCEL | MB_SETFOREGROUND, IDS_LOCALSPOOLER, IDS_ERROR_WRITING_TO_PORT, pDocumentName, pSpool->pIniPort->pName, pErrorString ? pErrorString : szNull); } else { Error = WinStationMessage(SessionId, NULL, MB_ICONSTOP | MB_RETRYCANCEL | MB_SETFOREGROUND, IDS_LOCALSPOOLER, IDS_ERROR_WRITING_TO_DISK, pDocumentName, pErrorString ? pErrorString : szNull); } if (!ImpersonatePrinterClient(hToken)) { Error = IDCANCEL; } } FreeSplStr(pErrorString); } } else { PWCHAR pPrinterName = NULL; // // There is no pIniJob or pIniPort, so we can't be very informative: // pErrorString = Error == ERROR_NOT_READY || Error == ERROR_OUT_OF_PAPER || Error == ERROR_DEVICE_REINITIALIZATION_NEEDED || Error == ERROR_DEVICE_REQUIRES_CLEANING || Error == ERROR_DEVICE_DOOR_OPEN || Error == ERROR_DEVICE_NOT_CONNECTED ? GetErrorString(Error) : NULL; if (pSpool->pIniPrinter) pPrinterName = pSpool->pIniPrinter->pName; if (!pPrinterName) { *szUnnamed = L'\0'; LoadString( hInst, IDS_UNNAMED, szUnnamed, COUNTOF( szUnnamed )); pPrinterName = szUnnamed; } Error = WinStationMessage(SessionId, NULL, MB_ICONSTOP | MB_RETRYCANCEL | MB_SETFOREGROUND, IDS_LOCALSPOOLER, IDS_ERROR_WRITING_GENERAL, pSpool->pIniPrinter->pName, pErrorString ? pErrorString : szNull); FreeSplStr(pErrorString); } if (Error == IDCANCEL) { EnterSplSem(); pSpool->Status |= SPOOL_STATUS_CANCELLED; if (pIniJob) { InterlockedOr((LONG*)&(pIniJob->Status), JOB_PENDING_DELETION); // Release any thread waiting on LocalSetPort SetPortErrorEvent(pIniJob->pIniPort); pIniJob->dwAlert |= JOB_NO_ALERT; // Release any thread waiting on SeekPrinter SeekPrinterSetEvent(pIniJob, NULL, TRUE); } LeaveSplSem(); SplOutSem(); SetLastError(ERROR_PRINT_CANCELLED); } return Error; } // Exclusively for use of the following routines. This is done so we would not have // to store LastError in PSPOOL. typedef struct _AUTORETRYTHDINFO { PSPOOL pSpool; DWORD LastError; } AUTORETRYTHDINFO; typedef AUTORETRYTHDINFO *PAUTORETRYTHDINFO; // ------------------------------------------------------------------------ // SpoolerBMThread // // Thread start up routine for the spooler error message box thread. Exit // code is the return ID from MessageBox. // // ------------------------------------------------------------------------ DWORD WINAPI SpoolerMBThread( PAUTORETRYTHDINFO pThdInfo ) { DWORD rc; rc = MyMessageBox( NULL, pThdInfo->pSpool, pThdInfo->LastError, NULL, NULL, 0 ); FreeSplMem( pThdInfo ); return rc; } #define _ONE_SECOND 1000 // in milliseconds #define SPOOL_WRITE_RETRY_INTERVAL_IN_SECOND 5 // seconds // ------------------------------------------------------------------------ // PromptWriteError // // we'll start a seperate thread to bring up // the message box while we'll (secretly) automatically retry on this // current thread, until user has chosen to retry or cancel. Call the error UI // on the main thread if printing direct. // // ------------------------------------------------------------------------ DWORD PromptWriteError( PSPOOL pSpool, PHANDLE phThread, PDWORD pdwThreadId ) { DWORD Error = GetLastError(); DWORD dwExitCode; DWORD dwWaitCount = 0; SplOutSem(); if( !UpdateJobStatus( pSpool, Error )){ return IDCANCEL; } // // If the spooler doesn't have popup retry messageboxes enabled, then // just sleep and return. // if( !pSpool->pIniSpooler->bEnableRetryPopups ){ Sleep( SPOOL_WRITE_RETRY_INTERVAL_IN_SECOND * _ONE_SECOND ); return IDRETRY; } // start a seperate thread to display the message box // so we can continue to retry here // or simply sleep for 5 seconds if we have already done so if( !*phThread ) { // start a thread to bring up the message box PAUTORETRYTHDINFO pThdInfo; pThdInfo = (PAUTORETRYTHDINFO)AllocSplMem( sizeof(AUTORETRYTHDINFO)); if ( pThdInfo == NULL ) { DBGMSG( DBG_WARNING, ("PromptWriteError failed to allocate AUTORETRYTHDINFO %d\n", GetLastError() )); goto _DoItOnCurrentThread; } pThdInfo->pSpool = pSpool; pThdInfo->LastError = Error; if (!(*phThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SpoolerMBThread, pThdInfo, 0, pdwThreadId))) { DBGMSG(DBG_WARNING, ("PromptWriteError: CreateThread Failed.\n")); FreeSplMem( pThdInfo ); goto _DoItOnCurrentThread; } } while (1) { // we've already started a MB thread, check if user has terminated // the message box if (GetExitCodeThread( *phThread, &dwExitCode) && (dwExitCode != STILL_ACTIVE)) { // if the thread has been terminated, find out the exit code // which is the return ID from MessageBox, then close the // thread handle. CloseHandle( *phThread ); *phThread = 0; return dwExitCode; } if (dwWaitCount++ >= SPOOL_WRITE_RETRY_INTERVAL_IN_SECOND) break; Sleep(_ONE_SECOND); } return IDRETRY; _DoItOnCurrentThread: return MyMessageBox(NULL, pSpool, Error, NULL, NULL, 0 ); } DWORD DetermineJobSessionId( PSPOOL pSpool ) /*++ Routine Description: Determine which session to notify for the current job. Arguments: pSpool - Open spooler handle Return Value: SessionId to send notification message to. --*/ { PINIJOB pIniJob = NULL; if (pSpool->pIniJob) pIniJob = pSpool->pIniJob; else if (pSpool->pIniPort) pIniJob = pSpool->pIniPort->pIniJob; if( pIniJob ) return( pIniJob->SessionId ); return( pSpool->SessionId ); } int WinStationMessageBox( DWORD SessionId, HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType ) /*++ Routine Description: Displays a message on the WinStation named by SessionId. If any problems in actually displaying the message, wait for the the message box timeout interval before returning. This prevents a spin in the spooler attempting to retry the print job without a message box to block the thread. Arguments: SessionId - ID of session to display the message on. Return Value: Result of MessageBox(). --*/ { UINT uOldErrorMode; DWORD MsgLength, CaptionLength, Response; BOOL Result; va_list vargs; // // Standard NT is always SessionId == 0. // On Hydra, the system console is always SessionId == 0. // if( SessionId == 0 ) { return( MessageBox( hWnd, lpText, lpCaption, uType ) ); } // // If its not SessionId == 0, then we must deliver // the message to a session connected on a Hydra // server. Non-Hydra will not ever allocate a // SessionId != 0. // // On failure, we send the message to the console. // if( pWinStationSendMessage == NULL ) { uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); WinStaDllHandle = LoadLibrary(L"winsta.dll"); SetErrorMode(uOldErrorMode); if( WinStaDllHandle == NULL ) { return( MessageBox( hWnd, lpText, lpCaption, uType ) ); } pWinStationSendMessage = (PWINSTATION_SEND_MESSAGEW)GetProcAddress( WinStaDllHandle, "WinStationSendMessageW" ); if( pWinStationSendMessage == NULL ) { return( MessageBox( hWnd, lpText, lpCaption, uType ) ); } } CaptionLength = (wcslen( lpCaption ) + 1) * sizeof(WCHAR); MsgLength = (wcslen( lpText ) + 1) * sizeof(WCHAR); // Send the message to the WinStation and wait for a response Result = pWinStationSendMessage( SERVERNAME_CURRENT, SessionId, (LPWSTR)lpCaption, CaptionLength, (LPWSTR)lpText, MsgLength, uType, WINSTATION_PRINTER_MESSAGE_TIMEOUT, &Response, FALSE ); if( Result ) { // If not an expected response, wait to prevent spinning if( (Response != IDTIMEOUT) && (Response != IDOK) && (Response != IDCANCEL) && (Response != IDRETRY) && (Response != IDIGNORE) && (Response != IDYES) && (Response != IDNO) ) { // Sleep to prevent a spin Sleep( WINSTATION_PRINTER_MESSAGE_TIMEOUT*1000); } return( Response ); } else { // Sleep to prevent a spin Sleep( WINSTATION_PRINTER_MESSAGE_TIMEOUT*1000); return( 0 ); } } int WinStationMessage( DWORD SessionId, HWND hWnd, DWORD Type, int CaptionID, int TextID, ... ) /*++ Routine Description: Displays a message on the WinStation named by SessionId. This takes the message text and caption from the resource file. If any problems in actually display the message, wait for the the message box timeout interval before returning. This prevents a spin in the spooler attempting to retry the print job without a message box to block the thread. Arguments: SessionId - ID of session to display the message on. Return Value: Result of MessageBox(). --*/ { UINT uOldErrorMode; WCHAR MsgText[512]; WCHAR MsgFormat[256]; WCHAR MsgCaption[40]; DWORD MsgLength, CaptionLength, Response; BOOL Result; va_list vargs; if( ( LoadString( hInst, TextID, MsgFormat, sizeof MsgFormat / sizeof *MsgFormat ) > 0 ) && ( LoadString( hInst, CaptionID, MsgCaption, sizeof MsgCaption / sizeof *MsgCaption ) > 0 ) ) { va_start( vargs, TextID ); StringCchVPrintf(MsgText, COUNTOF(MsgText), MsgFormat, vargs); MsgText[COUNTOF(MsgText)-1] = 0; va_end( vargs ); if( SessionId == 0 ) { return( MessageBox( hWnd, MsgText, MsgCaption, Type ) ); } if( pWinStationSendMessage == NULL ) { uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); WinStaDllHandle = LoadLibrary(L"winsta.dll"); SetErrorMode(uOldErrorMode); if( WinStaDllHandle == NULL ) { return( MessageBox( hWnd, MsgText, MsgCaption, Type ) ); } pWinStationSendMessage = (PWINSTATION_SEND_MESSAGEW)GetProcAddress( WinStaDllHandle, "WinStationSendMessageW" ); if( pWinStationSendMessage == NULL ) { return( MessageBox( hWnd, MsgText, MsgCaption, Type ) ); } } CaptionLength = (wcslen( MsgCaption ) + 1) * sizeof(WCHAR); MsgLength = (wcslen( MsgText ) + 1) * sizeof(WCHAR); // Send the message to the WinStation and wait for a response Result = pWinStationSendMessage( SERVERNAME_CURRENT, SessionId, MsgCaption, CaptionLength, MsgText, MsgLength, Type, // Style WINSTATION_PRINTER_MESSAGE_TIMEOUT, &Response, FALSE // DoNotWait ); if( Result ) { // If not an expected response, wait to prevent spinning if( (Response != IDTIMEOUT) && (Response != IDOK) && (Response != IDCANCEL) && (Response != IDRETRY) && (Response != IDIGNORE) && (Response != IDYES) && (Response != IDNO) ) { // Sleep to prevent a spin Sleep( WINSTATION_PRINTER_MESSAGE_TIMEOUT*1000); } return( Response ); } else { // Sleep to prevent a spin Sleep( WINSTATION_PRINTER_MESSAGE_TIMEOUT*1000); return( 0 ); } } else { // Sleep to prevent a spin Sleep( WINSTATION_PRINTER_MESSAGE_TIMEOUT*1000); return 0; } } DWORD LclIsSessionZero ( IN HANDLE hPrinter, IN DWORD JobId, OUT BOOL *pIsSessionZero ) /*++ Routine Description: Determines if the Job was submitted in Session 0. Arguments: hPrinter - printer handle JobId - Job ID pResponse - TRUE if the Job was submitted in Session0 Return Value: Last Error --*/ { DWORD dwRetValue = ERROR_SUCCESS; DWORD SessionId = -1; PSPOOL pSpool = (PSPOOL)hPrinter; if (pSpool && JobId && pIsSessionZero) { SessionId = GetJobSessionId(pSpool, JobId); } if(SessionId == -1) { dwRetValue = ERROR_INVALID_PARAMETER; } else { *pIsSessionZero = (SessionId == 0); } return dwRetValue; } BOOL LclPromptUIPerSessionUser( IN HANDLE hPrinter, IN DWORD JobId, IN PSHOWUIPARAMS pUIParams, OUT DWORD *pResponse ) /*++ Routine Description: Pops TS Message Box in the Session that created the Job. Arguments: hPrinter - printer handle JobId - Job ID pUIParams - UI Parameters pResponse - user's response Return Value: TRUE if it was able to show the UI --*/ { PSPOOL pSpool = (PSPOOL)hPrinter; DWORD SessionId = -1; PINIJOB pIniJob = NULL; DWORD dwReturnVal = 0; DWORD MessageLength; DWORD TitleLength; BOOL bRetValue = FALSE; if (pSpool && JobId && pUIParams && pResponse) { SessionId = GetJobSessionId(pSpool, JobId); } if(SessionId == -1) { SetLastError(ERROR_INVALID_PARAMETER); } else { switch (pUIParams->UIType) { case kMessageBox: { if (pUIParams->MessageBoxParams.cbSize == sizeof(MESSAGEBOX_PARAMS) && pUIParams->MessageBoxParams.pTitle && pUIParams->MessageBoxParams.pMessage && InitializeMessageBoxFunction()) { TitleLength = (wcslen(pUIParams->MessageBoxParams.pTitle) + 1) * sizeof(WCHAR); MessageLength = (wcslen(pUIParams->MessageBoxParams.pMessage) + 1) * sizeof(WCHAR); bRetValue = pWinStationSendMessage( SERVERNAME_CURRENT, SessionId, pUIParams->MessageBoxParams.pTitle, TitleLength, pUIParams->MessageBoxParams.pMessage, MessageLength, pUIParams->MessageBoxParams.Style, pUIParams->MessageBoxParams.dwTimeout, pResponse, !pUIParams->MessageBoxParams.bWait); } else { SetLastError(ERROR_INVALID_PARAMETER); } } default: { SetLastError(ERROR_INVALID_PARAMETER); } } } return bRetValue; } BOOL InitializeMessageBoxFunction( ) /*++ Routine Description: Returns the address of WinStationSendMessageW exported by winsta.dll. WTSSendMessage could have been used instead of doing this. Arguments: None. Return Value: The address of WinStationSendMessageW. --*/ { UINT uOldErrorMode; if (!pWinStationSendMessage) { if (WinStaDllHandle == NULL) { uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); WinStaDllHandle = LoadLibrary(L"winsta.dll"); SetErrorMode(uOldErrorMode); } if(WinStaDllHandle != NULL) { pWinStationSendMessage = (PWINSTATION_SEND_MESSAGEW)GetProcAddress( WinStaDllHandle, "WinStationSendMessageW" ); } } return !!pWinStationSendMessage; }