/*DDK*************************************************************************/
/*                                                                           */
/* 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.                                */
/*                                                                           */
/*****************************************************************************/
// enable.c

#define INCL_DOS
#define INCL_GPI
#define INCL_DEV
#define INCL_SPL
#define INCL_SPLDOSPRINT
#define INCL_SPLFSE
#define INCL_WINSEI
#define INCL_WINSHELLDATA
#define INCL_GPIERRORS
#include <os2.h>

#define INCL_GREALL
#define INCL_DDIMISC
#define INCL_DDIPATHS
#include <pmddi.h>

// c includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <builtin.h>



#include "def.h"
#include "driver.h"
#include "funcs.h"
#include "assert.h"


// enable defines
#define FILL_LOGICAL                    1
#define FILL_PHYSICAL                   2
#define DISABLE_PHYSICAL                4
#define ENABLE_DC                       5
#define DISABLE_DC                      6
#define SAVE_DC                         7
#define RESTORE_DC                      8
#define RESET_DC                        9
#define COMPLETE_OPEN_DC               10
#define BEGIN_CLOSE_DC                 11








// Fill logical device block parameter structures

struct _LDEVPARM1 {
  ULONG   ulGreVersion;         // graphic engine version
  ULONG   ulDispatchTableSize;  // size of dispatch table
};
typedef struct _LDEVPARM1  LDEVPARM1, *PLDEVPARM1;

struct _LDEVPARM2 {
  PUSHORT  pusFlags;           // bits 0,1,2, 7 modified
  PFN *    ppfnDispatchTable;  // pointer to first function pointer in the dispatch table
};
typedef struct _LDEVPARM2  LDEVPARM2, *PLDEVPARM2;





// -------------------------------------------------------------------------------------------------------------------------
ULONG APIENTRY OS2_PM_DRV_ENABLE( ULONG ulSubFunc, ULONG p1, ULONG p2  )
{
  APIRET             rc;
  BOOL               fOK;
  LONG               lWork;
  PCHAR              pch;
  PCHAR              pch2;
  PDDC               pddc;
  PDEVICEBLOCK       pdb;
  PVOID              pv = NULL;
  PUSHORT            pusFlags;
  REGREC             regrec;
  ULONG              ulException;
  ULONG              ulrc;
  BITMAPINFOHEADER   bmp;
  CHAR               szWork[ 48 ];
  HRGN               hrgnOld = 0;


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here as required
    switch( ulSubFunc ) {
    case FILL_PHYSICAL:
      if( pv ) {
        rc = DosFreeMem( pv );
        assert( rc == 0 );
        pv = NULL;
      }
      break;
    }

    // check for the killed-thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    // error result
    ulrc = -1;
    goto depart;
  }


  switch( ulSubFunc ) {
  case FILL_LOGICAL:
    // p1 points to engine version and dispatch table size
    // p2 points to flags and dispatch table
    // there is no inverse disable step to this subfunction

    assert( p1 );
    assert( p2 );
    DBPRINTF(( "FILL_LOGICAL(1); p1=%x, p2=%x\n", p1, p2 ));


    // init the globals structure if not already done
    rc = DosRequestMutexSem( procdata.hmtxGlobalSem, SEM_INDEFINITE_WAIT );
    assert( rc == 0 );
    if( NULL == globals.ppfnDispatchTableCopy ) {
      // copy params to global shared space
      globals.ulGreVersion          = ((PLDEVPARM1)p1)->ulGreVersion;
      globals.cDispatchEntries      = ((PLDEVPARM1)p1)->ulDispatchTableSize;

      // compute length required for copy of dispatch table
      lWork = sizeof(PFN)*globals.cDispatchEntries;
      // allocate memory for the copy out of shared space
      globals.ppfnDispatchTableCopy = AllocSharedMem( lWork );
      assert( globals.ppfnDispatchTableCopy );
      // make a copy the table as supplied by the engine
      memcpy( globals.ppfnDispatchTableCopy, ((PLDEVPARM2)p2)->ppfnDispatchTable, lWork );

      // change function pointers in the engine's dispatch table

      // attribute functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceSetAttributes       & 0x00FF ] = (PFN)DeviceSetAttributes;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceGetAttributes       & 0x00FF ] = (PFN)DeviceGetAttributes;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceSetGlobalAttribute  & 0x00FF ] = (PFN)DeviceSetGlobalAttribute;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetPairKerningTable       & 0x00FF ] = (PFN)GetPairKerningTable;
      // bitmap functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreBitblt                    & 0x00FF ] = (PFN)Bitblt;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceCreateBitmap        & 0x00FF ] = (PFN)DeviceCreateBitmap;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceDeleteBitmap        & 0x00FF ] = (PFN)DeviceDeleteBitmap;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceSelectBitmap        & 0x00FF ] = (PFN)DeviceSelectBitmap;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDrawBits                  & 0x00FF ] = (PFN)DrawBits;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDrawBorder                & 0x00FF ] = (PFN)DrawBorder;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetBitmapBits             & 0x00FF ] = (PFN)GetBitmapBits;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetPel                    & 0x00FF ] = (PFN)GetPel;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreImageData                 & 0x00FF ] = (PFN)ImageData;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreSetBitmapBits             & 0x00FF ] = (PFN)SetBitmapBits;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreSetPel                    & 0x00FF ] = (PFN)SetPel;
      // color table functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreCreateLogColorTable       & 0x00FF ] = (PFN)CreateLogColorTable;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryColorData            & 0x00FF ] = (PFN)QueryColorData;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryColorIndex           & 0x00FF ] = (PFN)QueryColorIndex;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryLogColorTable        & 0x00FF ] = (PFN)QueryLogColorTable;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryNearestColor         & 0x00FF ] = (PFN)QueryNearestColor;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryRealColors           & 0x00FF ] = (PFN)QueryRealColors;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryRGBColor             & 0x00FF ] = (PFN)QueryRGBColor;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreRealizeColorTable         & 0x00FF ] = (PFN)RealizeColorTable;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreUnrealizeColorTable       & 0x00FF ] = (PFN)UnrealizeColorTable;
      // device functions set 2
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceQueryFontAttributes & 0x00FF ] = (PFN)DeviceQueryFontAttributes;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceQueryFonts          & 0x00FF ] = (PFN)DeviceQueryFonts;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreErasePS                   & 0x00FF ] = (PFN)ErasePS;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreNotifyClipChange          & 0x00FF ] = (PFN)NotifyClipChange;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreNotifyTransformChange     & 0x00FF ] = (PFN)NotifyTransformChange;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreRealizeFont               & 0x00FF ] = (PFN)RealizeFont;
      // device functions set 3
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreAccumulateBounds          & 0x00FF ] = (PFN)AccumulateBounds;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDeviceSetDCOrigin         & 0x00FF ] = (PFN)DeviceSetDCOrigin;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetBoundsData             & 0x00FF ] = (PFN)GetBoundsData;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetCodePage               & 0x00FF ] = (PFN)GetCodePage;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetDCOrigin               & 0x00FF ] = (PFN)GetDCOrigin;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetLineOrigin             & 0x00FF ] = (PFN)GetLineOrigin;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreLockDevice                & 0x00FF ] = (PFN)LockDevice;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreResetBounds               & 0x00FF ] = (PFN)ResetBounds;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreSetCodePage               & 0x00FF ] = (PFN)SetCodePage;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreSetLineOrigin             & 0x00FF ] = (PFN)SetLineOrigin;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreUnlockDevice              & 0x00FF ] = (PFN)UnlockDevice;
      // escape
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreEscape                    & 0x00FF ] = (PFN)Escape;
      // lines
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDisjointLines             & 0x00FF ] = (PFN)DisjointLines;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreDrawLinesInPath           & 0x00FF ] = (PFN)DrawLinesInPath;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreGetCurrentPosition        & 0x00FF ] = (PFN)GetCurrentPosition;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGrePolyLine                  & 0x00FF ] = (PFN)PolyLine;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGrePolyScanline              & 0x00FF ] = (PFN)PolyScanline;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGrePolyShortLine             & 0x00FF ] = (PFN)PolyShortLine;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreSetCurrentPosition        & 0x00FF ] = (PFN)SetCurrentPosition;
      // markers
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGrePolyMarker                & 0x00FF ] = (PFN)PolyMarker;
      // query functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryDeviceBitmaps        & 0x00FF ] = (PFN)QueryDeviceBitmaps;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryDeviceCaps           & 0x00FF ] = (PFN)QueryDeviceCaps;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryDevResource2         & 0x00FF ] = (PFN)QueryDevResource2;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryHardcopyCaps         & 0x00FF ] = (PFN)QueryHardcopyCaps;
      // text functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreCharString                & 0x00FF ] = (PFN)CharString;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreCharStringPos             & 0x00FF ] = (PFN)CharStringPos;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryCharPositions        & 0x00FF ] = (PFN)QueryCharPositions;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryTextBox              & 0x00FF ] = (PFN)QueryTextBox;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[  NGreQueryWidthTable           & 0x00FF ] = (PFN)QueryWidthTable;

      // assign a local copy of the pointer to a WORD of flags
      pusFlags = ((PLDEVPARM2)p2)->pusFlags;
      // set flags; clear bits 0,1,2
      *pusFlags &= 0xFFF8;
      // bit 0: each dc has own physical device block
      // bit 7: mult dispatch tables on fill physical
      *pusFlags |= 0x0081;
    }
    rc = DosReleaseMutexSem( procdata.hmtxGlobalSem );
    assert( rc == 0 );


    // success result
    ulrc = 0;
    break;




  case FILL_PHYSICAL:
    // p1 points to a DEVOPENSTRUC followed by stateinfo, type, and hdc
    // p2 is the per-DC dispatch table
    // inverse is DISABLE_PHYSICAL
    assert( p1 );
    assert( p2 );
    DBPRINTF (("FILL_PHYSICAL(2); p1=%x p2=%x\n", p1, p2 ));


    rc = DosAllocMem( &pv, LEN_DCHEAP, PAG_WRITE );
    assert( rc == 0 );
    // init for suballocation (heap init)
    rc = DosSubSetMem( pv, DOSSUB_INIT | DOSSUB_SPARSE_OBJ, LEN_DCHEAP );
    assert( rc == 0 );

    // allocate deviceblock space out of this dc heap
    pdb = (PDEVICEBLOCK) AllocMem( pv, sizeof( DEVICEBLOCK ));
    assert( pdb );

    // store base of this DC heap
    pdb->pvHeap = pv;

    // copy strings and data from the devopenstruct (dos) to my device block pdb
    if( ((PDEVOPENSTRUC)p1)->pszLogAddress  ) {
      strncpy( pdb->szLogAddress, ((PDEVOPENSTRUC)p1)->pszLogAddress, sizeof( pdb->szLogAddress )-1  );
      pdb->dop.pszLogAddress = pdb->szLogAddress;
    }

    if(  ((PDEVOPENSTRUC)p1)->pszDriverName ) {
      strncpy( pdb->szDriverName, ((PDEVOPENSTRUC)p1)->pszDriverName, sizeof( pdb->szDriverName )-1  );
      pdb->dop.pszDriverName = pdb->szDriverName;
    }

    // driver data

    if( ((PDEVOPENSTRUC)p1)->pdriv ) {
      // caller has supplied driver data; check known versions
      switch( ((PDEVOPENSTRUC)p1)->pdriv->lVersion ) {
      case DRIVERDATA_VERSION:
        // fine the way it is
        memcpy( &pdb->DriverData, ((PDEVOPENSTRUC)p1)->pdriv, sizeof( DRIVERDATA ));
        break;
      default:
        // don't know about this version of drivdata
        SetErrorInfo( SEVERITY_ERROR, PMERR_INV_DRIVER_DATA );
        RAISEEXCEPTION( XCPT_USER );
        break;
      }
    } else {
      // caller left pdriv NULL; not an error, so go for all default values
      // NEEDS WORK; see page 7-10 in 2.0 driver ref concerning when pdriv supplied is null
      assert( FALSE );
    }
    pdb->dop.pdriv = (PDRIVDATA)&pdb->DriverData;



    // data type STD or RAW
    if( ((PDEVOPENSTRUC)p1)->pszDataType ) {
      strncpy( pdb->szDataType, ((PDEVOPENSTRUC)p1)->pszDataType, sizeof( pdb->szDataType ) -1 );
    } else {
      // assume raw
      assert( sizeof(  pdb->szDataType ) > strlen( "PM_Q_RAW" ));
      strcpy( pdb->szDataType, "PM_Q_RAW" );
    }
    pdb->dop.pszDataType = pdb->szDataType;


    if( ((PDEVOPENSTRUC)p1)->pszComment ) {
      strncpy( pdb->szComment, ((PDEVOPENSTRUC)p1)->pszComment, sizeof( pdb->szComment )- 1 );
      pdb->dop.pszComment = pdb->szComment;
    }

    if( ((PDEVOPENSTRUC)p1)->pszSpoolerParams ) {
      // a string used to specify form a la: FORM=Letter   or   FORM="Letter"
      strncpy( pdb->szSpoolerParams, ((PDEVOPENSTRUC)p1)->pszSpoolerParams, sizeof( pdb->szSpoolerParams ) -1  );
      pdb->dop.pszSpoolerParams = pdb->szSpoolerParams;

      // more to do, see below: must parse FORM= for preferred form name
    }

    // make a PDENPARAMS from p1
    pch = ((PCHAR)p1) + sizeof( DEVOPENSTRUC );

    // copy dem params
    pdb->ulStateInfo = ((PDENPARAMS)pch)->ulStateInfo;   // not used here, according to 2.0 pubs
    pdb->ulType      = ((PDENPARAMS)pch)->ulType;

    // get hdc at ENABLE_DC time
    assert( (HDC)0 == ((PDENPARAMS)pch)->ulHDC );

    // assign spool data type
    if( 0 == strcmp( pdb->szDataType, "PM_Q_STD" ) &&  pdb->ulType == OD_QUEUED ) {
      // DC can only be PM_Q_STD if OD_QUEUED
      pdb->ulDataType = PM_Q_STD;
    } else {
      // otherwise, it will be PM_Q_RAW
      pdb->ulDataType = PM_Q_RAW;
    }

    // create a mutex sem to serialize access to this dc; unowned state
    rc = DosCreateMutexSem( NULL, &pdb->hmtxDCSem, 0, 0 );
    assert( rc == 0 );

    // fill in the rest of the structure
    pdb->fStartDocCalled      = FALSE;
    pdb->fAbortDocCalled      = FALSE;
    pdb->szDocumentName[0]    = 0;
    pdb->ulPage               = 0;

    if( ((PDEVOPENSTRUC)p1)->pdriv )  {
      // hunt for the right device given the device name
      pdb->pSupportedDevice  = PSDFromDeviceName(  ((PDEVOPENSTRUC)p1)->pdriv->szDeviceName );
      assert( pdb->pSupportedDevice  );
    } else {
      // default to the first one
      pdb->pSupportedDevice  = globals.paDevice;
    }



    // parse spooler parm for form name
    // if found, assign local variable pFormInfo to point to the form specified by name with FORM= syntax
    pdb->pFormInfo = NULL;
    pch = strstr( pdb->szSpoolerParams, "FORM=" );
    if( pch ) {
      // bump pch past FORM= to form name; form name may be quoted with ' or " or not quoted at all
      pch += strlen( "FORM=" );

      // null end the string at the next space if present
      pch2 = strchr( pch, ' ' );
      if( pch2 ) *pch2 = 0;

      // make a copy of the form name
      assert( strlen( pch ) < sizeof( szWork ));
      strncpy( szWork, pch, sizeof( szWork )-1 );

      // strip off quotes and other delimiters on both ends of the string; strip() works something like Rexx strip()
      Strip( szWork, "BOTH", "'\" =,;" );

      if( strlen( szWork )) {
        // see if this form name is supported
        pdb->pFormInfo = FormInfoFromFormName( pdb->pSupportedDevice, szWork );
      }
    }

    if( !pdb->pFormInfo ) {
      // FORM= method did not work out; use the form name specified in the DriverData
      pdb->pFormInfo = FormInfoFromFormName( pdb->pSupportedDevice, pdb->DriverData.szFormName );
    }

    if( !pdb->pFormInfo ) {
      // default to something
      pdb->pFormInfo = pdb->pSupportedDevice->paFormInfo;
    }

    assert( pdb->pFormInfo );

    ulrc = (ULONG)pdb;
    break;





  case DISABLE_PHYSICAL:
    // p1 is pdb
    // inverse of FILL_PHYSICAL
    assert( p1 );
    DBPRINTF (("DISABLE_PHYSICAL(4); pdb=%x\n", p1 ));
    // cast a copy of pdb
    pdb = (PDEVICEBLOCK) p1;
    rc = DosCloseMutexSem( pdb->hmtxDCSem );
    assert( rc == 0 );
    // copy heap base pointer to an automatic variable
    pv = pdb->pvHeap;
    // undo the subset
    rc = DosSubUnsetMem( pv );
    assert( 0 == rc );
    // free the dc heap
    rc = DosFreeMem( pv );
    assert( 0 == rc );
    // success
    ulrc = 0;
    break;






  case ENABLE_DC:
    // p1 is a denparams
    // p2 is not used
    // inverse of DISABLE_DC
    assert( p1 );
    assert( (ULONG)p2  == 0 );
    DBPRINTF (("ENABLE_DC(5); p1=%x\n", p1 ));

    pdb = (PDEVICEBLOCK) ((PDENPARAMS)p1)->ulStateInfo;
    assert( pdb );

    pddc = (PDDC)AllocMem( pdb->pvHeap, sizeof( DDC ));
    assert( pddc );
    pddc->pdb = pdb;

    // store hdc
    pdb->hdc = ((PDENPARAMS)p1)->ulHDC;

    // fill in DDC

    // GreOpenDC a display OD_MEMORY DC to be used as a "shadow" DC for the printer DC
    // HDC=0 means obtain a display DC
    pddc->hdcShadow = GreOpenDC( (HDC)0, OD_MEMORY, "*", 0, (PDEVOPENDATA)0 );
    assert( pddc->hdcShadow != DEV_ERROR );
    assert( pddc->hdcShadow );

    // create an empty region to contain the clipping region for the shadow dc
    // at NotifiyClipChange time, this region will be set to the clipping region
    pddc->hrgnShadow = GreCreateRectRegion( pddc->hdcShadow, (PRECTL) NULL, 0 );
    assert( pddc->hrgnShadow );


    switch( pddc->pdb->ulType ) {
    case OD_DIRECT:
      switch( pddc->pdb->ulDataType ) {
      case PM_Q_RAW:
        break;
      case PM_Q_STD:
        // impossible
        assert( FALSE );
        break;
      }
      break;
    case OD_QUEUED:
      switch( pddc->pdb->ulDataType ) {
      case PM_Q_RAW:
        break;
      case PM_Q_STD:
        break;
      }
      break;
    }

    ulrc = (ULONG) pddc;
    break;







  case DISABLE_DC:
    // p1 is pointer to DC   pddc
    // inverse of ENABLE_DC
    assert( p1 );
    DBPRINTF (("DISABLE_DC(6); pddc = %X\n", p1 ));
    pddc = (PDDC)p1;

    FreeMem( pddc );
    // success
    ulrc = 0;
    break;




  case COMPLETE_OPEN_DC:
    // p1 is hdc
    // p2 is pddc
    // inverse of BEGIN_CLOSE_DC
    assert( p1 );
    assert( p2 );
    assert( ((PDDC)p2)->pdb->hdc == (HDC)p1 );
    DBPRINTF (("COMPLETE_OPEN_DC(10); hdc=%x; pddc=%x\n", p1, p2 ));
    pddc = (PDDC)p2;
    pdb  = pddc->pdb;


    // open spool buffer for queued PM_Q_STD data
    if( pddc->pdb->ulDataType == PM_Q_STD ) {
      assert( pddc->pdb->ulType == OD_QUEUED );

      fOK = SplStdOpen( pddc->pdb->hdc );
      assert( fOK );
    }


    // create a bitmap for the shadow DC
    // DEVICE_SPECIFIC: bitmapinfoheader setup would change for a color printer
    // this bitmapinfoheader assumes a monochrome laser printer
    bmp.cbFix       = sizeof( bmp );
    bmp.cx          = pdb->pFormInfo->hcinfo.xPels;
    bmp.cy          = pdb->pFormInfo->hcinfo.yPels;
    bmp.cPlanes     = 1;
    bmp.cBitCount   = 1;

    pddc->hbmShadow = GreCreateBitmap( pddc->hdcShadow, &bmp, 0, NULL, NULL );
    assert( pddc->hbmShadow );

    // select this bitmap into the shadow DC
    lWork = GreSelectBitmap( pddc->hdcShadow, pddc->hbmShadow );
    assert( lWork == 0 );

    lWork = GreErasePS (pddc->hdcShadow) ;
    assert( lWork );

    pddc->fComplete = TRUE;

    // success result
    ulrc = 0;
    break;



  case BEGIN_CLOSE_DC:
    // p1 is hdc; p2 is pddc
    // last chance at engine calls and close resources
    // inverse is COMPLETE_OPEN_DC

    assert( p1 );
    assert( p2 );
    DBPRINTF (("BEGIN_CLOSE_DC(11);  hdc=%x; pddc=%x\n", p1, p2 ));
    pddc = (PDDC)p2;

    // deselect the clip region for the shadow DC
    lWork = GreSelectClipRegion( pddc->hdcShadow, (HRGN)0, &hrgnOld );
    assert( lWork != (LONG)RGN_ERROR );
    assert( hrgnOld ? hrgnOld == pddc->hrgnShadow : TRUE );


    // clean up the clip region
    fOK = GreDestroyRegion( pddc->hdcShadow, pddc->hrgnShadow );
    assert( fOK );
    pddc->hrgnShadow = 0;

    // de-select the bitmap from the shadow DC
    lWork = GreSelectBitmap( pddc->hdcShadow, 0 );
    assert( (HBITMAP)lWork != HBM_ERROR );

    // destroy the bitmap for the shadow DC
    lWork = GreDeleteBitmap( pddc->hbmShadow );
    assert( lWork );
    pddc->hbmShadow = 0;

    // close the shadow DC in the display
    fOK = GreCloseDC( pddc->hdcShadow );
    assert( fOK );
    pddc->hdcShadow = 0;

    // close spool buffer for queued PM_Q_STD data
    if( pddc->pdb->ulDataType == PM_Q_STD ) {
      fOK = SplStdClose( pddc->pdb->hdc );
      assert( fOK );
    }

    // if a queued job was ever created for this DC, close spooler handle now
    if( pddc->pdb->hspl ) {
      fOK = SplQmClose( pddc->pdb->hspl );
      assert( fOK );
      pddc->pdb->hspl = 0;
    }


    pddc->fComplete = FALSE;

    // success result
    ulrc = 0;
    break;





  case SAVE_DC:
    DBPRINTF (("SAVE_DC(7); pddc=%x", p1  ));
    pddc = (PDDC)p1;
    fOK = GreSaveDC( pddc->hdcShadow );
    assert( fOK );
    // success result
    ulrc = 0;
    break;



  case RESTORE_DC:
    DBPRINTF (("RESTORE_DC(8); pddc=%x  state number=%x\n", p1, p2 ));
    pddc = (PDDC)p1;

    // deselect the clip region for the shadow DC
    lWork = GreSelectClipRegion( pddc->hdcShadow, (HRGN)0, &hrgnOld );
    assert( lWork != (LONG)RGN_ERROR );
    assert( hrgnOld ? hrgnOld == pddc->hrgnShadow : TRUE );

    fOK = GreRestoreDC( pddc->hdcShadow, (LONG)p2 );
    assert( fOK );

    // select the clip region for the shadow DC
    //lWork = GreSelectClipRegion( pddc->hdcShadow, pddc->hrgnShadow, &hrgnOld );
    //assert( lWork != (LONG)RGN_ERROR );
    //assert( hrgnOld == 0 );

    // success result
    ulrc = 0;
    break;



  case RESET_DC:
    DBPRINTF (("RESET_DC(9); pddc=%x \n", p1 ));
    pddc = (PDDC)p1;

    // deselect the clip region for the shadow DC; otherwise, the engine will delete the region if selected
    lWork = GreSelectClipRegion( pddc->hdcShadow, (HRGN)0, &hrgnOld );
    assert( lWork != (LONG)RGN_ERROR );
    assert( hrgnOld ? hrgnOld == pddc->hrgnShadow : TRUE );

    // reset the shadow DC
    fOK = GreResetDC( pddc->hdcShadow, 0 );
    assert( fOK );

    // select the clip region for the shadow DC
    //lWork = GreSelectClipRegion( pddc->hdcShadow, pddc->hrgnShadow, &hrgnOld );
    //assert( lWork != (LONG)RGN_ERROR );
    //assert( hrgnOld == 0 );

    // success result
    ulrc = 0;
    break;

  default:
    // unknown enable subfunction; -1 indicates unsupported
    ulrc = -1;
    break;
  }



depart:
  UNREGISTERHANDLER( regrec );
  return ulrc;
}





// -------------------------------------------------------------------------------------------------------------------
LONG APIENTRY OS2_PM_DRV_DEVICENAMES (  PSZ       pszDriverName,
                                        PLONG     pcNames,
                                        PSZ       pstr32DeviceNames,
                                        PSZ       pstr64DeviceDescriptions,
                                        PLONG     pcDataTypes,
                                        PSZ       pstr16DataTypes,
                                        ULONG     ulReserved1,
                                        ULONG     ulReserved2 )
{

  LONG        i;
  REGREC      regrec;
  ULONG       ulException;
  LONG        lrc;


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here
    // check for the killed-thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    // error result
    lrc = 0;
    goto depart;
  }


  if( *pcNames ) {
    // wants names and descriptions
    for( i = 0; i < min( globals.ulDeviceCount,  *pcNames); i++ ) {
      strcpy( pstr32DeviceNames,    globals.paDevice[ i ].szDeviceName  );
      strcpy( pstr64DeviceDescriptions, "A fine printer." );
      pstr32DeviceNames         += sizeof( STR32 );
      pstr64DeviceDescriptions  += sizeof( STR64 );
    }
  }
  *pcNames = globals.ulDeviceCount;



  if(  *pcDataTypes ) {
    // wants data type data
    for( i = 0; i < min( 2, *pcDataTypes ); i++ ) {
      switch( i ) {
      case 0:
        strcpy( pstr16DataTypes,         "PM_Q_STD"             );
        break;
      case 1:
        strcpy( pstr16DataTypes,         "PM_Q_RAW"             );
        break;
      }
      pstr16DataTypes  += 16;
    }
  }
  *pcDataTypes =  2;


  // successful result
  lrc = -1;

depart:
  UNREGISTERHANDLER( regrec );
  return lrc;
}





// -----------------------------------------------------------------------------------------------------
LONG APIENTRY OS2_PM_DRV_DEVMODE(  PDRIVDATA pDrivdata,
                                   PSZ       pszDriverName,
                                   PSZ       pszDeviceName,
                                   PSZ       pszPrinterName,
                                   ULONG     ulOption )

{
  BOOL                 fOK;
  LONG                 lrc;
  LONG                 rc;
  PCHAR                pszAppname  = NULL;
  PDLGINSTANCE         pdi         = NULL;
  PDRIVERDATA          pdriverdata = NULL;
  PSUPPORTEDDEVICE     psdDesired;
  REGREC               regrec;
  SHORT                pmerr = 0;
  ULONG                ul;
  ULONG                ulException;




  assert( pszDeviceName );
  assert( strlen( pszDeviceName ));
  assert( pDrivdata ? pDrivdata->cb > 0 : TRUE );


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here
    if( pdi ) {
      FreeMem( pdi );
      pdi = NULL;
    }
    if( pdriverdata ) {
      FreeMem( pdriverdata );
      pdriverdata = NULL;
    }
    if( pszAppname  ) {
      FreeMem( pszAppname );
      pszAppname = NULL;
    }
    if( pmerr ) {
      SetErrorInfo( SEVERITY_ERROR, pmerr );
    }
    // check for the killed-thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    // error result
    lrc = DPDM_ERROR;
    goto depart;
  }




  // sanity checks

  if( !pDrivdata ) {
    // user wants to know the length of driver data
    lrc = sizeof( DRIVERDATA );
    goto depart;
  }

  if( !pszPrinterName && ulOption == DPDM_CHANGEPROP ) {
    // invalid combination
    pmerr = PMERR_INV_DRIVER_NAME;
    RAISEEXCEPTION( XCPT_USER );
  }

  if( !pszDeviceName ) {
    pmerr = PMERR_INV_DEVICE_NAME;
    RAISEEXCEPTION( XCPT_USER );
  }

  // search for supplied device name in array of supported devices
  if( ! PSDFromDeviceName( pszDeviceName )) {
    // one given, but it's not mine
    pmerr = PMERR_INV_DEVICE_NAME;
    RAISEEXCEPTION( XCPT_USER );
  }

  // allocate from heap; conserve stack space
  pszAppname = AllocSharedMem( LEN_APPNAME );
  assert( pszAppname );

  // build the INI file Application name for accessing the INI file for this device
  fOK = BuildAppName( pszAppname, LEN_APPNAME, pszPrinterName, pszDriverName, pszDeviceName );
  assert( fOK );


  // look at options
  switch( ulOption ) {
  case DPDM_POSTJOBPROP:
  case DPDM_CHANGEPROP:

    // prepare to display a dialog box

    // allocate memory for dialog instance data
    // shared memory because there is no DC private heap around to allocate from
    pdi = (PDLGINSTANCE) AllocSharedMem( sizeof( DLGINSTANCE ));
    assert( pdi );

    // copy DEVMODE parameters into dialog instance data
    pdi->pddSupplied       = (PDRIVERDATA)pDrivdata;
    pdi->pszDriverName     = pszDriverName;
    pdi->pszDeviceName     = pszDeviceName;
    pdi->pszPrinterName    = pszPrinterName;
    pdi->ulOption          = ulOption;

    // find the pointer to the supporteddevice struct given the pszDeviceName supplied
    psdDesired = PSDFromDeviceName( pszDeviceName );
    assert( psdDesired );
    pdi->pSupportedDevice = psdDesired;

    // see which forms were previously stored as being selectable according to settings in the INI file
    // rets count returned including null trailer byte
    ul = PrfQueryProfileString( HINI_SYSTEMPROFILE, pszAppname, KEYNAME_SELECTABLEFORMS,
                                "Letter;",  // default value
                                pdi->achSelectableForms, sizeof( pdi->achSelectableForms ));
    assert( ul );


    switch( pDrivdata->lVersion ) {
    case 0:
      // can happen on DPDM_CHANGEPROP after an install
      pDrivdata->lVersion = DRIVERDATA_VERSION;
      fOK = SetDriverDataDefaults( (PDRIVERDATA)pDrivdata, pDrivdata->cb, pszDeviceName );
      assert( fOK );
      break;
    case DRIVERDATA_VERSION:
      break;
    default:
      pmerr = PMERR_INV_DRIVER_DATA;
      RAISEEXCEPTION( XCPT_USER );
      break;
    }


    assert( pDrivdata->cb == sizeof( DRIVERDATA ));
    memcpy( &pdi->ddScratch, pDrivdata, sizeof( DRIVERDATA ));


    switch( ulOption ) {
    case DPDM_POSTJOBPROP:
      // applications usually call with this option

      // perhaps device name in driverdata has not yet been set
      if( 0 == strlen( pDrivdata->szDeviceName )) {
        // copy the devicename supplied on call into drivdata
        assert( strlen( pszDeviceName ) );
        assert( strlen( pszDeviceName ) < sizeof( pdi->pddSupplied->szDeviceName ));
        strcpy( pdi->pddSupplied->szDeviceName, pszDeviceName );
      }

      rc = WinDlgBox( HWND_DESKTOP, HWND_DESKTOP, (PFNWP)JobPropertiesDlgProc, globals.hModule, IDD_JOB_PROPERTIES, (PVOID)pdi );
      assert( rc != DID_ERROR );

      // copy modified job properties back into caller's space
      assert( pDrivdata->cb >= sizeof( pdi->ddScratch ));
      *(PDRIVERDATA)pDrivdata = pdi->ddScratch;
      break;

    case DPDM_CHANGEPROP:
      // supposedly, only the shell calls with this option
      rc = WinDlgBox( HWND_DESKTOP, HWND_DESKTOP, (PFNWP)PrinterPropertiesDlgProc, globals.hModule,
                      IDD_PRINTER_PROPERTIES, (PVOID)pdi );
      assert( rc != DID_ERROR );
      // write selectable forms back to INI
      fOK = PrfWriteProfileString( HINI_SYSTEMPROFILE, pszAppname, KEYNAME_SELECTABLEFORMS, pdi->achSelectableForms );
      assert( fOK );
      // write drivdata back to INI
      fOK = PrfWriteProfileData( HINI_SYSTEMPROFILE, pszAppname, KEYNAME_DRIVERDATA, &pdi->ddScratch, sizeof( pdi->ddScratch ));
      assert( fOK );

      // copy modified job properties back into caller's space
      assert( pDrivdata->cb >= sizeof( pdi->ddScratch ));
      *(PDRIVERDATA)pDrivdata = pdi->ddScratch;
      break;

    default:
      assert( FALSE );
      break;
    }


    // free dialog instance data
    FreeMem( pdi );
    pdi = NULL;

    break;





  case DPDM_QUERYJOBPROP:
    // wants job properties from INI; if none there, return some defaults

    pdriverdata = (PDRIVERDATA) AllocSharedMem( sizeof( DRIVERDATA ));
    assert( pdriverdata );

    // look in the INI for driver data
    ul = sizeof( DRIVERDATA );
    fOK = PrfQueryProfileData( HINI_SYSTEMPROFILE, pszAppname, KEYNAME_DRIVERDATA,  (PVOID)pdriverdata, &ul );

    // if present, it should be the right length
    assert( fOK ? ul == sizeof( DRIVERDATA ): TRUE );

    if( !fOK ) {
      // not in INI file; make up the initial defaults for driverdata
      fOK = SetDriverDataDefaults( pdriverdata, sizeof( DRIVERDATA ), pszDeviceName );
      assert( fOK );
    }

    // if memcpy causes trap, set this error
    pmerr = PMERR_INV_DRIVER_DATA;
    // copy results back to user
    memcpy( pDrivdata, pdriverdata, sizeof( DRIVERDATA ));

    // return memory to global heap
    FreeMem( pdriverdata );
    pdriverdata = NULL;
    break;


  default:
    // blow up debug version
    assert( FALSE );
    break;
  }


  if( pszAppname  ) {
    FreeMem( pszAppname );
    pszAppname = NULL;
  }


  lrc = DEV_OK;

depart:
  UNREGISTERHANDLER( regrec );
  return lrc;
}




// --------------------------------------------------------------------------------------------------------------------
// driver is about to be installed or reinstalled
// at this time, migrate old settings in the INI to current driver standards


VOID APIENTRY DrvInstall( )
{
  BOOL               fOK;
  ULONG              ulWork;
  PCHAR              pchAppNames = NULL;
  PCHAR              pchKeyNames = NULL;
  PCHAR              pch2;
  PCHAR              pch;
  REGREC             regrec;
  ULONG              ulException;
  CHAR               szModule[ 16 ];


  assert( globals.pvSharedHeap );


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here
    if( pchAppNames ) {
      FreeMem( pchAppNames );
      pchAppNames = NULL;
    }
    if( pchKeyNames ) {
      FreeMem( pchKeyNames );
      pchKeyNames = NULL;
    }
    // check for the killed-thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    goto depart;
  }


  // get the 8-character module name from the fully-qualified module name in globals
  pch2 = strrchr( globals.szModule, '.' );
  assert( pch2 );
  *pch2 = 0;

  pch = strrchr( globals.szModule, '\\' );
  assert( pch );

  assert( sizeof( szModule ) > strlen( szModule ));
  strcpy( szModule, pch+1 );

  // restore globals back
  *pch2 = '.';






  // this pair of nested loops finds all MDRIVER application and key names in the INI
  // if any are found, this function should migrate the contents to this new version of the driver

  ulWork = 0;
  fOK = PrfQueryProfileSize( HINI_SYSTEMPROFILE, NULL, NULL, &ulWork );
  assert( fOK );

  pchAppNames = (PCHAR) AllocSharedMem( ulWork );
  assert( pchAppNames );

  PrfQueryProfileString( HINI_SYSTEMPROFILE, NULL, NULL, NULL, pchAppNames, ulWork );

  pch = pchAppNames;

  while( *pch ) {
    if( strstr( pch, szModule )  &&  0 == strncmp( pch, "PM_DD_", 6 )) {
      // this INI application name has my module name in it; enumerate all keys that go with it
      ulWork = 0;
      fOK = PrfQueryProfileSize( HINI_SYSTEMPROFILE, pch, NULL, &ulWork );
      assert( fOK );

      pchKeyNames = (PCHAR) AllocSharedMem( ulWork );
      assert( pchKeyNames );

      PrfQueryProfileString( HINI_SYSTEMPROFILE, pch, NULL, NULL, pchKeyNames, ulWork );

      pch2 = pchKeyNames;
      while( *pch2 ) {

        // should migrate the printer properties, driver data here

        // For now, delete this app/key combination and start fresh
        fOK = PrfWriteProfileData( HINI_SYSTEMPROFILE, pch, pch2, NULL, 0 );
        assert( fOK );

        // on to next key
        pch2 += ( 1 + strlen( pch2 ));
      }

      // return memory to the heap
      FreeMem( pchKeyNames );
      pchKeyNames = NULL;
    }

    // on to next app name
    pch += ( 1 + strlen( pch ));
  }


  // return memory to the heap
  FreeMem( pchAppNames );
  pchAppNames = NULL;



depart:
  UNREGISTERHANDLER( regrec );
}


