/* Hello, Emacs, this is -*-C-*-
 * $Id: ggi.trm,v 1.27 2008/06/26 23:12:17 sfeam Exp $
 */

/* GNUPLOT - ggi.trm */

/*[
 * Copyright 2000, 2004
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
 ]*/

/*
 * AUTHOR:
 *   Cesar Crusius <crusius@leland.stanford.edu>
 *   event / mouse processing & double-buffering
 *   by Johannes Zellner <johannes@zellner.org>
 *   pm3d support by Johannes Zellner <johannes@zellner.org> (Oct. 2000)
 *
 *   TODO:
 *
 *   	- reimplement wmh (if it's available)
 *   	- implement window title using wmh (if it's available)
 *   	- check for availability of two frames, and if not
 *   	  do it with one frame. (will eventually not do
 *   	  with mouse/event reporting)
 *   	- check if libxmi is available and if so, use
 *   	  it to draw filled polygons.
 *   	- enable cursors using blits.
 */

#include "driver.h"

#ifdef TERM_REGISTER
register_term(ggi)
#endif

#ifdef TERM_PROTO

#include <ggi/ggi.h>
#ifdef USE_MOUSE
#  include <ggi/ggi-unix.h>
#endif

#ifdef HAVE_GGI_WMH_H
#   include <ggi/wmh.h>
static TBOOLEAN GGI_use_whm = 0;
#endif

#if 1
#if defined(HAVE_GGI_XMI_H)
#   define ENABLE_XMI 1
#endif
#endif

#ifdef ENABLE_XMI
#   include <ggi/xmi.h>
#endif

static void GGI_line_colors __PROTO((void));
TERM_PUBLIC void GGI_graphics __PROTO((void));
TERM_PUBLIC void GGI_set_size __PROTO((void));
TERM_PUBLIC void GGI_init __PROTO((void));
TERM_PUBLIC void GGI_linetype __PROTO((int));
TERM_PUBLIC void GGI_move __PROTO((unsigned int,unsigned int));
TERM_PUBLIC void GGI_options __PROTO((void));
TERM_PUBLIC void GGI_put_text __PROTO((unsigned int,unsigned int, const char*));
TERM_PUBLIC void GGI_suspend __PROTO((void));
TERM_PUBLIC void GGI_resume __PROTO((void));
TERM_PUBLIC void GGI_fillbox __PROTO((int style, unsigned int x, unsigned int y,
	unsigned int w, unsigned int h));
TERM_PUBLIC void GGI_close __PROTO((void));
TERM_PUBLIC void GGI_reset __PROTO((void));
TERM_PUBLIC void GGI_text __PROTO((void));
TERM_PUBLIC void GGI_vector __PROTO((unsigned int,unsigned int));

#ifdef USE_MOUSE

/* zoom box information */

typedef struct  {
    int x;
    int y;
} GGI_point_t;

typedef struct {
    int x;
    int y;
    int width;
    int height;
    char str[0xff];
} GGI_vertex_t;

TERM_PUBLIC long int GGI_SetTime(const struct timeval* current);
TERM_PUBLIC int GGI_from_keysym __PROTO((uint32_t keysym));
TERM_PUBLIC int GGI_from_button __PROTO((uint32_t button));
TERM_PUBLIC int GGI_y __PROTO((int32_t y));
TERM_PUBLIC int GGI_dispatch_event __PROTO((const ggi_event* event));
TERM_PUBLIC int GGI_eventually_update_modifiers __PROTO((const ggi_event* event, const int add));
TERM_PUBLIC int GGI_waitforinput __PROTO((void));
TERM_PUBLIC void GGI_draw_ruler __PROTO((void));
TERM_PUBLIC void GGI_clear_zoombox __PROTO((void));
TERM_PUBLIC void GGI_draw_zoombox __PROTO((void));
TERM_PUBLIC void GGI_set_ruler __PROTO((int, int));
TERM_PUBLIC void GGI_set_cursor __PROTO((int, int, int));
TERM_PUBLIC void GGI_save_frame_canvas __PROTO((void));
TERM_PUBLIC void GGI_save_frame_stl __PROTO((void));
TERM_PUBLIC void GGI_replot __PROTO((void));
TERM_PUBLIC void GGI_clear __PROTO((const GGI_vertex_t* v, const int tag));
TERM_PUBLIC void GGI_save_puts __PROTO((GGI_vertex_t* v, const int tag));
TERM_PUBLIC void GGI_set_vertex __PROTO((GGI_vertex_t* v, const int x, const int y, const char* str, const int tag));
TERM_PUBLIC void GGI_abort_zooming __PROTO((void));
TERM_PUBLIC void GGI_put_tmptext __PROTO((int, const char str[]));
TERM_PUBLIC void GGI_relative __PROTO((int r[2]));
TERM_PUBLIC void GGI_clear_hline __PROTO((int x1, int x2, int y));
TERM_PUBLIC void GGI_clear_vline __PROTO((int y1, int y2, int x));
TERM_PUBLIC void GGI_draw_hline __PROTO((int x1, int x2, int y));
TERM_PUBLIC void GGI_draw_vline __PROTO((int y1, int y2, int x));
TERM_PUBLIC void GGI_set_clipboard __PROTO((const char[]));
#endif /* USE_MOUSE */

TERM_PUBLIC int GGI_make_palette __PROTO((t_sm_palette*));
TERM_PUBLIC void GGI_previous_palette __PROTO((void));
TERM_PUBLIC void GGI_set_color __PROTO((struct t_colorspec *colorspec));
#ifdef ENABLE_XMI
TERM_PUBLIC void GGI_filled_polygon __PROTO((int, gpiPoint*));
#endif

#define GOT_GGI_PROTO
#endif

#ifndef TERM_PROTO_ONLY
#ifdef TERM_BODY

#define GGI_XMAX 800
#define GGI_YMAX 600
#define GGI_VCHAR 8
#define GGI_HCHAR 8
#define GGI_VTIC 8
#define GGI_HTIC 8


#ifdef USE_MOUSE
static GGI_vertex_t GGI_zoom[2][2];
static GGI_vertex_t GGI_stl_vertex;
static GGI_point_t GGI_ruler = {-1, -1};
static GGI_point_t GGI_zoombox[2] = {{-1, -1}, {-1, -1}};

static struct timeval GGI_timestamp;
static int GGI_mouse_x = 0;
static int GGI_mouse_y = 0;
static int GGI_modifiers = 0;
static int GGI_use_mouse = 1; /* mouse is on by default */
static unsigned int GGIcanvas_height = 0;
static int GGI_font_width = 0;
static int GGI_font_height = 0;
static int GGI_saved_canvas = 0;
static int GGI_saved_stl = 0;
static int GGI_needs_update = 1;
#endif

static t_sm_palette GGI_save_pal = {
    -1, -1, -1, -1, -1, -1, -1, -1,
    (rgb_color*) 0, -1, -1
};

/* First to some global variables
 *
 * GGIvisual is our 'piece of paper.'
 * GGIborderColor and axixColor have the obvious meanings.
 * GGIcolors are the colors for linestyles 0 and up.
 * GGImap is for initializing colors.
 * GGIx,GGIy are the current coordinates.
 * GGIwidth, GGIheight are the extensions in the visual.
 * GGIymax = term->ymax
 */
static ggi_visual_t GGIvisual = (ggi_visual_t)0;
static ggi_pixel GGIborderColor;
static ggi_pixel GGIaxisColor;
static ggi_pixel GGIblack;
static ggi_pixel GGIcolors[7];
#define GGI_PM3D_COLORS 240
static const int ggi_pm3d_colors = GGI_PM3D_COLORS;
static ggi_pixel GGI_smooth_colors[GGI_PM3D_COLORS];
static unsigned int GGIx,GGIy;
static unsigned int GGIwidth, GGIheight, GGIymax;
#if 0
static unsigned int Xenv;
#endif
static int GGI_frames =
#ifdef USE_MOUSE
2
#else
1
#endif
;

#ifdef ENABLE_XMI
static miGC* GGI_miGC = (miGC*)0;
static miPaintedSet* GGI_miPaintedSet = (miPaintedSet*)0;
static miPixel GGI_miPixels[2]; /* only GGI_miPixels[1] is used */
static int GGI_numblendstages = 0;
static miBlendStage GGI_blendstages[4];
#endif

static TBOOLEAN GGI_mode_changed = 1;
static char GGI_mode_spec[0xff] = "";
static int GGI_acceleration = 7; /* arbitrary */

enum GGI_id {
    GGI_MODE,
    GGI_ACCELERATION,
    GGI_OTHER
};

static struct gen_table GGI_opts[] =
{
    { "mo$de", GGI_MODE },
    { "ac$celeration", GGI_ACCELERATION },
    { NULL, GGI_OTHER }
};

static void
GGI_line_colors()
{
    ggi_pixel GGIwhite,GGIred,GGIgreen,GGIblue,GGIcyan,GGImagenta,GGIgray;
    ggi_pixel GGIyellow;
    ggi_color color;

    color.r = 0xFFFF; color.g = 0xFFFF; color.b = 0xFFFF; GGIwhite   = ggiMapColor(GGIvisual,&color);
    color.r = 0x0000; color.g = 0x0000; color.b = 0x0000; GGIblack   = ggiMapColor(GGIvisual,&color);
    color.r = 0xFFFF; color.g = 0x0000; color.b = 0x0000; GGIred     = ggiMapColor(GGIvisual,&color);
    color.r = 0x0000; color.g = 0xFFFF; color.b = 0x0000; GGIgreen   = ggiMapColor(GGIvisual,&color);
    color.r = 0x0000; color.g = 0x0000; color.b = 0xFFFF; GGIblue    = ggiMapColor(GGIvisual,&color);
    color.r = 0x0000; color.g = 0xFFFF; color.b = 0xFFFF; GGIcyan    = ggiMapColor(GGIvisual,&color);
    color.r = 0xFFFF; color.g = 0x0000; color.b = 0xFFFF; GGImagenta = ggiMapColor(GGIvisual,&color);
    color.r = 0xFFFF; color.g = 0xFFFF; color.b = 0x0000; GGIyellow  = ggiMapColor(GGIvisual,&color);
    color.r = 0x8888; color.g = 0x8888; color.b = 0x8888; GGIgray    = ggiMapColor(GGIvisual,&color);

    GGIborderColor = GGIwhite;
    GGIaxisColor   = GGIgray;
    GGIcolors[0]   = GGIred;
    GGIcolors[1]   = GGIgreen;
    GGIcolors[2]   = GGIblue;
    GGIcolors[3]   = GGImagenta;
    GGIcolors[4]   = GGIcyan;
    GGIcolors[5]   = GGIyellow;
    GGIcolors[6]   = GGIgray;
}

/* Called bevore a graphic is displayed */
TERM_PUBLIC void GGI_graphics()
{
#ifdef USE_MOUSE
    int i, j;
    int display_frame = ggiGetDisplayFrame(GGIvisual);
#endif
#if 0
    if(!Xenv)
    {
	GGI_line_colors();
	return;
    }
#endif
    ggiSetGCForeground(GGIvisual,GGIblack);
#ifdef USE_MOUSE
    /* write to the currently not displayed buffer */
    ggiSetWriteFrame(GGIvisual, !display_frame);

    /* mark the contents of the alternate frame as invalid */
    GGI_saved_canvas = 0;
    GGI_saved_stl = 0;
    GGI_needs_update = 1;

    /* reset the stl vertex */
    GGI_stl_vertex.width = 0;

    /* reset the zoom box coordinates */
    for (i = 0; i < 2; i++) {
	for (j = 0; j < 2; j++) {
	    GGI_zoom[i][j].width = 0;
	}
	GGI_zoombox[i].x = -1;
	GGI_zoombox[i].y = -1;
    }
#endif /* USE_MOUSE */

    /* clear *write* buffer. */
    ggiDrawBox(GGIvisual, 0, 0, GGIwidth, GGIheight);

#ifdef USE_MOUSE
    if (GGI_use_mouse) {
	/* copy the contents of the currently
	 * displayed stl to the write frame.
	 * This way the stl won't jitter. */
	ggiSetReadFrame(GGIvisual, display_frame);
	ggiCopyBox(GGIvisual, 0, GGIcanvas_height,
	    GGIwidth, GGI_font_height, 0, GGIcanvas_height);
    }
#endif
}

TERM_PUBLIC void
GGI_set_size()
{
    ggi_mode mode;
    ggiGetMode(GGIvisual,&mode);

    GGIwidth  = mode.virt.x;
    GGIheight = mode.virt.y;

    term->xmax = mode.virt.x - 1;

#ifdef USE_MOUSE
    GGIcanvas_height = mode.virt.y - (GGI_use_mouse ? GGI_font_height : 0);
    term->ymax = GGIcanvas_height - 1;
#else
    term->ymax = mode.virt.y - 1;
#endif
    GGIymax = term->ymax;
}

/*
 * init
 * -----------------------
 * Called only once, when the terminal is initialized. We have to open the visual here because it
 * is during 'init' that we have to change the terminal dimensions (xmax, ymax).
 */
TERM_PUBLIC void GGI_init()
{
    int success = 0;
    ggi_mode mode;

#if 0
    if (0 != giiInit()) {
	ggiPanic("*** giiInit() failed *** \n");
    }
#endif

    if (0 != ggiInit()) {
	ggiPanic("*** ggiInit() failed *** \n");
    }

    if (NULL == (GGIvisual = ggiOpen(NULL))) {
	/* TODO: abort a bit more gracefully */
	ggiPanic("(GGI_init() unable to open default\n");
    }

    if (strlen(GGI_mode_spec)) {
	/* user specified mode */
	if (!ggiParseMode(GGI_mode_spec, &mode)) {
	    mode.frames = GGI_frames;
	    if (!ggiSetMode(GGIvisual, &mode)) {
		success = 1;
	    }
	}
    }

    if (!success) {
	/* try the default mode */
	if(ggiSetSimpleMode(GGIvisual,GGI_AUTO,GGI_AUTO,GGI_frames,GT_AUTO)) {
	    ggiPanic("(GGI_init() unable to set default mode\n");
	    GGIvisual = (ggi_visual_t)0;
	}
    }

    ggiGetMode(GGIvisual, &mode);

    /* print the mode only once if it has changed */
    if (GGI_mode_changed) {
	GGI_mode_changed = 0;
	ggiFPrintMode(stderr, &mode);
	fprintf(stderr, "\n");
    }

#ifdef USE_MOUSE
    /* must come before GGI_set_size() */
    ggiGetCharSize(GGIvisual, &GGI_font_width, &GGI_font_height);
#endif

    GGI_set_size();


#ifdef USE_MOUSE
    ggiSetReadFrame(GGIvisual, 0);
    ggiSetWriteFrame(GGIvisual, 0);
    ggiSetDisplayFrame(GGIvisual, 0);
#endif

#ifdef HAVE_GGI_WMH_H
    /* Initialize WMH extension */
    if (ggiWmhInit() != 0 || ggiWmhAttach(GGIvisual) < 0) {
	GGI_use_whm = 0;
    } else {
	GGI_use_whm = 1;
	ggiWmhAllowResize(GGIvisual, 100, 50, 2000, 2000, 10, 10);
	ggiWmhSetTitle(GGIvisual, "GGI Gnuplot Driver");
	ggiWmhSetIconTitle(GGIvisual, "Gnuplot");
    }
#endif

    /*
     * if(!(Xenv=!ggiWmhAttach(GGIvisual))) ggiWmhDetach(GGIvisual);
     * else Xenv=!ggiWmhSetTitle(GGIvisual,"GGI Gnuplot Driver");
     */
#if 0
    if(!Xenv)
    {
	/*
	 * ggiWmhDetach(GGIvisual);
	 * ggiWmhExit();
	 */
	ggiClose(GGIvisual);
	ggiExit();
	GGIvisual=NULL;
    }
#endif
    GGI_line_colors();
    ggiSetFlags(GGIvisual, GGIFLAG_ASYNC);
#ifdef USE_MOUSE
    GGI_mouse_x = 0;
    GGI_mouse_y = 0;
    GGI_modifiers = 0;
    {
	struct timeval tv;
	struct timezone tz;
	gettimeofday(&tv, &tz);
	GGI_SetTime(&tv); /* initialize time */
    }
#endif

#ifdef ENABLE_XMI
    if (0 != xmiInit()) {
	/* TODO: abort a bit more gracefully */
	ggiPanic("(GGI_init() unable to initialize xmi\n");
    }

    if (xmiAttach(GGIvisual) < 0) {
	ggiPanic("(GGI_init) Unable to attach XMI extension to visual\n");
    }

    /* miPaintedSet */
    if (GGI_miPaintedSet) {
	miDeletePaintedSet(GGIvisual, GGI_miPaintedSet);
	GGI_miPaintedSet = (miPaintedSet*)0;
    }
    GGI_miPaintedSet = miNewPaintedSet(GGIvisual);
    miClearPaintedSet(GGIvisual, GGI_miPaintedSet);

    /* miGC */
    if (GGI_miGC) {
	miDeleteGC(GGIvisual, GGI_miGC);
	GGI_miGC = (miGC*)0;
    }
    GGI_miGC = miNewGC(GGIvisual, 2,
	GGI_miPixels, GGI_numblendstages, GGI_blendstages);
#endif
    setvbuf(stdin, (char*)0, _IONBF, 0);
}

TERM_PUBLIC void GGI_linetype(int linetype)
{
    if(linetype == LT_BLACK)
	ggiSetGCForeground(GGIvisual,GGIborderColor);
    if(linetype == LT_AXIS)
	ggiSetGCForeground(GGIvisual,GGIaxisColor);
    if(linetype < 0)
	return;
    if(linetype >= 6)
	linetype%=6;
    ggiSetGCForeground(GGIvisual,GGIcolors[linetype]);
}

TERM_PUBLIC void GGI_move(unsigned int x, unsigned int y)
{
    GGIx=x;
    GGIy=GGI_y(y);
}

TERM_PUBLIC void
GGI_options()
{
    while (!END_OF_COMMAND) {
	switch(lookup_table(&GGI_opts[0], c_token)) {
	case GGI_ACCELERATION:
	{
	    int itmp;
	    struct value a;

	    c_token++;
	    itmp = (int) real(const_express(&a));
	    if (itmp < 1) {
		fprintf(stderr, "acceleration must be strictly positive!\n");
	    } else {
		GGI_acceleration = itmp;
	    }
	    break;
	}
	case GGI_MODE:
	    c_token++;
	    /* fallthru */
	default:
	    if (!END_OF_COMMAND) {
		copy_str(GGI_mode_spec, c_token, 0xfe);
		GGI_mode_changed = 1;
	    }
	    break;
	}
	c_token++;
    } /* while(command) */

    if (*GGI_mode_spec) {
	sprintf(term_options, "mode %s acceleration %d",
		GGI_mode_spec, GGI_acceleration);
    } else {
	sprintf(term_options, "acceleration %d", GGI_acceleration);
    }
}

TERM_PUBLIC void
GGI_close()
{
    ggiFlush(GGIvisual);
    /* DETACH EXTENSIONS */
#if HAVE_WMH_H
    if(GGI_use_whm) {
	ggiWmhDetach(GGIvisual);
    }
#endif
#ifdef ENABLE_XMI
    xmiDetach(GGIvisual);
#endif

    ggiClose(GGIvisual);
    GGIvisual = (ggi_visual_t)0;

    /* EXIT EXTENSIONS */
#if HAVE_WMH_H
    if(GGI_use_whm) {
	ggiWmhExit();
	GGI_use_whm = 0;
    }
#endif
#ifdef ENABLE_XMI
    xmiExit();
#endif
}

/* Called when terminal is terminated i.e.
 * when switching to another terminal. */
TERM_PUBLIC void
GGI_reset()
{
    if(GGIvisual!=NULL) {
	GGI_close();
    }
# if 0 /* not needed */
    GGI_save_pal.colorFormulae = -1; /* force later reallocation of palette */
# endif
}

TERM_PUBLIC void
GGI_put_text(unsigned int x, unsigned int y, const char *str)
{
    ggi_pixel current_foreground;
    ggiGetGCForeground(GGIvisual,&current_foreground);
    ggiSetGCForeground(GGIvisual,GGIborderColor);
    ggiPuts(GGIvisual,x,GGI_y(y) - 4 /* ? (joze ? */,str);
    ggiSetGCForeground(GGIvisual,current_foreground);
}

TERM_PUBLIC void
GGI_suspend()
{
    /* this fails on the console */
    GGI_text();
}

TERM_PUBLIC void
GGI_resume()
{
    /* do nothing */
}

TERM_PUBLIC void
GGI_fillbox(
    int style,
    unsigned int x, unsigned int y,
    unsigned int w, unsigned int h)
{
    ggiDrawBox(GGIvisual, x, GGI_y((int)(y+h)), w, h);
}

TERM_PUBLIC void
GGI_text()
{
    ggiFlush(GGIvisual);
#ifdef USE_MOUSE
    /* now display the buffer which was just written */
    ggiSetDisplayFrame(GGIvisual, ggiGetWriteFrame(GGIvisual));
    return;
#else
    /* Wait for a key to be pressed and exit graphics mode if
     * running in console mode. */
    /* TODO: return immediately, if in X */
    ggiGetc(GGIvisual);
    GGI_close();
#endif
}

TERM_PUBLIC void
GGI_vector(unsigned int x, unsigned int y)
{
    y = GGI_y(y);
    ggiDrawLine(GGIvisual,GGIx,GGIy,x,y);
    GGIx=x;
    GGIy=y;
}

#ifdef USE_MOUSE

/* translate ggi keysym to gnuplot keysym */
TERM_PUBLIC int
GGI_from_keysym(uint32_t keysym)
{
    switch (keysym) {
	case GIIUC_BackSpace:
	    return GP_BackSpace;
	case GIIUC_Tab:
	    return GP_Tab;
	case GIIUC_Linefeed:
	    return GP_Linefeed;
	case GIIK_Clear:
	    return GP_Clear;
	case GIIUC_Return:
	    return GP_Return;
	case GIIK_Pause:
	    return GP_Pause;
	case GIIK_ScrollLock:
	    return GP_Scroll_Lock;
	case GIIK_SysRq:
	    return GP_Sys_Req;
	case GIIUC_Escape:
	    return GP_Escape;
	case GIIK_Insert:
	    return GP_Insert;
	case GIIUC_Delete:
	    return GP_Delete;
	case GIIK_Home:
	    return GP_Home;
	case GIIK_Left:
	    return GP_Left;
	case GIIK_Up:
	    return GP_Up;
	case GIIK_Right:
	    return GP_Right;
	case GIIK_Down:
	    return GP_Down;
	case GIIK_PageUp:
	    return GP_PageUp;
	case GIIK_PageDown:
	    return GP_PageDown;
	case GIIK_End:
	    return GP_End;
	case GIIK_Begin:
	    return GP_Begin;
	case GIIK_PSpace:
	    return GP_KP_Space;
	case GIIK_PTab:
	    return GP_KP_Tab;
	case GIIK_PEnter:
	    return GP_KP_Enter;

	case GIIK_PF1:
	    return GP_KP_F1;
	case GIIK_PF2:
	    return GP_KP_F2;
	case GIIK_PF3:
	    return GP_KP_F3;
	case GIIK_PF4:
	    return GP_KP_F4;

#if 0
	case 1:
	    return GP_KP_Insert;    /* ~ KP_0 */
	case 1:
	    return GP_KP_End;       /* ~ KP_1 */
	case 1:
	    return GP_KP_Down;      /* ~ KP_2 */
	case 1:
	    return GP_KP_Page_Down; /* ~ KP_3 */
	case 1:
	    return GP_KP_Left;      /* ~ KP_4 */
	case 1:
	    return GP_KP_Begin;     /* ~ KP_5 */
	case 1:
	    return GP_KP_Right;     /* ~ KP_6 */
	case 1:
	    return GP_KP_Home;      /* ~ KP_7 */
	case 1:
	    return GP_KP_Up;        /* ~ KP_8 */
	case 1:
	    return GP_KP_Page_Up;   /* ~ KP_9 */
#endif

#if 0
	case GIIK_PDelete:
	    return GP_KP_Delete;
#endif
	case GIIK_PEqual:
	    return GP_KP_Equal;
	case GIIK_PAsterisk:
	    return GP_KP_Multiply;
	case GIIK_PPlus:
	    return GP_KP_Add;
	case GIIK_PSeparator:
	    return GP_KP_Separator;
	case GIIK_PMinus:
	    return GP_KP_Subtract;
	case GIIK_PDecimal:
	    return GP_KP_Decimal;
	case GIIK_PSlash:
	    return GP_KP_Divide;

	case GIIK_P0:
	    return GP_KP_0;
	case GIIK_P1:
	    return GP_KP_1;
	case GIIK_P2:
	    return GP_KP_2;
	case GIIK_P3:
	    return GP_KP_3;
	case GIIK_P4:
	    return GP_KP_4;
	case GIIK_P5:
	    return GP_KP_5;
	case GIIK_P6:
	    return GP_KP_6;
	case GIIK_P7:
	    return GP_KP_7;
	case GIIK_P8:
	    return GP_KP_8;
	case GIIK_P9:
	    return GP_KP_9;

	case GIIK_F1:
	    return GP_F1;
	case GIIK_F2:
	    return GP_F2;
	case GIIK_F3:
	    return GP_F3;
	case GIIK_F4:
	    return GP_F4;
	case GIIK_F5:
	    return GP_F5;
	case GIIK_F6:
	    return GP_F6;
	case GIIK_F7:
	    return GP_F7;
	case GIIK_F8:
	    return GP_F8;
	case GIIK_F9:
	    return GP_F9;
	case GIIK_F10:
	    return GP_F10;
	case GIIK_F11:
	    return GP_F11;
	case GIIK_F12:
	    return GP_F12;

	default:
	    /* return it untranslated */
	    return keysym;
    }
}

TERM_PUBLIC long int
GGI_SetTime(const struct timeval* current)
{
    /* --> dsec in musec */
    int dsec = (current->tv_sec - GGI_timestamp.tv_sec) * 1000000;
    /* --> dmu in millisec */
    int dmu = (current->tv_usec - GGI_timestamp.tv_usec + dsec) / 1000;

    GGI_timestamp = *current;
    return dmu;
}

TERM_PUBLIC int
GGI_from_button(uint32_t button)
{
    switch (button) {
    case GII_PBUTTON_LEFT:
	return 1;
    case GII_PBUTTON_MIDDLE:
	return 2;
    case GII_PBUTTON_RIGHT:
	return 3;
    default:
	/* should not happen */
	return 0;
    }
}

TERM_PUBLIC int
GGI_y(int32_t y)
{
    return GGIymax - y;
}

TERM_PUBLIC int
GGI_eventually_update_modifiers(const ggi_event* event, const int add)
{
    int mod = 0;
    int old_modifiers = GGI_modifiers;

    switch (event->key.sym) {
    case GIIK_Shift:
	mod = Mod_Shift;
	break;
    case GIIK_Ctrl:
	mod = Mod_Ctrl;
	break;
    case GIIK_Alt:
    case GIIK_Meta:
	mod = Mod_Alt;
	break;
    default:
	return 0;
    }

    if (add) {
	GGI_modifiers |= mod;
    } else {
	GGI_modifiers &= ~mod;
    }

    if (GGI_modifiers != old_modifiers) {

	struct gp_event_t gp_ev;

	gp_ev.type = GE_modifier;
	gp_ev.mx   = GGI_mouse_x;
	gp_ev.my   = GGI_y(GGI_mouse_y);
	gp_ev.par1 = 0;
	gp_ev.par2 = 0;
	gp_ev.par1 = GGI_modifiers;

	do_event(&gp_ev);
    }

    return 1;
}

TERM_PUBLIC int
GGI_dispatch_event(const ggi_event* event)
{
    struct gp_event_t gp_ev;

    gp_ev.type = 0;
    gp_ev.mx   = GGI_mouse_x;
    gp_ev.my   = GGI_y(GGI_mouse_y);
    gp_ev.par1 = 0;
    gp_ev.par2 = 0;

    switch (event->any.type) {

	/* [-- KEY EVENTS --] */
    case evKeyPress:
    case evKeyRepeat:
	if (GGI_eventually_update_modifiers(event, 1)) {
	    /* was just a modifier pressed */
	    return 0;
	}
	gp_ev.type = GE_keypress;
	gp_ev.par1 = GGI_from_keysym(event->key.sym);
	if ('q' == gp_ev.par1) {
	    return 'q';
	}
	break;
    case evKeyRelease:
	if (GGI_eventually_update_modifiers(event, 0)) {
	    /* was just a modifier pressed */
	    return 0;
	}
	break;

	/* [-- POINTER EVENTS --] */
    case evPtrRelative:
	/* relative motion is not implemented. Should it ? */
	/*
	 * fprintf(stderr, "%s:%d report this to <johannes@zellner.org> %d %d\n",
	 *     __FILE__, __LINE__, event->pmove.x, event->pmove.y);
	 */
	gp_ev.type  = GE_motion;
	GGI_mouse_x += GGI_acceleration * event->pmove.x;
	GGI_mouse_y += GGI_acceleration * event->pmove.y;
	break;
    case evPtrAbsolute:
	gp_ev.type  = GE_motion;
	GGI_mouse_x = event->pmove.x;
	GGI_mouse_y = event->pmove.y;
	break;
    case evPtrButtonPress:
	gp_ev.type = GE_buttonpress;
	gp_ev.par1 = GGI_from_button(event->pbutton.button);
	break;
    case evPtrButtonRelease:
	gp_ev.type = GE_buttonrelease;
	gp_ev.par1 = GGI_from_button(event->pbutton.button);
	gp_ev.par2 = GGI_SetTime(&(event->pbutton.time));
	break;
#ifdef HAVE_GGI_WMH_H
    case evCommand:
	/* [-- resizing --] */
	if (GGI_use_whm) {
	    /* fprintf(stderr, "(GGI_dispatch_event) \n"); */
	    if (event->cmd.code==GGICMD_REQUEST_SWITCH) {
		/*
		 * ggi_cmddata_switchrequest *req;
		 * req = &(event->cmd.data);
		 * ggi_resize(GGIvisual, &(req->mode));
		 */
		/*
		 * while( ggiEventPoll(GGIvisual, emAll, &tv) ) {
		 *     ggiEventRead(GGIvisual, event, emAll);
		 * }
		 */
	    }
	}
	break;
#endif
    default:
	/* fprintf(stderr, "(GGI_dispatch_event) unhandled event\n"); */
	break;
    }
    do_event(&gp_ev);
    gp_ev.type = GE_plotdone;
    do_event(&gp_ev);
    return 0;
}

/* save currently displayed frame to alternate buffer */
TERM_PUBLIC void
GGI_save_frame_canvas()
{
    if (!GGI_saved_canvas && GGIvisual) {
	int display_frame = ggiGetDisplayFrame(GGIvisual);

	/* save the currently displayed frame to alternate frame */
	ggiSetReadFrame(GGIvisual, display_frame);
	ggiSetWriteFrame(GGIvisual, !display_frame);
	ggiCopyBox(GGIvisual, 0, 0, GGIwidth, GGIcanvas_height, 0, 0);

	/* write again directly to the display frame */
	ggiSetWriteFrame(GGIvisual, display_frame);

	/* remember that the alternate frame is valid */
	GGI_saved_canvas = 1;
    }
}

TERM_PUBLIC void
GGI_save_frame_stl()
{
    if (!GGI_saved_stl) {
	int display_frame = ggiGetDisplayFrame(GGIvisual);

	/* clear the stl part of the alternate buffer */
	ggiSetGCForeground(GGIvisual, GGIblack);
	ggiSetWriteFrame(GGIvisual, !display_frame);
	ggiDrawBox(GGIvisual, 0, GGIcanvas_height, GGIwidth, GGI_font_height);
	ggiSetWriteFrame(GGIvisual, display_frame);

	/* clear the currently displayed area, which is left
	 * from a previous plot (see above, where the stl of
	 * the previous plot is copied to the current frame) */
	ggiSetReadFrame(GGIvisual, !display_frame);
	ggiCopyBox(GGIvisual, 0, GGIcanvas_height,
		   GGIwidth, GGI_font_height, 0, GGIcanvas_height);

	GGI_saved_stl = 1;
    }
}

TERM_PUBLIC void
GGI_replot()
{
    struct gp_event_t ev = {
	GE_replot,
	0, 0, 0, 0
    };

    do_event(&ev);
}

TERM_PUBLIC void
GGI_clear(const GGI_vertex_t* v, const int tag)
{
    if (tag && v->width) {
	/* turn off current */
	ggiSetReadFrame(GGIvisual, !ggiGetDisplayFrame(GGIvisual));
	ggiCopyBox(GGIvisual, v->x, v->y, v->width, v->height, v->x, v->y);
    }
}

TERM_PUBLIC void
GGI_save_puts(GGI_vertex_t* v, const int tag)
{
    GGI_clear(v, tag);

    if (v->width) {

	/* draw the text in the axis color (gray) */
	ggiSetGCForeground(GGIvisual, GGIaxisColor);

	/* write the string directly to the display */
	ggiPuts(GGIvisual, v->x, v->y, v->str);

    }
}

TERM_PUBLIC void
GGI_set_vertex(
    GGI_vertex_t* v,
    const int x,
    const int y,
    const char* str,
    const int tag)
{
    GGI_clear(v, tag);

    v->x = x;
    v->y = y;
    v->height = GGI_font_height;

    if (str && *str) {
	v->width = strlen(str) * GGI_font_width;
	strcpy(v->str, str);
    } else {
	/* turn string off */
	v->width = 0;
	*(v->str) = '\0';
    }
}

TERM_PUBLIC void
GGI_relative(int r[2])
{
    int diff = r[1] - r[0];
    if (diff < 0) {
	r[0] = r[1];
	r[1] = -diff;
    } else {
	r[1] = diff;
    }
}

TERM_PUBLIC void
GGI_clear_hline(int x1, int x2, int y)
{
    if (GGI_saved_canvas && x1 >= 0 && x2 >= 0 && y >= 0) {
	int r[2];
	ggiSetReadFrame(GGIvisual, !ggiGetDisplayFrame(GGIvisual));

	r[0] = x1;
	r[1] = x2;
	GGI_relative(r);
	/* horizontal line */
	ggiCopyBox(GGIvisual, r[0], y, r[1], 1, r[0], y);
    }
}

TERM_PUBLIC void
GGI_clear_vline(int y1, int y2, int x)
{
    if (GGI_saved_canvas && y1 >= 0 && y2 >= 0 && x >= 0) {
	int r[2];
	ggiSetReadFrame(GGIvisual, !ggiGetDisplayFrame(GGIvisual));

	r[0] = y1;
	r[1] = y2;
	GGI_relative(r);
	/* vertical line */
	ggiCopyBox(GGIvisual, x, r[0], 1, r[1], x, r[0]);
    }
}

TERM_PUBLIC void
GGI_draw_hline(int x1, int x2, int y)
{
    if (x1 >= 0 && x2 >= 0 && y >= 0) {
	int r[2];

	r[0] = x1;
	r[1] = x2;
	GGI_relative(r);
	/* horizontal line */
	ggiDrawHLine(GGIvisual, r[0], y, r[1]);
    }
}

TERM_PUBLIC void
GGI_draw_vline(int y1, int y2, int x)
{
    if (y1 >= 0 && y2 >= 0 && x >= 0) {
	int r[2];

	r[0] = y1;
	r[1] = y2;
	GGI_relative(r);
	/* vertical line */
	ggiDrawVLine(GGIvisual, x, r[0], r[1]);
    }
}

TERM_PUBLIC void
GGI_draw_ruler()
{
    if (GGI_ruler.x >= 0 && GGI_ruler.y >= 0) {
	ggi_pixel current_foreground;

	GGI_save_frame_canvas();

	/* TODO: we could choose a nicer color here */
	ggiGetGCForeground(GGIvisual, &current_foreground);
	ggiSetGCForeground(GGIvisual, GGIaxisColor);

	ggiDrawHLine(GGIvisual, 0, GGI_ruler.y, GGIwidth);
	ggiDrawVLine(GGIvisual, GGI_ruler.x, 0, GGIcanvas_height);

	/* restore old foreground color */
	/* XXX need this ? */
	ggiSetGCForeground(GGIvisual, current_foreground);
    }
}

TERM_PUBLIC void
GGI_clear_zoombox()
{
    GGI_clear_hline(GGI_zoombox[0].x, GGI_zoombox[1].x, GGI_zoombox[0].y);
    GGI_clear_hline(GGI_zoombox[0].x, GGI_zoombox[1].x, GGI_zoombox[1].y);
    GGI_clear_vline(GGI_zoombox[0].y, GGI_zoombox[1].y, GGI_zoombox[0].x);
    GGI_clear_vline(GGI_zoombox[0].y, GGI_zoombox[1].y, GGI_zoombox[1].x);
}

TERM_PUBLIC void
GGI_draw_zoombox()
{
    if (GGI_zoombox[0].x >= 0 && GGI_zoombox[0].y >= 0
	&& GGI_zoombox[0].x >= 0 && GGI_zoombox[0].y >= 0) {
	ggi_pixel current_foreground;

	GGI_save_frame_canvas();

	/* TODO: we could choose a nicer color here */
	ggiGetGCForeground(GGIvisual, &current_foreground);
	ggiSetGCForeground(GGIvisual, GGIaxisColor);

	GGI_draw_hline(GGI_zoombox[0].x, GGI_zoombox[1].x, GGI_zoombox[0].y);
	GGI_draw_hline(GGI_zoombox[0].x, GGI_zoombox[1].x, GGI_zoombox[1].y);
	GGI_draw_vline(GGI_zoombox[0].y, GGI_zoombox[1].y, GGI_zoombox[0].x);
	GGI_draw_vline(GGI_zoombox[0].y, GGI_zoombox[1].y, GGI_zoombox[1].x);

	/* restore old foreground color */
	/* XXX need this ? */
	ggiSetGCForeground(GGIvisual, current_foreground);
    }
}

TERM_PUBLIC void
GGI_abort_zooming()
{
    /* empty string: finish zooming */
    int i, j;
    GGI_clear_zoombox();
    for (i = 0; i < 2; i++) {
	for (j = 0; j < 2; j++) {
	    GGI_set_vertex(&(GGI_zoom[i][j]), 0, 0, (char*)0, GGI_saved_canvas);
	}
	GGI_zoombox[i].x = -1;
    }
}

TERM_PUBLIC int
GGI_waitforinput()
{
    char c;

    /* XXX:  if the input device it not a tty (e.g. /dev/null)
     *       mouse events are not processed. This is necessary
     *       as on some systems /dev/null is not selectable.
     */
    if (GGIvisual) {
	fd_set fds;
	int fd = fileno(stdin);
	int i, j;
	do {
	    int n;

	    ggi_event_mask mask = emAll; /* TODO: choose a more selective mask */
	    ggiSetEventMask(GGIvisual, mask);

	    FD_ZERO(&fds);
	    FD_SET(fd, &fds); /* listen to stdin */

	    if (GGI_needs_update) {
		/* draw the ruler below the other items */
		GGI_draw_ruler();

		/* update the zoombox */
		GGI_draw_zoombox();
		for (i = 0; i < 2; i++) {
		    for (j = 0; j < 2; j++) {
			GGI_save_puts(&(GGI_zoom[i][j]), GGI_saved_canvas);
		    }
		}

		/* update the status line */
		GGI_save_puts(&GGI_stl_vertex, GGI_saved_stl);

		ggiFlush(GGIvisual);

		GGI_needs_update = 0;
	    }

	    n = ggiEventSelect(GGIvisual, &mask, fd + 1,
		SELECT_TYPE_ARG234 &fds, 0, 0, (struct timeval*)0);

	    if (mask) {
		ggi_event event;
		/* mask pointer motions and key repeat events,
		 * to they don't pile up */
		ggiEventRead(GGIvisual, &event, mask);
		ggiRemoveEventMask(GGIvisual, emPtrMove | emKeyRepeat);
		if ('q' == GGI_dispatch_event(&event)) {
		    term_reset();
		    break;
		} else {
		    ggiAddEventMask(GGIvisual, emPtrMove | emKeyRepeat);
		}
	    }

	} while (!FD_ISSET(fd, &fds) && GGIvisual);
    }

    if (read(0, &c, 1)!=1) return EOF;
    else return c;
}

TERM_PUBLIC void
GGI_put_tmptext(int i, const char str[])
{
    char* second;

    switch (i) {
	case 0: /* statusline text */

	    if (!str || !(*str)) {
		/* statusline is empty. This is the case,
		 * if the mouse was just turned off. */
		if (GGI_use_mouse) {
		    /* The user just toggled of the mouse. */
		    GGI_use_mouse = 0;
		    GGI_set_size();
		    GGI_replot();
		}
	    } else {
		/* statusline is non-empty */
		if (!GGI_use_mouse) {
		    /* The mouse was off before and was just turned on. */
		    GGI_use_mouse = 1;
		    GGI_set_size();
		    GGI_replot();
		}
		GGI_save_frame_stl();
		GGI_set_vertex(&GGI_stl_vertex, 0, GGIcanvas_height, str, GGI_saved_stl);
	    }
	    break;

	case 1: /* coordinate text for first  corner of zoombox */
	case 2: /* coordinate text for second corner of zoombox */
	    GGI_save_frame_canvas();
	    second = (char*) strchr(str, '\r');
	    --i; /* transform to [0, 1] */
	    GGI_clear_zoombox();
	    if (second == NULL) {
		/* remove box and / or coordinates */
		GGI_set_vertex(&(GGI_zoom[i][0]), 0, 0, (char*)0, GGI_saved_canvas);
		GGI_set_vertex(&(GGI_zoom[i][1]), 0, 0, (char*)0, GGI_saved_canvas);
		break;
	    } else {
		*second = '\0'; /* XXX this assumes that str is writable XXX */
		second++;
		GGI_set_vertex(&(GGI_zoom[i][0]), GGI_mouse_x, GGI_mouse_y - GGI_font_height - 1, str, GGI_saved_canvas);
		GGI_set_vertex(&(GGI_zoom[i][1]), GGI_mouse_x, GGI_mouse_y + 1, second, GGI_saved_canvas);
		GGI_zoombox[i].x = GGI_mouse_x;
		GGI_zoombox[i].y = GGI_mouse_y;
	    }
	    break;
    }
    GGI_needs_update++;
}

TERM_PUBLIC void
GGI_set_ruler(int x, int y)
{
    if (x < 0) {

	/* turn ruler off */
	GGI_clear_hline(0, GGIwidth, GGI_ruler.y);
	GGI_clear_vline(0, GGIcanvas_height, GGI_ruler.x);
	GGI_ruler.x = -1;
	GGI_ruler.y = -1;

    } else {
	GGI_ruler.x = x;
	GGI_ruler.y = GGI_y(y);
    }
    GGI_needs_update++;
}

TERM_PUBLIC void
GGI_set_cursor(int c, int x, int y)
{
    /* TODO */
    switch (c) {
	case 0:
	    GGI_abort_zooming();
	    break;
	case 1:
	case 2:
	case 3:
	default:
	    /* XXX not implemented */
	    break;
    }
    GGI_needs_update++;
}

TERM_PUBLIC void
GGI_set_clipboard(const char s[])
{
    /* XXX: not implemented */
    (void) s;			/* avoid -Wunused */
}

#endif

TERM_PUBLIC int
GGI_make_palette(t_sm_palette *palette)
{
    /* reallocate only, if it has changed */
    if (palette && (GGI_save_pal.colorFormulae < 0
	    || palette->colorFormulae != GGI_save_pal.colorFormulae
	    || palette->colorMode != GGI_save_pal.colorMode
	    || palette->formulaR != GGI_save_pal.formulaR
	    || palette->formulaG != GGI_save_pal.formulaG
	    || palette->formulaB != GGI_save_pal.formulaB
	    || palette->positive != GGI_save_pal.positive)) {
	int i;
	ggi_color color;
	for (i = 0; i < ggi_pm3d_colors; i++) {
	    color.r = (short)floor(palette->color[i].r * 0xffff),
	    color.g = (short)floor(palette->color[i].g * 0xffff),
	    color.b = (short)floor(palette->color[i].b * 0xffff),
	    GGI_smooth_colors[i] = ggiMapColor(GGIvisual, &color);
	}
	GGI_save_pal = *palette;
    } else {
	return ggi_pm3d_colors;
    }
    return 0;
}

TERM_PUBLIC void
GGI_previous_palette()
{
#if 0
#ifdef ENABLE_XMI
    fprintf(stderr, "(GGI_previous_palette) \n");
    if (GGI_miPaintedSet) {
	miPoint offset;
	offset.x = 0; offset.y = 0;
	miCopyPaintedSetToVisual(GGIvisual, GGI_miGC, GGI_miPaintedSet, offset);
	miClearPaintedSet(GGIvisual, GGI_miPaintedSet);
    }
#endif
#endif
}

TERM_PUBLIC void
GGI_set_color(struct t_colorspec *colorspec)
{
    ggi_color rgb;
    double gray = colorspec->value;

    if (colorspec->type == TC_RGB) {
	rgb.r = ((colorspec->lt >> 16) & 0xff) * (65535./255.);
	rgb.g = ((colorspec->lt >> 8) & 0xff) * (65535./255.);
	rgb.b = ((colorspec->lt) & 0xff) * (65535./255.);
	ggiSetGCForeground(GGIvisual, ggiMapColor(GGIvisual,&rgb));
    }
    
    if (colorspec->type != TC_FRAC)
	return;

    if (GGIvisual) {
	int idx = (gray <= 0) ? 0 : (int)(gray * ggi_pm3d_colors);
	if (idx >= ggi_pm3d_colors)
	    idx = ggi_pm3d_colors - 1;
	ggiSetGCForeground(GGIvisual, GGI_smooth_colors[idx]);
#ifdef ENABLE_XMI
	GGI_miGC->pixels[1] = GGI_smooth_colors[idx];
#endif
    }
}

#ifdef ENABLE_XMI
TERM_PUBLIC void
GGI_filled_polygon(int points, gpiPoint *corners)
{
    static miPoint offset = {0, 0};
    if (GGI_miPaintedSet) {
#define MI_POINTS 4
	miPoint mi_corners[MI_POINTS];
	unsigned int i;
	if (MI_POINTS != points) {
	    fprintf(stderr, "(GGI_filled_polygon) internal error %s:%d\n", __FILE__, __LINE__);
	    return;
	}
	for (i = 0; i < MI_POINTS; i++) {
	    mi_corners[i].x = corners[i].x;
	    mi_corners[i].y = GGI_y(corners[i].y);
	}
	miFillPolygon(GGIvisual, GGI_miPaintedSet, GGI_miGC,
	    MI_SHAPE_GENERAL, MI_COORD_MODE_ORIGIN, MI_POINTS, mi_corners);
	miCopyPaintedSetToVisual(GGIvisual, GGI_miGC, GGI_miPaintedSet, offset);
	miClearPaintedSet(GGIvisual, GGI_miPaintedSet);
    }
}
#endif

#endif /* TERM_BODY */

#ifdef TERM_TABLE

TERM_TABLE_START(ggi_driver)
    "ggi", "GGI target",
    GGI_XMAX, GGI_YMAX, GGI_VCHAR, GGI_HCHAR, GGI_VTIC, GGI_HTIC,
    GGI_options, GGI_init, GGI_reset, GGI_text,
    null_scale, GGI_graphics, GGI_move, GGI_vector,
    GGI_linetype, GGI_put_text,
    0, /* angle */
    0, /* justify text */
    0, /* point */
    0, /* arrow */
    0, /* set_font */
    0, /* set_pointsize */
    TERM_CAN_MULTIPLOT,
    GGI_suspend,
    GGI_resume,
    GGI_fillbox,
    0 /* linewidth */
#ifdef USE_MOUSE
    , GGI_waitforinput, GGI_put_tmptext, GGI_set_ruler, GGI_set_cursor, GGI_set_clipboard
#endif
    , GGI_make_palette,
    GGI_previous_palette,
    GGI_set_color,
#ifdef ENABLE_XMI
    GGI_filled_polygon
#else
    0 /* GGI_filled_polygon */
#endif
TERM_TABLE_END(ggi_driver)

#endif /* TERM_TABLE */
#endif /* TERM_PROTO_ONLY */

#ifdef TERM_HELP
START_HELP(ggi)
"1 ggi",
"?commands set terminal ggi",
"?set terminal ggi",
"?set term ggi",
"?terminal ggi",
"?term ggi",
"?ggi",
" The `ggi` driver can run on different targets as X or svgalib.",
"",
" Syntax:",
"    set terminal ggi [acceleration <integer>] [[mode] {mode}]",
"",
" In X the window cannot be resized using window manager handles, but the",
" mode can be given with the mode option, e.g.:",
"  - V1024x768",
"  - V800x600",
"  - V640x480",
"  - V320x200",
" Please refer to the ggi documentation for other modes. The 'mode' keyword",
" is optional. It is recommended to select the target by environment variables",
" as explained in the libggi manual page. To get DGA on X, you should for",
" example",
"    bash> export GGI_DISPLAY=DGA",
"    csh>  setenv GGI_DISPLAY DGA",
"",
" 'acceleration' is only used for targets which report relative pointer",
" motion events (e.g. DGA) and is a strictly positive integer multiplication",
" factor for the relative distances.  The default for acceleration is 7.",
"",
" Examples:",
"    set term ggi acc 10",
"    set term ggi acc 1 mode V1024x768",
"    set term ggi V1024x768"
END_HELP(ggi)
#endif
