/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = QPAPI.C
 *
 * DESCRIPTIVE NAME = PM Print Queue Processor
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION
 *
 * This module contains the PM Print Queue Processor dynlink externalized
 * entry points.
 *
 * FUNCTIONS
 *
 * ENTRY POINTS: SplQpOpen, SplQpPrint, SplQpClose, SplQpControl,
 *               SplQpQueryDt, SplQpInstall, SplQpQueryFlags
 *
 * DEPENDENCIES:
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/

#define  INCL_WINSEI
#define  INCL_DEV
#define  INCL_GPIMETAFILES
#define  INCL_GPICONTROL
#define  INCL_DOSFILEMGR
#define  INCL_SHLERRORS
#define  INCL_WINDIALOGS
#define  INCL_GPIP
#define  INCL_DEVP
#include "pmprint.h"


/****************************************************************************
 *
 * FUNCTION NAME = SplQpQueryDt
 *
 * DESCRIPTION   = Public function to return list of datatypes supported
 *                 by this print queue processor
 *
 * INPUT         = pcDatatypes - pointer to value indicating:
 *                               Entry - number of PSZs in papszDatatypes
 *                                       0 to find out number supported
 *                               Exit  - *pcDataTypes = total supported
 *                                                      by queue processor
 *
 *                 papszDatatypes - pointer to an array of pointers that
 *                                  address the locations for the returned
 *                                  data type names.
 *                                  Each location must be an array of
 *                                  16 characters to accomodate a data type
 *                                  name of the maximum length(15) plus
 *                                  its terminating null.
 *                                  NOTE: the caller must allocate memory
 *                                        that these pointers address.
 *
 * OUTPUT        = TRUE  - successful and *pcDatatypes set to total data types
 *                         supported
 *                 FALSE - Error.  Use WinGetLastError() to find error code.
 *
 * RETURN-NORMAL = TRUE
 *
 * RETURN-ERROR  = FALSE
 *
 ****************************************************************************/


BOOL APIENTRY SplQpQueryDt(PLONG pcDatatypes,PSZ *papszDatatypes)
{
  LONG lCountReturned;

  if (!pcDatatypes)
  {
    LogError(PMERR_INVALID_PARM);
  }

  else
  {
    lCountReturned = *pcDatatypes;
    *pcDatatypes = QP_TYPE_COUNT;

    if (!lCountReturned)
      return (TRUE);

    if (lCountReturned < QP_TYPE_COUNT)
    {
      LogError(PMERR_SPL_INV_LENGTH_OR_COUNT);
    }

    else

      if (!papszDatatypes || !papszDatatypes[0] || !papszDatatypes[1])
      {
        LogError(PMERR_INVALID_PARM);
      }

      else
      {
        strcpy(papszDatatypes[QP_TYPE_STD], DT_STD);
        strcpy(papszDatatypes[QP_TYPE_RAW], DT_RAW);
        return (TRUE);
      }
  }

  return (FALSE);
}

/****************************************************************************
 *
 * FUNCTION NAME = SplQpOpen
 *
 * DESCRIPTION   = Public function to open queue driver for printing
 *
 * INPUT         = cbData      - number of elements in SQPOPENDATA structure
 *               = pbData      - pointer to SQPOPENDATA structure
 *                               This contains information about the job
 *                               to be sent to this queue processor.
 *
 * OUTPUT        = HPROC - NULL:  Error creating QProc Instance.
 *                                Use WinGetLastError() to find error code.
 *                         hProc: Queue processor handle to be used on
 *                                SplQpPrint/SplQpControl/SplQpClose
 *
 * RETURN-NORMAL = hProc
 *
 * RETURN-ERROR  = NULL
 *
 ****************************************************************************/

HPROC APIENTRY SplQpOpen(LONG cData,PQPOPENDATA pQPDataIn)
{
  PSQPOPENDATA pbData;                 /* Structure type of input parm       */
  PQPROCINST pQProc;                   /* QProcessor instance handle         */
  DEVOPENSTRUC OpenData;               /* DevOpenDC parameters               */
  PSZ pszDocName;                      /* Document name for START_DOC        */
  USHORT i;
  pbData = (PSQPOPENDATA)pQPDataIn;

  /*
  ** Init QP Handle
  */
  pQProc = NULL;

  /*
  ** Ensure count is large enough
  */
  if (cData < QPDAT_PROC_PARAMS-1)
  {
    LogError(PMERR_SPL_INV_LENGTH_OR_COUNT);
    return (HPROC)0;
  }
  /*
  ** if multiple threads from same process printing at once
  **    must wait for first thread to complete DLL initialization
  */
  if (fQprDoingInit == TRUE)
  {
    i = 0;

    do
    {
      DosSleep((ULONG)2000);           /* sleep 2 secs at a time             */
      i++;
    }

    while ((fQprDoingInit == TRUE) && (i < 30));
  }
  /*
  ** Install Exitlist processing
  */
  DosExitList(EXLST_ADD, (PFNEXITLIST)ExitListProc);

  /*
  ** Create a QP instance containing the open data
  */
  OpenData.pszLogAddress = pbData->pszLogAddress;
  OpenData.pszDriverName = pbData->pszDriverName;
  OpenData.pdriv = pbData->pdriv;
  OpenData.pszComment = pbData->pszComment;

  /*
  ** Make default datatype "PM_Q_RAW"
  */
  if (!(OpenData.pszDataType = pbData->pszDataType))
    OpenData.pszDataType = DT_RAW;

  if (!(OpenData.pszQueueProcParams = pbData->pszProcParams))
    OpenData.pszQueueProcParams = "";

  /*
  ** Keep document name for StartDoc
  */
  if (cData >= QPDAT_DOC_NAME)
    pszDocName = pbData->pszDocName;

  else
    pszDocName = (PSZ)NULL;
  pQProc = CreateQProcInst(&OpenData, pbData->idJobId, pbData->pszQueueName,
                                                               pszDocName);
  /*
  ** Return, removing exit list processing if an error
  */
  if (pQProc)
    return ((HPROC)pQProc);

  else
  {
    DosExitList(EXLST_REMOVE, (PFNEXITLIST)ExitListProc);
    return ((HPROC)NULL);
  }
}

/****************************************************************************
 *
 * FUNCTION NAME = SplQpPrint
 *
 * DESCRIPTION   = Public function to process and print a spool file
 *
 * INPUT         = hQProc      - Queue processor handle returned by SplQpOpen
 *               = pszFileName - pointer to name of file containing the
 *                               data to print.
 *                               Format of the file depends on the datatype
 *                               specified to SplQpOpen().
 *
 * OUTPUT        = FALSE - Error formatting the print job.
 *                         It is up to this queue processor to display an
 *                          error message to the user if warranted.
 *                         Use WinGetLastError() to find error code.
 *                         NOTE: if printer was offline and user selected
 *                                 to cancel the job,
 *                               then this function still returns TRUE
 *
 *                 TRUE  - Print job successfully formatted and printed
 *
 * RETURN-NORMAL = TRUE  - job printed successfully and spooler removes job
 *
 * RETURN-ERROR  = FALSE - job failed to print.  Job status set to "Held".
 *
 ****************************************************************************/

BOOL APIENTRY SplQpPrint(HPROC hQProc,PSZ pszFileName)
{
  PQPROCINST pQProc;
  BOOL result;
  EnterSem();
  pQProc = ValidateQProcInst(hQProc);
  LeaveSem();
  result = FALSE;

  /*
  ** Disable hard-error popups
  */
  DosError(FALSE);

  if (pQProc && pszFileName)
  {

    if (pQProc->qparms.cCopies)
    {
      result = TRUE;

      while (pQProc->qparms.cCopies && !(pQProc->fsStatus&QP_ABORTED) &&
         (result))
      {

        if (pQProc->uType == QP_TYPE_STD)
          result = SplQpStdPrintFile(pQProc, pszFileName);

        else
          result = SplQpRawPrintFile(pQProc, pszFileName);
        pQProc->qparms.cCopies--;
      }
    }
  }
  return (result);
}

/****************************************************************************
 *
 * FUNCTION NAME = SplQpClose
 *
 * DESCRIPTION   = Public function to close the queue processor connection
 *
 * INPUT         = hQProc      - Queue processor handle returned by SplQpOpen
 *
 * OUTPUT        = FALSE - Error releasing the queue processor handle.
 *                         Use WinGetLastError() to find error code.
 *
 *                 TRUE  - Queue processor handle released
 *
 * RETURN-NORMAL = TRUE
 *
 * RETURN-ERROR  = FALSE
 *
 ****************************************************************************/

BOOL APIENTRY SplQpClose(HPROC hQProc)
{
  PQPROCINST pQProc;
  BOOL result = TRUE;
  EnterSem();

  /*
  ** If valid handle then destroy instance data + remove exitlist proc
  */
  if (pQProc = ValidateQProcInst(hQProc))
  {
    DestroyQProcInst(pQProc);
    DosExitList(EXLST_REMOVE, (PFNEXITLIST)ExitListProc);
  }

  else
    result = FALSE;
  LeaveSem();
  return (result);
}

/****************************************************************************
 *
 * FUNCTION NAME = SplQpControl
 *
 * DESCRIPTION   = Public function to control the printing of a document
 *                 This is normally called by a thread while another thead
 *                   is inside a SplQpPrint() call, in order to control
 *                   the printing of that job.
 *
 * INPUT         = hQProc      - Queue processor handle returned by SplQpOpen
 *               = cmdCode     - Control code to perform on hQProc's job
 *                      SPLC_ABORT(1)    - abort printing the job
 *                      SPLC_PAUSE(2)    - pause printing the job
 *                      SPLC_CONTINUE(3) - release the paused job
 *
 * OUTPUT        = FALSE - Error controlling the print job.
 *                         Use WinGetLastError() to find error code.
 *
 *                 TRUE  - Command was successful.
 *
 * RETURN-NORMAL = TRUE
 *
 * RETURN-ERROR  = FALSE
 *
 ****************************************************************************/

BOOL APIENTRY SplQpControl(HPROC hQProc,LONG cmdCode)
{
  PQPROCINST pQProc;
  BOOL result;
  USHORT usStopMetaFile = SDW_OFF;
  ULONG ulPostCt;
  EnterSem();

  if (pQProc = ValidateQProcInst(hQProc))
  {
    result = TRUE;

    switch ((USHORT)cmdCode)
    {
      case  SPLC_PAUSE :

        /*
        ** if printing a metafile
        **   use undocumented GpiSuspendPlay() to attempt to
        **   suspend replaying of the metafile.
        **   This may have no effect.
        */
        if (pQProc->uType == QP_TYPE_STD)
          GpiSuspendPlay(pQProc->hps);

        /*
        ** Block threads which call DosWaitEventSem
        ** Set QProc flag to pause job
        */
        DosResetEventSem(pQProc->semPaused, &ulPostCt);
        pQProc->fsStatus |= QP_PAUSED;
        break;
      case  SPLC_ABORT :
        pQProc->fsStatus |= QP_ABORTED;

        if (pQProc->uType == QP_TYPE_STD)
        {

          if (pQProc->hps)
            GpiSetStopDraw(pQProc->hps, SDW_ON);
        }

        /*
        ** Must issue DEVESC_ABORTDOC if ENDDOC not completed because
        **  most of printer driver's time(especially plotters) is
        **  spent when they receive ENDDOC.
        **
        ** If user cancelled job
        **    we must end it as soon as possible.
        */
        if (pQProc->hdc)

          if (!(pQProc->fsStatus&QP_ENDDOC_COMPLETE))
            DevEscape(pQProc->hdc, DEVESC_ABORTDOC, 0L, NULL, 0L, NULL);

        /*
        ** fall through to release job if paused
        */
      case  SPLC_CONTINUE :

        if (pQProc->fsStatus&QP_PAUSED)
        {

          /*
          ** if the job paused is a metafile
          **   use undocumented call to resume playing the metafile
          */
          if (pQProc->uType == QP_TYPE_STD)
            GpiResumePlay(pQProc->hps);

          /*
          ** Clear Pause condition for print thread
          */
          DosPostEventSem(pQProc->semPaused);
          pQProc->fsStatus &= ~QP_PAUSED;
        }
        break;
      default  :
        LogError(PMERR_INVALID_PARM);
        result = FALSE;
        break;
    }
  }

  else
    result = FALSE;
  LeaveSem();
  return (result);
}

/****************************************************************************
 *
 * FUNCTION NAME = SplQpInstall
 *
 * DESCRIPTION   = Optional Public function to give user dialog to configure
 *                 this queue driver.
 *
 * INPUT         = hwnd - handle to parent window for setup dialog box
 *
 * OUTPUT        = FALSE - Setup unsuccessful
 *                         Use WinGetLastError() to find error code.
 *
 *                 TRUE  - Queue driver setup successful
 *
 * RETURN-NORMAL = TRUE
 *
 * RETURN-ERROR  = FALSE
 *
 * NOTE: This routine is only exported if your queue driver has a setup
 *         dialog, and is called when user Opens the settings on this
 *         queue driver in the print object's Queue Options settings page.
 *       This queue driver does not have a setup dialog, hence this
 *         routine is ifdef'd out and is commented out of the .def file.
 *
 ****************************************************************************/

#ifdef   PMPRINT_SETUP

BOOL APIENTRY SplQpInstall(HWND hwnd)
{
  USHORT us;
  us = WinMessageBox(HWND_DESKTOP, hwnd, pszSplStrings[SPL_ID_INSTALL_MSG],
     pszSplStrings[SPL_ID_INSTALL_CAPTION], 0, MB_OK|MB_INFORMATION|MB_MOVEABLE);
  return  us && us != MBID_ERROR;
}

#endif

/****************************************************************************
 *
 * FUNCTION NAME = SplQpQueryFlags
 *
 * DESCRIPTION   = Optional Public function to return queue processor flags
 *
 * INPUT         = pulFlags - points to ULONG to get updated with queue
 *                            processor flags if TRUE returned
 *
 * OUTPUT        = FALSE - Not supported
 *                         Use WinGetLastError() to find error code.
 *
 *                 TRUE  - *pulFlags set to queue processor flags
 *                         QP_RAWDATA_BYPASS(0x00000001) - set if this print
 *                            processor allows the spooler to bypass it for
 *                            printing raw("PM_Q_RAW") print jobs.
 * RETURN-NORMAL = TRUE
 *
 * RETURN-ERROR  = FALSE
 *
 * NOTE: Currently the only flag defined is QP_RAWDATA_BYPASS(0x00000001).
 *       This flag is set only if this print processor allows the spooler
 *         to print PM_Q_RAW jobs, rather than sending them to this
 *         print queue processor.
 *       Exporting this routine and setting QP_RAWDATA_BYPASS will allow
 *         the spooler to print raw jobs while still spooling.
 *
 ****************************************************************************/

BOOL APIENTRY SplQpQueryFlags(PULONG pulFlags)
{
  *pulFlags = QP_RAWDATA_BYPASS;
  return (TRUE);
}

/****************************************************************************
 *
 * FUNCTION NAME = CreateQProcInst
 *
 * DESCRIPTION   = Validate SplQpOpen parameters and create a QPROC instance
 *                  handle for caller.
 *
 * INPUT         = pQProcData - points to DEVOPENSTRUCT created from
 *                              SplQpOpen parameters.
 *                 uJobId     - Job ID from SplQpOpen
 *                 pszQName   - Name of print queue job is on
 *
 * OUTPUT        = NULL   - Invalid parameter passed
 *                          Use WinGetLastError() to find error code.
 *
 *                 hQProc - handle to queue processor to return to SplQpOpen
 *                          caller.
 * RETURN-NORMAL = hQProc != NULL
 *
 * RETURN-ERROR  = NULL
 *
 *
 ****************************************************************************/

PQPROCINST CreateQProcInst(PDEVOPENSTRUC pQProcData,USHORT uJobId,PSZ pszQName,
                            PSZ pszDocName)
{
  PQPROCINST pQProc;
  PSZ psz;
  PSZ pStr;
  PDRIVDATA pDriverData;
  USHORT cb;
  USHORT cbDriverData;                 /* sizeof DrivData for job            */
  USHORT cbDataType;                   /* Length of each string              */
  USHORT cbComment;
  USHORT cbPortName;
  USHORT cbDriverName;
  USHORT cbQName;
  USHORT cbDocName;
  USHORT uDataType;
  ULONG rc;
  QPPARMS qp_parms;
  PSZ pszComment;
  REGREC regrec;       // exception registration record      @Except
  ULONG  ulException;  // exception flag


  // Set exception handler in case invalid parameter

  regrec.pfnHandler = (PFN)ExceptHandler;
  DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  if( ulException = setjmp( regrec.jmp )) {
    // an exception occurred
    // clean up here: release memory, semaphores
    // must check for exiting thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      // thread is dying or being killed
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    Panic("Invalid QPOpen parameter", 0, 0);
    LogError(PMERR_INVALID_PARM);
    if (pQProc) {
       DosFreeMem(pQProc);
       pQProc = NULL;
    }
    goto depart;
  }

  pQProc = NULL;

  /*
  ** Determine amount of memory needed to store all the
  ** information about this job
  */
  if (pDriverData = pQProcData->pdriv)
    cbDriverData = (USHORT)pDriverData->cb;

  else
    cbDriverData = 0;
  cbDataType = 1+strlen(psz = pQProcData->pszDataType);

  if (!strcmp(psz, DT_STD))
    uDataType = QP_TYPE_STD;

  else

    if (!strcmp(psz, DT_RAW))
      uDataType = QP_TYPE_RAW;

    else
    {
      SplQpMessage(pQProcData->pszLogAddress, SPL_ID_QP_DATATYPE_ERROR,
         PMERR_SPL_INV_DATATYPE);
      goto depart;
    }
  cbComment = 1+strlen(pszComment = pQProcData->pszComment?
     pQProcData->pszComment:(PSZ)"");
  cbDriverName = 1+SafeStrlen(pQProcData->pszDriverName);
  cbPortName = 1+SafeStrlen(pQProcData->pszLogAddress);
  cbQName = 1+SafeStrlen(pszQName);
  cbDocName = 1+SafeStrlen(pszDocName);

  if (!ParseQProcParms(pQProcData->pszQueueProcParams, &qp_parms))
  {

    /*
    ** Unknown queue processor parameter passed.
    ** Inform user that QProc parms were ignored
    **  but the job will still be printed
    */
    SplQpMessage(pQProcData->pszLogAddress, SPL_ID_QP_INVALID_PARAMETER,
       PMERR_INVALID_PARM);
  }
  cb = sizeof(QPROCINST)+cbDataType+cbComment+cbQName+cbDriverName+cbPortName+
     cbDriverData+cbDocName;
  rc = DosAllocMem((PVOID)&pQProc, cb, PAG_READ|PAG_WRITE|PAG_COMMIT);

  if (rc)
  {
    Error("CreateQProcInst DosAllocMem failed", pQProcData->pszLogAddress, rc);
    SplQpMessage(pQProcData->pszLogAddress, SPL_ID_QP_MEM_ERROR,
       PMERR_SPL_NO_MEMORY);
    goto depart;
  }
  memset(pQProc, 0, sizeof(QPROCINST));
  pQProc->cb = cb;
  pQProc->signature = QP_SIGNATURE;
  pQProc->uPid = pPIB->pib_ulpid;
  pQProc->uJobId = uJobId;

  /*
  ** Create an anonymous Event semaphore used for job control
  */
  rc = DosCreateEventSem(NULL,         /* unnamed event semaphore            */
     &pQProc->semPaused, (ULONG)0,     /* attribute - not shared             */
     (BOOL32)FALSE);                   /* initial state is "set"             */

  if (rc)
  {
    Error("CreateQProcInst DosCreateEventSem failed", pQProcData->pszLogAddress,
                                                                            rc);
  }
  pQProc->uType = uDataType;
  pQProc->hFile = NULL_HFILE;

  /*
  ** Copy variable length strings immediately after QPROCINST structure
  */
  pStr = (PSZ)(pQProc+1);
  pQProc->pszDataType = pStr;
  pStr = 1+EndStrcpy(pStr, pQProcData->pszDataType);
  pQProc->pszComment = pStr;
  pStr = 1+EndStrcpy(pStr, pszComment);
  pQProc->pszPortName = pStr;
  pStr = 1+EndStrcpy(pStr, pQProcData->pszLogAddress);
  pQProc->pszDriverName = pStr;
  pStr = 1+EndStrcpy(pStr, pQProcData->pszDriverName);
  pQProc->pszQName = pStr;
  pStr = 1+EndStrcpy(pStr, pszQName);
  pQProc->pszDocument = pStr;
  pStr = 1+EndStrcpy(pStr, pszDocName ? pszDocName : "");

  if (cbDriverData)
  {
    pQProc->pDriverData = (PDRIVDATA)(PSZ)pStr;
    memcpy(pQProc->pDriverData, pDriverData, cbDriverData);
  }

  else
    pQProc->pDriverData = (PDRIVDATA)0L;
  pQProc->qparms = qp_parms;

  /*
  ** Add this queue processor instance to this process's list
  */
  EnterSem();
  pQProc->pNext = pQprocInstHead;
  pQprocInstHead = pQProc;
  LeaveSem();


depart:
  DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  return (pQProc);
}

/****************************************************************************
 *
 * FUNCTION NAME = DestroyQProcInst
 *
 * DESCRIPTION   = Free all allocated resources for given QPROC instance
 *                  and remove it from list.
 *
 * INPUT         = pQProc  - QPROC instance handle to free
 *
 * OUTPUT        = pQProc->pNext - next QPROC instance in chain
 *                                 NULL if no more
 *
 * RETURN-NORMAL =
 *
 *
 * RETURN-ERROR  =
 *
 * NOTE: This MUST be inside EnterSem() call to ensure pQprocInstHead list
 *       can be accessed/updated!
 *
 ****************************************************************************/

PQPROCINST DestroyQProcInst(PQPROCINST pQProc)
{
  PQPROCINST pQProc1 = pQprocInstHead;
  REGREC regrec;       // exception registration record           @Except
  ULONG  ulException;  // exception flag


  // Set exception handler in case invalid pointer

  regrec.pfnHandler = (PFN)ExceptHandler;
  DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  if( ulException = setjmp( regrec.jmp )) {
    // an exception occurred
    // clean up here: release memory, semaphores
    // must check for exiting thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      // thread is dying or being killed
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    Panic("Invalid hQProc pointer = %0p", hQProc, 0);
    pQProc = NULL;
    goto depart;
  }

  if (!pQProc || pQProc->signature != QP_SIGNATURE) {
    pQProc = NULL;
    goto depart;
  }

  /*
  ** Find QProc Instance handle in this process's handle list
  */
  if (pQProc == pQProc1)
  {
    pQprocInstHead = pQProc->pNext;
  }

  else
  {

    while (pQProc1)
    {

      if (pQProc1->pNext == pQProc)
      {
        pQProc1->pNext = pQProc->pNext;
        break;
      }

      else
      {
        pQProc1 = pQProc1->pNext;
      }
    }
  }

  if (!pQProc1)
    Panic("DestroyQProcInst pQProc=%0x not in list", pQProc, 0);
  pQProc1 = pQProc->pNext;
  pQProc->signature = 0;

  /*
  ** Free up print job if still active and release any Gpi resources
  */
  if (pQProc->hmf)
    GpiDeleteMetaFile(pQProc->hmf);

  if (pQProc->hps)
    GpiAssociate(pQProc->hps, (HDC)0);

  if (pQProc->hdc)
    DevCloseDC(pQProc->hdc);

  if (pQProc->hps)
    GpiDestroyPS(pQProc->hps);

  if (pQProc->hFile != NULL_HFILE)
    DosClose(pQProc->hFile);

  /*
  ** Close the Event semaphore for the print job
  */
  DosCloseEventSem(pQProc->semPaused);

  /*
  ** Free RAW data buffer if allocated
  */
  if (pQProc->pRawBuf)
    DosFreeMem(pQProc->pRawBuf);
  DosFreeMem((PVOID)pQProc);


depart:
  DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  return (pQProc1);
}

/****************************************************************************
 *
 * FUNCTION NAME = ValidateQProcInst
 *
 * DESCRIPTION   = Validate given QPROC instance by checking its signature
 *
 * INPUT         = hQProc  - QPROC instance handle to validate
 *
 * OUTPUT        = pQProc  - QPROC instance pointer or NULL if invalid handle
 *
 * RETURN-NORMAL = pQProc
 *
 *
 * RETURN-ERROR  = NULL
 *
 * NOTE: This MUST be inside EnterSem() call to ensure QProc Instance is
 *       not freed while accessing it.
 *
 ****************************************************************************/

PQPROCINST ValidateQProcInst(HPROC hQProc)
{
  PQPROCINST pQProc = (PQPROCINST)hQProc;
  REGREC regrec;       // exception registration record           @Except
  ULONG  ulException;  // exception flag


  // Set exception handler in case invalid pointer

  regrec.pfnHandler = (PFN)ExceptHandler;
  DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  if( ulException = setjmp( regrec.jmp )) {
    // an exception occurred
    // clean up here: release memory, semaphores
    // must check for exiting thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      // thread is dying or being killed
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    Panic("Invalid hQProc parameter = %0p", hQProc, 0);
    LogError(PMERR_SPL_INV_HSPL);
    pQProc = NULL;
    goto depart;
  }

  if (!pQProc || (pQProc->signature != QP_SIGNATURE) )
  {
    Panic("Invalid hQProc parameter = %0p", hQProc, 0);
    LogError(PMERR_SPL_INV_HSPL);
    pQProc = NULL;
  }

depart:
  DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
  return pQProc;
}

/****************************************************************************
 *
 * FUNCTION NAME = ParseQProcParms
 *
 * DESCRIPTION   = parses queue processor parameters string
 *
 * INPUT         = pszParms - entire queue processor parameter string
 *                            Each parameter(of form "PARM=Value"
 *                             is separated by a blank space.
 *                 pqp      - points to QPPARMS structure to store
 *                            flags representing parameters
 *
 * OUTPUT        = TRUE     - no invalid parameters were found
 *                 FALSE    - an invalid parameter was found,
 *                            all later parameters ignored
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL ParseQProcParms(PSZ pszParms,PQPPARMS pqp)
{
  PKEYDATA pQPParms = NULL;
  PSZ pParm;
  USHORT i;
  BOOL result = TRUE;
  CHAR chKeyBuf[512];                  /* temp buffer to hold keydata being
                                          parsed                             */
  memset(pqp, 0, sizeof(QPPARMS));
  pqp->cCopies = 1;
  pqp->fTransform = TRUE;

  if (!pszParms || !(i = SafeStrlen(pszParms)))
    return (TRUE);

  /*
  ** Parse string into array of KeyName/KeyValue pairs
  ** Pass in temporary buffer(chKeyBuf) to use for this array
  ** If temp buffer too small, ParseKeyData will allocate a
  **   new buffer, which must be freed
  */
  pQPParms = ParseKeyData(pszParms, ' ', i+1, (PKEYDATA)chKeyBuf, sizeof
     (chKeyBuf));

  if (pQPParms)
  {

    for (i = 0; i < pQPParms->cTokens; i++)
    {

      if (pParm = pQPParms->pTokens[i])
        result &= ParseQProcParm(pParm, pqp);
    }

    /*
    ** if returned buffer from ParseKeyData not the one we passed in
    ** then free it
    */
    if (pQPParms != (PKEYDATA)chKeyBuf)
      DosFreeMem((PVOID)pQPParms);
  }

  if (!result)
    Warning("Invalid queue processor parms: %0s", pszParms, 0);
  return (result);
}

/****************************************************************************
 *
 * FUNCTION NAME = ParseQProcParm
 *
 * DESCRIPTION   = parse  queue processor parameter string
 *
 * INPUT         = pszParm  - individual queue processor parameter
 *                            of form "PARM=Value,[value2,...]"
 *
 *                 pqp      - points to QPPARMS structure to store
 *                            flags representing parameters
 *
 * OUTPUT        = TRUE     - no invalid parameter were found
 *                 FALSE    - an invalid parameter was found,
 *                            all later parameters ignored
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL ParseQProcParm(PSZ pszParm,PQPPARMS pqp)
{
  PSZ pszVal = pszParm+3;
  USHORT us;

  if (SafeStrlen(pszParm) < 5)
    return (FALSE);

  if (*pszVal != '=')
    return (FALSE);
  *pszVal++ = '\0';

  if (!strcmp(pszParm, "COP"))
  {

    /*
    ** Number of copies to print "COP=n"
    */
    if (!(us = AsciiToInt(pszVal)))
      return (FALSE);

    else
      pqp->cCopies = us;

    while ((UCHAR)(*pszVal-'0') < 10)
      pszVal++;
  }

  else

    if (!strcmp(pszParm, "CDP"))
    {
      /*
      ** Codepage for Raw jobs "CDP=xxx"
      */
      pqp->uCodePage = AsciiToInt(pszVal);

      while ((UCHAR)(*pszVal-'0') < 10)
        pszVal++;

      if (*pszVal != '\0')
      {
        pqp->uCodePage = 0;
        return (FALSE);
      }
      ;
    }

    else

      if (!strcmp(pszParm, "XFM"))
      {
        /*
        ** Override ARE and FIT "XFM=0|1"
        */
        if (*pszVal == '0')
          pqp->fTransform = FALSE;

        else

          if (*pszVal == '1')
            pqp->fTransform = TRUE;

          else
            return (FALSE);
        pszVal++;
      }

      else

        if (!strcmp(pszParm, "XLT"))
        {
          /*
          ** Support undocumented translation removal "XLT=0|1"
          */
          if (*pszVal == '0')
            pqp->fTranslate = FALSE;

          else

            if (*pszVal == '1')
              pqp->fTranslate = TRUE;

            else
              return (FALSE);
          pszVal++;
        }

        else

          if (!strcmp(pszParm, "ORI"))
          {
            /*
            ** Don't give error if undocumented(and not supported)
            ** orientation parameter given.
            */
            pszVal++;
          }

          else

            if (!strcmp(pszParm, "COL"))
            {
              /*
              ** Specify color setting "COL=M|C"
              */
              if (*pszVal == 'M')
                pqp->fColor = FALSE;

              else

                if (*pszVal == 'C')
                  pqp->fColor = TRUE;

                else
                  return (FALSE);
              pszVal++;
            }

            else

              if (!strcmp(pszParm, "MAP"))
              {
                /*
                ** Determine how neutral colors are printed "MAP=N|A"
                */
                if (*pszVal == 'N')
                  pqp->fMapColors = FALSE;

                else

                  if (*pszVal == 'A')
                    pqp->fMapColors = TRUE;

                  else
                    return (FALSE);
                pszVal++;
              }

              else

                if (!strcmp(pszParm, "ARE"))
                {
                  /*
                  ** Determine size and position of output area "ARE=C|w,h,l,t"
                  */
                  if (*pszVal == 'C')
                  {
                    pqp->fArea = FALSE;
                    pszVal++;
                  }

                  else

                    if (pszVal = ParseQProcPercentList(pszVal, (PBYTE)
                       &pqp->ptAreaSize, 4))
                      pqp->fArea = TRUE;

                    else
                      return (FALSE);
                }

                else

                  if (!strcmp(pszParm, "FIT"))
                  {
                    /*
                    ** Determine which part of picture to print "FIT=S|C|l,t"
                    **
                    ** Note: Undocumented "FIT=C" to center the picture
                    */
                    if (*pszVal == 'S' || *pszVal == 'C')
                    {
                      pqp->fFit = FALSE;

                      if (*pszVal == 'S')
                        pqp->uFit = 0;

                      else
                        pqp->uFit = 1;
                      pszVal++;
                    }

                    else

                      if (pszVal = ParseQProcPercentList(pszVal, (PBYTE)
                         &pqp->ptFit, 2))
                        pqp->fFit = TRUE;

                      else
                        return (FALSE);
                  }

                  else
                    return (FALSE);
  return (*pszVal == '\0');
}

/****************************************************************************
 *
 * FUNCTION NAME = ParseQProcPercentList
 *
 * DESCRIPTION   = parses string representing percentages
 *
 * INPUT         = pszList   - string containing comma separated percentages
 *                 pResult   - points to array of BYTE variable to get
 *                             decimal returned percentages
 *                 cListElem - count of percentages in list
 *
 * OUTPUT        = pzsNext   - points to next percentage value in string to
 *                             parse
 *                 NULL      - end of list or invalid percentage found
 *
 * RETURN-NORMAL
 *
 *
 * RETURN-ERROR  = NULL
 *
 *
 ****************************************************************************/

PSZ ParseQProcPercentList(PSZ pszList,PBYTE pResult,USHORT cListElem)
{
  PSZ p = pszList;
  USHORT n;

  while (*pszList && cListElem)

    if (!*p || *p == ',')
    {

      if (!cListElem--)
        return (NULL);

      if (*p == ',')
        *p++ = '\0';

      if ((n = AsciiToInt(pszList)) > 100)
        return (NULL);
      *pResult++ = (BYTE)n;
      pszList = p;
    }

    else
      p++;

  if (cListElem)
    return (NULL);

  else
    return (p);
}

/****************************************************************************
 *
 * FUNCTION NAME = OpenQPInputFile
 *
 * DESCRIPTION   = Validates and attempts to open print spool file
 *
 * INPUT         = pQProc      - QProc instance pointer for print job
 *                 pszFileName - filename to open(and store in pQProc)
 *                 fOpen       - TRUE if file should be left open(RAW file)
 *                               FALSE if file should be closed
 *
 * OUTPUT        = TRUE   - filename is valid and able to be opened
 *                             parse
 *                 FALSE  - unable to open file(message was displayed to user)
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL OpenQPInputFile(PQPROCINST pQProc,PSZ pszFileName,BOOL fOpen)
{
  ULONG rc;
  ULONG actionTaken;
  ULONG MaxFileHandles;
  FILESTATUS3 inFileStatus;

  if (pszFileName)
  {
    strcpy(pQProc->szFileName, pszFileName);
  }

  if (pQProc->hFile == NULL_HFILE)
  {
    MaxFileHandles = 0;

    do
    {
      rc = DosOpen(pQProc->szFileName, &pQProc->hFile, &actionTaken, (ULONG)0,
                                        /* filesize NotApplicable            */
         (ULONG)0,                     /* no attributes                      */
         OPEN_ACTION_OPEN_IF_EXISTS,   /* open only if exists                */
         OPEN_FLAGS_NOINHERIT|OPEN_ACCESS_READONLY|OPEN_SHARE_DENYNONE, NULL);
                                        /* No EAs to set                     */
      MaxFileHandles += 30;
      ;
    }

    while ((rc == ERROR_TOO_MANY_OPEN_FILES) && ((rc = DosSetMaxFH
       (MaxFileHandles)) != ERROR_NOT_ENOUGH_MEMORY));
  }

  else
  {
    /*
    ** File handle alread set - this is a big error
    */
    rc = ERROR_SHARING_VIOLATION;
  }

  if (rc)
  {

    /*
    ** Unable to open file
    ** Tell User and return failure to SplQpPrint()
    */
    Panic("QP: DosOpen failed for %0s", (PSZ)pQProc->szFileName, rc);
    SplQpMessage(pQProc->pszPortName, SPL_ID_QP_FILE_NOT_FOUND,
                                      PMERR_SPL_FILE_NOT_FOUND);
  }

  else

    if (pQProc->hFile != NULL_HFILE)
    {
      /*
      ** Find size of file
      */
      if (!DosQueryFileInfo(pQProc->hFile, FIL_STANDARD, &inFileStatus, sizeof
         (inFileStatus)))
        pQProc->cbFile = inFileStatus.cbFile;

      if (!fOpen)
      {
        DosClose(pQProc->hFile);
        pQProc->hFile = NULL_HFILE;
      }
      return (TRUE);
    }
  return (FALSE);
}

/****************************************************************************
 *
 * FUNCTION NAME = CloseQPInputFile
 *
 * DESCRIPTION   = Closes print spool file if it is open
 *
 * INPUT         = pQProc      - QProc instance pointer for print job
 *
 * OUTPUT        = TRUE   - file handle closed and reset to NULL_HFILE
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL CloseQPInputFile(PQPROCINST pQProc)
{

  if (pQProc->hFile != NULL_HFILE)
  {
    DosClose(pQProc->hFile);
    pQProc->hFile = NULL_HFILE;
  }
  return (TRUE);
}

/****************************************************************************
 *
 * FUNCTION NAME = OpenQPOutputDC
 *
 * DESCRIPTION   = Open OD_DIRECT DC and issue StartDoc for print job
 *
 * INPUT         = pQProc      - QProc instance pointer for print job
 *                 fAssociate  - Associate flag
 *                               TRUE  - Associate PS to DC for STD jobs
 *                               FALSE - don't Associate PS to DC(Raw job)
 *
 * OUTPUT        = TRUE   - DC opened and StartDoc successfully issued
 *                 FALSE  - Failure to open DC or issue StartDoc
 *                          WinGetLastError() gets failure code.
 *                          Message displayed to user.
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL OpenQPOutputDC(PQPROCINST pQProc,USHORT fAssociate)
{
  DEVOPENSTRUC openData;
  PSZ pszDocument;
  ESCSETMODE ModeData;
  openData.pszLogAddress = (PSZ)pQProc->pszPortName;
  openData.pszDriverName = (PSZ)pQProc->pszDriverName;
  openData.pdriv = pQProc->pDriverData;
  openData.pszDataType = pQProc->pszDataType;
  openData.pszComment = pQProc->pszComment;
  pQProc->hdc = DevOpenDC(HABX, OD_DIRECT, "*", (LONG)DATA_TYPE+1,
     (PDEVOPENDATA)&openData, (HDC)NULL);

  if (!pQProc->hdc)
  {
    Panic("QP: DevOpenDC failed for %0s on %4s", openData.pszLogAddress,
                                                 openData.pszDriverName);
    SplQpMessage(pQProc->pszPortName, SPL_ID_QP_OPENDC_ERROR, 0);
  }

  else
  {

    if (fAssociate == ASSOCIATE)
    {
      GpiAssociate(pQProc->hps, pQProc->hdc);
    }

    /*
    ** Send StartDoc with Document name(if present)
    */
    if (DevEscape(pQProc->hdc, DEVESC_STARTDOC, (pszDocument =
       pQProc->pszDocument)?(ULONG)(SafeStrlen(pszDocument)+1):0L, pszDocument,
       NULL, NULL) != DEVESC_ERROR)
    {
      /*
      ** Handle codepage parameter( "CDP=850" ) for Raw jobs
      ** Metafiles(PM_Q_STD) have codepage info in them
      */
      if ((pQProc->uType == QP_TYPE_RAW) && (pQProc->qparms.uCodePage))
      {
        ModeData.mode = 0;
        ModeData.codepage = pQProc->qparms.uCodePage;
        DevEscape(pQProc->hdc, DEVESC_SETMODE, (ULONG)sizeof(ModeData), (PBYTE)
           &ModeData, 0L, NULL);
      }
      return (TRUE);
    }

    else
    {
      /*
      ** Display error if StartDoc fails
      */
      Panic("QP: StartDoc failed for %0s on %4s", openData.pszLogAddress,
                                                  openData.pszDriverName);
      SplQpMessage(pQProc->pszPortName, SPL_ID_QP_OPENDC_ERROR, 0);
    }
  }
  return (FALSE);
}

/****************************************************************************
 *
 * FUNCTION NAME = CloseQPOutputDC
 *
 * DESCRIPTION   = Close OD_DIRECT DC
 *
 * INPUT         = pQProc      - QProc instance pointer for print job
 *                 fEndDoc     - flag to issue EndDoc
 *                               TRUE  - issue DevEscape(DEVESC_ENDDOC)
 *                               FALSE - don't issue EndDoc because it
 *                                       either was already issued or
 *                                       an AbortDoc was already issued
 *
 * OUTPUT        = TRUE   - DC closed[and EndDoc successfully issued]
 *                 FALSE  - EndDoc failed, but DC closed anyway.
 *                          WinGetLastError() gets failure code.
 *                          No message displayed to user.
 *
 * RETURN-NORMAL = TRUE
 *
 *
 * RETURN-ERROR  = FALSE
 *
 *
 ****************************************************************************/

BOOL CloseQPOutputDC(PQPROCINST pQProc,BOOL fEndDoc)
{
  BOOL result = TRUE;

  if (pQProc->hdc)
  {

    /*
    ** Don't issue EndDoc if AbortDoc already issued for DC
    */
    if ((fEndDoc) && !(pQProc->fsStatus&QP_ABORTED))
    {

      if (DevEscape(pQProc->hdc, DEVESC_ENDDOC, 0L, (PBYTE)NULL, (PLONG)NULL,
         (PBYTE)NULL) == DEVESC_ERROR)
        result = FALSE;
      pQProc->fsStatus |= QP_ENDDOC_COMPLETE;
    }
    DevCloseDC(pQProc->hdc);
    pQProc->hdc = (HDC)0;
  }
  return (result);
}

