//      PLplot X-windows device driver.
//
// Copyright (C) 2004  Maurice LeBrun
// Copyright (C) 2004  Joao Cardoso
// Copyright (C) 2004  Rafael Laboissiere
// Copyright (C) 2004  Andrew Ross
//
// This file is part of PLplot.
//
// PLplot is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PLplot 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 Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//
#include "plDevs.h"

#define DEBUG

#ifdef PLD_xwin
#define NEED_PLDEBUG
#include "plplotP.h"
#include "plxwd.h"
#include "drivers.h"
#include "plevent.h"

#ifdef PL_HAVE_PTHREAD
#include <pthread.h>
#include <signal.h>
int    pthread_mutexattr_settype( pthread_mutexattr_t *attr, int kind );
static void events_thread( void *pls );
static pthread_mutex_t events_mutex;
static int             already = 0;
#endif

// Device info
PLDLLIMPEXP_DRIVER const char* plD_DEVICE_INFO_xwin = "xwin:X-Window (Xlib):1:xwin:5:xw\n";

static int synchronize = 0;                   // change to 1 for X synchronized operation
                                              // Use "-drvopt sync" cmd line option to set.

static int nobuffered = 0;                    // make it a buffered device by default
                                              // use "-drvopt unbuffered" to make it unbuffered

static int noinitcolors = 0;                  // Call InitColors by default
                                              // use "-drvopt noinitcolors" to leave colors uninitialized

static int defaultvisual = 1;                 // use "-drvopt defvis=0" to not use the default visual

static int usepthreads = 1;                   // use "-drvopt usepth=0" to not use pthreads to redisplay

//
// When -drvopt defvis is defined, DefaultVisual() is used to get the visual.
// Otherwise, the visual is obtained using XGetVisualInfo() to make a match.
//

//#define HACK_STATICCOLOR

// Number of instructions to skip between querying the X server for events

#define MAX_INSTR    20

// The xwin driver uses the xscale and yscale values to convert from virtual
// to real pixels using the current size in pixels of the display window.
// We define PHYSICAL here so that PLplot core knows about this rescaling
// and mm values are converted to virtual pixels at a ratio consistent with
// a constant ratio of DPMM real pixels per mm.
#define PHYSICAL    1

// These need to be distinguished since the handling is slightly different.

#define LOCATE_INVOKED_VIA_API       1
#define LOCATE_INVOKED_VIA_DRIVER    2

// Set constants for dealing with colormap.  In brief:
//
//      --- custom colormaps ---
// ccmap		When set, turns on custom color map
// CCMAP_XWM_COLORS	Number of low "pixel" values to copy.
//
//      --- r/w colormaps --
// RWMAP_CMAP1_COLORS	Color map 1 entries.
// RWMAP_MAX_COLORS	Maximum colors in RW colormaps
//
//      --- r/o colormaps --
// ROMAP_CMAP1_COLORS	Color map 1 entries.
// TC_CMAP1_COLORS	TrueColor visual Color map 1 entries.
//
// See Init_CustomCmap() and  Init_DefaultCmap() for more info.
// Set ccmap at your own risk -- still under development.
//

// plplot_ccmap is statically defined in plxwd.h.  Note that
// plframe.c also includes that header and uses the variable.

#define CCMAP_XWM_COLORS      70

#define RWMAP_CMAP1_COLORS    50
#define RWMAP_MAX_COLORS      256

#define ROMAP_CMAP1_COLORS    50
#define TC_CMAP1_COLORS       200

// Variables to hold RGB components of given colormap.
// Used in an ugly hack to get past some X11R5 and TK limitations.

static int    sxwm_colors_set;
static XColor sxwm_colors[RWMAP_MAX_COLORS];

// Keep pointers to all the displays in use

static XwDisplay *xwDisplay[PLXDISPLAYS];

// Function prototypes

// Driver entry and dispatch setup

void plD_dispatch_init_xw( PLDispatchTable *pdt );

void plD_init_xw( PLStream * );
void plD_line_xw( PLStream *, short, short, short, short );
void plD_polyline_xw( PLStream *, short *, short *, PLINT );
void plD_eop_xw( PLStream * );
void plD_bop_xw( PLStream * );
void plD_tidy_xw( PLStream * );
void plD_wait_xw( PLStream * );
void plD_state_xw( PLStream *, PLINT );
void plD_esc_xw( PLStream *, PLINT, void * );

// Initialization

static void  OpenXwin( PLStream *pls );
static void  Init( PLStream *pls );
static void  InitMain( PLStream *pls );
static void  InitColors( PLStream *pls );
static void  AllocCustomMap( PLStream *pls );
static void  AllocCmap0( PLStream *pls );
static void  AllocCmap1( PLStream *pls );
static void  MapMain( PLStream *pls );
static void  CreatePixmap( PLStream *pls );
static void  GetVisual( PLStream *pls );
static void  AllocBGFG( PLStream *pls );
static int   AreWeGrayscale( Display *display );

// Routines to poll the X-server

static void  WaitForPage( PLStream *pls );
static void  CheckForEvents( PLStream *pls );
static void  HandleEvents( PLStream *pls );

// Event handlers

static void  MasterEH( PLStream *pls, XEvent *event );
static void  ClientEH( PLStream *pls, XEvent *event );
static void  ExposeEH( PLStream *pls, XEvent *event );
static void  ResizeEH( PLStream *pls, XEvent *event );
static void  MotionEH( PLStream *pls, XEvent *event );
static void  EnterEH( PLStream *pls, XEvent *event );
static void  LeaveEH( PLStream *pls, XEvent *event );
static void  KeyEH( PLStream *pls, XEvent *event );
static void  ButtonEH( PLStream *pls, XEvent *event );
static void  LookupXKeyEvent( PLStream *pls, XEvent *event );
static void  LookupXButtonEvent( PLStream *pls, XEvent *event );

static void  ProcessKey( PLStream *pls );
static void  LocateKey( PLStream *pls );
static void  ProcessButton( PLStream *pls );
static void  LocateButton( PLStream *pls );
static void  Locate( PLStream *pls );

// Routines for manipulating graphic crosshairs

static void  CreateXhairs( PLStream *pls );
static void  DestroyXhairs( PLStream *pls );
static void  DrawXhairs( PLStream *pls, int x0, int y0 );
static void  UpdateXhairs( PLStream *pls );

// Escape function commands

static void  ExposeCmd( PLStream *pls, PLDisplay *ptr );
static void  RedrawCmd( PLStream *pls );
static void  ResizeCmd( PLStream *pls, PLDisplay *ptr );
static void  ConfigBufferingCmd( PLStream *pls, PLBufferingCB *ptr );
static void  GetCursorCmd( PLStream *pls, PLGraphicsIn *ptr );
static void  FillPolygonCmd( PLStream *pls );
static void  XorMod( PLStream *pls, PLINT *mod );
static void  DrawImage( PLStream *pls );

// Miscellaneous

static void  StoreCmap0( PLStream *pls );
static void  StoreCmap1( PLStream *pls );
static void  imageops( PLStream *pls, PLINT *ptr );
static void  SetBGFG( PLStream *pls );
#ifdef DUMMY
static void  SaveColormap( Display *display, Colormap colormap );
#endif
static void  PLColor_to_XColor( PLColor *plcolor, XColor *xcolor );
static void  PLColor_from_XColor( PLColor *plcolor, XColor *xcolor );

static DrvOpt xwin_options[] = { { "sync",         DRV_INT, &synchronize,   "Synchronized X server operation (0|1)" },
                                 { "nobuffered",   DRV_INT, &nobuffered,    "Sets unbuffered operation (0|1)"       },
                                 { "noinitcolors", DRV_INT, &noinitcolors,  "Sets cmap0 allocation (0|1)"           },
                                 { "defvis",       DRV_INT, &defaultvisual, "Use the Default Visual (0|1)"          },
                                 { "usepth",       DRV_INT, &usepthreads,   "Use pthreads (0|1)"                    },
                                 { NULL,           DRV_INT, NULL,           NULL                                    } };

void plD_dispatch_init_xw( PLDispatchTable *pdt )
{
#ifndef ENABLE_DYNDRIVERS
    pdt->pl_MenuStr = "X-Window (Xlib)";
    pdt->pl_DevName = "xwin";
#endif
    pdt->pl_type     = plDevType_Interactive;
    pdt->pl_seq      = 5;
    pdt->pl_init     = (plD_init_fp) plD_init_xw;
    pdt->pl_line     = (plD_line_fp) plD_line_xw;
    pdt->pl_polyline = (plD_polyline_fp) plD_polyline_xw;
    pdt->pl_eop      = (plD_eop_fp) plD_eop_xw;
    pdt->pl_bop      = (plD_bop_fp) plD_bop_xw;
    pdt->pl_tidy     = (plD_tidy_fp) plD_tidy_xw;
    pdt->pl_state    = (plD_state_fp) plD_state_xw;
    pdt->pl_esc      = (plD_esc_fp) plD_esc_xw;
    pdt->pl_wait     = (plD_wait_fp) plD_wait_xw;
}

//--------------------------------------------------------------------------
// plD_init_xw()
//
// Initialize device.
// X-dependent stuff done in OpenXwin() and Init().
//--------------------------------------------------------------------------

void
plD_init_xw( PLStream *pls )
{
    XwDev *dev;
    PLFLT pxlx, pxly;
    int   xmin = 0;
    int   xmax = PIXELS_X - 1;
    int   ymin = 0;
    int   ymax = PIXELS_Y - 1;

    dbug_enter( "plD_init_xw" );

    pls->termin      = 1;       // Is an interactive terminal
    pls->dev_flush   = 1;       // Handle our own flushes
    pls->dev_fill0   = 1;       // Handle solid fills
    pls->plbuf_write = 1;       // Activate plot buffer
    pls->dev_fastimg = 1;       // is a fast image device
    pls->dev_xor     = 1;       // device support xor mode

#ifndef PL_HAVE_PTHREAD
    usepthreads = 0;
#endif

    plParseDrvOpts( xwin_options );

#ifndef PL_HAVE_PTHREAD
    if ( usepthreads )
        plwarn( "You said you want pthreads, but they are not available." );
#endif

    if ( nobuffered )
        pls->plbuf_write = 0;   // deactivate plot buffer

// The real meat of the initialization done here

    if ( pls->dev == NULL )
        OpenXwin( pls );

    dev = (XwDev *) pls->dev;

    Init( pls );

// Get ready for plotting

    dev->xlen = (short) ( xmax - xmin );
    dev->ylen = (short) ( ymax - ymin );

    dev->xscale_init = (double) dev->init_width / (double) dev->xlen;
    dev->yscale_init = (double) dev->init_height / (double) dev->ylen;

    dev->xscale = dev->xscale_init;
    dev->yscale = dev->yscale_init;

#if PHYSICAL
    pxlx = DPMM / dev->xscale;
    pxly = DPMM / dev->yscale;
#else
    pxlx = (double) PIXELS_X / LPAGE_X;
    pxly = (double) PIXELS_Y / LPAGE_Y;
#endif

    plP_setpxl( pxlx, pxly );
    plP_setphy( xmin, xmax, ymin, ymax );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
    {
        pthread_mutexattr_t mutexatt;
        pthread_attr_t      pthattr;

        if ( !already )
        {
            pthread_mutexattr_init( &mutexatt );
            if ( pthread_mutexattr_settype( &mutexatt, PLPLOT_MUTEX_RECURSIVE ) )
                plexit( "xwin: pthread_mutexattr_settype() failed!\n" );

            pthread_mutex_init( &events_mutex, &mutexatt );
            already = 1;
        }
        else
        {
            pthread_mutex_lock( &events_mutex );
            already++;
            pthread_mutex_unlock( &events_mutex );
        }

        pthread_attr_init( &pthattr );
        pthread_attr_setdetachstate( &pthattr, PTHREAD_CREATE_JOINABLE );

        if ( pthread_create( &( dev->updater ), &pthattr, ( void *( * )( void * ) ) & events_thread, (void *) pls ) )
        {
            pthread_mutex_lock( &events_mutex );
            already--;
            pthread_mutex_unlock( &events_mutex );

            if ( already == 0 )
            {
                pthread_mutex_destroy( &events_mutex );
                plexit( "xwin: pthread_create() failed!\n" );
            }
            else
                plwarn( "xwin: couldn't create thread for this plot window!\n" );
        }
    }
#endif
}

//--------------------------------------------------------------------------
// plD_line_xw()
//
// Draw a line in the current color from (x1,y1) to (x2,y2).
//--------------------------------------------------------------------------

void
plD_line_xw( PLStream *pls, short x1a, short y1a, short x2a, short y2a )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    int       x1 = x1a, y1 = y1a, x2 = x2a, y2 = y2a;

    dbug_enter( "plD_line_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    CheckForEvents( pls );

    y1 = dev->ylen - y1;
    y2 = dev->ylen - y2;

    x1 = (int) ( x1 * dev->xscale );
    x2 = (int) ( x2 * dev->xscale );
    y1 = (int) ( y1 * dev->yscale );
    y2 = (int) ( y2 * dev->yscale );

    if ( dev->write_to_window )
        XDrawLine( xwd->display, dev->window, dev->gc, x1, y1, x2, y2 );

    if ( dev->write_to_pixmap )
        XDrawLine( xwd->display, dev->pixmap, dev->gc, x1, y1, x2, y2 );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// XorMod()
//
// Enter xor mode ( mod != 0) or leave it ( mode = 0)
//--------------------------------------------------------------------------

static void
XorMod( PLStream *pls, PLINT *mod )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    if ( *mod == 0 )
        XSetFunction( xwd->display, dev->gc, GXcopy );
    else
        XSetFunction( xwd->display, dev->gc, GXxor );
}

//--------------------------------------------------------------------------
// plD_polyline_xw()
//
// Draw a polyline in the current color from (x1,y1) to (x2,y2).
//--------------------------------------------------------------------------

void
plD_polyline_xw( PLStream *pls, short *xa, short *ya, PLINT npts )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    PLINT     i;
    XPoint    _pts[PL_MAXPOLY];
    XPoint    *pts;

    if ( npts > PL_MAXPOLY )
    {
        pts = (XPoint *) malloc( sizeof ( XPoint ) * (size_t) npts );
    }
    else
    {
        pts = _pts;
    }

    dbug_enter( "plD_polyline_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    CheckForEvents( pls );

    for ( i = 0; i < npts; i++ )
    {
        pts[i].x = (short) ( dev->xscale * xa[i] );
        pts[i].y = (short) ( dev->yscale * ( dev->ylen - ya[i] ) );
    }

    if ( dev->write_to_window )
        XDrawLines( xwd->display, dev->window, dev->gc, pts, npts,
            CoordModeOrigin );

    if ( dev->write_to_pixmap )
        XDrawLines( xwd->display, dev->pixmap, dev->gc, pts, npts,
            CoordModeOrigin );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif

    if ( npts > PL_MAXPOLY )
    {
        free( pts );
    }
}

//--------------------------------------------------------------------------
// plD_eop_xw()
//
// End of page.  User must hit return (or third mouse button) to continue.
//--------------------------------------------------------------------------

void
plD_eop_xw( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    dbug_enter( "plD_eop_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    XFlush( xwd->display );
    if ( pls->db )
        ExposeCmd( pls, NULL );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// plD_bop_xw()
//
// Set up for the next page.
//--------------------------------------------------------------------------

void
plD_bop_xw( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    dbug_enter( "plD_bop_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    dev->bgcolor = xwd->cmap0[0];

    if ( dev->write_to_window )
    {
        XSetWindowBackground( xwd->display, dev->window, dev->bgcolor.pixel );
        XSetBackground( xwd->display, dev->gc, dev->bgcolor.pixel );
        XClearWindow( xwd->display, dev->window );
    }
    if ( dev->write_to_pixmap )
    {
        XSetForeground( xwd->display, dev->gc, dev->bgcolor.pixel );
        XFillRectangle( xwd->display, dev->pixmap, dev->gc, 0, 0,
            dev->width, dev->height );
        XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
    }
    XSync( xwd->display, 0 );
    pls->page++;

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// plD_tidy_xw()
//
// Close graphics file
//--------------------------------------------------------------------------

void
plD_tidy_xw( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    dbug_enter( "plD_tidy_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
    {
        pthread_mutex_lock( &events_mutex );
        if ( pthread_cancel( dev->updater ) == 0 )
            pthread_join( dev->updater, NULL );

        pthread_mutex_unlock( &events_mutex );
        if ( --already == 0 )
            pthread_mutex_destroy( &events_mutex );
    }
#endif

    if ( dev->is_main )
    {
        XDestroyWindow( xwd->display, dev->window );
        if ( dev->write_to_pixmap )
            XFreePixmap( xwd->display, dev->pixmap );
        XFlush( xwd->display );
    }

    xwd->nstreams--;
    if ( xwd->nstreams == 0 )
    {
        int ixwd = xwd->ixwd;
        XFreeGC( xwd->display, dev->gc );
        XFreeGC( xwd->display, xwd->gcXor );
        XCloseDisplay( xwd->display );
        free_mem( xwd->cmap0 );
        free_mem( xwd->cmap1 );
        free_mem( xwDisplay[ixwd] );
    }
    // ANR: if we set this here the tmp file will not be closed
    // See also comment in tkwin.c
    //pls->plbuf_write = 0;
}

//--------------------------------------------------------------------------
// plD_wait_xw()
//
// Wait for user input
//--------------------------------------------------------------------------

void
plD_wait_xw( PLStream *pls )
{
    XwDev *dev = (XwDev *) pls->dev;
    dbug_enter( "plD_eop_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    if ( dev->is_main )
        WaitForPage( pls );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// plD_state_xw()
//
// Handle change in PLStream state (color, pen width, fill attribute, etc).
//--------------------------------------------------------------------------

void
plD_state_xw( PLStream *pls, PLINT op )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    dbug_enter( "plD_state_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    CheckForEvents( pls );

    switch ( op )
    {
    case PLSTATE_WIDTH:
        XSetLineAttributes( xwd->display, dev->gc, (unsigned int) pls->width,
            LineSolid, CapRound, JoinMiter );
        break;

    case PLSTATE_COLOR0: {
        int icol0 = pls->icol0;
        if ( xwd->color )
        {
            if ( icol0 == PL_RGB_COLOR )
            {
                PLColor_to_XColor( &pls->curcolor, &dev->curcolor );
                if ( !XAllocColor( xwd->display, xwd->map, &dev->curcolor ) )
                {
                    fprintf( stderr, "Warning: could not allocate color\n" );
                    dev->curcolor.pixel = xwd->fgcolor.pixel;
                }
            }
            else
            {
                dev->curcolor = xwd->cmap0[icol0];
            }
            XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
        }
        else
        {
            dev->curcolor = xwd->fgcolor;
            XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
        }
        break;
    }

    case PLSTATE_COLOR1: {
        int icol1;

        if ( xwd->ncol1 == 0 )
            AllocCmap1( pls );

        if ( xwd->ncol1 < 2 )
            break;

        icol1 = ( pls->icol1 * ( xwd->ncol1 - 1 ) ) / ( pls->ncol1 - 1 );
        if ( xwd->color )
            dev->curcolor = xwd->cmap1[icol1];
        else
            dev->curcolor = xwd->fgcolor;

        XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
        break;
    }

    case PLSTATE_CMAP0:
        SetBGFG( pls );
        // If ncol0 has changed, need to reallocate
        if ( pls->ncol0 != xwd->ncol0 )
            AllocCmap0( pls );
        StoreCmap0( pls );
        break;

    case PLSTATE_CMAP1:
        StoreCmap1( pls );
        break;
    }

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// plD_esc_xw()
//
// Escape function.
//
// Functions:
//
//    PLESC_EH	Handle pending events
//    PLESC_EXPOSE	Force an expose
//    PLESC_FILL	Fill polygon
//    PLESC_FLUSH	Flush X event buffer
//    PLESC_GETC	Get coordinates upon mouse click
//    PLESC_REDRAW	Force a redraw
//    PLESC_RESIZE	Force a resize
//    PLESC_XORMOD      set/reset xor mode
//    PLESC_IMAGE     draw the image in a fast way
//    PLESC_IMAGEOPS: image related operations:
//         ZEROW2D      disable writing to display
//         ZEROW2B      disable writing to buffer
//         ONEW2D       enable  writing to display
//         ONEW2B       enable  writing to buffer
//    PLESC_PL2DEVCOL	convert PLColor to device color (XColor)
//    PLESC_DEV2PLCOL	convert device color (XColor) to PLColor
//    PLESC_SETBGFG	set BG, FG colors
//    PLESC_DEVINIT	alternate device initialization
//
// Note the [GET|SET]DEVCOL functions go through the intermediary stream
// variable tmpcolor to keep the syntax simple.
//--------------------------------------------------------------------------

void
plD_esc_xw( PLStream *pls, PLINT op, void *ptr )
{
    dbug_enter( "plD_esc_xw" );

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_lock( &events_mutex );
#endif

    switch ( op )
    {
    case PLESC_EH:
        HandleEvents( pls );
        break;

    case PLESC_EXPOSE:
        ExposeCmd( pls, (PLDisplay *) ptr );
        break;

    case PLESC_FILL:
        FillPolygonCmd( pls );
        break;

    case PLESC_FLUSH: {
        XwDev     *dev = (XwDev *) pls->dev;
        XwDisplay *xwd = (XwDisplay *) dev->xwd;
        HandleEvents( pls );
        XFlush( xwd->display );
        break;
    }
    case PLESC_GETC:
        GetCursorCmd( pls, (PLGraphicsIn *) ptr );
        break;

    case PLESC_REDRAW:
        RedrawCmd( pls );
        break;

    case PLESC_RESIZE:
        ResizeCmd( pls, (PLDisplay *) ptr );
        break;

    case PLESC_XORMOD:
        XorMod( pls, (PLINT *) ptr );
        break;

    case PLESC_DOUBLEBUFFERING:
        ConfigBufferingCmd( pls, (PLBufferingCB *) ptr );
        break;

    case PLESC_IMAGE:
        DrawImage( pls );
        break;

    case PLESC_IMAGEOPS:
        imageops( pls, (PLINT *) ptr );
        break;

    case PLESC_PL2DEVCOL:
        PLColor_to_XColor( &pls->tmpcolor, (XColor *) ptr );
        break;

    case PLESC_DEV2PLCOL:
        PLColor_from_XColor( &pls->tmpcolor, (XColor *) ptr );
        break;

    case PLESC_SETBGFG:
        SetBGFG( pls );
        break;

    case PLESC_DEVINIT:
        OpenXwin( pls );
        break;
    }

#ifdef PL_HAVE_PTHREAD
    if ( usepthreads )
        pthread_mutex_unlock( &events_mutex );
#endif
}

//--------------------------------------------------------------------------
// GetCursorCmd()
//
// Waits for a graphics input event and returns coordinates.
//--------------------------------------------------------------------------

static void
GetCursorCmd( PLStream *pls, PLGraphicsIn *ptr )
{
    XwDev        *dev = (XwDev *) pls->dev;
    XwDisplay    *xwd = (XwDisplay *) dev->xwd;
    XEvent       event;
    PLGraphicsIn *gin = &( dev->gin );

// Initialize

    plGinInit( gin );
    dev->locate_mode = LOCATE_INVOKED_VIA_API;
    CreateXhairs( pls );

// Run event loop until a point is selected

    while ( gin->pX < 0 && dev->locate_mode )
    {
        // XWindowEvent( xwd->display, dev->window, dev->event_mask, &event );
        XNextEvent( xwd->display, &event );
        MasterEH( pls, &event );
    }
    *ptr = *gin;
    if ( dev->locate_mode )
    {
        dev->locate_mode = 0;
        DestroyXhairs( pls );
    }
}

//--------------------------------------------------------------------------
// FillPolygonCmd()
//
// Fill polygon described in points pls->dev_x[] and pls->dev_y[].
// Only solid color fill supported.
//--------------------------------------------------------------------------

static void
FillPolygonCmd( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    XPoint    _pts[PL_MAXPOLY];
    XPoint    *pts;
    int       i;

    if ( pls->dev_npts > PL_MAXPOLY )
    {
        pts = (XPoint *) malloc( sizeof ( XPoint ) * (size_t) ( pls->dev_npts ) );
    }
    else
    {
        pts = _pts;
    }

    CheckForEvents( pls );

    for ( i = 0; i < pls->dev_npts; i++ )
    {
        pts[i].x = (short) ( dev->xscale * pls->dev_x[i] );
        pts[i].y = (short) ( dev->yscale * ( dev->ylen - pls->dev_y[i] ) );
    }

// Fill polygons

    if ( dev->write_to_window )
        XFillPolygon( xwd->display, dev->window, dev->gc,
            pts, pls->dev_npts, Complex, CoordModeOrigin );

    if ( dev->write_to_pixmap )
        XFillPolygon( xwd->display, dev->pixmap, dev->gc,
            pts, pls->dev_npts, Complex, CoordModeOrigin );

// If in debug mode, draw outline of boxes being filled

#ifdef DEBUG
    if ( pls->debug )
    {
        XSetForeground( xwd->display, dev->gc, xwd->fgcolor.pixel );
        if ( dev->write_to_window )
            XDrawLines( xwd->display, dev->window, dev->gc, pts, pls->dev_npts,
                CoordModeOrigin );

        if ( dev->write_to_pixmap )
            XDrawLines( xwd->display, dev->pixmap, dev->gc, pts, pls->dev_npts,
                CoordModeOrigin );

        XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
    }
#endif

    if ( pls->dev_npts > PL_MAXPOLY )
    {
        free( pts );
    }
}

//--------------------------------------------------------------------------
// OpenXwin()
//
// Performs basic driver initialization, without actually opening or
// modifying a window.  May be called by the outside world before plinit
// in case the caller needs early access to the driver internals (not
// very common -- currently only used by the plframe widget).
//--------------------------------------------------------------------------

static void
OpenXwin( PLStream *pls )
{
    XwDev     *dev;
    XwDisplay *xwd;
    int       i;

    dbug_enter( "OpenXwin" );

// Allocate and initialize device-specific data

    if ( pls->dev != NULL )
        plwarn( "OpenXwin: device pointer is already set" );

    pls->dev = calloc( 1, (size_t) sizeof ( XwDev ) );
    if ( pls->dev == NULL )
        plexit( "plD_init_xw: Out of memory." );

    dev = (XwDev *) pls->dev;

// Variables used in querying the X server for events

    dev->instr     = 0;
    dev->max_instr = MAX_INSTR;

// See if display matches any already in use, and if so use that

    dev->xwd = NULL;
    for ( i = 0; i < PLXDISPLAYS; i++ )
    {
        if ( xwDisplay[i] == NULL )
        {
            continue;
        }
        else if ( pls->FileName == NULL && xwDisplay[i]->displayName == NULL )
        {
            dev->xwd = xwDisplay[i];
            break;
        }
        else if ( pls->FileName == NULL || xwDisplay[i]->displayName == NULL )
        {
            continue;
        }
        else if ( strcmp( xwDisplay[i]->displayName, pls->FileName ) == 0 )
        {
            dev->xwd = xwDisplay[i];
            break;
        }
    }

// If no display matched, create a new one

    if ( dev->xwd == NULL )
    {
        dev->xwd = (XwDisplay *) calloc( 1, (size_t) sizeof ( XwDisplay ) );
        if ( dev->xwd == NULL )
            plexit( "Init: Out of memory." );

        for ( i = 0; i < PLXDISPLAYS; i++ )
        {
            if ( xwDisplay[i] == NULL )
                break;
        }
        if ( i == PLXDISPLAYS )
            plexit( "Init: Out of xwDisplay's." );

        xwDisplay[i]  = xwd = (XwDisplay *) dev->xwd;
        xwd->nstreams = 1;

// Open display
#ifdef PL_HAVE_PTHREAD
        if ( usepthreads )
            if ( !XInitThreads() )
                plexit( "xwin: XInitThreads() not successful." );
#endif
        xwd->display = XOpenDisplay( pls->FileName );
        if ( xwd->display == NULL )
        {
            plexit( "Can't open display" );
        }
        xwd->displayName = pls->FileName;
        xwd->screen      = DefaultScreen( xwd->display );
        if ( synchronize )
            XSynchronize( xwd->display, 1 );

        // Get colormap and visual
        xwd->map = DefaultColormap( xwd->display, xwd->screen );
        GetVisual( pls );

        //
        // Figure out if we have a color display or not.
        // Default is color IF the user hasn't specified and IF the output device
        // is not grayscale.
        //
        if ( pls->colorset )
            xwd->color = pls->color;
        else
        {
            pls->color = 1;
            xwd->color = !AreWeGrayscale( xwd->display );
        }

        // Allocate space for colors
        // Note cmap1 allocation is deferred
        xwd->ncol0_alloc = pls->ncol0;
        xwd->cmap0       = (XColor *) calloc( (size_t) ( pls->ncol0 ), sizeof ( XColor ) );
        if ( xwd->cmap0 == 0 )
            plexit( "couldn't allocate space for cmap0 colors" );

        // Allocate & set background and foreground colors
        AllocBGFG( pls );
        SetBGFG( pls );
    }

// Display matched, so use existing display data

    else
    {
        xwd = (XwDisplay *) dev->xwd;
        xwd->nstreams++;
    }
    xwd->ixwd = i;
}

//--------------------------------------------------------------------------
// Init()
//
// Xlib initialization routine.
//
// Controlling routine for X window creation and/or initialization.
// The user may customize the window in the following ways:
//
// display:	pls->OutFile (use plsfnam() or -display option)
// size:	pls->xlength, pls->ylength (use plspage() or -geo option)
// bg color:	pls->cmap0[0] (use plscolbg() or -bg option)
//--------------------------------------------------------------------------

static void
Init( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    Window    root;
    int       x, y;

    dbug_enter( "Init" );

// If not plotting into a child window, need to create main window now

    if ( pls->window_id == 0 )
    {
        dev->is_main = TRUE;
        InitMain( pls );
    }
    else
    {
        dev->is_main = FALSE;
        dev->window  = (Window) pls->window_id;
    }

// Initialize colors

    if ( noinitcolors == 0 )
        InitColors( pls );
    XSetWindowColormap( xwd->display, dev->window, xwd->map );

// Set up GC for ordinary draws

    if ( !dev->gc )
        dev->gc = XCreateGC( xwd->display, dev->window, 0, 0 );

// Set up GC for rubber-band draws

    if ( !xwd->gcXor )
    {
        XGCValues     gcValues;
        unsigned long mask;

        gcValues.background = xwd->cmap0[0].pixel;
        gcValues.foreground = 0xFF;
        gcValues.function   = GXxor;
        mask = GCForeground | GCBackground | GCFunction;

        xwd->gcXor = XCreateGC( xwd->display, dev->window, mask, &gcValues );
    }

// Get initial drawing area dimensions

    (void) XGetGeometry( xwd->display, dev->window, &root, &x, &y,
        &dev->width, &dev->height, &dev->border, &xwd->depth );

    dev->init_width  = (long) dev->width;
    dev->init_height = (long) dev->height;

// Set up flags that determine what we are writing to
// If nopixmap is set, ignore db

    if ( pls->nopixmap )
    {
        dev->write_to_pixmap = 0;
        pls->db = 0;
    }
    else
    {
        dev->write_to_pixmap = 1;
    }
    dev->write_to_window = !pls->db;

// Create pixmap for holding plot image (for expose events).

    if ( dev->write_to_pixmap )
        CreatePixmap( pls );

// Set drawing color

    plD_state_xw( pls, PLSTATE_COLOR0 );

    XSetWindowBackground( xwd->display, dev->window, xwd->cmap0[0].pixel );
    XSetBackground( xwd->display, dev->gc, xwd->cmap0[0].pixel );

// Set fill rule.
    if ( pls->dev_eofill )
        XSetFillRule( xwd->display, dev->gc, EvenOddRule );
    else
        XSetFillRule( xwd->display, dev->gc, WindingRule );

// If main window, need to map it and wait for exposure

    if ( dev->is_main )
        MapMain( pls );
}

//--------------------------------------------------------------------------
// InitMain()
//
// Create main window using standard Xlib calls.
//--------------------------------------------------------------------------

static void
InitMain( PLStream *pls )
{
    XwDev      *dev = (XwDev *) pls->dev;
    XwDisplay  *xwd = (XwDisplay *) dev->xwd;

    Window     root;
    XSizeHints hint;
    int        x, y;
    U_INT      width, height, border, depth;

    dbug_enter( "InitMain" );

// Get root window geometry

    (void) XGetGeometry( xwd->display, DefaultRootWindow( xwd->display ),
        &root, &x, &y, &width, &height, &border, &depth );

// Set window size
// Need to be careful to set XSizeHints flags correctly

    hint.flags = 0;
    if ( pls->xlength == 0 && pls->ylength == 0 )
        hint.flags |= PSize;
    else
        hint.flags |= USSize;

    if ( pls->xlength == 0 )
        pls->xlength = (PLINT) ( width * 0.75 );
    if ( pls->ylength == 0 )
        pls->ylength = (PLINT) ( height * 0.75 );

    if ( pls->xlength > (short) width )
        pls->xlength = (PLINT) ( width - dev->border * 2 );
    if ( pls->ylength > (short) height )
        pls->ylength = (PLINT) ( height - dev->border * 2 );

    hint.width  = (int) pls->xlength;
    hint.height = (int) pls->ylength;
    dev->border = 5;

// Set window position if specified by the user.
// Otherwise leave up to the window manager

    if ( pls->xoffset != 0 || pls->yoffset != 0 )
    {
        hint.flags |= USPosition;
        hint.x      = (int) pls->xoffset;
        hint.y      = (int) pls->yoffset;
    }
    else
    {
        hint.x = 0;
        hint.y = 0;
    }

// Window creation

    dev->window =
        XCreateWindow( xwd->display,
            DefaultRootWindow( xwd->display ),
            hint.x, hint.y, (unsigned int) hint.width, (unsigned int) hint.height,
            dev->border, (int) xwd->depth,
            InputOutput, xwd->visual,
            0, NULL );

    XSetStandardProperties( xwd->display, dev->window, pls->plwindow, pls->plwindow,
        None, 0, 0, &hint );
}

//--------------------------------------------------------------------------
// MapMain()
//
// Sets up event handlers, maps main window and waits for exposure.
//--------------------------------------------------------------------------

static void
MapMain( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    XEvent    event;

    dbug_enter( "MapMain" );

// Input event selection

    dev->event_mask =
        ButtonPressMask |
        KeyPressMask |
        ExposureMask |
        ButtonMotionMask |     // drag
        StructureNotifyMask;

    XSelectInput( xwd->display, dev->window, dev->event_mask );

// Window mapping

    XMapRaised( xwd->display, dev->window );

    Atom wmDelete = XInternAtom( xwd->display, "WM_DELETE_WINDOW", False );
    XSetWMProtocols( xwd->display, dev->window, &wmDelete, 1 );

// Wait for exposure
// Remove extraneous expose events from the event queue

    for (;; )
    {
        XWindowEvent( xwd->display, dev->window, dev->event_mask, &event );
        if ( event.type == Expose )
        {
            while ( XCheckWindowEvent( xwd->display, dev->window,
                        ExposureMask, &event ) )
                ;
            break;
        }
    }
}

//--------------------------------------------------------------------------
// WaitForPage()
//
// This routine waits for the user to advance the plot, while handling
// all other events.
//--------------------------------------------------------------------------

static void
WaitForPage( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    XEvent    event;

    dbug_enter( "WaitForPage" );

    while ( !dev->exit_eventloop )
    {
        // XWindowEvent( xwd->display, dev->window, dev->event_mask, &event );
        XNextEvent( xwd->display, &event );
        MasterEH( pls, &event );
    }
    dev->exit_eventloop = FALSE;
}

//--------------------------------------------------------------------------
// events_thread()
//
// This function is being running continuously by a thread and is
// responsible for dealing with expose and resize X events, in
// the case that the main program is too busy to honor them.
//
// Dealing with other X events is possible, but not desirable,
// e.g. treating the "Q" or right-mouse-button would terminate
// the thread (if this is desirable, the thread should kill the
// main program -- not thread aware -- and kill itself afterward).
//
// This works pretty well, but the main program *must* be linked
// with the pthread library, although not being thread aware.
// This happens automatically when linking against libplplot.so,
// but when building modules for extending some language such as
// Python or Octave, the language providing binary itself must be
// relinked with -lpthread.
//
//--------------------------------------------------------------------------

#ifdef PL_HAVE_PTHREAD
static void
events_thread( void *pls )
{
    if ( usepthreads )
    {
        PLStream        *lpls = (PLStream *) pls;
        XwDev           *dev  = (XwDev *) lpls->dev;
        XwDisplay       *xwd  = (XwDisplay *) dev->xwd;
        PLStream        *oplsc;
        struct timespec delay;
        XEvent          event;
        long            event_mask;
        sigset_t        set;

        //
        // only treats exposures and resizes, but remove usual events from queue,
        // as it can be disturbing to not have them acknowledged in real time,
        // because the program is busy, and suddenly all being acknowledged.
        // Also, the locator ("L" key) is sluggish if driven by the thread.
        //
        // But this approach is a problem when the thread removes events
        // from the queue while the program is responsible! The user hits 'L'
        // and nothing happens, as the thread removes it.
        //
        // Perhaps the "Q" key should have a different treatment, quiting the
        // program anyway?
        //
        // Changed: does not remove non treated events from the queue
        //

        event_mask = ExposureMask | StructureNotifyMask;

        // block all signal for this thread
        sigemptyset( &set );
        // sigfillset(&set);  can't be all signals, decide latter
        sigaddset( &set, SIGINT );

        sigprocmask( SIG_BLOCK, &set, NULL );

        pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, NULL );
        pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL );

        delay.tv_sec  = 0;
        delay.tv_nsec = 10000000; // this thread runs 10 times a second. (1/10 ms)

        while ( 1 )
        {
            pthread_mutex_lock( &events_mutex );

            if ( dev->is_main && !lpls->plbuf_read &&
                 ++dev->instr % dev->max_instr == 0 )
            {
                dev->instr = 0;
                while ( XCheckWindowEvent( xwd->display, dev->window, event_mask, &event ) )
                {
                    // As ResizeEH() ends up calling plRemakePlot(), that
                    // indirectly uses the current stream, one needs to
                    // temporarily set plplot current stream to this thread's
                    // stream

                    oplsc = plsc;
                    plsc  = lpls;
                    switch ( event.type )
                    {
                    case Expose:
                        ExposeEH( lpls, &event );
                        break;
                    case ConfigureNotify:
                        ResizeEH( lpls, &event );
                        break;
                    }
                    plsc = oplsc;
                }
            }

            pthread_mutex_unlock( &events_mutex );
            nanosleep( &delay, NULL ); // 10ms in linux
            /* pthread_yield(NULL); */ /* this puts too much load on the CPU */
        }
    }
}
#endif

//--------------------------------------------------------------------------
// CheckForEvents()
//
// A front-end to HandleEvents(), which is only called if certain conditions
// are satisfied:
//
// - must be the creator of the main window (i.e. PLplot is handling the
//   X event loop by polling).
// - must not be in the middle of a plot redisplay (else the order of event
//   handling can become circuitous).
// - only query X for events and process them every dev->max_instr times
//   this function is called (good for performance since querying X is a
//   nontrivial performance hit).
//--------------------------------------------------------------------------

static void
CheckForEvents( PLStream *pls )
{
    XwDev *dev = (XwDev *) pls->dev;

    if ( dev->is_main &&
         !pls->plbuf_read &&
         ++dev->instr % dev->max_instr == 0 )
    {
        dev->instr = 0;
        HandleEvents( pls );
    }
}

//--------------------------------------------------------------------------
// HandleEvents()
//
// Just a front-end to MasterEH(), for use when not actually waiting for an
// event but only checking the event queue.
//--------------------------------------------------------------------------

static void
HandleEvents( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    XEvent    event;

    while ( XCheckTypedWindowEvent( xwd->display, dev->window,
                ClientMessage, &event ) ||
            XCheckWindowEvent( xwd->display, dev->window,
                dev->event_mask, &event ) )
        MasterEH( pls, &event );
}

//--------------------------------------------------------------------------
// MasterEH()
//
// Master X event handler routine.
// Redirects control to routines to handle:
//    - keyboard events
//    - mouse events
//    - expose events
//    - resize events
//
// By supplying a master event handler, the user can take over all event
// processing.  If events other than those trapped by PLplot need handling,
// just call XSelectInput with the appropriate flags.  The default PLplot
// event handling can be modified arbitrarily by changing the event struct.
//--------------------------------------------------------------------------

static void
MasterEH( PLStream *pls, XEvent *event )
{
    XwDev *dev = (XwDev *) pls->dev;

    if ( dev->MasterEH != NULL )
        ( *dev->MasterEH )( pls, event );

    switch ( event->type )
    {
    case KeyPress:
        KeyEH( pls, event );
        break;

    case ButtonPress:
        ButtonEH( pls, event );
        break;

    case Expose:
        ExposeEH( pls, event );
        break;

    case ConfigureNotify:
        ResizeEH( pls, event );
        break;

    case MotionNotify:
        if ( event->xmotion.state )
            ButtonEH( pls, event ); // drag
        MotionEH( pls, event );
        break;

    case EnterNotify:
        EnterEH( pls, event );
        break;

    case LeaveNotify:
        LeaveEH( pls, event );
        break;

    case ClientMessage:
        ClientEH( pls, event );
        break;
    }
}

//--------------------------------------------------------------------------
// ClientEH()
//
// Event handler routine for client message events (WM_DELETE_WINDOW)
//--------------------------------------------------------------------------

static void
ClientEH( PLStream *pls, XEvent *event )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    if ( (Atom) event->xclient.data.l[0] == XInternAtom( xwd->display, "WM_DELETE_WINDOW", False ) )
    {
        pls->nopause        = TRUE;
        pls->stream_closed  = TRUE;
        dev->exit_eventloop = TRUE;
        // plexit( "" );
    }
}


//--------------------------------------------------------------------------
// KeyEH()
//
// Event handler routine for keyboard events.
//--------------------------------------------------------------------------

static void
KeyEH( PLStream *pls, XEvent *event )
{
    XwDev *dev = (XwDev *) pls->dev;

    dbug_enter( "KeyEH" );

    LookupXKeyEvent( pls, event );
    if ( dev->locate_mode )
        LocateKey( pls );
    else
        ProcessKey( pls );
}

//--------------------------------------------------------------------------
// ButtonEH()
//
// Event handler routine for ButtonPress events.
//--------------------------------------------------------------------------

static void
ButtonEH( PLStream *pls, XEvent *event )
{
    XwDev *dev = (XwDev *) pls->dev;

    dbug_enter( "ButtonEH" );

    LookupXButtonEvent( pls, event );
    if ( dev->locate_mode )
        LocateButton( pls );
    else
        ProcessButton( pls );
}

//--------------------------------------------------------------------------
// LookupXKeyEvent()
//
// Fills in the PLGraphicsIn from an XKeyEvent.  The PLGraphicsIn keysym is
// the same as the X keysym for all cases except for control keys that have
// ASCII equivalents, i.e.:
//
// Name		      X-keysym	ASCII	Ctrl-key
// ----		      --------	-----	--------
// XK_BackSpace        0xFF08   0x08      ^H
// XK_Tab              0xFF09   0x09      ^I
// XK_Linefeed         0xFF0A   0x0A      ^J
// XK_Return           0xFF0D   0x0D      ^M
// XK_Escape           0xFF1B   0x1B      ^[
// XK_Delete           0xFFFF   0xFF     (none)
//
// The ASCII representation of these characters is used for the PLGraphicsIn
// keysym to simplify code logic.  It turns out that the X keysyms are
// identical to the ASCII values with the upper 8 bits set.
//--------------------------------------------------------------------------

static void
LookupXKeyEvent( PLStream *pls, XEvent *event )
{
    XwDev          *dev      = (XwDev *) pls->dev;
    PLGraphicsIn   *gin      = &( dev->gin );
    XKeyEvent      *keyEvent = (XKeyEvent *) event;
    KeySym         keysym;
    int            nchars, ncmax = PL_MAXKEY - 1;
    XComposeStatus cs;

    gin->pX = keyEvent->x;
    gin->pY = keyEvent->y;
    gin->dX = (PLFLT) keyEvent->x / ( dev->width - 1 );
    gin->dY = 1.0 - (PLFLT) keyEvent->y / ( dev->height - 1 );

    gin->state = keyEvent->state;

    nchars = XLookupString( keyEvent, gin->string, ncmax, &keysym, &cs );
    gin->string[nchars] = '\0';

    pldebug( "LookupXKeyEvent",
        "Keysym %x, translation: %s\n", keysym, gin->string );

    switch ( keysym )
    {
    case XK_BackSpace:
    case XK_Tab:
    case XK_Linefeed:
    case XK_Return:
    case XK_Escape:
    case XK_Delete:
        gin->keysym = 0xFF & keysym;
        break;

    default:
        gin->keysym = (unsigned int) keysym;
    }
}

//--------------------------------------------------------------------------
// LookupXButtonEvent()
//
// Fills in the PLGraphicsIn from an XButtonEvent.
//--------------------------------------------------------------------------

static void
LookupXButtonEvent( PLStream *pls, XEvent *event )
{
    XwDev        *dev         = (XwDev *) pls->dev;
    PLGraphicsIn *gin         = &( dev->gin );
    XButtonEvent *buttonEvent = (XButtonEvent *) event;

    pldebug( "LookupXButtonEvent",
        "Button: %d, x: %d, y: %d\n",
        buttonEvent->button, buttonEvent->x, buttonEvent->y );

    gin->pX = buttonEvent->x;
    gin->pY = buttonEvent->y;
    gin->dX = (PLFLT) buttonEvent->x / ( dev->width - 1 );
    gin->dY = 1.0 - (PLFLT) buttonEvent->y / ( dev->height - 1 );

    gin->button = buttonEvent->button;
    gin->state  = buttonEvent->state;
    gin->keysym = 0x20;
}

//--------------------------------------------------------------------------
// ProcessKey()
//
// Process keyboard events other than locate input.
//--------------------------------------------------------------------------

static void
ProcessKey( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    PLGraphicsIn *gin = &( dev->gin );

    dbug_enter( "ProcessKey" );

// Call user keypress event handler.  Since this is called first, the user
// can disable all internal event handling by setting key.keysym to 0.
//
    if ( pls->KeyEH != NULL )
        ( *pls->KeyEH )( gin, pls->KeyEH_data, &dev->exit_eventloop );

// Handle internal events

    switch ( gin->keysym )
    {
    case PLK_Return:
    case PLK_Linefeed:
    case PLK_Next:
        // Advance to next page (i.e. terminate event loop) on a <eol>
        // Check for both <CR> and <LF> for portability, also a <Page Down>
        dev->exit_eventloop = TRUE;
        break;

    case 'Q':
        // Terminate on a 'Q' (not 'q', since it's too easy to hit by mistake)
        pls->nopause = TRUE;
        plexit( "" );
        break;

    case 'L':
        // Begin locate mode
        dev->locate_mode = LOCATE_INVOKED_VIA_DRIVER;
        CreateXhairs( pls );
        break;
    }
}

//--------------------------------------------------------------------------
// ProcessButton()
//
// Process ButtonPress events other than locate input.
// On:
//   Button1: nothing (except when in locate mode, see ButtonLocate)
//   Button2: nothing
//   Button3: set page advance flag
//--------------------------------------------------------------------------

static void
ProcessButton( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    PLGraphicsIn *gin = &( dev->gin );

    dbug_enter( "ProcessButton" );

// Call user event handler.  Since this is called first, the user can
// disable all PLplot internal event handling by setting gin->button to 0.
//
    if ( pls->ButtonEH != NULL )
        ( *pls->ButtonEH )( gin, pls->ButtonEH_data, &dev->exit_eventloop );

// Handle internal events

    switch ( gin->button )
    {
    case Button3:
        dev->exit_eventloop = TRUE;
        break;
    }
}

//--------------------------------------------------------------------------
// LocateKey()
//
// Front-end to locate handler for KeyPress events.
// Provides for a number of special effects:
//
//  <Escape>		Ends locate mode
//  <Cursor>		Moves cursor one pixel in the specified direction
//  <Mod-Cursor>	Accelerated cursor movement (5x for each modifier)
//--------------------------------------------------------------------------

static void
LocateKey( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    XwDisplay    *xwd = (XwDisplay *) dev->xwd;
    PLGraphicsIn *gin = &( dev->gin );

// End locate mode on <Escape>

    if ( gin->keysym == PLK_Escape )
    {
        dev->locate_mode = 0;
        DestroyXhairs( pls );
        plGinInit( gin );
    }

// Ignore modifier keys

    else if ( IsModifierKey( gin->keysym ) )
    {
        plGinInit( gin );
    }

// Now handle cursor keys

    else if ( IsCursorKey( gin->keysym ) )
    {
        int x1, y1, dx = 0, dy = 0;
        int xmin = 0, xmax = (int) dev->width - 1, ymin = 0, ymax = (int) dev->height - 1;

        switch ( gin->keysym )
        {
        case PLK_Left:
            dx = -1;
            break;
        case PLK_Right:
            dx = 1;
            break;
        case PLK_Up:
            dy = -1;
            break;
        case PLK_Down:
            dy = 1;
            break;
        }

        // Each modifier key added increases the multiplication factor by 5

        // Shift

        if ( gin->state & 0x01 )
        {
            dx *= 5;
            dy *= 5;
        }

        // Caps Lock

        if ( gin->state & 0x02 )
        {
            dx *= 5;
            dy *= 5;
        }

        // Control

        if ( gin->state & 0x04 )
        {
            dx *= 5;
            dy *= 5;
        }

        // Alt

        if ( gin->state & 0x08 )
        {
            dx *= 5;
            dy *= 5;
        }

        // Bounds checking so that we don't send cursor out of window

        x1 = gin->pX + dx;
        y1 = gin->pY + dy;

        if ( x1 < xmin )
            dx = xmin - gin->pX;
        if ( y1 < ymin )
            dy = ymin - gin->pY;
        if ( x1 > xmax )
            dx = xmax - gin->pX;
        if ( y1 > ymax )
            dy = ymax - gin->pY;

        // Engage...

        XWarpPointer( xwd->display, dev->window, None, 0, 0, 0, 0, dx, dy );
        plGinInit( gin );
    }

// Call ordinary locate handler

    else
    {
        Locate( pls );
    }
}

//--------------------------------------------------------------------------
// LocateButton()
//
// Front-end to locate handler for ButtonPress events.
// Only passes control to Locate() for Button1 presses.
//--------------------------------------------------------------------------

static void
LocateButton( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    PLGraphicsIn *gin = &( dev->gin );

    switch ( gin->button )
    {
    case Button1:
        Locate( pls );
        break;
    }
}

//--------------------------------------------------------------------------
// Locate()
//
// Handles locate mode events.
//
// In locate mode: move cursor to desired location and select by pressing a
// key or by clicking on the mouse (if available).  Typically the world
// coordinates of the selected point are reported.
//
// There are two ways to enter Locate mode -- via the API, or via a driver
// command.  The API entry point is the call plGetCursor(), which initiates
// locate mode and does not return until input has been obtained.  The
// driver entry point is by entering a 'L' while the driver is waiting for
// events.
//
// Locate mode input is reported in one of three ways:
// 1. Through a returned PLGraphicsIn structure, when user has specified a
//    locate handler via (*pls->LocateEH).
// 2. Through a returned PLGraphicsIn structure, when locate mode is invoked
//    by a plGetCursor() call.
// 3. Through writes to stdout, when locate mode is invoked by a driver
//    command and the user has not supplied a locate handler.
//
// Hitting <Escape> will at all times end locate mode.  Other keys will
// typically be interpreted as locator input.  Selecting a point out of
// bounds will end locate mode unless the user overrides with a supplied
// Locate handler.
//--------------------------------------------------------------------------

static void
Locate( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    PLGraphicsIn *gin = &( dev->gin );

// Call user locate mode handler if provided

    if ( pls->LocateEH != NULL )
        ( *pls->LocateEH )( gin, pls->LocateEH_data, &dev->locate_mode );

// Use default procedure

    else
    {
        // Try to locate cursor

        if ( plTranslateCursor( gin ) )
        {
            // If invoked by the API, we're done
            // Otherwise send report to stdout

            if ( dev->locate_mode == LOCATE_INVOKED_VIA_DRIVER )
            {
                pltext();
                if ( gin->keysym < 0xFF && isprint( gin->keysym ) )
                    printf( "%f %f %c\n", gin->wX, gin->wY, gin->keysym );
                else
                    printf( "%f %f 0x%02x\n", gin->wX, gin->wY, gin->keysym );

                plgra();
            }
        }
        else
        {
            // Selected point is out of bounds, so end locate mode

            dev->locate_mode = 0;
            DestroyXhairs( pls );
        }
    }
}

//--------------------------------------------------------------------------
// MotionEH()
//
// Event handler routine for MotionNotify events.
// If drawing crosshairs, the first and last draws must be done "by hand".
//--------------------------------------------------------------------------

static void
MotionEH( PLStream *pls, XEvent *event )
{
    XwDev        *dev         = (XwDev *) pls->dev;
    XMotionEvent *motionEvent = (XMotionEvent *) event;

    if ( dev->drawing_xhairs )
    {
        DrawXhairs( pls, motionEvent->x, motionEvent->y );
    }
}

//--------------------------------------------------------------------------
// EnterEH()
//
// Event handler routine for EnterNotify events.  Only called if drawing
// crosshairs -- a draw must be done here to start off the new set.
//--------------------------------------------------------------------------

static void
EnterEH( PLStream *pls, XEvent *event )
{
    XwDev          *dev           = (XwDev *) pls->dev;
    XCrossingEvent *crossingEvent = (XCrossingEvent *) event;

    DrawXhairs( pls, crossingEvent->x, crossingEvent->y );
    dev->drawing_xhairs = 1;
}

//--------------------------------------------------------------------------
// LeaveEH()
//
// Event handler routine for EnterNotify or LeaveNotify events.
// If drawing crosshairs, a draw must be done here to start off the new
// set or erase the last set.
//--------------------------------------------------------------------------

static void
LeaveEH( PLStream *pls, XEvent * PL_UNUSED( event ) )
{
    XwDev *dev = (XwDev *) pls->dev;

    UpdateXhairs( pls );
    dev->drawing_xhairs = 0;
}

//--------------------------------------------------------------------------
// CreateXhairs()
//
// Creates graphic crosshairs at current pointer location.
//--------------------------------------------------------------------------

static void
CreateXhairs( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    XwDisplay    *xwd = (XwDisplay *) dev->xwd;
    Window       root, child;
    int          root_x, root_y, win_x, win_y;
    unsigned int mask;
    XEvent       event;

// Get a crosshair cursor and switch to it.

    if ( !xwd->xhair_cursor )
        xwd->xhair_cursor = XCreateFontCursor( xwd->display, XC_crosshair );

    XDefineCursor( xwd->display, dev->window, xwd->xhair_cursor );

// Find current pointer location and draw graphic crosshairs if pointer is
// inside our window

    if ( XQueryPointer( xwd->display, dev->window, &root, &child,
             &root_x, &root_y, &win_x, &win_y, &mask ) )
    {
        if ( win_x >= 0 && win_x < (int) dev->width &&
             win_y >= 0 && win_y < (int) dev->height )
        {
            DrawXhairs( pls, win_x, win_y );
            dev->drawing_xhairs = 1;
        }
    }

// Sync the display and then throw away all pending motion events

    XSync( xwd->display, 0 );
    while ( XCheckWindowEvent( xwd->display, dev->window,
                PointerMotionMask, &event ) )
        ;

// Catch PointerMotion and crossing events so we can update them properly

    dev->event_mask |= PointerMotionMask | EnterWindowMask | LeaveWindowMask;
    XSelectInput( xwd->display, dev->window, dev->event_mask );
}

//--------------------------------------------------------------------------
// DestroyXhairs()
//
// Destroys graphic crosshairs.
//--------------------------------------------------------------------------

static void
DestroyXhairs( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

// Switch back to boring old pointer

    XUndefineCursor( xwd->display, dev->window );

// Don't catch PointerMotion or crossing events any more

    dev->event_mask &=
        ~PointerMotionMask & ~EnterWindowMask & ~LeaveWindowMask;
    XSelectInput( xwd->display, dev->window, dev->event_mask );

// This draw removes the last set of graphic crosshairs

    UpdateXhairs( pls );
    dev->drawing_xhairs = 0;
}

//--------------------------------------------------------------------------
// DrawXhairs()
//
// Draws graphic crosshairs at (x0, y0).  The first draw erases the old set.
//--------------------------------------------------------------------------

static void
DrawXhairs( PLStream *pls, int x0, int y0 )
{
    XwDev *dev = (XwDev *) pls->dev;

    int   xmin = 0, xmax = (int) dev->width - 1;
    int   ymin = 0, ymax = (int) dev->height - 1;

    if ( dev->drawing_xhairs )
        UpdateXhairs( pls );

    dev->xhair_x[0].x = (short) xmin; dev->xhair_x[0].y = (short) y0;
    dev->xhair_x[1].x = (short) xmax; dev->xhair_x[1].y = (short) y0;

    dev->xhair_y[0].x = (short) x0; dev->xhair_y[0].y = (short) ymin;
    dev->xhair_y[1].x = (short) x0; dev->xhair_y[1].y = (short) ymax;

    UpdateXhairs( pls );
}

//--------------------------------------------------------------------------
// UpdateXhairs()
//
// Updates graphic crosshairs.  If already there, they are erased.
//--------------------------------------------------------------------------

static void
UpdateXhairs( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    XDrawLines( xwd->display, dev->window, xwd->gcXor, dev->xhair_x, 2,
        CoordModeOrigin );

    XDrawLines( xwd->display, dev->window, xwd->gcXor, dev->xhair_y, 2,
        CoordModeOrigin );
}

//--------------------------------------------------------------------------
// ExposeEH()
//
// Event handler routine for expose events.
// Front end to ExposeCmd() to deal with wierdnesses of Xlib.
//--------------------------------------------------------------------------

static void
ExposeEH( PLStream *pls, XEvent *event )
{
    XwDev        *dev         = (XwDev *) pls->dev;
    XwDisplay    *xwd         = (XwDisplay *) dev->xwd;
    XExposeEvent *exposeEvent = (XExposeEvent *) event;
    PLDisplay    pldis;
    int          redrawn;

    dbug_enter( "ExposeEH" );

    pldebug( "ExposeEH",
        "x = %d, y = %d, width = %d, height = %d, count = %d, pending = %d\n",
        exposeEvent->x, exposeEvent->y,
        exposeEvent->width, exposeEvent->height,
        exposeEvent->count, XPending( xwd->display ) );

// Handle expose
// If we have anything overlaid (like crosshairs), we need to refresh the
// entire plot in order to have a predictable outcome.  In this case we
// need to first clear window.  Otherwise it's better to not clear it, for a
// smoother redraw (unobscured regions appear to stay the same).

    if ( dev->drawing_xhairs )
    {
        XClearWindow( xwd->display, dev->window );
        ExposeCmd( pls, NULL );
        UpdateXhairs( pls );
        redrawn = 1;
    }
    else
    {
        pldis.x      = (unsigned int) exposeEvent->x;
        pldis.y      = (unsigned int) exposeEvent->y;
        pldis.width  = (unsigned int) exposeEvent->width;
        pldis.height = (unsigned int) exposeEvent->height;

        ExposeCmd( pls, &pldis );
        redrawn = !dev->write_to_pixmap;
    }

// If entire plot was redrawn, remove extraneous events from the queue

    if ( redrawn )
        while ( XCheckWindowEvent( xwd->display, dev->window,
                    ExposureMask | StructureNotifyMask, event ) )
            ;
}

//--------------------------------------------------------------------------
// ResizeEH()
//
// Event handler routine for resize events.
// Front end to ResizeCmd() to deal with wierdnesses of Xlib.
//--------------------------------------------------------------------------

static void
ResizeEH( PLStream *pls, XEvent *event )
{
    XwDev           *dev         = (XwDev *) pls->dev;
    XwDisplay       *xwd         = (XwDisplay *) dev->xwd;
    XConfigureEvent *configEvent = (XConfigureEvent *) event;
    PLDisplay       pldis;

    dbug_enter( "ResizeEH" );

    pldis.width  = (unsigned int) configEvent->width;
    pldis.height = (unsigned int) configEvent->height;

// Only need to resize if size is actually changed

    if ( pldis.width == dev->width && pldis.height == dev->height )
        return;

    pldebug( "ResizeEH",
        "x = %d, y = %d, pending = %d\n",
        configEvent->width, configEvent->height, XPending( xwd->display ) );

// Handle resize

    ResizeCmd( pls, &pldis );
    if ( dev->drawing_xhairs )
    {
        UpdateXhairs( pls );
    }

// Remove extraneous Expose and ConfigureNotify events from the event queue
// Exposes do not need to be handled since we've redrawn the whole plot

    XFlush( xwd->display );
    while ( XCheckWindowEvent( xwd->display, dev->window,
                ExposureMask | StructureNotifyMask, event ) )
        ;
}

//--------------------------------------------------------------------------
// ExposeCmd()
//
// Event handler routine for expose events.
// These are "pure" exposures (no resize), so don't need to clear window.
//--------------------------------------------------------------------------

static void
ExposeCmd( PLStream *pls, PLDisplay *pldis )
{
    XwDev        *dev = (XwDev *) pls->dev;
    XwDisplay    *xwd = (XwDisplay *) dev->xwd;
    int          x, y;
    unsigned int width, height;

    dbug_enter( "ExposeCmd" );

// Return if plD_init_xw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "ExposeCmd: Illegal call -- driver uninitialized" );
        return;
    }

// Exposed area.  If unspecified, the entire window is used.

    if ( pldis == NULL )
    {
        x      = 0;
        y      = 0;
        width  = dev->width;
        height = dev->height;
    }
    else
    {
        x      = (int) pldis->x;
        y      = (int) pldis->y;
        width  = pldis->width;
        height = pldis->height;
    }

// Usual case: refresh window from pixmap
// DEBUG option: draws rectangle around refreshed region

    XSync( xwd->display, 0 );
    if ( dev->write_to_pixmap )
    {
        XCopyArea( xwd->display, dev->pixmap, dev->window, dev->gc,
            x, y, width, height, x, y );
        XSync( xwd->display, 0 );
#ifdef DEBUG
        if ( pls->debug )
        {
            XPoint pts[5];
            int    x0 = x, x1 = x + (int) width, y0 = y, y1 = y + (int) height;
            pts[0].x = (short) x0; pts[0].y = (short) y0;
            pts[1].x = (short) x1; pts[1].y = (short) y0;
            pts[2].x = (short) x1; pts[2].y = (short) y1;
            pts[3].x = (short) x0; pts[3].y = (short) y1;
            pts[4].x = (short) x0; pts[4].y = (short) y0;

            XDrawLines( xwd->display, dev->window, dev->gc, pts, 5,
                CoordModeOrigin );
        }
#endif
    }
    else
    {
        plRemakePlot( pls );
        XFlush( xwd->display );
    }
}

//--------------------------------------------------------------------------
// ResizeCmd()
//
// Event handler routine for resize events.
//--------------------------------------------------------------------------

static void
ResizeCmd( PLStream *pls, PLDisplay *pldis )
{
    XwDev     *dev            = (XwDev *) pls->dev;
    XwDisplay *xwd            = (XwDisplay *) dev->xwd;
    int       write_to_window = dev->write_to_window;

    dbug_enter( "ResizeCmd" );

// Return if plD_init_xw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "ResizeCmd: Illegal call -- driver uninitialized" );
        return;
    }

// Return if pointer to window not specified.

    if ( pldis == NULL )
    {
        plwarn( "ResizeCmd: Illegal call -- window pointer uninitialized" );
        return;
    }

// Reset current window bounds

    dev->width  = pldis->width;
    dev->height = pldis->height;

    dev->xscale = dev->width / (double) dev->init_width;
    dev->yscale = dev->height / (double) dev->init_height;

    dev->xscale = dev->xscale * dev->xscale_init;
    dev->yscale = dev->yscale * dev->yscale_init;

#if PHYSICAL
    {
        PLFLT pxlx = DPMM / dev->xscale;
        PLFLT pxly = DPMM / dev->yscale;
        plP_setpxl( pxlx, pxly );
    }
#endif

// Note: the following order MUST be obeyed -- if you instead redraw into
// the window and then copy it to the pixmap, off-screen parts of the window
// may contain garbage which is then transferred to the pixmap (and thus
// will not go away after an expose).
//

// Resize pixmap using new dimensions

    if ( dev->write_to_pixmap )
    {
        dev->write_to_window = 0;
        XFreePixmap( xwd->display, dev->pixmap );
        CreatePixmap( pls );
    }

// This allows an external agent to take over the redraw
    if ( pls->ext_resize_draw )
        return;

// Initialize & redraw (to pixmap, if available).

    if ( dev->write_to_pixmap )
    {
        XSetForeground( xwd->display, dev->gc, dev->bgcolor.pixel );
        XFillRectangle( xwd->display, dev->pixmap, dev->gc, 0, 0,
            dev->width, dev->height );
        XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
    }
    if ( dev->write_to_window )
    {
        XClearWindow( xwd->display, dev->window );
    }
    plRemakePlot( pls );
    XSync( xwd->display, 0 );

// If pixmap available, fake an expose

    if ( dev->write_to_pixmap )
    {
        dev->write_to_window = write_to_window;
        XCopyArea( xwd->display, dev->pixmap, dev->window, dev->gc, 0, 0,
            dev->width, dev->height, 0, 0 );
        XSync( xwd->display, 0 );
    }
}

//--------------------------------------------------------------------------
// ConfigBufferingCmd()
//
// Allows a widget to manipulate the double buffering support in the
// xwin dirver.
//--------------------------------------------------------------------------

static void ConfigBufferingCmd( PLStream *pls, PLBufferingCB *ptr )
{
    XwDev *dev = (XwDev *) pls->dev;

    switch ( ptr->cmd )
    {
    case PLESC_DOUBLEBUFFERING_ENABLE:
        dev->write_to_window = 0;
        pls->db = 1;
        break;

    case PLESC_DOUBLEBUFFERING_DISABLE:
        dev->write_to_window = 1;
        pls->db = 0;
        break;

    case PLESC_DOUBLEBUFFERING_QUERY:
        ptr->result = pls->db;
        break;

    default:
        printf( "Unrecognized buffering request ignored.\n" );
        break;
    }
}

//--------------------------------------------------------------------------
// RedrawCmd()
//
// Handles page redraw without resize (pixmap does not get reallocated).
// Calling this makes sure all necessary housekeeping gets done.
//--------------------------------------------------------------------------

static void
RedrawCmd( PLStream *pls )
{
    XwDev     *dev            = (XwDev *) pls->dev;
    XwDisplay *xwd            = (XwDisplay *) dev->xwd;
    int       write_to_window = dev->write_to_window;

    dbug_enter( "RedrawCmd" );

// Return if plD_init_xw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "RedrawCmd: Illegal call -- driver uninitialized" );
        return;
    }

// Initialize & redraw (to pixmap, if available).

    if ( dev->write_to_pixmap )
    {
        dev->write_to_window = 0;
        XSetForeground( xwd->display, dev->gc, dev->bgcolor.pixel );
        XFillRectangle( xwd->display, dev->pixmap, dev->gc, 0, 0,
            dev->width, dev->height );
        XSetForeground( xwd->display, dev->gc, dev->curcolor.pixel );
    }
    if ( dev->write_to_window )
    {
        XClearWindow( xwd->display, dev->window );
    }
    plRemakePlot( pls );
    XSync( xwd->display, 0 );

    dev->write_to_window = write_to_window;

// If pixmap available, fake an expose

    if ( dev->write_to_pixmap )
    {
        XCopyArea( xwd->display, dev->pixmap, dev->window, dev->gc, 0, 0,
            dev->width, dev->height, 0, 0 );
        XSync( xwd->display, 0 );
    }
}

//--------------------------------------------------------------------------
// CreatePixmapErrorHandler()
//
// Error handler used in CreatePixmap() to catch errors in allocating
// storage for pixmap.  This way we can nicely substitute redraws for
// pixmap copies if the server has insufficient memory.
//--------------------------------------------------------------------------

static unsigned char CreatePixmapStatus;

static int
CreatePixmapErrorHandler( Display *display, XErrorEvent *error )
{
    CreatePixmapStatus = error->error_code;
    if ( error->error_code != BadAlloc )
    {
        char buffer[256];
        XGetErrorText( display, error->error_code, buffer, 256 );
        fprintf( stderr, "Error in XCreatePixmap: %s.\n", buffer );
    }
    return 1;
}

//--------------------------------------------------------------------------
// CreatePixmap()
//
// This routine creates a pixmap, doing error trapping in case there
// isn't enough memory on the server.
//--------------------------------------------------------------------------

static void
CreatePixmap( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    int       ( *oldErrorHandler )( Display *, XErrorEvent * );

    oldErrorHandler = XSetErrorHandler( CreatePixmapErrorHandler );

    CreatePixmapStatus = Success;
    pldebug( "CreatePixmap",
        "creating pixmap: width = %d, height = %d, depth = %d\n",
        dev->width, dev->height, xwd->depth );

    dev->pixmap = XCreatePixmap( xwd->display, dev->window,
        dev->width, dev->height, xwd->depth );
    XSync( xwd->display, 0 );
    if ( CreatePixmapStatus != Success )
    {
        dev->write_to_pixmap = 0;
        dev->write_to_window = 1;
        pls->db = 0;
        fprintf( stderr, "\n\
Warning: pixmap could not be allocated (insufficient memory on server).\n\
Driver will redraw the entire plot to handle expose events.\n" );
    }

    XSetErrorHandler( oldErrorHandler );
}

//--------------------------------------------------------------------------
// GetVisual()
//
// Get visual info.  In order to safely use a visual other than that of
// the parent (which hopefully is that returned by DefaultVisual), you
// must first find (using XGetRGBColormaps) or create a colormap matching
// this visual and then set the colormap window attribute in the
// XCreateWindow attributes and valuemask arguments.  I don't do this
// right now, so this is turned off by default.
//--------------------------------------------------------------------------

static void
GetVisual( PLStream *pls )
{
    XwDev     *dev            = (XwDev *) pls->dev;
    XwDisplay *xwd            = (XwDisplay *) dev->xwd;
    int       visuals_matched = 0;

    dbug_enter( "GetVisual" );

    if ( !defaultvisual )
    {
        XVisualInfo vTemplate, *visualList;

// Try for an 8 plane display, if unavailable go for the default

        vTemplate.screen = xwd->screen;
        vTemplate.depth  = 8;

        visualList = XGetVisualInfo( xwd->display,
            VisualScreenMask | VisualDepthMask,
            &vTemplate, &visuals_matched );

#ifdef HACK_STATICCOLOR
        if ( visuals_matched )
        {
            int i, found = 0;
            printf( "visuals_matched = %d\n", visuals_matched );
            for ( i = 0; i < visuals_matched && !found; i++ )
            {
                Visual *v = visualList[i].visual;
                printf( "Checking visual %d: ", i );
                switch ( v->class )
                {
                case PseudoColor:
                    printf( "PseudoColor\n" );
                    break;
                case GrayScale:
                    printf( "GrayScale\n" );
                    break;
                case DirectColor:
                    printf( "DirectColor\n" );
                    break;
                case TrueColor:
                    printf( "TrueColor\n" );
                    break;
                case StaticColor:
                    printf( "StaticColor\n" );
                    break;
                case StaticGray:
                    printf( "StaticGray\n" );
                    break;
                default:
                    printf( "Unknown.\n" );
                    break;
                }
                if ( v->class == StaticColor )
                {
                    xwd->visual = v;
                    xwd->depth  = visualList[i].depth;
                    found       = 1;
                }
            }
            if ( !found )
            {
                plexit( "Unable to get a StaticColor visual." );
            }
            printf( "Found StaticColor visual, depth=%d\n", xwd->depth );
        }
#else
        if ( visuals_matched )
        {
            xwd->visual = visualList->visual;   // Choose first match.
            xwd->depth  = (unsigned int) vTemplate.depth;
        }
#endif  // HACK_STATICCOLOR
    }

    if ( !visuals_matched )
    {
        xwd->visual = DefaultVisual( xwd->display, xwd->screen );
        xwd->depth  = (unsigned int) DefaultDepth( xwd->display, xwd->screen );
    }

// Check to see if we expect to be able to allocate r/w color cells.

    switch ( xwd->visual->class )
    {
    case TrueColor:
    case StaticColor:
    case StaticGray:
        xwd->rw_cmap = 0;
        break;
    default:
        xwd->rw_cmap = 1;
    }

/*xwd->rw_cmap = 0;*/ /* debugging hack. */

// Just for kicks, see what kind of visual we got.

    if ( pls->verbose )
    {
        fprintf( stderr, "XVisual class == " );
        switch ( xwd->visual->class )
        {
        case PseudoColor:
            fprintf( stderr, "PseudoColor\n" );
            break;
        case GrayScale:
            fprintf( stderr, "GrayScale\n" );
            break;
        case DirectColor:
            fprintf( stderr, "DirectColor\n" );
            break;
        case TrueColor:
            fprintf( stderr, "TrueColor\n" );
            break;
        case StaticColor:
            fprintf( stderr, "StaticColor\n" );
            break;
        case StaticGray:
            fprintf( stderr, "StaticGray\n" );
            break;
        default:
            fprintf( stderr, "Unknown.\n" );
            break;
        }
        fprintf( stderr, "xwd->rw_cmap = %d\n", xwd->rw_cmap );
    }
}

//--------------------------------------------------------------------------
// AllocBGFG()
//
// Allocate background & foreground colors.  If possible, I choose pixel
// values such that the fg pixel is the xor of the bg pixel, to make
// rubber-banding easy to see.
//--------------------------------------------------------------------------

static void
AllocBGFG( PLStream *pls )
{
    XwDev         *dev = (XwDev *) pls->dev;
    XwDisplay     *xwd = (XwDisplay *) dev->xwd;

    int           i, j, npixels;
    unsigned long plane_masks[1], pixels[RWMAP_MAX_COLORS];

    dbug_enter( "AllocBGFG" );

// If not on a color system, just return
    if ( !xwd->color )
        return;

    if ( xwd->rw_cmap &&
         // r/w color maps
         XAllocColorCells( xwd->display, xwd->map, False,
             plane_masks, 0, pixels, 1 ) )
    {
        // background
        xwd->cmap0[0].pixel = pixels[0];
    }
    else
    {
        // r/o color maps
        xwd->cmap0[0].pixel = BlackPixel( xwd->display, xwd->screen );
        xwd->fgcolor.pixel  = WhitePixel( xwd->display, xwd->screen );
        if ( xwd->rw_cmap && pls->verbose )
            fprintf( stderr, "Downgrading to r/o cmap.\n" );
        xwd->rw_cmap = 0;
        return;
    }

// Allocate as many colors as we can

    npixels = RWMAP_MAX_COLORS;
    for (;; )
    {
        if ( XAllocColorCells( xwd->display, xwd->map, False,
                 plane_masks, 0, pixels, (unsigned int) npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            break;
    }

// Find the color with pixel = xor of the bg color pixel.
// If a match isn't found, the last pixel allocated is used.

    for ( i = 0; i < npixels - 1; i++ )
    {
        if ( pixels[i] == ( ~xwd->cmap0[0].pixel & 0xFF ) )
            break;
    }

// Use this color cell for our foreground color.  Then free the rest.

    xwd->fgcolor.pixel = pixels[i];
    for ( j = 0; j < npixels; j++ )
    {
        if ( j != i )
            XFreeColors( xwd->display, xwd->map, &pixels[j], 1, 0 );
    }
}

//--------------------------------------------------------------------------
// SetBGFG()
//
// Set background & foreground colors.  Foreground over background should
// have high contrast.
//--------------------------------------------------------------------------

static void
SetBGFG( PLStream *pls )
{
    XwDev        *dev = (XwDev *) pls->dev;
    XwDisplay    *xwd = (XwDisplay *) dev->xwd;

    PLColor      fgcolor;
    unsigned int gslevbg, gslevfg;

    dbug_enter( "SetBGFG" );

//
// Set background color.
//
// Background defaults to black on color screens, white on grayscale (many
// grayscale monitors have poor contrast, and black-on-white looks better).
//

    if ( !xwd->color )
    {
        pls->cmap0[0].r = pls->cmap0[0].g = pls->cmap0[0].b = 0xFF;
    }
    gslevbg = (unsigned int) ( ( (long) pls->cmap0[0].r +
                                 (long) pls->cmap0[0].g +
                                 (long) pls->cmap0[0].b ) / 3 );

    PLColor_to_XColor( &pls->cmap0[0], &xwd->cmap0[0] );

//
// Set foreground color.
//
// Used for grayscale output, since otherwise the plots can become nearly
// unreadable (i.e. if colors get mapped onto grayscale values).  In this
// case it becomes the grayscale level for all draws, and is taken to be
// black if the background is light, and white if the background is dark.
// Note that white/black allocations never fail.
//

    if ( gslevbg > 0x7F )
        gslevfg = 0;
    else
        gslevfg = 0xFF;

    fgcolor.r = fgcolor.g = fgcolor.b = (unsigned char) gslevfg;

    PLColor_to_XColor( &fgcolor, &xwd->fgcolor );

// Now store

    if ( xwd->rw_cmap && xwd->color )
    {
        XStoreColor( xwd->display, xwd->map, &xwd->fgcolor );
        XStoreColor( xwd->display, xwd->map, &xwd->cmap0[0] );
    }
    else
    {
        XAllocColor( xwd->display, xwd->map, &xwd->cmap0[0] );
        XAllocColor( xwd->display, xwd->map, &xwd->fgcolor );
    }
}

//--------------------------------------------------------------------------
// InitColors()
//
// Does all color initialization.
//--------------------------------------------------------------------------

static void
InitColors( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    dbug_enter( "InitColors" );

// Allocate and initialize color maps.
// Defer cmap1 allocation until it's actually used

    if ( xwd->color )
    {
        if ( plplot_ccmap )
        {
            AllocCustomMap( pls );
        }
        else
        {
            AllocCmap0( pls );
        }
    }
}

//--------------------------------------------------------------------------
// AllocCustomMap()
//
// Initializes custom color map and all the cruft that goes with it.
//
// Assuming all color X displays do 256 colors, the breakdown is as follows:
//
// CCMAP_XWM_COLORS
//	Number of low "pixel" values to copy.  These are typically allocated
//	first, thus are in use by the window manager. I copy them to reduce
//	flicker.
//
//
// RWMAP_CMAP1_COLORS
//	Color map 1 entries.  There should be as many as practical available
//	for smooth shading.  On the order of 50-100 is pretty reasonable.  You
//	don't really need all 256, especially if all you're going to do is to
//	print it to postscript (which doesn't have any intrinsic limitation on
//	the number of colors).
//
// It's important to leave some extra colors unallocated for Tk.  In
// particular the palette tools require a fair amount.  I recommend leaving
// at least 40 or so free.
//--------------------------------------------------------------------------

static void
AllocCustomMap( PLStream *pls )
{
    XwDev         *dev = (XwDev *) pls->dev;
    XwDisplay     *xwd = (XwDisplay *) dev->xwd;

    XColor        xwm_colors[RWMAP_MAX_COLORS];
    int           i, npixels;
    unsigned long plane_masks[1], pixels[RWMAP_MAX_COLORS];

    dbug_enter( "AllocCustomMap" );

// Determine current default colors

    for ( i = 0; i < RWMAP_MAX_COLORS; i++ )
    {
        xwm_colors[i].pixel = (unsigned long int) i;
    }
    XQueryColors( xwd->display, xwd->map, xwm_colors, RWMAP_MAX_COLORS );

// Allocate cmap0 colors in the default colormap.
// The custom cmap0 colors are later stored at the same pixel values.
// This is a really cool trick to reduce the flicker when changing colormaps.
//

    AllocCmap0( pls );
    XAllocColor( xwd->display, xwd->map, &xwd->fgcolor );

// Create new color map

    xwd->map = XCreateColormap( xwd->display, DefaultRootWindow( xwd->display ),
        xwd->visual, AllocNone );

// Now allocate all colors so we can fill the ones we want to copy

    npixels = RWMAP_MAX_COLORS;
    for (;; )
    {
        if ( XAllocColorCells( xwd->display, xwd->map, False,
                 plane_masks, 0, pixels, (unsigned int) npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            plexit( "couldn't allocate any colors" );
    }

// Fill the low colors since those are in use by the window manager

    for ( i = 0; i < CCMAP_XWM_COLORS; i++ )
    {
        XStoreColor( xwd->display, xwd->map, &xwm_colors[i] );
        pixels[xwm_colors[i].pixel] = 0;
    }

// Fill the ones we will use in cmap0

    for ( i = 0; i < xwd->ncol0; i++ )
    {
        XStoreColor( xwd->display, xwd->map, &xwd->cmap0[i] );
        pixels[xwd->cmap0[i].pixel] = 0;
    }

// Finally, if the colormap was saved by an external agent, see if there are
// any differences from the current default map and save those!  A very cool
// (or sick, depending on how you look at it) trick to get over some X and
// Tk limitations.
//

    if ( sxwm_colors_set )
    {
        for ( i = 0; i < RWMAP_MAX_COLORS; i++ )
        {
            if ( ( xwm_colors[i].red != sxwm_colors[i].red ) ||
                 ( xwm_colors[i].green != sxwm_colors[i].green ) ||
                 ( xwm_colors[i].blue != sxwm_colors[i].blue ) )
            {
                if ( pixels[i] != 0 )
                {
                    XStoreColor( xwd->display, xwd->map, &xwm_colors[i] );
                    pixels[i] = 0;
                }
            }
        }
    }

// Now free the ones we're not interested in

    for ( i = 0; i < npixels; i++ )
    {
        if ( pixels[i] != 0 )
            XFreeColors( xwd->display, xwd->map, &pixels[i], 1, 0 );
    }

// Allocate colors in cmap 1

    AllocCmap1( pls );
}

//--------------------------------------------------------------------------
// AllocCmap0()
//
// Allocate & initialize cmap0 entries.
//--------------------------------------------------------------------------

static void
AllocCmap0( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    int       i;

    dbug_enter( "AllocCmap0" );

// Free all previous colors.  This should work for both rw & ro colormaps
    for ( i = 1; i < xwd->ncol0; i++ )
    {
        unsigned long pixel = xwd->cmap0[i].pixel;
        XFreeColors( xwd->display, xwd->map, &pixel, 1, 0 );
    }

// If the number of colors increased, need to allocate enough space for them
    if ( pls->ncol0 > xwd->ncol0_alloc )
    {
        xwd->ncol0_alloc = pls->ncol0;
        xwd->cmap0       = (XColor *)
                           realloc( xwd->cmap0, (size_t) pls->ncol0 * sizeof ( XColor ) );
        if ( xwd->cmap0 == 0 )
            plexit( "couldn't allocate space for cmap0 colors" );
    }

    if ( xwd->rw_cmap )
    {
        int           npixels;
        unsigned long plane_masks[1], pixels[RWMAP_MAX_COLORS];

        // Allocate and assign colors in cmap 0

        npixels = pls->ncol0 - 1;
        for (;; )
        {
            if ( XAllocColorCells( xwd->display, xwd->map, False,
                     plane_masks, 0, &pixels[1], (unsigned int) npixels ) )
                break;
            npixels--;
            if ( npixels == 0 )
                plexit( "couldn't allocate any colors" );
        }

        xwd->ncol0 = npixels + 1;
        for ( i = 1; i < xwd->ncol0; i++ )
        {
            xwd->cmap0[i].pixel = pixels[i];
        }

        StoreCmap0( pls );
    }
    else
    {
        if ( pls->verbose )
            fprintf( stderr, "Attempting to allocate r/o colors in cmap0.\n" );

        for ( i = 1; i < pls->ncol0; i++ )
        {
            int    r;
            XColor c;
            PLColor_to_XColor( &pls->cmap0[i], &c );
            r = XAllocColor( xwd->display, xwd->map, &c );
            if ( pls->verbose )
                fprintf( stderr, "i=%d, r=%d, pixel=%d\n", i, r, (int) c.pixel );
            if ( r )
            {
                xwd->cmap0[i]       = c;
                xwd->cmap0[i].pixel = c.pixel; // needed for deallocation
            }
            else
            {
                XColor screen_def, exact_def;

                if ( pls->verbose )
                    fprintf( stderr,
                        "color alloc failed, trying by name: %s.\n",
                        pls->cmap0[i].name );

                // Hmm, didn't work, try another approach.
                r = XAllocNamedColor( xwd->display, xwd->map,
                    pls->cmap0[i].name,
                    &screen_def, &exact_def );

//                 xwd->cmap0[i] = screen_def;

                if ( r )
                {
                    if ( pls->verbose )
                        fprintf( stderr, "yes, got a color by name.\n" );

                    xwd->cmap0[i]       = screen_def;
                    xwd->cmap0[i].pixel = screen_def.pixel;
                }
                else
                {
                    r = XAllocNamedColor( xwd->display, xwd->map,
                        "white",
                        &screen_def, &exact_def );
                    if ( r )
                    {
                        xwd->cmap0[i]       = screen_def;
                        xwd->cmap0[i].pixel = screen_def.pixel;
                    }
                    else
                        printf( "Can't find white?! Giving up...\n" );
                }
            }
        }
        xwd->ncol0 = i;

        if ( pls->verbose )
            fprintf( stderr, "Allocated %d colors in cmap0.\n", xwd->ncol0 );
    }
}

//--------------------------------------------------------------------------
// AllocCmap1()
//
// Allocate & initialize cmap1 entries.
//--------------------------------------------------------------------------

static void
AllocCmap1( PLStream *pls )
{
    XwDev         *dev = (XwDev *) pls->dev;
    XwDisplay     *xwd = (XwDisplay *) dev->xwd;

    int           i, j, npixels;
    unsigned long plane_masks[1], pixels[RWMAP_MAX_COLORS];

    dbug_enter( "AllocCmap1" );

    if ( xwd->rw_cmap )
    {
        if ( pls->verbose )
            fprintf( stderr, "Attempting to allocate r/w colors in cmap1.\n" );

        // If using the default color map, must severely limit number of colors
        // otherwise TK won't have enough.

        npixels = MAX( 2, MIN( RWMAP_CMAP1_COLORS, pls->ncol1 ) );
        for (;; )
        {
            if ( XAllocColorCells( xwd->display, xwd->map, False,
                     plane_masks, 0, pixels, (unsigned int) npixels ) )
                break;
            npixels--;
            if ( npixels == 0 )
                break;
        }

        if ( npixels < 2 )
        {
            xwd->ncol1 = -1;
            fprintf( stderr, "Warning: unable to allocate sufficient colors in cmap1.\n" );
            return;
        }

        xwd->ncol1 = npixels;
        if ( pls->verbose )
            fprintf( stderr, "AllocCmap1 (xwin.c): Allocated %d colors in cmap1.\n", npixels );

        // Allocate space if it hasn't been done yet
        if ( !xwd->cmap1 )
        {
            xwd->ncol1_alloc = xwd->ncol1;
            xwd->cmap1       = (XColor *) calloc( (size_t) ( xwd->ncol1 ), sizeof ( XColor ) );
            if ( !xwd->cmap1 )
                plexit( "couldn't allocate space for cmap1 colors" );
        }

        // Don't assign pixels sequentially, to avoid strange problems with xor
        // GC's.  Skipping by 2 seems to do the job best.

        for ( j = i = 0; i < xwd->ncol1; i++ )
        {
            while ( pixels[j] == 0 )
                j++;

            xwd->cmap1[i].pixel = pixels[j];
            pixels[j]           = 0;

            j += 2;
            if ( j >= xwd->ncol1 )
                j = 0;
        }

        StoreCmap1( pls );
    }
    else
    {
        int     r, ncolors;
        PLColor cmap1color;
        XColor  xcol;

        if ( pls->verbose )
            fprintf( stderr, "Attempting to allocate r/o colors in cmap1.\n" );

        switch ( xwd->visual->class )
        {
        case TrueColor:
            ncolors = TC_CMAP1_COLORS;
            break;
        default:
            ncolors = ROMAP_CMAP1_COLORS;
        }

        // Allocate space if it hasn't been done yet
        if ( !xwd->cmap1 )
        {
            xwd->ncol1_alloc = ncolors;
            xwd->cmap1       = (XColor *) calloc( (size_t) ncolors, sizeof ( XColor ) );
            if ( !xwd->cmap1 )
                plexit( "couldn't allocate space for cmap1 colors" );
        }

        for ( i = 0; i < ncolors; i++ )
        {
            plcol_interp( pls, &cmap1color, i, ncolors );
            PLColor_to_XColor( &cmap1color, &xcol );

            r = XAllocColor( xwd->display, xwd->map, &xcol );
            if ( pls->verbose )
                fprintf( stderr, "i=%d, r=%d, pixel=%d\n", i, r, (int) xcol.pixel );
            if ( r )
                xwd->cmap1[i] = xcol;
            else
                break;
        }
        if ( i < ncolors )
        {
            xwd->ncol1 = -1;
            fprintf( stderr,
                "Warning: unable to allocate sufficient colors in cmap1\n" );
            return;
        }
        else
        {
            xwd->ncol1 = ncolors;
            if ( pls->verbose )
                fprintf( stderr, "AllocCmap1 (xwin.c): Allocated %d colors in cmap1\n", ncolors );
        }
    }
}

//--------------------------------------------------------------------------
// StoreCmap0()
//
// Stores cmap 0 entries in X-server colormap.
//--------------------------------------------------------------------------

static void
StoreCmap0( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;
    int       i;

    if ( !xwd->color )
        return;

    for ( i = 1; i < xwd->ncol0; i++ )
    {
        PLColor_to_XColor( &pls->cmap0[i], &xwd->cmap0[i] );
        if ( xwd->rw_cmap )
            XStoreColor( xwd->display, xwd->map, &xwd->cmap0[i] );
        else
            XAllocColor( xwd->display, xwd->map, &xwd->cmap0[i] );
    }
}

//--------------------------------------------------------------------------
// StoreCmap1()
//
// Stores cmap 1 entries in X-server colormap.
//--------------------------------------------------------------------------

static void
StoreCmap1( PLStream *pls )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

    PLColor   cmap1color;
    int       i;

    if ( !xwd->color )
        return;

    for ( i = 0; i < xwd->ncol1; i++ )
    {
        plcol_interp( pls, &cmap1color, i, xwd->ncol1 );
        PLColor_to_XColor( &cmap1color, &xwd->cmap1[i] );
        if ( xwd->rw_cmap )
            XStoreColor( xwd->display, xwd->map, &xwd->cmap1[i] );
        else
            XAllocColor( xwd->display, xwd->map, &xwd->cmap1[i] );
    }
}

//--------------------------------------------------------------------------
// PLColor_to_XColor()
//
// Copies the supplied PLColor to an XColor, padding with bits as necessary
// (a PLColor uses 8 bits for color storage, while an XColor uses 16 bits).
// The argument types follow the same order as in the function name.
//--------------------------------------------------------------------------

#define ToXColor( a )     ( ( ( 0xFF & ( a ) ) << 8 ) | ( a ) )
#define ToPLColor( a )    ( ( (U_LONG) a ) >> 8 )

static void
PLColor_to_XColor( PLColor *plcolor, XColor *xcolor )
{
    xcolor->red   = (short unsigned) ToXColor( plcolor->r );
    xcolor->green = (short unsigned) ToXColor( plcolor->g );
    xcolor->blue  = (short unsigned) ToXColor( plcolor->b );
    xcolor->flags = DoRed | DoGreen | DoBlue;
}

//--------------------------------------------------------------------------
// PLColor_from_XColor()
//
// Copies the supplied XColor to a PLColor, stripping off bits as
// necessary.  See the previous routine for more info.
//--------------------------------------------------------------------------

static void
PLColor_from_XColor( PLColor *plcolor, XColor *xcolor )
{
    plcolor->r = (unsigned char) ToPLColor( xcolor->red );
    plcolor->g = (unsigned char) ToPLColor( xcolor->green );
    plcolor->b = (unsigned char) ToPLColor( xcolor->blue );
}

//--------------------------------------------------------------------------
// AreWeGrayscale(Display *display)
//
// Determines if we're using a monochrome or grayscale device.
// gmf 11-8-91; Courtesy of Paul Martz of Evans and Sutherland.
// Altered Andrew Ross 26-01-2004 to fix memory leak.
//--------------------------------------------------------------------------

static int
AreWeGrayscale( Display *display )
{
#if defined ( __cplusplus ) || defined ( c_plusplus )
#define THING    c_class
#else
#define THING    class
#endif

    XVisualInfo *visuals;
    int         nitems, i, igray;

    // get a list of info on the visuals available
    visuals = XGetVisualInfo( display, 0, NULL, &nitems );

    igray = 1;
    // check the list looking for non-monochrome visual classes
    for ( i = 0; i < nitems; i++ )
        if ( ( visuals[i].THING != GrayScale ) &&
             ( visuals[i].THING != StaticGray ) )
        {
            igray = 0;
            break;
        }

    XFree( visuals );
    // if igray = 1 only StaticGray and GrayScale classes available
    return igray;
}

#ifdef DUMMY
//--------------------------------------------------------------------------
// SaveColormap()  **** DUMMY, NOT USED ANYMORE ***
//
// Saves RGB components of given colormap.
// Used in an ugly hack to get past some X11R5 and TK limitations.
// This isn't guaranteed to work under all circumstances, but hopefully
// in the future there will be a nicer way to accomplish the same thing.
//
// Note: I tried using XCopyColormapAndFree to do the same thing, but under
// HPUX 9.01/VUE/X11R5 at least it doesn't preserve the previous read-only
// color cell allocations made by Tk.  Is this a bug?  Have to look at the
// source to find out.
//--------------------------------------------------------------------------

static void
SaveColormap( Display *display, Colormap colormap )
{
    int i;

    if ( !plplot_ccmap )
        return;

    sxwm_colors_set = 1;
    for ( i = 0; i < RWMAP_MAX_COLORS; i++ )
    {
        sxwm_colors[i].pixel = i;
    }
    XQueryColors( display, colormap, sxwm_colors, RWMAP_MAX_COLORS );
//
//  printf("\nAt startup, default colors are: \n\n");
//  for (i = 0; i < RWMAP_MAX_COLORS; i++) {
//      printf(" i: %d,  pixel: %d,  r: %d,  g: %d,  b: %d\n",
//             i, sxwm_colors[i].pixel,
//             sxwm_colors[i].red, sxwm_colors[i].green, sxwm_colors[i].blue);
//  }
//
}
#endif

//--------------------------------------------------------------------------
// GetImageErrorHandler()
//
// Error handler used in XGetImage() to catch errors when pixmap or window
// are not completely viewable.
//--------------------------------------------------------------------------

static int
GetImageErrorHandler( Display *display, XErrorEvent *error )
{
    if ( error->error_code != BadMatch )
    {
        char buffer[256];
        XGetErrorText( display, error->error_code, buffer, 256 );
        fprintf( stderr, "xwin: Error in XGetImage: %s.\n", buffer );
    }
    return 1;
}

//--------------------------------------------------------------------------
// DrawImage()
//
// Fill polygon described in points pls->dev_x[] and pls->dev_y[].
// Only solid color fill supported.
//--------------------------------------------------------------------------

static void
DrawImage( PLStream *pls )
{
    XwDev     *dev  = (XwDev *) pls->dev;
    XwDisplay *xwd  = (XwDisplay *) dev->xwd;
    XImage    *ximg = NULL;
    XColor    curcolor;
    PLINT     xmin, xmax, ymin, ymax, icol1;

    int       ( *oldErrorHandler )( Display *, XErrorEvent * );

    float     mlr, mtb;
    float     blt, brt, brb, blb;
    float     left, right;
    int       kx, ky;
    int       nx, ny, ix, iy;
    int       i, corners[4], r[4] = { 0, 0, 0, 0 };

    struct
    {
        float x, y;
    } Ppts[4];

    CheckForEvents( pls );

    xmin = (PLINT) ( dev->xscale * pls->imclxmin );
    xmax = (PLINT) ( dev->xscale * pls->imclxmax );
    ymin = (PLINT) ( dev->yscale * pls->imclymin );
    ymax = (PLINT) ( dev->yscale * pls->imclymax );

    nx = pls->dev_nptsX;
    ny = pls->dev_nptsY;

// XGetImage() call fails if either the pixmap or window is not fully viewable!
    oldErrorHandler = XSetErrorHandler( GetImageErrorHandler );

    XFlush( xwd->display );
    if ( dev->write_to_pixmap )
        ximg = XGetImage( xwd->display, dev->pixmap, 0, 0, dev->width, dev->height,
            AllPlanes, ZPixmap );

    if ( dev->write_to_window )
        ximg = XGetImage( xwd->display, dev->window, 0, 0, dev->width, dev->height,
            AllPlanes, ZPixmap );

    XSetErrorHandler( oldErrorHandler );

    if ( ximg == NULL )
    {
        plabort( "Can't get image, the window must be partly off-screen, move it to fit screen" );
        return;
    }

    if ( xwd->ncol1 == 0 )
        AllocCmap1( pls );
    if ( xwd->ncol1 < 2 )
        return;

// translate array for rotation
    switch ( (int) ( pls->diorot - 4. * floor( pls->diorot / 4. ) ) )
    {
    case 0:
        r[0] = 0; r[1] = 1; r[2] = 2; r[3] = 3; break;
    case 1:
        r[0] = 1; r[1] = 2; r[2] = 3; r[3] = 0; break;
    case 2:
        r[0] = 2; r[1] = 3; r[2] = 0; r[3] = 1; break;
    case 3:
        r[0] = 3; r[1] = 0; r[2] = 1; r[3] = 2;
    }

    // after rotation and coordinate translation, each fill
    // lozangue will have coordinates (Ppts), slopes (m...)
    // and y intercepts (b...):
    //
    //        Ppts[3]
    //          **
    // mlr,blt *  * mtb,brt
    //        *    *
    //Ppts[0]<      > Ppts[2]
    //        *    *
    // mtb,blt *  * mlr,brb
    //          **
    //        Ppts[1]
    //

// slope of left/right and top/bottom edges
    mlr = (float) ( ( dev->yscale * ( pls->dev_iy[1] - pls->dev_iy[0] ) ) /
                    ( dev->xscale * ( pls->dev_ix[1] - pls->dev_ix[0] ) ) );

    mtb = (float) ( ( dev->yscale * ( pls->dev_iy[ny] - pls->dev_iy[0] ) ) /
                    ( dev->xscale * ( pls->dev_ix[ny] - pls->dev_ix[0] ) ) );

    for ( ix = 0; ix < nx - 1; ix++ )
    {
        for ( iy = 0; iy < ny - 1; iy++ )
        {
            corners[0] = ix * ny + iy;             // [ix][iy]
            corners[1] = ( ix + 1 ) * ny + iy;     // [ix+1][iy]
            corners[2] = ( ix + 1 ) * ny + iy + 1; // [ix+1][iy+1]
            corners[3] = ix * ny + iy + 1;         // [ix][iy+1]

            for ( i = 0; i < 4; i++ )
            {
                Ppts[i].x = (float) ( dev->xscale * ( pls->dev_ix[corners[r[i]]] ) );
                Ppts[i].y = (float) ( dev->yscale * ( pls->dev_iy[corners[r[i]]] ) );
            }

            // if any corner is inside the draw area
            if ( Ppts[0].x >= xmin || Ppts[2].x <= xmax ||
                 Ppts[1].y >= ymin || Ppts[3].y <= ymax )
            {
                Ppts[0].x = MAX( Ppts[0].x, (float) xmin );
                Ppts[2].x = MIN( Ppts[2].x, (float) xmax );
                Ppts[1].y = MAX( Ppts[1].y, (float) ymin );
                Ppts[3].y = MIN( Ppts[3].y, (float) ymax );

                // the Z array has size (nx-1)*(ny-1)
                icol1 = pls->dev_z[ix * ( ny - 1 ) + iy];

                // only plot points within zmin/zmax range
                if ( icol1 < pls->dev_zmin || icol1 > pls->dev_zmax )
                    continue;

                icol1 = (PLINT) ( (float) icol1 / (float) USHRT_MAX * (float) ( xwd->ncol1 - 1 ) );
                if ( xwd->color )
                    curcolor = xwd->cmap1[icol1];
                else
                    curcolor = xwd->fgcolor;

                // Fill square between current and next points.

                // If the fill area is a single dot, accelerate the fill.
                if ( ( fabs( Ppts[2].x - Ppts[0].x ) == 1 ) &&
                     ( fabs( Ppts[3].y - Ppts[1].y ) == 1 ) )
                {
                    XPutPixel( ximg, (int) Ppts[0].x, (int) dev->height - 1 - (int) Ppts[0].y, (unsigned long) curcolor.pixel );

                    // integer rotate, accelerate
                }
                else if ( pls->diorot == floor( pls->diorot ) )
                {
                    for ( ky = (int) Ppts[1].y; ky < (int) Ppts[3].y; ky++ )
                        for ( kx = (int) Ppts[0].x; kx < (int) Ppts[2].x; kx++ )
                            XPutPixel( ximg, kx, (int) dev->height - 1 - ky, (unsigned int) curcolor.pixel );

                    // lozangue, scanline fill it
                }
                else
                {
                    // y interception point of left/right top/bottom edges
                    blt = Ppts[0].y - mlr * Ppts[0].x;
                    brb = Ppts[2].y - mlr * Ppts[2].x;

                    brt = Ppts[2].y - mtb * Ppts[2].x;
                    blb = Ppts[0].y - mtb * Ppts[0].x;

                    for ( ky = (int) Ppts[1].y; ky < (int) Ppts[3].y; ky++ )
                    {
                        left  = MAX( ( ( (float) ky - blt ) / mlr ), ( ( (float) ky - blb ) / mtb ) );
                        right = MIN( ( ( (float) ky - brt ) / mtb ), ( ( (float) ky - brb ) / mlr ) );
                        for ( kx = (int) Ppts[0].x; kx < (int) Ppts[2].x; kx++ )
                        {
                            if ( kx >= rint( left ) && kx <= rint( right ) )
                            {
                                XPutPixel( ximg, kx, (int) dev->height - 1 - ky, (unsigned int) curcolor.pixel );
                            }
                        }
                    }
                }
            }
        }
    }

    if ( dev->write_to_pixmap )
        XPutImage( xwd->display, dev->pixmap, dev->gc, ximg, 0, 0, 0, 0, dev->width, dev->height );

    if ( dev->write_to_window )
        XPutImage( xwd->display, dev->window, dev->gc, ximg, 0, 0,
            0, 0, dev->width, dev->height );

    XDestroyImage( ximg );
}

static void
imageops( PLStream *pls, PLINT *ptr )
{
    XwDev     *dev = (XwDev *) pls->dev;
    XwDisplay *xwd = (XwDisplay *) dev->xwd;

// TODO: store/revert to/from previous state

    switch ( *ptr )
    {
    case ZEROW2D:
        dev->write_to_window = 0;
        break;

    case ONEW2D:
        dev->write_to_window = 1;
        break;

    case ZEROW2B:
        dev->write_to_pixmap = 0;
        break;

    case ONEW2B:
        XFlush( xwd->display );
        dev->write_to_pixmap = 1;
        break;
    }
}

#else
int
pldummy_xwin()
{
    return 0;
}

#endif                          // PLD_xwin
