Leaked source code of windows server 2003
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

  1. /*++
  2. Copyright (c) 1990-2003 Microsoft Corporation
  3. All Rights Reserved
  4. Module Name:
  5. Abstract:
  6. Author:
  7. Revision History:
  8. --*/
  9. #define USECOMM
  10. #include "precomp.h"
  11. #include <winioctl.h>
  12. #include <ntddpar.h>
  13. // ---------------------------------------------------------------------
  14. // PROTO, CONSTANT, and GLOBAL
  15. //
  16. // ---------------------------------------------------------------------
  17. DWORD ProcessPJLString(PINIPORT, CHAR *, DWORD *);
  18. VOID ProcessParserError(DWORD);
  19. VOID InterpreteTokens(PINIPORT, PTOKENPAIR, DWORD);
  20. BOOL IsPJL(PINIPORT);
  21. BOOL WriteCommand(HANDLE, LPSTR);
  22. BOOL ReadCommand(HANDLE);
  23. #define WAIT_FOR_WRITE 100 // 0.1 sec
  24. #define WAIT_FOR_DATA_TIMEOUT 100 // 0.1 sec
  25. #define WAIT_FOR_USTATUS_THREAD_TIMEOUT 500 // 0.5 sec
  26. #define GETDEVICEID IOCTL_PAR_QUERY_DEVICE_ID
  27. #define MAX_DEVID 1024
  28. TCHAR cszInstalledMemory[] = TEXT("Installed Memory");
  29. TCHAR cszAvailableMemory[] = TEXT("Available Memory");
  30. BOOL
  31. DllMain(
  32. IN HANDLE hModule,
  33. IN DWORD dwReason,
  34. IN LPVOID lpRes
  35. )
  36. /*++
  37. Routine Description:
  38. Dll entry point
  39. Arguments:
  40. Return Value:
  41. --*/
  42. {
  43. UNREFERENCED_PARAMETER(lpRes);
  44. switch (dwReason) {
  45. case DLL_PROCESS_ATTACH:
  46. InitializeCriticalSection(&pjlMonSection);
  47. DisableThreadLibraryCalls(hModule);
  48. break;
  49. default:
  50. // do nothing
  51. ;
  52. }
  53. return TRUE;
  54. }
  55. VOID
  56. ClearPrinterStatusAndIniJobs(
  57. PINIPORT pIniPort
  58. )
  59. {
  60. PORT_INFO_3 PortInfo3;
  61. if ( pIniPort->PrinterStatus ||
  62. (pIniPort->status & PP_PRINTER_OFFLINE) ) {
  63. pIniPort->PrinterStatus = 0;
  64. pIniPort->status &= ~PP_PRINTER_OFFLINE;
  65. ZeroMemory(&PortInfo3, sizeof(PortInfo3));
  66. SetPort(NULL, pIniPort->pszPortName, 3, (LPBYTE)&PortInfo3);
  67. }
  68. SendJobLastPageEjected(pIniPort, ALL_JOBS, FALSE);
  69. }
  70. VOID
  71. RefreshPrinterInfo(
  72. PINIPORT pIniPort
  73. )
  74. {
  75. //
  76. // Only one thread should write to the printer at a time
  77. //
  78. if ( WAIT_OBJECT_0 != WaitForSingleObject(pIniPort->DoneWriting,
  79. WAIT_FOR_WRITE) ) {
  80. return;
  81. }
  82. //
  83. // If printer is power cycled and it does not talk back (but answers
  84. // PnP id) we got to clear the error on spooler to send jobs
  85. //
  86. ClearPrinterStatusAndIniJobs(pIniPort);
  87. if ( !IsPJL(pIniPort) ) {
  88. pIniPort->status &= ~PP_IS_PJL;
  89. }
  90. SetEvent(pIniPort->DoneWriting);
  91. }
  92. VOID
  93. UstatusThread(
  94. HANDLE hPort
  95. )
  96. /*++
  97. Routine Description:
  98. Unsolicited status information thread. This thread will continue to
  99. read unsolicited until it's asked to terminate, which will happen
  100. under one of these conditions:
  101. 1) Receive EOJ confirmation from the printer.
  102. 2) Timeout waiting for EOJ confirmation.
  103. 3) The port is been closed.
  104. Arguments:
  105. hPort : IniPort structure for the port
  106. Return Value:
  107. --*/
  108. {
  109. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  110. HANDLE hToken;
  111. SPLASSERT(pIniPort &&
  112. pIniPort->signature == PJ_SIGNATURE &&
  113. (pIniPort->status & PP_THREAD_RUNNING) == 0);
  114. if ( IsPJL(pIniPort) )
  115. pIniPort->status |= PP_IS_PJL;
  116. SetEvent(pIniPort->DoneWriting);
  117. if ( !(pIniPort->status & PP_IS_PJL) )
  118. goto StopThread;
  119. pIniPort->status |= PP_THREAD_RUNNING;
  120. pIniPort->PrinterStatus = 0;
  121. pIniPort->status &= ~PP_PRINTER_OFFLINE;
  122. pIniPort->dwLastReadTime = GetTickCount ();
  123. for ( ; ; ) {
  124. //
  125. // check if PP_RUN_THREAD has been cleared to terminate
  126. //
  127. if ( !(pIniPort->status & PP_RUN_THREAD) ) {
  128. if ( pIniPort->status & PP_INSTARTDOC ) {
  129. //
  130. // there's an active job, can't end the thread
  131. //
  132. pIniPort->status |= PP_RUN_THREAD;
  133. } else {
  134. DBGMSG(DBG_INFO,
  135. ("PJLMon Read Thread for Port %ws Closing Down.\n",
  136. pIniPort->pszPortName));
  137. pIniPort->status &= ~PP_THREAD_RUNNING;
  138. ClearPrinterStatusAndIniJobs(pIniPort);
  139. goto StopThread;
  140. }
  141. }
  142. //
  143. // check if the printer is bi-di
  144. //
  145. if (pIniPort->status & PP_IS_PJL) {
  146. (VOID)ReadCommand(hPort);
  147. //
  148. // If we are under error condition or if we have jobs pending
  149. // read status back from printer more frequently
  150. //
  151. if ( pIniPort->pIniJob ||
  152. (pIniPort->status & PP_PRINTER_OFFLINE) ||
  153. (pIniPort->status & PP_WRITE_ERROR) ) {
  154. WaitForSingleObject(pIniPort->WakeUp,
  155. dwReadThreadErrorTimeout);
  156. } else {
  157. WaitForSingleObject(pIniPort->WakeUp,
  158. dwReadThreadIdleTimeoutOther);
  159. }
  160. if ( pIniPort->pIniJob &&
  161. !(pIniPort->status & PP_PRINTER_OFFLINE) &&
  162. !(pIniPort->status & PP_WRITE_ERROR) ) {
  163. //
  164. // Some printers are PJL bi-di, but do not send
  165. // EOJ. We want jobs to disappear from printman
  166. //
  167. SendJobLastPageEjected(pIniPort,
  168. GetTickCount() - dwReadThreadEOJTimeout,
  169. TRUE);
  170. }
  171. //
  172. // If we did not read from printer for more than a minute
  173. // and no more jobs talk to printer again
  174. //
  175. if ( !(pIniPort->status & PP_INSTARTDOC) &&
  176. (GetTickCount() - pIniPort->dwLastReadTime) > 240000
  177. )
  178. RefreshPrinterInfo(pIniPort);
  179. } else {
  180. //
  181. // exit the thread if printer is not PJL bi-di capable
  182. //
  183. Sleep(2000);
  184. pIniPort->status &= ~PP_RUN_THREAD;
  185. DBGMSG(DBG_TRACE, ("Set ~PP_RUN_THREAD because printer is not bi-di\n"));
  186. }
  187. }
  188. StopThread:
  189. pIniPort->status &= ~PP_RUN_THREAD;
  190. pIniPort->status &= ~PP_THREAD_RUNNING;
  191. }
  192. BOOL
  193. CreateUstatusThread(
  194. PINIPORT pIniPort
  195. )
  196. /*++
  197. Routine Description:
  198. Creates the Ustatus thread
  199. Arguments:
  200. pIniPort : IniPort structure for the port
  201. Return Value:
  202. TRUE on succesfully creating the thread, else FALSE
  203. --*/
  204. {
  205. HANDLE ThreadHandle;
  206. DWORD ThreadId;
  207. DBGMSG(DBG_INFO, ("PJLMon Read Thread for Port %ws Starting.\n",
  208. pIniPort->pszPortName));
  209. if (pIniPort->hUstatusThread)
  210. {
  211. //
  212. // Make sure there is no running UstatusThread
  213. //
  214. pIniPort->status &= ~PP_RUN_THREAD;
  215. SetEvent (pIniPort->WakeUp);
  216. WaitForSingleObject (pIniPort->hUstatusThread, INFINITE);
  217. CloseHandle (pIniPort->hUstatusThread);
  218. pIniPort->hUstatusThread = NULL;
  219. }
  220. pIniPort->status |= PP_RUN_THREAD;
  221. WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
  222. //
  223. // Initialize events
  224. //
  225. ResetEvent (pIniPort->WakeUp);
  226. SetEvent (pIniPort->DoneReading);
  227. ThreadHandle = CreateThread(NULL, 16*1024,
  228. (LPTHREAD_START_ROUTINE)UstatusThread,
  229. pIniPort,
  230. 0, &ThreadId);
  231. if ( ThreadHandle ) {
  232. SetThreadPriority(ThreadHandle, THREAD_PRIORITY_LOWEST);
  233. pIniPort-> hUstatusThread = ThreadHandle;
  234. return TRUE;
  235. }
  236. else
  237. {
  238. pIniPort->status &= ~PP_RUN_THREAD;
  239. SetEvent(pIniPort->DoneWriting);
  240. return FALSE;
  241. }
  242. }
  243. BOOL
  244. WINAPI
  245. PJLMonOpenPortEx(
  246. IN LPTSTR pszPortName,
  247. IN LPTSTR pszPrinterName,
  248. IN OUT LPHANDLE pHandle,
  249. IN OUT LPMONITOR pMonitor
  250. )
  251. /*++
  252. Routine Description:
  253. Opens the port
  254. Arguments:
  255. pszPortName : Port name
  256. pszPrinterName : Printer name
  257. pHandle : Pointer to the handle to return
  258. pMonitor : Port monitor function table
  259. Return Value:
  260. TRUE on success, FALSE on error
  261. --*/
  262. {
  263. PINIPORT pIniPort;
  264. BOOL bRet = FALSE;
  265. BOOL bInSem = FALSE;
  266. //
  267. // Validate port monitor
  268. //
  269. if ( !pMonitor ||
  270. !pMonitor->pfnOpenPort ||
  271. !pMonitor->pfnStartDocPort ||
  272. !pMonitor->pfnWritePort ||
  273. !pMonitor->pfnReadPort ||
  274. !pMonitor->pfnClosePort ) {
  275. DBGMSG(DBG_WARNING,
  276. ("PjlMon: Invalid port monitors passed to OpenPortEx\n"));
  277. SetLastError(ERROR_INVALID_PRINT_MONITOR);
  278. goto Cleanup;
  279. }
  280. EnterSplSem();
  281. bInSem = TRUE;
  282. //
  283. // Is the port open already?
  284. //
  285. if ( pIniPort = FindIniPort(pszPortName) ) {
  286. SetLastError(ERROR_BUSY);
  287. goto Cleanup;
  288. }
  289. pIniPort = CreatePortEntry(pszPortName);
  290. LeaveSplSem();
  291. bInSem = FALSE;
  292. if ( pIniPort &&
  293. (*pMonitor->pfnOpenPort)(pszPortName, &pIniPort->hPort) ) {
  294. *pHandle = pIniPort;
  295. CopyMemory((LPBYTE)&pIniPort->fn, (LPBYTE)pMonitor, sizeof(*pMonitor));
  296. //
  297. // Create the ustatus thread always
  298. // If printer is not PJL it will die by itself
  299. // We do not want to write to the printer in this thread to determine
  300. // printer is PJL since that may take several seconds to fail
  301. //
  302. CreateUstatusThread(pIniPort);
  303. bRet = TRUE;
  304. } else {
  305. DBGMSG(DBG_WARNING, ("PjlMon: OpenPort "TSTR" : Failed\n", pszPortName));
  306. }
  307. Cleanup:
  308. if ( bInSem ) {
  309. LeaveSplSem();
  310. }
  311. SplOutSem();
  312. return bRet;
  313. }
  314. BOOL
  315. WINAPI
  316. PJLMonStartDocPort(
  317. IN HANDLE hPort,
  318. IN LPTSTR pszPrinterName,
  319. IN DWORD dwJobId,
  320. IN DWORD dwLevel,
  321. IN LPBYTE pDocInfo
  322. )
  323. /*++
  324. Routine Description:
  325. Language monitor StartDocPort
  326. Arguments:
  327. hPort : Port handle
  328. pszPrinterName : Printer name
  329. dwJobId : Job identifier
  330. dwLevel : Level of Doc info strucuture
  331. pDocInfo : Pointer to doc info structure
  332. Return Value:
  333. TRUE on success, FALSE on error
  334. --*/
  335. {
  336. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  337. PINIJOB pIniJob = NULL;
  338. DWORD cbJob;
  339. BOOL bRet = FALSE;
  340. //
  341. // Validate parameters
  342. //
  343. if ( !pIniPort ||
  344. pIniPort->signature != PJ_SIGNATURE ||
  345. !pDocInfo ||
  346. !pszPrinterName ||
  347. !*pszPrinterName ) {
  348. SPLASSERT(pIniPort &&
  349. pIniPort->signature == PJ_SIGNATURE &&
  350. pDocInfo);
  351. SetLastError(ERROR_INVALID_PARAMETER);
  352. return FALSE;
  353. }
  354. if ( dwLevel != 1 ) {
  355. SPLASSERT(dwLevel == 1);
  356. SetLastError(ERROR_INVALID_LEVEL);
  357. return FALSE;
  358. }
  359. //
  360. // Serialize access to the port
  361. //
  362. if ( pIniPort->status & PP_INSTARTDOC ) {
  363. SetLastError(ERROR_BUSY);
  364. return FALSE;
  365. }
  366. cbJob = sizeof(*pIniJob) + lstrlen(pszPrinterName) * sizeof(TCHAR)
  367. + sizeof(TCHAR);
  368. pIniJob = (PINIJOB) AllocSplMem(cbJob);
  369. if ( !pIniJob ) {
  370. goto Cleanup;
  371. }
  372. pIniJob->pszPrinterName = (LPTSTR) (pIniJob + 1);
  373. StringCbCopy (pIniJob->pszPrinterName, cbJob - sizeof (*pIniJob), pszPrinterName);
  374. if ( !OpenPrinter(pIniJob->pszPrinterName, &pIniJob->hPrinter, NULL) ) {
  375. DBGMSG(DBG_WARNING,
  376. ("pjlmon: OpenPrinter failed for "TSTR", last error %d\n",
  377. pIniJob->pszPrinterName, GetLastError()));
  378. goto Cleanup;
  379. }
  380. pIniPort->status |= PP_INSTARTDOC;
  381. bRet = (*pIniPort->fn.pfnStartDocPort)(pIniPort->hPort,
  382. pszPrinterName,
  383. dwJobId,
  384. dwLevel,
  385. pDocInfo);
  386. if ( !bRet ) {
  387. pIniPort->status &= ~PP_INSTARTDOC;
  388. goto Cleanup;
  389. }
  390. //
  391. // If Ustatus thread is not running then check if printer understands
  392. // PJL, unless we determined that printer does not understand PJL earlier
  393. //
  394. if ( !(pIniPort->status & PP_RUN_THREAD) &&
  395. !(pIniPort->status & PP_DONT_TRY_PJL) ) {
  396. CreateUstatusThread(pIniPort);
  397. }
  398. //
  399. // set PP_SEND_PJL flag here so the first write of the job
  400. // will try to send PJL command to initiate the job control
  401. //
  402. pIniJob->JobId = dwJobId;
  403. pIniJob->status |= PP_INSTARTDOC;
  404. EnterSplSem();
  405. if ( !pIniPort->pIniJob ) {
  406. pIniPort->pIniJob = pIniJob;
  407. } else {
  408. pIniJob->pNext = pIniPort->pIniJob;
  409. pIniPort->pIniJob = pIniJob;
  410. }
  411. LeaveSplSem();
  412. if ( pIniPort->status & PP_IS_PJL )
  413. pIniJob->status |= PP_SEND_PJL;
  414. WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
  415. Cleanup:
  416. if ( !bRet ) {
  417. if ( pIniJob )
  418. FreeIniJob(pIniJob);
  419. }
  420. return bRet;
  421. }
  422. BOOL
  423. WINAPI
  424. PJLMonReadPort(
  425. IN HANDLE hPort,
  426. OUT LPBYTE pBuffer,
  427. IN DWORD cbBuf,
  428. OUT LPDWORD pcbRead
  429. )
  430. /*++
  431. Routine Description:
  432. Language monitor ReadPort
  433. Arguments:
  434. hPort : Port handle
  435. pBuffer : Buffer to read data to
  436. cbBuf : Buffer size
  437. pcbRead : Pointer to the variable to return read count
  438. Return Value:
  439. TRUE on success, FALSE on error
  440. --*/
  441. {
  442. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  443. if ( !pIniPort ||
  444. pIniPort->signature != PJ_SIGNATURE ) {
  445. SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
  446. SetLastError(ERROR_INVALID_PARAMETER);
  447. return FALSE;
  448. }
  449. return (*pIniPort->fn.pfnReadPort)(pIniPort->hPort, pBuffer, cbBuf, pcbRead);
  450. }
  451. BOOL
  452. WINAPI
  453. PJLMonWritePort(
  454. IN HANDLE hPort,
  455. IN LPBYTE pBuffer,
  456. IN DWORD cbBuf,
  457. IN LPDWORD pcbWritten
  458. )
  459. /*++
  460. Routine Description:
  461. Language monitor WritePort
  462. Arguments:
  463. hPort : Port handle
  464. pBuffer : Data Buffer
  465. cbBuf : Buffer size
  466. pcbRead : Pointer to the variable to return written count
  467. Return Value:
  468. TRUE on success, FALSE on error
  469. --*/
  470. {
  471. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  472. BOOL ret;
  473. if ( !pIniPort ||
  474. pIniPort->signature != PJ_SIGNATURE ) {
  475. SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
  476. SetLastError(ERROR_INVALID_PARAMETER);
  477. return FALSE;
  478. }
  479. //
  480. // check if it's the fist write of the job
  481. //
  482. if ( pIniPort->pIniJob &&
  483. (pIniPort->pIniJob->status & PP_SEND_PJL) ) {
  484. // PP_SEND_PJL is set if it's the first write of the job
  485. char string[256];
  486. if ( !WriteCommand(hPort, "\033%-12345X@PJL \015\012") ) {
  487. return FALSE;
  488. }
  489. //
  490. // clear PP_SEND_PJL here if we have successfully send a PJL command.
  491. //
  492. pIniPort->pIniJob->status &= ~PP_SEND_PJL;
  493. //
  494. // set PP_PJL_SENT meaning that we have successfully sent a
  495. // PJL command to the printer, though it doesn't mean that
  496. // we will get a successfully read. PP_PJL_SENT gets cleared in
  497. // StartDocPort.
  498. //
  499. pIniPort->pIniJob->status |= PP_PJL_SENT;
  500. StringCchPrintfA (string, COUNTOF (string), "@PJL JOB NAME = \"MSJOB %d\"\015\012",
  501. pIniPort->pIniJob->JobId);
  502. WriteCommand(hPort, string);
  503. 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");
  504. }
  505. //
  506. // writing to port monitor
  507. //
  508. ret = (*pIniPort->fn.pfnWritePort)(pIniPort->hPort, pBuffer,
  509. cbBuf, pcbWritten);
  510. if ( ret ) {
  511. pIniPort->status &= ~PP_WRITE_ERROR;
  512. } else {
  513. pIniPort->status |= PP_WRITE_ERROR;
  514. }
  515. if ( (!ret || pIniPort->PrinterStatus) &&
  516. (pIniPort->status & PP_THREAD_RUNNING) ) {
  517. //
  518. // By waiting for the UStatus thread to finish reading if there
  519. // is an error and printer sends unsolicited status
  520. // and user gets status on queue view before the win32 popup
  521. //
  522. ResetEvent(pIniPort->DoneReading);
  523. SetEvent(pIniPort->WakeUp);
  524. WaitForSingleObject(pIniPort->DoneReading, INFINITE);
  525. }
  526. return ret;
  527. }
  528. BOOL
  529. WINAPI
  530. PJLMonEndDocPort(
  531. HANDLE hPort
  532. )
  533. /*++
  534. Routine Description:
  535. Language monitor EndDocPort
  536. Arguments:
  537. hPort : Port handle
  538. Return Value:
  539. TRUE on success, FALSE on error
  540. --*/
  541. {
  542. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  543. PINIJOB pIniJob;
  544. if ( !pIniPort ||
  545. pIniPort->signature != PJ_SIGNATURE ) {
  546. SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
  547. SetLastError(ERROR_INVALID_PARAMETER);
  548. return FALSE;
  549. }
  550. //
  551. // Find the job (which is the last)
  552. //
  553. pIniJob = pIniPort->pIniJob;
  554. if ( !pIniJob )
  555. DBGMSG(DBG_ERROR, ("No jobs?\n"));
  556. //
  557. // check if we had sent PJL command, i.e. if the printer is bi-di
  558. //
  559. if ( pIniJob && (pIniJob->status & PP_PJL_SENT) ) {
  560. //
  561. // if the printer is bi-di, tell printer to let us know when the job
  562. // is don't in the printer and we'll really EndDoc then. this is so
  563. // that we can continue to monitor the job status until the job is
  564. // really done in case there's an error occurs.
  565. // but some cheap printers like 4L, doesn't handle this EOJ command
  566. // reliably, so we time out if printer doesn't tell us EOJ after a
  567. // while so that we don't end up having the port open forever in this
  568. // case.
  569. //
  570. char string[256];
  571. StringCchPrintfA (string, COUNTOF (string),
  572. "\033%%-12345X@PJL EOJ NAME = \"MSJOB %d\"\015\012\033%%-12345X",
  573. pIniPort->pIniJob->JobId);
  574. WriteCommand(hPort, string);
  575. pIniJob->TimeoutCount = GetTickCount();
  576. pIniJob->status &= ~PP_INSTARTDOC;
  577. }
  578. (*pIniPort->fn.pfnEndDocPort)(pIniPort->hPort);
  579. if ( pIniJob && !(pIniJob->status & PP_PJL_SENT) ) {
  580. //
  581. // This is not bi-di printer send EOJ so that spooler deletes it
  582. //
  583. SendJobLastPageEjected(pIniPort, pIniJob->JobId, FALSE);
  584. }
  585. pIniPort->status &= ~PP_INSTARTDOC;
  586. // wake up the UStatus read thread if printer is bi-di
  587. if ( pIniPort->status & PP_THREAD_RUNNING )
  588. SetEvent(pIniPort->WakeUp);
  589. SetEvent(pIniPort->DoneWriting);
  590. return TRUE;
  591. }
  592. BOOL
  593. WINAPI
  594. PJLMonClosePort(
  595. HANDLE hPort
  596. )
  597. /*++
  598. Routine Description:
  599. Language monitor ClosePort
  600. Arguments:
  601. hPort : Port handle
  602. Return Value:
  603. TRUE on success, FALSE on error
  604. --*/
  605. {
  606. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  607. if ( !pIniPort ||
  608. pIniPort->signature != PJ_SIGNATURE ) {
  609. SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
  610. SetLastError(ERROR_INVALID_PARAMETER);
  611. return FALSE;
  612. }
  613. pIniPort->status &= ~PP_INSTARTDOC;
  614. //
  615. // Kill Ustatus thread if it is running
  616. //
  617. if (pIniPort-> hUstatusThread)
  618. {
  619. pIniPort->status &= ~PP_RUN_THREAD;
  620. SetEvent(pIniPort->WakeUp);
  621. WaitForSingleObject (pIniPort-> hUstatusThread, INFINITE);
  622. }
  623. if ( pIniPort->fn.pfnClosePort )
  624. (*pIniPort->fn.pfnClosePort)(pIniPort->hPort);
  625. EnterSplSem();
  626. DeletePortEntry(pIniPort);
  627. LeaveSplSem();
  628. return TRUE;
  629. }
  630. BOOL
  631. WriteCommand(
  632. HANDLE hPort,
  633. LPSTR cmd
  634. )
  635. /*++
  636. Routine Description:
  637. Write a command to the port
  638. Arguments:
  639. hPort : Port handle
  640. cmd : Command buffer
  641. Return Value:
  642. TRUE on success, FALSE on error
  643. --*/
  644. {
  645. DWORD cbWrite, cbWritten, dwRet;
  646. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  647. cbWrite = strlen(cmd);
  648. dwRet = (*pIniPort->fn.pfnWritePort)(pIniPort->hPort, cmd, cbWrite, &cbWritten);
  649. if ( dwRet ) {
  650. pIniPort->status &= ~PP_WRITE_ERROR;
  651. } else {
  652. pIniPort->status |= PP_WRITE_ERROR;
  653. DBGMSG(DBG_INFO, ("PJLMON!No data Written\n"));
  654. if ( pIniPort->status & PP_THREAD_RUNNING )
  655. SetEvent(pIniPort->WakeUp);
  656. }
  657. return dwRet;
  658. }
  659. #define CBSTRING 1024
  660. BOOL
  661. ReadCommand(
  662. HANDLE hPort
  663. )
  664. /*++
  665. Routine Description:
  666. Read a command from the port
  667. Arguments:
  668. hPort : Port handle
  669. Return Value:
  670. TRUE on successfully reading one or more commands, FALSE on error
  671. --*/
  672. {
  673. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  674. DWORD cbRead, cbToRead, cbProcessed, cbPrevious;
  675. char string[CBSTRING];
  676. DWORD status = STATUS_SYNTAX_ERROR; //Value should not matter
  677. BOOL bRet=FALSE;
  678. cbPrevious = 0;
  679. ResetEvent(pIniPort->DoneReading);
  680. cbToRead = CBSTRING - 1;
  681. for ( ; ; ) {
  682. if ( !PJLMonReadPort(hPort, &string[cbPrevious], cbToRead, &cbRead) )
  683. break;
  684. if ( cbRead ) {
  685. if (cbPrevious + cbRead > CBSTRING - 1)
  686. {
  687. bRet = FALSE;
  688. break;
  689. }
  690. string[cbPrevious + cbRead] = '\0';
  691. status = ProcessPJLString(pIniPort, string, &cbProcessed);
  692. if ( cbProcessed )
  693. bRet = TRUE;
  694. if (status == STATUS_END_OF_STRING ) {
  695. if ( cbProcessed )
  696. {
  697. size_t cbUnprocessed = min (
  698. strlen (string + cbProcessed) + 1,
  699. COUNTOF (string) - cbProcessed
  700. ) * sizeof (string [0]);
  701. memmove (string, string + cbProcessed, cbUnprocessed);
  702. }
  703. cbPrevious = cbRead + cbPrevious - cbProcessed;
  704. }
  705. } else {
  706. SPLASSERT(!cbPrevious);
  707. }
  708. if ( status != STATUS_END_OF_STRING && cbRead != cbToRead )
  709. break;
  710. cbToRead = CBSTRING - cbPrevious - 1;
  711. if ( cbToRead == 0 )
  712. {
  713. DBGMSG(DBG_ERROR,
  714. ("ReadCommand cbToRead is 0 (buffer too small)\n"));
  715. bRet = FALSE;
  716. break;
  717. }
  718. Sleep(WAIT_FOR_DATA_TIMEOUT);
  719. }
  720. SetEvent(pIniPort->DoneReading);
  721. //
  722. // Update the time we last read from printer
  723. //
  724. if ( bRet )
  725. pIniPort->dwLastReadTime = GetTickCount();
  726. return bRet;
  727. }
  728. BOOL
  729. WINAPI
  730. PJLMonGetPrinterDataFromPort(
  731. HANDLE hPort,
  732. DWORD ControlID,
  733. LPTSTR pValueName,
  734. LPTSTR lpInBuffer,
  735. DWORD cbInBuffer,
  736. LPTSTR lpOutBuffer,
  737. DWORD cbOutBuffer,
  738. LPDWORD lpcbReturned
  739. )
  740. /*++
  741. Routine Description:
  742. GetPrinter data from port. Supports predefined commands/valuenames.
  743. When we support Value name commands (not supported by DeviceIoControl)
  744. we should check for startdoc
  745. This monitor function supports the following two functionalities,
  746. 1. Allow spooler or language monitor to call DeviceIoControl to get
  747. information from the port driver vxd, i.e. ControlID != 0.
  748. And only port monitor support this functionality, language monitor
  749. doesn't, so language monitor just pass this kind of calls down to
  750. port monitor.
  751. 2. Allow app or printer driver query language monitor for some device
  752. information by specifying some key names that both parties understand,
  753. i.e. ControlID == 0 && pValueName != 0. So when printer driver call
  754. DrvGetPrinterData DDI, gdi will call spooler -> language monitor
  755. to get specific device information, for example, UNIDRV does this
  756. to get installed printer memory from PJL printers thru PJLMON.
  757. Only language monitor support this functionality,
  758. port monitor doesn't.
  759. Arguments:
  760. hPort : Port handle
  761. ControId : Control id
  762. pValueName : Value name
  763. lpInBuffer : Input buffer for the command
  764. cbinBuffer : Input buffer size
  765. lpOutBuffer : Output buffer
  766. cbOutBuffer : Output buffer size
  767. lpcbReturned : Set to the amount of data in output buffer on success
  768. Return Value:
  769. TRUE on success, FALSE on error
  770. --*/
  771. {
  772. PINIPORT pIniPort = (PINIPORT)((INIPORT *)hPort);
  773. BOOL bRet = FALSE, bStopUstatusThread = FALSE;
  774. SPLASSERT(pIniPort && pIniPort->signature == PJ_SIGNATURE);
  775. if ( ControlID ) {
  776. if ( !pIniPort->fn.pfnGetPrinterDataFromPort ) {
  777. SetLastError(ERROR_INVALID_PARAMETER);
  778. return FALSE;
  779. }
  780. return (*pIniPort->fn.pfnGetPrinterDataFromPort)(
  781. pIniPort->hPort,
  782. ControlID,
  783. pValueName,
  784. lpInBuffer,
  785. cbInBuffer,
  786. lpOutBuffer,
  787. cbOutBuffer,
  788. lpcbReturned);
  789. }
  790. //
  791. // Only 2 keys supported
  792. //
  793. if ( lstrcmpi(pValueName, cszInstalledMemory) &&
  794. lstrcmpi(pValueName, cszAvailableMemory) ) {
  795. SetLastError(ERROR_INVALID_PARAMETER);
  796. return FALSE;
  797. }
  798. //
  799. // Wait for crrent job to print since we can't send a PJL command
  800. // in the middle of job
  801. //
  802. WaitForSingleObject(pIniPort->DoneWriting, INFINITE);
  803. // make sure the first write succeeds
  804. // The multi-language printers (4M, 4ML, 4MP, 4V, 4SI), if you print a
  805. // PS print job, the memory resources claimed by the PS processor are not
  806. // release until you enter PCL or reset the printer with "EscE".
  807. //
  808. // So if we had just printed a PS job, the available memory will be
  809. // incorrect if we don't have the "EscE" here.
  810. if ( (pIniPort->status & PP_IS_PJL) &&
  811. WriteCommand(hPort, "\033E\033%-12345X@PJL INFO CONFIG\015\012") ) {
  812. if ( !(pIniPort->status & PP_RUN_THREAD) ) {
  813. bStopUstatusThread = TRUE;
  814. CreateUstatusThread(pIniPort);
  815. }
  816. // PJLMON currently only supports the following pValueName
  817. // 1. installed printer memory
  818. // 2. available printer memory
  819. if ( !lstrcmpi(pValueName, cszInstalledMemory) )
  820. pIniPort->dwInstalledMemory = 0;
  821. else if (!lstrcmpi(pValueName, cszAvailableMemory))
  822. pIniPort->dwAvailableMemory = 0;
  823. ResetEvent(pIniPort->DoneReading);
  824. SetEvent(pIniPort->WakeUp);
  825. WaitForSingleObject(pIniPort->DoneReading, READTHREADTIMEOUT);
  826. WriteCommand(hPort,
  827. "@PJL INFO MEMORY\015\012@PJL INFO STATUS\015\012");
  828. ResetEvent(pIniPort->DoneReading);
  829. SetEvent(pIniPort->WakeUp);
  830. WaitForSingleObject(pIniPort->DoneReading, READTHREADTIMEOUT);
  831. if ( bStopUstatusThread ) {
  832. pIniPort->status &= ~PP_RUN_THREAD;
  833. SetEvent(pIniPort->WakeUp);
  834. }
  835. if ( !lstrcmpi(pValueName, cszInstalledMemory) ) {
  836. *lpcbReturned = sizeof(DWORD);
  837. if ( lpOutBuffer &&
  838. cbOutBuffer >= sizeof(DWORD) &&
  839. pIniPort->dwInstalledMemory ) {
  840. *((LPDWORD)lpOutBuffer) = pIniPort->dwInstalledMemory;
  841. bRet = TRUE;
  842. }
  843. } else if ( !lstrcmpi(pValueName, cszAvailableMemory) ) {
  844. *lpcbReturned = sizeof(DWORD);
  845. if ( lpOutBuffer &&
  846. cbOutBuffer >= sizeof(DWORD) &&
  847. pIniPort->dwAvailableMemory)
  848. {
  849. *((LPDWORD)lpOutBuffer) = pIniPort->dwAvailableMemory;
  850. bRet = TRUE;
  851. }
  852. }
  853. if ( bStopUstatusThread ) {
  854. WaitForSingleObject (pIniPort-> hUstatusThread, INFINITE);
  855. }
  856. }
  857. if ( !bRet )
  858. SetLastError(ERROR_INVALID_PARAMETER);
  859. SetEvent(pIniPort->DoneWriting);
  860. return bRet;
  861. }
  862. MONITOREX MonitorEx = {
  863. sizeof(MONITOR),
  864. {
  865. NULL, // EnumPrinters not supported
  866. NULL, // OpenPort not supported
  867. PJLMonOpenPortEx,
  868. PJLMonStartDocPort,
  869. PJLMonWritePort,
  870. PJLMonReadPort,
  871. PJLMonEndDocPort,
  872. PJLMonClosePort,
  873. NULL, // AddPort not supported
  874. NULL, // AddPortEx not supported
  875. NULL, // ConfigurePort not supported
  876. NULL, // DeletePort not supported
  877. PJLMonGetPrinterDataFromPort,
  878. NULL // SetPortTimeOuts not supported
  879. }
  880. };
  881. LPMONITOREX
  882. WINAPI
  883. InitializePrintMonitor(
  884. IN LPTSTR pszRegistryRoot
  885. )
  886. /*++
  887. Routine Description:
  888. Fill the monitor function table. Spooler makes call to this routine
  889. to get the monitor functions.
  890. Arguments:
  891. pszRegistryRoot : Registry root to be used by this dll
  892. lpMonitor : Pointer to monitor fucntion table to be filled
  893. Return Value:
  894. TRUE on successfully initializing the monitor, false on error.
  895. --*/
  896. {
  897. if ( !pszRegistryRoot || !*pszRegistryRoot ) {
  898. SetLastError(ERROR_INVALID_PARAMETER);
  899. return NULL;
  900. }
  901. if ( UpdateTimeoutsFromRegistry(pszRegistryRoot) != ERROR_SUCCESS ) {
  902. return NULL;
  903. }
  904. return &MonitorEx;
  905. }
  906. #define NTOKEN 20
  907. DWORD
  908. ProcessPJLString(
  909. PINIPORT pIniPort,
  910. LPSTR pInString,
  911. DWORD *lpcbProcessed
  912. )
  913. /*++
  914. Routine Description:
  915. Process a PJL string read from the printer
  916. Arguments:
  917. pIniPort : Ini port
  918. pInString : Input string to process
  919. lpcbProcessed : On return set to the amount of data processed
  920. Return Value:
  921. Status value of the processing
  922. --*/
  923. {
  924. TOKENPAIR tokenPairs[NTOKEN];
  925. DWORD nTokenParsedRet;
  926. LPSTR lpRet;
  927. DWORD status = 0;
  928. lpRet = pInString;
  929. DBGMSG(DBG_TRACE, ("String to process: <"TSTR">\n", pInString));
  930. for (*lpcbProcessed = 0; *pInString != 0; pInString = lpRet) {
  931. //
  932. // Determine if printer is bi-di. LJ 4 does not have p1284
  933. // device ID so we do PCL memory query and see if it returns anything
  934. //
  935. if (!(pIniPort->status & PP_IS_PJL) &&
  936. !mystrncmp(pInString, "PCL\015\012INFO MEMORY", 16) )
  937. pIniPort->status |= PP_IS_PJL;
  938. status = GetPJLTokens(pInString, NTOKEN, tokenPairs,
  939. &nTokenParsedRet, &lpRet);
  940. if (status == STATUS_REACHED_END_OF_COMMAND_OK) {
  941. pIniPort->status |= PP_IS_PJL;
  942. InterpreteTokens(pIniPort, tokenPairs, nTokenParsedRet);
  943. } else {
  944. ProcessParserError(status);
  945. }
  946. //
  947. // if a PJL command straddles between buffers
  948. //
  949. if (status == STATUS_END_OF_STRING)
  950. break;
  951. *lpcbProcessed += (DWORD)(lpRet - pInString);
  952. }
  953. return status;
  954. }
  955. DWORD
  956. SeverityFromPjlStatus(
  957. DWORD dwPjlStatus
  958. )
  959. {
  960. if ( dwPjlStatus >= 10000 && dwPjlStatus < 12000 ) {
  961. //
  962. // 10xyz
  963. // 11xyz : load paper (paper available on another tray)
  964. //
  965. return PORT_STATUS_TYPE_WARNING;
  966. } else if ( dwPjlStatus >= 30000 && dwPjlStatus < 31000 ) {
  967. //
  968. // 30xyz : Auto continuable errors
  969. //
  970. return PORT_STATUS_TYPE_WARNING;
  971. } else if ( dwPjlStatus >= 35000 && dwPjlStatus < 36000 ) {
  972. //
  973. // 35xyz : Potential operator intervention conditions
  974. //
  975. return PORT_STATUS_TYPE_WARNING;
  976. } else if ( dwPjlStatus > 40000 && dwPjlStatus < 42000 ) {
  977. //
  978. // 40xyz : Operator intervention required
  979. // 41xyz : Load paper errors
  980. //
  981. return PORT_STATUS_TYPE_ERROR;
  982. }
  983. DBGMSG(DBG_ERROR,
  984. ("SeverityFromPjlStatus: Unknown status %d\n", dwPjlStatus));
  985. return PORT_STATUS_TYPE_INFO;
  986. }
  987. VOID
  988. InterpreteTokens(
  989. PINIPORT pIniPort,
  990. PTOKENPAIR tokenPairs,
  991. DWORD nTokenParsed
  992. )
  993. /*++
  994. Routine Description:
  995. Interpret succesfully read PJL tokens
  996. Arguments:
  997. pIniPort : Ini port
  998. tokenPairs : List of token pairs
  999. nTokenParsed : Number of token pairs
  1000. Return Value:
  1001. None
  1002. --*/
  1003. {
  1004. DWORD i, OldStatus;
  1005. PJLTOPRINTERSTATUS *pMap;
  1006. PORT_INFO_3 PortInfo3;
  1007. DWORD dwSeverity = 0;
  1008. HANDLE hToken;
  1009. OldStatus = pIniPort->PrinterStatus;
  1010. pIniPort->PrinterStatus = 0;
  1011. for (i = 0; i < nTokenParsed; i++) {
  1012. // DBGMSG(DBG_INFO, ("pjlmon!Token=0x%x, Value=%d\n",
  1013. // tokenPairs[i].token, tokenPairs[i].value));
  1014. switch(tokenPairs[i].token) {
  1015. case TOKEN_INFO_STATUS_CODE:
  1016. case TOKEN_USTATUS_DEVICE_CODE:
  1017. for (pMap = PJLToStatus; pMap->pjl; pMap++) {
  1018. if (pMap->pjl == tokenPairs[i].value) {
  1019. pIniPort->PrinterStatus = pMap->status;
  1020. dwSeverity = SeverityFromPjlStatus(pMap->pjl);
  1021. if ( dwSeverity == PORT_STATUS_TYPE_ERROR )
  1022. pIniPort->status |= PP_PRINTER_OFFLINE;
  1023. else
  1024. pIniPort->status &= ~PP_PRINTER_OFFLINE;
  1025. break;
  1026. }
  1027. }
  1028. if ( pMap->pjl && pMap->pjl == tokenPairs[i].value )
  1029. break;
  1030. //
  1031. // some printers use this to signal online/ready
  1032. //
  1033. if ( tokenPairs[i].value == 10001 ||
  1034. tokenPairs[i].value == 10002 ||
  1035. tokenPairs[i].value == 11002 ) {
  1036. pIniPort->status &= ~PP_PRINTER_OFFLINE;
  1037. pIniPort->PrinterStatus = 0;
  1038. dwSeverity = 0;
  1039. }
  1040. //
  1041. // background or foreground paper out
  1042. //
  1043. if ( tokenPairs[i].value > 11101 && tokenPairs[i].value < 12000 ||
  1044. tokenPairs[i].value > 41101 && tokenPairs[i].value < 42000 ) {
  1045. pIniPort->PrinterStatus = PORT_STATUS_PAPER_OUT;
  1046. if ( tokenPairs[i].value > 4000 ) {
  1047. dwSeverity = PORT_STATUS_TYPE_ERROR;
  1048. pIniPort->status |= PP_PRINTER_OFFLINE;
  1049. } else {
  1050. dwSeverity = PORT_STATUS_TYPE_WARNING;
  1051. }
  1052. } else if (tokenPairs[i].value > 40000) {
  1053. pIniPort->PrinterStatus = PORT_STATUS_USER_INTERVENTION;
  1054. pIniPort->status |= PP_PRINTER_OFFLINE;
  1055. dwSeverity = PORT_STATUS_TYPE_ERROR;
  1056. }
  1057. break;
  1058. case TOKEN_INFO_STATUS_ONLINE:
  1059. case TOKEN_USTATUS_DEVICE_ONLINE:
  1060. // DBGMSG(DBG_INFO, ("PJLMON:ONLINE = %d\n", tokenPairs[i].value));
  1061. if (tokenPairs[i].value) {
  1062. pIniPort->status &= ~PP_PRINTER_OFFLINE;
  1063. dwSeverity = pIniPort->PrinterStatus ? PORT_STATUS_TYPE_WARNING :
  1064. 0;
  1065. } else {
  1066. if ( !pIniPort->PrinterStatus )
  1067. pIniPort->PrinterStatus = PORT_STATUS_OFFLINE;
  1068. pIniPort->status |= PP_PRINTER_OFFLINE;
  1069. dwSeverity = PORT_STATUS_TYPE_ERROR;
  1070. }
  1071. break;
  1072. case TOKEN_USTATUS_JOB_NAME_MSJOB:
  1073. DBGMSG(DBG_TRACE, ("EOJ for %d\n", tokenPairs[i].value));
  1074. SendJobLastPageEjected(pIniPort, (DWORD)tokenPairs[i].value, FALSE);
  1075. break;
  1076. case TOKEN_INFO_CONFIG_MEMORY:
  1077. case TOKEN_INFO_CONFIG_MEMORY_SPACE:
  1078. // IMPORTANT NOTE:
  1079. //
  1080. // Use SetPrinterData to cache the information in printer's registry.
  1081. // GDI's DrvGetPrinterData will check the printer's registry first,
  1082. // and if cache data is available, it will use it and not call
  1083. // GetPrinterData (which calls language monitor's
  1084. // GetPrinterDataFromPort).
  1085. DBGMSG(DBG_TRACE, ("PJLMON installed memory %d\n", tokenPairs[i].value));
  1086. pIniPort->dwInstalledMemory = (DWORD)tokenPairs[i].value;
  1087. break;
  1088. case TOKEN_INFO_MEMORY_TOTAL:
  1089. // IMPORTANT NOTE:
  1090. //
  1091. // Use SetPrinterData to cache the information in printer's registry.
  1092. // GDI's DrvGetPrinterData will check the printer's registry first,
  1093. // and if cache data is available, it will use it and not call
  1094. // GetPrinterData (which calls language monitor's
  1095. // GetPrinterDataFromPort).
  1096. DBGMSG(DBG_TRACE, ("PJLMON available memory %d\n", tokenPairs[i].value));
  1097. pIniPort->dwAvailableMemory = (DWORD)tokenPairs[i].value;
  1098. break;
  1099. default:
  1100. break;
  1101. }
  1102. }
  1103. if ( OldStatus != pIniPort->PrinterStatus ) {
  1104. ZeroMemory(&PortInfo3, sizeof(PortInfo3));
  1105. PortInfo3.dwStatus = pIniPort->PrinterStatus;
  1106. PortInfo3.dwSeverity = dwSeverity;
  1107. if ( !SetPort(NULL,
  1108. pIniPort->pszPortName,
  1109. 3,
  1110. (LPBYTE)&PortInfo3) ) {
  1111. DBGMSG(DBG_WARNING,
  1112. ("pjlmon: SetPort failed %d (LE: %d)\n",
  1113. pIniPort->PrinterStatus, GetLastError()));
  1114. pIniPort->PrinterStatus = OldStatus;
  1115. }
  1116. }
  1117. }
  1118. VOID
  1119. ProcessParserError(
  1120. DWORD status
  1121. )
  1122. /*++
  1123. Routine Description:
  1124. Print error messages on parsing error
  1125. Arguments:
  1126. status : status
  1127. Return Value:
  1128. None
  1129. --*/
  1130. {
  1131. #ifdef DEBUG
  1132. LPSTR pString;
  1133. switch (status)
  1134. {
  1135. case STATUS_REACHED_END_OF_COMMAND_OK:
  1136. pString = "STATUS_REACHED_END_OF_COMMAND_OK\n";
  1137. break;
  1138. case STATUS_CONTINUE:
  1139. pString = "STATUS_CONTINUE\n";
  1140. break;
  1141. case STATUS_REACHED_FF:
  1142. pString = "STATUS_REACHED_FF\n";
  1143. break;
  1144. case STATUS_END_OF_STRING:
  1145. pString = "STATUS_END_OF_STRING\n";
  1146. break;
  1147. case STATUS_SYNTAX_ERROR:
  1148. pString = "STATUS_SYNTAX_ERROR\n";
  1149. break;
  1150. case STATUS_ATPJL_NOT_FOUND:
  1151. pString = "STATUS_ATPJL_NOT_FOUND\n";
  1152. break;
  1153. case STATUS_NOT_ENOUGH_ROOM_FOR_TOKENS:
  1154. pString = "STATUS_NOT_ENOUGH_ROOM_FOR_TOKENS\n";
  1155. break;
  1156. default:
  1157. pString = "INVALID STATUS RETURNED!!!!!!\n";
  1158. break;
  1159. };
  1160. OutputDebugStringA(pString);
  1161. #endif
  1162. }
  1163. #define MODEL "MODEL:"
  1164. #define MDL "MDL:"
  1165. #define COMMAND "COMMAND SET:"
  1166. #define CMD "CMD:"
  1167. #define COLON ':'
  1168. #define SEMICOLON ';'
  1169. LPSTR
  1170. FindP1284Key(
  1171. PINIPORT pIniPort,
  1172. LPSTR lpKey
  1173. )
  1174. /*++
  1175. Routine Description:
  1176. Find the 1284 key identifying the device id
  1177. Arguments:
  1178. status : status
  1179. Return Value:
  1180. Pointer to the command string, NULL if not found.
  1181. --*/
  1182. {
  1183. LPSTR lpValue; // Pointer to the Key's value
  1184. WORD wKeyLength; // Length for the Key (for stringcmps)
  1185. LPSTR ret = NULL;
  1186. // While there are still keys to look at.
  1187. DBGMSG(DBG_TRACE, ("PJLMon!DeviceId : <"TSTR">\n", lpKey));
  1188. while (lpKey && *lpKey) {
  1189. //
  1190. // Is there a terminating COLON character for the current key?
  1191. //
  1192. if (!(lpValue = mystrchr(lpKey, COLON)) ) {
  1193. //
  1194. // Something is wrong with the Device ID
  1195. //
  1196. return ret;
  1197. }
  1198. //
  1199. // The actual start of the Key value is one past the COLON
  1200. //
  1201. ++lpValue;
  1202. //
  1203. // Compute the Key length for Comparison, including the COLON
  1204. // which will serve as a terminator
  1205. //
  1206. wKeyLength = (WORD)(lpValue - lpKey);
  1207. //
  1208. // Compare the Key to the Know quantities. To speed up the comparison
  1209. // a Check is made on the first character first, to reduce the number
  1210. // of strings to compare against.
  1211. // If a match is found, the appropriate lpp parameter is set to the
  1212. // key's value, and the terminating SEMICOLON is converted to a NULL
  1213. // In all cases lpKey is advanced to the next key if there is one.
  1214. //
  1215. if ( *lpKey == 'C' ) {
  1216. //
  1217. // Look for COMMAND SET or CMD
  1218. //
  1219. if ( !mystrncmp(lpKey, COMMAND, wKeyLength) ||
  1220. !mystrncmp(lpKey, CMD, wKeyLength) ) {
  1221. ret = lpValue;
  1222. }
  1223. }
  1224. // Go to the next Key
  1225. if ( lpKey = mystrchr(lpValue, SEMICOLON) ) {
  1226. *lpKey = '\0';
  1227. ++lpKey;
  1228. }
  1229. }
  1230. return ret;
  1231. }
  1232. BOOL
  1233. IsPJL(
  1234. PINIPORT pIniPort
  1235. )
  1236. /*++
  1237. Routine Description:
  1238. Finds out if the printer is a PJL bi-di printer
  1239. Arguments:
  1240. pIniPort : Points to an INIPORT
  1241. Return Value:
  1242. TRUE if printer is PJL bi-di, else FALSE
  1243. On failure PP_DONT_TRY_PJL is set
  1244. --*/
  1245. {
  1246. char szID[MAX_DEVID];
  1247. DWORD cbRet;
  1248. LPSTR lpCMD;
  1249. HANDLE hPort = (HANDLE)pIniPort;
  1250. BOOL bRet = FALSE;
  1251. //
  1252. // for printers that supports P1284 plug and play like LJ 4L, DJ540.
  1253. // we parse the COMMAND string and see if PJL is supported
  1254. //
  1255. if (pIniPort->fn.pfnGetPrinterDataFromPort) {
  1256. //
  1257. // Only try P1284 if port monitor supports DeviceIOCtl
  1258. //
  1259. memset((LPBYTE)szID, 0, sizeof(szID));
  1260. cbRet = 0;
  1261. if ((*pIniPort->fn.pfnGetPrinterDataFromPort)
  1262. (pIniPort->hPort, GETDEVICEID, NULL, NULL,
  1263. 0, (LPWSTR)szID, sizeof(szID), &cbRet)
  1264. && cbRet) {
  1265. //
  1266. // succeeded the P1284 plug and play protocol
  1267. //
  1268. szID[cbRet] = '\0';
  1269. if ( lpCMD = FindP1284Key(pIniPort, szID) ) {
  1270. // found the COMMAND string
  1271. while (*lpCMD) {
  1272. //
  1273. // look for "PJL"
  1274. //
  1275. if ( lpCMD[0] == 'P' && lpCMD[1] == 'J' && lpCMD[2] == 'L' ){
  1276. pIniPort->status &= ~PP_DONT_TRY_PJL;
  1277. bRet = TRUE;
  1278. goto Cleanup;
  1279. }
  1280. lpCMD++;
  1281. }
  1282. pIniPort->status |= PP_DONT_TRY_PJL;
  1283. goto Cleanup;
  1284. }
  1285. }
  1286. //
  1287. // fall thru to try PJL bi-di if we failed the P1284 communication
  1288. // or P1284 didn't return a COMMAND string
  1289. //
  1290. }
  1291. //
  1292. // for printers that don't support P1284 plug and play, but support PJL
  1293. // language command, like LJ 4 and 4M. we try to write/read PJL
  1294. // command and see if it succeeds.
  1295. // if we can't set the time outs we don't want to try to read, just fail.
  1296. //
  1297. if ( pIniPort->fn.pfnSetPortTimeOuts &&
  1298. !(pIniPort->status & PP_DONT_TRY_PJL)) {
  1299. COMMTIMEOUTS CTO;
  1300. memset((LPSTR)&CTO, 0, sizeof(CTO));
  1301. CTO.ReadTotalTimeoutConstant = 5000;
  1302. CTO.ReadIntervalTimeout = 200;
  1303. if ( !(*pIniPort->fn.pfnSetPortTimeOuts)(pIniPort->hPort, &CTO, 0) ) {
  1304. goto Cleanup;
  1305. }
  1306. // This <ESC>*s1M is a PCL5 command to determine the amount of memory
  1307. // in a PCL5 printer, and if the printer is PCL5 and bi-di capable,
  1308. // it will return "PCL\015\012INFO MEMORY".
  1309. // See PJL Tech Ref Manual page 7-21.
  1310. pIniPort->status &= ~PP_IS_PJL;
  1311. if ( !WriteCommand(hPort, "\033*s1M") )
  1312. goto Cleanup;
  1313. // ReadCommand->ProcessPJLString will set PP_IS_PJL
  1314. // if we read any valid PJL command back from the printer
  1315. if ( !ReadCommand(hPort) ) {
  1316. //
  1317. // We have jumped through the hoop to determin if this printer can
  1318. // understand PJL. It DOES NOT. We are not going to try again.
  1319. // until there is a printer change.
  1320. //
  1321. pIniPort->status |= PP_DONT_TRY_PJL;
  1322. }
  1323. if (pIniPort->status & PP_IS_PJL) {
  1324. bRet = TRUE;
  1325. goto Cleanup;
  1326. }
  1327. }
  1328. Cleanup:
  1329. if ( bRet ) {
  1330. WriteCommand(hPort, "\033%-12345X@PJL \015\012@PJL USTATUS TIMED 30 \015\012\033%-12345X");
  1331. pIniPort->dwLastReadTime = GetTickCount();
  1332. }
  1333. return bRet;
  1334. }