/* -*- Mode: C; tab-width: 4 -*- */
/* matrix --- screensaver inspired by the 1999 sci-fi flick "The Matrix" */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)matrix.c	5.00 2000/11/01 xlockmore";
#endif

/* Matrix-style screensaver
 *
 * Author: Erik O'Shaughnessy (eriko@xenolab.com)  20 Apr 1999
 *
 * 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 file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 23-Jul-1999: Unleashed on an unsuspecting world
 * 29-Jun-1999: More hacking by Jeremy Buhler (jbuhler@cs.washington.edu)
 *              - fix memory-related bugs found by xlock maintainer
 *              - properly reinitialize screen with new size when init()
 *                is called multiple times
 *              - reinstate multiple final characters (suggested by
 *                Joan Touzet)
 *              - reduce delay to 1 ms (provisionally)
 *              - decouple pixmap height from screen height, to permit
 *                less memory use and shorter, more frequent updates
 *                (for smoother scrolling on slow boxes, but nothing
 *                will help if the machine is busy.  Also restores
 *                broken display on very small windows).
 *              - nevertheless, certain display parameters such as the
 *                interval between column speed updates should depend on
 *                the screen height -- enforce this at runtime.
 *              - fix column spacing to use entire window
 *              - remove redundant size fields from matrix_t and column_t
 *                structures -- rely on character size and window size
 *                instead.
 *              - consistently use PICK_MATRIX() instead of passing
 *                mp as a parameter whenever the active screen is
 *                implicitly required.
 * 27-Jun-1999: Overhauled by Jeremy Buhler (jbuhler@cs.washington.edu)
 *              - use screen-to-screen copies as much as possible
 *              - get by with a single pixmap per column instead of two
 *              - try to optimize draw_matrix() and new_column() as much
 *                as humanly possible
 *              - fixed double-freeing in release_matrix()
 *              - increased delay to 10 ms to prevent hyperfast scrolling
 *              - minor cleanups; removed unused or duplicate code/data
 * 26-Jun-1999: Tweaks by Jeremy Buhler (jbuhler@cs.washington.edu)
 *              - Use larger Katakana font with numbers
 *              - tweak character colors
 *              - change string and gap distributions for lower screen density
 *              - Matrix strings end with a fixed character
 *              - fix scrolling artifacts due to gaps between two column
 *                pixmaps
 *              - minor code cleanups to allow future parameterization
 *                of speed, density
 * 09-Jun-1999: Tweaks by Joan Touzet to look more like the original film's
 *              effects
 * 22-Apr-1999: Initial version
 */

#ifdef VMS
#include "vms_x_fix.h"

/*-
 * due to a Bug/feature in VMS X11/Intrinsic.h has to be placed before xlock.
 * otherwise caddr_t is not defined correctly
 */

#include <X11/Intrinsic.h>
#endif

#ifdef STANDALONE
#define MODE_matrix
#define PROGCLASS "Matrix"
#define HACK_INIT init_matrix
#define HACK_DRAW draw_matrix
#define matrix_opts xlockmore_opts
#define DEFAULTS "*delay: 1000\n"
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#endif /* STANDALONE */

ModeSpecOpt matrix_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct   matrix_description =
{"matrix", "init_matrix", "draw_matrix", "release_matrix",
 "refresh_matrix", "change_matrix", (char *) NULL, &matrix_opts,
 1000, 1, 1, 1, 64, 1.0, "",
 "Shows the Matrix", 0, NULL};
#endif

#ifdef MODE_matrix
#ifndef VMS
#ifndef WIN32
#include <X11/Intrinsic.h>
#endif /* WIN32 */
#endif

/*
 * state associated with a single column of the waterfall display
 */
typedef struct {
	int yPtr;		/* current offset relative to pixmap origin */
	Pixmap pixmap;

	int strLen;		/* remaining length of current string */
	int gapLen;		/* remaining length of current gap    */
	Bool endChar;		/* is the next char an ending char?   */

	int speed;		/* pixels downward per update */
	int nextSpeedUpdate;	/* time until next change in speed */

} column_t;


/*
 * Matrix display parameters associated with a particular X screen
 */
typedef struct {
	column_t *columns;      /* columns of the Matrix display */
	int num_cols;		/* number of columns of Matrix data
				 * (depends on screen width) */

	int charsPerPixmap;	/* height of column pixmaps in characters
				 * (depends on screen height) */

	int pixmapHeight;	/* height of column pixmaps in pixels
                                 * (depends on screen height) */

	int speedUpdateInterval; /* number of characters or spaces that
				 * must be written to column pixmap before
				 * the column speed is changed
				 * (depends on screen height) */

	Pixmap kana[2];		/* pixmap containing katakana
				 * [0] is fg on bg
				 * [1] is bold on bg */

	Pixel fg;		/* foreground */
	Pixel bg;		/* background */
	Pixel bold;		/* standout foreground color */
} matrix_t;


/* These Katakana font bitmaps were taken from the file 12x24rk.bdf
 * distributed with XFree86 3.3.3.  The numbers were taken from the file
 * 10x20.bdf, since the 12x24rk numbers were rather large compared to
 * the other characters.
 */
#define katakana_cell_width  12
#define katakana_cell_height 24
#define katakana_num_cells   65

#define katakana_width 780
#define katakana_height 24

static const unsigned char katakana_bits[] = {
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdf,0xff,0xff,0xff,
 0xff,0xfc,0xff,0xff,0xff,0xef,0xff,0xff,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0xff,0xff,0xff,0xff,0xff,
 0x7f,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xef,0x9f,0xff,0xff,0x9f,0xff,0xfd,0xef,0xff,0xf9,0xcf,0xff,
 0xff,0x7f,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xfd,0xff,0xff,0xfe,0xfb,0xfc,
 0xfc,0x9f,0xff,0xff,0xff,0xff,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xfc,0xff,0x3f,0xf8,0xcf,0xff,0xe7,0xff,0xff,0xfe,0xff,0xff,0xff,0x03,0x3c,
 0xc7,0xdf,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfd,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xcf,0x9f,0xff,0xff,
 0xbf,0xff,0xf9,0xcf,0xff,0xf9,0xcf,0xff,0xff,0x73,0x3e,0xff,0x7b,0xfc,0xfe,
 0xfb,0xfc,0xfd,0xff,0xdc,0x9c,0x03,0xfc,0xfc,0x9f,0xff,0xff,0xff,0xff,0xf9,
 0x7f,0xfe,0xfb,0xff,0xff,0xdf,0xff,0xff,0xf9,0xff,0x7f,0xf0,0x9f,0xff,0xef,
 0xff,0xff,0xfe,0xff,0x9f,0x9f,0x07,0x7c,0xcf,0x9f,0xff,0xff,0xff,0x9f,0xdf,
 0xff,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0x01,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0x1f,0xf8,0xcf,0x9f,0xff,0xff,0x3f,0xff,0xf9,0xdf,0xff,0xf9,0xcf,
 0x9f,0xdf,0x67,0x7e,0xff,0x03,0xf8,0xfc,0xfb,0xf9,0xcd,0x0f,0xdc,0x9d,0x07,
 0xff,0xfc,0xbf,0xff,0xff,0x1b,0xfc,0xf9,0xff,0x7c,0xf2,0xf9,0xdf,0x80,0xff,
 0xff,0xf9,0xff,0xff,0xc3,0x9f,0xff,0xcf,0xff,0xfe,0xfe,0xff,0x3f,0x80,0xff,
 0x7f,0xcf,0x93,0xbf,0xff,0xff,0x1f,0x80,0xf9,0x0f,0x9f,0xff,0xf9,0x0f,0xff,
 0xf0,0xff,0x3e,0xc0,0x0f,0x3f,0xc0,0x0f,0xff,0xf0,0x01,0xf8,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x05,0xf8,0xcf,0x9d,0xbd,
 0xc3,0x3f,0xff,0xf9,0xdf,0xff,0xdd,0xef,0x3f,0x80,0x67,0x7e,0xfe,0x87,0xfd,
 0xfc,0xf3,0xfd,0xc1,0x03,0x9f,0x9d,0xff,0xff,0xfc,0xbf,0xff,0xff,0x03,0xf8,
 0xef,0xff,0xfc,0xf4,0xf3,0x1f,0x80,0xcf,0xff,0xf9,0x3f,0xf8,0x8f,0x9f,0xff,
 0xcf,0x03,0xfc,0xfe,0xff,0x3f,0x80,0xff,0x7f,0xcf,0x87,0x3f,0xff,0xfb,0x3d,
 0x80,0xfb,0x0f,0x0f,0xff,0xf8,0x67,0x7e,0xe6,0x7f,0x3e,0xff,0x67,0xfe,0xcf,
 0x67,0x7e,0xe6,0xff,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xc1,0xf9,0xe7,0x01,0x38,0xc0,0x3f,0xff,0x81,0xdf,0xff,0x80,
 0xef,0x3f,0x80,0x67,0xfe,0xfe,0xff,0xfd,0xfc,0xf7,0xfd,0xc0,0xa1,0xbf,0x99,
 0xff,0xff,0xfc,0xbf,0xff,0xff,0xc7,0xfd,0xe1,0xff,0xfc,0xe4,0xfb,0x3f,0xcf,
 0xcf,0xdf,0x01,0x01,0xf0,0x9f,0xdf,0xff,0xcf,0x03,0xff,0x0c,0x7f,0xfe,0x9f,
 0xff,0x7f,0xcf,0x97,0x3f,0xff,0x03,0xbc,0xcf,0xf3,0x0f,0x67,0x7e,0xf8,0xf3,
 0x3c,0xcf,0x3f,0x3e,0xff,0xf3,0xfe,0xcf,0xf3,0x3c,0xcf,0xff,0xfc,0xff,0x7f,
 0xff,0xfd,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfb,0xfc,0xe7,0x03,
 0x3c,0xf8,0x3f,0xdf,0x80,0x1f,0xfc,0x80,0x67,0xf0,0xdf,0x67,0xfe,0x7f,0xff,
 0xfc,0xbc,0xe7,0xfc,0xce,0xbf,0x3f,0x99,0xff,0xff,0xfc,0xbf,0x3f,0xc0,0xff,
 0xdd,0xe0,0xff,0xfe,0xee,0xfb,0xff,0xcf,0x87,0x1f,0x00,0x81,0xf1,0xff,0xdf,
 0xff,0xcf,0xdf,0xff,0x00,0x01,0xfe,0x9f,0xff,0x7f,0xcf,0x97,0x3f,0xff,0x03,
 0xbc,0xcf,0xf7,0x0f,0x67,0x3e,0xf9,0xf3,0x3c,0xcf,0x1f,0x3e,0xff,0xf3,0xff,
 0xe7,0xf3,0x3c,0xcf,0xff,0xfc,0xc0,0x7f,0xfe,0xf9,0xff,0xff,0xfb,0xef,0xff,
 0xff,0xff,0xbf,0xfc,0x5f,0xfe,0xf3,0xfb,0xfc,0xf9,0x3d,0x10,0x98,0x05,0xfc,
 0xde,0x07,0xf0,0xdf,0x67,0xd8,0x3f,0xff,0xfe,0x00,0xe7,0x7c,0xee,0xbf,0x3f,
 0x99,0x04,0xf0,0xfc,0x3d,0x38,0xe0,0xff,0x1c,0xe4,0x7f,0xfe,0xce,0xfb,0xfc,
 0xcf,0xa7,0x1f,0xf8,0xf9,0x39,0xfe,0xcf,0x7f,0xee,0xdf,0xff,0xa0,0x03,0xfe,
 0x9f,0x0c,0x78,0xcf,0x97,0x3f,0x7f,0xf3,0xbc,0xcf,0xe7,0x0b,0xf3,0xfc,0xf9,
 0xff,0xfc,0xcf,0x4f,0x3e,0xff,0xf3,0xff,0xe7,0xf3,0x3c,0xcf,0xff,0x3c,0xc0,
 0xff,0xfe,0xf9,0xff,0xff,0xf3,0xcf,0xff,0xff,0xff,0xbc,0xcd,0x1f,0xfe,0xf3,
 0xfb,0xfc,0xf9,0x01,0x10,0x99,0x81,0x7f,0xce,0x07,0xff,0xdf,0x01,0x90,0xbf,
 0xff,0x6e,0x80,0xe7,0x7c,0xe6,0xbf,0x3f,0x99,0x01,0xf0,0xf0,0x01,0xf0,0xff,
 0xff,0x3c,0xf3,0x7f,0x7e,0xce,0x7b,0xfc,0xcf,0x37,0xff,0xf9,0xff,0x7c,0xf8,
 0xcf,0xff,0xec,0xdf,0x1f,0x9c,0x7f,0xfe,0x9f,0x01,0x78,0xcf,0x97,0x3f,0x3f,
 0xf3,0xbc,0xcf,0xe7,0x0b,0xf3,0xfc,0xf9,0xff,0xfc,0xe7,0x67,0x3e,0xf1,0x13,
 0xff,0xf3,0x67,0x3e,0xcf,0x03,0x3c,0xce,0x7f,0xbe,0xe9,0xff,0xff,0xf3,0xcf,
 0xfe,0xff,0x03,0x3c,0xc9,0x1f,0xff,0xf9,0xfb,0xfc,0xf9,0x01,0xff,0x99,0x9b,
 0x7f,0xef,0x33,0xff,0xdf,0x01,0x3e,0x9f,0x7f,0x0e,0xdc,0xe7,0x3e,0xe6,0x3f,
 0x38,0xdb,0xb9,0xff,0xe0,0x81,0xff,0xff,0xe7,0xfe,0xf3,0x7f,0x7f,0xde,0x1b,
 0xff,0xcf,0x73,0x7f,0xf1,0xff,0xfc,0xf0,0x6f,0xff,0xe0,0xdf,0x1f,0xdc,0x7f,
 0xfe,0x9f,0xf1,0x79,0xcf,0x97,0x3f,0xbf,0xf3,0xbc,0xcf,0xe7,0x09,0xf3,0xfc,
 0xf9,0x7f,0xfe,0xf1,0x73,0x3e,0xe6,0x63,0xfe,0xf3,0x0f,0x7f,0xc6,0x03,0xfc,
 0xef,0x3f,0x3f,0xe0,0xff,0xff,0xf3,0x0f,0xfc,0xff,0x07,0x7c,0xcb,0x9f,0xff,
 0xf9,0xfb,0xfc,0xf9,0x3f,0xff,0x9d,0x9f,0x3f,0xef,0x3b,0xff,0xdf,0x67,0x7e,
 0xde,0x7f,0x9f,0xcc,0xff,0xbe,0xf4,0x05,0xf8,0xcf,0xbf,0xff,0xc4,0x9f,0xff,
 0xff,0x4f,0xfe,0xf9,0x3f,0x7f,0x9e,0x83,0xff,0xcf,0x7b,0x7e,0xe9,0x7f,0xfe,
 0xe3,0x6f,0xff,0xf1,0x1f,0x98,0xdd,0x7f,0xfe,0x9f,0xff,0x79,0xcf,0x97,0x37,
 0x9f,0xf3,0xbc,0xcf,0xff,0x0d,0xf3,0xfc,0xf9,0x1f,0xff,0xe7,0x73,0xfe,0xcf,
 0xf3,0xfc,0xf9,0x67,0xfe,0xc8,0xff,0xfc,0xe1,0x3f,0x3f,0xe0,0x03,0x3e,0xc0,
 0x0f,0xfc,0xe7,0xff,0x7c,0xcb,0x9f,0xff,0xf8,0xfb,0xfc,0xf9,0x3f,0xff,0x9d,
 0x9f,0xbf,0xe7,0x39,0xff,0xdf,0x67,0x7e,0xce,0x3f,0xff,0xec,0x7f,0x9e,0xf0,
 0x81,0xff,0xcf,0xbf,0xff,0xcc,0x9f,0xff,0xff,0x4f,0xff,0xf1,0x3f,0x7f,0x9f,
 0xc3,0xff,0xe7,0xf9,0x7e,0xc9,0x7b,0xfe,0xc7,0x67,0xfe,0xf3,0x04,0xf8,0xcd,
 0x7f,0x3e,0x80,0xff,0x7c,0xcf,0x97,0x33,0xdf,0xf3,0xbc,0xcf,0xff,0x0c,0xf3,
 0xfc,0xf9,0xcf,0xff,0xcf,0x03,0xfc,0xcf,0xf3,0xfc,0xf9,0xf3,0xfc,0xcf,0x7f,
 0xfe,0xf1,0x1f,0x3f,0xef,0x03,0x3e,0xc0,0xc1,0x7c,0xe0,0xff,0x7c,0xca,0x9f,
 0x7f,0xfa,0xfb,0xfc,0xf9,0x1f,0xff,0x9d,0x9f,0x90,0xf7,0x3d,0xff,0xdf,0x67,
 0xfe,0xef,0xbf,0xff,0xe4,0x7f,0xdf,0xf1,0x99,0xff,0xcf,0xbf,0xff,0xdc,0x9f,
 0xff,0xff,0x1f,0xff,0xe0,0xbf,0x7f,0xbf,0xfb,0xff,0xe7,0xfd,0x3c,0xd9,0x73,
 0xff,0xcf,0xe7,0xfe,0xe7,0xc1,0xff,0xed,0x7f,0x7e,0xc0,0xff,0x7c,0xcf,0x97,
 0x3b,0xcf,0xf3,0xfc,0xcf,0xff,0x0e,0x67,0xfe,0xf9,0xe7,0x3f,0xcf,0x7f,0xfe,
 0xcf,0xf3,0xfc,0xfc,0xf3,0xfc,0xcf,0x7f,0xfe,0xf9,0x0f,0x3f,0xef,0x9f,0xff,
 0xf1,0xc3,0x7e,0xe0,0xff,0xfc,0xee,0x9f,0x3f,0xfb,0xfb,0xfe,0xf9,0x5f,0xff,
 0x9d,0x0f,0xd0,0xf7,0x3d,0xff,0xcf,0x67,0xfe,0xe7,0x1f,0xfe,0xf4,0x3f,0xff,
 0xf3,0x9f,0xff,0xef,0x9f,0xff,0xfc,0x9f,0xff,0xff,0x1f,0x7f,0xc0,0x9f,0x3f,
 0xbf,0xfb,0xff,0xf7,0xff,0x3d,0x99,0x27,0xff,0xff,0xe7,0xfc,0xe3,0xd9,0xff,
 0xe5,0x7f,0xff,0xdf,0x7f,0x7e,0xcf,0x97,0x39,0xef,0xf3,0xfc,0xef,0x7f,0x0e,
 0x67,0xfe,0xf9,0xf3,0x3f,0xcf,0x7f,0x3e,0xcf,0xf3,0xfc,0xfc,0xf3,0x7c,0xcf,
 0x7f,0xfe,0xf9,0x0f,0x3f,0xef,0x9f,0xff,0xf1,0x5f,0xfe,0xe7,0x07,0xfc,0xef,
 0x9f,0x9f,0xfb,0x7b,0xfe,0xf9,0x4f,0xff,0x9c,0x00,0xff,0xf3,0xbf,0xff,0xcf,
 0x67,0xfe,0xf7,0x5f,0xfe,0xf0,0xbf,0xff,0xf1,0x9f,0xff,0xe7,0x9f,0xff,0xfc,
 0x9f,0xff,0xff,0x1f,0x3e,0x88,0xdf,0x3f,0xbf,0xfb,0xff,0xf3,0xff,0xb9,0x99,
 0x8f,0xff,0xff,0xf7,0xfc,0xcb,0xdf,0xff,0xf1,0x7f,0xff,0xdf,0x7f,0x7e,0xef,
 0x97,0x3d,0xe7,0xf3,0xfc,0xe7,0x7f,0x0f,0x0f,0xff,0xf9,0xf3,0x7f,0xe6,0x7f,
 0x7e,0xe6,0x67,0x7e,0xfe,0x67,0x7e,0xe6,0x3f,0xff,0xfd,0x27,0x3f,0xe7,0x9f,
 0xff,0xf0,0x5f,0xff,0xe7,0x07,0xfe,0xe7,0xdf,0xdf,0xfb,0x7f,0xfe,0xb9,0x6f,
 0xff,0xdc,0xa1,0xff,0xfb,0xbf,0xff,0xcf,0x67,0xfe,0xf3,0xcf,0xfc,0xfc,0x9f,
 0xff,0xf5,0x9f,0xff,0xf7,0xdf,0xff,0xfc,0xdf,0xff,0xff,0xdf,0x1e,0x99,0xcf,
 0xbf,0xbf,0xfb,0xff,0xfb,0xff,0x99,0xb9,0x9f,0x3f,0xfe,0xf3,0xf9,0xd9,0xdf,
 0xff,0xf9,0x7f,0xff,0xdf,0x3f,0xff,0xe7,0x97,0x3c,0xf7,0xf3,0xfc,0xe7,0x3f,
 0x0f,0x9f,0x3f,0xc0,0x03,0xfc,0xf0,0x7f,0xfe,0xf0,0x0f,0x7f,0xfe,0x0f,0xff,
 0xf0,0x3f,0xff,0xfd,0x37,0xff,0xf7,0x9f,0xff,0xf2,0xdf,0xff,0xe7,0xff,0xfe,
 0xf7,0xcf,0xff,0xfb,0x3f,0xcf,0x00,0x67,0xff,0xde,0xbf,0xff,0xf9,0x9f,0x1f,
 0xc0,0x67,0xff,0xf9,0xef,0xfd,0xfc,0xdf,0xff,0xfc,0xdf,0xff,0xf3,0xcf,0xff,
 0xfc,0xdf,0xdf,0x00,0xcf,0x8c,0x39,0xef,0xbf,0xbf,0xfb,0xff,0xf9,0xff,0xc3,
 0xb9,0x3f,0x3f,0xf8,0x33,0xf8,0x9d,0xdf,0xff,0xf9,0x7c,0xff,0xdf,0xbf,0xff,
 0xe7,0x93,0x3e,0xf3,0x03,0xfc,0xf7,0x9f,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x9f,0xff,0xfd,0x3f,0xff,0xf3,
 0x9f,0x7f,0xf2,0xdf,0xff,0xe7,0xff,0xfe,0xf3,0xef,0xff,0xfb,0x3f,0x1f,0x00,
 0x77,0x7f,0xda,0xbf,0xff,0xfc,0xdf,0x3f,0xc0,0x7f,0xbf,0xfc,0xe7,0xf9,0xfc,
 0xcf,0xff,0xfe,0xcf,0xff,0xf9,0xef,0xff,0xfc,0xcf,0x1f,0x00,0xef,0xfd,0x39,
 0xe7,0x9f,0x3f,0xfb,0xff,0xfd,0xff,0x43,0xb8,0x3f,0xff,0xf1,0x01,0xf8,0xfc,
 0xdf,0xff,0xfb,0x01,0xf0,0xdf,0x9f,0xff,0xe7,0x13,0x3e,0xf9,0x03,0xfc,0xf3,
 0x8f,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0x9f,0xff,0xfc,0x3f,0xff,0xfb,0x9f,0x7f,0xf3,0x9f,0xff,0xe7,0xff,
 0xfe,0xfb,0xe7,0xff,0xfb,0x9f,0x1f,0xff,0x53,0x7f,0xc3,0xbf,0xff,0xfc,0xcf,
 0xff,0xff,0x3f,0x3f,0xfc,0xf3,0xf9,0xfc,0xef,0x7f,0xfe,0xef,0xff,0xf9,0xe7,
 0xff,0xfc,0xef,0x1f,0xff,0xe7,0xf9,0xf9,0xf7,0xdf,0x3f,0xfb,0xff,0xfc,0xff,
 0xf3,0xf8,0x7f,0xfe,0xe3,0xc1,0xf3,0xfe,0xdf,0xff,0xfb,0x01,0x10,0x80,0xdf,
 0xff,0xf7,0x1b,0x3e,0xfd,0xf3,0xff,0xfb,0xcb,0x0f,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xcf,0xff,0xfe,0x3f,0xff,
 0xf9,0x9f,0x3f,0xf3,0x9f,0xff,0xe7,0xff,0xfe,0xf9,0xf7,0xff,0xfb,0x9f,0xff,
 0xff,0x19,0x3f,0xe7,0xbf,0x7f,0xfe,0xef,0xff,0xff,0xbf,0x3f,0xfe,0xf9,0xfb,
 0x01,0xe7,0x3f,0xff,0xe7,0xff,0xfc,0xf3,0xff,0xfc,0xe7,0xff,0xff,0xf3,0xf3,
 0xf9,0xf3,0xcf,0xff,0xf3,0xf9,0xfe,0xff,0xff,0xf8,0x7f,0xfe,0xc7,0xf9,0x73,
 0xfe,0x1f,0xf0,0xfb,0xff,0x1f,0x80,0xcf,0xff,0xf3,0x19,0x3f,0xfc,0xf3,0xff,
 0xf9,0xe3,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xe7,0x7f,0xfe,0x3f,0xff,0xfd,0x03,0xb8,0xf1,0x9f,0x1f,0xc0,
 0x03,0xfc,0xfc,0xf3,0xff,0xfb,0xcf,0xff,0xff,0x19,0x9f,0xe7,0xbf,0x3f,0xff,
 0xe7,0xff,0xff,0x9f,0x7f,0xfe,0xf9,0xff,0x01,0xf7,0x1f,0xff,0xf3,0x7f,0xfe,
 0xf3,0xff,0xfc,0xf3,0xff,0xff,0xf9,0xf3,0xf9,0xfb,0xef,0xff,0x03,0x78,0xfe,
 0xff,0xff,0xf9,0xff,0xff,0xcf,0xff,0x33,0xff,0x1f,0xf0,0xfb,0xff,0xff,0xff,
 0xe7,0xff,0xf8,0x1d,0x3f,0xfe,0xff,0xff,0xfd,0xe3,0x0f,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe7,0x7f,0xff,0x3f,
 0xff,0xfc,0x03,0xf8,0xfb,0x9f,0x1f,0x80,0x07,0x7c,0xfc,0xfb,0xff,0xfb,0xe7,
 0xff,0xff,0x3c,0x9f,0xff,0xbf,0x9f,0xff,0xf7,0xff,0xff,0xcf,0xff,0xff,0xff,
 0xff,0xff,0xff,0x9f,0xff,0xf3,0x3f,0xff,0xf9,0xff,0xfc,0xff,0xff,0xff,0xf9,
 0xff,0xf9,0xf9,0xff,0xff,0x07,0x38,0xff,0xff,0xff,0xfd,0xff,0xff,0xdf,0xff,
 0x33,0xff,0xff,0xff,0xfb,0xff,0xff,0xff,0xf3,0xff,0xfc,0xbd,0x7f,0xff,0xff,
 0xff,0xfc,0xf3,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f
};

#define PIXMAP_WIDTH katakana_cell_width

#define PICK_MATRIX(mi) (&matrix[MI_SCREEN(mi)])
#define MATRIX_RANDOM(min, max) (NRAND((max) - (min)) + (min))

/*************************************************************************
 * CONFIGURABLE DISPLAY OPTIONS
 *************************************************************************/

/* Defining RANDOMIZE_COLUMNS causes each column pixmap to be regenerated
 * after scrolling off the screen rather than just repeating
 * infinitely.  This is a better effect but uses more CPU and will
 * cause update hesitations (especially on multi-headed machines or
 * slower hardware).  However, it can be done in relatively little memory
 * because the pixmaps can be kept short and regenerated frequently.
 *
 * Undefining RANDOMIZE_COLUMNS will cause the pixmaps to be generated only
 * once, which should save a lot of horsepower for whatever else your machine
 * might be doing.  However, the tradeoff is that the pixmaps are longer
 * to avoid a really boring display.
 */
#define RANDOMIZE_COLUMNS

/*
 * Speed at which columns move, in pixels per screen update.
 * Must be <= katakana_cell_height; interesting values range
 * from one (crawl) to about fifteen (zoom!).
 * (Note: the distribution of speeds implemented in new_column()
 *  is slightly biased against very slow scrolling.)
 */
#define MATRIX_SPEED  MATRIX_RANDOM(1, 8)

/*
 * Number of characters in each generated string
 * (Note: this is not limited by the pixmap size!)
 */
#define MATRIX_STRLEN MATRIX_RANDOM(15, 20)

/*
 * Number of character-heights in each generated gap
 * (Note: this is not limited by the pixmap size!)
 */
#define MATRIX_GAPLEN MATRIX_RANDOM(2, 6)

/* colors used for Matrix characters */
#define BRIGHTGREEN (28 * MI_NPIXELS(mi) / 64)
#define GREEN       (26 * MI_NPIXELS(mi) / 64)

/*************************************************************************/

/*
 * state of the Matrix
 */
static matrix_t *matrix = (matrix_t *) NULL;


/* NAME: new_column
 *
 * FUNCTION: clears and generates a pixmap of "matrix" data
 */

static void new_column(const ModeInfo *mi, column_t *c)
{
  matrix_t *mp = PICK_MATRIX(mi);
  int currChar;

  /*
   * clear the pixmap to get rid of previous data or initialize
   */
  XSetForeground(MI_DISPLAY(mi), MI_GC(mi), mp->bg);
  XFillRectangle(MI_DISPLAY(mi), (Drawable) c->pixmap, MI_GC(mi),
	 0, 0, PIXMAP_WIDTH, mp->pixmapHeight);

  /*
   * Write characters into the column pixmap, starting at the
   * bottom and moving upwards.  Decrement the string length, gap length,
   * and next-speed-update timers for the column and reinitialize them
   * if necessary.
   */
  for (currChar = mp->charsPerPixmap - 1; currChar >= 0; currChar--) {
	  if (c->nextSpeedUpdate-- == 0) {
		  c->speed = MATRIX_SPEED;
		  c->nextSpeedUpdate = mp->speedUpdateInterval;

		  /* tweak to prevent really slow columns from remaining slow */
		  if (c->speed <= 2)
			c->nextSpeedUpdate /= 2;
	  }

	  if (c->gapLen > 0) {
		  c->gapLen--;
		  continue;
	  } else if (c->strLen == 0) {
		  /*
		   * generate a gap of random length in the string, followed
		   * by a random number of characters.  Set the endChar flag
		   * to indicate the end of a new string.
		   */
		  c->gapLen  = MATRIX_GAPLEN;
		  c->strLen  = MATRIX_STRLEN;
		  c->endChar = True;
	  } else {
		  Pixmap src;
		  int kOffset;

		  if (c->endChar) {
			  src = mp->kana[1]; /* a bold, non-numeric character */
			  kOffset = MATRIX_RANDOM(10, katakana_num_cells);
			  c->endChar = False;
			} else {             /* inside a string - kana or number */
			  src = mp->kana[0];
			  kOffset = MATRIX_RANDOM(0, katakana_num_cells);
			}

		  XCopyArea(MI_DISPLAY(mi), (Drawable) src,
			(Drawable) c->pixmap, MI_GC(mi),
			kOffset * katakana_cell_width, 0,
			katakana_cell_width, katakana_cell_height,
			0, currChar * katakana_cell_height);

		  c->strLen--;
		}
	}
}

/* NAME: free_matrix
 *
 * FUNCTION: delete the data associated with a single screen.  Be careful
 * not to do spurious free()s of nonexistent data, and to nullify all
 * freed pointers that might be reused past the end of this call.
 */
static void
free_matrix(Display *display, matrix_t *mp)
{
	if (mp->columns != NULL) {
	  int c;

	  for (c = 0; c < mp->num_cols; c++) {
		  if (mp->columns[c].pixmap)
			XFreePixmap(display, mp->columns[c].pixmap);
	  }

	  free(mp->columns);
	  mp->columns = (column_t *) NULL;
	}

	if (mp->kana[0]) {
	  XFreePixmap(display, mp->kana[0]);
	  mp->kana[0] = None;
	}

	if (mp->kana[1]) {
	  XFreePixmap(display, mp->kana[1]);
	  mp->kana[1] = None;
	}
}

/* NAME: setup_matrix()
 *
 * FUNCTION: Initialize the Matrix state for the current screen.
 *           Based on the width and height of the display,
 *           allocate and initialize column data structures.
 */

static void setup_matrix(const ModeInfo *mi)
{
  matrix_t *mp = PICK_MATRIX(mi);
  Display *display = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);
  int c;

  /* screen may have changed size/depth/who-knows-what */
  free_matrix(display, mp);

  if (MI_NPIXELS(mi) > 2) {
	  mp->fg   = MI_PIXEL(mi, GREEN);
	  mp->bold = MI_PIXEL(mi, BRIGHTGREEN);
  } else
	mp->fg = mp->bold = MI_WHITE_PIXEL(mi);

  mp->bg = MI_BLACK_PIXEL(mi);

  if ((mp->kana[0] = XCreatePixmapFromBitmapData(display, window,
		(char *) katakana_bits, katakana_width, katakana_height,
		mp->bg, mp->fg, MI_DEPTH(mi))) == None) {
	free_matrix(display, mp);
	return;
  }

  if ((mp->kana[1] = XCreatePixmapFromBitmapData(display, window,
		(char *) katakana_bits, katakana_width, katakana_height,
		mp->bg, mp->bold, MI_DEPTH(mi))) == None) {
	free_matrix(display, mp);
	return;
  }

  /*
   * Using the width of the kana font, determine how many
   * columns can fit across the current screen (with a little
   * padding).
   */
  mp->num_cols = (int) (MI_WIDTH(mi) / (1.3 * katakana_cell_width));

  if ((mp->columns = (column_t *) calloc(mp->num_cols,
		sizeof(column_t))) == NULL) {
	free_matrix(display, mp);
	return;
  }

  /*
   * If we're randomizing columns, keep the column pixmap height small
   * to save memory and minimize time for each column update.  Otherwise,
   * make the height large to maximize variety without updating the pixmaps.
   */
#ifdef RANDOMIZE_COLUMNS
  mp->charsPerPixmap = MI_HEIGHT(mi) / (4 * katakana_cell_height);
#else
  mp->charsPerPixmap = MI_HEIGHT(mi) / katakana_cell_height;
#endif

  if (!mp->charsPerPixmap)  /* sanity check for tiny windows */
	mp->charsPerPixmap = 1;

  mp->pixmapHeight = mp->charsPerPixmap * katakana_cell_height;

  for (c = 0; c < mp->num_cols; c++)
	if ((mp->columns[c].pixmap  = XCreatePixmap(display, window,
		  PIXMAP_WIDTH, mp->pixmapHeight, MI_DEPTH(mi))) == None) {
		free_matrix(display, mp);
		return;
	}

  /* Change the speed of a column after it's covered half the screen */
  mp->speedUpdateInterval = MI_HEIGHT(mi) / (2 * katakana_cell_height);

  /* initialize the column data */
  for (c = 0; c < mp->num_cols; c++) {
	  column_t *column = &(mp->columns[c]);

	  column->yPtr = mp->pixmapHeight;

	  column->nextSpeedUpdate = 0; /* will generate new speed  */
	  column->strLen  = 0;         /* will generate new string */

	  new_column(mi, column);
  }
}



/* NAME: init_matrix
 *
 * FUNCTION: allocate space for global matrix array
 *           initialize colors
 *           initialize dimensions
 *           allocate a pair of pixmaps containing character set
 *               (0 is green on black, 1 is bold on black)
 *           create columns of "matrix" data
 */

void init_matrix(ModeInfo *mi)
{
  if (matrix == NULL) {
	if ((matrix = (matrix_t *) calloc(MI_NUM_SCREENS(mi),
			 sizeof(matrix_t))) == NULL)
		return;
  }

  /* don't want any exposure events from XCopyArea */
  XSetGraphicsExposures(MI_DISPLAY(mi), MI_GC(mi), False);

  MI_CLEARWINDOW(mi);
  setup_matrix(mi);
}


/* NAME: draw_matrix
 *
 * FUNCTION: update the matrix waterfall display
 */
void draw_matrix(ModeInfo *mi)
{
  double xDistance;
  int i;
  matrix_t *mp;

  if (matrix == NULL)
	return;
  mp = PICK_MATRIX(mi);
  if (mp->columns == NULL)
	return;

  MI_IS_DRAWN(mi) = True;
  xDistance = (double) MI_WIDTH(mi) / (double) mp->num_cols;

  /*
   * THEORY OF OPERATION
   *
   * Screen-to-screen blits occur within the video RAM and are usually
   * hardware-accelerated, while pixmap-to-screen blits require DMA
   * between main memory and vRAM.  To make updates as fast as possible,
   * we scroll down the screen contents in each column by <speed> pixels,
   * then fill only the top <speed> rows of the column with pixmap data.
   *
   * There are two tricky bits to this scheme.  First, we must keep a
   * pointer (yPtr) to the pixmap contents which should be used to
   * fill in the top rows of each column.  yPtr is decremented on each
   * call to draw_matrix(); when it reaches zero, the column's pixmap
   * may be redrawn to generate new characters for the column.
   *
   * Second, the scroll increment corresponding to <speed> need not be a
   * multiple of the pixmap height. We must special-case the handling of the
   * last decrement that makes yPtr <= 0 to ensure that the last few pixels
   * of the old data AND the first few pixels of the new data are both drawn.
   * It turns out to be quite reasonable to do the pixmap update inline,
   * between drawing the last few old pixels and the first few new pixels.
   * Hence, we can achieve smooth scrolling with only one pixmap per column.
   */

  for (i = 0; i < mp->num_cols; i++) {
	  column_t *c = &(mp->columns[i]);
	  int xOffset = (int) (i * xDistance);
	  int yDelta  = c->speed;

	  /* screen-screen blit takes care of most of the column */
	  XCopyArea(MI_DISPLAY(mi), MI_WINDOW(mi),
				MI_WINDOW(mi), MI_GC(mi),
				xOffset, 0,
				PIXMAP_WIDTH, MI_HEIGHT(mi) - yDelta,
				xOffset, yDelta);

	  c->yPtr -= yDelta;

	  if (c->yPtr <= 0) /* we're about to finish the current pixmap */
		{
		  /* exhaust the pixels in the current pixmap */
		  XCopyArea(MI_DISPLAY(mi), (Drawable) c->pixmap,
			MI_WINDOW(mi), MI_GC(mi),
			0, 0,
			PIXMAP_WIDTH, yDelta + c->yPtr,
			xOffset, -(c->yPtr));

#ifdef RANDOMIZE_COLUMNS
		  /* regenerate the column pixmap with new text */
		  new_column(mi, c);
#else
		  /* speed is normally updated in new_column(), which isn't called,
		   * so take this opportunity to update the column speed.
		   */
		  c->speed = MATRIX_SPEED;
#endif

		  /* fill in the remainder of the column from the new pixmap */
		  XCopyArea(MI_DISPLAY(mi), (Drawable) c->pixmap,
			MI_WINDOW(mi), MI_GC(mi),
			0, mp->pixmapHeight + c->yPtr,
			PIXMAP_WIDTH, -(c->yPtr),
			xOffset, 0);

		  c->yPtr += mp->pixmapHeight;
		}
	  else
		{
		  /* fill in the remainder of the column from the pixmap */
		  XCopyArea(MI_DISPLAY(mi), (Drawable) c->pixmap,
			MI_WINDOW(mi), MI_GC(mi),
			0, c->yPtr,
			PIXMAP_WIDTH, yDelta,
			xOffset, 0);
		}
	}
}


/* NAME: release_matrix
 *
 * FUNCTION: frees all allocated resources
 */

void release_matrix(ModeInfo *mi)
{

  /* If the matrix exists, free all data associated with all screens.
   * free_matrix() does no harm if given nonexistent screens.
   */
  if (matrix != NULL) {
	int screen;

	for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
		free_matrix(MI_DISPLAY(mi), &matrix[screen]);
	  free(matrix);
	  matrix = (matrix_t *) NULL;
  }
}


/* NAME: refresh_matrix
 *
 * FUNCTION: refresh the screen
 */

void refresh_matrix(ModeInfo *mi)
{
  int c;
  matrix_t *mp;

  if (matrix == NULL)
	return;
  mp = PICK_MATRIX(mi);
  if (mp->columns == NULL)
	return;

  /* We simply clear the whole window and restart the scrolling operation.
   * In principle, it is possible to restore at least some of the
   * column pixmap data to the screen, but it's not worth the trouble.
   */
  MI_CLEARWINDOW(mi);

  for (c = 0; c < mp->num_cols; c++)
	mp->columns[c].yPtr = mp->pixmapHeight;
}


/* NAME: change_matrix
 *
 * FUNCTION: resets column offsets and speeds and generates new pixmaps
 */

void change_matrix(ModeInfo *mi)
{
  int c;
  matrix_t *mp;

  if (matrix == NULL)
	return;
  mp = PICK_MATRIX(mi);
  if (mp->columns == NULL)
	return;

  MI_CLEARWINDOW(mi);

  for (c = 0; c < mp->num_cols; c++) {
	  column_t *column = &(mp->columns[c]);

	  column->yPtr = mp->pixmapHeight;

	  column->nextSpeedUpdate = 0; /* will generate new speed  */
	  column->strLen  = 0;         /* will generate new string */

	  new_column(mi, column);
  }
}

#endif /* MODE_matrix */
