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.
1762 lines
46 KiB
1762 lines
46 KiB
/*++
|
|
|
|
Copyright (c) 1990-2003 Microsoft Corporation
|
|
All Rights Reserved
|
|
|
|
|
|
Module Name:
|
|
|
|
Abstract:
|
|
|
|
Author:
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#define USECOMM
|
|
|
|
#include "precomp.h"
|
|
#include <winioctl.h>
|
|
#include <ntddpar.h>
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
// PROTO, CONSTANT, and GLOBAL
|
|
//
|
|
// ---------------------------------------------------------------------
|
|
|
|
DWORD ProcessPJLString(PINIPORT, CHAR *, DWORD *);
|
|
VOID ProcessParserError(DWORD);
|
|
VOID InterpreteTokens(PINIPORT, PTOKENPAIR, DWORD);
|
|
BOOL IsPJL(PINIPORT);
|
|
BOOL WriteCommand(HANDLE, LPSTR);
|
|
BOOL ReadCommand(HANDLE);
|
|
|
|
#define WAIT_FOR_WRITE 100 // 0.1 sec
|
|
#define WAIT_FOR_DATA_TIMEOUT 100 // 0.1 sec
|
|
#define WAIT_FOR_USTATUS_THREAD_TIMEOUT 500 // 0.5 sec
|
|
#define GETDEVICEID IOCTL_PAR_QUERY_DEVICE_ID
|
|
#define MAX_DEVID 1024
|
|
|
|
TCHAR cszInstalledMemory[] = TEXT("Installed Memory");
|
|
TCHAR cszAvailableMemory[] = TEXT("Available Memory");
|
|
|
|
BOOL
|
|
DllMain(
|
|
IN HANDLE hModule,
|
|
IN DWORD dwReason,
|
|
IN LPVOID lpRes
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Dll entry point
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER(lpRes);
|
|
|
|
switch (dwReason) {
|
|
|
|
case DLL_PROCESS_ATTACH:
|
|
InitializeCriticalSection(&pjlMonSection);
|
|
DisableThreadLibraryCalls(hModule);
|
|
break;
|
|
|
|
default:
|
|
// do nothing
|
|
;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ClearPrinterStatusAndIniJobs(
|
|
PINIPORT pIniPort
|
|
)
|
|
{
|
|
PORT_INFO_3 PortInfo3;
|
|
|
|
if ( pIniPort->PrinterStatus ||
|
|
(pIniPort->status & PP_PRINTER_OFFLINE) ) {
|
|
|
|
pIniPort->PrinterStatus = 0;
|
|
pIniPort->status &= ~PP_PRINTER_OFFLINE;
|
|
|
|
ZeroMemory(&PortInfo3, sizeof(PortInfo3));
|
|
SetPort(NULL, pIniPort->pszPortName, 3, (LPBYTE)&PortInfo3);
|
|
}
|
|
|
|
SendJobLastPageEjected(pIniPort, ALL_JOBS, FALSE);
|
|
}
|
|
|
|
|
|
VOID
|
|
RefreshPrinterInfo(
|
|
PINIPORT pIniPort
|
|
)
|
|
{
|
|
//
|
|
// Only one thread should write to the printer at a time
|
|
//
|
|
if ( WAIT_OBJECT_0 != WaitForSingleObject(pIniPort->DoneWriting,
|
|
WAIT_FOR_WRITE) ) {
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If printer is power cycled and it does not talk back (but answers
|
|
// PnP id) we got to clear the error on spooler to send jobs
|
|
//
|
|
ClearPrinterStatusAndIniJobs(pIniPort);
|
|
if ( !IsPJL(pIniPort) ) {
|
|
|
|
pIniPort->status &= ~PP_IS_PJL;
|
|
}
|
|
|
|
SetEvent(pIniPort->DoneWriting);
|
|
}
|
|
|
|
|
|
VOID
|
|
UstatusThread(
|
|
HANDLE hPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Unsolicited status information thread. This thread will continue to
|
|
read unsolicited until it's asked to terminate, which will happen
|
|
under one of these conditions:
|
|
1) Receive EOJ confirmation from the printer.
|
|
2) Timeout waiting for EOJ confirmation.
|
|
3) The port is been closed.
|
|
|
|
Arguments:
|
|
hPort : IniPort structure for the port
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
HANDLE hToken;
|
|
|
|
SPLASSERT(pIniPort &&
|
|
pIniPort->signature == PJ_SIGNATURE &&
|
|
(pIniPort->status & PP_THREAD_RUNNING) == 0);
|
|
|
|
if ( IsPJL(pIniPort) )
|
|
pIniPort->status |= PP_IS_PJL;
|
|
|
|
|
|
SetEvent(pIniPort->DoneWriting);
|
|
|
|
if ( !(pIniPort->status & PP_IS_PJL) )
|
|
goto StopThread;
|
|
|
|
pIniPort->status |= PP_THREAD_RUNNING;
|
|
|
|
pIniPort->PrinterStatus = 0;
|
|
pIniPort->status &= ~PP_PRINTER_OFFLINE;
|
|
pIniPort->dwLastReadTime = GetTickCount ();
|
|
|
|
for ( ; ; ) {
|
|
|
|
//
|
|
// check if PP_RUN_THREAD has been cleared to terminate
|
|
//
|
|
if ( !(pIniPort->status & PP_RUN_THREAD) ) {
|
|
|
|
if ( pIniPort->status & PP_INSTARTDOC ) {
|
|
|
|
//
|
|
// there's an active job, can't end the thread
|
|
//
|
|
pIniPort->status |= PP_RUN_THREAD;
|
|
} else {
|
|
|
|
DBGMSG(DBG_INFO,
|
|
("PJLMon Read Thread for Port %ws Closing Down.\n",
|
|
pIniPort->pszPortName));
|
|
|
|
pIniPort->status &= ~PP_THREAD_RUNNING;
|
|
|
|
ClearPrinterStatusAndIniJobs(pIniPort);
|
|
goto StopThread;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check if the printer is bi-di
|
|
//
|
|
if (pIniPort->status & PP_IS_PJL) {
|
|
|
|
(VOID)ReadCommand(hPort);
|
|
|
|
//
|
|
// If we are under error condition or if we have jobs pending
|
|
// read status back from printer more frequently
|
|
//
|
|
if ( pIniPort->pIniJob ||
|
|
(pIniPort->status & PP_PRINTER_OFFLINE) ||
|
|
(pIniPort->status & PP_WRITE_ERROR) ) {
|
|
|
|
WaitForSingleObject(pIniPort->WakeUp,
|
|
dwReadThreadErrorTimeout);
|
|
} else {
|
|
|
|
WaitForSingleObject(pIniPort->WakeUp,
|
|
dwReadThreadIdleTimeoutOther);
|
|
}
|
|
|
|
if ( pIniPort->pIniJob &&
|
|
!(pIniPort->status & PP_PRINTER_OFFLINE) &&
|
|
!(pIniPort->status & PP_WRITE_ERROR) ) {
|
|
|
|
//
|
|
// Some printers are PJL bi-di, but do not send
|
|
// EOJ. We want jobs to disappear from printman
|
|
//
|
|
SendJobLastPageEjected(pIniPort,
|
|
GetTickCount() - dwReadThreadEOJTimeout,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// If we did not read from printer for more than a minute
|
|
// and no more jobs talk to printer again
|
|
//
|
|
if ( !(pIniPort->status & PP_INSTARTDOC) &&
|
|
(GetTickCount() - pIniPort->dwLastReadTime) > 240000
|
|
)
|
|
RefreshPrinterInfo(pIniPort);
|
|
|
|
} else {
|
|
|
|
//
|
|
// exit the thread if printer is not PJL bi-di capable
|
|
//
|
|
Sleep(2000);
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
|
|
DBGMSG(DBG_TRACE, ("Set ~PP_RUN_THREAD because printer is not bi-di\n"));
|
|
}
|
|
}
|
|
|
|
StopThread:
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
pIniPort->status &= ~PP_THREAD_RUNNING;
|
|
}
|
|
|
|
|
|
BOOL
|
|
CreateUstatusThread(
|
|
PINIPORT pIniPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Creates the Ustatus thread
|
|
|
|
Arguments:
|
|
pIniPort : IniPort structure for the port
|
|
|
|
Return Value:
|
|
TRUE on succesfully creating the thread, else FALSE
|
|
--*/
|
|
{
|
|
HANDLE ThreadHandle;
|
|
DWORD ThreadId;
|
|
|
|
DBGMSG(DBG_INFO, ("PJLMon Read Thread for Port %ws Starting.\n",
|
|
pIniPort->pszPortName));
|
|
if (pIniPort->hUstatusThread)
|
|
{
|
|
//
|
|
// Make sure there is no running UstatusThread
|
|
//
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
SetEvent (pIniPort->WakeUp);
|
|
WaitForSingleObject (pIniPort->hUstatusThread, INFINITE);
|
|
CloseHandle (pIniPort->hUstatusThread);
|
|
pIniPort->hUstatusThread = NULL;
|
|
}
|
|
|
|
pIniPort->status |= PP_RUN_THREAD;
|
|
|
|
WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
|
|
|
|
//
|
|
// Initialize events
|
|
//
|
|
ResetEvent (pIniPort->WakeUp);
|
|
SetEvent (pIniPort->DoneReading);
|
|
|
|
ThreadHandle = CreateThread(NULL, 16*1024,
|
|
(LPTHREAD_START_ROUTINE)UstatusThread,
|
|
pIniPort,
|
|
0, &ThreadId);
|
|
|
|
if ( ThreadHandle ) {
|
|
|
|
SetThreadPriority(ThreadHandle, THREAD_PRIORITY_LOWEST);
|
|
pIniPort-> hUstatusThread = ThreadHandle;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
SetEvent(pIniPort->DoneWriting);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonOpenPortEx(
|
|
IN LPTSTR pszPortName,
|
|
IN LPTSTR pszPrinterName,
|
|
IN OUT LPHANDLE pHandle,
|
|
IN OUT LPMONITOR pMonitor
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Opens the port
|
|
|
|
Arguments:
|
|
pszPortName : Port name
|
|
pszPrinterName : Printer name
|
|
pHandle : Pointer to the handle to return
|
|
pMonitor : Port monitor function table
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort;
|
|
BOOL bRet = FALSE;
|
|
BOOL bInSem = FALSE;
|
|
|
|
//
|
|
// Validate port monitor
|
|
//
|
|
if ( !pMonitor ||
|
|
!pMonitor->pfnOpenPort ||
|
|
!pMonitor->pfnStartDocPort ||
|
|
!pMonitor->pfnWritePort ||
|
|
!pMonitor->pfnReadPort ||
|
|
!pMonitor->pfnClosePort ) {
|
|
|
|
|
|
DBGMSG(DBG_WARNING,
|
|
("PjlMon: Invalid port monitors passed to OpenPortEx\n"));
|
|
SetLastError(ERROR_INVALID_PRINT_MONITOR);
|
|
goto Cleanup;
|
|
}
|
|
|
|
EnterSplSem();
|
|
bInSem = TRUE;
|
|
|
|
//
|
|
// Is the port open already?
|
|
//
|
|
if ( pIniPort = FindIniPort(pszPortName) ) {
|
|
|
|
SetLastError(ERROR_BUSY);
|
|
goto Cleanup;
|
|
}
|
|
|
|
pIniPort = CreatePortEntry(pszPortName);
|
|
LeaveSplSem();
|
|
bInSem = FALSE;
|
|
|
|
if ( pIniPort &&
|
|
(*pMonitor->pfnOpenPort)(pszPortName, &pIniPort->hPort) ) {
|
|
|
|
*pHandle = pIniPort;
|
|
CopyMemory((LPBYTE)&pIniPort->fn, (LPBYTE)pMonitor, sizeof(*pMonitor));
|
|
|
|
//
|
|
// Create the ustatus thread always
|
|
// If printer is not PJL it will die by itself
|
|
// We do not want to write to the printer in this thread to determine
|
|
// printer is PJL since that may take several seconds to fail
|
|
//
|
|
CreateUstatusThread(pIniPort);
|
|
bRet = TRUE;
|
|
} else {
|
|
|
|
DBGMSG(DBG_WARNING, ("PjlMon: OpenPort "TSTR" : Failed\n", pszPortName));
|
|
}
|
|
|
|
Cleanup:
|
|
if ( bInSem ) {
|
|
|
|
LeaveSplSem();
|
|
}
|
|
SplOutSem();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonStartDocPort(
|
|
IN HANDLE hPort,
|
|
IN LPTSTR pszPrinterName,
|
|
IN DWORD dwJobId,
|
|
IN DWORD dwLevel,
|
|
IN LPBYTE pDocInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Language monitor StartDocPort
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
pszPrinterName : Printer name
|
|
dwJobId : Job identifier
|
|
dwLevel : Level of Doc info strucuture
|
|
pDocInfo : Pointer to doc info structure
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
PINIJOB pIniJob = NULL;
|
|
DWORD cbJob;
|
|
BOOL bRet = FALSE;
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if ( !pIniPort ||
|
|
pIniPort->signature != PJ_SIGNATURE ||
|
|
!pDocInfo ||
|
|
!pszPrinterName ||
|
|
!*pszPrinterName ) {
|
|
|
|
SPLASSERT(pIniPort &&
|
|
pIniPort->signature == PJ_SIGNATURE &&
|
|
pDocInfo);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( dwLevel != 1 ) {
|
|
|
|
SPLASSERT(dwLevel == 1);
|
|
SetLastError(ERROR_INVALID_LEVEL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Serialize access to the port
|
|
//
|
|
if ( pIniPort->status & PP_INSTARTDOC ) {
|
|
|
|
SetLastError(ERROR_BUSY);
|
|
return FALSE;
|
|
}
|
|
|
|
cbJob = sizeof(*pIniJob) + lstrlen(pszPrinterName) * sizeof(TCHAR)
|
|
+ sizeof(TCHAR);
|
|
pIniJob = (PINIJOB) AllocSplMem(cbJob);
|
|
if ( !pIniJob ) {
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
pIniJob->pszPrinterName = (LPTSTR) (pIniJob + 1);
|
|
StringCbCopy (pIniJob->pszPrinterName, cbJob - sizeof (*pIniJob), pszPrinterName);
|
|
|
|
|
|
if ( !OpenPrinter(pIniJob->pszPrinterName, &pIniJob->hPrinter, NULL) ) {
|
|
|
|
DBGMSG(DBG_WARNING,
|
|
("pjlmon: OpenPrinter failed for "TSTR", last error %d\n",
|
|
pIniJob->pszPrinterName, GetLastError()));
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
pIniPort->status |= PP_INSTARTDOC;
|
|
|
|
bRet = (*pIniPort->fn.pfnStartDocPort)(pIniPort->hPort,
|
|
pszPrinterName,
|
|
dwJobId,
|
|
dwLevel,
|
|
pDocInfo);
|
|
|
|
if ( !bRet ) {
|
|
|
|
pIniPort->status &= ~PP_INSTARTDOC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If Ustatus thread is not running then check if printer understands
|
|
// PJL, unless we determined that printer does not understand PJL earlier
|
|
//
|
|
if ( !(pIniPort->status & PP_RUN_THREAD) &&
|
|
!(pIniPort->status & PP_DONT_TRY_PJL) ) {
|
|
|
|
CreateUstatusThread(pIniPort);
|
|
}
|
|
|
|
//
|
|
// set PP_SEND_PJL flag here so the first write of the job
|
|
// will try to send PJL command to initiate the job control
|
|
//
|
|
|
|
pIniJob->JobId = dwJobId;
|
|
pIniJob->status |= PP_INSTARTDOC;
|
|
|
|
EnterSplSem();
|
|
if ( !pIniPort->pIniJob ) {
|
|
|
|
pIniPort->pIniJob = pIniJob;
|
|
} else {
|
|
|
|
pIniJob->pNext = pIniPort->pIniJob;
|
|
pIniPort->pIniJob = pIniJob;
|
|
}
|
|
LeaveSplSem();
|
|
|
|
if ( pIniPort->status & PP_IS_PJL )
|
|
pIniJob->status |= PP_SEND_PJL;
|
|
|
|
WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
|
|
|
|
Cleanup:
|
|
|
|
if ( !bRet ) {
|
|
|
|
if ( pIniJob )
|
|
FreeIniJob(pIniJob);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonReadPort(
|
|
IN HANDLE hPort,
|
|
OUT LPBYTE pBuffer,
|
|
IN DWORD cbBuf,
|
|
OUT LPDWORD pcbRead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Language monitor ReadPort
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
pBuffer : Buffer to read data to
|
|
cbBuf : Buffer size
|
|
pcbRead : Pointer to the variable to return read count
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
|
|
if ( !pIniPort ||
|
|
pIniPort->signature != PJ_SIGNATURE ) {
|
|
|
|
SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
return (*pIniPort->fn.pfnReadPort)(pIniPort->hPort, pBuffer, cbBuf, pcbRead);
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonWritePort(
|
|
IN HANDLE hPort,
|
|
IN LPBYTE pBuffer,
|
|
IN DWORD cbBuf,
|
|
IN LPDWORD pcbWritten
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Language monitor WritePort
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
pBuffer : Data Buffer
|
|
cbBuf : Buffer size
|
|
pcbRead : Pointer to the variable to return written count
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
BOOL ret;
|
|
|
|
if ( !pIniPort ||
|
|
pIniPort->signature != PJ_SIGNATURE ) {
|
|
|
|
SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// check if it's the fist write of the job
|
|
//
|
|
if ( pIniPort->pIniJob &&
|
|
(pIniPort->pIniJob->status & PP_SEND_PJL) ) {
|
|
|
|
// PP_SEND_PJL is set if it's the first write of the job
|
|
char string[256];
|
|
|
|
if ( !WriteCommand(hPort, "\033%-12345X@PJL \015\012") ) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// clear PP_SEND_PJL here if we have successfully send a PJL command.
|
|
//
|
|
pIniPort->pIniJob->status &= ~PP_SEND_PJL;
|
|
|
|
//
|
|
// set PP_PJL_SENT meaning that we have successfully sent a
|
|
// PJL command to the printer, though it doesn't mean that
|
|
// we will get a successfully read. PP_PJL_SENT gets cleared in
|
|
// StartDocPort.
|
|
//
|
|
pIniPort->pIniJob->status |= PP_PJL_SENT;
|
|
|
|
StringCchPrintfA (string, COUNTOF (string), "@PJL JOB NAME = \"MSJOB %d\"\015\012",
|
|
pIniPort->pIniJob->JobId);
|
|
|
|
WriteCommand(hPort, string);
|
|
WriteCommand(hPort, "@PJL USTATUS JOB = ON \015\012@PJL USTATUS PAGE = OFF \015\012@PJL USTATUS DEVICE = ON \015\012@PJL USTATUS TIMED = 30 \015\012\033%-12345X");
|
|
}
|
|
|
|
//
|
|
// writing to port monitor
|
|
//
|
|
ret = (*pIniPort->fn.pfnWritePort)(pIniPort->hPort, pBuffer,
|
|
cbBuf, pcbWritten);
|
|
|
|
if ( ret ) {
|
|
|
|
pIniPort->status &= ~PP_WRITE_ERROR;
|
|
} else {
|
|
|
|
pIniPort->status |= PP_WRITE_ERROR;
|
|
}
|
|
|
|
if ( (!ret || pIniPort->PrinterStatus) &&
|
|
(pIniPort->status & PP_THREAD_RUNNING) ) {
|
|
|
|
//
|
|
// By waiting for the UStatus thread to finish reading if there
|
|
// is an error and printer sends unsolicited status
|
|
// and user gets status on queue view before the win32 popup
|
|
//
|
|
ResetEvent(pIniPort->DoneReading);
|
|
SetEvent(pIniPort->WakeUp);
|
|
WaitForSingleObject(pIniPort->DoneReading, INFINITE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonEndDocPort(
|
|
HANDLE hPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Language monitor EndDocPort
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
PINIJOB pIniJob;
|
|
|
|
if ( !pIniPort ||
|
|
pIniPort->signature != PJ_SIGNATURE ) {
|
|
|
|
SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Find the job (which is the last)
|
|
//
|
|
pIniJob = pIniPort->pIniJob;
|
|
|
|
if ( !pIniJob )
|
|
DBGMSG(DBG_ERROR, ("No jobs?\n"));
|
|
|
|
//
|
|
// check if we had sent PJL command, i.e. if the printer is bi-di
|
|
//
|
|
if ( pIniJob && (pIniJob->status & PP_PJL_SENT) ) {
|
|
|
|
//
|
|
// if the printer is bi-di, tell printer to let us know when the job
|
|
// is don't in the printer and we'll really EndDoc then. this is so
|
|
// that we can continue to monitor the job status until the job is
|
|
// really done in case there's an error occurs.
|
|
// but some cheap printers like 4L, doesn't handle this EOJ command
|
|
// reliably, so we time out if printer doesn't tell us EOJ after a
|
|
// while so that we don't end up having the port open forever in this
|
|
// case.
|
|
//
|
|
|
|
char string[256];
|
|
StringCchPrintfA (string, COUNTOF (string),
|
|
"\033%%-12345X@PJL EOJ NAME = \"MSJOB %d\"\015\012\033%%-12345X",
|
|
pIniPort->pIniJob->JobId);
|
|
WriteCommand(hPort, string);
|
|
pIniJob->TimeoutCount = GetTickCount();
|
|
pIniJob->status &= ~PP_INSTARTDOC;
|
|
}
|
|
|
|
(*pIniPort->fn.pfnEndDocPort)(pIniPort->hPort);
|
|
|
|
if ( pIniJob && !(pIniJob->status & PP_PJL_SENT) ) {
|
|
|
|
//
|
|
// This is not bi-di printer send EOJ so that spooler deletes it
|
|
//
|
|
SendJobLastPageEjected(pIniPort, pIniJob->JobId, FALSE);
|
|
}
|
|
|
|
pIniPort->status &= ~PP_INSTARTDOC;
|
|
|
|
// wake up the UStatus read thread if printer is bi-di
|
|
|
|
if ( pIniPort->status & PP_THREAD_RUNNING )
|
|
SetEvent(pIniPort->WakeUp);
|
|
|
|
SetEvent(pIniPort->DoneWriting);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonClosePort(
|
|
HANDLE hPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Language monitor ClosePort
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
|
|
if ( !pIniPort ||
|
|
pIniPort->signature != PJ_SIGNATURE ) {
|
|
|
|
SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
pIniPort->status &= ~PP_INSTARTDOC;
|
|
|
|
//
|
|
// Kill Ustatus thread if it is running
|
|
//
|
|
if (pIniPort-> hUstatusThread)
|
|
{
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
SetEvent(pIniPort->WakeUp);
|
|
WaitForSingleObject (pIniPort-> hUstatusThread, INFINITE);
|
|
}
|
|
if ( pIniPort->fn.pfnClosePort )
|
|
(*pIniPort->fn.pfnClosePort)(pIniPort->hPort);
|
|
|
|
EnterSplSem();
|
|
DeletePortEntry(pIniPort);
|
|
LeaveSplSem();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WriteCommand(
|
|
HANDLE hPort,
|
|
LPSTR cmd
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Write a command to the port
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
cmd : Command buffer
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
DWORD cbWrite, cbWritten, dwRet;
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
|
|
cbWrite = strlen(cmd);
|
|
|
|
dwRet = (*pIniPort->fn.pfnWritePort)(pIniPort->hPort, cmd, cbWrite, &cbWritten);
|
|
|
|
if ( dwRet ) {
|
|
|
|
pIniPort->status &= ~PP_WRITE_ERROR;
|
|
} else {
|
|
|
|
pIniPort->status |= PP_WRITE_ERROR;
|
|
DBGMSG(DBG_INFO, ("PJLMON!No data Written\n"));
|
|
if ( pIniPort->status & PP_THREAD_RUNNING )
|
|
SetEvent(pIniPort->WakeUp);
|
|
}
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
|
|
#define CBSTRING 1024
|
|
|
|
BOOL
|
|
ReadCommand(
|
|
HANDLE hPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Read a command from the port
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
|
|
Return Value:
|
|
TRUE on successfully reading one or more commands, FALSE on error
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
DWORD cbRead, cbToRead, cbProcessed, cbPrevious;
|
|
char string[CBSTRING];
|
|
DWORD status = STATUS_SYNTAX_ERROR; //Value should not matter
|
|
BOOL bRet=FALSE;
|
|
|
|
cbPrevious = 0;
|
|
|
|
ResetEvent(pIniPort->DoneReading);
|
|
|
|
cbToRead = CBSTRING - 1;
|
|
|
|
for ( ; ; ) {
|
|
|
|
if ( !PJLMonReadPort(hPort, &string[cbPrevious], cbToRead, &cbRead) )
|
|
break;
|
|
if ( cbRead ) {
|
|
if (cbPrevious + cbRead > CBSTRING - 1)
|
|
{
|
|
bRet = FALSE;
|
|
break;
|
|
}
|
|
string[cbPrevious + cbRead] = '\0';
|
|
status = ProcessPJLString(pIniPort, string, &cbProcessed);
|
|
if ( cbProcessed )
|
|
bRet = TRUE;
|
|
|
|
if (status == STATUS_END_OF_STRING ) {
|
|
|
|
if ( cbProcessed )
|
|
{
|
|
size_t cbUnprocessed = min (
|
|
strlen (string + cbProcessed) + 1,
|
|
COUNTOF (string) - cbProcessed
|
|
) * sizeof (string [0]);
|
|
memmove (string, string + cbProcessed, cbUnprocessed);
|
|
}
|
|
cbPrevious = cbRead + cbPrevious - cbProcessed;
|
|
}
|
|
} else {
|
|
|
|
SPLASSERT(!cbPrevious);
|
|
}
|
|
|
|
if ( status != STATUS_END_OF_STRING && cbRead != cbToRead )
|
|
break;
|
|
|
|
cbToRead = CBSTRING - cbPrevious - 1;
|
|
if ( cbToRead == 0 )
|
|
{
|
|
DBGMSG(DBG_ERROR,
|
|
("ReadCommand cbToRead is 0 (buffer too small)\n"));
|
|
bRet = FALSE;
|
|
break;
|
|
}
|
|
Sleep(WAIT_FOR_DATA_TIMEOUT);
|
|
}
|
|
|
|
SetEvent(pIniPort->DoneReading);
|
|
|
|
//
|
|
// Update the time we last read from printer
|
|
//
|
|
if ( bRet )
|
|
pIniPort->dwLastReadTime = GetTickCount();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
PJLMonGetPrinterDataFromPort(
|
|
HANDLE hPort,
|
|
DWORD ControlID,
|
|
LPTSTR pValueName,
|
|
LPTSTR lpInBuffer,
|
|
DWORD cbInBuffer,
|
|
LPTSTR lpOutBuffer,
|
|
DWORD cbOutBuffer,
|
|
LPDWORD lpcbReturned
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
GetPrinter data from port. Supports predefined commands/valuenames.
|
|
|
|
When we support Value name commands (not supported by DeviceIoControl)
|
|
we should check for startdoc
|
|
|
|
This monitor function supports the following two functionalities,
|
|
|
|
1. Allow spooler or language monitor to call DeviceIoControl to get
|
|
information from the port driver vxd, i.e. ControlID != 0.
|
|
And only port monitor support this functionality, language monitor
|
|
doesn't, so language monitor just pass this kind of calls down to
|
|
port monitor.
|
|
|
|
2. Allow app or printer driver query language monitor for some device
|
|
information by specifying some key names that both parties understand,
|
|
i.e. ControlID == 0 && pValueName != 0. So when printer driver call
|
|
DrvGetPrinterData DDI, gdi will call spooler -> language monitor
|
|
to get specific device information, for example, UNIDRV does this
|
|
to get installed printer memory from PJL printers thru PJLMON.
|
|
Only language monitor support this functionality,
|
|
port monitor doesn't.
|
|
|
|
Arguments:
|
|
hPort : Port handle
|
|
ControId : Control id
|
|
pValueName : Value name
|
|
lpInBuffer : Input buffer for the command
|
|
cbinBuffer : Input buffer size
|
|
lpOutBuffer : Output buffer
|
|
cbOutBuffer : Output buffer size
|
|
lpcbReturned : Set to the amount of data in output buffer on success
|
|
|
|
Return Value:
|
|
TRUE on success, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
|
|
BOOL bRet = FALSE, bStopUstatusThread = FALSE;
|
|
|
|
SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
|
|
if ( ControlID ) {
|
|
|
|
if ( !pIniPort->fn.pfnGetPrinterDataFromPort ) {
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
return (*pIniPort->fn.pfnGetPrinterDataFromPort)(
|
|
pIniPort->hPort,
|
|
ControlID,
|
|
pValueName,
|
|
lpInBuffer,
|
|
cbInBuffer,
|
|
lpOutBuffer,
|
|
cbOutBuffer,
|
|
lpcbReturned);
|
|
}
|
|
|
|
//
|
|
// Only 2 keys supported
|
|
//
|
|
if ( lstrcmpi(pValueName, cszInstalledMemory) &&
|
|
lstrcmpi(pValueName, cszAvailableMemory) ) {
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Wait for crrent job to print since we can't send a PJL command
|
|
// in the middle of job
|
|
//
|
|
WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
|
|
|
|
// make sure the first write succeeds
|
|
// The multi-language printers (4M, 4ML, 4MP, 4V, 4SI), if you print a
|
|
// PS print job, the memory resources claimed by the PS processor are not
|
|
// release until you enter PCL or reset the printer with "EscE".
|
|
//
|
|
// So if we had just printed a PS job, the available memory will be
|
|
// incorrect if we don't have the "EscE" here.
|
|
|
|
if ( (pIniPort->status & PP_IS_PJL) &&
|
|
WriteCommand(hPort, "\033E\033%-12345X@PJL INFO CONFIG\015\012") ) {
|
|
|
|
if ( !(pIniPort->status & PP_RUN_THREAD) ) {
|
|
|
|
bStopUstatusThread = TRUE;
|
|
CreateUstatusThread(pIniPort);
|
|
}
|
|
|
|
// PJLMON currently only supports the following pValueName
|
|
// 1. installed printer memory
|
|
// 2. available printer memory
|
|
|
|
if ( !lstrcmpi(pValueName, cszInstalledMemory) )
|
|
pIniPort->dwInstalledMemory = 0;
|
|
else if (!lstrcmpi(pValueName, cszAvailableMemory))
|
|
pIniPort->dwAvailableMemory = 0;
|
|
|
|
ResetEvent(pIniPort->DoneReading);
|
|
SetEvent(pIniPort->WakeUp);
|
|
WaitForSingleObject(pIniPort->DoneReading, READTHREADTIMEOUT);
|
|
|
|
WriteCommand(hPort,
|
|
"@PJL INFO MEMORY\015\012@PJL INFO STATUS\015\012");
|
|
|
|
ResetEvent(pIniPort->DoneReading);
|
|
SetEvent(pIniPort->WakeUp);
|
|
WaitForSingleObject(pIniPort->DoneReading, READTHREADTIMEOUT);
|
|
|
|
if ( bStopUstatusThread ) {
|
|
|
|
pIniPort->status &= ~PP_RUN_THREAD;
|
|
SetEvent(pIniPort->WakeUp);
|
|
}
|
|
|
|
if ( !lstrcmpi(pValueName, cszInstalledMemory) ) {
|
|
|
|
*lpcbReturned = sizeof(DWORD);
|
|
|
|
if ( lpOutBuffer &&
|
|
cbOutBuffer >= sizeof(DWORD) &&
|
|
pIniPort->dwInstalledMemory ) {
|
|
|
|
*((LPDWORD)lpOutBuffer) = pIniPort->dwInstalledMemory;
|
|
|
|
bRet = TRUE;
|
|
}
|
|
} else if ( !lstrcmpi(pValueName, cszAvailableMemory) ) {
|
|
|
|
*lpcbReturned = sizeof(DWORD);
|
|
|
|
if ( lpOutBuffer &&
|
|
cbOutBuffer >= sizeof(DWORD) &&
|
|
pIniPort->dwAvailableMemory)
|
|
{
|
|
*((LPDWORD)lpOutBuffer) = pIniPort->dwAvailableMemory;
|
|
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( bStopUstatusThread ) {
|
|
WaitForSingleObject (pIniPort-> hUstatusThread, INFINITE);
|
|
}
|
|
|
|
}
|
|
|
|
if ( !bRet )
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
SetEvent(pIniPort->DoneWriting);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
MONITOREX MonitorEx = {
|
|
sizeof(MONITOR),
|
|
{
|
|
NULL, // EnumPrinters not supported
|
|
NULL, // OpenPort not supported
|
|
PJLMonOpenPortEx,
|
|
PJLMonStartDocPort,
|
|
PJLMonWritePort,
|
|
PJLMonReadPort,
|
|
PJLMonEndDocPort,
|
|
PJLMonClosePort,
|
|
NULL, // AddPort not supported
|
|
NULL, // AddPortEx not supported
|
|
NULL, // ConfigurePort not supported
|
|
NULL, // DeletePort not supported
|
|
PJLMonGetPrinterDataFromPort,
|
|
NULL // SetPortTimeOuts not supported
|
|
}
|
|
};
|
|
|
|
|
|
LPMONITOREX
|
|
WINAPI
|
|
InitializePrintMonitor(
|
|
IN LPTSTR pszRegistryRoot
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Fill the monitor function table. Spooler makes call to this routine
|
|
to get the monitor functions.
|
|
|
|
Arguments:
|
|
pszRegistryRoot : Registry root to be used by this dll
|
|
lpMonitor : Pointer to monitor fucntion table to be filled
|
|
|
|
Return Value:
|
|
TRUE on successfully initializing the monitor, false on error.
|
|
|
|
--*/
|
|
{
|
|
|
|
if ( !pszRegistryRoot || !*pszRegistryRoot ) {
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return NULL;
|
|
}
|
|
|
|
if ( UpdateTimeoutsFromRegistry(pszRegistryRoot) != ERROR_SUCCESS ) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
return &MonitorEx;
|
|
}
|
|
|
|
|
|
#define NTOKEN 20
|
|
|
|
DWORD
|
|
ProcessPJLString(
|
|
PINIPORT pIniPort,
|
|
LPSTR pInString,
|
|
DWORD *lpcbProcessed
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Process a PJL string read from the printer
|
|
|
|
Arguments:
|
|
pIniPort : Ini port
|
|
pInString : Input string to process
|
|
lpcbProcessed : On return set to the amount of data processed
|
|
|
|
Return Value:
|
|
Status value of the processing
|
|
|
|
--*/
|
|
{
|
|
TOKENPAIR tokenPairs[NTOKEN];
|
|
DWORD nTokenParsedRet;
|
|
LPSTR lpRet;
|
|
DWORD status = 0;
|
|
|
|
lpRet = pInString;
|
|
|
|
DBGMSG(DBG_TRACE, ("String to process: <"TSTR">\n", pInString));
|
|
|
|
for (*lpcbProcessed = 0; *pInString != 0; pInString = lpRet) {
|
|
|
|
//
|
|
// Determine if printer is bi-di. LJ 4 does not have p1284
|
|
// device ID so we do PCL memory query and see if it returns anything
|
|
//
|
|
if (!(pIniPort->status & PP_IS_PJL) &&
|
|
!mystrncmp(pInString, "PCL\015\012INFO MEMORY", 16) )
|
|
pIniPort->status |= PP_IS_PJL;
|
|
|
|
status = GetPJLTokens(pInString, NTOKEN, tokenPairs,
|
|
&nTokenParsedRet, &lpRet);
|
|
|
|
if (status == STATUS_REACHED_END_OF_COMMAND_OK) {
|
|
|
|
pIniPort->status |= PP_IS_PJL;
|
|
InterpreteTokens(pIniPort, tokenPairs, nTokenParsedRet);
|
|
} else {
|
|
|
|
ProcessParserError(status);
|
|
}
|
|
|
|
//
|
|
// if a PJL command straddles between buffers
|
|
//
|
|
if (status == STATUS_END_OF_STRING)
|
|
break;
|
|
|
|
*lpcbProcessed += (DWORD)(lpRet - pInString);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
DWORD
|
|
SeverityFromPjlStatus(
|
|
DWORD dwPjlStatus
|
|
)
|
|
{
|
|
if ( dwPjlStatus >= 10000 && dwPjlStatus < 12000 ) {
|
|
|
|
//
|
|
// 10xyz
|
|
// 11xyz : load paper (paper available on another tray)
|
|
//
|
|
return PORT_STATUS_TYPE_WARNING;
|
|
} else if ( dwPjlStatus >= 30000 && dwPjlStatus < 31000 ) {
|
|
|
|
//
|
|
// 30xyz : Auto continuable errors
|
|
//
|
|
return PORT_STATUS_TYPE_WARNING;
|
|
|
|
} else if ( dwPjlStatus >= 35000 && dwPjlStatus < 36000 ) {
|
|
|
|
//
|
|
// 35xyz : Potential operator intervention conditions
|
|
//
|
|
return PORT_STATUS_TYPE_WARNING;
|
|
} else if ( dwPjlStatus > 40000 && dwPjlStatus < 42000 ) {
|
|
|
|
//
|
|
// 40xyz : Operator intervention required
|
|
// 41xyz : Load paper errors
|
|
//
|
|
return PORT_STATUS_TYPE_ERROR;
|
|
}
|
|
|
|
DBGMSG(DBG_ERROR,
|
|
("SeverityFromPjlStatus: Unknown status %d\n", dwPjlStatus));
|
|
return PORT_STATUS_TYPE_INFO;
|
|
}
|
|
|
|
|
|
VOID
|
|
InterpreteTokens(
|
|
PINIPORT pIniPort,
|
|
PTOKENPAIR tokenPairs,
|
|
DWORD nTokenParsed
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Interpret succesfully read PJL tokens
|
|
|
|
Arguments:
|
|
pIniPort : Ini port
|
|
tokenPairs : List of token pairs
|
|
nTokenParsed : Number of token pairs
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
DWORD i, OldStatus;
|
|
PJLTOPRINTERSTATUS *pMap;
|
|
PORT_INFO_3 PortInfo3;
|
|
DWORD dwSeverity = 0;
|
|
HANDLE hToken;
|
|
|
|
OldStatus = pIniPort->PrinterStatus;
|
|
pIniPort->PrinterStatus = 0;
|
|
|
|
for (i = 0; i < nTokenParsed; i++) {
|
|
|
|
// DBGMSG(DBG_INFO, ("pjlmon!Token=0x%x, Value=%d\n",
|
|
// tokenPairs[i].token, tokenPairs[i].value));
|
|
|
|
switch(tokenPairs[i].token) {
|
|
|
|
case TOKEN_INFO_STATUS_CODE:
|
|
case TOKEN_USTATUS_DEVICE_CODE:
|
|
|
|
for (pMap = PJLToStatus; pMap->pjl; pMap++) {
|
|
|
|
if (pMap->pjl == tokenPairs[i].value) {
|
|
|
|
pIniPort->PrinterStatus = pMap->status;
|
|
dwSeverity = SeverityFromPjlStatus(pMap->pjl);
|
|
if ( dwSeverity == PORT_STATUS_TYPE_ERROR )
|
|
pIniPort->status |= PP_PRINTER_OFFLINE;
|
|
else
|
|
pIniPort->status &= ~PP_PRINTER_OFFLINE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( pMap->pjl && pMap->pjl == tokenPairs[i].value )
|
|
break;
|
|
|
|
//
|
|
// some printers use this to signal online/ready
|
|
//
|
|
if ( tokenPairs[i].value == 10001 ||
|
|
tokenPairs[i].value == 10002 ||
|
|
tokenPairs[i].value == 11002 ) {
|
|
|
|
pIniPort->status &= ~PP_PRINTER_OFFLINE;
|
|
pIniPort->PrinterStatus = 0;
|
|
dwSeverity = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// background or foreground paper out
|
|
//
|
|
if ( tokenPairs[i].value > 11101 && tokenPairs[i].value < 12000 ||
|
|
tokenPairs[i].value > 41101 && tokenPairs[i].value < 42000 ) {
|
|
|
|
pIniPort->PrinterStatus = PORT_STATUS_PAPER_OUT;
|
|
|
|
if ( tokenPairs[i].value > 4000 ) {
|
|
|
|
dwSeverity = PORT_STATUS_TYPE_ERROR;
|
|
pIniPort->status |= PP_PRINTER_OFFLINE;
|
|
} else {
|
|
|
|
dwSeverity = PORT_STATUS_TYPE_WARNING;
|
|
}
|
|
} else if (tokenPairs[i].value > 40000) {
|
|
|
|
pIniPort->PrinterStatus = PORT_STATUS_USER_INTERVENTION;
|
|
pIniPort->status |= PP_PRINTER_OFFLINE;
|
|
dwSeverity = PORT_STATUS_TYPE_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
case TOKEN_INFO_STATUS_ONLINE:
|
|
case TOKEN_USTATUS_DEVICE_ONLINE:
|
|
|
|
// DBGMSG(DBG_INFO, ("PJLMON:ONLINE = %d\n", tokenPairs[i].value));
|
|
|
|
if (tokenPairs[i].value) {
|
|
|
|
pIniPort->status &= ~PP_PRINTER_OFFLINE;
|
|
dwSeverity = pIniPort->PrinterStatus ? PORT_STATUS_TYPE_WARNING :
|
|
0;
|
|
} else {
|
|
|
|
if ( !pIniPort->PrinterStatus )
|
|
pIniPort->PrinterStatus = PORT_STATUS_OFFLINE;
|
|
pIniPort->status |= PP_PRINTER_OFFLINE;
|
|
dwSeverity = PORT_STATUS_TYPE_ERROR;
|
|
}
|
|
break;
|
|
|
|
case TOKEN_USTATUS_JOB_NAME_MSJOB:
|
|
|
|
DBGMSG(DBG_TRACE, ("EOJ for %d\n", tokenPairs[i].value));
|
|
|
|
SendJobLastPageEjected(pIniPort, (DWORD)tokenPairs[i].value, FALSE);
|
|
break;
|
|
|
|
case TOKEN_INFO_CONFIG_MEMORY:
|
|
case TOKEN_INFO_CONFIG_MEMORY_SPACE:
|
|
|
|
// IMPORTANT NOTE:
|
|
//
|
|
// Use SetPrinterData to cache the information in printer's registry.
|
|
// GDI's DrvGetPrinterData will check the printer's registry first,
|
|
// and if cache data is available, it will use it and not call
|
|
// GetPrinterData (which calls language monitor's
|
|
// GetPrinterDataFromPort).
|
|
|
|
DBGMSG(DBG_TRACE, ("PJLMON installed memory %d\n", tokenPairs[i].value));
|
|
|
|
pIniPort->dwInstalledMemory = (DWORD)tokenPairs[i].value;
|
|
break;
|
|
|
|
case TOKEN_INFO_MEMORY_TOTAL:
|
|
|
|
// IMPORTANT NOTE:
|
|
//
|
|
// Use SetPrinterData to cache the information in printer's registry.
|
|
// GDI's DrvGetPrinterData will check the printer's registry first,
|
|
// and if cache data is available, it will use it and not call
|
|
// GetPrinterData (which calls language monitor's
|
|
// GetPrinterDataFromPort).
|
|
|
|
DBGMSG(DBG_TRACE, ("PJLMON available memory %d\n", tokenPairs[i].value));
|
|
|
|
pIniPort->dwAvailableMemory = (DWORD)tokenPairs[i].value;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( OldStatus != pIniPort->PrinterStatus ) {
|
|
|
|
ZeroMemory(&PortInfo3, sizeof(PortInfo3));
|
|
PortInfo3.dwStatus = pIniPort->PrinterStatus;
|
|
PortInfo3.dwSeverity = dwSeverity;
|
|
|
|
if ( !SetPort(NULL,
|
|
pIniPort->pszPortName,
|
|
3,
|
|
(LPBYTE)&PortInfo3) ) {
|
|
|
|
DBGMSG(DBG_WARNING,
|
|
("pjlmon: SetPort failed %d (LE: %d)\n",
|
|
pIniPort->PrinterStatus, GetLastError()));
|
|
|
|
pIniPort->PrinterStatus = OldStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ProcessParserError(
|
|
DWORD status
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Print error messages on parsing error
|
|
|
|
Arguments:
|
|
status : status
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
#ifdef DEBUG
|
|
LPSTR pString;
|
|
|
|
switch (status)
|
|
{
|
|
case STATUS_REACHED_END_OF_COMMAND_OK:
|
|
pString = "STATUS_REACHED_END_OF_COMMAND_OK\n";
|
|
break;
|
|
|
|
case STATUS_CONTINUE:
|
|
pString = "STATUS_CONTINUE\n";
|
|
break;
|
|
|
|
case STATUS_REACHED_FF:
|
|
pString = "STATUS_REACHED_FF\n";
|
|
break;
|
|
|
|
case STATUS_END_OF_STRING:
|
|
pString = "STATUS_END_OF_STRING\n";
|
|
break;
|
|
|
|
case STATUS_SYNTAX_ERROR:
|
|
pString = "STATUS_SYNTAX_ERROR\n";
|
|
break;
|
|
|
|
case STATUS_ATPJL_NOT_FOUND:
|
|
pString = "STATUS_ATPJL_NOT_FOUND\n";
|
|
break;
|
|
|
|
case STATUS_NOT_ENOUGH_ROOM_FOR_TOKENS:
|
|
pString = "STATUS_NOT_ENOUGH_ROOM_FOR_TOKENS\n";
|
|
break;
|
|
|
|
default:
|
|
pString = "INVALID STATUS RETURNED!!!!!!\n";
|
|
break;
|
|
};
|
|
|
|
OutputDebugStringA(pString);
|
|
#endif
|
|
}
|
|
|
|
|
|
#define MODEL "MODEL:"
|
|
#define MDL "MDL:"
|
|
#define COMMAND "COMMAND SET:"
|
|
#define CMD "CMD:"
|
|
#define COLON ':'
|
|
#define SEMICOLON ';'
|
|
|
|
|
|
LPSTR
|
|
FindP1284Key(
|
|
PINIPORT pIniPort,
|
|
LPSTR lpKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Find the 1284 key identifying the device id
|
|
|
|
Arguments:
|
|
status : status
|
|
|
|
Return Value:
|
|
Pointer to the command string, NULL if not found.
|
|
|
|
--*/
|
|
{
|
|
LPSTR lpValue; // Pointer to the Key's value
|
|
WORD wKeyLength; // Length for the Key (for stringcmps)
|
|
LPSTR ret = NULL;
|
|
|
|
// While there are still keys to look at.
|
|
|
|
DBGMSG(DBG_TRACE, ("PJLMon!DeviceId : <"TSTR">\n", lpKey));
|
|
|
|
while (lpKey && *lpKey) {
|
|
|
|
//
|
|
// Is there a terminating COLON character for the current key?
|
|
//
|
|
if (!(lpValue = mystrchr(lpKey, COLON)) ) {
|
|
|
|
//
|
|
// Something is wrong with the Device ID
|
|
//
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// The actual start of the Key value is one past the COLON
|
|
//
|
|
++lpValue;
|
|
|
|
//
|
|
// Compute the Key length for Comparison, including the COLON
|
|
// which will serve as a terminator
|
|
//
|
|
wKeyLength = (WORD)(lpValue - lpKey);
|
|
|
|
//
|
|
// Compare the Key to the Know quantities. To speed up the comparison
|
|
// a Check is made on the first character first, to reduce the number
|
|
// of strings to compare against.
|
|
// If a match is found, the appropriate lpp parameter is set to the
|
|
// key's value, and the terminating SEMICOLON is converted to a NULL
|
|
// In all cases lpKey is advanced to the next key if there is one.
|
|
//
|
|
if ( *lpKey == 'C' ) {
|
|
|
|
//
|
|
// Look for COMMAND SET or CMD
|
|
//
|
|
if ( !mystrncmp(lpKey, COMMAND, wKeyLength) ||
|
|
!mystrncmp(lpKey, CMD, wKeyLength) ) {
|
|
|
|
ret = lpValue;
|
|
}
|
|
}
|
|
|
|
// Go to the next Key
|
|
|
|
if ( lpKey = mystrchr(lpValue, SEMICOLON) ) {
|
|
|
|
*lpKey = '\0';
|
|
++lpKey;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsPJL(
|
|
PINIPORT pIniPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Finds out if the printer is a PJL bi-di printer
|
|
|
|
Arguments:
|
|
pIniPort : Points to an INIPORT
|
|
|
|
Return Value:
|
|
TRUE if printer is PJL bi-di, else FALSE
|
|
|
|
On failure PP_DONT_TRY_PJL is set
|
|
|
|
--*/
|
|
{
|
|
char szID[MAX_DEVID];
|
|
DWORD cbRet;
|
|
LPSTR lpCMD;
|
|
HANDLE hPort = (HANDLE)pIniPort;
|
|
BOOL bRet = FALSE;
|
|
|
|
//
|
|
// for printers that supports P1284 plug and play like LJ 4L, DJ540.
|
|
// we parse the COMMAND string and see if PJL is supported
|
|
//
|
|
if (pIniPort->fn.pfnGetPrinterDataFromPort) {
|
|
|
|
//
|
|
// Only try P1284 if port monitor supports DeviceIOCtl
|
|
//
|
|
memset((LPBYTE)szID, 0, sizeof(szID));
|
|
cbRet = 0;
|
|
if ((*pIniPort->fn.pfnGetPrinterDataFromPort)
|
|
(pIniPort->hPort, GETDEVICEID, NULL, NULL,
|
|
0, (LPWSTR)szID, sizeof(szID), &cbRet)
|
|
&& cbRet) {
|
|
|
|
//
|
|
// succeeded the P1284 plug and play protocol
|
|
//
|
|
szID[cbRet] = '\0';
|
|
|
|
if ( lpCMD = FindP1284Key(pIniPort, szID) ) {
|
|
|
|
// found the COMMAND string
|
|
|
|
while (*lpCMD) {
|
|
|
|
//
|
|
// look for "PJL"
|
|
//
|
|
if ( lpCMD[0] == 'P' && lpCMD[1] == 'J' && lpCMD[2] == 'L' ){
|
|
|
|
pIniPort->status &= ~PP_DONT_TRY_PJL;
|
|
bRet = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
lpCMD++;
|
|
}
|
|
|
|
pIniPort->status |= PP_DONT_TRY_PJL;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// fall thru to try PJL bi-di if we failed the P1284 communication
|
|
// or P1284 didn't return a COMMAND string
|
|
//
|
|
}
|
|
|
|
//
|
|
// for printers that don't support P1284 plug and play, but support PJL
|
|
// language command, like LJ 4 and 4M. we try to write/read PJL
|
|
// command and see if it succeeds.
|
|
// if we can't set the time outs we don't want to try to read, just fail.
|
|
//
|
|
if ( pIniPort->fn.pfnSetPortTimeOuts &&
|
|
!(pIniPort->status & PP_DONT_TRY_PJL)) {
|
|
|
|
COMMTIMEOUTS CTO;
|
|
|
|
memset((LPSTR)&CTO, 0, sizeof(CTO));
|
|
CTO.ReadTotalTimeoutConstant = 5000;
|
|
CTO.ReadIntervalTimeout = 200;
|
|
if ( !(*pIniPort->fn.pfnSetPortTimeOuts)(pIniPort->hPort, &CTO, 0) ) {
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
// This <ESC>*s1M is a PCL5 command to determine the amount of memory
|
|
// in a PCL5 printer, and if the printer is PCL5 and bi-di capable,
|
|
// it will return "PCL\015\012INFO MEMORY".
|
|
// See PJL Tech Ref Manual page 7-21.
|
|
|
|
pIniPort->status &= ~PP_IS_PJL;
|
|
|
|
if ( !WriteCommand(hPort, "\033*s1M") )
|
|
goto Cleanup;
|
|
|
|
// ReadCommand->ProcessPJLString will set PP_IS_PJL
|
|
// if we read any valid PJL command back from the printer
|
|
|
|
if ( !ReadCommand(hPort) ) {
|
|
|
|
//
|
|
// We have jumped through the hoop to determin if this printer can
|
|
// understand PJL. It DOES NOT. We are not going to try again.
|
|
// until there is a printer change.
|
|
//
|
|
pIniPort->status |= PP_DONT_TRY_PJL;
|
|
}
|
|
|
|
if (pIniPort->status & PP_IS_PJL) {
|
|
|
|
bRet = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
if ( bRet ) {
|
|
|
|
WriteCommand(hPort, "\033%-12345X@PJL \015\012@PJL USTATUS TIMED 30 \015\012\033%-12345X");
|
|
pIniPort->dwLastReadTime = GetTickCount();
|
|
}
|
|
|
|
return bRet;
|
|
}
|