/* sqXWindow.c -- support for Unix and the X Window System.
 * 
 * @copyright@
 */

/* Author: Ian Piumarta <ian.piumarta@inria.fr>
 *
 * Last edited: 2000-11-08 14:33:54 by piumarta on emilia.rd.wdi.disney.com
 *
 * Support for displays deeper than 8 bits contributed by: Kazuki YASUMATSU
 *	<kyasu@crl.fujixerox.co.jp> <Kazuki.Yasumatsu@fujixerox.co.jp>
 *
 * Support for cursor and keypad editing keys based on code contributed by:
 *	Stefan Matthias Aust <sma@kiel.netsurf.de>
 *
 * Support for intelligent visual class selection contributed by:
 *	Bill Cattey <wdc@MIT.EDU>
 *
 * Support for European accented characters in selections contributed by:
 *	Bert Freudenberg <bert@isg.cs.uni-magdeburg.de>
 *
 * Support for 24bpp TrueColour X display devices contributed by:
 *	Tim Rowledge <tim@sumeru.stanford.edu>
 *
 * Support for OSProcess plugin contributed by:
 *	Dave Lewis <lewis@mail.msen.com> Mon Oct 18 20:36:54 EDT 1999
 *
 * Support for browser plugins contributed by:
 *	Bert Freudenberg <bert@isg.cs.uni-magdeburg.de>
 *
 * BUGS: this file is too long; it should be split into two (Unix vs. X11).
 *	 icon stuff should be removed (window manager's responsibility).
 *	 RCS stuff has been removed.
 */

#include "sq.h"

#include "FilePlugin.h"			/* sqFileInit() */
#include "JoystickTabletPlugin.h"	/* joystickInit() */

#define NO_ICON

/* if defined then the main Squeak window is a subwindow in an
   invisible enclosing InputOutput window.  helps integrate the
   Mozilla plugin stuff and fullscreen mode. */

#ifdef HAVE_LIBXEXT
# define USE_XSHM
#endif

/* OS_TYPE may be set in configure.in and passed via the Makefile */

#ifndef OS_TYPE
# ifdef UNIX
#   define OS_TYPE "unix"
# else
#  define OS_TYPE "unknown"
# endif
#endif

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

#ifndef HEADLESS
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xatom.h>
# define XK_MISCELLANY
# define XK_XKB_KEYS
# include <X11/keysymdef.h>
# ifdef USE_XSHM
#   include <sys/ipc.h>
#   include <sys/shm.h>
#   include <X11/extensions/XShm.h>
# endif
# ifndef NO_ICON
#   include "squeakIcon.bitmap"
# endif
#endif

#if defined(HAVE_LIBDL)
# define USE_INTERNAL_IMAGE
# include <dlfcn.h>
# if !defined(NDEBUG)
#   define NDEBUG
# endif
# include <assert.h>
# include "zipio.h"
#endif

/*** Variables -- Imported from Virtual Machine ***/
extern unsigned char *memory;
extern int interruptKeycode;
extern int interruptPending;
extern int interruptCheckCounter;
extern int savedWindowSize;

/*** Variables -- image and path names ***/
#define IMAGE_NAME_SIZE MAXPATHLEN

static char shortImageName[MAXPATHLEN+1];	/* image name */
char        imageName[MAXPATHLEN+1];		/* full path to image */
static char vmPath[MAXPATHLEN+1];		/* full path to image directory */
static char vmName[MAXPATHLEN+1];		/* full path to vm */

#define DefaultHeapSize		20	/* megabytes */

int initialHeapSize= DefaultHeapSize * 1024 * 1024;

/*** Variables -- globals for access from pluggable primitives ***/
int    argCnt= 0;
char **argVec= 0;
char **envVec= 0;

/* Pointers to arguments for VM.  Used by getAttributeIntoLength(). */
int    vmArgCnt= 0;
char **vmArgVec= 0;

/* Pointers to arguments for Squeak.  Used by getAttributeIntoLength(). */
int    squeakArgCnt= 0;
char **squeakArgVec= 0;

#if defined(USE_INTERNAL_IMAGE)
unsigned char *internalImage=    0;	/* non-zero means use internal image */
unsigned char *internalImagePtr= 0;	/* non-zero means plain image */
z_stream      *internalZStream=  0;	/* non-zero means gzipped image */
#endif

#undef USE_ITIMER			/* severely confuses GNU's profil() */

#ifdef USE_ITIMER
unsigned int	lowResMSecs= 0;
#define	LOW_RES_TICK_MSECS	20	/* 1/50 second resolution */
#endif

#ifndef HEADLESS
/*** Variables -- X11 Related ***/

/* name of Squeak windows in Xrm and the WM */
#define xResClass	"Squeak"
#define xResName	"squeak"

char		*displayName= 0;	/* name of display, or 0 for $DISPLAY */
Display		*stDisplay= null;	/* Squeak display */
int		 isConnectedToXServer=0;/* True when connected to an X server */
int		 stXfd= 0;		/* X connection file descriptor */
Window		 stParent= null;	/* Squeak parent window */
Window		 stWindow= null;	/* Squeak window */
Visual		*stVisual;		/* the default visual */
GC		 stGC;			/* graphics context used for rendering */
Colormap	 stColormap= null;	/* Squeak color map */
int		 scrW= 0;
int		 scrH= 0;
int		 stDisplayBitsIndex= 0;	/* last known oop of the VM's Display */
XImage		*stImage= 0;		/* ...and it's client-side pixmap */
char		*stPrimarySelection;	/* buffer holding selection */
char		*stEmptySelection= "";	/* immutable "empty string" value */
int		 stPrimarySelectionSize;/* size of buffer holding selection */
int		 stOwnsSelection= 0;	/* true if we own the X selection */
XColor		 stColorBlack;		/* black pixel value in stColormap */
XColor		 stColorWhite;		/* white pixel value in stColormap */
int		 savedWindowOrigin= -1;	/* initial origin of window */
XPoint		 mousePosition;		/* position at last PointerMotion event */
Time		 stButtonTime;		/* time of last ButtonRelease (for SetSeln) */
# ifdef USE_XSHM
XShmSegmentInfo  stShmInfo;		/* shared memory descriptor */
int		 completions= 0;	/* outstanding completion events */
int		 completionType;	/* the type of XShmCompletionEvent */
int		 useXshm= 0;		/* 1 if shared memory is in use */
int		 asyncUpdate= 0;	/* 1 for asynchronous screen updates */
# endif
int		 stDepth= 0;
int		 stBitsPerPixel= 0;
unsigned int	 stColors[256];
unsigned int	 stDownGradingColors[256];
int		 stHasSameRGBMask16;
int		 stHasSameRGBMask32;
int		 stRNMask, stGNMask, stBNMask;
int		 stRShift, stGShift, stBShift;
char		*stDisplayBitmap= 0;
int              secure= 0;		/* true if running as a browser plugin */
Window           browserWindow= 0;      /* parent window */
int		 headless= 0;

/* window states */
#define	WIN_NORMAL	0
#define	WIN_CHANGED	1
#define	WIN_ZOOMED	2
int	windowState=	WIN_CHANGED;

#define noteWindowChange() \
  if (windowState == WIN_NORMAL) \
    windowState= WIN_CHANGED;

#endif  /* !HEADLESS */

#define SqueakWhite	0
#define SqueakBlack	1

int		 asmAlign= 1;
int		 sleepWhenUnmapped= 0;
int		 noJitter= 1;
int		 withSpy= 0;
int		 noTitle= 0;
int		 fullScreen= 0;
struct timeval	 startUpTime;

/* maximum input polling frequency */
#define	MAXPOLLSPERSEC	33

#ifndef HEADLESS

/* we are interested in these events...
 */
#define	EVENTMASK	ButtonPressMask | ButtonReleaseMask | \
			KeyPressMask | PointerMotionMask | \
			ExposureMask

#define	WM_EVENTMASK	StructureNotifyMask

/* largest X selection that we will attempt to handle (bytes) */
#define MAX_SELECTION_SIZE	100*1024

/* longest we're prepared to wait for the selection owner to convert it (seconds) */
#define SELECTION_TIMEOUT	3


/*** Variables -- Event Recording ***/
#define KEYBUF_SIZE 64

int keyBuf[KEYBUF_SIZE];	/* circular buffer */
int keyBufGet= 0;		/* index of next item of keyBuf to read */
int keyBufPut= 0;		/* index of next item of keyBuf to write */
int keyBufOverflows= 0;		/* number of characters dropped */

int buttonState= 0;		/* mouse button and modifier state when mouse
				   button went down or 0 if not pressed */

/* This table maps the X modifier key bits to 4 Squeak modifier
   bits.  (The X caps lock key is mapped as shift, meta is
   mapped to command, and ctrl+meta is mapped to option.
	X bits:		<meta><control><shift-lock><shift>
	Squeak bits:	<command><option><control><shift>
*/
char modifierMap[16]= {
  0, 1, 1, 0, 2, 3, 3, 2, 8, 9, 9, 8, 4, 5, 5, 4
};

/* masks for the XButtonEvent modifier state */
#define MOD_SHIFT	1
#define MOD_CONTROL	4
#define MOD_META	8
#endif   /* !HEADLESS */

/* polling functions in socket/sound plugins, called from HandleEvents */

void (*socketPollFunction)(int delay, int extraFd)= 0;	/* aioPollForIO */
void (*soundPollFunction)(void)= 0;			/* auPollForIO */


/*** Functions ***/

#ifdef ioMSecs
# undef ioMSecs
#endif

int  HandleEvents(void);
void RecordFullPathForImageName(char *localImageName);
void SetUpTimers(void);
void usage(void);
void imageNotFound(char *imageName);
void ParseArguments(int argc, char **argv);
void segv(int ignored);

#ifndef HEADLESS
void SetColorEntry(int index, int red, int green, int blue);
void SetUpClipboard(void);
void SetUpPixmap(void);
void SetUpWindow(char *displayName);
void SetWindowSize(void);
void getMaskbit(unsigned long ul, int *nmask, int *shift);
void setupDownGradingColors(void);
void copyReverseImageBytes(int *fromImageData, int *toImageData,
			   int depth, int width, int height,
			   int affectedL, int affectedT, int affectedR, int affectedB);
void copyReverseImageWords(int *fromImageData, int *toImageData,
			   int depth, int width, int height,
			   int affectedL, int affectedT, int affectedR, int affectedB);

# define declareCopyFunction(NAME) \
  void NAME (int *fromImageData, int *toImageData, \
	     int width, int height, \
	     int affectedL, int affectedT, int affectedR, int affectedB)

declareCopyFunction(copyImage8To8);
declareCopyFunction(copyImage8To16);
declareCopyFunction(copyImage16To8);
declareCopyFunction(copyImage8To32);
declareCopyFunction(copyImage8To24);
declareCopyFunction(copyImage32To8);
declareCopyFunction(copyImage16To32);
declareCopyFunction(copyImage16To24);
declareCopyFunction(copyImage32To16);
declareCopyFunction(copyImage16To16);
declareCopyFunction(copyImage32To32);
declareCopyFunction(copyImage32To24);

# undef declareCopyFunction

void SetUpCharmap();
void ux2st(unsigned char *string);
void st2ux(unsigned char *string);
void claimSelection(void);
void sendSelection(XSelectionRequestEvent *requestEv);
char *getSelection(void);
static void redrawDisplay(int l, int r, int t, int b);
static int translateCode(KeySym symbolic);
void recordKeystroke(XKeyEvent *theEvent);
void recordMouseDown(XButtonEvent *theEvent);
void recordModifierButtons(XButtonEvent *theEvent);
# ifdef USE_XSHM
int XShmGetEventBase(Display *);
# endif
int ioMSecs(void);
void pluginInit();                      /* see sqUnixPluginSupport.c */
void pluginExit();
void pluginHandleEvent(XEvent *);
#endif /*!HEADLESS*/

int strtobkm(char *str);


time_t convertToSqueakTime(time_t);	/* unix epoch -> Squeak epoch */


/*** VM Home Directory Path ***/

int vmPathSize(void)
{
  return strlen(vmPath);
}

int vmPathGetLength(int sqVMPathIndex, int length)
{
  char *stVMPath= (char *)sqVMPathIndex;
  int count, i;

  count= strlen(vmPath);
  count= (length < count) ? length : count;

  /* copy the file name into the Squeak string */
  for (i= 0; i < count; i++)
    stVMPath[i]= vmPath[i];

  return count;
}

#ifndef HEADLESS

/* Conversion table from X to Squeak (reversible) */

unsigned char X_to_Squeak[256] =
{
  0,   1,   2,   3,   4,   5,   6,   7,       /*   0 -   7 */
  8,   9,   13,  11,  12,  10,  14,  15,      /*   8 -  15 */
  16,  17,  18,  19,  20,  21,  22,  23,      /*  16 -  23 */
  24,  25,  26,  27,  28,  29,  30,  31,      /*  24 -  31 */
  32,  33,  34,  35,  36,  37,  38,  39,      /*  32 -  39 */
  40,  41,  42,  43,  44,  45,  46,  47,      /*  40 -  47 */
  48,  49,  50,  51,  52,  53,  54,  55,      /*  48 -  55 */
  56,  57,  58,  59,  60,  61,  62,  63,      /*  56 -  63 */
  64,  65,  66,  67,  68,  69,  70,  71,      /*  64 -  71 */
  72,  73,  74,  75,  76,  77,  78,  79,      /*  72 -  79 */
  80,  81,  82,  83,  84,  85,  86,  87,      /*  80 -  87 */
  88,  89,  90,  91,  92,  93,  94,  95,      /*  88 -  95 */
  96,  97,  98,  99, 100, 101, 102, 103,      /*  96 - 103 */
  104, 105, 106, 107, 108, 109, 110, 111,     /* 104 - 111 */
  112, 113, 114, 115, 116, 117, 118, 119,     /* 112 - 119 */
  120, 121, 122, 123, 124, 125, 126, 127,     /* 120 - 127 */
  196, 197, 165, 201, 209, 247, 220, 225,     /* 128 - 135 */
  224, 226, 228, 227, 198, 176, 170, 248,     /* 136 - 143 */
  213, 206, 195, 207, 211, 212, 210, 219,     /* 144 - 151 */
  218, 221, 246, 245, 250, 249, 251, 252,     /* 152 - 159 */
  160, 193, 162, 163, 223, 180, 182, 164,     /* 160 - 167 */
  172, 169, 187, 199, 194, 173, 168, 255,     /* 168 - 175 */
  161, 177, 178, 179, 171, 181, 166, 183,     /* 176 - 183 */
  184, 185, 188, 200, 186, 189, 202, 192,     /* 184 - 191 */
  203, 231, 229, 204, 128, 129, 174, 130,     /* 192 - 199 */
  233, 131, 230, 232, 237, 234, 235, 236,     /* 200 - 207 */
  208, 132, 241, 238, 239, 205, 133, 215,     /* 208 - 215 */
  175, 244, 242, 243, 134, 217, 222, 167,     /* 216 - 223 */
  136, 135, 137, 139, 138, 140, 190, 141,     /* 224 - 231 */
  143, 142, 144, 145, 147, 146, 148, 149,     /* 232 - 239 */
  240, 150, 152, 151, 153, 155, 154, 214,     /* 240 - 247 */
  191, 157, 156, 158, 159, 253, 254, 216,     /* 248 - 255 */
};

unsigned char Squeak_to_X[256];

void SetUpCharmap()
{
  int i;
  for(i=0; i<256; i++)
    Squeak_to_X[X_to_Squeak[i]]= i;
}

void st2ux(unsigned char *string)
{
  if (!string) return;
  while (*string)
    {
     *string= Squeak_to_X[*string];
      string++;
    }
}

void ux2st(unsigned char *string)
{
  if (!string) return;
  while (*string)
    {
      *string= X_to_Squeak[*string];
      string++;
    }
}

/*** X-related Functions ***/

/* Called prior to forking a squeak session.
 */
int synchronizeXDisplay()
{
  if (isConnectedToXServer)
    XSync(stDisplay, False);
  return 0;
}

int openXDisplay()
{
  /* open the Squeak window. */
  if (!isConnectedToXServer) {
    SetUpCharmap();
    SetUpClipboard();
    SetUpWindow(displayName);
    SetUpPixmap();
    SetWindowSize();
    XMapWindow(stDisplay, stParent);
    XMapWindow(stDisplay, stWindow);
    isConnectedToXServer= 1;
  }
  return 0;
}

int forgetXDisplay()
{
  /* Initialise variables related to the X connection, and
     make the existing connection to the X Display invalid
     for any further access from this Squeak image.  Any socket
     connection to the X server is closed, but the server is
     not told to terminate any windows or X sessions.  This
     is used to support fork() for an existing Squeak image,
     where the child is expected to continue as a headless
     image, and the parent continues its normal execution. */

  displayName= 0;       /* name of display, or 0 for $DISPLAY   */
  stDisplay= null;      /* Squeak display                       */
  if (isConnectedToXServer)
    close(stXfd);
  stXfd= 0;             /* X connection file descriptor         */
  stParent= null;
  stWindow= null;       /* Squeak window                        */
  isConnectedToXServer= 0;
  return 0;
}

int disconnectXDisplay()
{
  if (isConnectedToXServer) {
    XSync(stDisplay, False);
    while (HandleEvents())
      {
        /* process all pending events */
      }
    XDestroyWindow(stDisplay, stWindow);
    if (browserWindow == 0)
      XDestroyWindow(stDisplay, stParent);
    XCloseDisplay(stDisplay);
  }
  forgetXDisplay();
  return 0;
}

#if 0
static char *debugVisual (int x)
{
  switch (x)
    {
    case 0: return "StaticGray";
    case 1: return "GrayScale";
    case 2: return "StaticColor";
    case 3: return "PseudoColor";
    case 4: return "TrueColor";
    case 5: return "DirectColor";
    default: return "Invalid";
    }
}
#endif

void claimSelection(void)
{
  XSetSelectionOwner(stDisplay, XA_PRIMARY, stWindow, CurrentTime);
  stOwnsSelection= (XGetSelectionOwner(stDisplay, XA_PRIMARY) == stWindow);
}

void sendSelection(XSelectionRequestEvent *requestEv)
{
  XSelectionEvent notifyEv;

  /* this should REFUSE the selection if the target type isn't XA_STRING */

  st2ux(stPrimarySelection);

  XChangeProperty(requestEv->display,
		  requestEv->requestor,
		  (requestEv->property == None
		     ? requestEv->target
		     : requestEv->property),
		  requestEv->target,
		  8, PropModeReplace, stPrimarySelection,
		  (stPrimarySelection ? strlen(stPrimarySelection) : 0));

  ux2st(stPrimarySelection);

  notifyEv.type= SelectionNotify;
  notifyEv.display= requestEv->display;
  notifyEv.requestor= requestEv->requestor;
  notifyEv.selection= requestEv->selection;
  notifyEv.target= requestEv->target;
  notifyEv.time= requestEv->time;
  notifyEv.property= (requestEv->property == None)
    ? requestEv->target
    : requestEv->property;

  XSendEvent(requestEv->display, requestEv->requestor,
	     False, 0, (XEvent *)&notifyEv);

  XFlush(stDisplay);
}

char *getSelection(void)
{
  XEvent  ev;
  fd_set  fdMask;
  char	 *data;

  /* request the selection */
  XConvertSelection(stDisplay, XA_PRIMARY, XA_STRING, XA_STRING, stWindow, CurrentTime);

  /* wait for selection notification, ignoring (most) other events. */
  FD_ZERO(&fdMask);
  FD_SET(stXfd, &fdMask);

  do {
    if (XPending(stDisplay) == 0)
      {
	int status;
	struct timeval timeout= {SELECTION_TIMEOUT, 0};

	while ((status= select(FD_SETSIZE, &fdMask, 0, 0, &timeout)) < 0
	       && errno == EINTR);
	if (status < 0)
	  {
	    perror("select(stDisplay)");
	    return stEmptySelection;
	  }
	if (status == 0)
	  {
	    XBell(stDisplay, 0);
	    return stEmptySelection;
	  }
      }
    XNextEvent(stDisplay, &ev);
    if (ev.type == ConfigureNotify) noteWindowChange();
    /* this is necessary so that we can supply our own selection when we
       are the requestor -- this could (should) be optimised to return the
       stored selection value instead! */
    if (ev.type == SelectionRequest) sendSelection(&ev.xselectionrequest);
# ifdef USE_XSHM
    if (ev.type == completionType) --completions;
# endif
  } while (ev.type != SelectionNotify);

  /* check if the selection was refused */
  if (ev.xselection.property == None)
    {
      XBell(stDisplay, 0);
      return stEmptySelection;
    }

  /* get the value of the selection from the containing property */
  {
    Atom type;
    int format;
    unsigned long nitems, bytesAfter;

    XGetWindowProperty(stDisplay, ev.xselection.requestor, ev.xselection.property,
		       (long)0, (long)(MAX_SELECTION_SIZE/4),
		       False, AnyPropertyType,
		       &type, &format, &nitems, &bytesAfter,
		       (unsigned char **)&data);
    if (bytesAfter > 0)
      XBell(stDisplay, 0);
  }

  /* return the selection -- which must be XFreed() when no longer needed! */

  return data;
}


/* a modified copy of fullDisplayUpdate() that redraws
   only the damaged parts of the window according to each
   expose event on the queue.
   Note: if the format of Form or Bitmap changes, or if
   the special object index of Display is changed, this
   version of the code WILL FAIL!  Otherwise it is to be
   preferred.
*/
static void redrawDisplay(int l, int r, int t, int b)
{
  extern int lengthOf(int);
  extern int displayObject(void);
# define longAt(i) (*((int *) (i)))
  int displayObj= displayObject();
  if ((((((unsigned)(longAt(displayObj))) >> 8) & 15) <= 4) &&
      ((lengthOf(displayObj)) >= 4))
    {
      int dispBits= longAt((displayObj + 4) + (0 * 4));
      int w= fetchIntegerofObject(1, displayObj);
      int h= fetchIntegerofObject(2, displayObj);
      int d= fetchIntegerofObject(3, displayObj);
      int dispBitsIndex= dispBits + 4;
      ioShowDisplay(dispBitsIndex, w, h, d, l, r, t, b);
    }
# undef longAt
}


static void getMousePosition(void)
{
  Window root, child;
  int rootX, rootY, winX, winY;
  unsigned int mask;
  if (True == XQueryPointer(stDisplay, stWindow, &root, &child,
			    &rootX, &rootY, &winX, &winY, &mask))
    {
      mousePosition.x= winX;
      mousePosition.y= winY;
      /* could update modifiers from mask too, but I can't be bothered... */
    }
}


/* Returns absolute window position.
 * 
 * If the Squeak window is `decorated' (border, title bar, etc.) by
 * the window manager then it will be a subwindow of a larger window
 * (the `WM window') holding the decorations.  When saving geometry
 * before going fullscreen we need the width and height of the Squeak
 * window, but the absolute position of the WM window.  Otherwise the
 * Squeak window will `creep' down and to the right on the display,
 * horizontally by the width of the decoration border and vertically
 * by the decoration border width + title bar height, each time we
 * toggle fullscreen off.
 */
static void getAbsoluteGeometry(Display *dpy, Window win,
				int *winX, int* winY, int* winW, int* winH)
{
  Window root, wmWin;
  int x, y, w, h;
  unsigned int b, d;

  XGetGeometry(dpy, win, &root, &x, &y, winW, winH, &b, &d);
  do
    {
      Window parent, *children;
      unsigned int nchildren;
      XQueryTree(dpy, win, &root, &parent, &children, &nchildren);
      if (children != 0) XFree(children);
      wmWin=win;
      win= parent;
    }
  while (win != root);
  XGetGeometry(dpy, wmWin, &root, winX, winY, &w, &h, &b, &d);
}

#endif /*!HEADLESS*/


int HandleEvents(void)
{
#ifndef HEADLESS
  XEvent theEvent;
#endif
  /* quick checks for asynchronous socket/sound i/o */
  if (socketPollFunction != 0)
    socketPollFunction(0, 0);
  if (soundPollFunction != 0)
    soundPollFunction();
#ifdef HEADLESS
  return 0;
#else /*!HEADLESS*/
  if (!isConnectedToXServer || !XPending(stDisplay))
    return 0;

  XNextEvent(stDisplay, &theEvent);
      
  switch (theEvent.type)
    {
    case MotionNotify:
      mousePosition.x= ((XMotionEvent *)&theEvent)->x;
      mousePosition.y= ((XMotionEvent *)&theEvent)->y;
      break;

    case ButtonPress:
      recordMouseDown((XButtonEvent *)&theEvent);
      return false;
      break;

    case ButtonRelease:
      recordModifierButtons((XButtonEvent *)&theEvent);
      /* button up on "paste" causes a selection retrieval:
	 record the event time in case we need it later */
      stButtonTime= ((XButtonEvent *)&theEvent)->time;
      return false;
      break;

    case KeyPress:
      recordModifierButtons((XButtonEvent *)&theEvent);
      recordKeystroke((XKeyEvent *)&theEvent);
      /* a key bound to the paste operation causes a selection
	 retrieval: record the event time in case we need it later */
      break;

    case SelectionClear:
      stOwnsSelection= 0;
      break;

    case SelectionRequest:
      sendSelection(&theEvent.xselectionrequest);
      break;

    case Expose:
      {
	XExposeEvent *ex= (XExposeEvent *)&theEvent;
# if defined(USE_XSHM)
	if (asyncUpdate)
	  {
	    /* wait for pending updates */
	    while (completions) HandleEvents();
	  }
# endif
# ifdef FULL_UPDATE_ON_EXPOSE
	/* ignore it if there are other exposures upstream */
	if (ex->count == 0)
	  fullDisplayUpdate();  /* this makes VM call ioShowDisplay */
# else
	redrawDisplay(ex->x, ex->x + ex->width, ex->y, ex->y + ex->height);
# endif /*!FULL_UPDATE_ON_EXPOSE*/
      }
      break;

    case MapNotify:
      /* The window has just been mapped, possibly for the first
	 time: update mousePosition (which otherwise may not be
	 set before the first button event). */
      getMousePosition();
      noteWindowChange();
      break;

    case UnmapNotify:
      {
	if (sleepWhenUnmapped)
	  do
	    {
	      XNextEvent(stDisplay, &theEvent);
	      switch (theEvent.type)
		{
		case SelectionClear:
		  stOwnsSelection= 0;
		  break;
		case SelectionRequest:
		  sendSelection(&theEvent.xselectionrequest);
		  break;
		}
	    } while (theEvent.type != MapNotify);
	getMousePosition();
      }
      noteWindowChange();
      break;

    case ClientMessage:
      if (browserWindow != 0)
	pluginHandleEvent(&theEvent);
      break;

      /* with the right hook in the VM we could also treat
	 ConfigureNotify here to automatically restore the
	 Display when the window size changes */
    case ConfigureNotify:
      noteWindowChange();
      break;

# ifdef USE_XSHM
    default:
      if (theEvent.type == completionType) --completions;
      break;
# endif
    }
  return 1;
#endif /* !HEADLESS */
}

#ifndef HEADLESS
void getMaskbit(unsigned long ul, int *nmask, int *shift)
{
  int i;
  unsigned long hb;

  *nmask= *shift= 0;
  hb= 0x8000;  hb= (hb<<16);  /* hb = 0x80000000UL */
  for (i= 31; ((ul & hb) == 0) && i >= 0; --i, ul<<= 1)
    ;
  for (; ((ul & hb) != 0) && i >= 0; --i, ul<<= 1)
    (*nmask)++;
  *shift= i+1;
}

void setupDownGradingColors(void)
{
  int r, g, b, i;

  for (r= 0; r < 0x8; r++)
    {
      for (g= 0; g < 0x8; g++)
	{
	  for (b= 0; b < 0x4; b++)
	    {
	      int mindiff= 0x7*0x7 + 0x7*0x7 + 0x3*0x3 + 1;
	      for (i= 0; i < 256; i++)
		{
		  int rdiff, gdiff, bdiff, diff;

		  rdiff= r - ((stColors[i]>>5) & 0x7);
		  gdiff= g - ((stColors[i]>>2) & 0x7);
		  bdiff= b -  (stColors[i] & 0x3);
		  diff= rdiff*rdiff + gdiff*gdiff + bdiff*bdiff;
		  if (diff < mindiff)
		    {
		      mindiff= diff;
		      stDownGradingColors[(r << 5) + (g << 2) + b]= i;
		    }
		}
	    }
	}
    }
}

void SetColorEntry(int index, int red, int green, int blue)
{
  if (index >= 256)
    return;

  if (stVisual->class == TrueColor || stVisual->class == DirectColor)
    {
      unsigned int r, g, b;
      r= red;
      g= green;
      b= blue;

      stColors[index]= (((r>>(16-stRNMask))<<stRShift) |
			((g>>(16-stGNMask))<<stGShift) |
			((b>>(16-stBNMask))<<stBShift));
    }
  else
    {
      XColor color;
      color.pixel= index;
      color.red= red;
      color.green= green;
      color.blue= blue;
      color.flags= DoRed|DoGreen|DoBlue;
      XStoreColor(stDisplay, stColormap, &color);
      /* map rgb weight=332 */
      stColors[index]= ((((unsigned int)red  >>(16-3))<<5) |
			(((unsigned int)green>>(16-3))<<2) |
			((unsigned int)blue >>(16-2)));
    }
}

void SetUpPixmap(void)
{
  int count;
  XPixmapFormatValues *xpv;

  xpv= XListPixmapFormats(stDisplay, &count);

  if (xpv)
    {
      while(--count >= 0)
	{
	  if (stDepth == xpv[count].depth)
	    stBitsPerPixel= xpv[count].bits_per_pixel;
	}
      XFree((void*)xpv);
    }
  if (stBitsPerPixel == 0)
    stBitsPerPixel= stDepth;

  switch (stVisual->class)
    {
    case PseudoColor:
      if (stBitsPerPixel == 8)
	break;
      else
	{
	  fprintf(stderr, "Visual class PseudoColor is not supported at depth %d\n", stBitsPerPixel);
	  exit(1);
	  return;
	}
    case TrueColor:
    case DirectColor:
      getMaskbit(stVisual->red_mask,   &stRNMask, &stRShift);
      getMaskbit(stVisual->green_mask, &stGNMask, &stGShift);
      getMaskbit(stVisual->blue_mask,  &stBNMask, &stBShift);
      if (stBitsPerPixel == 16)
	{
	  stHasSameRGBMask16= (stVisual->red_mask   == (0x1f << 10) &&
			       stVisual->green_mask == (0x1f << 5) &&
			       stVisual->blue_mask  == (0x1f));
	  break;
	}
      else if (stBitsPerPixel == 32)
	{
	  stHasSameRGBMask32= (stVisual->red_mask   == (0xff << 16) &&
			       stVisual->green_mask == (0xff << 8) &&
			       stVisual->blue_mask  == (0xff));
	  break;
	} else if (stBitsPerPixel == 24)
	{
	  /* nothing to do... */
	  break;
	}
      else
	{
	  fprintf(stderr, "Visual class TrueColor is not supported at depth %d\n", stBitsPerPixel);
	  exit(1);
	  return;
	}
    case GrayScale:
    case StaticColor:
    case StaticGray:
    default:
      fprintf(stderr, "This visual class is not supported\n");
      exit(1);
      return;
    }

  if (stVisual->class == PseudoColor)
    stColormap= XCreateColormap(stDisplay, stWindow, stVisual, AllocAll);



  /* 1-bit colors (monochrome) */
  SetColorEntry(0, 65535, 65535, 65535);	/* white or transparent */
  SetColorEntry(1,     0,     0,     0);	/* black */

	/* additional colors for 2-bit color */
  SetColorEntry(2, 65535, 65535, 65535);	/* opaque white */
  SetColorEntry(3, 32768, 32768, 32768);	/* 1/2 gray */

	/* additional colors for 4-bit color */
  SetColorEntry( 4, 65535,     0,     0);	/* red */
  SetColorEntry( 5,     0, 65535,     0);	/* green */
  SetColorEntry( 6,     0,     0, 65535);	/* blue */
  SetColorEntry( 7,     0, 65535, 65535);	/* cyan */
  SetColorEntry( 8, 65535, 65535,     0);	/* yellow */
  SetColorEntry( 9, 65535,     0, 65535);	/* magenta */
  SetColorEntry(10,  8192,  8192,  8192);	/* 1/8 gray */
  SetColorEntry(11, 16384, 16384, 16384);	/* 2/8 gray */
  SetColorEntry(12, 24576, 24576, 24576);	/* 3/8 gray */
  SetColorEntry(13, 40959, 40959, 40959);	/* 5/8 gray */
  SetColorEntry(14, 49151, 49151, 49151);	/* 6/8 gray */
  SetColorEntry(15, 57343, 57343, 57343);	/* 7/8 gray */

	/* additional colors for 8-bit color */
	/* 24 more shades of gray (does not repeat 1/8th increments) */
  SetColorEntry(16,  2048,  2048,  2048);	/*  1/32 gray */
  SetColorEntry(17,  4096,  4096,  4096);	/*  2/32 gray */
  SetColorEntry(18,  6144,  6144,  6144);	/*  3/32 gray */
  SetColorEntry(19, 10240, 10240, 10240);	/*  5/32 gray */
  SetColorEntry(20, 12288, 12288, 12288);	/*  6/32 gray */
  SetColorEntry(21, 14336, 14336, 14336);	/*  7/32 gray */
  SetColorEntry(22, 18432, 18432, 18432);	/*  9/32 gray */
  SetColorEntry(23, 20480, 20480, 20480);	/* 10/32 gray */
  SetColorEntry(24, 22528, 22528, 22528);	/* 11/32 gray */
  SetColorEntry(25, 26624, 26624, 26624);	/* 13/32 gray */
  SetColorEntry(26, 28672, 28672, 28672);	/* 14/32 gray */
  SetColorEntry(27, 30720, 30720, 30720);	/* 15/32 gray */
  SetColorEntry(28, 34815, 34815, 34815);	/* 17/32 gray */
  SetColorEntry(29, 36863, 36863, 36863);	/* 18/32 gray */
  SetColorEntry(30, 38911, 38911, 38911);	/* 19/32 gray */
  SetColorEntry(31, 43007, 43007, 43007);	/* 21/32 gray */
  SetColorEntry(32, 45055, 45055, 45055);	/* 22/32 gray */
  SetColorEntry(33, 47103, 47103, 47103);	/* 23/32 gray */
  SetColorEntry(34, 51199, 51199, 51199);	/* 25/32 gray */
  SetColorEntry(35, 53247, 53247, 53247);	/* 26/32 gray */
  SetColorEntry(36, 55295, 55295, 55295);	/* 27/32 gray */
  SetColorEntry(37, 59391, 59391, 59391);	/* 29/32 gray */
  SetColorEntry(38, 61439, 61439, 61439);	/* 30/32 gray */
  SetColorEntry(39, 63487, 63487, 63487);	/* 31/32 gray */

  /* The remainder of color table defines a color cube with six steps
     for each primary color. Note that the corners of this cube repeat
     previous colors, but simplifies the mapping between RGB colors and
     color map indices. This color cube spans indices 40 through 255.
     */
  {
    int r, g, b;

    for (r= 0; r < 6; r++)
      for (g= 0; g < 6; g++)
	for (b= 0; b < 6; b++)
	  {
	    int i= 40 + ((36 * r) + (6 * b) + g);
	    if (i > 255) error("index out of range in color table compuation");
	    SetColorEntry(i, (r * 65535) / 5, (g * 65535) / 5, (b * 65535) / 5);
	  }
  }

  stColorWhite.red= stColorWhite.green= stColorWhite.blue= 65535;
  stColorBlack.red= stColorBlack.green= stColorBlack.blue= 0;

  if (stVisual->class == PseudoColor)
    {
      XSetWindowColormap(stDisplay, stWindow, stColormap);
      stColorWhite.pixel= 0;
      stColorBlack.pixel= 1;
#if 0
      /* initialise the black and white color values for cursor creation */
      if (XAllocColor(stDisplay, stColormap, &stColorWhite))
	fprintf(stderr, "failed to find white pixel in Squeak colormap\n");

      if (XAllocColor(stDisplay, stColormap, &stColorBlack))
	fprintf(stderr, "failed to find black pixel in Squeak colormap\n");
#endif
      setupDownGradingColors();
    }
  else
    {
      stColorWhite.pixel= WhitePixel(stDisplay, DefaultScreen(stDisplay));
      stColorBlack.pixel= BlackPixel(stDisplay, DefaultScreen(stDisplay));
    }
}


void SetUpWindow(char *displayName)
{
  XRectangle windowBounds= { 0, 0, 640, 480 };  /* default window bounds */

  int right, bottom;

  stDisplay= XOpenDisplay(displayName);
  if (!stDisplay)
    {
      fprintf(stderr, "Could not open display `%s'.\n", displayName);
      exit(1);
    }

  /* Plugin must be initialized after Display has been opened, but
  before the Squeak window is created. */
  if (browserWindow) pluginInit();

  /* get screen size */
  scrH= (DisplayHeight(stDisplay, DefaultScreen(stDisplay)));
  scrW= (DisplayWidth(stDisplay, DefaultScreen(stDisplay)));
  if ((scrW % sizeof(void *)) != 0)
    scrW= (scrW / sizeof(void *)) * sizeof(void *);

  stXfd= ConnectionNumber(stDisplay);

  /* find the most suitable Visual */
  {
    /* preferred visuals in order of decreasing priority */
    static int trialVisuals[][2]= {
      { 32, TrueColor },
      { 32, DirectColor },
      { 32, StaticColor },
      { 32, PseudoColor },
      { 24, TrueColor },
      { 24, DirectColor },
      { 24, StaticColor },
      { 24, PseudoColor },
      { 16, TrueColor },
      { 16, DirectColor },
      { 16, StaticColor },
      { 16, PseudoColor },
      {  8, PseudoColor },
      {  8, DirectColor },
      {  8, TrueColor },
      {  8, StaticColor },
      {  0, 0 }
    };

    XVisualInfo viz;
    int i;

    for (i= 0; trialVisuals[i][0] != 0; ++i)
      {
#       if 0
	fprintf(stderr, "Trying %d bit %s.\n", trialVisuals[i][0],
		debugVisual(trialVisuals[i][1]));
#       endif
	if (XMatchVisualInfo(stDisplay, DefaultScreen(stDisplay),
			     trialVisuals[i][0], trialVisuals[i][1],
			     &viz) != 0) break;
      }
    if (trialVisuals [i][0] == 0)
      {
#	if 0
	fprintf(stderr, "Using default visual.\n");
#	endif
	stVisual= DefaultVisual(stDisplay, DefaultScreen(stDisplay));
	stDepth= DefaultDepth(stDisplay, DefaultScreen(stDisplay));
      }
    else
      {
	stVisual= viz.visual;
	stDepth= trialVisuals[i][0];
      }
  }

  if (savedWindowSize != 0)
    {
      right=  windowBounds.x + ((unsigned) savedWindowSize >> 16);
      bottom= windowBounds.y + (savedWindowSize & 0xFFFF);
    }
  else
    {
      right= windowBounds.x + windowBounds.width;
      bottom= windowBounds.y + windowBounds.height;
    }

  /* minimum size is 64 x 64 */
  right= ( right > (windowBounds.x + 64)) ?  right : (windowBounds.x + 64);
  bottom= (bottom > (windowBounds.y + 64)) ? bottom : (windowBounds.y + 64);

  /* maximum bottom-right is screen bottom-right */
  right=  ( right <= DisplayWidth(stDisplay, DefaultScreen(stDisplay)))
    ?  right : (DisplayWidth(stDisplay, DefaultScreen(stDisplay))  - 8);
  bottom= (bottom <= DisplayHeight(stDisplay, DefaultScreen(stDisplay)))
    ? bottom : (DisplayHeight(stDisplay, DefaultScreen(stDisplay)) - 8);

  windowBounds.width= right - windowBounds.x;
  windowBounds.height= bottom - windowBounds.y;

  /* create the Squeak window */
  {
    XSetWindowAttributes attributes;
    unsigned long valuemask;

    attributes.border_pixel= BlackPixel(stDisplay, DefaultScreen(stDisplay));
    attributes.background_pixel= BlackPixel(stDisplay, DefaultScreen(stDisplay));
    attributes.event_mask= WM_EVENTMASK;
    attributes.backing_store= Always;

    valuemask= CWEventMask | CWBackingStore | CWBorderPixel | CWBackPixel;

    if (browserWindow != 0)
      stParent= browserWindow;
    else
      stParent= XCreateWindow(stDisplay,
			      DefaultRootWindow(stDisplay),
			      windowBounds.x, windowBounds.y,
			      windowBounds.width, windowBounds.height,
			      0,
			      stDepth, CopyFromParent, stVisual,
			      CWEventMask, &attributes);

    attributes.event_mask= EVENTMASK;

    /* A visual that is not DefaultVisual requires its own color map.
       If visual is PseudoColor, the new color map is made elsewhere. */
    if ((stVisual != DefaultVisual(stDisplay, DefaultScreen(stDisplay))) &&
	(stVisual->class != PseudoColor))
      {
	stColormap= XCreateColormap(stDisplay,
				    RootWindow(stDisplay, DefaultScreen(stDisplay)),
				    stVisual,
				    AllocNone);
	attributes.colormap= stColormap;
	valuemask|= CWColormap;
      }

    stWindow= XCreateWindow(stDisplay, stParent,
			    0, 0,
			    windowBounds.width, windowBounds.height,
			    0,
			    stDepth, InputOutput, stVisual,
			    valuemask, &attributes);
  }

  /* set the window title and resource/class names */
  {
    XClassHint *classHints= XAllocClassHint();
    classHints->res_class= xResClass;
    classHints->res_name= xResName;
    if (browserWindow == 0)
      {
	XSetClassHint(stDisplay, stParent, classHints);
	XStoreName(stDisplay, stParent, shortImageName);
      }
  }

  /* tell the WM that we can't be bothered managing focus for ourselves */
  {
    XWMHints *wmHints= XAllocWMHints();
    wmHints->input= True;
    wmHints->initial_state= NormalState;
    wmHints->flags= InputHint|StateHint;
# ifndef NO_ICON
    wmHints->icon_pixmap=
      XCreateBitmapFromData(stDisplay, DefaultRootWindow(stDisplay),
			    sqXIcon_bits, sqXIcon_width, sqXIcon_height);
    if (wmHints->icon_pixmap != None) wmHints->flags |= IconPixmapHint;
# endif
    XSetWMHints(stDisplay, stParent, wmHints);
    XFree((void *)wmHints);
  }

  /* create a suitable graphics context */
  {
    XGCValues gcValues;

    gcValues.function= GXcopy;
    gcValues.subwindow_mode= IncludeInferiors;
    gcValues.clip_x_origin= 0;
    gcValues.clip_y_origin= 0;
    gcValues.clip_mask= None;
    gcValues.foreground= SqueakBlack;
    gcValues.background= SqueakWhite;
    gcValues.fill_style= FillSolid;
    stGC= XCreateGC(stDisplay,
		    stWindow,
		    GCFunction | GCSubwindowMode |
		    GCClipXOrigin | GCClipYOrigin | GCClipMask |
		    GCForeground | GCBackground | GCFillStyle,
		    &gcValues);
  }

  if (noTitle || fullScreen)
    /* naughty, but effective */
    XSetTransientForHint(stDisplay, stParent, DefaultRootWindow(stDisplay));

# ifdef USE_XSHM
  completionType= XShmGetEventBase(stDisplay) + ShmCompletion;
# endif
}


void SetWindowSize(void)
{
  int width, height, maxWidth, maxHeight;

  if (browserWindow) return;

  if (savedWindowSize != 0)
    {
      width=  (unsigned)savedWindowSize >> 16;
      height= savedWindowSize & 0xFFFF;
    }
  else
    {
      width=  640;
      height= 480;
    }

  /* minimum size is 64 x 64 */
  width=  ( width > 64) ?   width : 64;
  height= (height > 64) ?  height : 64;

  /* maximum size is screen size */
  maxWidth=  (DisplayWidth(stDisplay, DefaultScreen(stDisplay)));
  maxHeight= (DisplayHeight(stDisplay, DefaultScreen(stDisplay)));
  width=  ( width <= maxWidth)  ?  width : maxWidth;
  height= (height <= maxHeight) ? height : maxHeight;

  if (fullScreen)
    {
      width= maxWidth;
      height= maxHeight;
    }

  XResizeWindow(stDisplay, stParent, width, height);
}


/*** Event Recording Functions ***/


static int translateCode(KeySym symbolic)
{
# define ALT (8<<8)
  switch (symbolic)
    {
    case XK_Left:	return 28;
    case XK_Up:		return 30;
    case XK_Right:	return 29;
    case XK_Down:	return 31;
    case XK_Insert:	return  5;
    case XK_Prior:	return 11;	/* page up */
    case XK_Next:	return 12;	/* page down */
    case XK_Home:	return  1;
    case XK_End:	return  4;

    /* "aliases" for Sun keyboards */
    case XK_R9:		return 11;	/* page up */
    case XK_R15:	return 12;	/* page down */
    case XK_R7:		return  1;	/* home */
    case XK_R13:	return  4;	/* end */
    case XK_L1:		return ALT+'.';	/* stop */
    case XK_L2:		return ALT+'j';	/* again */
    case XK_L4:		return ALT+'z';	/* undo */
    case XK_L6:		return ALT+'c';	/* copy */
    case XK_L8:		return ALT+'v';	/* paste */
    case XK_L9:		return ALT+'f';	/* find */
    case XK_L10:	return ALT+'x';	/* cut */

    /* XKB extensions */
# ifdef XK_ISO_Left_Tab
    case XK_ISO_Left_Tab: return  9;	/* shift-tab */
# endif

    default:		return -1;
    }
  /*NOTREACHED*/
# undef ALT
}


void keyBufAppend(int keystate)
{
  /* bug: this should be rewritten to cope with nConv > 1 */
  keyBuf[keyBufPut]= keystate;
  keyBufPut= (keyBufPut + 1) % KEYBUF_SIZE;
  if (keyBufGet == keyBufPut)
    {
      /* buffer overflow; drop the last character */
      keyBufGet= (keyBufGet + 1) % KEYBUF_SIZE;
      keyBufOverflows++;
    }
}


void recordKeystroke(XKeyEvent *theEvent)
{
  int keystate;
  unsigned char buf[32];
  int nConv;
  KeySym symbolic;

  nConv= XLookupString(theEvent, buf, sizeof(buf), &symbolic, 0);

  keystate= buf[0];

  if (keystate == 127)
    keystate= 8;

  if (nConv == 0 && (keystate= translateCode(symbolic)) < 0)
    return;	/* unknown key */

  if (keystate >= 128)
    keystate= X_to_Squeak[keystate];

  keystate|= (modifierMap[(theEvent->state) & 0xF] << 8);

  if (keystate == interruptKeycode)
    {
      /* Note: interrupt key is "meta"; it not reported as a keystroke */
      interruptPending= true;
      interruptCheckCounter= 0;
    }
  else
    {
      keyBufAppend(keystate);
    }
}

void recordMouseDown(XButtonEvent *theEvent)
{
  int stButtons= 0;

  switch (theEvent->button)
    {
    case 1: stButtons= 4; break;
    case 2: stButtons= 2; break;
    case 3: stButtons= 1; break;
    case 4: /* Mouse wheel support */
    case 5: keyBufAppend( (theEvent->button + 26)        /* Up/Down */
		       | (2 << 8)                          /* Ctrl  */
		       | (modifierMap[(theEvent->state) & 0xF] << 8));
            return;
    default: ioBeep(); break;
    }

  if (stButtons == 4)	/* red button honours the modifiers */
    {
      if (theEvent->state & MOD_CONTROL)
	stButtons= 2;	/* yellow button if CTRL down */
      else if (theEvent->state & MOD_META)
	stButtons= 1;	/* blue button if META down */
    }
  /* button state: low three bits are mouse buttons; next 4 bits are modifier bits */
  buttonState= (modifierMap[(theEvent->state) & 0xF] << 3) | (stButtons & 0x7);
}

/* both button and key events have the state member in the same place */
void recordModifierButtons(XButtonEvent *theEvent)
{
  int stButtons= 0;

  if (theEvent->type == ButtonPress)
    stButtons= buttonState & 0x7;
  else
    stButtons= 0;
  /* button state: low three bits are mouse buttons; next 4 bits are modifier bits */
  buttonState= (modifierMap[(theEvent->state) & 0xF] << 3) | (stButtons & 0x7);
}

#endif /*!HEADLESS*/


/*** I/O Primitives ***/


int ioFormPrint(int bitsAddr, int width, int height, int depth,
		double hScale, double vScale, int landscapeFlag)
{
#ifdef HEADLESS

  fprintf(stderr,
	  "Sorry, a headless VM cannot print Forms.  If you\n"
	  "*really* need this then let me know, since there\n"
	  "is a (rather painful to implement) solution.\n");
  return false;

#else /*!HEADLESS*/

  /* Write the form as a PPM (Portable PixMap) file, from which it can
     be converted into almost any existing graphical format (including
     PostScript).  See the "netpbm" utilities for a huge collection of
     image manipulation tools that understand the PPM format.
     Note that "xv" can also read, convert, and do image processing on
     PPM files.
     The output filename is defined in "sqPlatformSpecific.h". */

  FILE *ppm;
  int ok= true;

  if ((ppm= fopen(SQ_FORM_FILENAME, "wb")) == 0)
    return false;
  /* PPM magic number and pixmap header */
  fprintf(ppm, "P3\n%d %d 65535\n", width, height);

  switch (depth)
    {
    case 8:
      {
	unsigned char *bits= (unsigned char *) bitsAddr;
	int ppw= 32 / depth;
	int raster= ((width + ppw - 1) / ppw) * 4;
	/* stColors[] is too approximate: query the real colormap */
	XColor colors[256];
	int i;
	for (i= 0; i < 256; ++i) colors[i].pixel= i;
	/* all colors in one query reduces server traffic */
	XQueryColors(stDisplay, stColormap, colors, 256);
	/* write the pixmap */
	{
	  int y;
	  for (y= 0; y < height; ++y) {
	    int x;
	    for (x= 0; x < width; ++x) {
	      /* everything is backwards (as usual ;) */
	      int index= y * raster + x;
	      int byte= 3 - (index & 0x00000003);
	      int word= index & -4;
	      int pixel= bits[word + byte];
	      fprintf(ppm, "%d %d %d\n",
		      colors[pixel].red, colors[pixel].green, colors[pixel].blue);
	    }
	  }
	}
	break;
      } /* case 8 */
    default:
      fprintf(stderr,
	      "Depth %d pixmaps are not yet supported.  If you *really*\n"
	      "need this then bribe me with something (a Korg OPSYS PCI\n"
	      "sound card would do the trick ;).\n", depth);
      ok= false;
      break;
    } /* switch */

  fclose(ppm);
  return ok;

#endif /*!HEADLESS*/
}


int ioBeep(void)
{
#ifndef HEADLESS
  XBell(stDisplay, 0);	/* ring at default volume */
#endif
  return 0;
}


int ioGetButtonState(void)
{
  ioProcessEvents();  /* process all pending events */
#ifndef HEADLESS
  return buttonState;
#else
  return 0;
#endif
}

int ioGetKeystroke(void)
{
  int keystate;

  ioProcessEvents();  /* process all pending events */
#ifdef HEADLESS
  return -1;  /* keystroke buffer is empty */
#else
  if (keyBufGet == keyBufPut)
    return -1;  /* keystroke buffer is empty */

  keystate= keyBuf[keyBufGet];
  keyBufGet= (keyBufGet + 1) % KEYBUF_SIZE;
  /* set modifer bits in buttonState to reflect the last keystroke fetched */
  buttonState= ((keystate >> 5) & 0xF8) | (buttonState & 0x7);

  return keystate;
#endif
}


int ioLowResMSecs(void)
{
#ifdef USE_ITIMER
  return lowResMSecs;
#else
  return ioMSecs();
#endif
}


int ioMSecs(void)
{
  struct timeval now;
  gettimeofday(&now, 0);
  if ((now.tv_usec-= startUpTime.tv_usec) < 0) {
    now.tv_usec+= 1000000;
    now.tv_sec-= 1;
  }
  now.tv_sec-= startUpTime.tv_sec;
  return (now.tv_usec / 1000 + now.tv_sec * 1000);
}

int ioMicroMSecs(void)
{
  /* return the highest available resolution of the millisecond clock */
  return ioMSecs();	/* this already to the nearest millisecond */
}

int ioRelinquishProcessorForMicroseconds(int microSeconds)
{
  /* sleep in select() for immediate response to socket i/o */
  if (socketPollFunction != 0)
#ifdef HEADLESS
    socketPollFunction(microSeconds, 0);
#else
    socketPollFunction(microSeconds, stXfd);
#endif
  else
    {
      /* can't rely on BSD usleep */
      struct timeval tv;
      tv.tv_sec=  microSeconds / 1000000;
      tv.tv_usec= microSeconds % 1000000;
      select(0, 0, 0, 0, &tv);
    }
  return microSeconds;
}

int ioMousePoint(void)
{
  ioProcessEvents();  /* process all pending events */
  /* x is high 16 bits; y is low 16 bits */
#ifndef HEADLESS
  return (mousePosition.x << 16) | (mousePosition.y);
#else
  return 0;
#endif
}

int ioPeekKeystroke(void)
{
#ifdef HEADLESS
  ioProcessEvents();  /* process all pending events */
  return -1;  /* keystroke buffer is empty */
#else
  int keystate;

  ioProcessEvents();  /* process all pending events */

  if (keyBufGet == keyBufPut)
    return -1;  /* keystroke buffer is empty */

  keystate= keyBuf[keyBufGet];
  /* set modifer bits in buttonState to reflect the last keystroke peeked at */
  buttonState= ((keystate >> 5) & 0xF8) | (buttonState & 0x7);
  return keystate;
#endif
}


/* this should be rewritten to use SIGIO and/or the interval timers */
int ioProcessEvents(void)
{
  static unsigned long nextPollTick= 0;

  if ((unsigned long)ioLowResMSecs() > nextPollTick)
    {
      /* time to process events! */
      while (HandleEvents())
	{
	  /* process all pending events */
	}
    /* wait a while before trying again */
      nextPollTick= ioLowResMSecs() + (1000 / MAXPOLLSPERSEC);
    }
  return 0;
}


/* returns the size of the Squeak window */
int ioScreenSize(void)
{
#ifdef HEADLESS
  return ((64 << 16) | 64);
#else
  static unsigned int w= 0, h= 0;

  if (windowState == WIN_NORMAL)  return (   w << 16) |    h;
  if (windowState == WIN_ZOOMED)  return (scrW << 16) | scrH;
  if (!isConnectedToXServer)      return (  64 << 16) |   64;

  windowState= WIN_NORMAL;
  {
    Window root;
    int x, y;
    unsigned int b, d;
    XGetGeometry(stDisplay, stParent, &root, &x, &y, &w, &h, &b, &d);
  }
  /* width must be a multiple of sizeof(void *), or X[Shm]PutImage goes gaga */
  if ((w % sizeof(void *)) != 0)
    {
      w= (w / sizeof(void *)) * sizeof(void *);
      XResizeWindow(stDisplay, stParent, w, h);
      XResizeWindow(stDisplay, stWindow, w, h);
      noteWindowChange();
    }
  return (w << 16) | h;  /* w is high 16 bits; h is low 16 bits */
#endif
}


time_t convertToSqueakTime(time_t unixTime)
{
#ifdef HAVE_TM_GMTOFF
  unixTime+= localtime(&unixTime)->tm_gmtoff;
#else
# ifdef HAVE_TIMEZONE
  unixTime+= ((daylight) * 60*60) - timezone;
# else
#  error: cannot determine timezone correction
# endif
#endif
  /* Squeak epoch is Jan 1, 1901.  Unix epoch is Jan 1, 1970: 17 leap years
     and 52 non-leap years later than Squeak. */
  return unixTime + ((52*365UL + 17*366UL) * 24*60*60UL);
}


/* returns the local wall clock time */
int ioSeconds(void)
{
  return convertToSqueakTime(time(0));
}

#ifndef HEADLESS
static unsigned char swapBits(unsigned char in)
{
  unsigned char out= 0;
  int i;
  for (i= 0; i < 8; i++)
    {
      out= (out << 1) + (in & 1);
      in >>= 1;
    }
  return out;
}
#endif


int ioSetCursorWithMask(int cursorBitsIndex, int cursorMaskIndex,
			int offsetX, int offsetY)
{
#ifndef HEADLESS
  unsigned char data[32], mask[32];	/* cursors are always 16x16 */
  int i;
  Cursor cursor;
  Pixmap dataPixmap, maskPixmap;

#if 0
  if (cursorMaskIndex == null) {
    for (i= 0; i < 16; i++)
      {
	data[i*2+0]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 24) & 0xFF;
	data[i*2+1]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 16) & 0xFF;
	mask[i*2+0]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 24) & 0xFF;
	mask[i*2+1]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 16) & 0xFF;
      }
  } else {
    for (i= 0; i < 16; i++)
      {
	data[i*2+0]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 24) & 0xFF;
	data[i*2+1]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 16) & 0xFF;
	mask[i*2+0]= ((unsigned)checkedLongAt(cursorMaskIndex + (4 * i)) >> 24) & 0xFF;
	mask[i*2+1]= ((unsigned)checkedLongAt(cursorMaskIndex + (4 * i)) >> 16) & 0xFF;
      }
  }
#else
  if (cursorMaskIndex == null)
    cursorMaskIndex= cursorBitsIndex;

  for (i= 0; i < 16; i++)
    {
      data[i*2+0]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 24) & 0xFF;
      data[i*2+1]= ((unsigned)checkedLongAt(cursorBitsIndex + (4 * i)) >> 16) & 0xFF;
      mask[i*2+0]= ((unsigned)checkedLongAt(cursorMaskIndex + (4 * i)) >> 24) & 0xFF;
      mask[i*2+1]= ((unsigned)checkedLongAt(cursorMaskIndex + (4 * i)) >> 16) & 0xFF;
    }
#endif

  /*  if (BitmapBitOrder(stDisplay) == LSBFirst)*/
    {
      /* the bytes are always in the right order: swap only bits within bytes */
      char *dp= (char *)data;
      char *mp= (char *)mask;
      for (i= 0; i < 32; i++)
	{
	  dp[i]= swapBits(dp[i]);
	  mp[i]= swapBits(mp[i]);
	}
    }

  dataPixmap= XCreateBitmapFromData(stDisplay,
				    DefaultRootWindow(stDisplay),
				    (char *)data, 16, 16);

  maskPixmap= XCreateBitmapFromData(stDisplay,
				    DefaultRootWindow(stDisplay),
				    (char *)mask, 16, 16);

  cursor= XCreatePixmapCursor(stDisplay, dataPixmap, maskPixmap,
			      &stColorBlack, &stColorWhite,
			      -offsetX, -offsetY);

  XFreePixmap(stDisplay, dataPixmap);
  XFreePixmap(stDisplay, maskPixmap);

  if (cursor != None)
    XDefineCursor(stDisplay, stWindow, cursor);

  XFreeCursor(stDisplay, cursor);
#endif
  return 0;
}


int ioSetCursor(int cursorBitsIndex, int offsetX, int offsetY)
{
  /* Deprecated: forward to new version with explicit mask. */
  ioSetCursorWithMask(cursorBitsIndex, null, offsetX, offsetY);
  return 0;
}


static void overrideRedirect(Display *dpy, Window win, int flag)
{
  XSetWindowAttributes attrs;
  attrs.override_redirect= flag;
  XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &attrs);
}

int ioSetFullScreen(int fullScreen)
{
#ifndef HEADLESS
  int winX, winY;
  unsigned int winW, winH;

  if (fullScreen)
    {
      /* setting full-screen mode */
      if (savedWindowOrigin == -1)
	{
	  /* EITHER: no previous call, OR: previous call disabled full-screen mode */
	  Window root;
	  {
	    /* need extent only */
	    unsigned int b, d;
	    XGetGeometry(stDisplay, stWindow, &root, &winX, &winY, &winW, &winH, &b, &d);
	  }
	  /* width must be a multiple of sizeof(void *), or X[Shm]PutImage goes gaga */
	  if ((winW % sizeof(void *)) != 0)
	    winW= (winW / sizeof(void *)) * sizeof(void *);
	  savedWindowSize= (winW << 16) + (winH & 0xFFFF);
	  savedWindowOrigin= (winX << 16) + (winY & 0xFFFF);
	  XSynchronize(stDisplay, True);
	  overrideRedirect(stDisplay, stWindow, True);
	  XReparentWindow(stDisplay, stWindow, root, 0, 0);
	  XResizeWindow(stDisplay, stWindow, scrW, scrH);
	  XLowerWindow(stDisplay, stParent);
	  XRaiseWindow(stDisplay, stWindow);
	  XSynchronize(stDisplay, False);
	  windowState= WIN_ZOOMED;
	}
    }
  else
    {
      /* reverting to sub-screen mode */
      if (savedWindowOrigin != -1)
	{ /* previous call enabled full-screen mode */
	  /* get old window size */
	  winW= (unsigned) savedWindowSize >> 16;
	  winH= savedWindowSize & 0xFFFF;
	  /* minimum size is 64 x 64 */
	  winW= (winW > 64) ? winW : 64;
	  winH= (winH > 64) ? winH : 64;
	  /* old origin */
	  winX= savedWindowOrigin >> 16;
	  winY= savedWindowOrigin & 0xFFFF;
	  savedWindowOrigin= -1; /* prevents consecutive full-screen disables */
	  XSynchronize(stDisplay, True);
	  XRaiseWindow(stDisplay, stParent);
	  XReparentWindow(stDisplay, stWindow, stParent, 0, 0);
	  overrideRedirect(stDisplay, stWindow, False);
	  XResizeWindow(stDisplay, stWindow, winW, winH);
	  XSynchronize(stDisplay, False);
	  windowState= WIN_CHANGED;
	}
    }
  /* sync avoids race with ioScreenSize() reading geometry before resize event */
  XSync(stDisplay, False);
  getMousePosition();
#endif /*!HEADLESS*/
  return 0;
}


#ifndef HEADLESS



/*** shared-memory stuff ***/



# ifdef USE_XSHM
static int xError(Display *dpy, XErrorEvent *evt)
{
  char buf[2048];
  XGetErrorText(stDisplay, evt->error_code, buf, sizeof(buf));
  fprintf(stderr, "XShmAttach: %s\n", buf);
  return 0;
}
#endif


static void *stMalloc(size_t lbs)
{
# ifdef USE_XSHM
  if (useXshm)
    {
      if ((stShmInfo.shmid= shmget(IPC_PRIVATE, lbs, IPC_CREAT|0777)) == -1)
	perror("shmget");
      else
	{
	  if ((int)(stShmInfo.shmaddr= (char *)shmat(stShmInfo.shmid, 0, 0)) == -1)
	    perror("shmat");
	  else
	    {
	      /* temporarily install error handler */
	      XErrorHandler prev= XSetErrorHandler(xError);
	      int result= 0;
	      stShmInfo.readOnly= False;
	      result= XShmAttach(stDisplay, &stShmInfo);
	      XSync(stDisplay, False);
	      XSetErrorHandler(prev);
	      if (result == 0)
		/* success */
		return stShmInfo.shmaddr;
	    }
	  /* could not attach to allocated shared memory segment */
	  shmctl(stShmInfo.shmid, IPC_RMID, 0);
	  shmdt(stShmInfo.shmaddr);
	}
      /* could not allocate shared memory segment */
      useXshm= 0;
    }
# endif /* USE_XSHM */
  return (void *)malloc(lbs);
}


static void stFree(void *addr)
{
#ifdef USE_XSHM
  if (!useXshm)
#endif
    {
      free(addr);
      return;
    }
#ifdef USE_XSHM
  shmctl(stShmInfo.shmid, IPC_RMID, 0);
  shmdt(stShmInfo.shmaddr);
#endif
}

#ifdef USE_XSHM
static void shmExit(void)
{
  if (stDisplayBitmap && useXshm)
    stFree(stDisplayBitmap);
}
#endif


static XImage *stXCreateImage(Display *display, Visual *visual,
			      int depth, int format, int flags, char *data,
			      int width, int height, int bpp, int pad)
{
#ifdef USE_XSHM
  if (!useXshm)
#endif
    return XCreateImage(display, visual, depth, format, flags,
			data, width, height, bpp, pad);
#ifdef USE_XSHM
  return XShmCreateImage(display, visual, depth, format, data,
			 &stShmInfo, width, height);
#endif
}


static void stXPutImage(Display *display, Window window, GC gc, XImage *image,
			int src_x, int src_y, int dst_x, int dst_y, int w, int h)
{
#ifdef USE_XSHM
  if (!useXshm)
#endif
    {
      XPutImage(display, window, gc, image, src_x, src_y, dst_x, dst_y, w, h);
      return;
    }
#ifdef USE_XSHM
  XShmPutImage(display, window, gc, image, src_x, src_y, dst_x, dst_y, w, h, True);
  ++completions;
  if (!asyncUpdate)
    do {
      HandleEvents();
    } while (completions);
#endif
}


static void stXDestroyImage(XImage *image)
{
#ifdef USE_XSHM
  if (useXshm)
    XShmDetach(stDisplay, &stShmInfo);
#endif
  XDestroyImage(image);
}


#define bytesPerLine(width, depth)	((((width)*(depth) + 31) >> 5) << 2)
#define bytesPerLineRD(width, depth)	((((width)*(depth)) >> 5) << 2)

#endif /*!HEADLESS*/


int ioForceDisplayUpdate(void)
{
#if !defined(HEADLESS) && defined(USE_XSHM)
  if (asyncUpdate) {
    XFlush(stDisplay);
    do {
      HandleEvents();
    } while (completions);
  }
#endif
  return 0;
}


int ioShowDisplay(int dispBitsIndex, int width, int height, int depth,
		  int affectedL, int affectedR, int affectedT, int affectedB)
{
#ifndef HEADLESS
  if (stWindow == 0)
    return 0;

  if(affectedR <= affectedL || affectedT >= affectedB)
    return 1;

  if (!(depth == 8 || depth == 16 || depth == 32))
  {
    fprintf(stderr, "depth %d is not supported\n", depth);
    exit(1);
    return 0;
  }

  if (stDisplayBitsIndex != dispBitsIndex)
    {
# if defined(USE_XSHM)
      if (asyncUpdate) {
	/* wait for pending updates to complete before freeing the XImage */
	while (completions) HandleEvents();
      }
# endif
      stDisplayBitsIndex= dispBitsIndex;
      if (stImage)
	{
	  stImage->data= 0; /* don't you dare free() Display's Bitmap! */
	  stXDestroyImage(stImage);
	  if (stDisplayBitmap)
	    {
	      stFree(stDisplayBitmap);
	      stDisplayBitmap= 0;
	    }
	}

# ifndef USE_XSHM
#  define useXshm 0
# endif

# ifdef WORDS_BIGENDIAN
      if (!useXshm && depth == stBitsPerPixel &&
	  (depth != 16 || stHasSameRGBMask16) &&
	  (depth != 32 || stHasSameRGBMask32))
# else
      if (!useXshm && depth == stBitsPerPixel && depth == 32 && stHasSameRGBMask32)
# endif
	{
	  stDisplayBitmap= 0;
	}
      else
	{
	  stDisplayBitmap= stMalloc(bytesPerLine(width, stBitsPerPixel) * height);
	}

# ifndef USE_XSHM
#  undef useXshm
# endif

      stImage= stXCreateImage(stDisplay,
			      DefaultVisual(stDisplay, DefaultScreen(stDisplay)),
			      stDepth,
			      ZPixmap,
			      0,
			      (stDisplayBitmap
			         ? stDisplayBitmap
			         : (char *)stDisplayBitsIndex),
			      width,
			      height,
			      32,
			      0);
      /* Xlib ignores the following */
# ifdef WORDS_BIGENDIAN
      stImage->byte_order= MSBFirst;
      stImage->bitmap_bit_order= MSBFirst;
# else
      stImage->byte_order= LSBFirst;
      stImage->bitmap_bit_order= LSBFirst;
# endif
      /* not really required (since we never call Get/PutPixel), but what the hey */
      /*
      if (!XInitImage(stImage))
	fprintf(stderr, "XInitImage failed (but we don't care)\n");
      */
    }

  /* this can happen after resizing the window */
  if (affectedR > width) affectedR= width;
  if (affectedB > height) affectedB= height;
  if ((affectedR <= affectedL) || (affectedT >= affectedB))
    return 1;

  if (depth != stBitsPerPixel)
    {
      if (depth == 8)
	{
	  if (stBitsPerPixel == 16)
	    {
	      copyImage8To16((int *)dispBitsIndex, (int *)stDisplayBitmap,
			     width, height,
			     affectedL, affectedT, affectedR, affectedB);
	    }
	  else if(stBitsPerPixel == 24)
	    {
	      copyImage8To24((int *)dispBitsIndex, (int *)stDisplayBitmap,
			     width, height,
			     affectedL, affectedT, affectedR, affectedB);
	    } else /* stBitsPerPixel == 32 */
	    {
	      copyImage8To32((int *)dispBitsIndex, (int *)stDisplayBitmap,
			     width, height,
			     affectedL, affectedT, affectedR, affectedB);
	    }
	}
      else if (depth == 16)
	{
	  if (stBitsPerPixel == 8)
	    {
	      copyImage16To8((int *)dispBitsIndex, (int *)stDisplayBitmap,
			     width, height,
			     affectedL, affectedT, affectedR, affectedB);
	    }
	  else if ( stBitsPerPixel == 24)
	    {
	      copyImage16To24((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,
			      affectedL, affectedT, affectedR, affectedB);
	    }else /* stBitsPerPixel == 32 */
	    {
	      copyImage16To32((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,
			      affectedL, affectedT, affectedR, affectedB);
	    }
	}
      else /* depth == 32 */
	{
	  if (stBitsPerPixel == 8)
	    {
	      copyImage32To8((int *)dispBitsIndex, (int *)stDisplayBitmap,
			     width, height,
			     affectedL, affectedT, affectedR, affectedB);
	    }
	  else if (stBitsPerPixel == 16)
	    {
	      copyImage32To16((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,
			      affectedL, affectedT, affectedR, affectedB);
	    }
	  else /* stBitPerPixel == 24 */
	    {
	      copyImage32To24((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,
			      affectedL, affectedT, affectedR, affectedB);
	    }
	}
    }
  else /* depth == stBitsPerPixel */
    {
      if (depth == 16 && !stHasSameRGBMask16)
	{
	  copyImage16To16((int *)dispBitsIndex, (int *)stDisplayBitmap,
			  width, height,
			  affectedL, affectedT, affectedR, affectedB);
	}
      else if (depth == 32 && !stHasSameRGBMask32)
	{
	  copyImage32To32((int *)dispBitsIndex, (int *)stDisplayBitmap,
			  width, height,
			  affectedL, affectedT, affectedR, affectedB);
	}
# ifdef WORDS_BIGENDIAN
#   ifdef USE_XSHM
      else if (useXshm)
	{
	  if (depth == 8)
	    {
	      copyImage8To8((int *)dispBitsIndex, (int *)stDisplayBitmap,
			    width, height,affectedL, affectedT, affectedR, affectedB);
	    }
	  else if (depth == 16)
	    {
	      copyImage16To16((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,affectedL, affectedT, affectedR, affectedB);
	    }
	  else if (depth == 32)
	    {
	      copyImage32To32((int *)dispBitsIndex, (int *)stDisplayBitmap,
			      width, height,affectedL, affectedT, affectedR, affectedB);
	    }
	  else
	    {
	      fprintf(stderr, "shared memory not supported for this depth/byte-order\n");
	      exit(1);
	    }
	}
#   endif
# else
      else if (depth == 8)
	{
	  copyReverseImageBytes((int *)dispBitsIndex, (int *)stDisplayBitmap,
				depth, width, height,
				affectedL, affectedT, affectedR, affectedB);
	}
      else if (depth == 16)
	{
	  copyReverseImageWords((int *)dispBitsIndex, (int *)stDisplayBitmap,
				depth, width, height,
				affectedL, affectedT, affectedR, affectedB);
	}
# endif
    }

  stXPutImage(stDisplay, stWindow, stGC, stImage,
	      affectedL, affectedT,	/* src_x, src_y */
	      affectedL, affectedT,	/* dst_x, dst_y */
	      affectedR-affectedL,	/* width */
	      affectedB-affectedT);	/* height */
#endif /* !HEADLESS */
  return 0;
}


int ioHasDisplayDepth(int i)
{
  switch (i) {
  case 8:
  case 16:
  case 32:
    return true;
  };
  return false;
}


int ioSetDisplayMode(int width, int height, int depth, int fullscreenFlag)
{
  fprintf(stderr, "ioSetDisplayMode(%d, %d, %d, %d)\n",
	  width, height, depth, fullscreenFlag);
  return 0;
}


#ifndef HEADLESS
void copyReverseImageBytes(int *fromImageData, int *toImageData,
			   int depth, int width, int height,
			   int affectedL, int affectedT, int affectedR, int affectedB)
{
  register int scanLine, firstWord, lastWord;
  register int line;

  scanLine= bytesPerLine(width, depth);
  firstWord= scanLine*affectedT + bytesPerLineRD(affectedL, depth);
  lastWord= scanLine*affectedT + bytesPerLine(affectedR, depth);

  for (line= affectedT; line < affectedB; line++)
  {
    register unsigned char *from= (unsigned char *)((int)fromImageData+firstWord);
    register unsigned char *limit= (unsigned char *)((int)fromImageData+lastWord);
    register unsigned char *to= (unsigned char *)((int)toImageData+firstWord);
    while (from < limit)
      {
	to[0]= from[3];
	to[1]= from[2];
	to[2]= from[1];
	to[3]= from[0];
	from+= 4;
	to+= 4;
      }
    firstWord+= scanLine;
    lastWord+= scanLine;
  }
}

void copyReverseImageWords(int *fromImageData, int *toImageData,
			   int depth, int width, int height,
			   int affectedL, int affectedT, int affectedR, int affectedB)
{
  register int scanLine, firstWord, lastWord;
  register int line;

  scanLine= bytesPerLine(width, depth);
  firstWord= scanLine*affectedT + bytesPerLineRD(affectedL, depth);
  lastWord= scanLine*affectedT + bytesPerLine(affectedR, depth);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned short *from= (unsigned short *)((int)fromImageData+firstWord);
      register unsigned short *limit= (unsigned short *)((int)fromImageData+lastWord);
      register unsigned short *to= (unsigned short *)((int)toImageData+firstWord);
      while (from < limit)
	{
	  to[0]= from[1];
	  to[1]= from[0];
	  from+= 2;
	  to+= 2;
	}
      firstWord+= scanLine;
      lastWord+= scanLine;
    }
}

void copyImage8To8(int *fromImageData, int *toImageData,
		   int width, int height,
		   int affectedL, int affectedT, int affectedR, int affectedB)
{
  register int scanLine, firstWord, lastWord;
  register int line;

  scanLine= bytesPerLine(width, 8);
  firstWord= scanLine*affectedT + bytesPerLineRD(affectedL, 8);
  lastWord= scanLine*affectedT + bytesPerLine(affectedR, 8);

  for (line= affectedT; line < affectedB; line++) {
    register unsigned int *from= (unsigned int *)((int)fromImageData+firstWord);
    register unsigned int *limit= (unsigned int *)((int)fromImageData+lastWord);
    register unsigned int *to= (unsigned int *)((int)toImageData+firstWord);
    while (from < limit)
      *to++= *from++;
    firstWord+= scanLine;
    lastWord+= scanLine;
  }
}

void copyImage8To16(int *fromImageData, int *toImageData,
		    int width, int height,
		    int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine8, firstWord8, lastWord8;
  int scanLine16, firstWord16, lastWord16;
  register int line;

  scanLine8= bytesPerLine(width, 8);
  firstWord8= scanLine8*affectedT + bytesPerLineRD(affectedL, 8);
  lastWord8= scanLine8*affectedT + bytesPerLine(affectedR, 8);
  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + (bytesPerLineRD(affectedL, 8) << 1);
  lastWord16= scanLine16*affectedT + (bytesPerLine(affectedR, 8) << 1);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned char *from= (unsigned char *)((int)fromImageData+firstWord8);
      register unsigned char *limit= (unsigned char *)((int)fromImageData+lastWord8);
      register unsigned short *to= (unsigned short *)((int)toImageData+firstWord16);
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  to[0]= stColors[from[0]];
	  to[1]= stColors[from[1]];
	  to[2]= stColors[from[2]];
	  to[3]= stColors[from[3]];
#else
	  to[0]= stColors[from[3]];
	  to[1]= stColors[from[2]];
	  to[2]= stColors[from[1]];
	  to[3]= stColors[from[0]];
#endif
	  from+= 4;
	  to+= 4;
	}
      firstWord8+= scanLine8;
      lastWord8+= scanLine8;
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
    }
}

void copyImage16To8(int *fromImageData, int *toImageData,
		    int width, int height,
		    int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine16, firstWord16, lastWord16;
  int scanLine8, firstWord8, lastWord8;
  int line;

#define map16To8(w) (col= (w), stDownGradingColors[ \
  (((col >> (10+(5-3))) & 0x7) << 5) | \
  (((col >> (5+(5-3)))  & 0x7) << 2) | \
   ((col >> (0+(5-2)))  & 0x7)])

  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + bytesPerLineRD(affectedL, 16);
  lastWord16= scanLine16*affectedT + bytesPerLine(affectedR, 16);
  scanLine8= bytesPerLine(width, 8);
  firstWord8= scanLine8*affectedT + (bytesPerLineRD(affectedL, 16) >> 1);
  lastWord8= scanLine8*affectedT + (bytesPerLine(affectedR, 16) >> 1);

  for (line= affectedT; line < affectedB; line++)
    {
      register int col;
      register unsigned short *from= (unsigned short *)((int)fromImageData+firstWord16);
      register unsigned short *limit= (unsigned short *)((int)fromImageData+lastWord16);
      register unsigned char *to= (unsigned char *)((int)toImageData+firstWord8);

      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  to[0]= map16To8(from[0]);
	  to[1]= map16To8(from[1]);
#else
	  to[0]= map16To8(from[1]);
	  to[1]= map16To8(from[0]);
#endif
	  from+= 2;
	  to+= 2;
	}
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
      firstWord8+= scanLine8;
      lastWord8+= scanLine8;
    }
#undef map16To8
}

void copyImage8To32(int *fromImageData, int *toImageData,
		    int width, int height,
		    int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine8, firstWord8, lastWord8;
  int scanLine32, firstWord32, lastWord32;
  int line;

  scanLine8= bytesPerLine(width, 8);
  firstWord8= scanLine8*affectedT + bytesPerLineRD(affectedL, 8);
  lastWord8= scanLine8*affectedT + bytesPerLine(affectedR, 8);
  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + (bytesPerLineRD(affectedL, 8) << 2);
  lastWord32= scanLine32*affectedT + (bytesPerLine(affectedR, 8) << 2);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned char *from= (unsigned char *)((int)fromImageData+firstWord8);
      register unsigned char *limit= (unsigned char *)((int)fromImageData+lastWord8);
      register unsigned int *to= (unsigned int *)((int)toImageData+firstWord32);
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  to[0]= stColors[from[0]];
	  to[1]= stColors[from[1]];
	  to[2]= stColors[from[2]];
	  to[3]= stColors[from[3]];
#else
	  to[0]= stColors[from[3]];
	  to[1]= stColors[from[2]];
	  to[2]= stColors[from[1]];
	  to[3]= stColors[from[0]];
#endif
	  from+= 4;
	  to+= 4;
	}
      firstWord8+= scanLine8;
      lastWord8+= scanLine8;
      firstWord32+= scanLine32;
      lastWord32+= scanLine32;
    }
}

void copyImage8To24(int *fromImageData, int *toImageData,
		    int width, int height,
		    int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine8, firstWord8, lastWord8;
  int scanLine24, firstWord24;
  int line;

  scanLine8= bytesPerLine(width, 8);
  firstWord8= scanLine8*affectedT + bytesPerLineRD(affectedL, 8);
  lastWord8= scanLine8*affectedT + bytesPerLine(affectedR, 8);
  scanLine24= bytesPerLine(width, 24);
  firstWord24= scanLine24*affectedT + ((affectedL>>2) * 12);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned char *from= (unsigned char *)((int)fromImageData+firstWord8);
      register unsigned char *limit= (unsigned char *)((int)fromImageData+lastWord8);
      register unsigned char *to= (unsigned char *)((int)toImageData+firstWord24);
      register unsigned int newpix= 0;
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  newpix= stColors[from[0]];
#else
	  newpix= stColors[from[3]];
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
#ifdef WORDS_BIGENDIAN
	  newpix= stColors[from[1]];
#else
	  newpix= stColors[from[2]];
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
#ifdef WORDS_BIGENDIAN
	  newpix= stColors[from[2]];
#else
	  newpix= stColors[from[1]];
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
#ifdef WORDS_BIGENDIAN
	  newpix= stColors[from[3]];
#else
	  newpix= stColors[from[0]];
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
	  from+=4;
	}
      firstWord8+= scanLine8;
      lastWord8+= scanLine8;
      firstWord24+= scanLine24;
    }
}

void copyImage32To8(int *fromImageData, int *toImageData,
		    int width, int height,
		    int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine32, firstWord32, lastWord32;
  int scanLine8, firstWord8, lastWord8;
  int line;

#define map32To8(w) (col= (w), stDownGradingColors[\
  (((col >> (16+(8-3))) & 0x7) << 5) | \
  (((col >> ( 8+(8-3))) & 0x7) << 2) | \
   ((col >> ( 0+(8-2))) & 0x7)])

  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + bytesPerLineRD(affectedL, 32);
  lastWord32= scanLine32*affectedT + bytesPerLine(affectedR, 32);
  scanLine8= bytesPerLine(width, 8);
  firstWord8= scanLine8*affectedT + (bytesPerLineRD(affectedL, 32) >> 2);
  lastWord8= scanLine8*affectedT + (bytesPerLine(affectedR, 32) >> 2);

  for (line= affectedT; line < affectedB; line++)
  {
    register int col;
    register unsigned int *from= (unsigned int *)((int)fromImageData+firstWord32);
    register unsigned int *limit= (unsigned int *)((int)fromImageData+lastWord32);
    register unsigned char *to= (unsigned char *)((int)toImageData+firstWord8);
    while (from < limit)
    {
      to[0]= map32To8(from[0]);
      from++;
      to++;
    }
    firstWord32+= scanLine32;
    lastWord32+= scanLine32;
    firstWord8+= scanLine8;
    lastWord8+= scanLine8;
  }
#undef map32To8
}

void copyImage16To32(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine16, firstWord16, lastWord16;
  int scanLine32, firstWord32, lastWord32;
  int line;
  int rshift, gshift, bshift;
  register unsigned int col;

  rshift= stRNMask-5 + stRShift;
  gshift= stGNMask-5 + stGShift;
  bshift= stBNMask-5 + stBShift;

#define map16To32(w) (col= (w), \
  (((col >> 10) & 0x1f) << rshift) | \
  (((col >> 5)  & 0x1f) << gshift) | \
   ((col & 0x1f) << bshift))

  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + bytesPerLineRD(affectedL, 16);
  lastWord16= scanLine16*affectedT + bytesPerLine(affectedR, 16);
  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + (bytesPerLineRD(affectedL, 16) << 1);
  lastWord32= scanLine32*affectedT + (bytesPerLine(affectedR, 16) << 1);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned short *from= (unsigned short *)((int)fromImageData+firstWord16);
      register unsigned short *limit= (unsigned short *)((int)fromImageData+lastWord16);
      register unsigned int *to= (unsigned int *)((int)toImageData+firstWord32);
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  to[0]= map16To32(from[0]);
	  to[1]= map16To32(from[1]);
#else
	  to[0]= map16To32(from[1]);
	  to[1]= map16To32(from[0]);
#endif
	  from+= 2;
	  to+= 2;
	}
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
      firstWord32+= scanLine32;
      lastWord32+= scanLine32;
    }
#undef map16To32
}

void copyImage16To24(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine16, firstWord16, lastWord16;
  int scanLine24, firstWord24;
  int line;

  int rshift, gshift, bshift;
  register unsigned int col;

  rshift= stRNMask-5 + stRShift;
  gshift= stGNMask-5 + stGShift;
  bshift= stBNMask-5 + stBShift;

#define map16To24(w) (col= (w), \
  (((col >> 10) & 0x1f) << rshift) | \
  (((col >> 5)  & 0x1f) << gshift) | \
   ((col & 0x1f) << bshift))

  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + bytesPerLineRD(affectedL, 16);
  lastWord16= scanLine16*affectedT + bytesPerLine(affectedR, 16);
  scanLine24= bytesPerLine(width, 24);
  firstWord24= scanLine24*affectedT + ((affectedL>>1) * 6);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned short *from= (unsigned short *)((int)fromImageData+firstWord16);
      register unsigned short *limit= (unsigned short *)((int)fromImageData+lastWord16);
      register unsigned char *to= (unsigned char *)((int)toImageData+firstWord24);
      register unsigned int newpix= 0;
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  newpix= map16To24(from[0]);
#else
	  newpix= map16To24(from[1]);
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
#ifdef WORDS_BIGENDIAN
	  newpix= map16To24(from[1]);
#else
	  newpix= map16To24(from[0]);
#endif
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
	  from+=2;
	}
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
      firstWord24+= scanLine24;
    }
#undef map16To24
}


void copyImage32To16(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine32, firstWord32, lastWord32;
  int scanLine16, firstWord16, lastWord16;
  int line;
  int rshift, gshift, bshift;
  register unsigned int col;

  rshift= stRNMask-5 + stRShift;
  gshift= stGNMask-5 + stGShift;
  bshift= stBNMask-5 + stBShift;

#define map32To16(w) (col= (w), \
  (((col >> 19) & 0x1f) << rshift) | \
  (((col >> 11) & 0x1f) << gshift) | \
  (((col >>  3) & 0x1f) << bshift))

  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + bytesPerLineRD(affectedL, 32);
  lastWord32= scanLine32*affectedT + bytesPerLine(affectedR, 32);
  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + (bytesPerLineRD(affectedL, 32) >> 1);
  lastWord16= scanLine16*affectedT + (bytesPerLine(affectedR, 32) >> 1);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned int *from= (unsigned int *)((int)fromImageData+firstWord32);
      register unsigned int *limit= (unsigned int *)((int)fromImageData+lastWord32);
      register unsigned short *to= (unsigned short *)((int)toImageData+firstWord16);
      while (from < limit)
	{
	  to[0]= map32To16(from[0]);
	  from++;
	  to++;
	}
      firstWord32+= scanLine32;
      lastWord32+= scanLine32;
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
    }
#undef map32To16
}

void copyImage16To16(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine16, firstWord16, lastWord16;
  int line;
  int rshift, gshift, bshift;
  register unsigned int col;

  rshift= stRNMask-5 + stRShift;
  gshift= stGNMask-5 + stGShift;
  bshift= stBNMask-5 + stBShift;

#define map16To16(w) (col= (w), \
  (((col >> 10) & 0x1f) << rshift) | \
  (((col >> 5)  & 0x1f) << gshift) | \
   ((col & 0x1f) << bshift))

  scanLine16= bytesPerLine(width, 16);
  firstWord16= scanLine16*affectedT + bytesPerLineRD(affectedL, 16);
  lastWord16= scanLine16*affectedT + bytesPerLine(affectedR, 16);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned short *from= (unsigned short *)((int)fromImageData+firstWord16);
      register unsigned short *limit= (unsigned short *)((int)fromImageData+lastWord16);
      register unsigned short *to= (unsigned short *)((int)toImageData+firstWord16);
      while (from < limit)
	{
#ifdef WORDS_BIGENDIAN
	  to[0]= map16To16(from[0]);
	  to[1]= map16To16(from[1]);
#else
	  to[0]= map16To16(from[1]);
	  to[1]= map16To16(from[0]);
#endif
	  from+= 2;
	  to+= 2;
	}
      firstWord16+= scanLine16;
      lastWord16+= scanLine16;
    }
#undef map16To16
}

void copyImage32To32(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine32, firstWord32, lastWord32;
  int line;
  int rshift, gshift, bshift;
  register unsigned int col;

  rshift= stRNMask-8 + stRShift;
  gshift= stGNMask-8 + stGShift;
  bshift= stBNMask-8 + stBShift;

#define map32To32(w) (col= (w), \
  (((col >> 16) & 0xff) << rshift) | \
  (((col >> 8)  & 0xff) << gshift) | \
   ((col & 0xff) << bshift))

  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + bytesPerLineRD(affectedL, 32);
  lastWord32= scanLine32*affectedT + bytesPerLine(affectedR, 32);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned int *from= (unsigned int *)((int)fromImageData+firstWord32);
      register unsigned int *limit= (unsigned int *)((int)fromImageData+lastWord32);
      register unsigned int *to= (unsigned int *)((int)toImageData+firstWord32);
      while (from < limit)
	{
	  *to= map32To32(*from);
	  from++;
	  to++;
	}
      firstWord32+= scanLine32;
      lastWord32+= scanLine32;
    }
#undef map32To32
}

void copyImage32To24(int *fromImageData, int *toImageData,
		     int width, int height,
		     int affectedL, int affectedT, int affectedR, int affectedB)
{
  int scanLine24, firstWord24;
  int scanLine32, firstWord32, lastWord32;
  int line;
  int rshift, gshift, bshift;
  register unsigned int col;
  rshift= stRNMask-8 + stRShift;
  gshift= stGNMask-8 + stGShift;
  bshift= stBNMask-8 + stBShift;

#define map32To24(w) (col= (w), \
  (((col >> 16) & 0xff) << rshift) | \
  (((col >> 8)  & 0xff) << gshift) | \
   ((col & 0xff) << bshift))

  /* offsets for the 24bpp destination */
  scanLine24= bytesPerLine(width, 24);
  firstWord24= scanLine24*affectedT + (affectedL * 3) /* NOT bytesPerLineRD(affectedL, 24) ! */ ;

  /* offsets for the 32bpp source */
  scanLine32= bytesPerLine(width, 32);
  firstWord32= scanLine32*affectedT + bytesPerLineRD(affectedL, 32);
  lastWord32= scanLine32*affectedT + bytesPerLine(affectedR, 32);

  for (line= affectedT; line < affectedB; line++)
    {
      register unsigned int *from= (unsigned int *)((int)fromImageData+firstWord32);
      register unsigned int *limit= (unsigned int *)((int)fromImageData+lastWord32);
      register unsigned char *to= (unsigned char *)((int)toImageData+firstWord24);
      register unsigned int newpix= 0;
      while (from < limit)
	{
	  newpix= map32To24(*from);
	  from++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  newpix= newpix >> 8;
	  to++;
	  *to= (unsigned char) (newpix & 0xff);
	  to++;
	}
      firstWord24+= scanLine24;
      firstWord32+= scanLine32;
      lastWord32+= scanLine32;
    }
#undef map32To24
}

#endif


/*** Image File Naming ***/


void RecordFullPathForImageName(char *localImageName)
{
  struct stat s;
  /* get canonical path to image */
  if ((stat(localImageName, &s) == 0) || (realpath(localImageName, vmPath) == 0))
    {
      if (localImageName[0] == '/')
	{
	  strcpy(vmPath, localImageName);
	}
      else 
	{
	  getcwd(vmPath, sizeof(vmPath));
	  strcat(vmPath, "/");
	  strcat(vmPath, localImageName);
	}
    }
  strcpy(imageName, vmPath);
  /* truncate vmPath to dirname */
  {
    int i= 0;
    for (i= strlen(vmPath); i >= 0; i--)
      if ('/' == vmPath[i])
	{
	  vmPath[i+1]= '\0';
	  return;
	}
  }
  /* this might just work in an emergency... */
  strcpy(imageName, vmPath);
}

int imageNameSize(void)
{
  return strlen(imageName);
}

int imageNameGetLength(int sqImageNameIndex, int length)
{
  char *sqImageName= (char *)sqImageNameIndex;
  int count, i;

  count= strlen(imageName);
  count= (length < count) ? length : count;

  /* copy the file name into the Squeak string */
  for (i= 0; i < count; i++)
    sqImageName[i]= imageName[i];

  return count;
}

int imageNamePutLength(int sqImageNameIndex, int length)
{
  char *sqImageName= (char *)sqImageNameIndex;
  int count, i;

  count= (IMAGE_NAME_SIZE < length) ? IMAGE_NAME_SIZE : length;

  /* copy the file name into a null-terminated C string */
  for (i= 0; i < count; i++) imageName[i]= sqImageName[i];
  imageName[count]= 0;

#ifndef HEADLESS
  /* update the window title */
  XStoreName(stDisplay, stParent, imageName);
#endif

  return count;
}


/*** Timer support ***/


#ifdef USE_ITIMER
void sigalrm(int signum)
{
  lowResMSecs+= LOW_RES_TICK_MSECS;
}
#endif


void SetUpTimers(void)
{
  /* set up the micro/millisecond clock */
  gettimeofday(&startUpTime, 0);
#ifdef USE_ITIMER
  /* set up the low-res (50th second) millisecond clock */
  /* WARNING: all system calls must check for EINTR!!! */
  {
    struct sigaction sa;
    sigset_t ss1, ss2;
    sigemptyset(&ss1);
    sigprocmask(SIG_BLOCK, &ss1, &ss2);
    sa.sa_handler= sigalrm;
    sa.sa_mask= ss2;
#ifdef SA_RESTART
    sa.sa_flags= SA_RESTART;
#else
    sa.sa_flags= 0;	/* assume that we have default BSD behaviour */
#endif
#ifdef __linux__
    sa.sa_restorer= 0;
#endif
    sigaction(SIGALRM, &sa, 0);
  }
  {
    struct itimerval iv;
    iv.it_interval.tv_sec= 0;
    iv.it_interval.tv_usec= LOW_RES_TICK_MSECS * 1000;
    iv.it_value.tv_sec= 0;
    iv.it_value.tv_usec= LOW_RES_TICK_MSECS;
    setitimer(ITIMER_REAL, &iv, 0);
  }
#endif
}



/*** X selection handling ***/



#ifndef HEADLESS
void SetUpClipboard(void)
{
  stPrimarySelection= 0;
  stPrimarySelectionSize= 0;
  stOwnsSelection= 0;
}
#endif

int clipboardSize(void)
{
#ifndef HEADLESS
  if (stOwnsSelection)
    return stPrimarySelection ? strlen(stPrimarySelection) : 0;
  return strlen(getSelection());
#else
  return 0;
#endif
}

#ifndef HEADLESS
static void allocateSelectionBuffer(int count)
{
  if (count+1 > stPrimarySelectionSize)
    {
      if (stPrimarySelection)
	{
	  free(stPrimarySelection);
	  stPrimarySelection= 0;
	  stPrimarySelectionSize= 0;
	}
      if (!(stPrimarySelection= (char *)malloc(count+1)))
	{
	  fprintf(stderr, "failed to allocate X selection buffer\n");
	  return;
	}
      stPrimarySelectionSize= count;
    }
}
#endif

/* claim ownership of the X selection, providing the given string to requestors */
int clipboardWriteFromAt(int count, int byteArrayIndex, int startIndex)
{
#ifndef HEADLESS
  char *srcPtr, *dstPtr, *end;

  allocateSelectionBuffer(count);

  srcPtr= (char *)byteArrayIndex + startIndex;
  dstPtr= stPrimarySelection;
  end= srcPtr + count;
  while (srcPtr < end)
    *dstPtr++= *srcPtr++;

  *dstPtr= '\0';

  claimSelection();
#endif
  return 0;
}

/* transfer the X selection into the given byte array; optimise local requests */
int clipboardReadIntoAt(int count, int byteArrayIndex, int startIndex)
{
#ifndef HEADLESS
  long clipSize, charsToMove;
  char *srcPtr, *dstPtr, *end;

  if (!stOwnsSelection)
    {
      char *newSelection;
      int newSize;

      /* need to keep a separate selection buffer, or we wouldn't know if it
	 was allocated by us or by X */
      newSelection= getSelection();	/* not necessarily allocated by X... */
      newSize= strlen(newSelection);
      allocateSelectionBuffer(newSize);
      strcpy(stPrimarySelection, newSelection);
      if (newSelection != stEmptySelection) /* ...so we only *sometimes*... */
	XFree(newSelection);		/* ...need to XFree() it! */
      ux2st(stPrimarySelection);
    }

  clipSize= clipboardSize();
  charsToMove= (count < clipSize) ? count : clipSize;

  srcPtr= (char *)stPrimarySelection;
  dstPtr= (char *)byteArrayIndex + startIndex;
  end= srcPtr + charsToMove;
  while (srcPtr < end)
    *dstPtr++= *srcPtr++;

  return charsToMove;
#else
  return 0;
#endif
}



/*** Profiling ***/



int clearProfile(void) { return 0; }
int dumpProfile(void) { return 0; }
int startProfiling(void) { return 0; }
int stopProfiling(void) { return 0; }



/*** Browser plugin stuff ***/



/* Return true if access to the given file is allowed.  Grant permission
   to access only those files in directories at or under the image directory. 
   Note: vmPath is actually the path to the image.
   NOTE ALSO: that this is broken.  Symbolic links will confuse it. -- ikp
 */
int plugInAllowAccessToFilePath(char *pathString, int pathStringLength)
{
#ifndef HEADLESS

# if 1 /* Bert Freudenberg's version of reality */

  int vmPathLength= 0, i= 0;

  if (!secure)
    return true;                             /* not a plugin ==> grant */

  vmPathLength= strlen(vmPath) - 1;
  if (pathStringLength < vmPathLength)
    return false;                            /* path too short ==> deny */

  for (i= 0; i < vmPathLength; i++)
    if (pathString[i] != vmPath[i])
      return false;                          /* no common root ==> deny */

  for (; i < pathStringLength-3; i++)
    if (!strncmp(&pathString[i], "/..", 3))
      return false;                          /* parent component ==> deny */

  return true;                               /* ==> grant */

# else /* ikp's alternative reality */

  char vmPathEnd= strrchr(vmPath, '/');
  int  vmPathLen= vmPathEnd - vmPath;
  char piPath[MAXPATHLEN];
  strncpy(piPath, pathString, pathStringLength);
  realpath(piPath, piPath);
  {
    char piPathEnd= strrchr(piPath, '/');
    int  piPathLen= piPathEnd - piPath;
    return ((piPathLen >= vmPathLen)
	    && !strncmp(vmPath, piPath, vmPathLen));
  }
  /* IKP also says: of course, this is all COMPLETELY BOGUS anyway (named
   * prims give access to the entirety of libc).
   */

# endif /* alternative reality */

#endif    /* !HEADLESS */
  return true;
}



/*** Access to system attributes and command-line arguments ***/



static char *getAttribute(int id)
{
  if (id < 0) {
    /* VM argument */
    if (-id  < vmArgCnt)
      return vmArgVec[-id];
  } else switch (id) {
    case 0: 
      return vmArgVec[0];
    case 1:
      return imageName;
    case 1001:
      /* basic OS type: "unix", "win32", "mac", ... */
      return OS_TYPE;
    case 1002:
      /* specific OS type: "solaris2.5" on unix, "win95" on win32 */
      return VM_HOST_OS;
    case 1003:
      /* processor architecture: "68k", "x86", "PowerPC", ...  */
      return VM_HOST_CPU;
    case 1004:
      return  (char *)interpreterVersion;
    default:
      if ((id-2) < squeakArgCnt)
	return squeakArgVec[id-2];
  }
  success(false);
  return "";
}

int attributeSize(int id)
{
  return strlen(getAttribute(id));
}

int getAttributeIntoLength(int id, int byteArrayIndex, int length)
{
  if (length > 0)
    strncpy((char *)byteArrayIndex, getAttribute(id), length);
  return 0;
}



/*** Command line ***/



static char *progName= 0;

void usage()
{
  printf("Usage: %s [<option>...] [<imageName> [<argument>...]]\n", progName);
  printf("       %s [<option>...] -- [<argument>...]\n\n", progName);
  printf("<option> can be:\n");
  printf("  -align <n>           align functions at n-bytes (jit)\n");
  printf("  -browserWindow <id>  run as Netscape plugin in window <id>\n");
  printf("  -display <dpy>       display on <dpy> (default: $DISPLAY)\n");
  printf("  -fullscreen          occupy the entire screen\n");
  printf("  -headless            run in headless (no window) mode\n");
  printf("  -help                print this help message, then exit\n");
  printf("  -lazy                go to sleep when main window unmapped\n");
  printf("  -memory <size>[mk]   set initial memory size (default: %dm)\n",
	 DefaultHeapSize);
  printf("  -jit                 enable the dynamic (runtime) compiler\n");
  printf("  -notitle             disable the Squeak window title bar\n");
  printf("  -secure              enable file sandbox\n");
  printf("  -spy                 enable the system spy (jit)\n");
  printf("  -version             print version information, then exit\n");
  printf("  -xasync              don't serialize display updates\n");
  printf("  -xshm                use X shared memory extension\n");
  printf("\nNotes:\n");
  printf("  <imageName> defaults to `Squeak"SQ_VERSION".image'.\n");
  printf("  Using `unix:0' for <dpy> may improve local display performance.\n");
  printf("  -xshm only works when Squeak is running on the X server host.\n");
  printf("  <argument>s are ignored, but are processed by the Squeak image.\n");
  printf("  The first <argument> normally names a Squeak `script' to execute.\n");
  printf("  Precede <arguments> by `--' to use default image.\n");
  exit(1);
}


void imageNotFound(char *imageName)
{
  /* image file is not found */
  fprintf(stderr,"\
Could not open the Squeak image file `%s'.

There are three ways to open a Squeak image file.  You can:
  1. Put copies of the default image and changes files in this directory.
  2. Put the name of the image file on the command line when you
     run squeak (use the `-help' option for more information).
  3. Set the environment variable SQUEAK_IMAGE to the name of the image
     that you want to use by default.\n", imageName);
  exit(1);
}


void versionInfo(void)
{
  extern int vm_serial;
  extern char *vm_date, *cc_version, *ux_version;

  fprintf(stderr, "%s %s #%d %s%s %s\n%s\n",
	  VM_HOST, SQ_VERSION, vm_serial,
# if defined(USE_XSHM)
	  "XShm ",
# else
	  " ",
# endif
	  vm_date, cc_version, ux_version);
  fprintf(stderr, "default plugin location: %s/%s*.so\n",
	  SQ_LIBDIR, SQ_MODULE_PREFIX);
  exit(0);
}


void outOfMemory(void)
{
  fprintf(stderr, "out of memory\n");
  exit(1);
}


int strtobkm(char *str)
{
  char *suffix;
  int value= strtol(str, &suffix, 10);
  switch (*suffix)
    {
    case 'k': case 'K':
      value*= 1024;
      break;
    case 'm': case 'M':
      value*= 1024*1024;
      break;
    }
  return value;
}


void ParseEnvironment(void)
{
  char *ev= 0;

  if ((ev= getenv("SQUEAK_ALIGN")))	asmAlign= atoi(ev);
  if (asmAlign <  1) asmAlign= 1;
  if (asmAlign > 16) asmAlign= 16;

  if ((ev= getenv("SQUEAK_IMAGE")))	strcpy(shortImageName, ev);
  else					strcpy(shortImageName,
					       "Squeak"SQ_VERSION".image");
  if ((ev= getenv("SQUEAK_MEMORY")))	initialHeapSize= strtobkm(ev);
  if (getenv("SQUEAK_LAZY"))		sleepWhenUnmapped= 1;
  if (getenv("SQUEAK_NOJIT"))		noJitter= 1;
  if (getenv("SQUEAK_JIT"))		noJitter= 0;
  if (getenv("SQUEAK_SPY"))		withSpy= 1;
  if (getenv("SQUEAK_NOTITLE"))		noTitle= 1;
  if (getenv("SQUEAK_FULLSCREEN"))	fullScreen= 1;
  if (getenv("SQUEAK_SECURE"))		secure= 1;
# if defined(USE_XSHM) && !defined(HEADLESS)
  if (getenv("SQUEAK_XSHM"))		useXshm= 1;
  if (getenv("SQUEAK_XASYNC"))		asyncUpdate= 1;
# endif
}


void ParseArguments(int argc, char *argv[])
{
# define skipArg()	(--argc, argv++)
# define saveArg()	(vmArgVec[vmArgCnt++]= *skipArg())

  saveArg();		/* vm name */

  while ((argc > 0) && (**argv == '-'))	/* more options to parse */
    {
      char *arg= *argv;
      if (!strcmp(arg, "--"))		/* escape from option processing */
	break;

      saveArg();

      if      (!strcmp(arg, "-help"))		usage();
      else if (!strcmp(arg, "-headless"))	headless= 1;
#     if defined(USE_XSHM) && !defined(HEADLESS)
      else if (!strcmp(arg, "-xshm"))		useXshm= 1;
      else if (!strcmp(arg, "-xasync"))		asyncUpdate= 1;
#     endif
      else if (!strcmp(arg, "-lazy"))		sleepWhenUnmapped= 1;
      else if (!strcmp(arg, "-notitle"))	noTitle= 1;
      else if (!strcmp(arg, "-nojit"))		noJitter= 1;
      else if (!strcmp(arg, "-jit"))		noJitter= 0;
      else if (!strcmp(arg, "-spy"))		withSpy= 1;
      else if (!strcmp(arg, "-fullscreen"))	fullScreen= 1;
      else if (!strcmp(arg, "-secure"))		secure= true;
      else if (!strcmp(arg, "-version"))	versionInfo();
      else
	{
	  /* option requires an argument */
	  if      (arg == 0)			usage();
	  else if (!strcmp(arg, "-align"))
	    {
	      asmAlign= atoi(saveArg());
	      if (asmAlign <  1) asmAlign= 1;
	      if (asmAlign > 16) asmAlign= 16;
	    }
#         if !defined(HEADLESS)
	  else if (!strcmp(arg, "-display"))	displayName= saveArg();
#         endif
	  else if (!strcmp(arg, "-memory"))	initialHeapSize= strtobkm(saveArg());
	  else if (!strcmp(arg, "-browserWindow"))
	    {
	      sscanf(saveArg(), "%li", &browserWindow);
	      if (browserWindow == 0)
		{
		  fprintf(stderr, "Error: invalid -browserWindow argument!\n");
		  exit(1);
		}
	      secure= true;
	    }
	  else
	    usage();
	} /* option with argument */
    } /* while(more options) */

  if (argc > 0)
    {
      if (!strcmp(*argv, "--"))
	skipArg();
      else
	{
	  strcpy(shortImageName, saveArg());
	  if (0 == strstr(shortImageName, ".image"))
	    strcat(shortImageName, ".image");
	}
    }
  /* save remaining arguments as Squeak arguments */
  while (argc > 0)
    squeakArgVec[squeakArgCnt++]= *skipArg();

# undef saveArg
# undef skipArg
}



/*** internal image ***/



#if defined(USE_INTERNAL_IMAGE)

  /*-- EXPERIMENTAL -- read [and inflate] an internal image file --*/

  int sqImageFileClose(FILE *f)
  {
    int err= 0;
    if (f != 0)
      return fclose(f);
    assert(internalImage != 0);
    if (internalZStream != 0)
      {
	printf("decompressed %ld bytes\n", ztell(internalZStream));
	err= zclose(internalZStream);
      }
    internalImage= 0;
    internalImagePtr= 0;
    internalZStream= 0;
    return err;
  }

  int sqImageFilePosition(FILE *f)
  {
    if (f != 0)
      return ftell(f);
    assert(internalImage != 0);
    if (internalZStream != 0)
      return ztell(internalZStream);
    return internalImagePtr - internalImage;
  }

  int sqImageFileRead(void *ptr, size_t sz, size_t count, FILE *f)
  {
    if (f != 0)
      return fread(ptr, sz, count, f);
    assert(internalImage != 0);
    if (internalZStream != 0)
      return zread(ptr, sz, count, internalZStream);
    memcpy(ptr, (void *)internalImagePtr, sz * count);
    internalImagePtr+= sz*count;
    return sz*count;
  }

  int sqImageFileSeek(FILE *f, long pos)
  {
    if (f != 0)
      return fseek(f, pos, SEEK_SET);
    assert(internalImage != 0);
    if (internalZStream != 0)
      return zseek(internalZStream, pos, SEEK_SET);
    internalImagePtr= internalImage + pos;
    return 0;
  }

/* get a value for RTLD_NOW, with increasing levels of desperation... */

#if !defined(RTLD_NOW)
# if defined(DL_NOW)
#   define RTLD_NOW DL_NOW
# elif defined(RTLD_LAZY)
#   define RTLD_NOW RTLD_LAZY
# elif defined(DL_LAZY)
#   define RTLD_NOW DL_LAZY
# else
#   warning: defining RTLD_NOW as 1
#   define RTLD_NOW 1
# endif
#endif
 
  int openInternal(void)
  {
    unsigned char *internalImageEnd= 0;
    void *handle= dlopen(0, RTLD_NOW);
    if (handle == 0)
      {
	fprintf(stderr, "dlopen: %s\n", dlerror());
	exit(1);
      }
    /* non-zero means use in-memory file operations */
    internalImage= (unsigned char *)dlsym(handle, "__squeak_image_start");
    if (internalImage != 0)
      {
	internalImageEnd= (unsigned char *)dlsym(handle, "__squeak_image_end");
	internalImagePtr= internalImage;
	printf("reading internal image at 0x%08x + %d\n",
	       (unsigned)internalImage,
	       (int)(internalImageEnd - internalImage));
	strcpy(shortImageName, "internal.image");
      }
    else
      {
	internalImage= (unsigned char *)dlsym(handle, "__squeak_image_gz_start");
	if (internalImage == 0)
	  return 0;
	internalImageEnd= (unsigned char *)dlsym(handle, "__squeak_image_gz_end");
	{
	  char name[64], comment[64];
	  if (0 == gzstat(internalImage,
			  name, sizeof(name), comment, sizeof(comment)))
	    {
	      fprintf(stderr, "internal image: %s\n", z_error);
	      exit(1);
	    }
	  printf("decompressing %s at 0x%08x + %d\n",
		 ((*name == '\0') ? "internal image" : name),
		 (unsigned)internalImage,
		 (int)(internalImageEnd - internalImage));
	  if (*name == '\0')
	    strcpy(shortImageName, "internal.image");
	  else
	    strcpy(shortImageName, name);
	  if (*comment != '\0')
	    printf("%s\n", comment);
	}
	/* non-zero means inflate on-the-fly */
	internalZStream= gzopen(internalImage, internalImageEnd - internalImage);
	if (internalZStream == 0)
	  {
	    fprintf(stderr, "zopen: %s\n", z_error);
	    exit(1);
	  }
      }
    dlclose(handle);
    return internalImage != 0;
  }

#endif /* USE_INTERNAL_IMAGE */


/*** Segmentation fault handler ***/


#include <signal.h>

void segv(int ignore)
{
  error("Segmentation fault");
}

#ifdef __alpha__
/* headers for setsysinfo (see below) */
# include <sys/sysinfo.h>
# include <sys/proc.h>
#endif



/*** ...we came in? ***/

int main(int argc, char **argv, char **envp)
{
  /* check the interpreter's size assumptions for basic data types */
  if (sizeof(int)    != 4) error("This C platform's integers are not 32 bits.");
  if (sizeof(double) != 8) error("This C platform's floats are not 64 bits.");
  if (sizeof(time_t) != 4) error("This C platform's time_t's are not 32 bits.");

  /* Make parameters global for access from pluggable primitives */
  argCnt= argc;
  argVec= argv;
  envVec= envp;

  /* Allocate arrays to store copies of pointers to command line
     arguments.  Used by getAttributeIntoLength(). */
  if ((vmArgVec= calloc(argc + 1, sizeof(char *))) == 0)
    outOfMemory();

  if ((squeakArgVec= calloc(argc + 1, sizeof(char *))) == 0)
    outOfMemory();

  signal(SIGSEGV, segv);

  /* initialisation */

# if defined(__alpha__)
  /* disable printing of unaligned access exceptions */
  {
    int buf[2]= { SSIN_UACPROC, UAC_NOPRINT };
    if (setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0) < 0)
      {
	perror("setsysinfo(UAC_NOPRINT)");
      }
  }
# endif

  progName= argv[0];
  ParseEnvironment();
  ParseArguments(argc, argv);
  SetUpTimers();

# if !defined(HEADLESS) && defined(USE_XSHM)
#   ifdef AT_EXIT
      AT_EXIT(shmExit);
      {
	AT_EXIT((void(*)(void))ioShutdownAllModules);
      }
#   else
#     warning: cannot free display bitmap on exit!
#     warning: cannot shut down module system on exit!
#   endif
# endif

  sqFileInit();
  joystickInit();

# if defined(HAVE_TZSET)
  tzset();	/* should _not_ be necessary! */
# endif

  if (!realpath(argv[0], vmName))
    vmName[0]= 0; /* full VM name */

  /* read the image file and allocate memory for Squeak heap */
  {
    FILE *f= 0;

#   if defined(USE_INTERNAL_IMAGE) /* EXPERIMENTAL */
    if (openInternal())
      {
	RecordFullPathForImageName(shortImageName);
      }
    else
#   endif
      {
	if (0 == (f= fopen(shortImageName, "r")))
	  imageNotFound(shortImageName);
	else
	  RecordFullPathForImageName(shortImageName); /* full image path */
      }
    readImageFromFileHeapSize(f, initialHeapSize);
    sqImageFileClose(f);
  }

#ifndef HEADLESS
  /* open the Squeak window. */
  if (headless)
    forgetXDisplay();
  else
    openXDisplay();
#endif

#if defined(HAVE_LIBDL) && defined(J_ENABLED) && !defined(J_PROFILE)
  if (!noJitter)
    {
      /* first try to find an internal dynamic compiler... */
      int comp= ioFindExternalFunctionIn("j_interpret", 0);
      /* ...and if that fails... */
      if (!comp)
	{
	  /* ...try to find an external one */
	  int handle= ioLoadModule("SqueakCompiler");
	  if (handle != 0)
	    comp= ioFindExternalFunctionIn("j_interpret", handle);
	}
      if (comp)
	{
	  ((void(*)(void))comp)();
	  return 0;
	}
      else
	printf("could not find j_interpret\n");
    }
#endif /*HAVE_LIBDL*/

  /* run Squeak */
#ifndef J_PROFILE
  interpret();
#else
  j_interpret();
#endif

  return 0;
}


int ioExit(void)
{
#ifndef HEADLESS
  if (browserWindow) pluginExit();
  disconnectXDisplay();
#endif

  /*** Isn't this where... ***/

  exit(0);
}
