/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *  Copyright (C) 1997--2015  The R Core Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 */

/* The version for R 2.1.0 is partly based on patches by
   Ei-ji Nakama for use in Japanese.

   <MBCS> all the strings manipulated here like display and fonts specs
   are probably ASCII, or at least start with ASCII in the part searched.
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <Defn.h>

/* rint is C99 */
#ifdef HAVE_RINT
#define R_rint(x) rint(x)
#else
#define R_rint(x) ((int) x + 0.5)
#endif

/* needed on Solaris */
#define XK_MISCELLANY
#include <stdio.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Intrinsic.h>	/*->	Xlib.h	Xutil.h Xresource.h .. */
#ifdef HAVE_X11_Xmu
# include <X11/Xmu/Atoms.h>
#endif
#include <X11/keysymdef.h>


#define R_USE_PROTOTYPES 1
#include <R_ext/GraphicsEngine.h>
#include "Fileio.h"		/* R_fopen */
#include "rotated.h"		/* 'Public' routines from here */
/* For the input handlers of the event loop mechanism: */
#include <R_ext/eventloop.h>
#include <R_ext/Memory.h>	/* vmaxget */

/* In theory we should do this, but it works less well
# ifdef X_HAVE_UTF8_STRING
#  define HAVE_XUTF8TEXTESCAPEMENT 1
#  define HAVE_XUTF8TEXTEXTENTS 1
# endif */

typedef int (*X11IOhandler)(Display *);

#include "devX11.h"
#include "rlogo_icon.h" /* hard-coded ARGB icon */

#include <Rmodules/RX11.h>

static Cursor watch_cursor = (Cursor) 0 ;
static Cursor arrow_cursor = (Cursor) 0 ;
static Cursor cross_cursor = (Cursor) 0 ;


#define MM_PER_INCH	25.4			/* mm -> inch conversion */

#define X_BELL_VOLUME 0 /* integer between -100 and 100 for the volume
			    of the bell in locator. */
			/* Note: This is in relation to
			the general bell level. Was 50, but if > 0
			then "xset b off" will not disable the
			locator bell - pd 2002-3-11 */
/* a colour used to represent the background on png if transparent
   NB: must be grey as used as RGB and BGR
*/
#define PNG_TRANS 0xfefefe

	/********************************************************/
	/* If there are resources that are shared by all devices*/
	/* of this type, you may wish to make them globals	*/
	/* rather than including them in the device-specific	*/
	/* parameters structure (especially if they are large !)*/
	/********************************************************/

	/* X11 Driver Specific parameters
	 * with only one copy for all x11 devices */

static Display *display;			/* Display */
static char dspname[101]="";
static int screen;				/* Screen */
static Window rootwin;				/* Root Window */
static Visual *visual;				/* Visual */
static int depth;				/* Pixmap depth */
static int Vclass;				/* Visual class */
static X_COLORTYPE model;			/* User color model */
static int maxcubesize;				/* Max colorcube size */
static XSetWindowAttributes attributes;		/* Window attributes */
static Colormap colormap;			/* Default color map */
static int whitepixel;				/* bg overlaying canvas */
static XContext devPtrContext;
static Atom _XA_WM_PROTOCOLS, protocol;

static Rboolean displayOpen = FALSE;
static Rboolean inclose = FALSE;
static int numX11Devices = 0;

	/********************************************************/
	/* There must be an entry point for the device driver	*/
	/* which will create device-specific resources,		*/
	/* initialise the device-specific parameters structure	*/
	/* and return whether the setup succeeded		*/
	/* This is called by the graphics engine when the user	*/
	/* creates a new device of this type			*/
	/********************************************************/


	/********************************************************/
	/* There are a number of actions that every device	*/
	/* driver is expected to perform (even if, in some	*/
	/* cases it does nothing - just so long as it doesn't	*/
	/* crash !).  this is how the graphics engine interacts */
	/* with each device. ecah action will be documented	*/
	/* individually.					*/
	/* hooks for these actions must be set up when the	*/
	/* device is first created				*/
	/********************************************************/

	/* Device Driver Actions */

static void X11_Activate(pDevDesc dd);
static void X11_Circle(double x, double y, double r,
		       const pGEcontext gc, pDevDesc dd);
static void X11_Clip(double x0, double x1, double y0, double y1,
		     pDevDesc dd);
static void X11_Close(pDevDesc dd);
static void X11_Deactivate(pDevDesc dd);
static Rboolean X11_Locator(double *x, double *y, pDevDesc dd);
static void X11_Line(double x1, double y1, double x2, double y2,
		     const pGEcontext gc, pDevDesc dd);
static void X11_MetricInfo(int c, const pGEcontext gc,
			   double* ascent, double* descent,
			   double* width, pDevDesc dd);
static void X11_Mode(int mode, pDevDesc dd);
static void X11_NewPage(const pGEcontext gc, pDevDesc dd);
static void X11_Polygon(int n, double *x, double *y,
			const pGEcontext gc, pDevDesc dd);
static void X11_Polyline(int n, double *x, double *y,
			 const pGEcontext gc, pDevDesc dd);
static void X11_Rect(double x0, double y0, double x1, double y1,
		     const pGEcontext gc, pDevDesc dd);
static void X11_Raster(unsigned int *raster, int w, int h,
                       double x, double y, double width, double height,
                       double rot, Rboolean interpolate,
                       const pGEcontext gc, pDevDesc dd);
static SEXP X11_Cap(pDevDesc dd);
static void X11_Size(double *left, double *right,
		     double *bottom, double *top,
		     pDevDesc dd);
static double X11_StrWidth(const char *str, const pGEcontext gc, pDevDesc dd);
static void X11_Text(double x, double y, const char *str,
		     double rot, double hadj,
		     const pGEcontext gc, pDevDesc dd);
static void X11_eventHelper(pDevDesc dd, int code);

	/*************************************************/
	/* End of list of required device driver actions */
	/*************************************************/

	/* Support Routines */

static void *RLoadFont(pX11Desc, char*, int, int);
static double pixelHeight(void);
static double pixelWidth(void);
static void SetColor(unsigned int, pX11Desc);
static void SetFont(const pGEcontext, pX11Desc);
static void SetLinetype(const pGEcontext, pX11Desc);
static void X11_Close_bitmap(pX11Desc xd);
static char* translateFontFamily(char* family, pX11Desc xd);

static double RedGamma	 = 1.0;
static double GreenGamma = 1.0;
static double BlueGamma	 = 1.0;

#ifdef HAVE_WORKING_CAIRO
# include "cairoFns.c"

	/************************/
	/*        Buffering     */
	/************************/

/*
  Buffering is only implemented for the cairo-based devices.
   The original (Feb 2008) version had two types:
   - "nbcairo".  This wrote directly to a cairo_xlib_surface, xd->cs.
   - "cairo".  This wrote to a cairo_image_surface xd->cs, and copied that to 
     the cairo_xlib_surface (xd->xcs) at mode(0) calls.

   Further types were introduced (experimentally) in May 2011.  We kept:
   - "dbcairo".  Similar to cairo, but the copying is only done when needed
      based on a timer.
   Timing requires a medium-res timer. The current method is to update
   ca 100ms after the last activity (using the event loop) or at a
   mode(0) call if it is 500ms after the last update.
 */

#if (defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)) || defined(HAVE_GETTIMEOFDAY)
/* We need to avoid this in the rare case that it is only in seconds */
extern double currentTime(void); /* from datetime.c */
#else
/* Alternatively, use times() which R >= 2.14.0 requires.  This could
  conceivably wrap around, but on the sort of system where this might
  be used, clock_t is 32-bit (it is typically long or unsigned long)
  and CLK_TCK is 60-100, so it happens after many months of uptime.
*/
# include <sys/times.h>
# ifndef CLK_TCK
#   define CLK_TCK 60
# endif
static double currentTime(void)
{
    struct tms ti;
    return ((double) times(&ti))/CLK_TCK;
}
#endif

static void Cairo_update(pX11Desc xd)
{
    if(inclose || !xd || !xd->buffered || xd->holdlevel > 0) return;
    cairo_paint(xd->xcc);
    /* workaround for bug in cairo 1.12.x (PR#15168) */
    cairo_surface_flush(xd->xcs);
    if (xd->type == WINDOW) XDefineCursor(display, xd->window, arrow_cursor);
    XSync(display, 0);
    xd->last = currentTime();
}


/* 
   We record a linked list of devices which are open and double-buffered.
   The head of the list is a dummy entry to make removals the same for 
   any element.  
*/
struct xd_list {
    pX11Desc this;
   struct xd_list *next;
};

typedef struct xd_list *Xdl;
static struct xd_list xdl0;
static Xdl xdl = &xdl0;

static void CairoHandler(void)
{
    static int  buffer_lock = 0; /* reentrancy guard */
    if (!buffer_lock && xdl->next) {
	double current = currentTime();
	buffer_lock = 1;
	for(Xdl z = xdl->next; z; z = z->next) {
	    pX11Desc xd = z->this;
	    if(xd->last > xd->last_activity) continue;
	    if((current - xd->last) < xd->update_interval) continue;
	    Cairo_update(xd);
	}
	buffer_lock = 0;
    }
}

/* private hooks in sys-std.c */
extern void (* Rg_PolledEvents)(void);
extern int Rg_wait_usec;

/*
  check for updates every 50ms:
  by default the updater is only run >= 100ms after last update.
*/
#define WAIT 50000
static int timingInstalled = 0;
static void addBuffering(pX11Desc xd)
{
    Xdl xdln = (Xdl) malloc(sizeof(struct xd_list));
    xdln->this = xd;
    xdln->next = xdl->next;
    xdl->next = xdln;
    if(timingInstalled) return;
    timingInstalled = 1;
    Rg_PolledEvents = CairoHandler;
    Rg_wait_usec = WAIT;
}

static void removeBuffering(pX11Desc xd)
{
    for(Xdl z = xdl; z->next; z = z->next)
	if (z->next->this == xd) {
	    Xdl old = z->next;
	    z->next = z->next->next;
	    free(old);
	    break; 
	}
    if(xdl->next == NULL) {
	Rg_wait_usec = 0;
	timingInstalled = 0;
    }
}

static void Cairo_NewPage(const pGEcontext gc, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    cairo_reset_clip(xd->cc);
    xd->fill = R_OPAQUE(gc->fill) ? gc->fill: xd->canvas;
    CairoColor(xd->fill, xd);
    cairo_new_path(xd->cc);
    cairo_paint(xd->cc);
    if(xd->buffered) Cairo_update(xd); 
    else XSync(display, 0);
}

static int Cairo_holdflush(pDevDesc dd, int level)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    int old = xd->holdlevel;

    if(!xd->buffered) return old;
    xd->holdlevel += level;
    if(xd->holdlevel <= 0) xd->holdlevel = 0;
//    printf("holdlevel = %d\n",  xd->holdlevel);
    /* flush if at level zero - also changes cursor */
    if(xd->holdlevel == 0) {
	if(xd->buffered) Cairo_update(xd);
	else {
	  if (xd->type == WINDOW) XDefineCursor(display, xd->window, arrow_cursor);
	    XSync(display, 0);
	}
    } else if (old == 0) {
	/* May need to flush before holding */
	if(xd->buffered > 1 && xd->last_activity > xd->last) {
	    xd->holdlevel = old;
	    Cairo_update(xd);
	    xd->holdlevel = level;
	}
	if (xd->type == WINDOW) XDefineCursor(display, xd->window, watch_cursor);
	XSync(display, 0);
    }
    return xd->holdlevel;
}
#endif /* HAVE_WORKING_CAIRO */



	/************************/
	/* X11 Color Management */
	/************************/

/* Variables Used To Store Colormap Information */
static struct { int red; int green; int blue; } RPalette[512];
static XColor XPalette[512];
static int PaletteSize;


/* Monochome Displays : Compute pixel values by converting */
/* RGB values to luminance and then thresholding. */
/* See: Foley & van Damm. */

static void SetupMonochrome(void)
{
    depth = 1;
}

static unsigned GetMonochromePixel(int r, int g, int b)
{
    if ((int)(0.299 * r + 0.587 * g + 0.114 * b) > 127)
	return (unsigned) WhitePixel(display, screen);
    else
	return (unsigned) BlackPixel(display, screen);
}


/* Grayscale Displays : Compute pixel values by converting */
/* RGB values to luminance.  See: Foley & van Damm. */

static unsigned GetGrayScalePixel(int r, int g, int b)
{
    unsigned int d, dmin = 0xFFFFFFFF;
    unsigned int dr;
    int i;
    unsigned int pixel = 0;  /* -Wall */
    int gray = (int)((0.299 * r + 0.587 * g + 0.114 * b) + 0.0001);
    for (i = 0; i < PaletteSize; i++) {
	dr = (RPalette[i].red - gray);
	d = dr * dr;
	if (d < dmin) {
	    pixel = (unsigned) XPalette[i].pixel;
	    dmin = d;
	}
    }
    return pixel;
}

static Rboolean GetGrayPalette(Display *displ, Colormap cmap, int n)
{
    int status, i, m;
    m = 0;
    i = 0;
    for (i = 0; i < n; i++) {
	RPalette[i].red	  = (unsigned short) ((i * 0xff) / (n - 1));
	RPalette[i].green = RPalette[i].red;
	RPalette[i].blue  = RPalette[i].red;
	/* Gamma correct here */
	XPalette[i].red	  = (unsigned short)((i * 0xffff) / (n - 1));
	XPalette[i].green = XPalette[i].red;
	XPalette[i].blue  = XPalette[i].red;
	status = XAllocColor(displ, cmap, &XPalette[i]);
	if (status == 0) {
	    XPalette[i].flags = 0;
	    m++;
	}
	else
	    XPalette[i].flags = DoRed|DoGreen|DoBlue;
    }
    PaletteSize = n;
    if (m > 0) {
	for (i = 0; i < PaletteSize; i++) {
	    if (XPalette[i].flags != 0)
		XFreeColors(displ, cmap, &(XPalette[i].pixel), 1, 0);
	}
	PaletteSize = 0;
	return FALSE;
    }
    else return TRUE;
}

static void SetupGrayScale(void)
{
    int res = 0, d;
    PaletteSize = 0;
    /* try for 128 grays on an 8-bit display */
    if (depth > 8) d = depth = 8; else d = depth - 1;
    /* try (256), 128, 64, 32, 16 grays */
    while (d >= 4 && !(res = GetGrayPalette(display, colormap, 1 << d)))
	d--;
    if (!res) {
	/* Can't find a sensible grayscale, so revert to monochrome */
	warning(_("cannot set grayscale: reverting to monochrome"));
	model = MONOCHROME;
	SetupMonochrome();
    }
}

/* PseudoColor Displays : There are two strategies here. */
/* 1) allocate a standard color cube and match colors */
/* within that based on (weighted) distances in RGB space. */
/* 2) allocate colors exactly as they are requested until */
/* all color cells are used.  Fail with an error message */
/* when this happens. */

static int RGBlevels[][3] = {  /* PseudoColor Palettes */
    { 8, 8, 4 },
    { 6, 7, 6 },
    { 6, 6, 6 },
    { 6, 6, 5 },
    { 6, 6, 4 },
    { 5, 5, 5 },
    { 5, 5, 4 },
    { 4, 4, 4 },
    { 4, 4, 3 },
    { 3, 3, 3 },
    { 2, 2, 2 }
};
static int NRGBlevels = sizeof(RGBlevels) / (3 * sizeof(int));


static int GetColorPalette(Display *dpy, Colormap cmap, int nr, int ng, int nb)
{
    int status, i, m, r, g, b;
    m = 0;
    i = 0;
    for (r = 0; r < nr; r++) {
	for (g = 0; g < ng; g++) {
	    for (b = 0; b < nb; b++) {
		RPalette[i].red	  = (r * 0xff) / (nr - 1);
		RPalette[i].green = (g * 0xff) / (ng - 1);
		RPalette[i].blue  = (b * 0xff) / (nb - 1);
		/* Perform Gamma Correction Here */
		XPalette[i].red	  =
		    (unsigned short)(pow(r / (nr - 1.0), RedGamma) * 0xffff);
		XPalette[i].green = 
		    (unsigned short)(pow(g / (ng - 1.0), GreenGamma) * 0xffff);
		XPalette[i].blue  = 
		    (unsigned short)(pow(b / (nb - 1.0), BlueGamma) * 0xffff);
		/* End Gamma Correction */
		status = XAllocColor(dpy, cmap, &XPalette[i]);
		if (status == 0) {
		    XPalette[i].flags = 0;
		    m++;
		}
		else
		    XPalette[i].flags = DoRed|DoGreen|DoBlue;
		i++;
	    }
	}
    }
    PaletteSize = nr * ng * nb;
    if (m > 0) {
	for (i = 0; i < PaletteSize; i++) {
	    if (XPalette[i].flags != 0)
		XFreeColors(dpy, cmap, &(XPalette[i].pixel), 1, 0);
	}
	PaletteSize = 0;
	return 0;
    }
    else
	return 1;
}

static void SetupPseudoColor(void)
{
    int i, size;
    PaletteSize = 0;
    if (model == PSEUDOCOLOR1) {
	for (i = 0; i < NRGBlevels; i++) {
	    size = RGBlevels[i][0] * RGBlevels[i][1] * RGBlevels[i][2];
	    if (size < maxcubesize && GetColorPalette(display, colormap,
				RGBlevels[i][0],
				RGBlevels[i][1],
				RGBlevels[i][2]))
		break;
	}
	if (PaletteSize == 0) {
	    warning(_("X11 driver unable to obtain color cube\n  reverting to monochrome"));
	    model = MONOCHROME;
	    SetupMonochrome();
	}
    }
    else {
	PaletteSize = 0;
    }
}

static unsigned int GetPseudoColor1Pixel(int r, int g, int b)
{
    unsigned int d, dmin = 0xFFFFFFFF;
    unsigned int dr, dg, db;
    unsigned int pixel;
    int i;
    pixel = 0;			/* -Wall */
    for (i = 0; i < PaletteSize; i++) {
	dr = (RPalette[i].red - r);
	dg = (RPalette[i].green - g);
	db = (RPalette[i].blue - b);
	d = dr * dr + dg * dg + db * db;
	if (d < dmin) {
	    pixel = (unsigned int) XPalette[i].pixel;
	    dmin = d;
	}
    }
    return pixel;
}

static unsigned int GetPseudoColor2Pixel(int r, int g, int b)
{
    int i;
    /* Search for previously allocated color */
    for (i = 0; i < PaletteSize ; i++) {
	if (r == RPalette[i].red &&
	    g == RPalette[i].green &&
	    b == RPalette[i].blue) return (unsigned int) XPalette[i].pixel;
    }
    /* Attempt to allocate a new color */
    XPalette[PaletteSize].red	= 
	(unsigned short)(pow(r / 255.0, RedGamma) * 0xffff);
    XPalette[PaletteSize].green = 
	(unsigned short)(pow(g / 255.0, GreenGamma) * 0xffff);
    XPalette[PaletteSize].blue	= 
	(unsigned short)(pow(b / 255.0, BlueGamma) * 0xffff);
    if (PaletteSize == 256 ||
	XAllocColor(display, colormap, &XPalette[PaletteSize]) == 0) {
	error(_("Error: X11 cannot allocate additional graphics colors.\n\
Consider using X11 with colortype=\"pseudo.cube\" or \"gray\"."));
    }
    RPalette[PaletteSize].red = r;
    RPalette[PaletteSize].green = g;
    RPalette[PaletteSize].blue = b;
    PaletteSize++;
    return (unsigned int)XPalette[PaletteSize - 1].pixel;
}

static unsigned int GetPseudoColorPixel(int r, int g, int b)
{
    if (model == PSEUDOCOLOR1)
	return GetPseudoColor1Pixel(r, g, b);
    else
	return GetPseudoColor2Pixel(r, g, b);
}

/* Truecolor Displays : Allocate the colors as they are requested */

static unsigned int RMask, RShift;
static unsigned int GMask, GShift;
static unsigned int BMask, BShift;

static void SetupTrueColor(void)
{
    RMask = (unsigned int)visual->red_mask;
    GMask = (unsigned int)visual->green_mask;
    BMask = (unsigned int)visual->blue_mask;
    RShift = 0; while ((RMask & 1) == 0) { RShift++; RMask >>= 1; }
    GShift = 0; while ((GMask & 1) == 0) { GShift++; GMask >>= 1; }
    BShift = 0; while ((BMask & 1) == 0) { BShift++; BMask >>= 1; }
}

static unsigned GetTrueColorPixel(int r, int g, int b)
{
    r = (int)(pow((r / 255.0), RedGamma) * 255);
    g = (int)(pow((g / 255.0), GreenGamma) * 255);
    b = (int)(pow((b / 255.0), BlueGamma) * 255);
    return
	(((r * RMask) / 255) << RShift) |
	(((g * GMask) / 255) << GShift) |
	(((b * BMask) / 255) << BShift);
}

/* Interface for General Visual */

static unsigned int GetX11Pixel(int r, int g, int b)
{
    switch(model) {
    case MONOCHROME:
	return GetMonochromePixel(r, g, b);
    case GRAYSCALE:
	return GetGrayScalePixel(r, g, b);
    case PSEUDOCOLOR1:
    case PSEUDOCOLOR2:
	return GetPseudoColorPixel(r, g, b);
    case TRUECOLOR:
	return GetTrueColorPixel(r, g, b);
    default:
	printf("Unknown Visual\n");
    }
    return 0;
}

static void FreeX11Colors(void)
{
    int i;
    if (model == PSEUDOCOLOR2) {
	for (i = 0; i < PaletteSize; i++)
	    XFreeColors(display, colormap, &(XPalette[i].pixel), 1, 0);
	PaletteSize = 0;
    }
}

static Rboolean SetupX11Color(void)
{
    if (depth <= 1) {
	/* On monchome displays we must use black/white */
	model = MONOCHROME;
	SetupMonochrome();
    }
    else if (Vclass ==	StaticGray || Vclass == GrayScale) {
	if (model == MONOCHROME)
	    SetupMonochrome();
	else {
	    model = GRAYSCALE;
	    SetupGrayScale();
	}
    }
    else if (Vclass == StaticColor) {
	/* FIXME : Currently revert to mono. */
	/* Should do the real thing. */
	model = MONOCHROME;
	SetupMonochrome();
    }
    else if (Vclass ==	PseudoColor) {
	if (model == MONOCHROME)
	    SetupMonochrome();
	else if (model == GRAYSCALE)
	    SetupGrayScale();
	else {
	    if (model == TRUECOLOR)
		model = PSEUDOCOLOR2;
	    SetupPseudoColor();
	}
    }
    else if (Vclass == TrueColor) {
	if (model == MONOCHROME)
	    SetupMonochrome();
	else if (model == GRAYSCALE)
	    SetupGrayScale();
	else if (model == PSEUDOCOLOR1 || model == PSEUDOCOLOR2)
	    SetupPseudoColor();
	else
	    SetupTrueColor();
    }
    else if (Vclass == DirectColor) {
	/* FIXME : Currently revert to mono. */
	/* Should do the real thing. */
	model = MONOCHROME;
	SetupMonochrome();
    }
    else {
	printf("Unknown Visual\n");
	return FALSE;
    }
    return TRUE;
}

	/* Pixel Dimensions (Inches) */


static double pixelWidth(void)
{
    double width, widthMM;
    width = DisplayWidth(display, screen);
    widthMM = DisplayWidthMM(display, screen);
    return ((double)widthMM / (double)width) / MM_PER_INCH;
}

static double pixelHeight(void)
{
    double height, heightMM;
    height = DisplayHeight(display, screen);
    heightMM = DisplayHeightMM(display, screen);
    return ((double)heightMM / (double)height) / MM_PER_INCH;
}

static void handleEvent(XEvent event)
{
    if (event.xany.type == Expose) {
	/* ----- window repaint ------ */
	while (XCheckTypedWindowEvent(display, event.xexpose.window, Expose, &event));
	if (inclose) return;
	if (event.xexpose.count != 0) return;
	caddr_t temp;
	XFindContext(display, event.xexpose.window, devPtrContext, &temp);
	pDevDesc dd = (pDevDesc) temp;
	pGEDevDesc gdd = desc2GEDesc(dd);
	if(gdd->dirty) {
#ifdef HAVE_WORKING_CAIRO
	    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
	    /* We can use the buffered copy where we have it */ 
	    if(xd->buffered == 1) {
		cairo_paint(xd->xcc);
		/* workaround for bug in cairo 1.12.x (PR#15168) */
		cairo_surface_flush(xd->xcs);
	    } else if (xd->buffered > 1)
		/* rely on timer to repaint eventually */
		xd->last_activity = currentTime();
	    else
#endif
		GEplayDisplayList(gdd);
	    XSync(display, 0);
	}
    } else if (event.type == ConfigureNotify) {
	while (XCheckTypedEvent(display, ConfigureNotify, &event)) ;
	if (inclose) return;
	caddr_t temp;
	XFindContext(display, event.xconfigure.window, devPtrContext, &temp);
	pDevDesc dd = (pDevDesc) temp;
	pX11Desc xd = (pX11Desc) dd->deviceSpecific;
	if (xd->windowWidth != event.xconfigure.width ||
	    xd->windowHeight != event.xconfigure.height) {

	    /* ----- window resize ------ */

	    xd->windowWidth = event.xconfigure.width;
	    xd->windowHeight = event.xconfigure.height;
#if defined HAVE_WORKING_CAIRO
	    if(xd->useCairo) {
		if(xd->buffered) {
		    cairo_surface_destroy(xd->cs); xd->cs = NULL;
		    cairo_destroy(xd->cc); xd->cc = NULL;
		    cairo_xlib_surface_set_size(xd->xcs, xd->windowWidth,
						    xd->windowHeight);
		    xd->cs = 
			cairo_image_surface_create(CAIRO_FORMAT_RGB24,
						   xd->windowWidth,
						   xd->windowHeight);
		    cairo_status_t res = cairo_surface_status(xd->cs);
		    if (res != CAIRO_STATUS_SUCCESS) {
			warning("cairo error '%s'", 
				cairo_status_to_string(res));
			error("fatal error on resize: please shut down the device");
		    }
		    xd->cc = cairo_create(xd->cs);
		    cairo_set_antialias(xd->cc, xd->antialias);
		    cairo_set_source_surface (xd->xcc, xd->cs, 0, 0);
		} else { /* not buffered */
		    cairo_xlib_surface_set_size(xd->cs, xd->windowWidth,
						xd->windowHeight);
		    cairo_reset_clip(xd->cc);
		}
	    }
#endif
	    dd->size(&(dd->left), &(dd->right), &(dd->bottom), &(dd->top), dd);	
	    /* gobble Expose events; we'll redraw anyway */
	    while (XCheckTypedWindowEvent(display, event.xexpose.window, Expose, &event));
	    pGEDevDesc gdd = desc2GEDesc(dd);
	    if(gdd->dirty) {
		GEplayDisplayList(gdd);
		XSync(display, 0);
	    }
	}
    } else if ((event.type == ClientMessage) &&
	     (event.xclient.message_type == _XA_WM_PROTOCOLS)) {
	if (!inclose && event.xclient.data.l[0] == protocol) {
	    caddr_t temp;
	    XFindContext(display, event.xclient.window, devPtrContext, &temp);
	    killDevice(ndevNumber((pDevDesc) temp));
	}
    }
}

static void R_ProcessX11Events(void *data)
{
    XEvent event;

    while (!R_isForkedChild && displayOpen && XPending(display)) {
	XNextEvent(display, &event);
	/* printf("%i\n",event.type); */
	handleEvent(event);
    }
}


	/************************/
	/* X11 Font Management  */
	/************************/

static char *fontname = "-adobe-helvetica-%s-%s-*-*-%d-*-*-*-*-*-*-*";
static char *symbolname	 = "-adobe-symbol-medium-r-*-*-%d-*-*-*-*-*-*-*";

static char *slant[]  = {"r", "o"};
static char *weight[] = {"medium", "bold"};

/* Bitmap of the Adobe design sizes */

static unsigned int adobe_sizes = 0x0403165D;

#define MAXFONTS 64
#define CLRFONTS 16 /* Number to free when cache runs full */

typedef struct {
    char family[500];
    int face, size;
    R_XFont *font;
} cacheentry;

static cacheentry fontcache[MAXFONTS];
static int nfonts = 0;
static int force_nonscalable = 0; /* for testing */

#define ADOBE_SIZE(I) ((I) > 7 && (I) < 35 && (adobe_sizes & (1<<((I)-8))))
#define SMALLEST 2


static R_XFont *R_XLoadQueryFont(Display *display, char *name)
{
    R_XFont *tmp;
    tmp = (R_XFont *) malloc(sizeof(R_XFont));
    tmp->type = One_Font;
    tmp->font = XLoadQueryFont(display, name);
    if(tmp->font)
	return tmp;
    else {
	free(tmp);
	return NULL;
    }
}

static void R_XFreeFont(Display *display, R_XFont *font)
{
    if(font->type == Font_Set) XFreeFontSet(display, font->fontset);
    else XFreeFont(display, font->font);
    free(font);
}


/*
 * Can't load Symbolfont to XFontSet!!
 */
static R_XFont *R_XLoadQueryFontSet(Display *display,
				    const char *fontset_name)
{
    R_XFont *tmp = (R_XFont *) malloc(sizeof(R_XFont));
    XFontSet fontset;
    int  /*i,*/ missing_charset_count;
    char **missing_charset_list, *def_string;

#ifdef DEBUG_X11
    printf("loading fontset %s\n", fontset_name);
#endif
    fontset = XCreateFontSet(display, fontset_name, &missing_charset_list,
			     &missing_charset_count, &def_string);
    if(!fontset) {
	free(tmp);
	return NULL;
    }
    if (missing_charset_count) {
#ifdef DEBUG_X11
	int i;
	for(i = 0; i < missing_charset_count; i++)
	   warning("font for charset %s is lacking.", missing_charset_list[i]);
	XFreeStringList(missing_charset_list);
#endif
    }
    tmp->type = Font_Set;
    tmp->fontset = fontset;
    return tmp;
}


static void *RLoadFont(pX11Desc xd, char* family, int face, int size)
{
    /* size is in points here */
    int pixelsize, i, dpi;
    cacheentry *f;
    char buf[BUFSIZ];
    char buf1[BUFSIZ];
    R_XFont *tmp = NULL;

#ifdef DEBUG_X11
    printf("trying face %d size %d\n", face, size);
#endif

    if (size < SMALLEST) size = SMALLEST;
    face--;

    if(xd->type == PNG || xd->type == JPEG ||
       xd->type == TIFF || xd->type == BMP) {
	dpi = (xd->res_dpi > 0) ? (int)(xd->res_dpi + 0.5) : 72;
    } else {
	dpi = (int)(1./pixelHeight() + 0.5);
    }

    if(abs(dpi - 75) < 5) {
	/* use pointsize as pixel size */
    } else if(abs(dpi - 100) < 5) {
    /* Here's a 1st class fudge: make sure that the Adobe design sizes
       8, 10, 11, 12, 14, 17, 18, 20, 24, 25, 34 can be obtained via
       an integer "size" at 100 dpi, namely 6, 7, 8, 9, 10, 12, 13,
       14, 17, 18, 24 points. It's almost y = x * 100/72, but not
       quite. The constants were found using lm(). --pd */
	size = (int) R_rint(size * 1.43 - 0.4);
    } else size = (int) R_rint(size * dpi/72);

    /* search fontcache */
    for ( i = nfonts ; i-- ; ) {
	f = &fontcache[i];
	if ( strcmp(f->family, family) == 0 &&
	     f->face == face &&
	     f->size == size )
	    return f->font;
    }

    /* 'size' is the requested size, 'pixelsize'  the size of the
       actually allocated font*/
    pixelsize = size;

    /*
     * The symbol font face is a historical oddity
     * Always use a standard font for font face 5
     */
    if (face == SYMBOL_FONTFACE - 1) /* NB: face-- above */
	sprintf(buf, xd->symbolfamily,  pixelsize);
    else
      if (mbcslocale && *slant[(face & 2) >> 1] == 'o') {
	sprintf(buf, family, weight[face & 1], slant[(face & 2) >> 1],
		pixelsize);
	sprintf(buf1, family, weight[face & 1], "i",  pixelsize);
	strcat(buf,",");
	strcat(buf,buf1);
      } else
	  sprintf(buf, family, weight[face & 1], slant[(face & 2) >> 1],
		  pixelsize);
#ifdef DEBUG_X11
    Rprintf("loading:\n%s\n",buf);
#endif
    if (!mbcslocale || face == SYMBOL_FONTFACE - 1)
      tmp = R_XLoadQueryFont(display, buf);
    else
      tmp = R_XLoadQueryFontSet(display, buf);

#ifdef DEBUG_X11
    if (tmp) Rprintf("success\n"); else Rprintf("failure\n");
#endif
    /*
     * IF can't find the font specified then
     * go to great lengths to find something else to use.
     */
    if (!tmp || (force_nonscalable && !ADOBE_SIZE(size)) ){
	static int near[]=
	  {14,14,14,17,17,18,20,20,20,20,24,24,24,25,25,25,25};
	/* 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  */

	/* If ADOBE_SIZE(pixelsize) is true at this point then
	   the user's system does not have the standard ADOBE font set
	   so we just have to use a "fixed" font.
	   If we can't find a "fixed" font then something is seriously
	   wrong */
	if ( ADOBE_SIZE(pixelsize) ) {
	    if(tmp)
		R_XFreeFont(display, tmp);
	    if(mbcslocale)
		tmp = (void*) R_XLoadQueryFontSet(display,
		   "-*-fixed-medium-r-*--13-*-*-*-*-*-*-*");
	    else
		tmp = (void*) R_XLoadQueryFont(display, "fixed");

	    if (tmp)
		return tmp;
	    else
		error(_("could not find any X11 fonts\nCheck that the Font Path is correct."));
	}

	if ( pixelsize < 8 )
	    pixelsize = 8;
	else if (pixelsize == 9)
	    pixelsize = 8;
	else if (pixelsize < 30) /* must be at least 13 */
	    pixelsize = near[size-13];
	else
	    pixelsize = 34;


	if (face == SYMBOL_FONTFACE - 1)
	    sprintf(buf, symbolname, pixelsize);
	else
	    sprintf(buf, fontname,
		    weight[face & 1],
		    slant[(face & 2) >> 1 ],  pixelsize);
#ifdef DEBUG_X11
	Rprintf("loading:\n%s\n",buf);
#endif
	if (!mbcslocale || face == SYMBOL_FONTFACE - 1)
	    tmp = R_XLoadQueryFont(display, buf);
	else
	    tmp = R_XLoadQueryFontSet(display, buf);
#ifdef DEBUG_X11
	if (tmp) Rprintf("success\n"); else Rprintf("failure\n");
#endif
    }
    if(!tmp && size > 24) {
	/* final try, size 24 */
	pixelsize = 24;
	if (face == 4)
	    sprintf(buf, symbolname, 24);
	else
	    sprintf(buf, fontname,
		    weight[face & 1],
		    slant[(face & 2) >> 1 ],  24);
#ifdef DEBUG_X11
	Rprintf("loading:\n%s\n",buf);
#endif

	if (!mbcslocale || face == SYMBOL_FONTFACE - 1)
	    tmp = R_XLoadQueryFont(display, buf);
	else
	    tmp = R_XLoadQueryFontSet(display, buf);

#ifdef DEBUG_X11
	if (tmp) Rprintf("success\n"); else Rprintf("failure\n");
#endif
    }

    if (tmp){
	f = &fontcache[nfonts++];
	strcpy(f->family, family);
	f->face = face;
	f->size = size;
	f->font = tmp;
	if (fabs( (pixelsize - size)/(double)size ) > 0.1)
	    warning(_("X11 used font size %d when %d was requested"),
		    pixelsize, size);
    }
    if (nfonts == MAXFONTS) /* make room in the font cache */
    {
	for (i = 0 ; i < CLRFONTS ; i++)
	      R_XFreeFont(display, fontcache[i].font);
	for (i = CLRFONTS ; i < MAXFONTS ; i++)
	    fontcache[i - CLRFONTS] = fontcache[i];
	nfonts -= CLRFONTS;
    }
    return tmp;
}


static void SetFont(const pGEcontext gc, pX11Desc xd)
{
    R_XFont *tmp;
    char *family = translateFontFamily(gc->fontfamily, xd);
    /* size is in points here */
    int size = (int)(gc->cex * gc->ps + 0.5), face = gc->fontface;

    if (face < 1 || face > 5) face = 1;

    if (size != xd->fontsize	|| face != xd->fontface ||
	strcmp(family, xd->fontfamily) != 0) {

	tmp = RLoadFont(xd, family, face, size);
	if(tmp) {
	    xd->font = tmp;
	    strcpy(xd->fontfamily, family);
	    xd->fontface = face;
	    xd->fontsize = size;
	} else
	    error(_("X11 font %s, face %d at size %d could not be loaded"),
		  family, face, size);
    }
}

static void CheckAlpha(int color, pX11Desc xd)
{
    unsigned int alpha = R_ALPHA(color);
    if (alpha > 0 && alpha < 255 && !xd->warn_trans) {
	warning(_("semi-transparency is not supported on this device: reported only once per page"));
	xd->warn_trans = TRUE;
    }
}

static void SetColor(unsigned int color, pX11Desc xd)
{
    if (color != xd->col) {
	int col = GetX11Pixel(R_RED(color), R_GREEN(color), R_BLUE(color));
	xd->col = color;
	XSetState(display, xd->wgc, col, whitepixel, GXcopy, AllPlanes);
    }
}

static int gcToX11lend(R_GE_lineend lend) {
    int newend = CapRound; /* -Wall */
    switch (lend) {
    case GE_ROUND_CAP:
	newend = CapRound;
	break;
    case GE_BUTT_CAP:
	newend = CapButt;
	break;
    case GE_SQUARE_CAP:
	newend = CapProjecting;
	break;
    default:
	error(_("invalid line end"));
    }
    return newend;
}

static int gcToX11ljoin(R_GE_linejoin ljoin) {
    int newjoin = JoinRound; /* -Wall */
    switch (ljoin) {
    case GE_ROUND_JOIN:
	newjoin = JoinRound;
	break;
    case GE_MITRE_JOIN:
	newjoin = JoinMiter;
	break;
    case GE_BEVEL_JOIN:
	newjoin = JoinBevel;
	break;
    default:
	error(_("invalid line join"));
    }
    return newjoin;
}

/* --> See "Notes on Line Textures" in GraphicsEngine.h
 *
 *	27/5/98 Paul - change to allow lty and lwd to interact:
 *	the line texture is now scaled by the line width so that,
 *	for example, a wide (lwd=2) dotted line (lty=2) has bigger
 *	dots which are more widely spaced.  Previously, such a line
 *	would have "dots" which were wide, but not long, nor widely
 *	spaced.
 */

/* Not at all clear the optimization here is worth it */
static void SetLinetype(const pGEcontext gc, pX11Desc xd)
{
    int i, newlty, newlend, newljoin;
    double newlwd;

    newlty = gc->lty;
    newlwd = gc->lwd;
    if (newlwd < 1)/* not less than 1 pixel */
	newlwd = 1;
    if (newlty != xd->lty || newlwd != xd->lwd ||
	gc->lend != xd->lend || gc->ljoin != xd->ljoin) {
	xd->lty = newlty;
	xd->lwd = newlwd;
	xd->lend = gc->lend;
	xd->ljoin = gc->ljoin;
	newlend = gcToX11lend(gc->lend);
	newljoin = gcToX11ljoin(gc->ljoin);
	if (newlty == 0 || newlty == NA_INTEGER) {/* special hack for lty = 0 -- only for X11 */
	    XSetLineAttributes(display, xd->wgc,
			       (int)(newlwd*xd->lwdscale+0.5),
			       LineSolid, newlend, newljoin);
	} else {
	    static char dashlist[8];
	    for(i = 0 ; i < 8 && (newlty != 0); i++) {
		int j = newlty & 15;
		if (j == 0) j = 1; /* Or we die with an X Error */
		/* scale line texture for line width */
		j = (int)(j*newlwd*xd->lwdscale+0.5);
		/* make sure that scaled line texture */
		/* does not exceed X11 storage limits */
		if (j > 255) j = 255;
		dashlist[i] = (char) j;
		newlty >>= 4;
	    }
	    /* NB if i is odd the pattern will be interpreted as
	       the original pattern concatenated with itself */
	    XSetDashes(display, xd->wgc, 0, dashlist, i);
	    XSetLineAttributes(display, xd->wgc,
			       (int)(newlwd*xd->lwdscale+0.5),
			       LineOnOffDash, newlend, newljoin);
	}
    }
}

/* Error handling. FIXME: This is rather sloppy; we ought to respect
   any 3rd party handlers by checking whether dsp is "our" display and
   calling the previous handler otherwise. */

static int R_X11Err(Display *dsp, XErrorEvent *event)
{
    char buff[1000];
    /* for tcl/tk */
    if (event->error_code == BadWindow) return 0;

    XGetErrorText(dsp, event->error_code, buff, 1000);
    warning(_("X11 protocol error: %s"), buff);
    return 0;
}

static int NORET R_X11IOErrSimple(Display *dsp)
{
    char *dn = XDisplayName(dspname);
    strcpy(dspname, "");
    error(_("X11 I/O error while opening X11 connection to '%s'"), dn);
}

static int NORET R_X11IOErr(Display *dsp)
{
    int fd = ConnectionNumber(display);
    /*
    while (nfonts--)  R_XFreeFont(display, fontcache[nfonts].font);
    nfonts = 0;
    */
    removeInputHandler(&R_InputHandlers,
		       getInputHandler(R_InputHandlers,fd));
    /*
    XCloseDisplay(display);
    displayOpen = FALSE;
    strcpy(dspname, "");
    */
    error(_("X11 fatal IO error: please save work and shut down R"));
}

#define USE_Xt 1

#ifdef USE_Xt
#include <X11/StringDefs.h>
#include <X11/Shell.h>
typedef struct gx_device_X_s {
    Pixel background, foreground, borderColor;
    Dimension borderWidth;
    String geometry;
} gx_device_X;

/* (String) casts are here to suppress warnings about discarding `const' */
#define RINIT(a,b,t,s,o,it,n)\
  {(String)(a), (String)(b), (String)t, sizeof(s),\
   XtOffsetOf(gx_device_X, o), (String)it, (n)}
#define rpix(a,b,o,n)\
  RINIT(a,b,XtRPixel,Pixel,o,XtRString,(XtPointer)(n))
#define rdim(a,b,o,n)\
  RINIT(a,b,XtRDimension,Dimension,o,XtRImmediate,(XtPointer)(n))
#define rstr(a,b,o,n)\
  RINIT(a,b,XtRString,String,o,XtRString,(char*)(n))

static XtResource x_resources[] = {
    rpix(XtNbackground, XtCBackground, background, "XtDefaultBackground"),
    rstr(XtNgeometry, XtCGeometry, geometry, NULL),
};

static const int x_resource_count = XtNumber(x_resources);

static String x_fallback_resources[] = {
    (String) "R_x11*Background: white",
    NULL
};
#endif

Rboolean
X11_Open(pDevDesc dd, pX11Desc xd, const char *dsp,
	 double w, double h, double gamma_fac, X_COLORTYPE colormodel,
	 int maxcube, int bgcolor, int canvascolor, int res,
	 int xpos, int ypos)
{
    /* if we have to bail out with "error", then must free(dd) and free(xd) */
    /* That means the *caller*: the X11DeviceDriver code frees xd, for example */

    XEvent event;
    int iw, ih, blackpixel;
    X_GTYPE type;
    const char *p = dsp;
    XGCValues gcv;
    XSizeHints *hint;

    if (!XSupportsLocale ())
	warning(_("locale not supported by Xlib: some X ops will operate in C locale"));
    if (!XSetLocaleModifiers ("")) warning(_("X cannot set locale modifiers"));

    if (!strncmp(dsp, "png::", 5)) {
#ifndef HAVE_PNG
	warning(_("no png support in this version of R"));
	return FALSE;
#else
	char buf[PATH_MAX]; /* allow for pageno formats */
	FILE *fp;
	if(strlen(dsp+5) >= PATH_MAX)
	    error(_("filename too long in png() call"));
	strcpy(xd->filename, dsp+5);
	snprintf(buf, PATH_MAX, dsp+5, 1); /* page 1 to start */
	if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) {
	    warning(_("could not open PNG file '%s'"), buf);
	    return FALSE;
	}
	xd->fp = fp;
	type = PNG;
	p = "";
	xd->res_dpi = res; /* place holder */
	dd->displayListOn = FALSE;
#endif
    }
    else if (!strncmp(dsp, "jpeg::", 6)) {
#ifndef HAVE_JPEG
	warning(_("no jpeg support in this version of R"));
	return FALSE;
#else
	char buf[PATH_MAX]; /* allow for pageno formats */
	char tmp[PATH_MAX], *pp;
	FILE *fp;
	strcpy(tmp, dsp+6);
	pp = strchr(tmp, ':'); *pp='\0';
	xd->quality = atoi(dsp+6);
	if(strlen(pp+1) >= PATH_MAX)
	    error(_("filename too long in jpeg() call"));
	strcpy(xd->filename, pp+1);
	snprintf(buf, PATH_MAX, pp+1, 1); /* page 1 to start */
	if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) {
	    warning(_("could not open JPEG file '%s'"), buf);
	    return FALSE;
	}
	xd->fp = fp;
	type = JPEG;
	p = "";
	xd->res_dpi = res; /* place holder */
	dd->displayListOn = FALSE;
#endif
    }
    else if (!strncmp(dsp, "tiff::", 5)) {
#ifndef HAVE_TIFF
	warning(_("no tiff support in this version of R"));
	return FALSE;
#else
	char tmp[PATH_MAX], *pp;
	strcpy(tmp, dsp+6);
	pp = strchr(tmp, ':'); *pp='\0';
	xd->quality = atoi(dsp+6);
	if(strlen(pp+1) >= PATH_MAX)
	    error(_("filename too long in tiff() call"));
	strcpy(xd->filename, pp+1);
	xd->fp = NULL;
	type = TIFF;
	p = "";
	xd->res_dpi = res; /* place holder */
	dd->displayListOn = FALSE;
#endif
    } else if (!strncmp(dsp, "bmp::", 5)) {
	char buf[PATH_MAX]; /* allow for pageno formats */
	FILE *fp;
	if(strlen(dsp+5) >= PATH_MAX)
	    error(_("filename too long in bmp() call"));
	strcpy(xd->filename, dsp+5);
	snprintf(buf, PATH_MAX, dsp+5, 1); /* page 1 to start */
	if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) {
	    warning(_("could not open BMP file '%s'"), buf);
	    return FALSE;
	}
	xd->fp = fp;
	type = BMP;
	p = "";
	xd->res_dpi = res; /* place holder */
	dd->displayListOn = FALSE;
    } else if (!strcmp(dsp, "XImage")) {
	type = XIMAGE;
	xd->fp = NULL;
	p = "";
    }
    else type = WINDOW;
    xd->type = type;

    /* If there is no server connection, establish one and */
    /* initialize the X11 device driver data structures. */

    if (!displayOpen) {
	/* Bill Dunlap sees an error when tunneling to a non-existent
	   X11 connection that BDR cannot reproduce.  We leave a handler set
	   if we get an error, but that is rare.
	*/
	X11IOhandler old;
	strncpy(dspname, p, 101);
	dspname[100] = '\0';
	old = XSetIOErrorHandler(R_X11IOErrSimple);
	if ((display = XOpenDisplay(p)) == NULL) {
	    XSetIOErrorHandler(old);
	    warning(_("unable to open connection to X11 display '%s'"), p);
	    return FALSE;
	}
	XSetIOErrorHandler(old);
	Rf_setX11Display(display, gamma_fac, colormodel, maxcube, TRUE);
	displayOpen = TRUE;
	if(xd->handleOwnEvents == FALSE)
	    addInputHandler(R_InputHandlers, ConnectionNumber(display),
			    R_ProcessX11Events, XActivity);
    } else if(strcmp(p, dspname))
	warning(_("ignoring 'display' argument as an X11 device is already open"));
    whitepixel = GetX11Pixel(R_RED(canvascolor), R_GREEN(canvascolor),
			     R_BLUE(canvascolor));
    blackpixel = GetX11Pixel(0, 0, 0);
#ifdef HAVE_WORKING_CAIRO
    if(xd->useCairo && Vclass != TrueColor) {
	warning(_("cairo-based types may only work correctly on TrueColor visuals"));
    }
#endif

    /* Foreground and Background Colors */

    xd->fill = bgcolor; /* was transparent */
    xd->col = R_RGB(0, 0, 0);
    xd->canvas = canvascolor;
    if(type == JPEG && !R_OPAQUE(xd->canvas)) {
	warning(_("jpeg() does not support transparency: using white bg"));
	xd->canvas = 0xffffff;
    }
    if(type > WINDOW) xd->fill = xd->canvas;


    /* Try to create a simple window. */
    /* We want to know about exposures */
    /* and window-resizes and locations. */

    /*
     * <MBCS-FIXED>: R on gnome window manager task-bar button see?
     * I try it.
     * A button such as, maximization disappears
     * unless I give Hint for clear statement in
     * gnome window manager.
     */

    memset(&attributes, 0, sizeof(attributes));
    attributes.background_pixel = whitepixel;
    attributes.border_pixel = blackpixel;
    attributes.backing_store = Always;
    attributes.event_mask = ButtonPressMask 
      | ButtonMotionMask 
      | PointerMotionHintMask
      | ButtonReleaseMask
      | ExposureMask
      | StructureNotifyMask
      | KeyPressMask;


    if (type == WINDOW) {
	int alreadyCreated = (xd->window != (Window)NULL);
	if(alreadyCreated == 0) {
	    xd->windowWidth = iw = (int)((ISNA(w)?7:w)/pixelWidth());
	    xd->windowHeight = ih = (int)((ISNA(h)?7:h)/pixelHeight());

	    hint = XAllocSizeHints();
	    if(xpos == NA_INTEGER)
		hint->x = numX11Devices*20 %
		    ( DisplayWidth(display, screen) - iw - 10 );
	    else hint->x = (xpos >= 0) ? xpos :
		DisplayWidth(display, screen) - iw + xpos;

	    if(ypos == NA_INTEGER)
		hint->y = numX11Devices*20 %
		    ( DisplayHeight(display, screen) + ih - 10 );
	    else hint->y = (ypos >= 0)? ypos :
		DisplayHeight(display, screen) - iw - ypos;
	    hint->width  = iw;
	    hint->height = ih;
	    hint->flags  = PPosition | PSize;
#ifdef USE_Xt
	    {
		XtAppContext app_con;
		Widget toplevel;
		Display *xtdpy;
		int zero = 0;
		gx_device_X xdev;

		XtToolkitInitialize();

		app_con = XtCreateApplicationContext();
		XtAppSetFallbackResources(app_con, x_fallback_resources);
		xtdpy = XtOpenDisplay(app_con, dspname, "r_x11", "R_x11",
				      NULL, 0, &zero, NULL);
		if(xtdpy) {
		    toplevel = XtAppCreateShell(NULL, "R_x11",
						applicationShellWidgetClass,
						xtdpy, NULL, 0);
		    XtGetApplicationResources(toplevel, (XtPointer) &xdev,
					      x_resources,
					      x_resource_count,
					      NULL, 0);
		    if (xdev.geometry != NULL) {
			char gstr[40];
			int bitmask;

			sprintf(gstr, "%dx%d+%d+%d", hint->width,
				hint->height, hint->x, hint->y);
			bitmask = XWMGeometry(display, DefaultScreen(display),
					      xdev.geometry, gstr,
					      1,
					      hint,
					      &hint->x, &hint->y,
					      &hint->width, &hint->height,
					      &hint->win_gravity);

			if (bitmask & (XValue | YValue))
			    hint->flags |= USPosition;
			if (bitmask & (WidthValue | HeightValue))
			    hint->flags |= USSize;
			/* Restore user-specified settings */
			if(xpos != NA_INTEGER)
			    hint->x = (xpos >= 0) ? xpos :
				DisplayWidth(display, screen) - iw + xpos;
			if(ypos != NA_INTEGER)
			    hint->y = (ypos >= 0)? ypos :
				DisplayHeight(display, screen) - iw - ypos;
			if(!ISNA(w)) hint->width = iw;
			if(!ISNA(h)) hint->height = ih;
		    }
		    XtDestroyWidget(toplevel);
		    XtCloseDisplay(xtdpy);
		} else {
		    warning(_("unable to obtain information on display '%s'"),
			    dsp);
		}
		XtDestroyApplicationContext(app_con);
	    }
#endif
	    xd->windowWidth = hint->width;
	    xd->windowHeight = hint->height;
	    /*printf("x = %d, y = %d\n", hint->x, hint->y);*/
	    xd->window = XCreateSimpleWindow(display,
					     rootwin,
					     hint->x,hint->y,
					     hint->width, hint->height,
					     1,
					     blackpixel,
					     whitepixel);
	    if (xd->window == 0 ) {
	      XFree(hint);
	      warning(_("unable to create X11 window"));
	      return FALSE;
	    }
	    XSetWMNormalHints(display, xd->window, hint);
	    XFree(hint);
	    XChangeWindowAttributes(display, xd->window,
				    CWEventMask | CWBackPixel |
				    CWBorderPixel | CWBackingStore,
				    &attributes);

	    XStoreName(display, xd->window, xd->title);

	    /* See (PR#14588) */
	    XClassHint *chint;
	    chint = XAllocClassHint();
	    if (chint) {
		chint->res_name = "r_x11";
		chint->res_class = "R_x11";
		XSetClassHint(display, xd->window, chint);
	    	XFree(chint);
	    }

            /* set window icon */
            XChangeProperty(display, xd->window,
                            XInternAtom(display, "_NET_WM_ICON", False),
                            XInternAtom(display, "CARDINAL", False), 32,
                            PropModeReplace,
                            (const unsigned char*) rlogo_icon, 2 + 48*48);

	    /* set up protocols so that window manager sends */
	    /* me an event when user "destroys" window */
	    _XA_WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0);
	    protocol = XInternAtom(display, "WM_DELETE_WINDOW", 0);
	    XSetWMProtocols(display, xd->window, &protocol, 1);

	    if(!arrow_cursor)
		arrow_cursor = XCreateFontCursor(display, XC_left_ptr) ;
	    if(!cross_cursor)
		cross_cursor = XCreateFontCursor(display, XC_crosshair);
	    if(!watch_cursor)
		watch_cursor = XCreateFontCursor(display, XC_watch) ;
	    if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor);

#ifdef HAVE_WORKING_CAIRO
	    if(xd->useCairo) {
		cairo_status_t res;
		if(xd->buffered) {
		    xd->xcs = 
			cairo_xlib_surface_create(display, xd->window,
						  visual,
						  xd->windowWidth,
						  xd->windowHeight);
		    res = cairo_surface_status(xd->xcs);
		    if (res != CAIRO_STATUS_SUCCESS) {
			warning("cairo error '%s'",
				cairo_status_to_string(res));
			/* bail out */
			return FALSE;
		    }
		    xd->xcc = cairo_create(xd->xcs);
		    res = cairo_status(xd->xcc);
		    if (res != CAIRO_STATUS_SUCCESS) {
			warning("cairo error '%s'", 
				cairo_status_to_string(res));
			cairo_surface_destroy(xd->xcs);
			/* bail out */
			return FALSE;
		    }
		    xd->cs = 
			cairo_image_surface_create(CAIRO_FORMAT_RGB24,
						   xd->windowWidth,
						   xd->windowHeight);
		    cairo_set_source_surface (xd->xcc, xd->cs, 0, 0);
		    if(xd->buffered > 1) addBuffering(xd);
		} else /* non-buffered */
		    xd->cs = 
			cairo_xlib_surface_create(display, xd->window,
						  visual,
						  xd->windowWidth,
						  xd->windowHeight);

		res = cairo_surface_status(xd->cs);
		if (res != CAIRO_STATUS_SUCCESS) {
		    warning("cairo error '%s'", cairo_status_to_string(res));
		    /* bail out */
		    if(xd->xcs) cairo_surface_destroy(xd->xcs);
		    if(xd->xcc) cairo_destroy(xd->xcc);
		    return FALSE;
		}
		xd->cc = cairo_create(xd->cs);
		res = cairo_status(xd->cc);
		if (res != CAIRO_STATUS_SUCCESS) {
		    warning("cairo error '%s'", cairo_status_to_string(res));
		    cairo_surface_destroy(xd->cs);
		    /* bail out */
		    if(xd->xcs) cairo_surface_destroy(xd->xcs);
		    if(xd->xcc) cairo_destroy(xd->xcc);
		    return FALSE;
		}
		cairo_set_operator(xd->cc, CAIRO_OPERATOR_OVER);
		cairo_set_antialias(xd->cc, xd->antialias);
		CairoColor(xd->canvas, xd);
		cairo_new_path(xd->cc);
		cairo_paint(xd->cc);
	    }
#endif
	}
	/* Save the pDevDesc with the window for event dispatching */
	XSaveContext(display, xd->window, devPtrContext, (caddr_t) dd);

	/* Map the window */
	if(alreadyCreated == 0) {
	    XSelectInput(display, xd->window,
			 ExposureMask | ButtonPressMask | StructureNotifyMask 
			 | ButtonReleaseMask | ButtonMotionMask  
                         | PointerMotionHintMask | KeyPressMask);
	    XMapWindow(display, xd->window);
	    XSync(display, 0);

	    /* Gobble expose events */

	    while ( XPeekEvent(display, &event),
		    !XCheckTypedEvent(display, Expose, &event))
		;
	    /* XNextEvent(display, &event);
	       if (event.xany.type == Expose) {
	       while (event.xexpose.count)
	       XNextEvent(display, &event);
	       }
	    */
	}
    } else { /* PIXMAP */
	xd->windowWidth = iw = (int) w;
	xd->windowHeight = ih = (int) h;
	if (iw < 20 && ih < 20)
	    warning(_("'width=%d, height=%d' are unlikely values in pixels"),
		    iw, ih);
	if ((xd->window = XCreatePixmap(
	    display, rootwin,
	    iw, ih, DefaultDepth(display, screen))) == 0) {
	    warning(_("unable to create pixmap"));
	    return FALSE;
	}
	/* Save the pDevDesc with the window for event dispatching */
	/* Is this needed? */
	XSaveContext(display, xd->window, devPtrContext, (caddr_t) dd);
	xd->npages = 0;
    }

    /* Set the graphics context */

    gcv.arc_mode = ArcChord;
    xd->wgc = XCreateGC(display, xd->window, GCArcMode, &gcv);
    XSetState(display, xd->wgc, blackpixel, whitepixel, GXcopy, AllPlanes);

    /* ensure that line drawing is set up at the first graphics call */
    xd->lty = -1;
    xd->lwd = -1;
    xd->lend = 0;
    xd->ljoin = 0;

    numX11Devices++;
    return TRUE;
}

/* Return a non-relocatable copy of a string */

static char *SaveFontSpec(SEXP sxp, int offset)
{
    char *s;
    if(!isString(sxp) || length(sxp) <= offset)
	error(_("invalid font specification"));
    s = R_alloc(strlen(CHAR(STRING_ELT(sxp, offset)))+1, sizeof(char));
    strcpy(s, CHAR(STRING_ELT(sxp, offset)));
    return s;
}

/*
 * Take the fontfamily from a gcontext (which is device-independent)
 * and convert it into an X11-specific font description using
 * the X11 font database (see src/library/graphics/R/unix/x11.R)
 *
 * IF gcontext fontfamily is empty ("")
 * OR IF can't find gcontext fontfamily in font database
 * THEN return xd->basefontfamily (the family set up when the
 *   device was created)
 */
static char* translateFontFamily(char* family, pX11Desc xd)
{
    SEXP graphicsNS, x11env, fontdb, fontnames;
    int i, nfonts;
    char* result = xd->basefontfamily;
    PROTECT_INDEX xpi;

    PROTECT(graphicsNS = R_FindNamespace(ScalarString(mkChar("grDevices"))));
    PROTECT_WITH_INDEX(x11env = findVar(install(".X11env"), graphicsNS), &xpi);
    if(TYPEOF(x11env) == PROMSXP)
	REPROTECT(x11env = eval(x11env, graphicsNS), xpi);
    PROTECT(fontdb = findVar(install(".X11.Fonts"), x11env));
    PROTECT(fontnames = getAttrib(fontdb, R_NamesSymbol));
    nfonts = LENGTH(fontdb);
    if (family[0]) {
	Rboolean found = FALSE;
	for (i = 0; i < nfonts && !found; i++) {
	    const char* fontFamily = CHAR(STRING_ELT(fontnames, i));
	    if (strcmp(family, fontFamily) == 0) {
		found = TRUE;
		result = SaveFontSpec(VECTOR_ELT(fontdb, i), 0);
	    }
	}
	if (!found)
	    warning(_("font family not found in X11 font database"));
    }
    UNPROTECT(4);
    return result;
}

static double X11_StrWidth(const char *str, const pGEcontext gc, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    SetFont(gc, xd);

    if (xd->font->type == One_Font)
	return (double) XTextWidth(xd->font->font, str, (int)strlen(str));
    else  {
#ifdef HAVE_XUTF8TEXTESCAPEMENT
	if(utf8locale)
	    return (double) Xutf8TextEscapement(xd->font->fontset,
						str, (int)strlen(str));
	else
#endif
	    return (double) XmbTextEscapement(xd->font->fontset,
					      str, (int)strlen(str));
    }
}


	/* Character Metric Information */
	/* Passing c == 0 gets font information */

static void X11_MetricInfo(int c, const pGEcontext gc,
			   double* ascent, double* descent,
			   double* width, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    int first = 0, last = 0;
    XFontStruct *f = NULL;

    if (c < 0)
	error(_("invalid use of %d < 0 in '%s'"), c, "X11_MetricInfo");

    SetFont(gc, xd);

    *ascent = 0; *descent = 0; *width = 0; /* fallback position */
    if (xd->font) {
	if (xd->font->type != One_Font) {
	    char **ml; XFontStruct **fs_list;
#ifdef DEBUG_X11
	    int i, cnt = XFontsOfFontSet(xd->font->fontset, &fs_list, &ml);

	    for (i = 0; i < cnt; i++) printf("%s\n", ml[i]);
	    printf("--- end of fontlist ---\n\n");
#else
	    XFontsOfFontSet(xd->font->fontset, &fs_list, &ml);
#endif

	    f = fs_list[0];
	} else f = xd->font->font;
	first = f->min_char_or_byte2;
	last = f->max_char_or_byte2;
    } else return;

    if (c == 0) {
	*ascent = f->ascent;
	*descent = f->descent;
	*width = f->max_bounds.width;
	return;
    }

    if (xd->font->type != One_Font) {  /* so an MBCS */
	XRectangle ink, log;
	char buf[16];

	ucstomb(buf, (unsigned int) c);
#ifdef HAVE_XUTF8TEXTEXTENTS
	if(utf8locale)
	    Xutf8TextExtents(xd->font->fontset, buf, (int)strlen(buf), &ink, &log);
	else
#endif
	    XmbTextExtents(xd->font->fontset, buf, (int)strlen(buf), &ink, &log);
	/* Rprintf("%d %d %d %d\n", ink.x, ink.y, ink.width, ink.height);
	   Rprintf("%d %d %d %d\n", log.x, log.y, log.width, log.height); */
	*ascent = -ink.y;
	*descent = ink.y + ink.height;
	/* <FIXME> why logical and not ink width? */
	*width = log.width;
	/* Rprintf("%d %lc w=%f a=%f d=%f\n", c, wc[0],
		    *width, *ascent, *descent);*/
    } else { /* symbol font */
	if(first <= c && c <= last) {
	  /*
	   * <MBCS-FIXED>: try demo(lm.glm,package="stats")
	   * per_char is NULL case.
	   */
	  if(f->per_char) {
	    *ascent = f->per_char[c-first].ascent;
	    *descent = f->per_char[c-first].descent;
	    *width = f->per_char[c-first].width;
	  } else {
	    *ascent = f->max_bounds.ascent;
	    *descent = f->max_bounds.descent;
	    *width = f->max_bounds.width;
	  }
	}
    }
}

static void X11_Clip(double x0, double x1, double y0, double y1,
			pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    if (x0 < x1) {
	xd->clip.x = (unsigned short) x0 ;
	xd->clip.width = (unsigned short) x1 - (unsigned short) x0 + 1;
    }
    else {
	xd->clip.x = (unsigned short) x1;
	xd->clip.width = (unsigned short) x0 - (unsigned short) x1 + 1;
    }

    if (y0 < y1) {
	xd->clip.y = (unsigned short) y0;
	xd->clip.height = (unsigned short) y1 -  (unsigned short) y0 + 1;
    }
    else {
	xd->clip.y = (unsigned short) y1;
	xd->clip.height = (unsigned short) y0 - (unsigned short) y1 + 1;
    }

    XSetClipRectangles(display, xd->wgc, 0, 0, &(xd->clip), 1, Unsorted);
}

static void X11_Size(double *left, double *right,
		     double *bottom, double *top,
		     pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    *left = 0.0;
    *right = xd->windowWidth;
    *bottom = xd->windowHeight;
    *top = 0.0;
}

static void X11_NewPage(const pGEcontext gc, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    xd->warn_trans = FALSE;
    if (xd->type > WINDOW) {
	if (xd->npages++) {
	    /* try to preserve the page we do have */
	    if (xd->type != XIMAGE) X11_Close_bitmap(xd);
	    if (xd->type != XIMAGE && xd->fp != NULL) fclose(xd->fp);
	    if (xd->type == PNG || xd->type == JPEG || xd->type == BMP) {
		char buf[PATH_MAX];
		snprintf(buf, PATH_MAX, xd->filename, xd->npages);
		xd->fp = R_fopen(R_ExpandFileName(buf), "w");
		if (!xd->fp)
		    error(_("could not open file '%s'"), buf);
	    }
	}
	CheckAlpha(gc->fill, xd);
	xd->fill = R_OPAQUE(gc->fill) ? gc->fill: PNG_TRANS;
	SetColor(xd->fill, xd);
	xd->clip.x = 0; xd->clip.width = (unsigned short)xd->windowWidth;
	xd->clip.y = 0; xd->clip.height = (unsigned short)xd->windowHeight;
	XSetClipRectangles(display, xd->wgc, 0, 0, &(xd->clip), 1, Unsorted);
	XFillRectangle(display, xd->window, xd->wgc, 0, 0,
		       xd->windowWidth, xd->windowHeight);
	return;
    }

    FreeX11Colors();
    if ( (model == PSEUDOCOLOR2) || (xd->fill != gc->fill)) {
	xd->fill = R_OPAQUE(gc->fill) ? gc->fill : xd->canvas;
	whitepixel = GetX11Pixel(R_RED(xd->fill),R_GREEN(xd->fill),R_BLUE(xd->fill));
	XSetWindowBackground(display, xd->window, whitepixel);
    }
    XClearWindow(display, xd->window);
    XSync(display, 0);
}

#include "bitmap.h"

static int knowncols[512];

static unsigned int bitgp(void *xi, int x, int y)
{
    int i, r, g, b;
    XColor xcol;

    /*	returns the colour of the (x,y) pixel stored as RGB */
    i = (int) XGetPixel((XImage *) xi, y, x);
    switch(model) {
    case MONOCHROME:
	return i == 0 ? 0xFFFFFFFF : 0;
    case GRAYSCALE:
    case PSEUDOCOLOR1:
    case PSEUDOCOLOR2:
	if (i < 512) {
	    if (knowncols[i] < 0) {
		xcol.pixel = i;
		XQueryColor(display, colormap, &xcol);
		knowncols[i] = ((xcol.red>>8)<<16) | ((xcol.green>>8)<<8)
		    | (xcol.blue>>8);
	    }
	    return knowncols[i] | 0xFF000000;
	} else {
	    xcol.pixel = i;
	    XQueryColor(display, colormap, &xcol);
	    return ((xcol.red>>8)<<16) | ((xcol.green>>8)<<8) | (xcol.blue>>8);
	}
    case TRUECOLOR:
	r = ((i>>RShift)&RMask) * 255 /(RMask);
	g = ((i>>GShift)&GMask) * 255 /(GMask);
	b = ((i>>BShift)&BMask) * 255 /(BMask);
	return (r<<16) | (g<<8) | b | 0xFF000000;
    default:
	return 0;
    }
    /* return 0;  not reached, needed for some compilers */
}

static void X11_Close_bitmap(pX11Desc xd)
{
    int i;
    XImage *xi;
    for (i = 0; i < 512; i++) knowncols[i] = -1;
    xi = XGetImage(display, xd->window, 0, 0,
		   xd->windowWidth, xd->windowHeight,
		   AllPlanes, ZPixmap);
    if (xd->type == PNG) {
	unsigned int pngtrans = PNG_TRANS;
	if(model == TRUECOLOR) {
	    int i, r, g, b;
	    /* some 'truecolor' displays distort colours */
	    i = GetX11Pixel(R_RED(PNG_TRANS),
			    R_GREEN(PNG_TRANS),
			    R_BLUE(PNG_TRANS));
	    r = ((i>>RShift)&RMask) * 255 /(RMask);
	    g = ((i>>GShift)&GMask) * 255 /(GMask);
	    b = ((i>>BShift)&BMask) * 255 /(BMask);
	    pngtrans = (r<<16) | (g<<8) | b | 0xFF000000;
	}
	R_SaveAsPng(xi, xd->windowWidth, xd->windowHeight,
		    bitgp, 0, xd->fp,
		    (xd->fill != PNG_TRANS) ? 0 : pngtrans, xd->res_dpi);
    } else if (xd->type == JPEG)
	R_SaveAsJpeg(xi, xd->windowWidth, xd->windowHeight,
		     bitgp, 0, xd->quality, xd->fp, xd->res_dpi);
    else if (xd->type == BMP)
	R_SaveAsBmp(xi, xd->windowWidth, xd->windowHeight,
		    bitgp, 0, xd->fp, xd->res_dpi);
    else if (xd->type == TIFF) {
	char buf[PATH_MAX];
	snprintf(buf, PATH_MAX, xd->filename, xd->npages);
	R_SaveAsTIFF(xi, xd->windowWidth, xd->windowHeight,
		     bitgp, 0, R_ExpandFileName(buf), xd->res_dpi,
		     xd->quality);
    }

    XDestroyImage(xi);
}

static void X11_Close(pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    if (xd->type == WINDOW) {
#ifdef HAVE_WORKING_CAIRO
	if(xd->buffered > 1) removeBuffering(xd);
#endif
	/* process pending events */
	/* set block on destroy events */
	inclose = TRUE;
	R_ProcessX11Events((void*) NULL);

#ifdef HAVE_WORKING_CAIRO
	if(xd->useCairo) {
	    if(xd->cs) cairo_surface_destroy(xd->cs);
	    if(xd->cc) cairo_destroy(xd->cc);
	    if(xd->xcs) cairo_surface_destroy(xd->xcs);
	    if(xd->xcc) cairo_destroy(xd->xcc);
	}
#endif

	XFreeGC(display, xd->wgc);
	XDestroyWindow(display, xd->window);
	XSync(display, 0);
    } else {
	if (xd->npages && xd->type != XIMAGE) X11_Close_bitmap(xd);
	XFreeGC(display, xd->wgc);
	XFreePixmap(display, xd->window);
	if (xd->type != XIMAGE && xd->fp != NULL) fclose(xd->fp);
    }

    numX11Devices--;
    if (numX11Devices == 0)  {
	int fd = ConnectionNumber(display);
	/* Free Resources Here */
	while (nfonts--)
	      R_XFreeFont(display, fontcache[nfonts].font);
	nfonts = 0;
	if(xd->handleOwnEvents == FALSE)
	    removeInputHandler(&R_InputHandlers,
			       getInputHandler(R_InputHandlers,fd));
	if(arrow_cursor) XFreeCursor(display, arrow_cursor);
	if(cross_cursor) XFreeCursor(display, cross_cursor);
	if(watch_cursor) XFreeCursor(display, watch_cursor);
	arrow_cursor = cross_cursor = watch_cursor = (Cursor) 0;
	XCloseDisplay(display);
	displayOpen = FALSE;
    }

    free(xd);
    inclose = FALSE;
}

static void X11_Activate(pDevDesc dd)
{
    char t[150];
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    if (xd->type > WINDOW) return;
    if(xd->title[0]) {
	snprintf(t, 140, xd->title, ndevNumber(dd) + 1);
	t[139] = '\0';
    } else {
	sprintf(t, "R Graphics: Device %d", ndevNumber(dd) + 1);
    }
    strcat(t, " (ACTIVE)");
    XStoreName(display, xd->window, t);
    XSync(display, 0);
}

static void X11_Deactivate(pDevDesc dd)
{
    char t[150];
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    if (xd->type > WINDOW) return;
    if(xd->title[0]) {
	snprintf(t, 140, xd->title, ndevNumber(dd) + 1);
	t[139] = '\0';
    } else {
	sprintf(t, "R Graphics: Device %d", ndevNumber(dd) + 1);
    }
    strcat(t, " (inactive)");
    XStoreName(display, xd->window, t);
    XSync(display, 0);
}

static void X11_Rect(double x0, double y0, double x1, double y1,
		     const pGEcontext gc, pDevDesc dd)
{
    double tmp;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    if (x0 > x1) {
	tmp = x0;
	x0 = x1;
	x1 = tmp;
    }
    if (y0 > y1) {
	tmp = y0;
	y0 = y1;
	y1 = tmp;
    }
    CheckAlpha(gc->fill, xd);
    if (R_OPAQUE(gc->fill)) {
	SetColor(gc->fill, xd);
	XFillRectangle(display, xd->window, xd->wgc, (int)x0, (int)y0,
		       (int)x1 - (int)x0, (int)y1 - (int)y0);
    }
    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetColor(gc->col, xd);
	SetLinetype(gc, xd);
	XDrawRectangle(display, xd->window, xd->wgc, (int)x0, (int)y0,
		       (int)x1 - (int)x0, (int)y1 - (int)y0);
    }
}

static void X11_Path(double *x, double *y,
                     int npoly, int *nper,
                     Rboolean winding,
                     const pGEcontext gc, pDevDesc dd)
{
    warning(_("%s not available for this device"), "Path drawing");
}

static unsigned int makeX11Pixel(unsigned int * rasterImage, int pixel) {
    return GetX11Pixel(R_RED(rasterImage[pixel]), 
                       R_GREEN(rasterImage[pixel]), 
                       R_BLUE(rasterImage[pixel]));
}

static void flipRaster(unsigned int *rasterImage,
                       int imageWidth, int imageHeight,
                       int invertX, int invertY,
                       unsigned int *flippedRaster) {
    int i, j;
    int rowInc, rowOff, colInc, colOff;

    if (invertX) {
        colInc = -1;
        colOff = imageWidth - 1;
    } else {
        colInc = 1;
        colOff = 0;
    }
    if (invertY) {
        rowInc = -1;
        rowOff = imageHeight - 1;
    } else {
        rowInc = 1;
        rowOff = 0;
    }

    for (i = 0; i < imageHeight ;i++) {
        for (j = 0; j < imageWidth; j++) {
            int row = (rowInc*i + rowOff);
            int col = (colInc*j + colOff);
            flippedRaster[i*imageWidth + j] = 
                rasterImage[row*imageWidth + col];
        }
    }
}

static void X11_Raster(unsigned int *raster, int w, int h,
                      double x, double y, 
                      double width, double height,
                      double rot, 
                      Rboolean interpolate,
                      const pGEcontext gc, pDevDesc dd)
{
    int i, j, pixel;
    int imageWidth;
    int imageHeight;
    int invertX = 0;
    int invertY = 0;
    double angle = rot*M_PI/180;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    XImage *image;
    unsigned int *rasterImage;
    const void *vmax = vmaxget();
   
    if (height < 0) {
        imageHeight = (int) -(height - .5);
        /* convert (x, y) from bottom-left to top-left */
        y = y - imageHeight*cos(angle);
        if (angle != 0) {
            x = x - imageHeight*sin(angle);
        }
    } else {
        imageHeight = (int) (height + .5);
        invertY = 1;
    }

    if (width < 0) {
        imageWidth = (int) -(width - .5);
        x = x - imageWidth*cos(angle);
        if (angle != 0)
            y = y - imageWidth*sin(angle);
        invertX = 1;
    } else {
        imageWidth = (int) (width + .5);
    }

    rasterImage = (unsigned int *) R_alloc(imageWidth * imageHeight,
                                           sizeof(unsigned int));
    if (interpolate) {
        R_GE_rasterInterpolate(raster, w, h, 
                               rasterImage, imageWidth, imageHeight);
    } else {
        R_GE_rasterScale(raster, w, h, 
                         rasterImage, imageWidth, imageHeight);
    }
    
    if (rot != 0) {
        
        int newW, newH;
        double xoff, yoff;
        unsigned int *resizedRaster, *rotatedRaster;

        R_GE_rasterRotatedSize(imageWidth, imageHeight, angle, &newW, &newH);
        R_GE_rasterRotatedOffset(imageWidth, imageHeight, angle, 0,
                                 &xoff, &yoff);

        resizedRaster = (unsigned int *) R_alloc(newW * newH, 
                                             sizeof(unsigned int));
        R_GE_rasterResizeForRotation(rasterImage, imageWidth, imageHeight, 
                                     resizedRaster, newW, newH, gc);

        rotatedRaster = (unsigned int *) R_alloc(newW * newH, 
                                                 sizeof(unsigned int));
        R_GE_rasterRotate(resizedRaster, newW, newH, angle, rotatedRaster, gc,
                          FALSE);                          
            
        /* 
         * Adjust (x, y) for resized and rotated image
         */
        x = x - (newW - imageWidth)/2 - xoff;
        y = y - (newH - imageHeight)/2 + yoff;        

        rasterImage = rotatedRaster;
        imageWidth = newW;
        imageHeight = newH;
    }

    if (invertX || invertY) {
        unsigned int *flippedRaster;

        flippedRaster = (unsigned int *) R_alloc(imageWidth * imageHeight,
                                                 sizeof(unsigned int));
        flipRaster(rasterImage, imageWidth, imageHeight, 
                   invertX, invertY, flippedRaster);
        rasterImage = flippedRaster;
    }

    image = XCreateImage(display, visual, depth,
                         ZPixmap,
                         0, /* offset */
                         /* This is just provides (at least enough)
                          * allocated memory for the image data;  
                          * each pixel is set separately below
                          */
                         (char *) rasterImage,
                         imageWidth, imageHeight,
                         depth >= 24 ? 32 : 16, /* bitmap_pad */
                         0); /* bytes_per_line: 0 means auto-calculate*/

    if (image == NULL || XInitImage(image) == 0)
        error(_("Unable to create XImage"));

    for (i = 0; i < imageHeight ;i++) {
        for (j = 0; j < imageWidth; j++) {
            pixel = i*imageWidth + j;
            XPutPixel(image, j, i, makeX11Pixel(rasterImage, pixel));
        }
    }

    XPutImage(display, xd->window, xd->wgc, 
              image, 0, 0,
              (int) x, (int) y, imageWidth, imageHeight);

    /* XFree() rather than XDestroyImage() because the latter
     * tries to free the image 'data' as well
     */
    XFree(image);

    vmaxset(vmax);
}

static SEXP X11_Cap(pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    XImage *image = XGetImage(display, xd->window, 0, 0,
                              xd->windowWidth, xd->windowHeight, 
                              AllPlanes, ZPixmap);
    SEXP raster = R_NilValue;

    if (image) {
        int i, j;
        SEXP dim;
        int size = xd->windowWidth * xd->windowHeight;
        const void *vmax = vmaxget();
        unsigned int *rint;

        PROTECT(raster = allocVector(INTSXP, size));
        
        /* Copy each byte of screen to an R matrix. 
         * The ARGB32 needs to be converted to an R ABGR32 */
        rint = (unsigned int *) INTEGER(raster);
        for (i = 0; i < xd->windowHeight; i++) {
            for (j = 0; j < xd->windowWidth; j++) {
                /* 
                 * Convert each pixel in image to an R colour
                 */
                rint[i*xd->windowWidth + j] = bitgp((void *) image, i, j);
            }
        }
        PROTECT(dim = allocVector(INTSXP, 2));
        INTEGER(dim)[0] = xd->windowHeight;
        INTEGER(dim)[1] = xd->windowWidth;
        setAttrib(raster, R_DimSymbol, dim);
    
        UNPROTECT(2);

        XDestroyImage(image);
        vmaxset(vmax);
    }

    return raster;
}

static void X11_Circle(double x, double y, double r,
		       const pGEcontext gc, pDevDesc dd)
{
    int ir, ix, iy;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    ir = (int)floor(r + 0.5);

    ix = (int)x;
    iy = (int)y;
    CheckAlpha(gc->fill, xd);
    if (R_OPAQUE(gc->fill)) {
	SetColor(gc->fill, xd);
	XFillArc(display, xd->window, xd->wgc,
		 ix-ir, iy-ir, 2*ir, 2*ir, 0, 23040);
    }
    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetLinetype(gc, xd);
	SetColor(gc->col, xd);
	XDrawArc(display, xd->window, xd->wgc,
		 ix-ir, iy-ir, 2*ir, 2*ir, 0, 23040);
    }
}

static void X11_Line(double x1, double y1, double x2, double y2,
		     const pGEcontext gc, pDevDesc dd)
{
    int xx1, yy1, xx2, yy2;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    /* In-place conversion ok */

    xx1 = (int) x1;
    yy1 = (int) y1;
    xx2 = (int) x2;
    yy2 = (int) y2;

    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetColor(gc->col, xd);
	SetLinetype(gc, xd);
	XDrawLine(display, xd->window, xd->wgc, xx1, yy1, xx2, yy2);
    }
}

static void X11_Polyline(int n, double *x, double *y,
			 const pGEcontext gc, pDevDesc dd)
{
    const void *vmax = vmaxget();
    XPoint *points;
    int i, j;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    points = (XPoint *) R_alloc(n, sizeof(XPoint));

    for(i = 0 ; i < n ; i++) {
	points[i].x = (short)(x[i]);
	points[i].y = (short)(y[i]);
    }

    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetColor(gc->col, xd);
	SetLinetype(gc, xd);
/* Some X servers need npoints < 64K */
	for(i = 0; i < n; i+= 10000-1) {
	    j = n - i;
	    j = (j <= 10000) ? j: 10000; /* allow for overlap */
	    XDrawLines(display, xd->window, xd->wgc, points+i, j,
		       CoordModeOrigin);
	}
    }

    vmaxset(vmax);
}

static void X11_Polygon(int n, double *x, double *y,
			const pGEcontext gc, pDevDesc dd)
{
    const void *vmax = vmaxget();
    XPoint *points;
    int i;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    points = (XPoint *) R_alloc(n+1, sizeof(XPoint));

    for (i = 0 ; i < n ; i++) {
	points[i].x = (short)(x[i]);
	points[i].y = (short)(y[i]);
    }
    points[n].x = (short)(x[0]);
    points[n].y = (short)(y[0]);
    CheckAlpha(gc->fill, xd);
    if (R_OPAQUE(gc->fill)) {
	SetColor(gc->fill, xd);
	XFillPolygon(display, xd->window, xd->wgc, points, n,
		     Complex, CoordModeOrigin);
    }
    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetColor(gc->col, xd);
	SetLinetype(gc, xd);
	XDrawLines(display, xd->window, xd->wgc, points, n+1, CoordModeOrigin);
    }

    vmaxset(vmax);
}


static void X11_Text(double x, double y,
		     const char *str, double rot, double hadj,
		     const pGEcontext gc, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;

    SetFont(gc, xd);
    CheckAlpha(gc->col, xd);
    if (R_OPAQUE(gc->col)) {
	SetColor(gc->col, xd);
	XRfRotDrawString(display, xd->font, rot, xd->window,
			 xd->wgc, (int)x, (int)y, str);
    }
}

static Rboolean X11_Locator(double *x, double *y, pDevDesc dd)
{
    XEvent event;
    pDevDesc ddEvent;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    caddr_t temp;
    int done = 0;

    if (xd->type > WINDOW) return 0;
#ifdef HAVE_WORKING_CAIRO
    if (xd->holdlevel > 0)
	error(_("attempt to use the locator after dev.hold()"));
    if (xd->buffered) Cairo_update(xd);
#endif
    R_ProcessX11Events((void*)NULL);	/* discard pending events */
    if(xd->type==WINDOW) XDefineCursor(display, xd->window, cross_cursor);
    XSync(display, 1);
    /* handle X events as normal until get a button */
    /* click in the desired device */
    while (!done && displayOpen) {
	XNextEvent(display, &event);
	/* possibly later R_CheckUserInterrupt(); */
	if (event.type == ButtonPress) {
	    XFindContext(display, event.xbutton.window,
			 devPtrContext, &temp);
	    ddEvent = (pDevDesc) temp;
	    if (ddEvent == dd) {
		if (event.xbutton.button == Button1) {
		    int useBeep = asLogical(GetOption1(install("locatorBell")));
		    *x = event.xbutton.x;
		    *y = event.xbutton.y;
		       /* Make a beep! Was print "\07", but that
			  messes up some terminals. */
		    if(useBeep) XBell(display, X_BELL_VOLUME);
		    XSync(display, 0);
		    done = 1;
		}
		else
		    done = 2;
	    }
	}
	else
	    handleEvent(event);
    }
    /* In case it got closed asynchronously, PR#14872 */
    if (!displayOpen) return 0;
    /* if it was a Button1 succeed, otherwise fail */
    if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor);
    XSync(display, 0);
    return (done == 1);
}

static int translate_key(KeySym keysym)
{
    if ((keysym >= XK_F1) && (keysym <= XK_F12))
    	return knF1 + (int)keysym - XK_F1;
    else {
    	switch(keysym) {
	case XK_Left: return knLEFT;
	case XK_Up:   return knUP;
	case XK_Right:return knRIGHT;
	case XK_Down: return knDOWN;
	case XK_Page_Up: 	return knPGUP;
	case XK_Page_Down: 	return knPGDN;
	case XK_End:  return knEND;
	case XK_Begin:return knHOME;
	case XK_Insert:  	return knINS;
	}
    }
    return knUNKNOWN;
}

static void X11_eventHelper(pDevDesc dd, int code)
{
    XEvent event;
    pDevDesc ddEvent;
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    caddr_t temp;
    int done = 0;

    if (xd->type > WINDOW) return;
    if (code == 1) {
    	R_ProcessX11Events((void*)NULL);	/* discard pending events */
    	if (isEnvironment(dd->eventEnv)) {
    	    SEXP prompt = findVar(install("prompt"), dd->eventEnv);
    	    if (isString(prompt) && length(prompt) == 1) {
    		 PROTECT(prompt);
    		 XStoreName(display, xd->window, CHAR(asChar(prompt)));
    		 UNPROTECT(1);
    	    } else 
    	    	XStoreName(display, xd->window, "");
    	}
    	XSync(display, 1);
    } else if (code == 2) {
	XNextEvent(display, &event);
	if (event.type == ButtonRelease || event.type == ButtonPress || event.type == MotionNotify) {
	    XFindContext(display, event.xbutton.window,
			 devPtrContext, &temp);
	    ddEvent = (pDevDesc) temp;
	    if (ddEvent == dd && dd->gettingEvent) {
		if (event.type == MotionNotify) { /* Because of PointerMotionHintMask, need to update */
		    Window root, child;
		    int rootX, rootY, winX, winY;
		    unsigned int mask;
		    if (!XQueryPointer(display, event.xbutton.window,
                                      &root, &child, &rootX, &rootY,
				      &winX, &winY, &mask)) {
			done = 1;
		    } else {
			event.xbutton.x = winX;
			event.xbutton.y = winY;
		    }
		}
		if (!done) {
        	    doMouseEvent(dd, event.type == ButtonRelease ? meMouseUp :
        	                 event.type == ButtonPress ? meMouseDown : meMouseMove, 
        	                 event.xbutton.button, event.xbutton.x, event.xbutton.y);
                    XSync(display, 0);
                    done = 1;
		}
    	    }
	} else if (event.type == KeyPress) {
	    char keybuffer[13] = "";
	    char *keystart=keybuffer;
	    XComposeStatus compose;
  	    KeySym keysym;
	    int keycode;
	    if (event.xkey.state & ControlMask) {
	    	keystart += 5; 
	    	sprintf(keybuffer, "ctrl-"); /* report control keys using labels like "ctrl-A" */
	    	event.xkey.state &= !ControlMask;
	    	event.xkey.state |= ShiftMask;
	    }
      	    XLookupString(&event.xkey, keystart, 
			  sizeof(keybuffer)-(int)(keystart-keybuffer), 
			  &keysym, &compose);
      	    /* Rprintf("keysym=%x\n", keysym); */
      	    if ((keycode = translate_key(keysym)) > knUNKNOWN)
      	    	doKeybd(dd, keycode, NULL);
      	    else if (*keystart)
	    	doKeybd(dd, knUNKNOWN, keybuffer);
	    done = 1;
	}
	if (!done) 
	    handleEvent(event);
    } else if (code == 0) {
	/* Restore the default title */
	if (ndevNumber(dd) == curDevice())
	    X11_Activate(dd);
	else
	    X11_Deactivate(dd);  
    }

    return;
}

	/********************************************************/
	/* device_Mode is called whenever the graphics engine	*/
	/* starts drawing (mode=1) or stops drawing (mode=0)	*/
	/* the device is not required to do anything		*/
	/********************************************************/

static void X11_Mode(int mode, pDevDesc dd)
{
    pX11Desc xd = (pX11Desc) dd->deviceSpecific;
    if(xd->holdlevel > 0) {
#ifdef HAVE_WORKING_CAIRO
	if(mode == 0 && xd->buffered > 1)
	    xd->last_activity = currentTime();
#endif
	return;
    }
    if(mode == 1) {
	if(xd->type==WINDOW) XDefineCursor(display, xd->window, watch_cursor);
	XSync(display, 0);
    }
    if(mode == 0) {
#ifdef HAVE_WORKING_CAIRO
	if(xd->buffered > 1) {
	    xd->last_activity = currentTime();
	    if((currentTime() - xd->last) > 0.5 /* 5*xd->update_interval */)
		Cairo_update(xd);
	    return;
	}
	if(xd->buffered) {
	    cairo_paint(xd->xcc);
	    cairo_surface_flush(xd->xcs);
	}
	
#endif
	if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor);
	XSync(display, 0);
    }
}


	/*  X11 Device Driver Arguments	:	*/
	/*	1) display name			*/
	/*	2) width (inches)		*/
	/*	3) height (inches)		*/
	/*	4) base pointsize		*/
	/*	5) gamma correction factor	*/
	/*	6) colormodel,			*/
	/*	 see X_COLORTYPE at top of file */
	/*	7) maxcube			*/

Rboolean X11DeviceDriver(pDevDesc dd,
			 const char *disp_name,
			 double width,
			 double height,
			 double pointsize,
			 double gamma_fac,
			 X_COLORTYPE colormodel,
			 int maxcube,
			 int bgcolor,
			 int canvascolor,
			 SEXP sfonts,
			 int res,
			 int xpos, int ypos,
			 const char *title,
			 int useCairo,
			 int antialias,
			 const char *family)
{
    pX11Desc xd;
    const char *fn;

    xd = Rf_allocX11DeviceDesc(pointsize);
    if(!xd) return FALSE;
    xd->bg = bgcolor;
#ifdef HAVE_WORKING_CAIRO
    xd->useCairo = useCairo != 0;
    xd->buffered = 0;
    switch(useCairo) {
    case 0: break; /* Xlib */
    case 1: xd->buffered = 1; break; /* cairo */
    case 2: xd->buffered = 0; break; /* nbcairo */
    case 3: xd->buffered = 2; break; /* dbcairo */
    default:
	warning("that type is not supported on this platform - using \"nbcairo\"");
	xd->buffered = 0;
    }
    if(useCairo) {
	switch(antialias){
	case 1: xd->antialias = CAIRO_ANTIALIAS_DEFAULT; break;
	case 2: xd->antialias = CAIRO_ANTIALIAS_NONE; break;
	case 3: xd->antialias = CAIRO_ANTIALIAS_GRAY; break;
	case 4: xd->antialias = CAIRO_ANTIALIAS_SUBPIXEL; break;
	}
    }
#else
    /* Currently this gets caught at R level */
    if(useCairo) {
	warning("cairo-based types are not supported on this build - using \"Xlib\"");
	useCairo = FALSE;
    }
#endif

    if(!useCairo) {
	if(strlen(fn = CHAR(STRING_ELT(sfonts, 0))) > 499) {
	    strcpy(xd->basefontfamily, fontname);
	    strcpy(xd->fontfamily, fontname);
	} else {
	    strcpy(xd->basefontfamily, fn);
	    strcpy(xd->fontfamily, fn);
	}
	if(strlen(fn = CHAR(STRING_ELT(sfonts, 1))) > 499)
	    strcpy(xd->symbolfamily, symbolname);
	else strcpy(xd->symbolfamily, fn);
    } else strcpy(xd->basefontfamily, family);

    /*	Start the Device Driver and Hardcopy.  */

    strncpy(xd->title, title, 100);
    xd->title[100] = '\0';

#ifdef HAVE_WORKING_CAIRO
    {
	SEXP timeouts = GetOption1(install("X11updates"));
	double tm = asReal(timeouts);
	xd->update_interval = (ISNAN(tm) || tm < 0) ? 0.10 : tm;
    }
#endif

    if (!X11_Open(dd, xd, disp_name, width, height,
		  gamma_fac, colormodel, maxcube, bgcolor,
		  canvascolor, res, xpos, ypos)) {
	free(xd);
	return FALSE;
    }

    Rf_setX11DeviceData(dd, gamma_fac, xd);
    xd->fill = 0xffffffff; /* this is needed to ensure that the
			      first newpage does set whitecolor
			      if par("bg") is not transparent */

#if BUG
    R_ProcessX11Events((void*) NULL);
#endif

    return TRUE;
}

/**
  This fills the general device structure (dd) with the X-specific
  methods/functions. It also specifies the current values of the
  dimensions of the device, and establishes the fonts, line styles, etc.
 */
int
Rf_setX11DeviceData(pDevDesc dd, double gamma_fac, pX11Desc xd)
{
    double ps = xd->pointsize;
    int res0 = (xd->res_dpi > 0) ? xd->res_dpi : 72;
    /*	Set up Data Structures. */

#ifdef HAVE_WORKING_CAIRO
    if(xd->useCairo) {
	dd->newPage = Cairo_NewPage;
	dd->clip = Cairo_Clip;
	dd->rect = Cairo_Rect;
	dd->circle = Cairo_Circle;
	dd->line = Cairo_Line;
	dd->polyline = Cairo_Polyline;
	dd->polygon = Cairo_Polygon;
        dd->path = Cairo_Path;
        dd->raster = Cairo_Raster;
        dd->cap = Cairo_Cap;
	dd->hasTextUTF8 = TRUE;
	dd->wantSymbolUTF8 = TRUE;
#ifdef HAVE_PANGOCAIRO
	dd->metricInfo = PangoCairo_MetricInfo;
	dd->strWidth = dd->strWidthUTF8 = PangoCairo_StrWidth;
	dd->text = dd->textUTF8 = PangoCairo_Text;
#else
	dd->metricInfo = Cairo_MetricInfo;
	dd->strWidth = dd->strWidthUTF8 = Cairo_StrWidth;
	dd->text = dd->textUTF8 = Cairo_Text;
#endif
	dd->holdflush = Cairo_holdflush;
	dd->haveTransparency = 2;
	dd->haveTransparentBg = 3;
	dd->haveRaster = 2;
	dd->haveCapture = (xd->type > WINDOW) ? 1 : 2;
	dd->haveLocator = (xd->type > WINDOW) ? 1 : 2;
    } else
#endif
    {
	dd->newPage = X11_NewPage;
	dd->clip = X11_Clip;
	dd->strWidth = X11_StrWidth;
	dd->text = X11_Text;
	dd->rect = X11_Rect;
        dd->path = X11_Path;
        dd->raster     = X11_Raster;
        dd->cap        = X11_Cap;
	dd->circle = X11_Circle;
	dd->line = X11_Line;
	dd->polyline = X11_Polyline;
	dd->polygon = X11_Polygon;
	dd->metricInfo = X11_MetricInfo;
	dd->hasTextUTF8 = FALSE;
    	dd->eventHelper = X11_eventHelper;
    	dd->canGenMouseDown = TRUE;
	dd->canGenMouseUp = TRUE;
	dd->canGenMouseMove = TRUE;
	dd->canGenKeybd = TRUE;

	dd->haveTransparency = 1;
	dd->haveTransparentBg = 2;
	dd->haveRaster = 3;
	dd->haveCapture = (xd->type > WINDOW) ? 1 : 2;
	dd->haveLocator = (xd->type > WINDOW) ? 1 : 2;
    }

    dd->activate = X11_Activate;
    dd->close = X11_Close;
    dd->deactivate = X11_Deactivate;
    dd->size = X11_Size;
    dd->locator = X11_Locator;
    dd->mode = X11_Mode;
    dd->useRotatedTextInContour = FALSE;

    /* Set required graphics parameters. */

    /* Window Dimensions in Pixels */
    /* Initialise the clipping rect too */

    dd->left = dd->clipLeft = 0;			/* left */
    dd->right = dd->clipRight = xd->windowWidth;	/* right */
    dd->bottom = dd->clipBottom = xd->windowHeight;	/* bottom */
    dd->top = dd->clipTop = 0;			/* top */

    /* Nominal Character Sizes in Pixels */
    /* Recommendation from 'R internals': changed for 2.7.0 */
    /* Inches per raster unit */

    /* ps is in points, we want this in device units */
    if(xd->type == PNG || xd->type == JPEG ||
       xd->type == BMP || xd->type == TIFF) {
	dd->cra[0] = 0.9*ps * res0/72.0;
	dd->cra[1] = 1.2*ps * res0/72.0;
	dd->ipr[0] =  dd->ipr[1] = 1.0/res0;
	xd->lwdscale = res0/96.0;
    } else if(xd->type >= SVG) { /* SVG, PDF, PS -- unused */
	/* Device units are bp */
	dd->cra[0] = 0.9*ps;
	dd->cra[1] = 1.2*ps;
	dd->ipr[0] =  dd->ipr[1] = 1.0/72.0;
	xd->lwdscale = 1.0/96.0;
    } else {
	dd->cra[0] = 0.9*ps * 1.0/(72.0*pixelWidth());
	dd->cra[1] = 1.2*ps * 1.0/(72.0*pixelHeight());
	dd->ipr[0] = pixelWidth();
	dd->ipr[1] = pixelHeight();
	xd->lwdscale = 1.0/(96.0*pixelWidth());
#ifdef HAVE_WORKING_CAIRO
	if(xd->useCairo) {
# ifdef HAVE_PANGOCAIRO
	    /* Pango's default resolution is 96 dpi */
	    ps *= 1.0/(96.0*pixelWidth());
# else
	    /* Cairo's default resolution is 72 dpi */
	    ps *= 1.0/(72.0*pixelWidth());
# endif
	}
#endif
    }

    /* Character Addressing Offsets */
    /* These are used to plot a single plotting character */
    /* so that it is exactly over the plotting point */

    dd->xCharOffset = 0.4900;
    dd->yCharOffset = 0.3333;
    dd->yLineBias = 0.2;


    /* Device capabilities */

    dd->canClip = TRUE;
#ifdef HAVE_WORKING_CAIRO
    dd->canHAdj = xd->useCairo ? 2 : 0;
#else
    dd->canHAdj = 0;
#endif
    dd->canChangeGamma = FALSE;

    dd->startps = ps;
    xd->fontscale = 1.0;
    dd->startcol = xd->col;
    dd->startfill = xd->fill;
    dd->startlty = LTY_SOLID;
    dd->startfont = 1;
    dd->startgamma = gamma_fac;

    /* initialise x11 device description */
    /* (most of the work has been done in X11_Open) */
    xd->resize = 0;

    dd->deviceSpecific = (void *) xd;

    dd->displayListOn = TRUE;

    return TRUE;
}


/**
 This allocates an X11Desc instance  and sets its default values.
 */
pX11Desc Rf_allocX11DeviceDesc(double ps)
{
    pX11Desc xd;
    /* allocate new device description */
    if (!(xd = (pX11Desc)calloc(1, sizeof(X11Desc))))
	return NULL;

    /* From here on, if we need to bail out with "error", */
    /* then we must also free(xd). */

    /*	Font will load at first use.  */

    if (ps < 6 || ps > 24) ps = 12;
    xd->fontface = -1;
    xd->fontsize = -1;
    xd->pointsize = ps;
    xd->handleOwnEvents = FALSE;
    xd->window = (Window) NULL;

    return xd;
}


static
Rboolean in_R_GetX11Image(int d, void *pximage, int *pwidth, int *pheight)
{
    SEXP dev = elt(findVar(install(".Devices"), R_BaseEnv), d);

    if (TYPEOF(dev) != STRSXP ||
	!(strcmp(CHAR(STRING_ELT(dev, 0)), "XImage") == 0 ||
	  strncmp(CHAR(STRING_ELT(dev, 0)), "PNG", 3) == 0 ||
	  strncmp(CHAR(STRING_ELT(dev, 0)), "X11", 3) == 0))
	return FALSE;
    else {
	pX11Desc xd = GEgetDevice(d)->dev->deviceSpecific;

	*((XImage**) pximage) =
	    XGetImage(display, xd->window, 0, 0,
		      xd->windowWidth, xd->windowHeight,
		      AllPlanes, ZPixmap);
	*pwidth = xd->windowWidth;
	*pheight = xd->windowHeight;
	return TRUE;
    }
}

/**
   Allows callers to retrieve the current Display setting for the process.
 */
Display*
Rf_getX11Display(void)
{
    return(display);
}


/**
 Allows the caller to register the X11 Display object for the process.
 Typically this will be done when the first X device is created, but this allows
 other code to generate the Display object and then register it with the R graphics
 engine.
 In addition to providing the Display, the caller should also give the default value for the
 gamma factor and also the colormodel and color cube size. See the documentation for the x11()
 function.
 Finally, setHandlers controls whether the code establishes handlers for the X errors.
 */
int
Rf_setX11Display(Display *dpy, double gamma_fac, X_COLORTYPE colormodel,
		 int maxcube, Rboolean setHandlers)
{
/*    static int alreadyDone = 0;
    if(alreadyDone) return(TRUE);
    alreadyDone = 1; */
    display = dpy;

/* Note: this sets a global gamma, not just for the current device */
#define SETGAMMA
#ifdef SETGAMMA
    RedGamma   = gamma_fac;
    GreenGamma = gamma_fac;
    BlueGamma  = gamma_fac;
#endif
    screen = DefaultScreen(display);
    rootwin = DefaultRootWindow(display);
    depth = DefaultDepth(display, screen);
    visual = DefaultVisual(display, screen);
    colormap = DefaultColormap(display, screen);
    Vclass = visual->class;
    model = colormodel;
    maxcubesize = maxcube;
    SetupX11Color();
    devPtrContext = XUniqueContext();
    displayOpen = TRUE;
    /* set error handlers */
    if(setHandlers == TRUE) {
	XSetErrorHandler(R_X11Err);
	XSetIOErrorHandler(R_X11IOErr);
    }

    return(TRUE);
}

typedef Rboolean (*X11DeviceDriverRoutine)(pDevDesc, char*,
					   double, double, double, double,
					   X_COLORTYPE, int, int);

static void
Rf_addX11Device(const char *display, double width, double height, double ps,
		double gamma, int colormodel, int maxcubesize,
		int bgcolor, int canvascolor, const char *devname, SEXP sfonts,
		int res, int xpos, int ypos, const char *title,
		int useCairo, int antialias, const char * family, SEXP call)
{
    pDevDesc dev = NULL;
    pGEDevDesc dd;

    R_GE_checkVersionOrDie(R_GE_version);
    R_CheckDeviceAvailable();
    BEGIN_SUSPEND_INTERRUPTS {
	/* Allocate and initialize the device driver data */
	if (!(dev = (pDevDesc) calloc(1, sizeof(DevDesc)))) return;
	if (!X11DeviceDriver(dev, display, width, height,
			     ps, gamma, colormodel, maxcubesize,
			     bgcolor, canvascolor, sfonts, res,
			     xpos, ypos, title, useCairo, antialias, family)) {
	    free(dev);
	    errorcall(call, _("unable to start device %s"), devname);
	}
	dd = GEcreateDevDesc(dev);
	GEaddDevice2(dd, devname);
    } END_SUSPEND_INTERRUPTS;
}

static SEXP in_do_X11(SEXP call, SEXP op, SEXP args, SEXP env)
{
    const char *display, *cname, *devname, *title, *family;
    const void *vmax;
    double height, width, ps, gamma;
    int colormodel, maxcubesize, bgcolor, canvascolor, res, xpos, ypos,
	useCairo, antialias;
    SEXP sc, sfonts;

    checkArity(op, args);
    vmax = vmaxget();

    if(R_isForkedChild)
	error("a forked child should not open a graphics device");

    /* Decode the arguments */
    display = CHAR(STRING_ELT(CAR(args), 0)); args = CDR(args);
    width = asReal(CAR(args));	args = CDR(args);
    height = asReal(CAR(args)); args = CDR(args);
    if (width <= 0 || height <= 0)
	errorcall(call, _("invalid 'width' or 'height'"));
    ps = asReal(CAR(args)); args = CDR(args);
    gamma = asReal(CAR(args)); args = CDR(args);
    if (gamma < 0 || gamma > 100)
	errorcall(call, _("invalid '%s' value"), "gamma");

    if (!isValidString(CAR(args)))
	error(_("invalid colortype passed to X11 driver"));
    cname = CHAR(STRING_ELT(CAR(args), 0));
    if (strcmp(cname, "mono") == 0)
	colormodel = 0;
    else if (strcmp(cname, "gray") == 0 || strcmp(cname, "grey") == 0)
	colormodel = 1;
    else if (strcmp(cname, "pseudo.cube") == 0)
	colormodel = 2;
    else if (strcmp(cname, "pseudo") == 0)
	colormodel = 3;
    else if (strcmp(cname, "true") == 0)
	colormodel = 4;
    else {
	warningcall(call,
		    _("unknown X11 color/colour model -- using monochrome"));
	colormodel = 0;
    }
    args = CDR(args);
    maxcubesize = asInteger(CAR(args));
    if (maxcubesize < 1 || maxcubesize > 256)
	maxcubesize = 256;
    args = CDR(args);
    sc = CAR(args);
    if (!isString(sc) && !isInteger(sc) && !isLogical(sc) && !isReal(sc))
	errorcall(call, _("invalid '%s' value"), "bg");
    bgcolor = RGBpar(sc, 0);
    args = CDR(args);
    sc = CAR(args);
    if (!isString(sc) && !isInteger(sc) && !isLogical(sc) && !isReal(sc))
	errorcall(call, _("invalid '%s' value"), "canvas");
    canvascolor = RGBpar(sc, 0);
    args = CDR(args);
    sfonts = CAR(args);
    if (!isString(sfonts) || LENGTH(sfonts) != 2)
	errorcall(call, _("invalid '%s' value"), "fonts");
    args = CDR(args);
    res = asInteger(CAR(args));
    args = CDR(args);
    xpos = asInteger(CAR(args));
    args = CDR(args);
    ypos = asInteger(CAR(args));
    args = CDR(args);
    sc = CAR(args);
    if (!isString(sc) || LENGTH(sc) != 1)
	errorcall(call, _("invalid '%s' value"), "title");
    title = CHAR(STRING_ELT(sc, 0));
    args = CDR(args);
    useCairo = asInteger(CAR(args));
    if (useCairo == NA_INTEGER)
	errorcall(call, _("invalid '%s' value"), "type");
    args = CDR(args);
    antialias = asInteger(CAR(args));
    if (antialias == NA_INTEGER)
	errorcall(call, _("invalid '%s' value"), "antialias");
    args = CDR(args);
    sc = CAR(args);
    if (!isString(sc) || LENGTH(sc) != 1)
	errorcall(call, _("invalid '%s' value"), "family");
    family = CHAR(STRING_ELT(sc, 0));


    if (!strncmp(display, "png::", 5)) devname = "PNG";
    else if (!strncmp(display, "jpeg::", 6)) devname = "JPEG";
    else if (!strncmp(display, "tiff::", 6)) devname = "TIFF";
    else if (!strncmp(display, "bmp::", 5)) devname = "BMP";
    else if (!strcmp(display, "XImage")) devname = "XImage";
    else if (useCairo) devname = "X11cairo";
    else devname = "X11";

    Rf_addX11Device(display, width, height, ps, gamma, colormodel,
		    maxcubesize, bgcolor, canvascolor, devname, sfonts,
		    res, xpos, ypos, title, useCairo, antialias, family, call);
    vmaxset(vmax);
    return R_NilValue;
}


#ifdef HAVE_WORKING_CAIRO
static int stride;
static unsigned int Sbitgp(void *xi, int x, int y)
{
    unsigned int *data = xi;
    return data[x*stride+y] | 0xFF000000; /* force opaque */
}


/* savePlot(filename, type, device) */
static SEXP in_do_saveplot(SEXP call, SEXP op, SEXP args, SEXP env)
{
    int devNr;
    const char *fn, *type;
    pGEDevDesc gdd;
    pX11Desc xd;

    checkArity(op, args);
    if (!isString(CAR(args)) || LENGTH(CAR(args)) < 1)
	error(_("invalid '%s' argument"), "filename");
    fn = R_ExpandFileName(translateChar(STRING_ELT(CAR(args), 0)));
    if (!isString(CADR(args)) || LENGTH(CADR(args)) < 1)
	error(_("invalid '%s' argument"), "type");
    type = CHAR(STRING_ELT(CADR(args), 0));
    devNr = asInteger(CADDR(args));
    if (devNr == NA_INTEGER) error(_("invalid '%s' argument"), "device");
    gdd = GEgetDevice(devNr - 1); /* 0-based */
    if (!gdd->dirty) error(_("no plot on device to save"));
    xd = gdd->dev->deviceSpecific;
    if (!xd->cs || !xd->useCairo) error(_("not an open X11cairo device"));
    if (streql(type, "png")) {
	cairo_status_t res = cairo_surface_write_to_png(xd->cs, fn);
	if (res != CAIRO_STATUS_SUCCESS)
	    error("cairo error '%s'", cairo_status_to_string(res));
    }
    else if (streql(type, "jpeg")) {
	void *xi = cairo_image_surface_get_data(xd->cs);
	FILE *fp = R_fopen(fn, "w");
	if (!fp) error(_("cannot open file '%s'"), fn);
	stride = xd->windowWidth;
	R_SaveAsJpeg(xi, xd->windowWidth, xd->windowHeight,
		     Sbitgp, 0, 75, fp, 0);
	fclose(fp);
    } else if (streql(type, "tiff")) {
	void *xi = cairo_image_surface_get_data(xd->cs);
	stride = xd->windowWidth;
	R_SaveAsTIFF(xi, xd->windowWidth, xd->windowHeight,
		     Sbitgp, 0, fn, 0, 1L);
    } else
	error(_("invalid '%s' argument"), "type");
    return R_NilValue;
}
#else
static SEXP in_do_saveplot(SEXP call, SEXP op, SEXP args, SEXP env)
{
    error(_("savePlot() is not supported on this build"));
    return R_NilValue;
}
#endif


static int in_R_X11_access(void)
{
    char *p;
    X11IOhandler old;

    if (displayOpen) return TRUE;
    if(!(p = getenv("DISPLAY"))) return FALSE;
    /* Bill Dunlap sees an error when tunneling to a non-existent
       X11 connection that BDR cannot reproduce.  We leave a handler set
       if we get an error, but that is rare.
    */
    old = XSetIOErrorHandler(R_X11IOErrSimple);
    if ((display = XOpenDisplay(NULL)) == NULL) {
	XSetIOErrorHandler(old);
	return FALSE;
    } else {
	XCloseDisplay(display);
	XSetIOErrorHandler(old);
	return TRUE;
    }
}

static Rboolean in_R_X11readclp(Rclpconn this, char *type)
{
    Window clpwin;
    Atom sel = XA_PRIMARY, pty, pty_type;
    XEvent evt;
    unsigned char *buffer;
    unsigned long pty_size, pty_items;
    int pty_format, ret;
    Rboolean res = TRUE;

    if (!displayOpen) {
	if ((display = XOpenDisplay(NULL)) == NULL) {
	    warning(_("unable to contact X11 display"));
	    return FALSE;
	}
    }
    if(strcmp(type, "X11_secondary") == 0) sel = XA_SECONDARY;
    if(strcmp(type, "X11_clipboard") == 0)
#ifdef HAVE_X11_Xmu
      sel = XA_CLIPBOARD(display);
#else
      error("X11 clipboard selection is not supported on this system");
#endif

    pty = XInternAtom(display, "RCLIP_READ", False);

    clpwin = XCreateSimpleWindow(display, DefaultRootWindow(display),
				 0, 0, 1, 1, 0, 0, 0);
    /* <FIXME> this is not optimal in a UTF-8 locale.
       What we should do is see if UTF-8 extensions are available
       (via X_HAVE_UTF8_STRING) then ask with target TARGETS and see if
       UTF8_STRING is available.  See
       http://www.pps.jussieu.fr/~jch/software/UTF8_STRING/UTF8_STRING.text
    */

    /* send a selection request */
    ret = XConvertSelection(display, sel, XA_STRING, pty, clpwin, CurrentTime);

    /* wait for the response */
    while(1) {
	XNextEvent(display, &evt);
	if (evt.type == SelectionNotify) break;
    }

    /* find the size and format of the data in the selection */
    ret = XGetWindowProperty(display, clpwin, pty, 0, 0, False, AnyPropertyType,
			     &pty_type, &pty_format, &pty_items, &pty_size,
			     &buffer);
    if (ret) {
	warning(_("clipboard cannot be opened or contains no text"));
	res = FALSE;
    } else {
	XFree(buffer);
	if (pty_format != 8) { /* bytes */
	    warning(_("clipboard cannot be opened or contains no text"));
	    res = FALSE;
	} else { /* read the property */
	    ret = XGetWindowProperty(display, clpwin, pty, 0, (long)pty_size, False,
				     AnyPropertyType, &pty_type, &pty_format,
				     &pty_items, &pty_size, &buffer);
	    if (ret) {
		warning(_("clipboard cannot be read (error code %d)"), ret);
		res = FALSE;
	    } else {
		this->buff = (char *)malloc(pty_items + 1);
		this->last = this->len = (int) pty_items;
		if(this->buff) {
		    /* property always ends in 'extra' zero byte */
		    memcpy(this->buff, buffer, pty_items + 1);
		} else {
		    warning(_("memory allocation to copy clipboard failed"));
		    res = FALSE;
		}
		XFree(buffer);
	    }
	}
    }
    
    XDeleteProperty(display, clpwin, pty);
    if (!displayOpen) {
	XCloseDisplay(display);
	strcpy(dspname, "");
    }
    return res;
}

#include <R_ext/Rdynload.h>

extern const char * in_R_pngVersion(void);
extern const char * in_R_jpegVersion(void);
extern const char * in_R_tiffVersion(void);

void R_init_R_X11(DllInfo *info)
{
    R_X11Routines *tmp;
    tmp = (R_X11Routines*) malloc(sizeof(R_X11Routines));
    if(!tmp) {
	error(_("cannot allocate memory for X11Routines structure"));
	return;
    }
    tmp->X11 = in_do_X11;
    tmp->saveplot = in_do_saveplot;
    tmp->image = in_R_GetX11Image;
    tmp->access = in_R_X11_access;
    tmp->readclp = in_R_X11readclp;
    tmp->R_pngVersion = in_R_pngVersion;
    tmp->R_jpegVersion = in_R_jpegVersion;
    tmp->R_tiffVersion = in_R_tiffVersion;
    R_setX11Routines(tmp);
}
