/*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
// code for OS2_PM_DRV_ENABLE

#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_32
#define INCL_VMANDDI
#define INCL_GRE_DEVICESURFACE
#include <ddi.h>

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

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


#define INCL_GENPLIB_MEMORY
#define INCL_GENPLIB_JOURNAL
#include <genplib.h>


#include "def.h"
#include "driver.h"
#include "funcs.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
#define QUERY_DEVICE_SURFACE           14








// 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;
  CHAR               szWork[ 48 ];
  HMCB               hmcb = 0;
  LONG               lWork;
  PCHAR              pch2;
  PCHAR              pch;
  PDDC               pddc;
  PDDC               pddcCompatible;
  PDENPARAMS         pdenparams;
  PDEVICESURFACE     pds;
  PDEVOPENSTRUC      pdevoSource;
  PUSHORT            pusFlags;
  REGREC             regrec;
  ULONG              ulException;
  ULONG              ulrc;
  int                i;


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here as required
    switch( ulSubFunc ) {
    case FILL_PHYSICAL:
      if( hmcb ) {
        rc = GplMemoryDeleteInstance( hmcb );
        assert( rc == 0 );
        hmcb = 0;
      }
      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;
  }


  DBPRINTF(( "OS2_PM_DRV_ENABLE: " ));

  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
    DBPRINTF(( "FILL_LOGICAL(1); p1=%x, p2=%x\n", p1, p2 ));

    assert( p1 );
    assert( p2 );

    if(  ((PLDEVPARM1)p1)->ulGreVersion  < 0x0220  ||  !globals.pfnSetDeviceSurface ) {
      // error
      WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, "Requires GRE version 0x0220", "MDriver", 0, MB_CANCEL );
      SetErrorInfo( SEVERITY_ERROR, PMERR_INV_DRIVER_DATA );
      RAISEEXCEPTION( XCPT_USER );
    }

    // This was set in initterm.c.
    assert( globals.pfnSetDeviceSurface );

    // 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 = GplMemoryAlloc( globals.hmcbSharedHeap, lWork );
      assert( globals.ppfnDispatchTableCopy );
      // make a copy the table as supplied by the engine
      memcpy( globals.ppfnDispatchTableCopy, ((PLDEVPARM2)p2)->ppfnDispatchTable, lWork );

      // hook mandatory dispatch table functions
      ((PLDEVPARM2)p2)->ppfnDispatchTable[ NGreEscape             & 0x00FF ] = (PFN)Escape;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[ NGreQueryHardcopyCaps  & 0x00FF ] = (PFN)QueryHardcopyCaps;
      ((PLDEVPARM2)p2)->ppfnDispatchTable[ 0x4040                 & 0x00FF ] = (PFN)DevicePalette;

      // 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 a pointer to element zero of the per-DC dispatch table, a PFN pointer.
    // Given job properties, it is possible to continue to hook the dispatch table
    // here.

    // inverse is DISABLE_PHYSICAL
    DBPRINTF (("FILL_PHYSICAL(2); p1=%x p2=%x\n", p1, p2 ));
    assert( p1 );
    assert( p2 );

    // allocate the DC heap
    hmcb = GplMemoryCreateInstance( LEN_DCHEAP, 0, 0, 0 );
    assert( hmcb );

    // from this heap, allocate a device drawing context structure (DDC)
    pddc = GplMemoryAlloc( hmcb, sizeof( DDC ));
    assert( pddc );

    // assign DDC signature to start of structure.
    pddc->ulSignature = DDC_SIGNATURE;

    // store base of this DC heap in the DDC
    pddc->hmcbHeap = hmcb;

    // p1 is a DEVOPENSTRUC
    pdevoSource = (PDEVOPENSTRUC)p1;
    // there is a DENPARAMS just after the DEVOPENSTRUC pointed to by pdevoSource
    pdenparams = (PDENPARAMS)(pdevoSource+1);

    // get DC type: OD_DIRECT, OD_MEMORY, OD_QUEUED etc
    pddc->ulType = pdenparams->ulType;

    // when type is OD_MEMORY, then there is a HDC supplied in the denparams
    assert( pdenparams->ulType == OD_MEMORY ? pdenparams->ulHDC : 0 == pdenparams->ulHDC );

    // check if we are opening a DC to be compatible with another of our DCs
    if( pdenparams->ulHDC ) {
      assert( OD_MEMORY == pdenparams->ulType );
      assert( pdevoSource->pszLogAddress ? 0 == *pdevoSource->pszLogAddress : TRUE );

      pddcCompatible = (PDDC)GetDriverInfo( pdenparams->ulHDC, 0, pdenparams->ulHDC );
      assert( (PDDC)GPI_ALTERROR != pddcCompatible );
      assert( pddcCompatible );

      // use the DEVOPENSTRUC in compatible DC as source DEVOPENSTRUC
      pdevoSource = &pddcCompatible->dop;
    }



    // copy strings and data from the source DEVOPENSTRUC
    if( pdevoSource->pszLogAddress  ) {
      strncpy( pddc->szLogAddress, pdevoSource->pszLogAddress, sizeof( pddc->szLogAddress )-1  );
      pddc->dop.pszLogAddress = pddc->szLogAddress;
    }

    if( pdevoSource->pszDriverName ) {
      strncpy( pddc->szDriverName, pdevoSource->pszDriverName, sizeof( pddc->szDriverName )-1  );
      pddc->dop.pszDriverName = pddc->szDriverName;
    }

    // driver data
    assert( pdevoSource->pdriv );
    if( ! pdevoSource->pdriv ) {
      SetErrorInfo( SEVERITY_ERROR, PMERR_INV_DRIVER_DATA );
      RAISEEXCEPTION( XCPT_USER );
    }

    // hunt for the right device given the device name
    pch = strrchr( pdevoSource->pdriv->szDeviceName, ',' );
    if( pch ) {
      *pch = 0;
    }
    pddc->pSupportedDevice  = PSDFromDeviceName(  pdevoSource->pdriv->szDeviceName );
    assert( pddc->pSupportedDevice  );
    if( ! pddc->pSupportedDevice  ) {
      SetErrorInfo( SEVERITY_ERROR, PMERR_INV_DRIVER_DATA );
      RAISEEXCEPTION( XCPT_USER );
    }


    // caller has supplied driver data; check known versions
    switch( pdevoSource->pdriv->lVersion ) {
    case DRIVERDATA_VERSION:
      // fine the way it is
      assert( pdevoSource->pdriv->cb >= sizeof( DRIVERDATA ));
      memcpy( &pddc->DriverData, pdevoSource->pdriv, sizeof( DRIVERDATA ));
      break;
    case 0:
      // setup with some defaults
      memcpy( &pddc->DriverData, pdevoSource->pdriv, pdevoSource->pdriv->cb );
      pddc->DriverData.cb = sizeof( pddc->DriverData );
      SetDriverDataDefaults( &pddc->DriverData, sizeof( pddc->DriverData ), pddc->pSupportedDevice->szDeviceName );
      break;
    default:
      // don't know about this version of drivdata
      SetErrorInfo( SEVERITY_ERROR, PMERR_INV_DRIVER_DATA );
      RAISEEXCEPTION( XCPT_USER );
      break;
    }

    pddc->dop.pdriv = (PDRIVDATA)&pddc->DriverData;



    // data type STD or RAW
    if( pdevoSource->pszDataType ) {
      strncpy( pddc->szDataType, pdevoSource->pszDataType, sizeof( pddc->szDataType ) -1 );
    } else {
      // most drivers default to standard
      assert( sizeof(  pddc->szDataType ) > strlen( "PM_Q_STD" ));
      strcpy( pddc->szDataType, "PM_Q_STD" );
    }
    pddc->dop.pszDataType = pddc->szDataType;

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

    if( pdevoSource->pszComment ) {
      strncpy( pddc->szComment, pdevoSource->pszComment, sizeof( pddc->szComment )- 1 );
      pddc->dop.pszComment = pddc->szComment;
    }

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

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

    // copy queue processor parameters
    if( pdevoSource->pszQueueProcParams ) {
      strncpy( pddc->szQueueProcParams, pdevoSource->pszQueueProcParams, sizeof( pddc->szQueueProcParams ) -1  );
    }
    pddc->dop.pszQueueProcParams = pddc->szQueueProcParams;
    // search for "DATATYPE=PM_Q_RAW" in the queue processor parameters
    // If present, then there's no need to journal or band because
    // the only thing in store for this DC will be buffer after buffer of DEVESC_RAWDATA.
    // Helps performance greatly because driver allocates less memory.
    if( strstr( pddc->dop.pszQueueProcParams,  "DATATYPE=PM_Q_RAW" )) {
      pddc->fRawDataOnly = TRUE;
    }


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

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



    // parse spooler parm for form name
    // if found, assign local variable pFormInfo to point to the form specified by name with FORM= syntax
    pddc->pFormInfo = NULL;
    pch = strstr( pddc->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
        pddc->pFormInfo = FormInfoFromFormName( pddc->pSupportedDevice, szWork );
      }
    }

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

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

    assert( pddc->pFormInfo );

    // Set capabilities for this printer device. See caps.c.
    // Caps are copied to the device surface struct when engine calls QueryDeviceSurface.
    SetCapabilities( pddc );

    // This sample differs from others about the return result of
    // FillPhysicalDeviceBlock. Documents say to return a pointer to a device
    // block. This sample returns pddc here and also from enable_dc.
    // GRE does not reference either one, but supplies it to the driver
    // when needed.
    ulrc = (ULONG)pddc;
    break;







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

    pddc = (PDDC) ((PDENPARAMS)p1)->ulStateInfo;
    assert( pddc );

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

    // sanity check: DC type can't be direct when data type is PM_Q_STD
    assert( !( pddc->ulType == OD_DIRECT  && pddc->ulDataType == PM_Q_STD ) );

    // Return the same PDDC as it was returned from FILL_PHYSICAL and supplied here.
    // On hooked GRE functions, this pointer will be supplied by GRE.
    // GRE calls it "pointer to DC instance data."
    ulrc = (ULONG) pddc;
    break;




  case QUERY_DEVICE_SURFACE:
    // p1 is devicesurface
    // p2 is pddc
    DBPRINTF (("QUERY_DEVICE_SURFACE(14); p1=%x p2=%x\n", p1, p2 ));
    if( p1 && p2 ) {
      // GRE calls here after EnableDC subfunction, and before CompleteOpenDC.

      pds  = (PDEVICESURFACE)p1;
      pddc = (PDDC)p2;

      // memory holding device surface data structure was allocated by GRE
      // store this pointer in my ddc
      pddc->pDeviceSurface = pds;

      // early version of GRE22 do not set length of struct.
      pds->ulLength = sizeof( DEVICESURFACE );

      // Flags for monochrome printing.
      // DEVICE_SPECIFIC: depends on origin of hardcopy device
      // use TOPBOTTOM when origin is upper left corner
      pds->ulDSFlgs = DS_TOPBOTTOM | DS_BYTEALIGN | DS_KEEP_EXTFORMAT | DS_MONO_INVERT;

      // DEVICE_SPECIFIC: copy device capabilities from pddc
      // to GRE's device surface structure. Driver capabilities
      // were set in the FILL_PHYSICAL case above.
      for( i=0; i < CAPS_MAX_CAPS; i++ ) {
        pds->DevCaps[ i ]  =  pddc->aDeviceCaps[ i ];
      }
      pds->ulCapsCnt = CAPS_MAX_CAPS;

      // These functions do little for printer drivers at this time.
      pds->pfnLockPhysDev   = (PFN)LockDevice;
      pds->pfnUnLockPhysDev = (PFN)LockDevice;

      // set dithering flags
      memset( &pds->DitherMatrix, 0, sizeof( pds->DitherMatrix ));
      pds->DitherMatrix.ulLength     = sizeof( pds->DitherMatrix );
      pds->DitherMatrix.fLog2PhysSup = GDM_MATRIX_DITHER;
      pds->DitherMatrix.fExt2IntSup  = GDM_ERRORDIF_DITHER;

      // Set device surface bitmap info.
      pds->SurfaceBmapInfo.ulLength          = sizeof( BMAPINFO );
      pds->SurfaceBmapInfo.ulType            = 0;
      pds->ulStyleRatio                      = 0x4040;
      pds->SurfaceBmapInfo.ulWidth           = pddc->aDeviceCaps[ CAPS_WIDTH ];
      pds->SurfaceBmapInfo.ulHeight          = pddc->aDeviceCaps[ CAPS_HEIGHT ];
      pds->SurfaceBmapInfo.ulBpp             = pddc->pSupportedDevice->cPhysicalBitsPerPel;

      // Defer allocation of pBits and ulBytesPerLine until DEVESCAPE_STARTDOC.
      pds->SurfaceBmapInfo.ulBytesPerLine    = 0;
      pds->SurfaceBmapInfo.pBits             = NULL;

      // needs work: set pds->abmapinfoDefPattern with some
      // bitmap patterns that work better for high-resolution printers.
      // The default bitmap patterns are suited for 90 DPI (displays)
      // but are too fine for 300 and 600 DPI laser printers.
      // See GplPatternCreate() in newer versions of GENPLIB.

      // success result is zero
      ulrc = 0;

    } else {
      // GRE calls here early in DC enablement with zero p1 and p2
      // to find if QueryDeviceSurface is supported.
      // Indicate to GRE that this subfunction is supported.
      ulrc = 0;
    }
    break;









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

    pddc  = (PDDC)p2;

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

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

    pddc->fComplete = TRUE;

    // success result
    ulrc = 0;
    break;








  case BEGIN_CLOSE_DC:
    // p1 is hdc; p2 is pddc
    // last chance at engine calls and to 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;

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

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

    pddc->fComplete = FALSE;

    // success result
    ulrc = 0;
    break;









  case DISABLE_DC:
    // p1 is pointer to device block
    // inverse of ENABLE_DC
    assert( p1 );
    DBPRINTF (("DISABLE_DC(6); pddc = %X\n", p1 ));
    // success
    ulrc = 0;
    break;







  case DISABLE_PHYSICAL:
    // p1 is pddc
    // inverse of FILL_PHYSICAL
    assert( p1 );
    DBPRINTF (("DISABLE_PHYSICAL(4); pddc=%x\n", p1 ));
    // cast a copy of pddc
    pddc = (PDDC) p1;
    rc = DosCloseMutexSem( pddc->hmtxDCSem );
    assert( rc == 0 );

    // Surface pBits should have been freed by now, or else we're leaking memory.
    assert( pddc->pDeviceSurface->SurfaceBmapInfo.pBits == NULL );

    // copy heap handle to a local variable
    hmcb = pddc->hmcbHeap;

    rc = GplMemoryFree( pddc );
    assert( 0 == rc );

    // free the DC heap
    rc = GplMemoryDeleteInstance( hmcb );
    assert( 0 == rc );
    // success
    ulrc = 0;
    break;





  case SAVE_DC:
    assert( p1 );
    DBPRINTF (("SAVE_DC(7); pddc=%x\n", p1  ));
    // success result
    ulrc = 0;
    break;



  case RESTORE_DC:
    assert( p1 );
    assert( p2 );
    DBPRINTF (("RESTORE_DC(8); pddc=%x  state number=%x\n", p1, p2 ));
    // success result
    ulrc = 0;
    break;



  case RESET_DC:
    assert( p1 );
    DBPRINTF (("RESET_DC(9); pddc=%x\n", p1 ));
    // success result
    ulrc = 0;
    break;

  default:
    // an unsupported subfunction
    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;
  }


  assert( pcNames );
  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;



  assert( pcDataTypes );
  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 ) {
      GplMemoryFree( pdi );
      pdi = NULL;
    }
    if( pdriverdata ) {
      GplMemoryFree( pdriverdata );
      pdriverdata = NULL;
    }
    if( pszAppname  ) {
      GplMemoryFree( 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;
  }



#ifdef IPMD
    _interrupt(3);
#endif



  // 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 = GplMemoryAlloc( globals.hmcbSharedHeap, 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 = GplMemoryAlloc( globals.hmcbSharedHeap, 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
    GplMemoryFree( pdi );
    pdi = NULL;

    break;





  case DPDM_QUERYJOBPROP:
    // wants job properties from INI; if none there, return some defaults
    pdriverdata = GplMemoryAlloc( globals.hmcbSharedHeap, 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
    GplMemoryFree( pdriverdata );
    pdriverdata = NULL;
    break;


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


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


  lrc = DEV_OK;

depart:
  UNREGISTERHANDLER( regrec );
  return lrc;
}



