/* Copyright (C) 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevpm.c,v 1.4.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/*
 * OS/2 Presentation manager driver
 *
 * By Russell Lang (based on gdevmswn.c and gdevwdib.c)
 *
 * If Ghostscript is a PM application, stdin/stdout are not
 * provided and so no text window is available.
 * If Ghostscript is a windowed text application, a message queue
 * can't be created so a PM window for graphics can't be created.
 * The solution used here is to have two programs - gsos2.exe is a 
 * text application and the outboard PM driver is gspmdrv.exe.
 * Another solution may be to make Ghostscript a PM application
 * and use VIO calls to provide a text window.
 *
 * If PM GSview starts Ghostscript, PM GSview displays the
 * bitmap instead of the PM driver (gspmdrv.exe).
 *
 * Since Ghostscript is not a PM application, this driver creates a
 * BMP bitmap in a named shared memory block and a second process
 * gspmdrv.exe reads this memory block and provides the PM window.  
 * Communication to gspmdrv.exe is via the shared memory block
 * and semaphores.
 */

#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_WINWINDOWMGR
#define INCL_DEV
#define INCL_GPIBITMAPS

#include <os2.h>
#include "string_.h"
#include <stdlib.h>
#include "gx.h"
#include "gserrors.h"
#include "gsexit.h"		/* for gs_exit_status */
#include "gxdevice.h"

#include "gp.h"
#include "gpcheck.h"
#include "gsparam.h"
#include "gdevpccm.h"

#include "gxdevmem.h"
#include "gdevpm.h"
#ifdef __DLL__
#include "gsdll.h"
#include "gsdllos2.h"
#endif

#define MIN_COMMIT 4096		/* memory is committed in these size chunks */
#define ID_NAME "GSPMDRV_%u_%u"

/* Initial values for width and height */
#define INITIAL_RESOLUTION 96
#define INITIAL_WIDTH (INITIAL_RESOLUTION * 85 / 10 + 1)
#define INITIAL_HEIGHT (INITIAL_RESOLUTION * 11 + 1)

/* A macro for casting the device argument */
#define pmdev ((gx_device_pm *)dev)


#define pm_gsview_sizeof 80
typedef struct gx_device_pm_s gx_device_pm;

#define gx_device_pm_common\
	int BitsPerPixel;\
	int UpdateInterval;\
	char GSVIEW[pm_gsview_sizeof];\
	BOOL dll;\
	int nColors;\
	BOOL updating;\
	HTIMER update_timer;\
	HEV sync_event;\
	HEV next_event;\
	HMTX bmp_mutex;\
	HQUEUE drv_queue;\
	HQUEUE term_queue;\
	ULONG session_id;\
	PID process_id;\
	PID gspid;\
	unsigned char *bitmap;\
	ULONG committed;\
	PBITMAPINFO2 bmi

/* The device descriptor */
struct gx_device_pm_s {
    gx_device_common;
    gx_device_pm_common;
    gx_device_memory mdev;
};

/* Device procedures */

/* See gxdevice.h for the definitions of the procedures. */
private dev_proc_open_device(pm_open);
private dev_proc_get_initial_matrix(pm_get_initial_matrix);
private dev_proc_sync_output(pm_sync_output);
private dev_proc_output_page(pm_output_page);
private dev_proc_close_device(pm_close);
private dev_proc_map_rgb_color(pm_map_rgb_color);
private dev_proc_map_color_rgb(pm_map_color_rgb);
private dev_proc_fill_rectangle(pm_fill_rectangle);
private dev_proc_copy_mono(pm_copy_mono);
private dev_proc_copy_color(pm_copy_color);
private dev_proc_get_bits(pm_get_bits);
private dev_proc_get_params(pm_get_params);
private dev_proc_put_params(pm_put_params);

private gx_device_procs pm_procs =
{
    pm_open,
    pm_get_initial_matrix,
    pm_sync_output,
    pm_output_page,
    pm_close,
    pm_map_rgb_color,
    pm_map_color_rgb,
    pm_fill_rectangle,
    NULL,			/* tile rectangle */
    pm_copy_mono,
    pm_copy_color,
    NULL,			/* draw line */
    pm_get_bits,
    pm_get_params,
    pm_put_params,
    NULL,			/* map_cmyk_color */
    gx_default_get_xfont_procs,
    NULL,			/* get_xfont_device */
    NULL,			/* map_rgb_alpha_color */
    gx_page_device_get_page_device
};

#ifdef __DLL__
gx_device_pm far_data gs_os2dll_device =
{
    std_device_std_body(gx_device_pm, &pm_procs, "os2dll",
			INITIAL_WIDTH, INITIAL_HEIGHT,
			INITIAL_RESOLUTION, INITIAL_RESOLUTION),
    {0},			/* std_procs */
    8,				/* BitsPerPixel */
    5000,			/* UpdateInterval */
    "\0",			/* GSVIEW */
    1				/* is DLL device */
};

#endif
gx_device_pm far_data gs_os2pm_device =
{
    std_device_std_body(gx_device_pm, &pm_procs, "os2pm",
			INITIAL_WIDTH, INITIAL_HEIGHT,
			INITIAL_RESOLUTION, INITIAL_RESOLUTION),
    {0},			/* std_procs */
    8,				/* BitsPerPixel */
    5000,			/* UpdateInterval */
    "\0",			/* GSVIEW */
    0				/* is not DLL device */
};

/* Compress a gx_color_value into an 8-bit PM color value, */
/* using only the high order 5 bits. */
#define pm_color_value(z)\
  ((((z) >> (gx_color_value_bits - 5)) << 3) +\
   ((z) >> (gx_color_value_bits - 3)))

/* prototypes for internal procedures */
private void pm_makepalette(gx_device_pm *);
private void pm_update(gx_device_pm *);
private uint pm_set_bits_per_pixel(gx_device_pm *, int);
private uint pm_palette_size(gx_device_pm *);
private int pm_alloc_bitmap(gx_device_pm *, gx_device *);
private int pm_run_gspmdrv(gx_device_pm *);
private void pm_write_bmp(gx_device_pm *);

/* Open the pm driver */
int
pm_open(gx_device * dev)
{
    int ccode;
    CHAR id[128];
    CHAR name[128];
    PTIB pptib;
    PPIB pppib;

    if (!pmdev->dll && (_osmode == DOS_MODE)) {
	eprintf("os2pm driver can't be used under DOS\n");
	return gs_error_limitcheck;
    }
    if (DosGetInfoBlocks(&pptib, &pppib)) {
	eprintf("\npm_open: Couldn't get pid\n");
	return gs_error_limitcheck;
    }
#ifdef __DLL__
    if (pppib->pib_ultype == 3)	/* if caller is PM app */
	pmdev->gspid = pppib->pib_ulpid;	/* use caller pid */
    else
#endif
	pmdev->gspid = pppib->pib_ulppid;	/* use parent (CMD.EXE) pid */
    sprintf(id, ID_NAME, pmdev->gspid, (ULONG) dev);

    /* Allocate, but don't commit, enough memory for the largest */
    /* possible bitmap (13Mbytes = A3 x 150dpi x 24bits) */
#ifdef __DLL__
    if (pmdev->dll) {
	/* We don't need to use shared memory for the DLL */
	if (DosAllocMem((PPVOID) & pmdev->bitmap,
			13 * 1024 * 1024, PAG_READ | PAG_WRITE)) {
	    eprintf("pm_open: failed allocating BMP memory\n");
	    return gs_error_limitcheck;
	}
    } else
#endif
    {
	/* Shared memory is common to all processes so we don't want to allocate too much */
	sprintf(name, SHARED_NAME, *pmdev->GSVIEW ? pmdev->GSVIEW : id);
	if (DosAllocSharedMem((PPVOID) & pmdev->bitmap, name,
			      13 * 1024 * 1024, PAG_READ | PAG_WRITE)) {
	    eprintf1("pm_open: failed allocating shared BMP memory %s\n", name);
	    return gs_error_limitcheck;
	}
    }

    /* commit one page so there is enough storage for a */
    /* bitmap header and palette */
    if (DosSetMem(pmdev->bitmap, MIN_COMMIT, PAG_COMMIT | PAG_DEFAULT)) {
	DosFreeMem(pmdev->bitmap);
	eprintf("pm_open: failed committing BMP memory\n");
	return gs_error_limitcheck;
    }
    pmdev->committed = MIN_COMMIT;

    if (pmdev->dll) {
	/* Create mutex - used for preventing another thread from accessing */
	/* bitmap while we are changing the bitmap size. Initially unowned. */
	sprintf(name, MUTEX_NAME, id);
	if (DosCreateMutexSem(name, &(pmdev->bmp_mutex), 0, FALSE)) {
	    DosFreeMem(pmdev->bitmap);
	    DosCloseEventSem(pmdev->sync_event);
	    DosCloseQueue(pmdev->drv_queue);
	    eprintf1("pm_open: failed to create mutex semaphore %s\n", name);
	    return gs_error_limitcheck;
	}
    } else {
	if (*pmdev->GSVIEW) {
	    APIRET rc;

	    /* GSview has already created the necessary objects */
	    /* so we use Open instead of Create */
	    rc = 0;
	    if (!rc) {
		sprintf(name, NEXT_NAME, pmdev->GSVIEW);
		rc = DosOpenEventSem(name, &pmdev->next_event);
	    }
	    if (!rc) {
		sprintf(name, MUTEX_NAME, pmdev->GSVIEW);
		rc = DosOpenMutexSem(name, &pmdev->bmp_mutex);
	    }
	    if (!rc) {
		PID owner_pid;

		sprintf(name, QUEUE_NAME, pmdev->GSVIEW);
		rc = DosOpenQueue(&owner_pid, &pmdev->drv_queue, name);
	    }
	    if (rc) {
		DosFreeMem(pmdev->bitmap);
		DosCloseEventSem(pmdev->next_event);
		eprintf2("pm_open: failed to open %s, rc = %u\n", name, rc);
		return gs_error_limitcheck;
	    }
	} else {		/* not GSVIEW */
	    /* Create update event semaphore */
	    sprintf(name, SYNC_NAME, id);
	    if (DosCreateEventSem(name, &(pmdev->sync_event), 0, FALSE)) {
		DosFreeMem(pmdev->bitmap);
		eprintf1("pm_open: failed to create event semaphore %s\n", name);
		return gs_error_limitcheck;
	    }
	    /* Create mutex - used for preventing gspmdrv from accessing */
	    /* bitmap while we are changing the bitmap size. Initially unowned. */
	    sprintf(name, MUTEX_NAME, id);
	    if (DosCreateMutexSem(name, &(pmdev->bmp_mutex), 0, FALSE)) {
		DosFreeMem(pmdev->bitmap);
		DosCloseEventSem(pmdev->sync_event);
		DosCloseQueue(pmdev->drv_queue);
		eprintf1("pm_open: failed to create mutex semaphore %s\n", name);
		return gs_error_limitcheck;
	    }
	}
    }

    if ((pm_set_bits_per_pixel(pmdev, pmdev->BitsPerPixel) < 0) ||
	(gdev_mem_device_for_bits(dev->color_info.depth) == 0)) {
	if (!pmdev->dll) {
	    if (*pmdev->GSVIEW) {
		DosCloseQueue(pmdev->drv_queue);
		DosCloseEventSem(pmdev->next_event);
	    } else
		DosCloseEventSem(pmdev->sync_event);
	}
	DosCloseMutexSem(pmdev->bmp_mutex);
	DosFreeMem(pmdev->bitmap);
	return gs_error_limitcheck;
    }
    /* initialise bitmap header */
    pmdev->bmi = (PBITMAPINFO2) pmdev->bitmap;
    pmdev->bmi->cbFix = 40;	/* OS/2 2.0 and Windows 3.0 compatible */
    pmdev->bmi->cx = dev->width;
    pmdev->bmi->cy = dev->height;
    pmdev->bmi->cPlanes = 1;
    pmdev->bmi->cBitCount = dev->color_info.depth;
    pmdev->bmi->ulCompression = BCA_UNCOMP;
    pmdev->bmi->cbImage = 0;
    pmdev->bmi->cxResolution = (ULONG) (dev->x_pixels_per_inch / 25.4 * 1000);
    pmdev->bmi->cyResolution = (ULONG) (dev->y_pixels_per_inch / 25.4 * 1000);
    if (pmdev->BitsPerPixel <= 8) {
	pmdev->bmi->cclrUsed = 1 << (pmdev->BitsPerPixel);
	pmdev->bmi->cclrImportant = pmdev->nColors;
    } else {
	pmdev->bmi->cclrUsed = 0;
	pmdev->bmi->cclrImportant = 0;
    }

    pm_makepalette(pmdev);

    /* commit pages */
    ccode = pm_alloc_bitmap((gx_device_pm *) dev, dev);
    if (ccode < 0) {
	if (!pmdev->dll) {
	    if (*pmdev->GSVIEW) {
		DosCloseQueue(pmdev->drv_queue);
		DosCloseEventSem(pmdev->next_event);
	    } else
		DosCloseEventSem(pmdev->sync_event);
	}
	DosCloseMutexSem(pmdev->bmp_mutex);
	DosFreeMem(pmdev->bitmap);
	return ccode;
    }
    if (*pmdev->GSVIEW)
	return 0;		/* GSview will handle displaying */

#ifdef __DLL__
    if (pmdev->dll && pgsdll_callback) {
	/* notify caller about new device */
	(*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)pmdev, 1);
	return 0;		/* caller will handle displaying */
    }
#endif

    ccode = pm_run_gspmdrv(pmdev);
    if (ccode < 0) {
	DosFreeMem(pmdev->bitmap);
	DosCloseEventSem(pmdev->sync_event);
	DosCloseMutexSem(pmdev->bmp_mutex);
    }
    return ccode;
}

/* Get the initial matrix.  BMPs, unlike most displays, */
/* put (0,0) in the lower left corner. */
private void
pm_get_initial_matrix(gx_device * dev, gs_matrix * pmat)
{
    pmat->xx = dev->x_pixels_per_inch / 72.0;
    pmat->xy = 0;
    pmat->yx = 0;
    pmat->yy = dev->y_pixels_per_inch / 72.0;
    pmat->tx = 0;
    pmat->ty = 0;
    if (*pmdev->GSVIEW)
	pm_update((gx_device_pm *) dev);	/* let GSVIEW know we are drawing */
}

/* Make the output appear on the screen. */
int
pm_sync_output(gx_device * dev)
{
#ifdef __DLL__
    if (pmdev->dll && pgsdll_callback) {
	(*pgsdll_callback) (GSDLL_SYNC, (unsigned char *)dev, 0);
	return 0;
    }
#endif

    /* tell gspmdrv or GSview process to update display */
    if (*pmdev->GSVIEW) {
	APIRET rc;

	rc = DosWriteQueue(pmdev->drv_queue, GS_SYNC, 0, NULL, 0);
	if (rc)
	    eprintf1("pm_sync_output: DosWriteQueue error %d\n", rc);
    } else {
	if (pmdev->updating)
	    DosStopTimer(pmdev->update_timer);
	DosPostEventSem(pmdev->sync_event);
    }
    pmdev->updating = FALSE;
    return (0);
}

/* Make the output appear on the screen  */
/* and bring image window to foreground. */
private int
pm_do_output_page(gx_device * dev, int copies, int flush)
{
    int code;
    APIRET rc;

#ifdef DEBUG
    pm_write_bmp(pmdev);
#endif
#ifdef __DLL__
    if (pmdev->dll && pgsdll_callback) {
	(*pgsdll_callback) (GSDLL_PAGE, (unsigned char *)dev, 0);
	return 0;
    }
#endif

    if (*pmdev->GSVIEW) {
	if (copies == -2) {
	    rc = DosWriteQueue(pmdev->drv_queue, GS_END, 0, NULL, 0);
	    if (rc)
		eprintf1("pm_output_page: DosWriteQueue error %d\n", rc);
	} else if (copies == -1) {
	    rc = DosWriteQueue(pmdev->drv_queue, GS_BEGIN, 0, NULL, 0);
	    if (rc)
		eprintf1("pm_output_page: DosWriteQueue error %d\n", rc);
	} else {
	    ULONG count;

	    pmdev->updating = FALSE;
	    /* signal GSview that another page is ready */
	    rc = DosWriteQueue(pmdev->drv_queue, GS_PAGE, 0, NULL, 0);
	    if (rc)
		eprintf1("pm_output_page: DosWriteQueue error %d\n", rc);
	    /* wait for GSview to signal we can move on to next page */
	    DosWaitEventSem(pmdev->next_event, SEM_INDEFINITE_WAIT);
	    DosResetEventSem(pmdev->next_event, &count);
	}
	code = 0;
    } else {
	code = pm_sync_output(dev);
	rc = DosSelectSession(pmdev->session_id);
	if (rc) {
	    DosSleep(2000);	/* give gspmdrv.exe a chance to run */
	    rc = DosSelectSession(pmdev->session_id);
	    if (rc == ERROR_SMG_NO_TARGET_WINDOW) {
		DosSleep(5000);	/* give gspmdrv.exe a chance to run */
		rc = DosSelectSession(pmdev->session_id);	/* try yet again */
	    }
	    if ((rc == ERROR_SMG_SESSION_NOT_FOUND) ||
		(rc == ERROR_SMG_INVALID_SESSION_ID)) {
		/* someone has killed the session */
		REQUESTDATA Request;
		ULONG DataLength;
		PVOID DataAddress;
		PULONG QueueEntry;
		BYTE ElemPriority;

		/* Close gspmdrv driver */
		DosStopSession(STOP_SESSION_SPECIFIED, pmdev->session_id);
		Request.pid = pmdev->gspid;
		Request.ulData = 0;
		/* wait for termination queue, queue is then closed by session manager */
		DosReadQueue(pmdev->term_queue, &Request, &DataLength,
		     &DataAddress, 0, DCWW_WAIT, &ElemPriority, (HEV) NULL);
		DosCloseQueue(pmdev->term_queue);
		pmdev->term_queue = (HQUEUE) 0;
		/* restart it */
		pm_run_gspmdrv(pmdev);
		DosSleep(2000);	/* give gspmdrv.exe a chance to run */
		rc = DosSelectSession(pmdev->session_id);
	    }
	    if (rc == ERROR_SMG_SESSION_NOT_FOREGRND)
		DosBeep(400, 50);
	    else if (rc)
		eprintf1("pm_output_page: Select Session error code %u\n", rc);
	}
    }
    return code;
}
int
pm_output_page(gx_device * dev, int copies, int flush)
{
    int code = pm_do_output_page(dev, copies, flush);

    if (code >= 0)
	code = gx_finish_output_page(dev, copies, flush);
    return code;
}

/* Close the pm driver */
int
pm_close(gx_device * dev)
{
    APIRET rc;

#ifdef __DLL__
    if (pmdev->dll) {
	DosRequestMutexSem(pmdev->bmp_mutex, 60000);
	if (pgsdll_callback)
	    (*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)dev, 0);
	DosReleaseMutexSem(pmdev->bmp_mutex);
    } else
#endif
    {
	if (*pmdev->GSVIEW) {
	    rc = DosWriteQueue(pmdev->drv_queue, GS_CLOSE, 0, NULL, 0);
	    if (rc)
		eprintf1("pm_close: DosWriteQueue error %d\n", rc);
	} else {
	    REQUESTDATA Request;
	    ULONG DataLength;
	    PVOID DataAddress;
	    PULONG QueueEntry;
	    BYTE ElemPriority;

	    /* Close gspmdrv driver */
	    DosStopSession(STOP_SESSION_SPECIFIED, pmdev->session_id);
	    Request.pid = pmdev->gspid;
	    Request.ulData = 0;
	    /* wait for termination queue, queue is then closed by session manager */
	    DosReadQueue(pmdev->term_queue, &Request, &DataLength,
		     &DataAddress, 0, DCWW_WAIT, &ElemPriority, (HEV) NULL);
	    /* queue needs to be closed by us */
	    DosCloseQueue(pmdev->term_queue);
	}
    }
    /* release memory */
    DosFreeMem(pmdev->bitmap);
    pmdev->bitmap = (unsigned char *)NULL;
    pmdev->committed = 0;

    if (!pmdev->dll) {
	/* close objects */
	if (*pmdev->GSVIEW) {
	    DosCloseQueue(pmdev->drv_queue);
	    DosCloseEventSem(pmdev->next_event);
	} else {
	    DosCloseEventSem(pmdev->sync_event);
	    /* stop update timer */
	    if (pmdev->updating)
		DosStopTimer(pmdev->update_timer);
	    pmdev->updating = FALSE;
	}
    }
    DosCloseMutexSem(pmdev->bmp_mutex);
    return (0);
}


/* Map a r-g-b color to the colors available under PM */
gx_color_index
pm_map_rgb_color(gx_device * dev, gx_color_value r, gx_color_value g,
		 gx_color_value b)
{
    switch (dev->color_info.depth) {
	case 24:
	    return ((b >> (gx_color_value_bits - 8)) << 16) +
		((g >> (gx_color_value_bits - 8)) << 8) +
		((r >> (gx_color_value_bits - 8)));
	case 8:{
		int i;
		RGB2 *prgb;
		byte cr, cg, cb;

		/* map colors to 0->255 in 32 steps */
		cr = pm_color_value(r);
		cg = pm_color_value(g);
		cb = pm_color_value(b);

		prgb = (RGB2 *) ((PBYTE) pmdev->bmi + pmdev->bmi->cbFix);
		/* search in palette */
		for (i = 0; i < pmdev->nColors; i++, prgb++) {
		    if (!((cr ^ prgb->bRed) & 0xf8) &&
			!((cg ^ prgb->bGreen) & 0xf8) &&
			!((cb ^ prgb->bBlue) & 0xf8)
			)
			return ((gx_color_index) i);	/* found it */
		}

		/* next try adding it to palette */
		if (i < 230) {	/* allow 26 for PM and other apps */
		    prgb->bRed = cr;
		    prgb->bGreen = cg;
		    prgb->bBlue = cb;
		    prgb->fcOptions = 0;
		    pmdev->nColors = i + 1;
		    pmdev->bmi->cclrImportant = pmdev->nColors;
		    if (*pmdev->GSVIEW) {
			APIRET rc;

			rc = DosWriteQueue(pmdev->drv_queue, GS_PALCHANGE, 0, NULL, 0);
			if (rc)
			    eprintf1("pm_sync_output: DosWriteQueue error %d\n", rc);
		    }
		    return ((gx_color_index) i);	/* return new palette index */
		}
		return (gx_no_color_index);	/* not found - dither instead */
	    }
	case 4:
	    if ((r == g) && (g == b) && (r >= gx_max_color_value / 3 * 2 - 1)
		&& (r < gx_max_color_value / 4 * 3))
		return ((gx_color_index) 8);	/* light gray */
	    return pc_4bit_map_rgb_color(dev, r, g, b);
    }
    return (gx_default_map_rgb_color(dev, r, g, b));
}

/* Map a color code to r-g-b. */
int
pm_map_color_rgb(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    gx_color_value one;

    switch (dev->color_info.depth) {
	case 24:
	    one = (gx_color_value) (gx_max_color_value / 255);
	    prgb[0] = ((color) & 255) * one;
	    prgb[1] = ((color >> 8) & 255) * one;
	    prgb[2] = ((color >> 16) & 255) * one;
	    break;
	case 8:
	    if (!dev->is_open)
		return -1;
	    {
		RGB2 *argb = (RGB2 *) ((PBYTE) pmdev->bmi + pmdev->bmi->cbFix);

		one = (gx_color_value) (gx_max_color_value / 255);
		prgb[0] = argb[(int)color].bRed * one;
		prgb[1] = argb[(int)color].bGreen * one;
		prgb[2] = argb[(int)color].bBlue * one;
	    }
	    break;
	case 4:
	    if (color == 8)	/* VGA light gray */
		prgb[0] = prgb[1] = prgb[2] = (gx_max_color_value / 4 * 3);
	    else
		pc_4bit_map_color_rgb(dev, color, prgb);
	    break;
	default:
	    prgb[0] = prgb[1] = prgb[2] =
		(int)color ? gx_max_color_value : 0;
    }
    return 0;
}

#define pmmdev ((gx_device *)&pmdev->mdev)
#define pmmproc(proc) (*dev_proc(&pmdev->mdev, proc))

/* Fill a rectangle. */
private int
pm_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
		  gx_color_index color)
{
    pmmproc(fill_rectangle) (pmmdev, x, y, w, h, color);
    pm_update((gx_device_pm *) dev);
    return 0;
}

/* Copy a monochrome bitmap.  The colors are given explicitly. */
/* Color = gx_no_color_index means transparent (no effect on the image). */
private int
pm_copy_mono(gx_device * dev,
	     const byte * base, int sourcex, int raster, gx_bitmap_id id,
	     int x, int y, int w, int h,
	     gx_color_index zero, gx_color_index one)
{
    pmmproc(copy_mono) (pmmdev, base, sourcex, raster, id,
			x, y, w, h, zero, one);
    pm_update((gx_device_pm *) dev);
    return 0;
}

/* Copy a color pixel map.  This is just like a bitmap, except that */
/* each pixel takes 8 or 4 bits instead of 1 when device driver has color. */
private int
pm_copy_color(gx_device * dev,
	      const byte * base, int sourcex, int raster, gx_bitmap_id id,
	      int x, int y, int w, int h)
{
    pmmproc(copy_color) (pmmdev, base, sourcex, raster, id,
			 x, y, w, h);
    pm_update((gx_device_pm *) dev);
    return 0;
}

int
pm_get_bits(gx_device * dev, int y, byte * str, byte ** actual_data)
{
    return pmmproc(get_bits) (pmmdev, y, str, actual_data);
}
/* Get PM parameters */
int
pm_get_params(gx_device * dev, gs_param_list * plist)
{
    int code = gx_default_get_params(dev, plist);
    gs_param_string gvs;

    gvs.data = pmdev->GSVIEW, gvs.size = strlen(gvs.data),
	gvs.persistent = false;
    code < 0 ||
	(code = param_write_int(plist, "UpdateInterval", &pmdev->UpdateInterval)) < 0 ||
	(code = param_write_string(plist, "GSVIEW", &gvs)) < 0;
    return code;
}

/* Put parameters. */

/* Set PM parameters -- size and resolution. */
/* We implement this ourselves so that we can do it without */
/* closing and opening the device. */
/* Also set BitsPerPixel and GSVIEW if device not open */
int
pm_put_params(gx_device * dev, gs_param_list * plist)
{
    int ecode = 0, code;
    bool reopen = false;
    bool is_open = dev->is_open;
    int width = dev->width;
    int height = dev->height;
    int old_bpp = dev->color_info.depth;
    int bpp = old_bpp;
    int uii = pmdev->UpdateInterval;
    gs_param_string gsvs;

    /* Handle extra parameters */
    switch (code = param_read_string(plist, "GSVIEW", &gsvs)) {
	case 0:
	    if (gsvs.size == strlen(pmdev->GSVIEW) &&
		!memcmp(pmdev->GSVIEW, gsvs.data, gsvs.size)
		) {
		gsvs.data = 0;
		break;
	    }
	    if (dev->is_open)
		ecode = gs_error_rangecheck;
	    else if (gsvs.size >= pm_gsview_sizeof)
		ecode = gs_error_limitcheck;
	    else
		break;
	    goto gsve;
	default:
	    ecode = code;
	  gsve:param_signal_error(plist, "GSVIEW", ecode);
	case 1:
	    gsvs.data = 0;
	    break;
    }

    switch (code = param_read_int(plist, "UpdateInterval", &uii)) {
	case 0:
	    if (uii < 0)
		ecode = gs_error_rangecheck;
	    else
		break;
	    goto uie;
	default:
	    ecode = code;
	  uie:param_signal_error(plist, "UpdateInterval", ecode);
	case 1:
	    break;
    }

    switch (code = param_read_int(plist, "BitsPerPixel", &bpp)) {
	case 0:
	    if (dev->is_open && bpp != old_bpp)
		ecode = gs_error_rangecheck;
	    else {
		code = pm_set_bits_per_pixel(pmdev, bpp);
		if (code < 0)
		    ecode = code;
		else
		    break;
	    }
	    goto bppe;
	default:
	    ecode = code;
	  bppe:param_signal_error(plist, "BitsPerPixel", ecode);
	case 1:
	    break;
    }

    if (ecode >= 0) {		/* Prevent gx_default_put_params from closing the device. */
	dev->is_open = false;
	ecode = gx_default_put_params(dev, plist);
	dev->is_open = is_open;
    }
    if (ecode < 0) {
	if (bpp != old_bpp)
	    pm_set_bits_per_pixel(pmdev, old_bpp);
	return ecode;
    }
    /* Hand off the change to the implementation. */
    /* obtain mutex - to prevent gspmdrv from using bitmap */
    /* while we change its size */
    if (DosRequestMutexSem(pmdev->bmp_mutex, 20000) == ERROR_TIMEOUT)
	eprintf("pm_put_params: mutex timeout\n");
    if (is_open && (old_bpp != bpp ||
		    dev->width != width || dev->height != height)
	) {
	int ccode;

	ccode = pm_alloc_bitmap(pmdev, dev);
	if (ccode < 0) {	/* Bad news!  Some of the other device parameters */
	    /* may have changed.  We don't handle this. */
	    /* This is ****** WRONG ******. */
	    dev->width = width;
	    dev->height = height;
	    pm_set_bits_per_pixel(pmdev, old_bpp);
	    pm_alloc_bitmap(pmdev, dev);
	    DosReleaseMutexSem(pmdev->bmp_mutex);
	    return ccode;
	}
	reopen = true;
    }
    pmdev->UpdateInterval = uii;
    if (gsvs.data != 0) {
	memcpy(pmdev->GSVIEW, gsvs.data, gsvs.size);
	pmdev->GSVIEW[gsvs.size] = 0;
    }
    if (dev->is_open && reopen) {
	/* need to update bitmap info header also */
	pmdev->bmi->cx = dev->width;
	pmdev->bmi->cy = dev->height;
	/* update bit count and palette */
	pmdev->bmi->cBitCount = dev->color_info.depth;
	pmdev->bmi->cclrUsed = 1 << (pmdev->BitsPerPixel);
	pmdev->bmi->cclrImportant = pmdev->nColors;
	pm_makepalette(pmdev);
	/* erase bitmap - before window gets redrawn */
	(*dev_proc(dev, fill_rectangle)) (dev,
					  0, 0, dev->width, dev->height,
				   pm_map_rgb_color(dev, gx_max_color_value,
				   gx_max_color_value, gx_max_color_value));
	/* cause scroll bars to be redrawn */
	/* need to signal gspmdrv that bitmap size has changed */
	/* or perhaps gspmdrv can check if the bitmap size has */
	/* before each use */

#ifdef __DLL__
	if (pmdev->dll && pgsdll_callback)
	    (*pgsdll_callback) (GSDLL_SIZE, (unsigned char *)dev,
		    (dev->width & 0xffff) + ((dev->height & 0xffff) << 16));
#endif
    }
    /* release bmp mutex */
    DosReleaseMutexSem(pmdev->bmp_mutex);
    return 0;
}

#ifdef __DLL__
/* ------ DLL routines ------ */
/* store at pbitmap the address of the bitmap */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
unsigned long GSDLLAPI
gsdll_get_bitmap(unsigned char *device, unsigned char **pbitmap)
{
    gx_device *dev = (gx_device *) device;

    *pbitmap = (unsigned char *)(pmdev->bmi);
    return 0;
}

/* Lock the device (so it's size cannot be changed) if flag = TRUE */
/* or unlock the device if flag = FALSE */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
int GSDLLAPI
gsdll_lock_device(unsigned char *device, int flag)
{
    gx_device *dev = (gx_device *) device;
    APIRET rc;

    if (flag)
	rc = DosRequestMutexSem(pmdev->bmp_mutex, 60000);
    else
	rc = DosReleaseMutexSem(pmdev->bmp_mutex);
    return rc;
}

#endif /* __DLL__ */

/* ------ Internal routines ------ */

#undef pmdev


/* start gspmdrv.exe */
private int
pm_run_gspmdrv(gx_device_pm * pmdev)
{
    int ccode;
    PCHAR pdrvname = "gspmdrv.exe";
    CHAR error_message[256];
    CHAR term_queue_name[128];
    CHAR id[128];
    CHAR arg[1024];
    STARTDATA sdata;
    APIRET rc;
    PTIB pptib;
    PPIB pppib;
    CHAR progname[256];
    PCHAR tail;

    sprintf(id, ID_NAME, pmdev->gspid, (ULONG) pmdev);

    /* Create termination queue - used to find out when gspmdrv terminates */
    sprintf(term_queue_name, "\\QUEUES\\TERMQ_%s", id);
    if (DosCreateQueue(&(pmdev->term_queue), QUE_FIFO, term_queue_name)) {
	eprintf("pm_run_gspmdrv: failed to create termination queue\n");
	return gs_error_limitcheck;
    }
    /* get full path to gsos2.exe and hence path to gspmdrv.exe */
    if ((rc = DosGetInfoBlocks(&pptib, &pppib)) != 0) {
	eprintf1("pm_run_gspmdrv: Couldn't get module handle, rc = %d\n", rc);
	return gs_error_limitcheck;
    }
    if ((rc = DosQueryModuleName(pppib->pib_hmte, sizeof(progname) - 1, progname)) != 0) {
	eprintf1("pm_run_gspmdrv: Couldn't get module name, rc = %d\n", rc);
	return gs_error_limitcheck;
    }
    if ((tail = strrchr(progname, '\\')) != (PCHAR) NULL) {
	tail++;
	*tail = '\0';
    } else
	tail = progname;
    strcat(progname, pdrvname);

    /* Open the PM driver session gspmdrv.exe */
    /* arguments are: */
    /*  (1) -d (display) option */
    /*  (2) id string */
    sprintf(arg, "-d %s", id);

    /* because gspmdrv.exe is a different EXE type to gs.exe, 
     * we must use start session not DosExecPgm() */
    sdata.Length = sizeof(sdata);
    sdata.Related = SSF_RELATED_CHILD;	/* to be a child  */
    sdata.FgBg = SSF_FGBG_BACK;	/* start in background */
    sdata.TraceOpt = 0;
    sdata.PgmTitle = "Ghostscript PM driver session";
    sdata.PgmName = progname;
    sdata.PgmInputs = arg;
    sdata.TermQ = term_queue_name;
    sdata.Environment = pppib->pib_pchenv;	/* use Parent's environment */
    sdata.InheritOpt = 0;	/* Can't inherit from parent because different sesison type */
    sdata.SessionType = SSF_TYPE_DEFAULT;	/* default is PM */
    sdata.IconFile = NULL;
    sdata.PgmHandle = 0;
    sdata.PgmControl = 0;
    sdata.InitXPos = 0;
    sdata.InitYPos = 0;
    sdata.InitXSize = 0;
    sdata.InitYSize = 0;
    sdata.ObjectBuffer = error_message;
    sdata.ObjectBuffLen = sizeof(error_message);

    rc = DosStartSession(&sdata, &pmdev->session_id, &pmdev->process_id);
    if (rc == ERROR_FILE_NOT_FOUND) {
	sdata.PgmName = pdrvname;
	rc = DosStartSession(&sdata, &pmdev->session_id, &pmdev->process_id);
    }
    if (rc) {
	eprintf2("pm_run_gspmdrv: failed to run %s, rc = %d\n", sdata.PgmName, rc);
	eprintf1("pm_run_gspmdrv: error_message: %s\n", error_message);
	return gs_error_limitcheck;
    }
    return 0;

}

/* Allocate the backing bitmap. */
private int
pm_alloc_bitmap(gx_device_pm * pmdev, gx_device * param_dev)
{
    gx_device_memory mdev;
    byte *base;
    ulong data_size;
    uint ptr_size;
    uint pal_size;
    uint raster;
    ULONG rc;
    ULONG needed;

    /* Finish initializing the bitmap. */

    gs_make_mem_device(&mdev, gdev_mem_device_for_bits(pmdev->color_info.depth), 0, 0, (gx_device *) pmdev);
    mdev.width = param_dev->width;
    mdev.height = param_dev->height;
    /* BMP files need width rounded up so that a scan line is */
    /* a multiple of 4 bytes. */
    /* This is currently done by gdev_mem_raster(). */
    /* It may be better to do it here explicitly in case */
    /* gdev_mem_raster changes. */
    raster = gdev_mem_raster(&mdev);
    data_size = (ulong) raster *mdev.height;

    ptr_size = sizeof(byte **) * mdev.height;
    pal_size = pm_palette_size(pmdev);
    needed = pmdev->bmi->cbFix + pal_size + data_size + ptr_size;
    /* round up to page boundary */
    needed = (needed + MIN_COMMIT - 1) & (~(MIN_COMMIT - 1));
    if (needed > pmdev->committed) {
	/* commit more memory */
	if (rc = DosSetMem(pmdev->bitmap + pmdev->committed,
			   needed - pmdev->committed,
			   PAG_COMMIT | PAG_DEFAULT)) {
	    eprintf1("No memory in pm_alloc_bitmap, rc = %d\n", rc);
	    return gs_error_limitcheck;
	}
	pmdev->committed = needed;
    }
    /* Shared memory can't be decommitted */
#ifdef __DLL__
    if (pmdev->dll && (needed < pmdev->committed)) {
	/* decommit memory */
	if (rc = DosSetMem(pmdev->bitmap + needed,
			   pmdev->committed - needed,
			   PAG_DECOMMIT)) {
	    eprintf1("Failed to decommit memory in pm_alloc_bitmap, rc = %d\n", rc);
	    return gs_error_limitcheck;
	}
	pmdev->committed = needed;
    }
#endif

    /* Nothing can go wrong now.... */
    base = pmdev->bitmap + pmdev->bmi->cbFix + pm_palette_size(pmdev);
    pmdev->mdev = mdev;
    pmdev->mdev.base = (byte *) base;
    pmmproc(open_device) ((gx_device *) & pmdev->mdev);
    pmdev->bmi->cbImage = data_size;
    return 0;
}

private void
pm_makepalette(gx_device_pm * pmdev)
{
    int i, val;
    RGB2 *argb = (RGB2 *) ((PBYTE) pmdev->bmi + pmdev->bmi->cbFix);

    if (pmdev->BitsPerPixel > 8)
	return;			/* these don't use a palette */

    for (i = 0; i < pmdev->nColors; i++) {
	switch (pmdev->nColors) {
	    case 64:
		/* colors are rrggbb */
		argb[i].bRed = ((i & 0x30) >> 4) * 85;
		argb[i].bGreen = ((i & 0xC) >> 2) * 85;
		argb[i].bBlue = (i & 3) * 85;
		argb[i].fcOptions = 0;
		/* zero unused entries */
		argb[i + 64].bRed = argb[i + 64].bGreen = argb[i + 64].bBlue = 0;
		argb[i + 64].fcOptions = 0;
		argb[i + 128].bRed = argb[i + 128].bGreen = argb[i + 128].bBlue = 0;
		argb[i + 128].fcOptions = 0;
		argb[i + 192].bRed = argb[i + 192].bGreen = argb[i + 192].bBlue = 0;
		argb[i + 192].fcOptions = 0;
		break;
	    case 16:
		/* colors are irgb */
		val = (i & 8 ? 255 : 128);
		argb[i].bRed = i & 4 ? val : 0;
		argb[i].bGreen = i & 2 ? val : 0;
		argb[i].bBlue = i & 1 ? val : 0;
		if (i == 8) {	/* light gray */
		    argb[i].bRed =
			argb[i].bGreen =
			argb[i].bBlue = 192;
		    argb[i].fcOptions = 0;
		}
		break;
	    case 2:
		argb[i].bRed =
		    argb[i].bGreen =
		    argb[i].bBlue = (i ? 255 : 0);
		argb[i].fcOptions = 0;
		break;
	}
    }
}


/* Cause display to be updated periodically */
private void
pm_update(gx_device_pm * pmdev)
{
    if (pmdev->updating)
	return;
    if (!pmdev->UpdateInterval)
	return;
    if (*pmdev->GSVIEW) {
	APIRET rc;

	rc = DosWriteQueue(pmdev->drv_queue, GS_UPDATING, 0, NULL, 0);
	if (rc)
	    eprintf1("pm_update: DosWriteQueue error %d\n", rc);
    } else {
	DosStartTimer(pmdev->UpdateInterval, (HSEM) pmdev->sync_event,
		      &pmdev->update_timer);
    }
    pmdev->updating = TRUE;
}

private uint
pm_set_bits_per_pixel(gx_device_pm * pmdev, int bpp)
{
    static const gx_device_color_info pm_24bit_color = dci_color(24, 255, 255);
    static const gx_device_color_info pm_8bit_color = dci_color(8, 31, 4);
    static const gx_device_color_info pm_4bit_color = dci_pc_4bit;
    static const gx_device_color_info pm_2color = dci_black_and_white;
    /* remember old anti_alias info */
    gx_device_anti_alias_info anti_alias = pmdev->color_info.anti_alias;

    switch (bpp) {
	case 24:
	    pmdev->color_info = pm_24bit_color;
	    pmdev->nColors = (1 << 24);
	    break;
	case 8:
	    /* use 64 static colors and 166 dynamic colors from 8 planes */
	    pmdev->color_info = pm_8bit_color;
	    pmdev->nColors = 64;
	    break;
	case 4:
	    pmdev->color_info = pm_4bit_color;
	    pmdev->nColors = 16;
	    break;
	case 1:
	    pmdev->color_info = pm_2color;
	    pmdev->nColors = 2;
	    break;
	default:
	    return (gs_error_rangecheck);
    }
    pmdev->BitsPerPixel = bpp;
    /* restore old anti_alias info */
    pmdev->color_info.anti_alias = anti_alias;
    return 0;
}

/* return length of BMP palette in bytes */
private uint
pm_palette_size(gx_device_pm * pmdev)
{
    switch (pmdev->color_info.depth) {
	case 24:
	    return 0;
	case 8:
	    return 256 * sizeof(RGB2);
	case 4:
	    return 16 * sizeof(RGB2);
    }
    /* must be two color */
    return 2 * sizeof(RGB2);
}

/* This is used for testing */
/* Write out a BMP file to "out.bmp" */
private void
pm_write_bmp(gx_device_pm * pmdev)
{
    BITMAPFILEHEADER2 bmfh;
    uint bmfh_length = sizeof(BITMAPFILEHEADER2) - sizeof(BITMAPINFOHEADER2);
    uint length;		/* bitmap length */
    ULONG fh;			/* file handle */
    ULONG action;
    ULONG count;

    bmfh.usType = 0x4d42;	/* "BM" */
    length = pmdev->bmi->cbFix + pm_palette_size(pmdev)
	+ ((gdev_mem_raster(&pmdev->mdev) * pmdev->mdev.height));
    bmfh.cbSize = bmfh_length + length;
    bmfh.xHotspot = bmfh.yHotspot = 0;
    bmfh.offBits = bmfh_length + pmdev->bmi->cbFix + pm_palette_size(pmdev);
    if (DosOpen("out.bmp",	/* filename */
		&fh,		/* pointer to handle */
		&action,	/* pointer to result */
		0,		/* initial length */
		FILE_NORMAL,	/* normal file */
		OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_REPLACE_IF_EXISTS,
		OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYREADWRITE,
		0)) {
	eprintf("error opening out.bmp\n");
	return;
    }
    if (DosWrite(fh, (PBYTE) & bmfh, bmfh_length, &count))
	eprintf("error writing header for out.bmp\n");
    if (DosWrite(fh, pmdev->bitmap, length, &count))
	eprintf("error writing out.bmp\n");
    if (DosClose(fh))
	eprintf("error closing out.bmp\n");
}
