mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
18 KiB
644 lines
18 KiB
/* ---File: printer.c -----------------------------------------------------
|
|
*
|
|
* Description:
|
|
* Contains functions for managing network print queues.
|
|
*
|
|
* This document contains confidential/proprietary information.
|
|
* Copyright (c) 1990-1992 Microsoft Corporation, All Rights Reserved.
|
|
*
|
|
* Revision History:
|
|
* [00] 21-Nov-90 stevecat created
|
|
* [01] 03-Jan-91 stevecat Modified to use Windows MDI
|
|
* [02] 25-Mar-91 stevecat Modified to use NT WINSPOOL APIs
|
|
* [03] 13-Jan-92 stevecat New PrintMan UI
|
|
* [04] 01-Mar-92 davesn New APIs
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
/* Notes -
|
|
|
|
Global Functions:
|
|
|
|
AllocQueue () - Allocate memory for QUEUE struct and initialize it
|
|
DeleteQJ () - Delete selected print job or all jobs on a queue
|
|
DestroyPrinter () - Delete printer and memory associated with it
|
|
FreeQueue () - Free local memory for QUEUE struct.
|
|
GetJobs () - Retrieve info on print queues and theirjobs
|
|
PauseResumeQJ () - Pause or Continue selected print job or queue
|
|
|
|
Local Functions:
|
|
|
|
*/
|
|
#include "printman.h"
|
|
|
|
|
|
VOID FreeJobInfo( PQUEUE pQueue );
|
|
|
|
|
|
/* --- Function: AllocQueue -------------------------------------------------
|
|
*
|
|
* AllocQueue (LPTSTR pPrinterName)
|
|
*
|
|
* Description:
|
|
* Allocate local memory for QUEUE struct and initialize it.
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
PQUEUE
|
|
AllocQueue(
|
|
LPTSTR pPrinterName
|
|
)
|
|
{
|
|
PQUEUE pQueue;
|
|
|
|
if( ( pQueue = AllocSplMem( sizeof( QUEUE ) ) )
|
|
&&( pQueue->pPrinterName = AllocSplStr( pPrinterName ) ) )
|
|
{
|
|
pQueue->pPrinter = NULL;
|
|
pQueue->cbPrinterBuf = 0;
|
|
pQueue->pJobs = NULL;
|
|
pQueue->cJobs = 0;
|
|
pQueue->cbJobsBuf = 0;
|
|
}
|
|
else
|
|
if( pQueue )
|
|
{
|
|
FreeSplMem( pQueue );
|
|
pQueue = NULL;
|
|
}
|
|
return pQueue;
|
|
}
|
|
|
|
/* --- Function: FreeQueue -------------------------------------------------
|
|
*
|
|
* FreeQueue(PQUEUE pQueue)
|
|
*
|
|
* Description:
|
|
* Free local memory for QUEUE struct.
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
BOOL
|
|
FreeQueue(
|
|
PQUEUE pQueue
|
|
)
|
|
{
|
|
if(pQueue->pServerName)
|
|
FreeSplStr(pQueue->pServerName);
|
|
FreeSplStr(pQueue->pPrinterName);
|
|
FreeSplMem(pQueue);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* --- Function: DeleteQJ ---------------------------------------------------
|
|
*
|
|
* DeleteQJ (PQUEUE pQueue)
|
|
*
|
|
* Description:
|
|
* Delete selected print job or all jobs on print queue
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
DWORD DeleteQJ (HWND hwnd, PQUEUE pQueue)
|
|
{
|
|
DWORD Error = 0;
|
|
|
|
if (!pQueue)
|
|
return 0;
|
|
|
|
if (pQueue->SelJobId)
|
|
{
|
|
// Cancel the print job
|
|
if (!SetJob(pQueue->hPrinter,
|
|
pQueue->SelJobId,
|
|
0, NULL,
|
|
JOB_CONTROL_CANCEL))
|
|
{
|
|
Error = GetLastError();
|
|
DBGMSG( DBG_WARNING, ("PrintManager.DeleteQJ::SetJob.Cancel failed %d.\n", Error) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Make sure user wants to delete ALL jobs before actually doing it,
|
|
// if there's more than one job queued:
|
|
|
|
if ( ( pQueue->cJobs == 1 )
|
|
||( Message( hwnd, MSG_CONFIRMATION, IDS_PRINTMANAGER,
|
|
IDS_DELETEALLPRINTJOBS_S, pQueue->pPrinterName ) == IDOK ) )
|
|
|
|
// Delete all jobs on printer
|
|
if (!SetPrinter(pQueue->hPrinter, 0, NULL, PRINTER_CONTROL_PURGE))
|
|
{
|
|
Error = GetLastError();
|
|
DBGMSG( DBG_WARNING, ("PrintManager.DeleteQJ::SetPrinter failed %d.\n", Error) );
|
|
}
|
|
}
|
|
return Error;
|
|
}
|
|
|
|
/* --- Function: DestroyPrinter ---------------------------------------------
|
|
*
|
|
* DestroyPrinter(PQUEUE pQueue)
|
|
*
|
|
* Description:
|
|
* Delete printer and free all memory for it.
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
BOOL
|
|
DestroyPrinter(
|
|
PQUEUE pQueue
|
|
)
|
|
{
|
|
if (pQueue->pPrinter)
|
|
FreeSplMem(pQueue->pPrinter);
|
|
|
|
if( pQueue->pJobs )
|
|
FreeSplMem(pQueue->pJobs);
|
|
|
|
if( pQueue->cbSelJob > 0 )
|
|
FreeSplMem(pQueue->pSelJob);
|
|
|
|
FreeQueue(pQueue);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
DestroyServer(
|
|
PSERVER_CONTEXT pServerContext
|
|
)
|
|
{
|
|
FreeSplStr( pServerContext->pServerName );
|
|
|
|
if (pServerContext->pPrinters )
|
|
FreeSplMem( pServerContext->pPrinters );
|
|
|
|
FreeSplMem( pServerContext );
|
|
}
|
|
|
|
|
|
VOID DestroyMDIWinInfo( PMDIWIN_INFO pInfo )
|
|
{
|
|
if( pInfo->WindowType == MDIWIN_SERVER )
|
|
DestroyServer( pInfo->pContext );
|
|
else
|
|
DestroyPrinter( pInfo->pContext );
|
|
|
|
CloseHandle( pInfo->DataMutex );
|
|
CloseHandle( pInfo->RefreshSignal );
|
|
|
|
FreeSplMem( pInfo->pColumns );
|
|
FreeSplMem( pInfo );
|
|
}
|
|
|
|
|
|
int GetSelectedJobIndex( PQUEUE pQueue )
|
|
{
|
|
BOOL JobIdFound = FALSE;
|
|
DWORD i;
|
|
|
|
i = 0;
|
|
|
|
while( !JobIdFound && ( i < pQueue->cEnumJobs ) )
|
|
{
|
|
if( pQueue->pJobs[i].JobId == pQueue->SelJobId )
|
|
JobIdFound = TRUE;
|
|
else
|
|
i++;
|
|
}
|
|
|
|
return ( JobIdFound ? i : -1 );
|
|
}
|
|
|
|
|
|
LPJOB_INFO_2
|
|
UpdateJobInfo(
|
|
HANDLE hPrinter,
|
|
DWORD JobId,
|
|
LPJOB_INFO_2 pJob,
|
|
PDWORD pcbBuf
|
|
)
|
|
{
|
|
DWORD cbNeeded;
|
|
DWORD Error;
|
|
BOOL ForgetIt = FALSE;
|
|
|
|
DBGMSG( DBG_TRACE, ( "UpdateJobInfo, JobId == %d\n", JobId ) );
|
|
|
|
if( !GetJob( hPrinter, JobId, 2, (LPBYTE)pJob, *pcbBuf, &cbNeeded ) )
|
|
{
|
|
Error = GetLastError();
|
|
|
|
if( Error == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
pJob = ReallocSplMem( pJob, cbNeeded );
|
|
|
|
if( pJob )
|
|
{
|
|
if( !GetJob( hPrinter, JobId, 2, (LPBYTE)pJob, cbNeeded, &cbNeeded ) )
|
|
ForgetIt = TRUE;
|
|
}
|
|
else
|
|
ForgetIt = TRUE;
|
|
}
|
|
else
|
|
ForgetIt = TRUE;
|
|
|
|
|
|
if( ForgetIt )
|
|
{
|
|
if( pJob )
|
|
FreeSplMem( pJob );
|
|
pJob = NULL;
|
|
*pcbBuf = 0;
|
|
}
|
|
}
|
|
else
|
|
*pcbBuf = cbNeeded;
|
|
|
|
DBGMSG( DBG_TRACE, ( "UpdateJobInfo returned %08x\n", pJob ) );
|
|
|
|
return pJob;
|
|
}
|
|
|
|
|
|
|
|
/* --- Function: GetJobs ----------------------------------------------------
|
|
*
|
|
* GetJobs (HWND hWnd, PQUEUE pQueue)
|
|
*
|
|
* Description:
|
|
* Retrieve current Printer status and its Jobs from WINSPOOL API.
|
|
*
|
|
* ---------------------------------------------------------------------- */
|
|
BOOL GetJobs( PVOID pContext, PDWORD pFlags )
|
|
{
|
|
LPPRINTER_INFO_2 pNewPrinter = NULL;
|
|
DWORD cbNewPrinterBuf;
|
|
PQUEUE pQueue;
|
|
DWORD Error;
|
|
DWORD cbNewJobsBuf;
|
|
LPJOB_INFO_2 pNewJobs = NULL;
|
|
DWORD cNewJobs;
|
|
int SelectedJobIndex;
|
|
DWORD TryEnumJobs;
|
|
BOOL rc;
|
|
|
|
pQueue = (PQUEUE)pContext;
|
|
|
|
if( !pQueue )
|
|
{
|
|
DBGMSG( DBG_WARNING, ( "GetJobs called with pQueue == NULL\n" ) );
|
|
return FALSE;
|
|
}
|
|
|
|
if( !pQueue->hPrinter )
|
|
{
|
|
LEAVE_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
DBGMSG( DBG_WARNING, ( "GetJobs called with hPrinter == NULL, re-opening!\n" ) );
|
|
ReopenPrinter(pQueue, pQueue->pMDIWinInfo->WindowType, FALSE);
|
|
ENTER_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
}
|
|
|
|
if (!pQueue->hPrinter)
|
|
return FALSE;
|
|
|
|
pQueue->Error = 0;
|
|
|
|
if( *pFlags & PRINTER_CHANGE_PRINTER )
|
|
{
|
|
if( cbNewPrinterBuf = pQueue->cbPrinterBuf )
|
|
pNewPrinter = AllocSplMem( cbNewPrinterBuf );
|
|
|
|
DBG_IN_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
LEAVE_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
ENTER_PROTECTED_HANDLE( pQueue->pMDIWinInfo );
|
|
|
|
rc = GetGeneric( (PROC)GetPrinter, 2, (LPBYTE *)&pNewPrinter,
|
|
cbNewPrinterBuf, &cbNewPrinterBuf,
|
|
pQueue->hPrinter, NULL );
|
|
|
|
LEAVE_PROTECTED_HANDLE( pQueue->pMDIWinInfo );
|
|
#if DBG
|
|
if( rc && !pNewPrinter )
|
|
{
|
|
DBGMSG( DBG_ERROR, ( "GetGeneric( GetPrinter ) returned TRUE, but pNewBuffer is NULL.\n"
|
|
"\tPrinter: %ls\n", pQueue->pPrinterName ) );
|
|
}
|
|
#endif /* DBG */
|
|
ENTER_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
|
|
if (!rc) {
|
|
|
|
//
|
|
// Attempt to reopen
|
|
//
|
|
if (pQueue->hPrinter) {
|
|
|
|
ClosePrinter(pQueue->hPrinter);
|
|
pQueue->hPrinter = NULL;
|
|
}
|
|
|
|
LEAVE_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
|
|
ReopenPrinter(pQueue,
|
|
pQueue->pMDIWinInfo->WindowType,
|
|
FALSE);
|
|
|
|
ENTER_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
|
|
|
|
#ifdef SEP_WAITHANDLE
|
|
|
|
if (pQueue->hPrinterWait) {
|
|
|
|
ClosePrinter(pQueue->hPrinterWait);
|
|
pQueue->hPrinterWait = NULL;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Try again
|
|
//
|
|
rc = GetGeneric( (PROC)GetPrinter, 2, (LPBYTE *)&pNewPrinter,
|
|
cbNewPrinterBuf, &cbNewPrinterBuf,
|
|
pQueue->hPrinter, NULL );
|
|
}
|
|
|
|
if (rc) {
|
|
|
|
if( pQueue->pPrinter )
|
|
FreeSplMem( pQueue->pPrinter );
|
|
|
|
pQueue->pPrinter = pNewPrinter;
|
|
pQueue->cbPrinterBuf = cbNewPrinterBuf;
|
|
|
|
pQueue->pMDIWinInfo->Status &= ~PRINTER_STATUS_UNKNOWN;
|
|
|
|
} else {
|
|
|
|
DBGMSG( DBG_WARNING, ("GetPrinter failed for %ls: Error %d\n",
|
|
pQueue->pPrinterName, pQueue->Error ) );
|
|
|
|
pQueue->pMDIWinInfo->Status |= PRINTER_STATUS_UNKNOWN;
|
|
|
|
if( pQueue->pPrinter )
|
|
FreeSplMem( pQueue->pPrinter );
|
|
|
|
if( pNewPrinter )
|
|
FreeSplMem( pNewPrinter );
|
|
|
|
pQueue->pPrinter = NULL;
|
|
pQueue->cbPrinterBuf = 0;
|
|
|
|
FreeJobInfo( pQueue );
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
if( *pFlags & PRINTER_CHANGE_JOB )
|
|
{
|
|
/* Try to enumerate a buffer big enough to fill three pages of information,
|
|
* so that we can scroll up or down a full page before it becomes necessary
|
|
* to refresh the buffer. (Though with a refresh rate of once per second,
|
|
* this will probably happen anyway.)
|
|
* Note that pQueue->FirstEnumJob was set in Refresh (printman.c).
|
|
* If it is 0, we may be currently scrolled to the top of the list,
|
|
* in which case the buffer will extend two pages forward.
|
|
*/
|
|
TryEnumJobs = ( pQueue->pMDIWinInfo->cNumLines * 3 );
|
|
|
|
if( cbNewJobsBuf = pQueue->cbJobsBuf )
|
|
pNewJobs = AllocSplMem( cbNewJobsBuf );
|
|
|
|
DBG_IN_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
LEAVE_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
|
|
ENTER_PROTECTED_HANDLE( pQueue->pMDIWinInfo );
|
|
|
|
rc = ENUM_JOBS( pQueue->hPrinter,
|
|
pQueue->FirstEnumJob,
|
|
TryEnumJobs,
|
|
2,
|
|
pNewJobs,
|
|
cbNewJobsBuf,
|
|
&cbNewJobsBuf,
|
|
&cNewJobs );
|
|
|
|
LEAVE_PROTECTED_HANDLE( pQueue->pMDIWinInfo );
|
|
|
|
ENTER_PROTECTED_DATA( pQueue->pMDIWinInfo );
|
|
|
|
if( rc )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "EnumJobs returned %d job%s @%08x in %d (0x%x) bytes for %s\n",
|
|
cNewJobs, ( cNewJobs == 1 ? " " : "s" ),
|
|
pNewJobs, cbNewJobsBuf, cbNewJobsBuf, pQueue->pPrinterName ) );
|
|
|
|
|
|
if( pQueue->pJobs )
|
|
{
|
|
FreeSplMem( pQueue->pJobs );
|
|
}
|
|
|
|
/* Free up the previously allocated buffer
|
|
* if we don't need it any more:
|
|
*/
|
|
if( pNewJobs && ( cNewJobs == 0 ) )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "Freeing %d (0x%x) bytes @%08x\n",
|
|
cbNewJobsBuf, cbNewJobsBuf, pNewJobs ) );
|
|
FreeSplMem( pNewJobs );
|
|
pNewJobs = NULL;
|
|
cbNewJobsBuf = 0;
|
|
}
|
|
|
|
pQueue->pJobs = pNewJobs;
|
|
pQueue->cbJobsBuf = cbNewJobsBuf;
|
|
|
|
/* We shouldn't really get here if pQueue->pPrinter is non-NULL,
|
|
* but it seems to be happening sometimes.
|
|
*/
|
|
if( pQueue->pPrinter )
|
|
pQueue->cJobs = pQueue->pPrinter->cJobs;
|
|
else
|
|
pQueue->cJobs = 0;
|
|
|
|
pQueue->cEnumJobs = cNewJobs;
|
|
}
|
|
|
|
else
|
|
{
|
|
Error = GetLastError( );
|
|
|
|
DBGMSG( DBG_WARNING, ("EnumJobs failed for %ls: Error %d\n",
|
|
pQueue->pPrinterName, Error) );
|
|
|
|
if( Error != ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
FreeJobInfo( pQueue );
|
|
pQueue->Error = Error;
|
|
}
|
|
}
|
|
|
|
|
|
if( pQueue->pJobs )
|
|
{
|
|
SelectedJobIndex = GetSelectedJobIndex( pQueue );
|
|
|
|
DBGMSG( DBG_TRACE, ( "Selected job index == %d\n", SelectedJobIndex ) );
|
|
|
|
/* The selected job may have been scrolled out of buffer range.
|
|
* In this case we allocate some job info for it:
|
|
*/
|
|
if( ( pQueue->SelJobId ) && ( SelectedJobIndex == -1 ) )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "Selected job is scrolled out of range\n" ) );
|
|
|
|
/* If it's just gone out of range, set the selected-job pointer
|
|
* to NULL, so we don't try to Realloc part of the buffer.
|
|
* (cbSelJob is non-null if we were out of range last time round,
|
|
* because we allocated a job info structure.)
|
|
*/
|
|
if( pQueue->cbSelJob == 0 )
|
|
pQueue->pSelJob = NULL;
|
|
|
|
pQueue->pSelJob = UpdateJobInfo( pQueue->hPrinter,
|
|
pQueue->SelJobId,
|
|
pQueue->pSelJob,
|
|
&pQueue->cbSelJob );
|
|
|
|
/* If the returned job info is NULL, either the job is no longer
|
|
* around, or there was some sort of error.
|
|
* In either case, deselect the current job:
|
|
*/
|
|
if( !pQueue->pSelJob )
|
|
{
|
|
pQueue->SelJobId = 0;
|
|
pQueue->pMDIWinInfo->ObjSelected = NOSELECTION;
|
|
}
|
|
}
|
|
|
|
/* Otherwise just point the selected-job pointer at the appropriate
|
|
* bit of the buffer:
|
|
*/
|
|
else
|
|
{
|
|
/* If there was a job info allocated earlier, free it up.
|
|
* cbSelJob must be 0 if there is no specially allocated buffer:
|
|
*/
|
|
if( pQueue->cbSelJob )
|
|
{
|
|
FreeSplMem( pQueue->pSelJob );
|
|
pQueue->pSelJob = NULL;
|
|
pQueue->cbSelJob = 0;
|
|
}
|
|
|
|
if( pQueue->SelJobId )
|
|
{
|
|
pQueue->pSelJob = &pQueue->pJobs[SelectedJobIndex];
|
|
pQueue->pMDIWinInfo->ObjSelected = ( pQueue->FirstEnumJob
|
|
+ SelectedJobIndex );
|
|
|
|
DBGMSG( DBG_TRACE, ( "Selected job == %08x\n", pQueue->pSelJob ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Make sure any previous selection is canceled:
|
|
*/
|
|
pQueue->pSelJob = NULL;
|
|
pQueue->SelJobId = 0;
|
|
pQueue->pMDIWinInfo->ObjSelected = NOSELECTION;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
VOID FreeJobInfo( PQUEUE pQueue )
|
|
{
|
|
if (pQueue->pJobs)
|
|
FreeSplMem(pQueue->pJobs);
|
|
|
|
pQueue->pJobs = NULL;
|
|
pQueue->cbJobsBuf = 0;
|
|
|
|
pQueue->cEnumJobs = 0;
|
|
pQueue->cJobs = 0;
|
|
}
|
|
|
|
|
|
|
|
/* OpenPrinterForSpecifiedAccess
|
|
*
|
|
* Attempts to open the printer for the requested access.
|
|
* The following access permissions are valid:
|
|
*
|
|
* PRINTER_ALL_ACCESS
|
|
* PRINTER_READ
|
|
* READ_CONTROL
|
|
*
|
|
* If PRINTER_ACCESS_HIGHEST_PERMITTED is specified,
|
|
* this function will attempt to open the printer using each of the above
|
|
* permissions until it is successful.
|
|
*
|
|
*/
|
|
BOOL OpenPrinterForSpecifiedAccess(
|
|
LPTSTR pName,
|
|
LPHANDLE pHandle,
|
|
DWORD AccessRequested,
|
|
OPTIONAL PDWORD pAccessGranted )
|
|
{
|
|
PRINTER_DEFAULTS PrinterDefaults;
|
|
BOOL rc = FALSE;
|
|
BOOL TryAll = FALSE;
|
|
|
|
PrinterDefaults.pDatatype = NULL;
|
|
PrinterDefaults.pDevMode = NULL;
|
|
|
|
switch( AccessRequested )
|
|
{
|
|
case PRINTER_ACCESS_HIGHEST_PERMITTED:
|
|
TryAll = TRUE;
|
|
/* fall through ... */
|
|
|
|
case PRINTER_ALL_ACCESS:
|
|
PrinterDefaults.DesiredAccess = PRINTER_ALL_ACCESS;
|
|
rc = OpenPrinter( pName, pHandle, &PrinterDefaults );
|
|
if( rc || !TryAll || (( GetLastError( ) != ERROR_ACCESS_DENIED ) &&
|
|
( GetLastError( ) != ERROR_PRIVILEGE_NOT_HELD)) )
|
|
break;
|
|
|
|
case PRINTER_READ:
|
|
PrinterDefaults.DesiredAccess = PRINTER_READ;
|
|
rc = OpenPrinter( pName, pHandle, &PrinterDefaults );
|
|
if( rc || !TryAll || (( GetLastError( ) != ERROR_ACCESS_DENIED ) &&
|
|
( GetLastError( ) != ERROR_PRIVILEGE_NOT_HELD)) )
|
|
break;
|
|
|
|
case READ_CONTROL:
|
|
PrinterDefaults.DesiredAccess = READ_CONTROL;
|
|
rc = OpenPrinter( pName, pHandle, &PrinterDefaults );
|
|
}
|
|
|
|
if( pAccessGranted )
|
|
{
|
|
if( rc )
|
|
*pAccessGranted = PrinterDefaults.DesiredAccess;
|
|
else
|
|
*pAccessGranted = PRINTER_ACCESS_DENIED;
|
|
}
|
|
|
|
return rc;
|
|
}
|