/* $Id: xp-replay.c,v 5.1 2001/04/24 13:51:06 bertg Exp $
 *
 * XP-Replay, playback an XPilot session.  Copyright (C) 1994-98 by
 *
 *      Bjørn Stabell        <bjoern@xpilot.org>
 *      Ken Ronny Schouten   <ken@xpilot.org>
 *      Bert Gijsbers        <bert@xpilot.org>
 *      Steven Singer        (S.Singer@ph.surrey.ac.uk)
 *
 * 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
 * MERC_HANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 * The scaling and gamma correction subroutines are adaptations from pbmplus:
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and 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.  This software is provided "as is" without express or
** implied warranty.
 */

#if !defined(_WINDOWS)
# include <unistd.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifndef _WINDOWS
# ifndef __hpux
#  include <sys/time.h>
# endif
# ifdef _AIX
#  include <sys/select.h> /* _BSD not defined in <sys/types.h>, so done by hand */
# endif
# include <stdarg.h>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# if !defined(select) && defined(__linux__)
#  define select(N, R, W, E, T)   select((N),             \
        (fd_set*)(R), (fd_set*)(W), (fd_set*)(E), (T))
# endif
#endif

#ifdef _SEQUENT_
# include <sys/procstats.h>
# define gettimeofday(T,X)	get_process_stats(T, PS_SELF, \
					(struct process_stats *)NULL, \
					(struct process_stats *)NULL)
#endif

#include "recordfmt.h"
#include "item.h"
#include "buttons.h"
#include "xp-replay.h"

#include "items/itemRocketPack.xbm"
#include "items/itemCloakingDevice.xbm"
#include "items/itemEnergyPack.xbm"
#include "items/itemWideangleShot.xbm"
#include "items/itemRearShot.xbm"
#include "items/itemMinePack.xbm"
#include "items/itemSensorPack.xbm"
#include "items/itemTank.xbm"
#include "items/itemEcm.xbm"
#include "items/itemAfterburner.xbm"
#include "items/itemTransporter.xbm"
#include "items/itemDeflector.xbm"
#include "items/itemHyperJump.xbm"
#include "items/itemPhasingDevice.xbm"
#include "items/itemLaser.xbm"
#include "items/itemMirror.xbm"
#include "items/itemArmor.xbm"
#include "items/itemEmergencyThrust.xbm"
#include "items/itemTractorBeam.xbm"
#include "items/itemAutopilot.xbm"
#include "items/itemEmergencyShield.xbm"

#include "tools/eject.xbm"
#include "tools/fastf.xbm"
#include "tools/pause.xbm"
#include "tools/play.xbm"
#include "tools/record.xbm"
#include "tools/revplay.xbm"
#include "tools/rewind.xbm"
#include "tools/stop.xbm"

struct rGC {
    struct rGC		*next;		/* linked list */
    long		mask;		/* XGCValues mask */
    unsigned long	foreground;
    unsigned long	background;
    unsigned char	line_width;
    unsigned char	line_style;
    unsigned char	dash_offset;
    unsigned char	function;
    unsigned char	fill_style;
    unsigned char	num_dashes;
    char		*dash_list;
    int			ts_x_origin;
    int			ts_y_origin;
    Pixmap		tile;
};

struct rArc {
    short		x;
    short		y;
    unsigned short	width;
    unsigned short	height;
    short		angle1;
    short		angle2;
};

struct rLines {
    XPoint		*points;
    unsigned short	npoints;
    short		mode;
};

struct rLine {
    short		x1;
    short		y1;
    short		x2;
    short		y2;
};

struct rString {
    short		x;
    short		y;
    unsigned char	font;
    short		length;
    char		*string;
};

struct rPolygon {
    XPoint		*points;
    unsigned short	npoints;
    unsigned char	shape;
    unsigned char	mode;
};

struct rSymbol {
    unsigned char	type;
    short		x;
    short		y;
};

struct rRectangle {
    short		x;
    short		y;
    unsigned short	width;
    unsigned short	height;
};

struct rRectangles {
    unsigned short	nrectangles;
    XRectangle		*rectangles;
};

struct rArcs {
    unsigned short	narcs;
    XArc		*arcs;
};

struct rSegments {
    unsigned short	nsegments;
    XSegment		*segments;
};

struct rDamage {
    unsigned char	damaged;
};

union shapep {
    struct rArc		arc;
    struct rLines	lines;
    struct rLine	line;
    struct rString	string;
    struct rPolygon	polygon;
    struct rSymbol	symbol;
    struct rRectangle	rectangle;
    struct rRectangles	rectangles;
    struct rArcs	arcs;
    struct rSegments	segments;
    struct rDamage	damage;
};

/*
 * Shape holds all info for one X windows drawing call.
 */
struct shape {
    struct shape	*next;		/* to next shape on frame list */
    char		type;		/* which drawing call */
    struct rGC		*gc;		/* Graphics Context */
    union shapep	shape;		/* actual shape data */
};

struct frame {
    struct frame	*next;		/* to next on frame list */
    struct frame	*prev;		/* to previous on frame list */
    struct frame	*older;		/* to next on LRU list */
    struct frame	*newer;		/* to previous on LRU list */
    long		filepos;	/* position in record file */
    unsigned short	width;		/* width of view window */
    unsigned short	height;		/* height of view window */
    struct shape	*shapes;	/* head of shape list */
    int			number;		/* frame sequence number */
};

typedef struct tile_list {
    struct tile_list	*next;
    Pixmap		tile;
    unsigned char	tile_id;
    int			flag;
} tile_list_t;

struct errorwin {
    Window		win;			/* Error display window */
    GC			gc;			/* GC for drawing therein */
    XFontStruct		*font;			/* Font to use */
    char		message[4096];		/* Current error message */
    int			len;			/* length thereof */
    Button		but;			/* OK button */
    Bool		grabbed;		/* Have we grabbed the pointer ? */
};

struct recordwin {
    Window		win;		/* Error display window */
    GC			gc;		/* GC for drawing therein */
    Button		mark_start_but;	/* Mark start button */
    Button		mark_end_but;	/* Mark end button */
    Button		recbut;		/* Record button */
    struct errorwin	*ewin;		/* Pointer to error window */
};

struct xprc {
    char		*filename;	/* name of input */
    FILE		*fp;		/* FILE pointer for input */
    int			seekable;	/* only seek if file is regular */
    int			eof;		/* if EOF encountered */
    int			majorversion;	/* major version of protocol */
    int			minorversion;	/* minor version of protocol */
    char		*nickname;	/* XPilot nick name of player */
    char		*realname;	/* login name of player */
    char		*hostname;	/* hostname of player */
    char		*servername;	/* hostname of server */
    int			fps;		/* frames per second of game */
    char		*recorddate;	/* date of game played */
    unsigned char	maxColors;	/* number of colors used */
    XColor		*colors;	/* pointer to color info */
    unsigned long	*pixels;	/* pointer to my pixel values */
    char		*gameFontName;	/* name of font used */
    char		*msgFontName;	/* name of font used */
    struct frame	*head;		/* to first frame */
    struct frame	*tail;		/* to last frame read sofar */
    struct frame	*cur;		/* current frame drawn */
    struct frame	*newest;	/* to first frame in LRU list */
    struct frame	*oldest;	/* to last frame in LRU list */
    struct frame	*save_first;	/* first frame to include in saving */
    struct frame	*save_last;	/* last frame to include in saving */
    XFontStruct		*gameFont;	/* X font for game situations */
    XFontStruct		*msgFont;	/* X font for messages */
    unsigned short	view_width;	/* initial width of viewing area */
    unsigned short	view_height;	/* initial height of viewing area */
    Window		topview;	/* Window to display frames in */
    GC			gc;		/* GC to use for frame display */
    double		scale;		/* scale reduction when saving */
    double		gamma;		/* gamma correction when saving */
    struct errorwin	*ewin;		/* Error display window */
    tile_list_t		*tlist;		/* list of pixmaps */
};

enum LabelDataTypes {
    LABEL_INTEGER,
    LABEL_STRING,
    LABEL_TIME
};

enum LabelTypes {
    LABEL_POSITION,
    LABEL_FPS,
    LABEL_SERVER,
    LABEL_PLAYER,
    LABEL_LOGIN,
    LABEL_CLIENT,
    NUM_LABELS
};

struct label {
    int			x, y;		/* position */
    int			w;		/* width (used to line up values) */
    Bool		j;		/* True for right justification */
    const char		*name;		/* Field name */
    enum LabelDataTypes	type;		/* data type */
    union {
	int		*i;
	char		*s;
	struct frame	**f;
    } data;				/* pointer to data */
};

enum ButtonTypes {
    BUTTON_RECORD,
    BUTTON_REWIND,
    BUTTON_REVERSE_PLAY,
    BUTTON_PLAY,
    BUTTON_FAST_FORWARD,
    BUTTON_PAUSE,
    BUTTON_STOP,
    BUTTON_EJECT,
    NUM_BUTTONS
};

#define BUTTON_BORDER 4
#define BUTTON_SPACING 2

static void quitCallback(void *);
static void pauseCallback(void *);
static void stopCallback(void *);
static void rewindCallback(void *);
static void fastfCallback(void *);
static void playCallback(void *);
static void revplayCallback(void *);
static void recordCallback(void *);
static void markSaveStart(void *);
static void markSaveEnd(void *);
static void saveStartToEndPPM(void *);
static void saveStartToEndXPR(void *);

static struct button_init {
    unsigned char *data;
    char colour;
    int width;
    int height;
    void (*callback)(void *);
    int flags;
    int group;
} buttonInit[] = {
    {
	record_bits, 1, record_width, record_height,
	recordCallback,
	BUTTON_RELEASE, 0
    },
    {
	rewind_bits, 0, rewind_width, rewind_height,
	rewindCallback,
	0, 1
    },
    {
	revplay_bits, 0, revplay_width, revplay_height,
	revplayCallback,
	0, 1
    },
    {
	play_bits, 0, play_width, play_height,
	playCallback,
	0, 1
    },
    {
	fastf_bits, 0, fastf_width, fastf_height,
	fastfCallback,
	0, 1
    },
    {
	pause_bits, 0, pause_width, pause_height,
	pauseCallback,
	0, 1
    },
    {
	stop_bits, 0, stop_width, stop_height,
	stopCallback,
	BUTTON_PRESSED, 1
    },
    {
	eject_bits, 0, eject_width, eject_height,
	quitCallback,
	BUTTON_RELEASE, 0
    },
};

#define NUM_BUTTONS	(sizeof(buttonInit) / sizeof(struct button_init))

static enum playStates {
    STATE_PLAYING,
    STATE_PAUSED
} playState = STATE_PLAYING;

static int	currentSpeed = 0, frameStep = 0;

struct xui {
    Window		topmain;
    GC			gc;
    unsigned long	black;
    unsigned long	white;
    unsigned long	mainbg;
    unsigned long	red;
    XFontStruct		*smallFont;
    XFontStruct		*boldFont;
    Button		buttons[NUM_BUTTONS];
    struct label	*labels;
    struct errorwin	*ewin;
    struct recordwin	*rwin;
};

static char small_font[] = "-*-helvetica-medium-r-*--14-*-*-*-*-*-iso8859-1";
static char small_bold_font[] =
	"-*-helvetica-bold-r-*--14-*-*-*-*-*-iso8859-1";

static int		Argc;
static char		**Argv;

static int		debug = 0;	/* want debugging output */
static int		verbose = 0;	/* want extra info messages */
static int		compress = 0;	/* save files in compressed format */
static int		frame_count;	/* number of frame next read in */
static int		frames_in_core;	/* number of frame next read in */
static struct rGC	*gclist;	/* list of all GCs used */
static int		forceRedraw = False;
static int		quit = 0;
static struct xprc	*purge_argument;
static void 		Purge(struct xprc *rc);

static Display		*dpy;
static Colormap		colormap;
static int		screen_num;

static char	*mem_lowest;			/* lowest pointer seen */
static char	*mem_highest;			/* highest pointer seen */
static long	mem_program_used;		/* max. size of malloced mem */
static long	mem_all_types_used;		/* frame memory in use */
static long	mem_typed_used[NUM_MEMTYPES];	/* debugging & analysis */
static long	max_mem = 8 * 1024 * 1024;	/* memory limit (soft) */

static void openErrorWindow(struct errorwin *, const char *, ...);

/*
 * Print memory statistics when debugging.
 */
static void MemPrint(void)
{
    if (!debug) {
	return;
    }
    printf("after %d frames (%d in core):\n", frame_count, frames_in_core);
    printf("	string:          %10ld\n", mem_typed_used[MEM_STRING]);
    printf("	frame:           %10ld\n", mem_typed_used[MEM_FRAME]);
    printf("	shape:           %10ld\n", mem_typed_used[MEM_SHAPE]);
    printf("	point:           %10ld\n", mem_typed_used[MEM_POINT]);
    printf("	gc:              %10ld\n", mem_typed_used[MEM_GC]);
    printf("	misc:            %10ld\n", mem_typed_used[MEM_MISC]);
    printf("	user-interface:  %10ld\n", mem_typed_used[MEM_UI]);
    printf("	all types:       %10ld\n", mem_all_types_used);
    printf("	total program:   %10ld\n", mem_program_used);
}

/*
 * Maintain statistics on memory usage.
 * We want to know what part of the program uses what amount of memory.
 * This information is used to keep amount of memory used below a
 * certain maximum by limiting the number of frames which are kept
 * in memory.
 */
static void MemStats(char *p, size_t size, enum MemTypes mt)
{
    long		prev_mem_program_used = mem_program_used;

    if (!mem_lowest || p < mem_lowest) {
	mem_lowest = p;
    }
    if (!mem_highest || p + size > mem_highest) {
	mem_highest = p + size;
    }
    mem_typed_used[mt] += size;
    mem_all_types_used += size;
    mem_program_used = mem_highest - mem_lowest;
    if (mem_program_used >> 20 != prev_mem_program_used >> 20) {
	MemPrint();
    }
}

/*
 * Wrapper for free(3).
 * This one helps in maintaining statistics on memory usage.
 */
static void MyFree(void *p, size_t size, enum MemTypes mt)
{
    if (p) {
	free(p);
	mem_typed_used[mt] -= size;
	mem_all_types_used -= size;
    }
}

/*
 * Wrapper for malloc(3).
 * This one helps in maintaining statistics on memory usage.
 */
void *MyMalloc(size_t size, enum MemTypes mt)
{
    void *p;

    if (!(p = malloc(size))) {
	if (!size) {
	    /*
	     * caller shouldn't actually be using zero-sized memory anyway.
	     */
	    return NULL;
	}
	if (debug) {
	    printf("no memory\n");
	    MemPrint();
	}
	max_mem = mem_typed_used[MEM_FRAME] / 2;
	if (purge_argument != NULL) {
	    Purge(purge_argument);
	}
	if (!(p = malloc(size))) {
	    fprintf(stderr, "Not enough memory after allocating %ld bytes.\n",
		    mem_program_used);
	    exit(1);
	}
    }
    MemStats((char *)p, size, mt);
    return p;
}

#if 0
/*
 * Wrapper for strdup(3).
 * This one uses MyMalloc() instead of malloc()
 */
static char *MyStrDup(const char *str)
{
    size_t size = strlen(str) + 1;
    char *dup = (char *) MyMalloc(size, MEM_STRING);
    memcpy(dup, str, size);
    return dup;
}
#endif

/*
 * Read one 8-bit byte from the recorded input stream.
 */
static unsigned char RReadByte(FILE *fp)
{
    return (unsigned char) (getc(fp));
}

/*
 * Read one 16-bit unsigned word from the recorded input stream.
 */
static unsigned short RReadUShort(FILE *fp)
{
    unsigned short i;

    i = (getc(fp) & 0xFF);
    i |= (getc(fp) & 0xFF) << 8;

    return i;
}

/*
 * Read one 16-bit signed word from the recorded input stream.
 */
static short RReadShort(FILE *fp)
{
    short i;

    i = (short) RReadUShort(fp);

    if (i & 0x8000)
	i = -(-i & 0xffff);

    return i;
}

/*
 * Read one 32-bit unsigned longword from the recorded input stream.
 */
static unsigned long RReadULong(FILE *fp)
{
    unsigned long	i;

    i = (getc(fp) & 0xFF);
    i |= (getc(fp) & 0xFF) << 8;
    i |= (getc(fp) & 0xFF) << 16;
    i |= (getc(fp) & 0xFF) << 24;

    return i;
}

/*
 * Read one 32-bit signed longword from the recorded input stream.
 */
static long RReadLong(FILE *fp)
{
    long i;

    i = (long) RReadULong(fp);

    if (i & 0x80000000)
	i = -(-i & 0xffffffff);

    return i;
}

/*
 * Read a pascal-type string from the recorded input stream
 * and convert it to a nul-byte terminated C-string.
 */
static char *RReadString(FILE *fp)
{
    char		*s;
    int			i;
    int			len;

    len = RReadUShort(fp);
    s = (char *) MyMalloc(len + 1, MEM_STRING);
    s[len] = '\0';
    for (i = 0; i < len; i++) {
	s[i] = getc(fp);
    }
    return s;
}

/*
 * Read the XPilot Record protocol header from the recorded input stream.
 */
static int RReadHeader(struct xprc *rc)
{
    char		magic[5];
    unsigned char	minor;
    unsigned char	major;
    char		dot;
    char		nl;
    int			i;

    magic[0] = getc(rc->fp);
    magic[1] = getc(rc->fp);
    magic[2] = getc(rc->fp);
    magic[3] = getc(rc->fp);
    magic[4] = '\0';
    major = getc(rc->fp);
    dot = getc(rc->fp);
    minor = getc(rc->fp);
    nl = getc(rc->fp);
    if (strcmp(magic, "XPRC") || dot != '.' || nl != '\n') {
	fprintf(stderr, "Error: Not a valid XPilot Recording file.\n");
	return -1;
    }
    /*
     * We are incompatible with newer versions
     * and with older versions whose major version number
     * differs from ours.
     */
    if (major != RC_MAJORVERSION || minor > RC_MINORVERSION) {
	fprintf(stderr,
		"Error: Incompatible version. (file: %c.%c)(program: %c.%c)\n",
		major, minor, RC_MAJORVERSION, RC_MINORVERSION);
	return -1;
    }
    rc->majorversion = major;
    rc->minorversion = minor;
    rc->nickname = RReadString(rc->fp);
    rc->realname = RReadString(rc->fp);
    rc->hostname = RReadString(rc->fp);
    rc->servername = RReadString(rc->fp);
    rc->fps = RReadByte(rc->fp);
    rc->recorddate = RReadString(rc->fp);
    rc->maxColors = (unsigned char) getc(rc->fp);
    rc->colors = (XColor *) MyMalloc(rc->maxColors * sizeof(XColor), MEM_MISC);
    for (i = 0; i < rc->maxColors; i++) {
	rc->colors[i].pixel = RReadULong(rc->fp);
	rc->colors[i].red = RReadUShort(rc->fp);
	rc->colors[i].green = RReadUShort(rc->fp);
	rc->colors[i].blue = RReadUShort(rc->fp);
	rc->colors[i].flags = DoRed | DoGreen | DoBlue;
    }
    rc->gameFontName = RReadString(rc->fp);
    rc->msgFontName = RReadString(rc->fp);
    rc->view_width = RReadUShort(rc->fp);
    rc->view_height = RReadUShort(rc->fp);

    if (verbose) {
	printf("Player is %s (in real life %s@%s).\n",
	       rc->nickname, rc->realname, rc->hostname);
	printf("Server was %s, running at %d frames per second.\n",
	       rc->servername, rc->fps);
	printf("Recorded on %s.\n", rc->recorddate);
    }

    return 0;
}

/*
 * Read an encoded Pixmap from the recorded input stream.
 */
static Pixmap RReadTile(struct xprc *rc)
{
    tile_list_t			*lptr;
    unsigned			width, height;
    unsigned			x, y;
    unsigned			depth;
    XImage			*img;
    unsigned char		ch;
    Pixmap			tile;
    unsigned char		tile_id;

    ch = RReadByte(rc->fp);
    tile_id = RReadByte(rc->fp);
    if (ch == RC_TILE) {
	if (tile_id == 0) {
	    return None;
	}
	for (lptr = rc->tlist; lptr != NULL; lptr = lptr->next) {
	    if (lptr->tile_id == tile_id) {
		return lptr->tile;
	    }
	}
	return None;
    }
    if (ch != RC_NEW_TILE) {
	fprintf(stderr, "Error: New tile expected, not found! (%d)\n", ch);
	exit(1);
    }
    width = RReadUShort(rc->fp);
    height = RReadUShort(rc->fp);
    depth = DefaultDepth(dpy, screen_num);
    img = XCreateImage(dpy, DefaultVisual(dpy, screen_num),
		       depth, ZPixmap,
		       0, NULL,
		       width, height,
		       (depth <= 8) ? 8 : (depth <= 16) ? 16 : 32,
		       0);
    if (!img) {
	fprintf(stderr, "Can't create XImage %ux%u", width, height);
	exit(1);
    }
    img->data = (char *)MyMalloc(img->bytes_per_line * height, MEM_GC);
    for (y = 0; y < img->height; y++) {
	for (x = 0; x < img->width; x++) {
	    ch = RReadByte(rc->fp);
	    XPutPixel(img, x, y, rc->pixels[ch]);
	}
    }
    tile = XCreatePixmap(dpy, RootWindow(dpy, screen_num),
			 width, height, depth);
    if (!tile) {
	fprintf(stderr, "Can't create Pixmap %ux%u", width, height);
	exit(1);
    }
    XPutImage(dpy, tile, rc->gc, img, 0, 0, 0, 0, width, height);
    XDestroyImage(img);

    if (!(lptr = (tile_list_t *)malloc(sizeof(tile_list_t)))) {
	perror("memory");
	exit(1);
    }
    lptr->next = rc->tlist;
    lptr->tile = tile;
    lptr->tile_id = tile_id;
    rc->tlist = lptr;

    return tile;
}

/*
 * Read an encoded GC from the recorded input stream.
 */
static struct rGC *RReadGCValues(struct xprc *rc)
{
    int			c = getc(rc->fp);
    struct rGC		gc, *gcp;
    unsigned short	input_mask;

    memset(&gc, 0, sizeof(gc));

    if (c == RC_NOGC) {
	gc.mask = 0;
    }
    else if (c != RC_GC) {
	openErrorWindow(rc->ewin, "GC expected on position %ld, not %d",
			ftell(rc->fp), c);
	return NULL;
    }
    else {
	input_mask = RReadByte(rc->fp);
	if (input_mask & RC_GC_B2) {
	    input_mask |= (RReadByte(rc->fp) << 8);
	}
	gc.mask = 0;
	if (input_mask & RC_GC_FG) {
	    gc.mask |= GCForeground;
	    gc.foreground = rc->pixels[RReadByte(rc->fp)];
	}
	if (input_mask & RC_GC_BG) {
	    gc.mask |= GCBackground;
	    gc.background = rc->pixels[RReadByte(rc->fp)];
	}
	if (input_mask & RC_GC_LW) {
	    gc.mask |= GCLineWidth;
	    gc.line_width = RReadByte(rc->fp);
	}
	if (input_mask & RC_GC_LS) {
	    gc.mask |= GCLineStyle;
	    gc.line_style = RReadByte(rc->fp);
	}
	if (input_mask & RC_GC_DO) {
	    gc.mask |= GCDashOffset;
	    gc.dash_offset = RReadByte(rc->fp);
	}
	if (input_mask & RC_GC_FU) {
	    gc.mask |= GCFunction;
	    gc.function = RReadByte(rc->fp);
	}
	if (input_mask & RC_GC_DA) {
	    int i;
	    gc.num_dashes = RReadByte(rc->fp);
	    if (gc.num_dashes == 0) {
		gc.dash_list = NULL;
	    } else {
		gc.dash_list = (char *) MyMalloc(gc.num_dashes, MEM_GC);
		for (i = 0; i < gc.num_dashes; i++) {
		    gc.dash_list[i] = RReadByte(rc->fp);
		}
	    }
	}
	if (input_mask & RC_GC_B2) {
	    if (input_mask & RC_GC_FS) {
		gc.mask |= GCFillStyle;
		gc.fill_style = RReadByte(rc->fp);
	    }
	    if (input_mask & RC_GC_XO) {
		gc.mask |= GCTileStipXOrigin;
		gc.ts_x_origin = RReadLong(rc->fp);
	    }
	    if (input_mask & RC_GC_YO) {
		gc.mask |= GCTileStipYOrigin;
		gc.ts_y_origin = RReadLong(rc->fp);
	    }
	    if (input_mask & RC_GC_TI) {
		gc.mask |= GCTile;
		gc.tile = RReadTile(rc);
	    }
	}
    }

    for (gcp = gclist; gcp != NULL; gcp = gcp->next) {
	if (gcp->mask != gc.mask)
	    continue;
	if ((gc.mask & GCForeground) && gc.foreground != gcp->foreground)
	    continue;
	if ((gc.mask & GCBackground) && gc.background != gcp->background)
	    continue;
	if ((gc.mask & GCLineWidth) && gc.line_width != gcp->line_width)
	    continue;
	if ((gc.mask & GCLineStyle) && gc.line_style != gcp->line_style)
	    continue;
	if ((gc.mask & GCDashOffset) && gc.dash_offset != gcp->dash_offset)
	    continue;
	if ((gc.mask & GCFunction) && gc.function != gcp->function)
	    continue;
	if ((gc.mask & GCFillStyle) && gc.fill_style != gcp->fill_style)
	    continue;
	if ((gc.mask & GCTileStipXOrigin) && gc.ts_x_origin != gcp->ts_x_origin)
	    continue;
	if ((gc.mask & GCTileStipYOrigin) && gc.ts_y_origin != gcp->ts_y_origin)
	    continue;
	if ((gc.mask & GCTile) && gc.tile != gcp->tile)
	    continue;
	if (gc.num_dashes != gcp->num_dashes)
	    continue;
	if (gc.num_dashes > 0) {
	    if (memcmp(gc.dash_list, gcp->dash_list, gc.num_dashes))
		continue;
	    MyFree(gc.dash_list, gc.num_dashes, MEM_GC);
	}
	return gcp;
    }
    gcp = (struct rGC *) MyMalloc(sizeof(*gcp), MEM_GC);
    memcpy(gcp, &gc, sizeof(*gcp));
    gcp->next = gclist;
    gclist = gcp;

    return gcp;
}

static void RemoveFrameFromLRU(struct xprc *rc, struct frame *f)
{
    if (f->older != NULL) {
	f->older->newer = f->newer;
    }
    if (rc->newest == f) {
	rc->newest = f->older;
    }
    if (f->newer != NULL) {
	f->newer->older = f->older;
    }
    if (rc->oldest == f) {
	rc->oldest = f->newer;
    }
    f->newer = NULL;
    f->older = NULL;
}

static void AddFrameToLRU(struct xprc *rc, struct frame *f)
{
    f->newer = NULL;
    f->older = rc->newest;
    if (rc->newest) {
	rc->newest->newer = f;
    } else {
	rc->oldest = f;
    }
    rc->newest = f;
}

static void TouchFrame(struct xprc *rc, struct frame *f)
{
    if (rc->newest != f) {
	RemoveFrameFromLRU(rc, f);
	AddFrameToLRU(rc, f);
    }
}

/*
 * Release all memory associated with a list of shape data.
 */
static void FreeShapes(struct shape *shp)
{
    struct shape	*nextshp;

    while (shp) {

	nextshp = shp->next;
	shp->next = NULL;

	switch (shp->type) {

	case RC_DRAWLINES:
	    MyFree(shp->shape.lines.points,
		   shp->shape.lines.npoints * sizeof(XPoint),
		   MEM_POINT);
	    break;

	case RC_DRAWSTRING:
	    MyFree(shp->shape.string.string,
		   shp->shape.string.length,
		   MEM_STRING);
	    break;

	case RC_FILLPOLYGON:
	    MyFree(shp->shape.polygon.points,
		   shp->shape.polygon.npoints * sizeof(XPoint),
		   MEM_POINT);
	    break;

	case RC_FILLRECTANGLES:
	    MyFree(shp->shape.rectangles.rectangles,
		   shp->shape.rectangles.nrectangles * sizeof(XRectangle),
		   MEM_POINT);
	    break;

	case RC_DRAWARCS:
	    MyFree(shp->shape.arcs.arcs,
		   shp->shape.arcs.narcs * sizeof(XArc),
		   MEM_POINT);
	    break;

	case RC_DRAWSEGMENTS:
	    MyFree(shp->shape.segments.segments,
		   shp->shape.segments.nsegments * sizeof(XSegment),
		   MEM_POINT);
	    break;
	}

	shp->type = 0;
	MyFree(shp, sizeof(struct shape), MEM_SHAPE);
	shp = nextshp;
    }
}

/*
 * Release all memory used for storing the data of a frame.
 */
static void FreeFrameData(struct frame *f)
{
    if (!f) {
	return;
    }
    FreeShapes(f->shapes);
    f->shapes = NULL;

    frames_in_core--;
}

/*
 * Release all memory associated with a frame, including the frame header.
 */
static void FreeFrame(struct xprc *rc, struct frame *f)
{
    if (!f) {
	return;
    }
    FreeFrameData(f);
    if (f->next) {
	f->next->prev = f->prev;
    }
    if (f->prev) {
	f->prev->next = f->next;
    }
    if (f == rc->head) {
	rc->head = f->next;
    }
    if (f == rc->tail) {
	rc->tail = f->prev;
    }
    if (f == rc->cur) {
	rc->cur = (f->next != NULL) ? f->next : f->prev;
    }
    f->next = NULL;
    f->prev = NULL;
    RemoveFrameFromLRU(rc, f);
    MyFree(f, sizeof(struct frame), MEM_FRAME);
}

/*
 * Free all frames, including their headers.
 */
static void FreeXPRCData(struct xprc *rc)
{
    while (rc->head) {
	FreeFrame(rc, rc->head);
    }
}

/*
 * Free up memory by removing the data of some frames from memory.
 * The frames last accessed are the ones chosen to be freed.
 */
static void Purge(struct xprc *rc)
{
    long		goal = (3 * max_mem) / 4;	/* free one quarter */
    int			max_purge = 3;		/* don't purge too much */
    struct frame	*keep_frame;
    struct frame	*f;

    /*
     * If there are a huge number of recorded frames
     * then the frame headers could take up all of the reserved memory.
     * If this happens permit ourselves to use more memory.
     */
    if (goal < mem_typed_used[MEM_FRAME]) {
	max_mem = mem_typed_used[MEM_FRAME] + (max_mem - goal);
	goal = (3 * max_mem) / 4;
    }
    if (!(keep_frame = rc->newest) || !(keep_frame = keep_frame->older)) {
	/* should at least keep two frames. */
	return;
    }
    while (mem_all_types_used > goal
	&& rc->oldest != keep_frame
	&& max_purge > 0) {
	f = rc->oldest;
	RemoveFrameFromLRU(rc, f);
	FreeFrameData(f);
	max_purge--;
    }
}

/*
 * Read all of the data of one complete frame into memory.
 * The frame header has already been allocated.
 * If our input is a regular file then we may be reloading
 * the data of a previous purged frame.
 */
static int readFrameData(struct xprc *rc, struct frame *f)
{
    int			c = 0, prev_c;
    struct shape	*shp = NULL,
			*shphead = NULL,
			*newshp;
    XPoint		*xpp;
    XRectangle		*xrp;
    XArc		*xap;
    XSegment		*xsp;
    char		*cp;
    int			done = False;

    clearerr(rc->fp);
    if (rc->seekable && fseek(rc->fp, f->filepos, 0) != 0) {
	perror("Can't reposition file");
	exit(1);
    }

    while (!done) {

	prev_c = c;
	c = getc(rc->fp);

	switch (c) {

	case EOF:
	    openErrorWindow(rc->ewin,
			    "Premature End-Of-File encountered. Truncating.");
	    done = True;
	    rc->eof = True;
	    continue;

	case RC_ENDFRAME:
	    done = True;
	    continue;

	case RC_DRAWARC:
	case RC_DRAWLINES:
	case RC_DRAWLINE:
	case RC_DRAWRECTANGLE:
	case RC_DRAWSTRING:
	case RC_FILLARC:
	case RC_FILLPOLYGON:
	case RC_PAINTITEMSYMBOL:
	case RC_FILLRECTANGLE:
	case RC_FILLRECTANGLES:
	case RC_DRAWARCS:
	case RC_DRAWSEGMENTS:
	case RC_DAMAGED:
	    newshp = (struct shape *) MyMalloc(sizeof(struct shape),
					       MEM_SHAPE);
	    newshp->next = NULL;
	    newshp->type = 0;
	    if ((newshp->gc = RReadGCValues(rc)) == NULL) {
		MyFree(newshp, sizeof(struct shape), MEM_SHAPE);
		done = True;
		continue;
	    }
	    if (shp == NULL) {
		shp = newshp;
		shphead = shp;
	    }
	    else {
		shp->next = newshp;
		shp = shp->next;
	    }
	    shp->type = c;

	    switch (c) {

	    case RC_DRAWARC:
	    case RC_FILLARC:
		shp->shape.arc.x = RReadShort(rc->fp);
		shp->shape.arc.y = RReadShort(rc->fp);
		shp->shape.arc.width = RReadByte(rc->fp);
		shp->shape.arc.height = RReadByte(rc->fp);
		shp->shape.arc.angle1 = RReadShort(rc->fp);
		shp->shape.arc.angle2 = RReadShort(rc->fp);
		break;

	    case RC_DRAWLINES:
		shp->shape.lines.npoints = c = RReadUShort(rc->fp);
		shp->shape.lines.points = xpp =
		    (XPoint *) MyMalloc(sizeof(XPoint) * c, MEM_POINT);
		while (c--)
		{
		    xpp->x = RReadShort(rc->fp);
		    xpp->y = RReadShort(rc->fp);
		    xpp++;
		}
		shp->shape.lines.mode = RReadByte(rc->fp);
		break;

	    case RC_DRAWLINE:
		shp->shape.line.x1 = RReadShort(rc->fp);
		shp->shape.line.y1 = RReadShort(rc->fp);
		shp->shape.line.x2 = RReadShort(rc->fp);
		shp->shape.line.y2 = RReadShort(rc->fp);
		break;

	    case RC_DRAWRECTANGLE:
	    case RC_FILLRECTANGLE:
		shp->shape.rectangle.x = RReadShort(rc->fp);
		shp->shape.rectangle.y = RReadShort(rc->fp);
		shp->shape.rectangle.width = RReadByte(rc->fp);
		shp->shape.rectangle.height = RReadByte(rc->fp);
		break;

	    case RC_DRAWSTRING:
		shp->shape.string.x = RReadShort(rc->fp);
		shp->shape.string.y = RReadShort(rc->fp);
		shp->shape.string.font = RReadByte(rc->fp);
		shp->shape.string.length = c = RReadUShort(rc->fp);
		shp->shape.string.string = cp = (char *) MyMalloc(c, MEM_STRING);
		while (c--)
		    *cp++ = getc(rc->fp);
		break;

	    case RC_FILLPOLYGON:
		shp->shape.polygon.npoints = c = RReadUShort(rc->fp);
		shp->shape.polygon.points = xpp =
		    (XPoint *) MyMalloc(sizeof(XPoint) * c, MEM_POINT);
		while (c--)
		{
		    xpp->x = RReadShort(rc->fp);
		    xpp->y = RReadShort(rc->fp);
		    xpp++;
		}
		shp->shape.polygon.shape = RReadByte(rc->fp);
		shp->shape.polygon.mode = RReadByte(rc->fp);
		break;

	    case RC_PAINTITEMSYMBOL:
		shp->shape.symbol.type = RReadByte(rc->fp);
		shp->shape.symbol.x = RReadShort(rc->fp);
		shp->shape.symbol.y = RReadShort(rc->fp);
		break;

	    case RC_FILLRECTANGLES:
		shp->shape.rectangles.nrectangles = c = RReadUShort(rc->fp);
		shp->shape.rectangles.rectangles = xrp =
		    (XRectangle *) MyMalloc(sizeof(XRectangle) * c, MEM_POINT);
		while (c--)
		{
		    xrp->x = RReadShort(rc->fp);
		    xrp->y = RReadShort(rc->fp);
		    xrp->width = RReadByte(rc->fp);
		    xrp->height = RReadByte(rc->fp);
		    xrp++;
		}
		break;

	    case RC_DRAWARCS:
		shp->shape.arcs.narcs = c = RReadUShort(rc->fp);
		shp->shape.arcs.arcs = xap =
		    (XArc *) MyMalloc(sizeof(XArc) * c, MEM_POINT);
		while (c--)
		{
		    xap->x = RReadShort(rc->fp);
		    xap->y = RReadShort(rc->fp);
		    xap->width = RReadByte(rc->fp);
		    xap->height = RReadByte(rc->fp);
		    xap->angle1 = RReadShort(rc->fp);
		    xap->angle2 = RReadShort(rc->fp);
		    xap++;
		}
		break;

	    case RC_DRAWSEGMENTS:
		shp->shape.segments.nsegments = c = RReadUShort(rc->fp);
		shp->shape.segments.segments = xsp =
		    (XSegment *) MyMalloc(sizeof(XSegment) * c, MEM_POINT);
		while (c--)
		{
		    xsp->x1 = RReadShort(rc->fp);
		    xsp->y1 = RReadShort(rc->fp);
		    xsp->x2 = RReadShort(rc->fp);
		    xsp->y2 = RReadShort(rc->fp);
		    xsp++;
		}
		break;

	    case RC_DAMAGED:
		shp->shape.damage.damaged = RReadByte(rc->fp);
		break;
	    }
	    break;

	default:
	    openErrorWindow(rc->ewin,
			    "Unknown shape type %d (previous = %d) when "
			    "reading frame %d,\nafter %d frames with %d in "
			    "core. Truncating...",
			    c, prev_c, f->number, frame_count, frames_in_core);
	    done = True;
	    continue;
	}

    }

    if (c != RC_ENDFRAME) {
	FreeShapes(shphead);
	return -1;
    }

    f->shapes = shphead;

    frames_in_core++;

    AddFrameToLRU(rc, f);

    if (mem_all_types_used >= max_mem) {
	Purge(rc);
    }

    return 0;
}

static int readNewFrame(struct xprc *rc)
{
    int			c;
    struct frame	*f = NULL;

    if (rc->eof) {
	return -1;
    }
    if ((c = getc(rc->fp)) == EOF) {
	rc->eof = True;
	MemPrint();
	return -1;
    }
    if (c != RC_NEWFRAME) {
	openErrorWindow(rc->ewin, "Corrupt record file, next frame expected, "
			"not %d.  Truncating.", c);
	rc->eof = True;
	return -1;
    }
    f = (struct frame *) MyMalloc(sizeof(struct frame), MEM_FRAME);
    f->width = RReadUShort(rc->fp);
    f->height = RReadUShort(rc->fp);
    f->shapes = NULL;
    f->next = NULL;
    f->prev = NULL;
    f->newer = NULL;
    f->older = NULL;
    f->number = frame_count;
    if (rc->seekable && (f->filepos = ftell(rc->fp)) == -1) {
	openErrorWindow(rc->ewin, "Can't get file position. Truncating.");
	rc->eof = True;
	MyFree(f, sizeof(struct frame), MEM_FRAME);
	return -1;
    }

    if (readFrameData(rc, f) == -1) {
	FreeFrame(rc, f);
	return -1;
    }

    if (rc->tail == NULL) {
	f->next = NULL;
	f->prev = NULL;
	rc->tail = rc->head = rc->cur = f;
    }
    else {
	f->prev = rc->tail;
	f->next = NULL;
	rc->tail->next = f;
	rc->tail = f;
    }

    frame_count++;

    return 0;
}

static Atom		ProtocolAtom;
static Atom		KillAtom;

static Pixmap		itemBitmaps[NUM_ITEMS];         /* Bitmaps for the items */
static unsigned char	*itemData[NUM_ITEMS] = {
    itemEnergyPack_bits,
    itemWideangleShot_bits,
    itemRearShot_bits,
    itemAfterburner_bits,
    itemCloakingDevice_bits,
    itemSensorPack_bits,
    itemTransporter_bits,
    itemTank_bits,
    itemMinePack_bits,
    itemRocketPack_bits,
    itemEcm_bits,
    itemLaser_bits,
    itemEmergencyThrust_bits,
    itemTractorBeam_bits,
    itemAutopilot_bits,
    itemEmergencyShield_bits,
    itemDeflector_bits,
    itemHyperJump_bits,
    itemPhasingDevice_bits,
    itemMirror_bits,
    itemArmor_bits
};

static XFontStruct *loadQueryFont(const char *fontName, GC gc)
{
    XFontStruct		*font;

    if ((font = XLoadQueryFont(dpy, fontName)) == NULL) {
	fprintf(stderr, "Can't load font \"%s\".\n", fontName);
	font = XQueryFont(dpy, XGContextFromGC(gc));
    }
    return font;
}

static void allocViewColors(struct xprc *rc)
{
    XColor		*cp, *cp2, myColor;
    int			i, j;

    rc->pixels = (unsigned long *)
		MyMalloc(2 * rc->maxColors * sizeof(*rc->pixels), MEM_MISC);

    for (i = 0; i < rc->maxColors; i++) {
	cp = &rc->colors[i];
	for (j = 0; j < i; j++) {
	    cp2 = &rc->colors[j];
	    if (cp->red == cp2->red &&
		cp->green == cp2->green &&
		cp->blue == cp2->blue) {
		break;
	    }
	}
	if (j < i) {
	    rc->pixels[j] = rc->pixels[i];
	    continue;
	}
	if (cp->red < 0x0100 &&
	    cp->green < 0x0100 &&
	    cp->blue < 0x0100 &&
	    colormap == DefaultColormap(dpy, screen_num)) {
	    rc->pixels[i] = BlackPixel(dpy, screen_num);
	    continue;
	}
	if (cp->red >= 0xFF00 &&
	    cp->green >= 0xFF00 &&
	    cp->blue >= 0xFF00 &&
	    colormap == DefaultColormap(dpy, screen_num)) {
	    rc->pixels[i] = WhitePixel(dpy, screen_num);
	    continue;
	}
	myColor = *cp;
	if (!XAllocColor(dpy, colormap, &myColor)) {
	    fprintf(stderr, "Hmm, not enough colors? (%d: %04x,%04x,%04x)\n",
		    i, cp->red, cp->green, cp->blue);
	    rc->pixels[i] = (i == 0)
			    ? BlackPixel(dpy, screen_num)
			    : WhitePixel(dpy, screen_num);
	}
	else if (i > 0
		 && myColor.pixel == BlackPixel(dpy, screen_num)
		 && colormap == DefaultColormap(dpy, screen_num)) {
	    rc->pixels[i] = WhitePixel(dpy, screen_num);
	} else {
	    rc->pixels[i] = myColor.pixel;
	}
    }
    for (i = 0; i < rc->maxColors; i++) {
	rc->pixels[i + rc->maxColors] = rc->pixels[BLACK] ^ rc->pixels[i];
    }
}

static unsigned long allocColor(const char *name)
{
    XColor		color;

    if (!XParseColor(dpy, colormap, name, &color)) {
	fprintf(stderr, "Can't parse color \"%s\"\n", name);
	return WhitePixel(dpy, screen_num);
    }
    if (!XAllocColor(dpy, colormap, &color)) {
	fprintf(stderr, "Can't allocate color \"%s\"\n", name);
	return WhitePixel(dpy, screen_num);
    }
    return color.pixel;
}

static void drawShapes(struct frame *f, XID drawable, struct xprc *rc)
{
    struct shape	*sp;
    XGCValues		values;

    for (sp = f->shapes; sp != NULL; sp = sp->next) {
	if (sp->gc != NULL) {
	    if (sp->gc->mask != 0) {
		values.foreground = sp->gc->foreground;
		values.background = sp->gc->background;
		values.line_width = sp->gc->line_width;
		values.line_style = sp->gc->line_style;
		values.dash_offset = sp->gc->dash_offset;
		values.function = sp->gc->function;
		values.fill_style = sp->gc->fill_style;
		values.ts_x_origin = sp->gc->ts_x_origin;
		values.ts_y_origin = sp->gc->ts_y_origin;
		values.tile = sp->gc->tile;
		XChangeGC(dpy, rc->gc, sp->gc->mask, &values);
	    }
	    if (sp->gc->num_dashes > 0) {
		XSetDashes(dpy, rc->gc,
			   sp->gc->dash_offset,
			   sp->gc->dash_list,
			   sp->gc->num_dashes);
	    }
	}

	switch(sp->type) {

	case RC_DRAWARC:
	    XDrawArc(dpy, drawable, rc->gc,
		     sp->shape.arc.x, sp->shape.arc.y,
		     sp->shape.arc.width, sp->shape.arc.height,
		     sp->shape.arc.angle1, sp->shape.arc.angle2);
	    break;

	case RC_DRAWLINES:
	    XDrawLines(dpy, drawable, rc->gc, sp->shape.lines.points,
		       sp->shape.lines.npoints, sp->shape.lines.mode);
	    break;

	case RC_DRAWLINE:
	    XDrawLine(dpy, drawable, rc->gc,
		      sp->shape.line.x1, sp->shape.line.y1,
		      sp->shape.line.x2, sp->shape.line.y2);
	    break;

	case RC_DRAWRECTANGLE:
	    XDrawRectangle(dpy, drawable, rc->gc,
			   sp->shape.rectangle.x, sp->shape.rectangle.y,
			   sp->shape.rectangle.width,
			   sp->shape.rectangle.height);
	    break;

	case RC_DRAWSTRING:
	    if (sp->shape.string.font == 0)
		XSetFont(dpy, rc->gc, rc->gameFont->fid);
	    else
		XSetFont(dpy, rc->gc, rc->msgFont->fid);
	    XDrawString(dpy, drawable, rc->gc,
			sp->shape.string.x, sp->shape.string.y,
			sp->shape.string.string,
			sp->shape.string.length);
	    break;

	case RC_FILLARC:
	    XFillArc(dpy, drawable, rc->gc,
		     sp->shape.arc.x, sp->shape.arc.y,
		     sp->shape.arc.width, sp->shape.arc.height,
		     sp->shape.arc.angle1, sp->shape.arc.angle2);
	    break;

	case RC_FILLPOLYGON:
	    XFillPolygon(dpy, drawable, rc->gc,
			 sp->shape.polygon.points, sp->shape.polygon.npoints,
			 sp->shape.polygon.shape,
			 sp->shape.polygon.mode);
	    break;

	case RC_FILLRECTANGLE:
	    XFillRectangle(dpy, drawable, rc->gc,
			   sp->shape.rectangle.x, sp->shape.rectangle.y,
			   sp->shape.rectangle.width,
			   sp->shape.rectangle.height);
	    break;

	case RC_PAINTITEMSYMBOL:
	    values.stipple = itemBitmaps[sp->shape.symbol.type];
	    values.fill_style = FillStippled;
	    values.ts_x_origin = sp->shape.symbol.x;
	    values.ts_y_origin = sp->shape.symbol.y;
	    XChangeGC(dpy, rc->gc, GCStipple | GCFillStyle |
		      GCTileStipXOrigin | GCTileStipYOrigin, &values);
	    XFillRectangle(dpy, drawable, rc->gc,
			   sp->shape.symbol.x, sp->shape.symbol.y,
			   ITEM_SIZE, ITEM_SIZE);
	    XSetFillStyle(dpy, rc->gc, FillSolid);
	    break;

	case RC_FILLRECTANGLES:
	    XFillRectangles(dpy, drawable, rc->gc,
			    sp->shape.rectangles.rectangles,
			    sp->shape.rectangles.nrectangles);
	    break;

	case RC_DRAWARCS:
	    XDrawArcs(dpy, drawable, rc->gc,
		      sp->shape.arcs.arcs,
		      sp->shape.arcs.narcs);
	    break;

	case RC_DRAWSEGMENTS:
	    XDrawSegments(dpy, drawable, rc->gc,
			  sp->shape.segments.segments,
			  sp->shape.segments.nsegments);
	    break;

	case RC_DAMAGED:
	    if (sp->shape.damage.damaged) {
		XFillRectangle(dpy, drawable, rc->gc,
			       0, 0,
			       f->width, f->height);
	    }
	    break;

	}
    }
}

static void OverWriteMsg(struct xprc *rc, const char *msg)
{
    XFontStruct		*font = rc->gameFont;
    int			len = strlen(msg);
    int			text_w = XTextWidth(font, msg, len);
    int			text_h = font->ascent + font->descent;
    int			text_x = (rc->cur->width - text_w) / 2;
    int			text_y = (rc->cur->height - text_h) / 2;
    int			area_w = (text_w < rc->cur->width / 2)
				 ? (rc->cur->width / 2) : text_w;
    int			area_h = rc->cur->height / 4;
    int			area_x = (rc->cur->width - area_w) / 2;
    int			area_y = (rc->cur->height - area_h) / 2;

    XSetForeground(dpy, rc->gc, rc->pixels[BLACK]);
    XFillRectangle(dpy, rc->topview, rc->gc,
		   area_x, area_y,
		   area_w, area_h);
    XSetForeground(dpy, rc->gc, rc->pixels[WHITE]);
    XSetLineAttributes(dpy, rc->gc, 0, LineSolid, CapButt, JoinMiter);
    XDrawRectangle(dpy, rc->topview, rc->gc,
		   area_x, area_y,
		   area_w, area_h);
    XSetFont(dpy, rc->gc, rc->gameFont->fid);
    XDrawString(dpy, rc->topview, rc->gc,
		text_x, text_y + font->ascent,
		msg, len);
    XFlush(dpy);
}

static void redrawWindow(struct xprc *rc)
{
    XWindowAttributes	attrib;

    if (!rc->cur->shapes) {
	readFrameData(rc, rc->cur);
    }
    else if (rc->seekable) {
	TouchFrame(rc, rc->cur);
    }

    forceRedraw = False;

    XGetWindowAttributes(dpy, rc->topview, &attrib);

    if (attrib.width != rc->cur->width ||
	attrib.height != rc->cur->height)
    {
	XWindowChanges values;

	values.width = rc->cur->width;
	values.height = rc->cur->height;

	XReconfigureWMWindow(dpy, rc->topview, screen_num, CWWidth | CWHeight,
			     &values);
    }

    XClearWindow(dpy, rc->topview);

    drawShapes(rc->cur, rc->topview, rc);
}

/*
 * Initialize miscellaneous window hints and properties.
 */
static void Init_wm_prop(Window win,
			 int w, int h,
			 int x, int y,
			 int min_w, int min_h,
			 int max_w, int max_h,
			 int flags)
{
    XClassHint		xclh;
    XWMHints		xwmh;
    XSizeHints		xsh;
    char		msg[256];
    static char		myClass[] = "XP-replay";

    xwmh.flags	   = InputHint|StateHint;
    xwmh.input	   = True;
    xwmh.initial_state = NormalState;

    xsh.flags = (flags|PResizeInc);
    xsh.x = x;
    xsh.width = w;
    xsh.base_width =
    xsh.min_width = min_w;
    xsh.max_width = max_w;
    xsh.width_inc = 1;
    xsh.height = h;
    xsh.base_height =
    xsh.min_height = min_h;
    xsh.max_height = max_h;
    xsh.height_inc = 1;
    xsh.y = y;

    xclh.res_name = NULL;		/* NULL: Automatically uses Argv[0], */
    xclh.res_class = myClass;		/* stripped of directory prefixes. */

    /*
     * Set the above properties.
     */
    XSetWMProperties(dpy, win,
		     NULL, NULL,
		     Argv, Argc,
		     &xsh, &xwmh, &xclh);

    /*
     * Now initialize icon and window title name.
     */
    strcpy(msg, strchr(*Argv, '/') ? strchr(*Argv, '/') + 1 : *Argv);
    XStoreName(dpy, win, msg);

    XSetIconName(dpy, win, msg);

    /*
     * Tell the window manager that we know how to quit ourselves.
     */
    ProtocolAtom = XInternAtom(dpy, "WM_PROTOCOLS", False);
    KillAtom = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(dpy, win, &KillAtom, 1);
}

static struct recordwin *Init_recordwindow(unsigned long bg, void *data)
{
    struct recordwin	*rwin = (struct recordwin *)
				MyMalloc(sizeof(struct recordwin), MEM_UI);
    int			w, h, x, y;
    XWindowChanges	values;
    union button_image	image;

    w = 100;
    h = 50;
    x = (DisplayWidth(dpy, screen_num) - w) / 2;
    y = (DisplayHeight(dpy, screen_num) - h) / 2;

    rwin->win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen_num),
				    x, y,
				    w, h,
				    1,
				    BlackPixel(dpy, screen_num),
				    bg);
    rwin->gc = XCreateGC(dpy, rwin->win, 0, NULL);
    XSetForeground(dpy, rwin->gc, BlackPixel(dpy, screen_num));
    XSelectInput(dpy, rwin->win,
		 KeyPressMask | KeyReleaseMask | ButtonPressMask);

    x = 0;
    y = 5;
    image.string = "Start save here";
    rwin->mark_start_but = CreateButton(dpy, rwin->win,
					5, y,
					0, 0,
					image, 0, 0,
					BlackPixel(dpy, screen_num),
					markSaveStart, data,
					BUTTON_TEXT | BUTTON_RELEASE,
					0);
    GetButtonSize(rwin->mark_start_but, &w, &h);
    if (x < w)
	x = w;
    y += h + 5;

    image.string = "End save here";
    rwin->mark_end_but = CreateButton(dpy, rwin->win,
				      5, y,
				      0, 0,
				      image, 0, 0,
				      BlackPixel(dpy, screen_num),
				      markSaveEnd, data,
				      BUTTON_TEXT | BUTTON_RELEASE,
				      0);
    GetButtonSize(rwin->mark_end_but, &w, &h);
    if (x < w)
	x = w;
    y += h + 5;

    image.string = "Save in PPM format";
    rwin->recbut = CreateButton(dpy, rwin->win,
				5, y,
				0, 0,
				image, 0, 0,
				BlackPixel(dpy, screen_num),
				saveStartToEndPPM, data,
				BUTTON_TEXT | BUTTON_RELEASE,
				0);
    GetButtonSize(rwin->recbut, &w, &h);
    if (x < w)
	x = w;
    y += h + 5;

    image.string = "Save in XPR format";
    rwin->recbut = CreateButton(dpy, rwin->win,
				5, y,
				0, 0,
				image, 0, 0,
				BlackPixel(dpy, screen_num),
				saveStartToEndXPR, data,
				BUTTON_TEXT | BUTTON_RELEASE,
				0);
    GetButtonSize(rwin->recbut, &w, &h);
    if (x < w)
	x = w;
    y += h + 5;

    values.width = x + 10;
    values.height = y;
    values.x = (DisplayWidth(dpy, screen_num) - values.width) / 2;
    values.y = (DisplayHeight(dpy, screen_num) - values.height) / 2;
    
    XReconfigureWMWindow(dpy, rwin->win, screen_num,
			 CWX | CWY | CWWidth | CWHeight, &values);

    Init_wm_prop(rwin->win,
		 x, y,
		 w, h,
		 0, 0,
		 0, 0,
		 PSize | PPosition);
    return(rwin);
}

static void openErrorWindow(struct errorwin *ewin, const char *fmt, ...)
{
    int w, h, i;
    va_list ap;
    XWindowChanges values;
    char *p = ewin->message, *q = ewin->message;

    va_start(ap, fmt);
    vsprintf(ewin->message, fmt, ap);
    va_end(ap);

    ewin->len = strlen(ewin->message);

    w = 0;
    h = 0;

    while(*p != '\0')
    {
	while(*p != '\0' && *p != '\n')
	    p++;
	if (p != q)
	    if ((i = XTextWidth(ewin->font, q, p-q)) > w)
		w = i;
	if (*p != '\0')
	    p++;
	q = p;
	h++;
    }

    h *= ewin->font->ascent + ewin->font->descent + 2;
    MoveButton(ewin->but, 5, h + 5);
    values.width = w + 10;
    values.height = h + 15;
    GetButtonSize(ewin->but, &w, &h);
    values.height += h;
    if (values.width < w + 10)
	values.width = w + 10;
    values.x = (DisplayWidth(dpy, screen_num) - values.width)/2;
    values.y = (DisplayHeight(dpy, screen_num) - values.height)/2;

    XReconfigureWMWindow(dpy, ewin->win, screen_num,
			 CWX | CWY | CWWidth | CWHeight, &values);

    XMapWindow(dpy, ewin->win);

    /* In case it's already mapped, we'd better clear it */

    XClearArea(dpy, ewin->win, 0, 0, 0, 0, True);
    XBell(dpy, 0);

    /* We try to grab the pointer now, but our window may not have been
       mapped. If it hasn't we'll grab the pointer on out first expose
       event */

    ewin->grabbed = False;
    if (XGrabPointer(dpy, ewin->win, True, 0, GrabModeAsync, GrabModeSync,
		     ewin->win, None, CurrentTime) == GrabSuccess)
	ewin->grabbed = True;
}

static void closeErrorWindow(void *data)
{
    struct errorwin *ewin = (struct errorwin *) data;

    XUngrabPointer(dpy, CurrentTime);
    XUnmapWindow(dpy, ewin->win);
}

static struct errorwin *Init_errorwindow(unsigned long bg)
{
    struct errorwin *ewin = (struct errorwin *)
	MyMalloc(sizeof(struct errorwin), MEM_UI);
    int w, h, x, y;
    union button_image image;

    w = 100;
    h = 50;
    x = (DisplayWidth(dpy, screen_num) - w) / 2;
    y = (DisplayHeight(dpy, screen_num) - h) / 2;

    ewin->win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen_num),
				    x, y,
				    w, h,
				    1,
				    BlackPixel(dpy, screen_num),
				    bg);
    ewin->gc = XCreateGC(dpy, ewin->win, 0, NULL);
    ewin->font = loadQueryFont("-*-helvetica-medium-r-*-*-18-*-*-*-*-*-*-*",
			       ewin->gc);
    XSetForeground(dpy, ewin->gc, BlackPixel(dpy, screen_num));
    XSetFont(dpy, ewin->gc, ewin->font->fid);
    image.string = "OK";
    ewin->but = CreateButton(dpy, ewin->win, 5, 15,
			     0, 0, image, 0, 0,
			     BlackPixel(dpy, screen_num),
			     closeErrorWindow, (void *) ewin,
			     BUTTON_TEXT | BUTTON_RELEASE, 0);
    XSelectInput(dpy, ewin->win, ExposureMask);
    *ewin->message = '\0';
    ewin->len = 0;
    ewin->grabbed = True;
    Init_wm_prop(ewin->win,
		 x, y,
		 w, h,
		 0, 0,
		 0, 0,
		 PSize | PPosition);
    return(ewin);
}

static void Init_topview(struct xprc *rc)
{
    int			i;
    int			w = rc->view_width;
    int			h = rc->view_height;
    int			x = 0;
    int			y = (DisplayHeight(dpy, screen_num) - h) / 2;

    rc->topview = XCreateSimpleWindow(dpy, RootWindow(dpy, screen_num),
				      x, y,
				      w, h,
				      1,
				      BlackPixel(dpy, screen_num),
				      BlackPixel(dpy, screen_num));

    Init_wm_prop(rc->topview,
		 x, y,
		 w, h,
		 0, 0,
		 0, 0,
		 PSize | PPosition);

    rc->gc = XCreateGC(dpy, rc->topview, 0, NULL);

    XSelectInput(dpy, rc->topview,
		 ExposureMask |
		 KeyPressMask | KeyReleaseMask | ButtonPressMask);

    for (i=0; i<NUM_ITEMS; i++)
        itemBitmaps[i] = XCreateBitmapFromData(dpy, rc->topview,
					       (char *)itemData[i],
					       ITEM_SIZE, ITEM_SIZE);

    rc->gameFont = loadQueryFont(rc->gameFontName, rc->gc);
    rc->msgFont = loadQueryFont(rc->msgFontName, rc->gc);

    allocViewColors(rc);

    rc->ewin = Init_errorwindow(BlackPixel(dpy, screen_num));
}

static void Init_topmain(struct xui *ui, struct xprc *rc)
{
    int			topx, topy, topw, toph, i, x, y, w, mw;
    XWindowChanges	values;

    ui->black = BlackPixel(dpy, screen_num);
    ui->white = WhitePixel(dpy, screen_num);
    ui->mainbg = allocColor("gray80");
    ui->red = allocColor("red");

    topw = 26 - BUTTON_SPACING;
    toph = 0;

    for (i = 0; i < NUM_BUTTONS; i++) {

	topw += buttonInit[i].width + BUTTON_BORDER + BUTTON_SPACING;
	if (toph < buttonInit[i].height)
	    toph = buttonInit[i].height;
    }


   ui->topmain = XCreateSimpleWindow(dpy, RootWindow(dpy, screen_num),
				      0, 0,
				      topw, toph+100,
				      1,
				      BlackPixel(dpy, screen_num),
				      ui->mainbg);

    ui->gc = XCreateGC(dpy, ui->topmain, 0, NULL);

    XSelectInput(dpy, ui->topmain,
		 ExposureMask |
		 KeyPressMask | KeyReleaseMask |
		 ButtonPressMask | ButtonReleaseMask);

    ui->smallFont = loadQueryFont(small_font, ui->gc);
    ui->boldFont = loadQueryFont(small_bold_font, ui->gc);

    SetGlobalButtonAttributes(ui->mainbg, ui->white, ui->black, ui->black);

    x = 5;
    y = (toph>>1) + 5;
    for (i=0; i<NUM_BUTTONS; i++)
    {
	union button_image p;

	p.icon = XCreateBitmapFromData(dpy, ui->topmain,
				       (char *) buttonInit[i].data,
				       buttonInit[i].width,
				       buttonInit[i].height);

	if (i == BUTTON_EJECT)
	    x += 16;

	ui->buttons[i] =
	    CreateButton(dpy, ui->topmain, x, y - (buttonInit[i].height>>1),
			 buttonInit[i].width+4, buttonInit[i].height+4, p,
			 buttonInit[i].width, buttonInit[i].height,
			 (buttonInit[i].colour == 0) ? ui->black : ui->red,
			 buttonInit[i].callback, (void *) ui,
			 buttonInit[i].flags, buttonInit[i].group);
	x += buttonInit[i].width+BUTTON_BORDER+BUTTON_SPACING;
    }

    ui->labels = (struct label *)
		    MyMalloc(NUM_LABELS * sizeof(struct label),
			     MEM_UI);
    memset(ui->labels, 0, NUM_LABELS * sizeof(struct label));
    ui->labels[0].name = "Position";
    ui->labels[0].type = LABEL_TIME;
    ui->labels[0].data.f = &(rc->cur);
    ui->labels[0].j = False;
    ui->labels[1].name = "Server";
    ui->labels[1].type = LABEL_STRING;
    ui->labels[1].data.s = rc->servername;
    ui->labels[1].j = False;
    ui->labels[2].name = "Player";
    ui->labels[2].type = LABEL_STRING;
    ui->labels[2].data.s = rc->nickname;
    ui->labels[2].j = False;
    ui->labels[3].name = "Name";
    ui->labels[3].type = LABEL_STRING;
    ui->labels[3].data.s = rc->realname;
    ui->labels[3].j = False;
    ui->labels[4].name = "Client";
    ui->labels[4].type = LABEL_STRING;
    ui->labels[4].data.s = rc->hostname;
    ui->labels[4].j = False;
    ui->labels[5].name = "FPS";
    ui->labels[5].type = LABEL_INTEGER;
    ui->labels[5].data.i = &(rc->fps);
    ui->labels[5].j = True;

    mw = 0;
    ui->labels[0].x = 5;
    ui->labels[0].y = toph + 15;
    for (i=1; i<5; i++)
    {
	ui->labels[i].x = ui->labels[i-1].x;
	ui->labels[i].y = ui->labels[i-1].y
	    + ui->boldFont->ascent + ui->boldFont->descent + 2;
    }
    toph = ui->labels[4].y + ui->boldFont->ascent + ui->boldFont->descent + 5;
    for (i=0; i<5; i++)
    {
	w = XTextWidth(ui->boldFont, ui->labels[i].name,
		       strlen(ui->labels[i].name));
	if (w > mw)
	    mw = w;
    }
    for (i=0; i<5; i++)
	ui->labels[i].w = mw;
    ui->labels[5].x = topw - ui->labels[0].x;
    ui->labels[5].y = ui->labels[0].y;
    ui->labels[5].w = 0;

    topx = DisplayWidth(dpy, screen_num) - topw;
    topy = (DisplayHeight(dpy, screen_num) - toph) / 3;

    values.x = topx;
    values.y = topy;
    values.width = topw;
    values.height = toph;

    XReconfigureWMWindow(dpy, ui->topmain, screen_num,
			 CWX | CWY | CWWidth | CWHeight, &values);

    Init_wm_prop(ui->topmain,
		 topx, topy,
		 topw, toph,
		 0, 0,
		 0, 0,
		 PSize | PPosition);

    ui->ewin = Init_errorwindow(ui->mainbg);
    ui->rwin = Init_recordwindow(ui->mainbg, (void *) rc);
}

static void redrawLabel(struct xui *ui, struct xprc *rc, struct label *lb)
{
    int			fn, hr, min, sec, fr, lw, vw, cw;
    char		value_str[256];

    XSetForeground(dpy, ui->gc, ui->black);

    switch (lb->type) {

    case LABEL_TIME:
	fn = (*lb->data.f)->number;
	hr = fn / (60 * 60 * rc->fps);
	min = (fn - (hr * 60 * 60 * rc->fps)) / (60 * rc->fps);
	sec = (fn - ((hr * 60 + min) * 60 * rc->fps)) / (rc->fps);
	fr = (fn - (((hr * 60 + min) * 60 + sec) * rc->fps)) * 100 / rc->fps;
	sprintf(value_str, "%02d:%02d:%02d.%02d", hr, min, sec, fr);
	break;

    case LABEL_INTEGER:
	sprintf(value_str, "%d", *lb->data.i);
	break;

    case LABEL_STRING:
	strcpy(value_str,lb->data.s);
	break;
    }

    if (lb->w == 0)
	lw = XTextWidth(ui->boldFont, lb->name, strlen(lb->name));
    else
	lw = lb->w;
    cw = XTextWidth(ui->boldFont, ": ", 2);
    vw = XTextWidth(ui->smallFont, value_str, strlen(value_str));
#if 0
	XClearArea(dpy, ui->topmain,
		   lb->x, lb->y,
		   lw + 2,
		   ui->boldFont->ascent + ui->boldFont->descent,
		   False);
#endif
    XClearArea(dpy, ui->topmain,
	       lb->x + lw + cw + 1, lb->y,
	       vw + 2,
	       ui->smallFont->ascent + ui->smallFont->descent,
	       False);
    if (lb->j)
    {
	lw = -lw-cw-vw;
	cw = -cw-vw;
	vw = -vw;
    }
    else
    {
	vw = lw+cw;
	cw = lw;
	lw = 0;
    }
    XSetFont(dpy, ui->gc, ui->boldFont->fid);
    XDrawString(dpy, ui->topmain, ui->gc,
		lb->x + 1 + lw, lb->y + ui->boldFont->ascent,
		lb->name, strlen(lb->name));
    XDrawString(dpy, ui->topmain, ui->gc,
		lb->x + 1 + cw, lb->y + ui->boldFont->ascent,
		": ", 2);
    XSetFont(dpy, ui->gc, ui->smallFont->fid);
    XDrawString(dpy, ui->topmain, ui->gc,
		lb->x + 1 + vw, lb->y + ui->smallFont->ascent,
		value_str, strlen(value_str));
}

static void redrawMain(struct xui *ui, struct xprc *rc)
{
    int			i;

    for (i = 0; i < NUM_LABELS; i++) {
	redrawLabel(ui, rc, &(ui->labels[i]));
    }
}

static void redrawError(struct errorwin *ewin)
{
    int y = 5 + ewin->font->ascent;
    char *p = ewin->message, *q = ewin->message;

    if (!ewin->grabbed)
	XGrabPointer(dpy, ewin->win, True, 0, GrabModeAsync, GrabModeSync,
		     ewin->win, None, CurrentTime);
    ewin->grabbed = True;	/* Only try once */

    while(*p != '\0')
    {
	while(*p != '\0' && *p != '\n')
	    p++;
	if (p != q)
	    XDrawString(dpy, ewin->win, ewin->gc, 5, y, q, p-q);
	if (*p != '\0')
	    p++;
	q = p;
	y += ewin->font->ascent + ewin->font->descent + 2;
    }
}

static void BuildGamma(unsigned char tbl[256], double gamma)
{
    int			i, v;
    double		one_over_gamma, ind;
    /* extern double	pow(double, double); */

    one_over_gamma = 1.0 / gamma;
    for (i = 0 ; i <= 255; i++) {
	ind = (double) i / 255.0;
	v = (int)((255.0 * pow(ind, one_over_gamma)) + 0.5);
	tbl[i] = (v > 255) ? 255 : v;
    }
    if (debug) {
	printf("gamma table for gamma correction factor %f:\n", gamma);
	for (i = 0 ; i <= 255; i++) {
	    printf("%02x  ", tbl[i]);
	    if (!((i + 1) & 0x0F)) {
		printf("\n");
	    }
	}
    }
}

static void GammaCorrect(unsigned char *data, int size, unsigned char tbl[256])
{
    while (size) {
	*data = tbl[*data];
	size--;
	data++;
    }
}

static void ScalePPM(unsigned char *rgbdata, int cols, int rows,
		     double scale, double gamma, FILE *fp)
{
#define SCALE		4096
#define HALFSCALE	2048

    unsigned char	*xelrow;
    unsigned char	*tempxelrow;
    unsigned char	*newxelrow;
    unsigned char	*xP;
    unsigned char	*nxP;
    int			rowsread, newrows, newcols;
    int			row, col, needtoreadrow;
    double		xscale, yscale;
    long		sxscale, syscale;
    long		fracrowtofill, fracrowleft;
    long		*rs;
    long		*gs;
    long		*bs;
    long		r, g, b;
    long		fraccoltofill, fraccolleft;
    int			needcol;
    size_t		size_tempxelrow, size_newxelrow, size_rsgsbs;
    unsigned char	gammatbl[256];

    if (gamma > 0) {
	BuildGamma(gammatbl, gamma);
    }

    newcols = (int) (cols * scale + 0.999);
    newrows = (int) (rows * scale + 0.999);
    xscale = (double) newcols / (double) cols;
    yscale = (double) newrows / (double) rows;
    sxscale = (long) (xscale * SCALE);
    syscale = (long) (yscale * SCALE);
    size_newxelrow = 3 * newcols;
    size_tempxelrow = 3 * cols;
    size_rsgsbs = cols * sizeof(long);
    newxelrow = (unsigned char *) MyMalloc(size_newxelrow, MEM_MISC);
    tempxelrow = (unsigned char *) MyMalloc(size_tempxelrow, MEM_MISC);
    rs = (long *) MyMalloc(size_rsgsbs, MEM_MISC);
    gs = (long *) MyMalloc(size_rsgsbs, MEM_MISC);
    bs = (long *) MyMalloc(size_rsgsbs, MEM_MISC);
    fracrowtofill = SCALE;
    fracrowleft = syscale;
    for (col = 0; col < cols; col++) {
	rs[col] = gs[col] = bs[col] = HALFSCALE;
    }

    fprintf(fp, "P6\n");
    fprintf(fp, "%d %d\n", newcols, newrows);
    fprintf(fp, "%d\n", 255);

    xelrow = rgbdata;
    rowsread = 1;
    needtoreadrow = 0;
    for (row = 0; row < newrows; row++) {
	while (fracrowleft < fracrowtofill) {
	    if (needtoreadrow) {
		if (rowsread < rows) {
		    xelrow = &rgbdata[3 * rowsread * cols];
		    rowsread++;
		}
	    }
	    for (col = 0, xP = xelrow; col < cols; col++) {
		rs[col] += fracrowleft * *xP++;
		gs[col] += fracrowleft * *xP++;
		bs[col] += fracrowleft * *xP++;
	    }
	    fracrowtofill -= fracrowleft;
	    fracrowleft = syscale;
	    needtoreadrow = 1;
	}

	/* Now fracrowleft is >= fracrowtofill, so we can produce a row. */
	if (needtoreadrow) {
	    if (rowsread < rows) {
		xelrow = &rgbdata[3 * rowsread * cols];
		rowsread++;
		needtoreadrow = 0;
	    }
	}
	for (col = 0, xP = xelrow, nxP = tempxelrow; col < cols; col++) {
	    r = rs[col] + fracrowtofill * *xP++;
	    g = gs[col] + fracrowtofill * *xP++;
	    b = bs[col] + fracrowtofill * *xP++;
	    r /= SCALE;
	    if (r > 255) r = 255;
	    g /= SCALE;
	    if (g > 255) g = 255;
	    b /= SCALE;
	    if (b > 255) b = 255;
	    *nxP++ = r;
	    *nxP++ = g;
	    *nxP++ = b;
	    rs[col] = gs[col] = bs[col] = HALFSCALE;
	}
	fracrowleft -= fracrowtofill;
	if (fracrowleft == 0) {
	    fracrowleft = syscale;
	    needtoreadrow = 1;
	}
	fracrowtofill = SCALE;

	/* Now scale X from tempxelrow into newxelrow and write it out. */
	nxP = newxelrow;
	fraccoltofill = SCALE;
	r = g = b = HALFSCALE;
	needcol = 0;
	for (col = 0, xP = tempxelrow; col < cols; col++, xP += 3) {
	    fraccolleft = sxscale;
	    while (fraccolleft >= fraccoltofill) {
		if (needcol) {
		    r = g = b = HALFSCALE;
		}
		r += fraccoltofill * xP[0];
		g += fraccoltofill * xP[1];
		b += fraccoltofill * xP[2];
		r /= SCALE;
		if (r > 255) r = 255;
		g /= SCALE;
		if (g > 255) g = 255;
		b /= SCALE;
		if (b > 255) b = 255;
		*nxP++ = r;
		*nxP++ = g;
		*nxP++ = b;
		fraccolleft -= fraccoltofill;
		fraccoltofill = SCALE;
		needcol = 1;
	    }
	    if (fraccolleft > 0) {
		if (needcol) {
		    r = g = b = HALFSCALE;
		    needcol = 0;
		}
		r += fraccolleft * xP[0];
		g += fraccolleft * xP[1];
		b += fraccolleft * xP[2];
		fraccoltofill -= fraccolleft;
	    }
	}
	if (!needcol) {
	    if (fraccoltofill > 0) {
		xP -= 3;
		r += fraccoltofill * *xP++;
		g += fraccoltofill * *xP++;
		b += fraccoltofill * *xP++;
	    }
	    r /= SCALE;
	    if (r > 255) r = 255;
	    g /= SCALE;
	    if (g > 255) g = 255;
	    b /= SCALE;
	    if (b > 255) b = 255;
	    *nxP++ = r;
	    *nxP++ = g;
	    *nxP++ = b;
	}
	if (gamma > 0) {
	    GammaCorrect(newxelrow, 3 * newcols, gammatbl);
	}
	fwrite(newxelrow, 1, 3 * newcols, fp);
    }

    fflush(fp);

    MyFree(newxelrow, size_newxelrow, MEM_MISC);
    MyFree(tempxelrow, size_tempxelrow, MEM_MISC);
    MyFree(rs, size_rsgsbs, MEM_MISC);
    MyFree(gs, size_rsgsbs, MEM_MISC);
    MyFree(bs, size_rsgsbs, MEM_MISC);
}

static void SaveFramesPPM(struct xprc *rc)
{
    struct frame	*begin = rc->save_first;
    struct frame	*end = rc->save_last;
    struct frame	*save;
    Pixmap		pixmap;
    XImage		*img;
    unsigned long	pixel;
    int			i, x, y;
    int			done = 0;
    FILE		*fp;
    unsigned char	*ptr, *line, *rgbdata;
    char		buf[256];

    if (!begin) {
	openErrorWindow(rc->ewin, "The first frame to save hasn't been set.");
    }
    else if (!end) {
	openErrorWindow(rc->ewin, "The last frame to save hasn't been set.");
    }
    if (!begin || !end) {
	return;
    }
    if (begin->number > end->number) {
	openErrorWindow(rc->ewin, "First save frame exceeds last save frame, "
			"not saving");
	return;
    }
    if (!rc->seekable) {
	if (!begin->shapes) {
	    openErrorWindow(rc->ewin, "Save failed for standard input");
	    return;
	}
    }

    pixmap = XCreatePixmap(dpy, RootWindow(dpy, screen_num),
			   rc->view_width, rc->view_height,
			   DefaultDepth(dpy, screen_num));
    if (pixmap == BadAlloc || pixmap == BadValue) {
	openErrorWindow(rc->ewin, "Can't allocate a pixmap. Unable to save");
	return;
    }
    if (rc->scale > 0) {
	rgbdata = (unsigned char *)MyMalloc(3 * rc->view_width * rc->view_height, MEM_MISC);
	line = NULL;
    } else {
	line = (unsigned char *)MyMalloc(3 * rc->view_width, MEM_MISC);
	rgbdata = NULL;
    }

    for (save = begin; !done; save = save->next) {
	sprintf(buf, "Saving frame %d (of %d) ...\n",
		save->number - begin->number + 1,
		end->number - begin->number + 1);
	OverWriteMsg(rc, buf);
	XSetForeground(dpy, rc->gc, rc->pixels[BLACK]);
	XFillRectangle(dpy, pixmap, rc->gc,
		       0, 0,
		       rc->view_width, rc->view_height);
	XFlush(dpy);
	if (!save->shapes) {
	    readFrameData(rc, save);
	}
	drawShapes(save, pixmap, rc);
	XFlush(dpy);

	if (!compress) {
	    sprintf(buf, "xp%05d.ppm", save->number);
	    if (!(fp = fopen(buf, "w"))) {
		perror(buf);
		break;
	    } else {
		setvbuf(fp, NULL, _IOFBF, (size_t)(8 * 1024));
	    }
	} else {
	    sprintf(buf, "compress > xp%05d.ppm.Z", save->number);
	    if (!(fp = popen(buf, "w"))) {
		perror(buf);
		break;
	    }
	}
	if (!(rc->scale > 0)) {
	    fprintf(fp, "P6\n");
	    fprintf(fp, "%d %d\n", rc->view_width, rc->view_height);
	    fprintf(fp, "%d\n", 255);
	}

	img = XGetImage(dpy, pixmap,
			0, 0,
			rc->view_width, rc->view_height,
			AllPlanes, ZPixmap);
	for (y = 0; y < rc->view_height; y++) {
	    if (rc->scale > 0) {
		ptr = line = rgbdata + (3 * y * rc->view_width);
	    } else {
		ptr = line;
	    }
	    for (x = 0; x < rc->view_width; x++) {
		pixel = XGetPixel(img, x, y);
		for (i = 0;;) {
		    if (pixel == rc->pixels[i]) {
			break;
		    }
		    if (++i >= rc->maxColors) {
			/* impossible? */
			i = 0;
			break;
		    }
		}
		*ptr++ = rc->colors[i].red >> 8;
		*ptr++ = rc->colors[i].green >> 8;
		*ptr++ = rc->colors[i].blue >> 8;
	    }
	    if (rc->scale > 0) {
		line = ptr;
	    } else {
		fwrite(line, 1, 3 * rc->view_width, fp);
	    }
	}
	XDestroyImage(img);

	if (rc->scale > 0) {
	    sprintf(buf, "Scaling frame %d (of %d) ...\n",
		    save->number - begin->number + 1,
		    end->number - begin->number + 1);
	    OverWriteMsg(rc, buf);
	    ScalePPM(rgbdata, rc->view_width, rc->view_height,
		     rc->scale, rc->gamma, fp);
	}

	if (!compress) {
	    fclose(fp);
	} else {
	    pclose(fp);
	}

	done = (save == end);
    }

    if (rc->scale > 0) {
	MyFree(rgbdata, 3 * rc->view_width * rc->view_height, MEM_MISC);
    } else {
	MyFree(line, 3 * rc->view_width, MEM_MISC);
    }
    XFreePixmap(dpy, pixmap);

    if (done) {
	sprintf(buf, "Saved %d frames OK.\n", end->number - begin->number + 1);
	OverWriteMsg(rc, buf);
    } else {
	sprintf(buf, "Saving failed!\n");
	OverWriteMsg(rc, buf);
    }
}

static void RWriteByte(unsigned char i, FILE *fp)
{
    putc(i, fp);
}

static void RWriteShort(short i, FILE *fp)
{
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
}

static void RWriteUShort(unsigned short i, FILE *fp)
{
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
}

static void RWriteLong(long i, FILE *fp)
{
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
}

static void RWriteULong(unsigned long i, FILE *fp)
{
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
    i >>= 8;
    putc(i, fp);
}

static void RWriteString(char *str, FILE *fp)
{
    int				len = strlen(str);
    int				i;

    RWriteUShort(len, fp);
    for (i = 0; i < len; i++) {
	putc(str[i], fp);
    }
}

static int pixel2index(struct xprc *rc, unsigned long pixel)
{
    int				i;

    for (i = 0; i < 2 * rc->maxColors; i++) {
	if (rc->pixels[i] == pixel) {
	    return i;
	}
    }
    fprintf(stderr, "Can't find matching pixel\n");
    return 0;
}

static XImage *pixmap2image(Pixmap pixmap)
{
    XImage		*img;
    Window		rootw;
    int			x, y;
    unsigned		width, height, border_width, depth;

    if (!XGetGeometry(dpy, pixmap, &rootw,
		      &x, &y,
		      &width, &height,
		      &border_width, &depth)) {
	return NULL;
    }
    img = XGetImage(dpy, pixmap,
		    0, 0,
		    width, height,
		    AllPlanes, ZPixmap);
    if (!img) {
	return NULL;
    }
    return img;
}

static void RWriteTile(struct xprc *rc, struct rGC *gcp, FILE *fp)
{
    tile_list_t			*tptr;
    XImage			*img;
    int				i, x, y;

    for (tptr = rc->tlist; tptr; tptr = tptr->next) {
	if (tptr->tile == gcp->tile) {
	    break;
	}
    }
    if (!tptr || tptr->tile_id == 0 || tptr->tile == None) {
	RWriteByte(RC_TILE, fp);
	RWriteByte(0, fp);
	return;
    }
    if (tptr->flag) {
	RWriteByte(RC_TILE, fp);
	RWriteByte(tptr->tile_id, fp);
	return;
    }
    if (!(img = pixmap2image(tptr->tile))) {
	RWriteByte(RC_TILE, fp);
	RWriteByte(0, fp);
	return;
    }
    tptr->flag = 1;
    RWriteByte(RC_NEW_TILE, fp);
    RWriteByte(tptr->tile_id, fp);
    RWriteUShort(img->width, fp);
    RWriteUShort(img->height, fp);
    for (y = 0; y < img->height; y++) {
	for (x = 0; x < img->width; x++) {
	    unsigned long pixel = XGetPixel(img, x, y);
	    for (i = 0; i < rc->maxColors - 1; i++) {
		if (pixel == rc->pixels[i]) {
		    break;
		}
	    }
	    RWriteByte(i, fp);
	}
    }

    XDestroyImage(img);
}

static void RWriteGC(struct xprc *rc, struct rGC *gcp, FILE *fp)
{
    int				mask = 0;
    int				i;

    if (gcp->mask == 0) {
	RWriteByte(RC_NOGC, fp);
	return;
    }

    RWriteByte(RC_GC, fp);

    if (gcp->mask & GCForeground) {
	mask |= RC_GC_FG;
    }
    if (gcp->mask & GCBackground) {
	mask |= RC_GC_BG;
    }
    if (gcp->mask & GCLineWidth) {
	mask |= RC_GC_LW;
    }
    if (gcp->mask & GCLineStyle) {
	mask |= RC_GC_LS;
    }
    if (gcp->mask & GCDashOffset) {
	mask |= RC_GC_DO;
    }
    if (gcp->mask & GCFunction) {
	mask |= RC_GC_FU;
    }
    if (gcp->num_dashes > 0 || (gcp->mask & GCDashOffset)) {
	mask |= RC_GC_DA;
	if (gcp->mask & GCDashOffset) {
	    mask |= RC_GC_DO;
	}
    }
    if (gcp->mask & GCFillStyle) {
	mask |= RC_GC_FS;
    }
    if (gcp->mask & GCTileStipXOrigin) {
	mask |= RC_GC_XO;
    }
    if (gcp->mask & GCTileStipYOrigin) {
	mask |= RC_GC_YO;
    }
    if (gcp->mask & GCTile) {
	mask |= RC_GC_TI;
    }
    if ((mask & 0xFF00) != 0) {
	mask |= RC_GC_B2;
    }

    RWriteByte(mask, fp);
    if (mask & RC_GC_B2) {
	RWriteByte(mask >> 8, fp);
    }
    if (mask & RC_GC_FG) {
	RWriteByte(pixel2index(rc, gcp->foreground), fp);
    }
    if (mask & RC_GC_BG) {
	RWriteByte(pixel2index(rc, gcp->background), fp);
    }
    if (mask & RC_GC_LW) {
	RWriteByte(gcp->line_width, fp);
    }
    if (mask & RC_GC_LS) {
	RWriteByte(gcp->line_style, fp);
    }
    if (mask & RC_GC_DO) {
	RWriteByte(gcp->dash_offset, fp);
    }
    if (mask & RC_GC_FU) {
	RWriteByte(gcp->function, fp);
    }
    if (mask & RC_GC_DA) {
	RWriteByte(gcp->num_dashes, fp);
	for (i = 0; i < gcp->num_dashes; i++) {
	    RWriteByte(gcp->dash_list[i], fp);
	}
    }
    if (mask & RC_GC_FS) {
	RWriteByte(gcp->fill_style, fp);
    }
    if (mask & RC_GC_XO) {
	RWriteLong(gcp->ts_x_origin, fp);
    }
    if (mask & RC_GC_YO) {
	RWriteLong(gcp->ts_y_origin, fp);
    }
    if (mask & RC_GC_TI) {
	RWriteTile(rc, gcp, fp);
    }
}

static void WriteHeader(struct xprc *rc, FILE *fp)
{
    int				i;

    rewind(fp);

    /* First write out magic 4 letter word */
    putc('X', fp);
    putc('P', fp);
    putc('R', fp);
    putc('C', fp);

    /* Write which version of the XPilot Record Protocol this is. */
    putc(RC_MAJORVERSION, fp);
    putc('.', fp);
    putc(RC_MINORVERSION, fp);
    putc('\n', fp);

    /* Write player's nick, login, host, server, FPS and the date. */
    RWriteString(rc->nickname, fp);
    RWriteString(rc->realname, fp);
    RWriteString(rc->hostname, fp);
    RWriteString(rc->servername, fp);
    RWriteByte(rc->fps, fp);
    RWriteString(rc->recorddate, fp);

    /* Write info about graphics setup. */
    putc(rc->maxColors, fp);
    for (i = 0; i < rc->maxColors; i++) {
	RWriteULong(rc->colors[i].pixel, fp);
	RWriteUShort(rc->colors[i].red, fp);
	RWriteUShort(rc->colors[i].green, fp);
	RWriteUShort(rc->colors[i].blue, fp);
    }
    RWriteString(rc->gameFontName, fp);
    RWriteString(rc->msgFontName, fp);

    RWriteUShort(rc->view_width, fp);
    RWriteUShort(rc->view_height, fp);
}

static void WriteFrame(struct xprc *rc, struct frame *f, FILE *fp)
{
    /* drawShapes(save, pixmap, rc); */
    struct shape	*sp;
    int			i;

    putc(RC_NEWFRAME, fp);
    RWriteUShort(f->width, fp);
    RWriteUShort(f->height, fp);

    for (sp = f->shapes; sp != NULL; sp = sp->next) {

	switch(sp->type) {

	case RC_DRAWARC:
	    putc(RC_DRAWARC, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.arc.x, fp);
	    RWriteShort(sp->shape.arc.y, fp);
	    RWriteByte(sp->shape.arc.width, fp);
	    RWriteByte(sp->shape.arc.height, fp);
	    RWriteShort(sp->shape.arc.angle1, fp);
	    RWriteShort(sp->shape.arc.angle2, fp);
	    break;

	case RC_DRAWLINES:
	    putc(RC_DRAWLINES, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteUShort(sp->shape.lines.npoints, fp);
	    for (i = 0; i < sp->shape.lines.npoints; i++) {
		RWriteShort(sp->shape.lines.points[i].x, fp);
		RWriteShort(sp->shape.lines.points[i].y, fp);
	    }
	    RWriteByte(sp->shape.lines.mode, fp);
	    break;

	case RC_DRAWLINE:
	    putc(RC_DRAWLINE, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.line.x1, fp);
	    RWriteShort(sp->shape.line.y1, fp);
	    RWriteShort(sp->shape.line.x2, fp);
	    RWriteShort(sp->shape.line.y2, fp);
	    break;

	case RC_DRAWRECTANGLE:
	    putc(RC_DRAWRECTANGLE, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.rectangle.x, fp);
	    RWriteShort(sp->shape.rectangle.y, fp);
	    RWriteByte(sp->shape.rectangle.width, fp);
	    RWriteByte(sp->shape.rectangle.height, fp);
	    break;

	case RC_DRAWSTRING:
	    putc(RC_DRAWSTRING, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.string.x, fp);
	    RWriteShort(sp->shape.string.y, fp);
	    RWriteByte(sp->shape.string.font, fp);
	    RWriteUShort(sp->shape.string.length, fp);
	    for (i = 0; i < sp->shape.string.length; i++) {
		putc(sp->shape.string.string[i], fp);
	    }
	    break;

	case RC_FILLARC:
	    putc(RC_FILLARC, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.arc.x, fp);
	    RWriteShort(sp->shape.arc.y, fp);
	    RWriteByte(sp->shape.arc.width, fp);
	    RWriteByte(sp->shape.arc.height, fp);
	    RWriteShort(sp->shape.arc.angle1, fp);
	    RWriteShort(sp->shape.arc.angle2, fp);
	    break;

	case RC_FILLPOLYGON:
	    putc(RC_FILLPOLYGON, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteUShort(sp->shape.polygon.npoints, fp);
	    for (i = 0; i < sp->shape.polygon.npoints; i++) {
		RWriteShort(sp->shape.polygon.points[i].x, fp);
		RWriteShort(sp->shape.polygon.points[i].y, fp);
	    }
	    RWriteByte(sp->shape.polygon.shape, fp);
	    RWriteByte(sp->shape.polygon.mode, fp);
	    break;

	case RC_FILLRECTANGLE:
	    putc(RC_FILLRECTANGLE, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteShort(sp->shape.rectangle.x, fp);
	    RWriteShort(sp->shape.rectangle.y, fp);
	    RWriteByte(sp->shape.rectangle.width, fp);
	    RWriteByte(sp->shape.rectangle.height, fp);
	    break;

	case RC_PAINTITEMSYMBOL:
	    putc(RC_PAINTITEMSYMBOL, fp);
	    RWriteGC(rc, sp->gc, fp);
	    putc(sp->shape.symbol.type, fp);
	    RWriteShort(sp->shape.symbol.x, fp);
	    RWriteShort(sp->shape.symbol.y, fp);
	    break;

	case RC_FILLRECTANGLES:
	    putc(RC_FILLRECTANGLES, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteUShort(sp->shape.rectangles.nrectangles, fp);
	    for (i = 0; i < sp->shape.rectangles.nrectangles; i++) {
		RWriteShort(sp->shape.rectangles.rectangles[i].x, fp);
		RWriteShort(sp->shape.rectangles.rectangles[i].y, fp);
		RWriteByte(sp->shape.rectangles.rectangles[i].width, fp);
		RWriteByte(sp->shape.rectangles.rectangles[i].height, fp);
	    }
	    break;

	case RC_DRAWARCS:
	    putc(RC_DRAWARCS, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteUShort(sp->shape.arcs.narcs, fp);
	    for (i = 0; i < sp->shape.arcs.narcs; i++) {
		RWriteShort(sp->shape.arcs.arcs[i].x, fp);
		RWriteShort(sp->shape.arcs.arcs[i].y, fp);
		RWriteByte(sp->shape.arcs.arcs[i].width, fp);
		RWriteByte(sp->shape.arcs.arcs[i].height, fp);
		RWriteShort(sp->shape.arcs.arcs[i].angle1, fp);
		RWriteShort(sp->shape.arcs.arcs[i].angle2, fp);
	    }
	    break;

	case RC_DRAWSEGMENTS:
	    putc(RC_DRAWSEGMENTS, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteUShort(sp->shape.segments.nsegments, fp);
	    for (i = 0; i < sp->shape.segments.nsegments; i++) {
		RWriteShort(sp->shape.segments.segments[i].x1, fp);
		RWriteShort(sp->shape.segments.segments[i].y1, fp);
		RWriteShort(sp->shape.segments.segments[i].x2, fp);
		RWriteShort(sp->shape.segments.segments[i].y2, fp);
	    }
	    break;

	case RC_DAMAGED:
	    putc(RC_DAMAGED, fp);
	    RWriteGC(rc, sp->gc, fp);
	    RWriteByte(sp->shape.damage.damaged, fp);
	    break;
	}
    }

    putc(RC_ENDFRAME, fp);
}

static void SaveFramesXPR(struct xprc *rc)
{
    struct frame	*begin = rc->save_first;
    struct frame	*end = rc->save_last;
    struct frame	*save;
    int			done = 0;
    FILE		*fp;
    tile_list_t		*tptr;
    char		buf[256];

    if (!begin) {
	openErrorWindow(rc->ewin, "The first frame to save hasn't been set.");
    }
    else if (!end) {
	openErrorWindow(rc->ewin, "The last frame to save hasn't been set.");
    }
    if (!begin || !end) {
	return;
    }
    if (begin->number > end->number) {
	openErrorWindow(rc->ewin, "First save frame exceeds last save frame, "
			"not saving");
	return;
    }
    if (!rc->seekable) {
	if (!begin->shapes) {
	    openErrorWindow(rc->ewin, "Save failed for standard input");
	    return;
	}
    }
    for (tptr = rc->tlist; tptr; tptr = tptr->next) {
	tptr->flag = 0;
    }

    if (!compress) {
	sprintf(buf, "xp%d-%d.xpr", begin->number, end->number);
	if (!(fp = fopen(buf, "w"))) {
	    perror(buf);
	    return;
	} else {
	    setvbuf(fp, NULL, _IOFBF, (size_t)(8 * 1024));
	}
    } else {
	sprintf(buf, "compress > xp%d-%d.xpr.Z", begin->number, end->number);
	if (!(fp = popen(buf, "w"))) {
	    perror(buf);
	    return;
	}
    }

    WriteHeader(rc, fp);

    for (save = begin; !done; save = save->next) {
	sprintf(buf, "Saving frame %d (of %d) ...\n",
		save->number - begin->number + 1,
		end->number - begin->number + 1);
	OverWriteMsg(rc, buf);
	if (!save->shapes) {
	    readFrameData(rc, save);
	}
	WriteFrame(rc, save, fp);

	done = (save == end);
    }

    fclose(fp);

    if (done) {
	sprintf(buf, "Saved %d frames OK.\n", end->number - begin->number + 1);
	OverWriteMsg(rc, buf);
    } else {
	sprintf(buf, "Saving failed!\n");
	OverWriteMsg(rc, buf);
    }
}

static void dox(struct xui *ui, struct xprc *rc)
{
    XEvent		event;
    int			count;
    KeySym		keysym;
    XComposeStatus	compose;
    char		c;
    struct timeval	tv0, tv1;

    screen_num = DefaultScreen(dpy);
    colormap = DefaultColormap(dpy, screen_num);

    Init_topview(rc);

    readNewFrame(rc);
    if (rc->cur == NULL) {
	fprintf(stderr, "No frames, nothing to do.\n");
	return;
    }

    XMapWindow(dpy, rc->topview);
    XFlush(dpy);

    Init_topmain(ui, rc);

    XMapWindow(dpy, ui->topmain);
    XFlush(dpy);

    rc->ewin = ui->ewin;

    gettimeofday(&tv0, NULL);

    for (;;) {

	while (XEventsQueued(dpy, QueuedAfterFlush) > 0
	       || (!currentSpeed && !forceRedraw && !frameStep)) {

	    XNextEvent(dpy, &event);

	    if (CheckButtonEvent(&event)) {
		if (quit)
		    return;
		continue;
	    }

	    switch(event.type) {

	    case ClientMessage:
		if (event.xclient.message_type == ProtocolAtom
		    && event.xclient.data.l[0] == KillAtom) {
		    return;
		}
		break;

	    case Expose:
		if (event.xexpose.count != 0)
		    break;
		if (event.xany.window == rc->topview) {
		    forceRedraw = True;
		}
		if (event.xany.window == ui->topmain) {
		    redrawMain(ui, rc);
		}
		if (event.xany.window == ui->ewin->win) {
		    redrawError(ui->ewin);
		}
		break;

	    case ButtonPress:
		if (event.xany.window == rc->topview) {
		    if (event.xbutton.button == 1) {
			frameStep--;
		    }
		    else if (event.xbutton.button == 2) {
			frameStep++;
		    }
		}
		break;

	    case KeyRelease:
		break;

	    case KeyPress:
		count = XLookupString(&(event.xkey), &c, 1,
				      &keysym, &compose);
		if (count == NoSymbol) {
		    break;
		}

		switch(c) {

		case 'f':
		case 'F':
		case ' ':
		    frameStep++;
		    break;

		case 'b':
		case 'B':
		case '\b':
		case '\177':
		    frameStep--;
		    break;

		case 'z':
		case 'Z':
		    frameStep = -rc->cur->number;
		    break;

		case '[':
		    rc->save_first = rc->cur;
		    break;
		case ']':
		    rc->save_last = rc->cur;
		    break;
		case '*':
		    SaveFramesPPM(rc);
		    break;
		case '&':
		    SaveFramesXPR(rc);
		    break;

		case 'd':
		case 'D':
		    MemPrint();
		    break;

		case 'q':
		case 'Q':
		    XFreeGC(dpy, rc->gc);
		    return;
		}

	    default:
		break;
	    }

	}

	gettimeofday(&tv1, NULL);
	if (frameStep != 0) {
	    tv0 = tv1;
	}
	else if (!forceRedraw && currentSpeed != 0) {
	    long	delta_sec = tv1.tv_sec - tv0.tv_sec;
	    long	delta_usec = (long)tv1.tv_usec - (long)tv0.tv_usec;
	    long	delta_time = 1000000L * delta_sec + delta_usec;
	    long	frame_rate = 1000000L / rc->fps;

	    if (delta_time + 1000 < frame_rate) {
		fd_set	rset;
		int	rfd = ConnectionNumber(dpy);
		int	num;

		tv1.tv_sec = 0;
		tv1.tv_usec = frame_rate - delta_time;
		FD_ZERO(&rset);
		FD_SET(rfd, &rset);
		num = select(rfd + 1, &rset, NULL, NULL, &tv1);
		if (num == 1) {
		    continue;
		}
		tv0.tv_usec = tv0.tv_usec + frame_rate;
		if (tv0.tv_usec >= 1000000) {
		    tv0.tv_usec -= 1000000;
		    tv0.tv_sec++;
		}
	    }
	    else {
		tv0 = tv1;
	    }
	    frameStep += currentSpeed;
	}

	while (frameStep > 0) {
	    if (rc->cur->next == NULL) {
		if (rc->eof == False) {
		    readNewFrame(rc);
		}
	    }
	    if (rc->cur->next != NULL) {
		rc->cur = rc->cur->next;
		forceRedraw = True;
		frameStep--;
	    }
	    else {
		frameStep = 0;
	    }
	}
	while (frameStep < 0) {
	    if (rc->cur->prev != NULL) {
		if (!rc->seekable && rc->cur->prev->shapes == NULL) {
		    static int before;
		    if (!before++) {
			openErrorWindow(rc->ewin,
					"Can't go backwards any further, "
					"because input is not a regular "
					"file.");
		    }
		    frameStep = 0;
		}
		else {
		    rc->cur = rc->cur->prev;
		    forceRedraw = True;
		    frameStep++;
		}
	    }
	    else {
		frameStep = 0;
	    }
	}

	if (forceRedraw == True) {
	    redrawWindow(rc);
	    redrawLabel(ui, rc, &(ui->labels[LABEL_POSITION]));
	}
    }
}

static void TestInput(struct xprc *rc)
{
    int			fd = fileno(rc->fp);
    struct stat		st;
    unsigned char	ch0, ch1;
    char		buf[1024];

    rc->seekable = False;
    if (fstat(fd, &st)) {
	perror("Can't stat input");
	return;
    }
    rc->seekable = S_ISREG(st.st_mode);
    if (rc->seekable) {
	ch0 = getc(rc->fp);
	ch1 = getc(rc->fp);
	rewind(rc->fp);
	if (ch0 == 0x1F && ch1 == 0x9D) {
	    if (verbose) {
		fprintf(stderr, "%s: \"%s\" is in compressed format, starting compress...\n",
			*Argv, rc->filename);
	    }
	    lseek(fd, 0L, SEEK_SET);
	    if (rc->fp == stdin) {
		sprintf(buf, "compress -d");
	    } else {
		fclose(rc->fp);
		sprintf(buf, "compress -d < %s", rc->filename);
	    }
	    if ((rc->fp = popen(buf, "r")) == NULL) {
		perror("Unable to start compress");
		exit(1);
	    }
	    rc->seekable = 0;
	}
	if (ch0 == 0x1F && ch1 == 0x8B) {
	    if (verbose) {
		fprintf(stderr, "%s: \"%s\" is in gzip format, starting gzip...\n",
			*Argv, rc->filename);
	    }
	    lseek(fd, 0L, SEEK_SET);
	    if (rc->fp == stdin) {
		sprintf(buf, "gzip -d");
	    } else {
		fclose(rc->fp);
		sprintf(buf, "gzip -d < %s", rc->filename);
	    }
	    if ((rc->fp = popen(buf, "r")) == NULL) {
		perror("Unable to start gzip");
		exit(1);
	    }
	    rc->seekable = 0;
	}
    }
    if (!rc->seekable) {
	if (verbose) {
	    fprintf(stderr,
		    "Input is not a regular file, this may result\n"
		    "in limited reverse playback functionality.\n");
	}
    }
    else {
	if (max_mem > 1 * 1024 * 1024) {
	    max_mem = 1 * 1024 * 1024;
	}
    }
}

static void usage(void)
{
    fprintf(stderr, "Usage: %s [options] filename\n", *Argv);
    fprintf(stderr,
"    If filename is a dash - then standard input is used.\n"
"    Valid options are:\n"
"        -scale \"factor\"\n"
"               Set the scale reduction factor for saving operations.\n"
"               Valid scale factors are in the range [0.01 - 1.0].\n"
"        -gamma \"factor\"\n"
"               Set the gamma correction factor when saving scaled frames.\n"
"               Valid gamma correction factors are in the range [0.1 - 10].\n"
"        -compress\n"
"               Save frames compressed using the \"compress\" program.\n"
"        -debug\n"
"        -verbose\n"
"        -help\n"
"    In addition to the pushbuttons you can use the following keys:\n"
"        f  -  move forwards to the next frame.\n"
"        b  -  move backwards to the next frame.\n"
"        z  -  move backwards to the first frame.\n"
"        [  -  mark the current frame as the first frame to be saved.\n"
"        ]  -  mark the current frame as the last frame to be saved.\n"
"        *  -  save the marked frames in PPM format.\n"
"              WARNING: saving many frames takes HUGE amounts of diskspace!\n"
"        &  -  save the marked frames in XPilot Recording format.\n"
"        q  -  quit the program.\n"
    );
    exit(2);
}

int main(int argc, char **argv)
{
    FILE		*fp;
    int			argi;
    char		*filename;
    struct xprc		*rc;
    struct xui		*ui;
    double		scale = 0;
    double		gamma = 0;

    Argc = argc;
    Argv = argv;

    for (argi = 1; argi < argc; argi++) {
	if (argv[argi][0] != '-' || argv[argi][1] == '\0') {
	    break;
	}
	else if (!strcmp(argv[argi], "-debug")) {
	    debug = 1;
	    verbose = 1;
	}
	else if (!strcmp(argv[argi], "-verbose")) {
	    verbose = 1;
	}
	else if (!strcmp(argv[argi], "-compress")) {
	    compress = 1;
	}
	else if (!strcmp(argv[argi], "-scale")) {
	    if (++argi == argc || sscanf(argv[argi], "%lf", &scale) != 1) {
		usage();
	    }
	    if (scale < 0.01 || scale > 1.0) {
		usage();
	    }
	    if (scale >= 1.0) {
		scale = 0;
	    }
	}
	else if (!strcmp(argv[argi], "-gamma")) {
	    if (++argi == argc || sscanf(argv[argi], "%lf", &gamma) != 1) {
		usage();
	    }
	    if (gamma < 0.1 || gamma > 100) {
		usage();
	    }
	    if (gamma == 1.0) {
		gamma = 0;
	    }
	}
	else {
	    if (!strncmp(argv[argi], "-h", 2) ||
		!strncmp(argv[argi], "--h", 3) ||
		!strcmp(argv[argi], "-?")) {
		usage();
	    }
	    else if (argi < argc - 1) {
		fprintf(stderr, "%s: Unknown option \"%s\"\n", Argv[0], argv[argi]);
		fprintf(stderr, "\tType: \"%s -help\" for some help.\n", Argv[0]);
		exit(2);
	    }
	    else {
		break;
	    }
	}
    }
    if (argi != argc - 1) {
	usage();
    }
    filename = argv[argc - 1];
    if (!strcmp(filename, "-")) {
	fp = stdin;
    } else {
	if ((fp = fopen(filename, "r")) == NULL) {
	    perror("Unable to open record file");
	    fprintf(stderr, "Type: \"%s -help\" to get some help.\n", *argv);
	    return 1;
	}
    }

    if ((dpy = XOpenDisplay(NULL)) == NULL) {
	fprintf(stderr, "Cannot connect to X server %s\n", XDisplayName(NULL));
	exit(1);
    }

    ui = (struct xui *) MyMalloc(sizeof(*ui), MEM_UI);
    memset(ui, 0, sizeof(*ui));

    rc = (struct xprc *) MyMalloc(sizeof(*rc), MEM_MISC);
    memset(rc, 0, sizeof(*rc));
    rc->filename = filename;
    rc->fp = fp;
    rc->scale = scale;
    rc->gamma = gamma;
    TestInput(rc);
    purge_argument = rc;
    if (RReadHeader(rc) >= 0) {
	dox(ui, rc);
	FreeXPRCData(rc);
    }
    fp = rc->fp;

    MyFree(rc, sizeof(struct xprc), MEM_MISC);
    MyFree(ui, sizeof(struct xui), MEM_UI);
    XCloseDisplay(dpy);

    if (fp != NULL && fp != stdin) {
	fclose(fp);
    }

    MemPrint();

    return 0;
}

/* ARGSUSED */
static void quitCallback(void *data)
{
    quit = 1;
}

static void stopCallback(void *data)
{
    struct xui *ui = (struct xui *) data;

    playState = STATE_PLAYING;
    currentSpeed = 0;
    forceRedraw = True;
    NonreleaseableButton(ui->buttons[BUTTON_REWIND]);
    NonreleaseableButton(ui->buttons[BUTTON_REVERSE_PLAY]);
    NonreleaseableButton(ui->buttons[BUTTON_PLAY]);
    NonreleaseableButton(ui->buttons[BUTTON_FAST_FORWARD]);
    ChangeButtonGroup(ui->buttons[BUTTON_REWIND], 1);
    ChangeButtonGroup(ui->buttons[BUTTON_REVERSE_PLAY], 1);
    ChangeButtonGroup(ui->buttons[BUTTON_PLAY], 1);
    ChangeButtonGroup(ui->buttons[BUTTON_FAST_FORWARD], 1);
}

static void pauseCallback(void *data)
{
    struct xui *ui = (struct xui *) data;

    playState = STATE_PAUSED;
    currentSpeed = 0;
    forceRedraw = True;
    ReleaseableButton(ui->buttons[BUTTON_REWIND]);
    ReleaseableButton(ui->buttons[BUTTON_REVERSE_PLAY]);
    ReleaseableButton(ui->buttons[BUTTON_PLAY]);
    ReleaseableButton(ui->buttons[BUTTON_FAST_FORWARD]);
    ChangeButtonGroup(ui->buttons[BUTTON_REWIND], 0);
    ChangeButtonGroup(ui->buttons[BUTTON_REVERSE_PLAY], 0);
    ChangeButtonGroup(ui->buttons[BUTTON_PLAY], 0);
    ChangeButtonGroup(ui->buttons[BUTTON_FAST_FORWARD], 0);
}

/* ARGSUSED */
static void rewindCallback(void *data)
{
    switch(playState)
    {
    case STATE_PLAYING:
	currentSpeed = -10;
	break;
    case STATE_PAUSED:
	frameStep -= 10;
	break;
    }
}

/* ARGSUSED */
static void fastfCallback(void *data)
{
    switch(playState)
    {
    case STATE_PLAYING:
	currentSpeed = 10;
	break;
    case STATE_PAUSED:
	frameStep += 10;
	break;
    }
}

/* ARGSUSED */
static void playCallback(void *data)
{
    switch(playState)
    {
    case STATE_PLAYING:
	currentSpeed = 1;
	break;
    case STATE_PAUSED:
	frameStep++;
	break;
    }
}

/* ARGSUSED */
static void revplayCallback(void *data)
{
    switch(playState)
    {
    case STATE_PLAYING:
	currentSpeed = -1;
	break;
    case STATE_PAUSED:
	frameStep--;
	break;
    }
}

static void recordCallback(void *data)
{
    struct xui *ui = (struct xui *) data;

    XMapWindow(dpy, ui->rwin->win);
}

static void markSaveStart(void *data)
{
    struct xprc		*rc = (struct xprc *) data;

    rc->save_first = rc->cur;
}

static void markSaveEnd(void *data)
{
    struct xprc		*rc = (struct xprc *) data;

    rc->save_last = rc->cur;
}

static void saveStartToEndPPM(void *data)
{
    struct xprc		*rc = (struct xprc *) data;

    SaveFramesPPM(rc);
}

static void saveStartToEndXPR(void *data)
{
    struct xprc		*rc = (struct xprc *) data;

    SaveFramesXPR(rc);
}

