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

			       XCopilot

This code is part of XCopilot, a port of copilot

     Portions of this code are Copyright (C) 1997 Ivan A. Curtis
		       icurtis@radlogic.com.au

The original MS-Windows95 copilot emulator was written by Greg Hewgill.
The following copyright notice appeared on the original copilot sources:

		  Copyright (c) 1996 Greg Hewgill

Some of the code in this file was derived from code in which
the following copyright notice appeared:

		     XWindow Support for QuickCam
		   by Paul Chinn <loomer@svpal.org>
	      Modified by Scott Laird <scott@laird.com>
 
	 I took a bunch of this code from the source for VGB
	  "Virtual GameBoy" emulator by Marat Fayzullin and
			    Elan Feingold

 MC68000 Emulation code is from Bernd Schmidt's Unix Amiga Emulator.
       The following copyright notice appeared in those files:

	  Original UAE code Copyright (c) 1995 Bernd Schmidt

This code must not be distributed without these copyright notices intact.

*******************************************************************************
*******************************************************************************

Filename:	display.c

Description:	Display module for xcopilot emulator

Update History:   (most recent first)
   Gene McCulley   7-May-98 19:13 -- reworked main loop, added calibration
   Gene McCulley   6-May-98 15:25 -- Changed pixel doubling to MagFactor
                                     Dirty rectangle drawing
                                     Bug fix for 2-bit, doubled, 16 bpp mode
   Ian Goldberg    4-Sep-97 13:29 -- bug fixes for screen updates
   Eric Howe       3-Sep-97 15:09 -- trap window closure from window manager
   C. Chan-Nui    18-Jul-97 10:23 -- lazy screen updates
   Brian Grossman 15-Jul-97 21:40 -- fixed pixeldoubling colors
   Brandon Long   15-Jul-97 15:27 -- fixed byte-order display problems
   Chris Bare     10-Jul-97 11:18 -- shaped screen, better bg colour
   Brian Grossman 30-Jun-97 24:00 -- added pixeldoubling
   Ian Goldberg   20-Jun-97 14:09 -- added support for greyscale and panning
   Ian Goldberg   10-Apr-97 16:53 -- changed beeps into true amp/freq/dur sound
   I. Curtis       9-Apr-97 11:43 -- 16bpp and keboard events courtesy of
   	Andrew Pfiffer <andyp@co.intel.com> 
   I. Curtis       5-Mar-97 20:33 -- added key event processing code
   I. Curtis      25-Feb-97 20:17 -- major tidy up
   I. Curtis      23-Feb-97 21:17 -- Created.

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

#include <config.h>
#include <bittypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "display.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/xpm.h>
#include <X11/StringDefs.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#ifdef USE_XSHM
#include <X11/extensions/XShm.h> /* MIT Shared Memory Extensions */
#endif
#include <X11/extensions/shape.h>

#include "cmap_mgr.h"
#include "sysdeps.h"
#include "shared.h"
#include "dragonball.h"
#include "case.xpm"

#include "libmx.h"

/*****************************************************************************
 *                                                                           *
 * 			   Global Variables                                  *
 *                                                                           *
 *****************************************************************************/
#ifdef USE_XSHM
XShmSegmentInfo xcpSHMInfo;	/* info about shm pixmap (if using shm) */
#endif
static int xcpUsingShm = 0;		/* set True if we are using MIT shm ext. */
static int xcpStarted = 0;		/* set True once XCPilot is started */
static int xcpQuit = 0;		/* set True if XCopilot must quit */
static Display *xcpDisplay;		/* X Display connection */
static Screen *xcpScreen;		/* X screen */
static int xcpScreenNum;		/* screen number */
static Window xcpLCDWindow;		/* window for Copilot LCD image */
static Window xcpCaseWindow;		/* window for case */
static XImage *xcpLCDImage;		/* XImage of LCD screen */
static GC xcpLCDgc;			/* GC for rendering LCD XImage */
static XColor xcpGreys[14];		/* Greyscales for LCD */
static int xcpDepth;			/* Pixel depth of XImage */
static int MagFactor = 1;       /* pixel magnification factor */
static int (*oldErrorfunc)();
static int hitError;
static Atom xcpDeleteWindow;
static shared_img *shared;
static char *new_sbuf;
static int screen_size = 0;
static int buffer_size;

extern int dbg_loadapp(FILE *out, FILE *in, char *cmd, char *line,
		       shared_img *shptr);

#define xcpOffPixel (xcpGreys[0].pixel)
#define xcpOnPixel (xcpGreys[6].pixel)

/*************************************
 * Menu Items                        *
 *                                   *
 * These structures hold the entries *
 * for menus                         *
 *************************************/
mx_menu_item main_items[] = {
  {MXItemFlag_Center, "XCopilot", 0},
  {MXItemFlag_Left, "Load database...", 0},
  {MXItemFlag_Left, "Load file...", 0},
  {MXItemFlag_Left, "About...", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Left, "Quit", 0},
};

mx_menu_item about_items[] = {
  {MXItemFlag_Center, NULL, 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "Unix/X Port by Ivan A. Curtis", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "bugs fixed and features added by:", 0},
  {MXItemFlag_Center, "Ian Goldberg", 0},
  {MXItemFlag_Center, "Brian Grossman", 0},
  {MXItemFlag_Center, "Chris Bare", 0},
  {MXItemFlag_Center, "Brandon Long", 0},
  {MXItemFlag_Center, "C. Chan-Nui", 0},
  {MXItemFlag_Center, "Eric Howe", 0},
  {MXItemFlag_Center, "Gene McCulley", 0},
  {MXItemFlag_Center, "Jon Abbott", 0},
  {MXItemFlag_Center, "Bill Janssen", 0},
  {MXItemFlag_Center, "Felix Croes", 0},
  {MXItemFlag_Center, "Max Okumoto", 0},
  {MXItemFlag_Center, "Anders Hammarquist", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "Copilot by Greg Hewgill", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
};

mx_menu_item appok_items[] = {
  {MXItemFlag_Center, "XCopilot", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "  App Loaded  ", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
};

mx_menu_item appfail_items[] = {
  {MXItemFlag_Center, "XCopilot", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "  Load App Failed  ", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
};

mx_menu_item ok_items[] = {
  {MXItemFlag_Center, "OK", 0},
};

#include "icon2.xpm"
static void set_icon(Widget w)
{
  Pixmap pixmap, mask;
  XpmAttributes attributes;
  Arg args[2];

  bzero((void*)&attributes, sizeof(attributes));
  if(XpmCreatePixmapFromData(XtDisplay(w), DefaultRootWindow(XtDisplay(w)),
                             pilot_xpm, &pixmap, &mask, &attributes))
    return;               

  XtSetArg(args[0], XtNiconPixmap, pixmap);
  XtSetArg(args[1], XtNiconMask, mask);
  XtSetValues(w, args, 2);
}

/***************************************
 * This is a signal handler. It simply *
 * sets xcpQuit True, which causes the *
 * event loop to terminate             *
 ***************************************/
static void xcpSignalQuit(int unused)
{
  xcpQuit = 1;
}
 
/********************************************************
 * Check if the X Shared Memory extension is available. *
 * Return:  0 = not available                           *
 *   1 = shared XImage support available                *
 *   2 = shared Pixmap support available also           *
 ********************************************************/
static int xcpCheckXshm(Display *display)
{
  int major, minor, ignore;
  int pixmaps;

#ifdef USE_XSHM
  if (XQueryExtension(display, "MIT-SHM", &ignore, &ignore, &ignore)) {
    if (XShmQueryVersion(display, &major, &minor, &pixmaps) == True) {
      return (pixmaps == True) ? 2 : 1;
    } else {
      return 0;
    }
  } else {
    return 0;
  }
#else
  return 0;
#endif
}

/************************************************
 * Initialize xwindows, and prepare a shared    *
 * memory buffer for the LCD image. width and   *
 * height are the dimensions of the LCD screen. *
 * Returns a pointer to shared memory buffer    *
 * or regular ximage buffer.                    *
 ************************************************/
#define CASEWIDTH 220
#define CASEHEIGHT 337
#define LCDWIDTH 160
#define LCDHEIGHT 160
int casewidth = CASEWIDTH,
    caseheight = CASEHEIGHT,
    lcdwidth = LCDWIDTH,
    lcdheight = LCDHEIGHT ;

static mx_appearance *app;    /* appearance structure form menu */

/* panels for menus */
static mx_panel *panel_main, *panel_about, *panel_ok;
static mx_panel *panel_appok, *panel_appfail;

/***************************************
 * Put the LCD image on the LCD window *
 ***************************************/
static void xcpPutImage(int x, int y, int width, int height) {
#ifdef USE_XSHM
  if (xcpUsingShm)
    XShmPutImage(xcpDisplay, xcpLCDWindow, xcpLCDgc, xcpLCDImage, x, y, x, y, 
		 width, height, False);
  else
#endif
    XPutImage(xcpDisplay, xcpLCDWindow, xcpLCDgc, xcpLCDImage, x, y, x, y,
	      width, height);
}

static int xcpHandleExpose(XEvent *event) {
  xcpPutImage(event->xexpose.x, event->xexpose.y, event->xexpose.width,
              event->xexpose.height);
  return event->xexpose.count;
}

static void HandleExpose(Widget w, XtPointer client_data, XEvent *event,
                         Boolean *continue_to_dispatch)
{
  *continue_to_dispatch = True;
  xcpHandleExpose(event);
}

/*********************************
 * Release allocated X resources *
 * in preparation for exit       *
 *********************************/
static void xcpCleanup(void)
{
  shared->CpuReq = cpuExit;
  if (xcpStarted) {
    if (xcpLCDImage) {
      XDestroyImage(xcpLCDImage);
    }
#ifdef USE_XSHM
    if (xcpUsingShm) {
      XShmDetach(xcpDisplay, &xcpSHMInfo);
      if (xcpSHMInfo.shmaddr) {
	shmdt(xcpSHMInfo.shmaddr);
      }
      if (xcpSHMInfo.shmid > 0) {
	shmctl(xcpSHMInfo.shmid, IPC_RMID, 0);
      }
    }
#endif
  }
  exit(0);
}

static void do_menu(int x, int y)
{
  int choice;
  choice = mx_popup_menu(xcpDisplay, xcpScreenNum, panel_main, &x, &y, True);

  switch (choice) {
    char *appname;
    int rx, ry;
    case 1:            /* Load App.. */
    case 2:
      rx = x;
      ry = y;
      appname = mx_popup_filesel(xcpDisplay, xcpScreenNum, app, &rx, &ry,
                                 choice - 1);
      if (appname != NULL)
        if (dbg_loadapp(NULL, NULL, NULL, appname, shared) == 0)
          mx_popup_alert(xcpDisplay, xcpScreenNum, panel_appok, panel_ok, &x,
                         &y);
        else
          mx_popup_alert(xcpDisplay, xcpScreenNum, panel_appfail, panel_ok,
                         &x, &y);
      break;
    case 3:             /* About.. */
      mx_popup_alert(xcpDisplay, xcpScreenNum, panel_about, panel_ok, &x, &y);
      break;
    case 5:             /* Quit */
      xcpCleanup();
      break;
    default:
      break;
  }
}

/************************************************************
 * Handle a key event                                       *
 * img is a pointer to the shared memory structure which    *
 * holds the custom registers which are shared between the  *
 * CPU and Display processes. down is 1 if the key is down, *
 * key is the number of the key                             *
 ************************************************************/
static void xcpKeyEvent(int down, int key)
{
  shared->keydown = down;
  shared->key = key;
  shared->run_updateisr = 1;
}

typedef struct _Key {
  const char *name;
  XRectangle bounds;
} Key;

static Key keys[] = {{"power", {0, 281, 16, 22}},
                     {"up", {100, 280, 25, 12}},
                     {"down", {100, 302, 25, 12}},
                     {"datebook", {24, 277, 28, 29}},
                     {"phone", {63, 277, 28, 29}},
                     {"todo", {134, 277, 28, 29}},
                     {"memo", {173, 277, 28, 29}}};

static void HandleKey(Widget w, XtPointer client_data, XEvent *event,
                      Boolean *continue_to_dispatch)
{
  unsigned key = (unsigned) client_data;

  *continue_to_dispatch = True;
  switch(event->type) {
  case ButtonPress:
    if (event->xbutton.button == 1)
      xcpKeyEvent(1, key);
    else if (event->xbutton.button == 3)
      do_menu(event->xbutton.x_root, event->xbutton.y_root);
    break;
  case ButtonRelease:
    if (event->xbutton.button == 1)
      xcpKeyEvent(0, key);
    break;
  default:
    fprintf(stderr, "got an unexpected event\n");
    exit(1);
    break;
  }
}

static void CreateKeys(Widget w)
{
  unsigned i;

  for (i = 0; i < XtNumber(keys); i++) {
    Widget key = XtVaCreateManagedWidget(keys[i].name, widgetClass, w,
                                         XtNx, keys[i].bounds.x * MagFactor,
                                         XtNy, keys[i].bounds.y * MagFactor,
                                         XtNwidth,
                                         keys[i].bounds.width * MagFactor,
                                         XtNheight,
                                         keys[i].bounds.height * MagFactor,
                                         XtNborderWidth, 0,
                                         XtNbackgroundPixmap, ParentRelative,
                                         NULL);

    XtAddEventHandler(key, ButtonPressMask | ButtonReleaseMask |
                      OwnerGrabButtonMask, False, HandleKey, (XtPointer)i);
  }
}

/************************************************************
 * Handle a pen event                                       *
 * img is a pointer to the shared memory structure which    *
 * holds the custom registers which are shared between the  *
 * CPU and Display processes. down is 1 if the pen is down, *
 * x and y are the coordinates of the pen                   *
 ************************************************************/
static void xcpPenEvent(shared_img *img, int down, int x, int y)
{
  if (!img->pendown && down) {	/* pen going down */
    img->pen = 1;
    img->run_updateisr = 1;
  } else if (img->pendown && !down) { /* pen coming up */
    img->pen = 0;
    img->run_updateisr = 1;
  }
  img->pendown = down;
  img->penx = (int)(x/MagFactor);
  img->peny = (int)(y/MagFactor);
}

static void HandleDigitizer(Widget w, XtPointer client_data, XEvent *event,
                            Boolean *continue_to_dispatch)
{
  Time start;

  *continue_to_dispatch = True;

  switch(event->type) {
  case ButtonPress:     /* a mouse button was pressed */
    if (event->xbutton.button == 1) {
      xcpPenEvent(shared, 1, event->xbutton.x, event->xbutton.y);
      start = event->xbutton.time;
    }
    else if (event->xbutton.button == 3)
      do_menu(event->xbutton.x_root, event->xbutton.y_root);
    break;
  case ButtonRelease:   /* a mouse button was released */
    if (event->xbutton.button == 1)
      xcpPenEvent(shared, 0, event->xbutton.x, event->xbutton.y);
    break;
  case MotionNotify:    /* mouse moved with button down */
    if (!buffer_size)
      xcpPenEvent(shared, 1, event->xmotion.x, event->xmotion.y);
    else {
      int nevents, i;
      XTimeCoord *xypos;

      xypos = XGetMotionEvents(XtDisplay(w), XtWindow(w), start,
                               event->xmotion.time, &nevents);
      start = event->xmotion.time;

      for (i = 0; i < nevents; i++)
        xcpPenEvent(shared, 1, xypos[i].x, xypos[i].y);
      XFree(xypos);
    }
    break;
  default:
    fprintf(stderr, "got an unexpected event\n");
    exit(1);
    break;
  }
}

static Widget CreateDigitizer(Widget w)
{
  Widget digitizer = XtVaCreateManagedWidget("digitizer",
                                             compositeWidgetClass, w,
                                             XtNx, 32 * MagFactor,
                                             XtNy, 33 * MagFactor,
                                             XtNwidth, lcdwidth,
                                             XtNheight, 227 * MagFactor,
                                             XtNborderWidth, 0,
                                             XtNbackgroundPixmap,
                                             ParentRelative,
                                             NULL);

  XtAddEventHandler(digitizer, ButtonPressMask | ButtonReleaseMask |
                    Button1MotionMask | OwnerGrabButtonMask, False,
                    HandleDigitizer, NULL);
  return digitizer;
}

static void xcpKBtoASCII(shared_img *shptr, XEvent *event)
{
  static KeySym		keysym;
  static XComposeStatus	compose;
  int			count, bufsize, in;
  char			buffer[8];

  bufsize = 8;
  count = XLookupString((XKeyEvent *) event,
			buffer,
			bufsize,
			&keysym,
			&compose);
  if (count > 0) {
    if (buffer[0] == '\r') {
      buffer[0] = '\n';
    }
    in = shptr->kbin;
    shptr->kb[in] = buffer[0];
    shptr->kbin = (in + 1) & 7;
  }
}

static void HandleClientMessage(Widget w, XtPointer client_data, XEvent *event,
                                Boolean *continue_to_dispatch)
{
  *continue_to_dispatch = True;
  if (event->type == ClientMessage)
    if (event->xclient.data.l[0] == (Atom)client_data) {
      xcpCleanup();
    }
}

static void HandleXEvents(Widget w, XtPointer client_data, XEvent *event,
                          Boolean *continue_to_dispatch)
{
  shared_img *shptr = (shared_img *)client_data;

  *continue_to_dispatch = True;

  switch(event->type) {
  case KeyPress:
    xcpKBtoASCII(shptr, event);
    break;
  case ButtonPress:		/* a mouse button was pressed */
    if (event->xbutton.button == 3)
      do_menu(event->xbutton.x_root, event->xbutton.y_root);
    break;
  default:
    fprintf(stderr, "got an unexpected event\n");
    exit(1);
    break;
  }
}

static int shmError(Display *dis, XErrorEvent *err)
{
    hitError = 1;
    return 0;
}

char *xcpInitialize(shared_img *shptr, XtAppContext context, Widget topWidget,
                    int no_x_shm, int magfactor, int use_private_colormap,
                    char *BackgroundColorName, char *BacklightColorName)
{
  int depth, pad;
  char *sbuf = NULL;
  int i;
  CMAP cmap;
  Widget LCD;
  Widget Case, digitizer;
  Pixmap case_bitmap, case_mask;
  static const unsigned short greylevel[7] =
    { 65535, 49152, 45056, 32768, 20480, 16384, 0};

  if(magfactor) MagFactor = magfactor;
  casewidth *= MagFactor;
  caseheight *= MagFactor;
  lcdwidth *= MagFactor;
  lcdheight *= MagFactor;

  shared = shptr;
  /*
   * Attempt to open a display connection
   * to the default display
   */
  xcpDisplay = XtDisplay(topWidget);
  if(!xcpDisplay) {
    fprintf(stderr, "E - open display failed\n");
    return NULL;
  }

  /*
   * Get some information about the
   * display
   */
  xcpScreen = DefaultScreenOfDisplay(xcpDisplay);
  xcpScreenNum = DefaultScreen(xcpDisplay);
  depth = DefaultDepthOfScreen(xcpScreen);
  xcpDepth = depth;

  buffer_size = XDisplayMotionBufferSize(xcpDisplay);

  cmap = CMAP_Create(xcpDisplay, xcpScreenNum,
		     DefaultVisual(xcpDisplay, xcpScreenNum), AllocAll,
		     use_private_colormap);

  /*
   * Create a window for the case
   */
  Case = XtVaCreateManagedWidget("case", compositeWidgetClass, topWidget,
                                 XtNwidth, casewidth, XtNheight, caseheight,
                                 XtNcolormap, CMAP_GetColormap(cmap),
                                 XtNborder,
                                 BlackPixel(xcpDisplay, xcpScreenNum),
                                 NULL);

  XtVaSetValues(topWidget, XtNwidth, casewidth, XtNheight, caseheight,
                XtNmaxWidth, casewidth, XtNmaxHeight, caseheight,
                XtNminWidth, casewidth, XtNminHeight, caseheight,
                XtNcolormap, CMAP_GetColormap(cmap),
                XtNinput, True,
                XtNborder, BlackPixel(xcpDisplay, xcpScreenNum), NULL);

  XtAddEventHandler(topWidget, ButtonPressMask | KeyPressMask |
                    OwnerGrabButtonMask, False, HandleXEvents,
                    (XtPointer)shptr);

  /*
   * Create a window for the LCD
   * make it a child of the case window
   * offset it by (32, 33) from TL of case window
   */
  /* Get colors */
  /* Make the "normal" background */
  if (BackgroundColorName) {
    CMAP_AllocNamedColor(cmap, BackgroundColorName, &xcpGreys[0]);
  } else {
    xcpGreys[0].red = 0x9000;
    xcpGreys[0].green = 0xb300;
    xcpGreys[0].blue = 0x9800;

    CMAP_AllocColor(cmap, &xcpGreys[0]);
  }
  /* Make the rest of the normal shades */
  for(i=1;i<7;++i) {
    xcpGreys[i].red = (((unsigned long)(greylevel[i]))*
	((unsigned long)(xcpGreys[0].red)))/((unsigned long)(greylevel[0]));
    xcpGreys[i].green = (((unsigned long)(greylevel[i]))*
	((unsigned long)(xcpGreys[0].green)))/((unsigned long)(greylevel[0]));
    xcpGreys[i].blue = (((unsigned long)(greylevel[i]))*
	((unsigned long)(xcpGreys[0].blue)))/((unsigned long)(greylevel[0]));
    CMAP_AllocColor(cmap, xcpGreys+i);
  }
  /* Make the "backlit" background */
  if (BacklightColorName) {
    CMAP_AllocNamedColor(cmap, BacklightColorName, &xcpGreys[7]);
  } else {
    xcpGreys[7].red = 0x8000;
    xcpGreys[7].green = 0xffff;
    xcpGreys[7].blue = 0x8000;

    CMAP_AllocColor(cmap, &xcpGreys[7]);
  }
  /* Make the rest of the normal shades */
  for(i=8;i<14;++i) {
    xcpGreys[i].red = (((unsigned long)(greylevel[i-7]))*
	((unsigned long)(xcpGreys[7].red)))/((unsigned long)(greylevel[0]));
    xcpGreys[i].green = (((unsigned long)(greylevel[i-7]))*
	((unsigned long)(xcpGreys[7].green)))/((unsigned long)(greylevel[0]));
    xcpGreys[i].blue = (((unsigned long)(greylevel[i-7]))*
	((unsigned long)(xcpGreys[7].blue)))/((unsigned long)(greylevel[0]));
    CMAP_AllocColor(cmap, xcpGreys+i);
  }

  digitizer = CreateDigitizer(Case);

  LCD = XtVaCreateManagedWidget("lcd", widgetClass, digitizer,
                         XtNx, 0,
                         XtNy, 0,
                         XtNwidth, lcdwidth,
                         XtNheight, lcdheight,
                         XtNcolormap, CMAP_GetColormap(cmap),
                         XtNbackground, xcpOffPixel,
                         XtNborder, xcpOffPixel,
                         XtNborderWidth, 0,
                         NULL);
  XtAddEventHandler(LCD, ExposureMask | OwnerGrabButtonMask, False,
                    HandleExpose, NULL);

  XtSetMappedWhenManaged(topWidget, False);
  set_icon(topWidget);
  XtRealizeWidget(topWidget);

  /*
   * Display the pixmap of the case
   */
  {
    XpmColorSymbol symbols[10];
    XpmAttributes case_attributes;
    XImage *case_img, *case_img_mask, *case_img_, *case_img_mask_;
    int status;
    int i,j,k=0,l=0;

    case_attributes.colorsymbols  = symbols;
    case_attributes.colormap      = CMAP_GetColormap(cmap);
    case_attributes.numsymbols    = 0;
    case_attributes.valuemask     = 0;
    case_attributes.valuemask    |= XpmReturnPixels;
    case_attributes.valuemask    |= XpmReturnInfos;
    case_attributes.valuemask    |= XpmReturnExtensions;
    case_attributes.valuemask    |= XpmColormap;
    case_attributes.valuemask    |= XpmCloseness; /* close enough..   */
    case_attributes.closeness     = 40000;        /* ..is good enough */
/* We need to have a version of Xpm the supports the colormap closures.  The
 * earliest one that does this is 4.9 which is also known as 3.4i.
 */
#if XpmIncludeVersion < 30409
    case_attributes.alloc_color   = CMAP_GetXPMAllocColorFunc();
    case_attributes.free_colors   = CMAP_GetXPMFreeColorsFunc();
    case_attributes.color_closure = CMAP_GetXPMClosure(cmap);
    case_attributes.valuemask    |= XpmAllocColor;
    case_attributes.valuemask    |= XpmFreeColors;
    case_attributes.valuemask    |= XpmColorClosure;
#endif

    status = XpmCreateImageFromData(xcpDisplay, case_xpm, &case_img,
                                    &case_img_mask, &case_attributes);
    if (status != 0) {
      fprintf(stderr, "X - XpmCreateImageFromData failed. Sorry, no case.\n");
    } else {
      if(MagFactor > 1) {
	unsigned long pixel = 0;

        sbuf = (char *)malloc(((depth + 7) / 8) * casewidth * caseheight);
        case_img_ = XCreateImage(xcpDisplay,
                                 DefaultVisual(xcpDisplay, xcpScreenNum),
                                 depth, ZPixmap, 0, sbuf, casewidth,
                                 caseheight, depth,
                                 casewidth * ((depth + 7) / 8));
        if(!case_img_) {
          (void)fprintf(stderr, "E - XCreateImage (case) Failed\n");
          return(NULL);
        }

        for(i=0;i<casewidth / MagFactor;++i)
          for(j=0;j<caseheight / MagFactor;++j) {
	    pixel = XGetPixel(case_img, i, j);
            for(k=0;k<MagFactor;++k)
              for(l=0;l<MagFactor;++l)
		XPutPixel(case_img_, i*MagFactor+k, j*MagFactor+l, pixel);
	  }

        XDestroyImage(case_img);
	case_img = case_img_;

        sbuf = (char *)malloc(((casewidth+7)&(~7)) * caseheight);
        case_img_mask_ = XCreateImage(xcpDisplay,
			       DefaultVisual(xcpDisplay, xcpScreenNum), 
			       1, ZPixmap, 0, sbuf, casewidth, caseheight,
			       8, (casewidth+7)&(~7));
        if(!case_img_) {
          (void)fprintf(stderr, "E - XCreateImage (case) Failed\n");
          return(NULL);
        }

        for(i=0;i<casewidth / MagFactor;++i)
          for(j=0;j<caseheight / MagFactor;++j) {
	    pixel = XGetPixel(case_img_mask, i, j);
            for(k=0;k<MagFactor;++k)
              for(l=0;l<MagFactor;++l)
		XPutPixel(case_img_mask_, i*MagFactor+k,
		    j*MagFactor+l, pixel);
	  }

        XDestroyImage(case_img_mask);
	case_img_mask = case_img_mask_;
      }
      case_bitmap = XCreatePixmap(xcpDisplay, DefaultRootWindow(xcpDisplay),
				casewidth, caseheight, xcpDepth);
      XPutImage(xcpDisplay, case_bitmap, DefaultGC(xcpDisplay, xcpScreenNum),
                case_img, 0, 0, 0, 0, casewidth, caseheight);
      case_mask = XCreatePixmap(xcpDisplay, DefaultRootWindow(xcpDisplay),
                                casewidth, caseheight, 1);
      if (case_img_mask) {
	XGCValues values;
	GC maskgc = XCreateGC(xcpDisplay, case_mask, 0, &values);

	XPutImage(xcpDisplay, case_mask, maskgc, case_img_mask, 0, 0, 0, 0,
                  casewidth, caseheight);
        XFreeGC(xcpDisplay, maskgc);
	XShapeCombineMask(xcpDisplay, XtWindow(topWidget), ShapeBounding, 0,
                          0, case_mask, ShapeSet);
	XDestroyImage(case_img_mask); /* Free XImage since we're done */
      }
      XtVaSetValues(Case, XtNbackgroundPixmap, case_bitmap, NULL);
      XDestroyImage(case_img); /* Free XImage since we're done */
      XFreePixmap(xcpDisplay, case_bitmap); /* Free Pixmap since we're done */
      XFreePixmap(xcpDisplay, case_mask);
    }
  }

  XtMapWidget(topWidget);

  CreateKeys(Case);
  xcpCaseWindow = XtWindow(Case);

  if(!xcpCaseWindow) {
    (void)fprintf(stderr, "E - create case window failed\n");
    return(NULL); 
  }

#ifdef USE_XSHM
 /*
  * try to use XShm
  */  
  if (!no_x_shm) {
    if (xcpCheckXshm(xcpDisplay) == 2) {
	/*
	fprintf(stderr,
	    "X - Shared mem available, using Shared Mem Images...\n");
	 */
	xcpLCDImage = XShmCreateImage(xcpDisplay,
				      DefaultVisual(xcpDisplay, xcpScreenNum), 
				      depth, ZPixmap, NULL, &xcpSHMInfo,
				      lcdwidth, lcdheight);
	if (xcpLCDImage) {
	    xcpSHMInfo.shmid = shmget(IPC_PRIVATE,
		xcpLCDImage->bytes_per_line * xcpLCDImage->height,
		IPC_CREAT | 0777);
	    screen_size = xcpLCDImage->bytes_per_line * xcpLCDImage->height;
	    if (xcpSHMInfo.shmid) {
		sbuf = xcpLCDImage->data = xcpSHMInfo.shmaddr =
		    shmat(xcpSHMInfo.shmid, NULL, 0);
		hitError = 0;
		oldErrorfunc = XSetErrorHandler(shmError);

		XShmAttach(xcpDisplay, &xcpSHMInfo);
		XSync(xcpDisplay, False);
		xcpUsingShm = !hitError;
		XSetErrorHandler(oldErrorfunc);
	    }
	    if (!xcpUsingShm) {
		XDestroyImage(xcpLCDImage);
		shmdt(xcpSHMInfo.shmaddr);
		shmctl(xcpSHMInfo.shmid, IPC_RMID, 0);
	    }
	}
    }
  }
#endif

  if (!xcpUsingShm) {
#ifdef USE_XSHM
    if (!no_x_shm)
      fprintf(stderr, "X - Shared memory unavailable, using regular images\n");
#endif
    pad = ((depth == 24) ? 32 : depth);
    screen_size = (((pad + 7) / 8) * lcdwidth * lcdheight);
    sbuf = (char *)malloc(screen_size);
    xcpLCDImage = XCreateImage(xcpDisplay,
			       DefaultVisual(xcpDisplay, xcpScreenNum), 
			       depth, ZPixmap, 0, sbuf, lcdwidth, lcdheight,
			       pad, lcdwidth * ((pad + 7) / 8));
    if(!xcpLCDImage) {
      fprintf(stderr, "E - XCreateImage Failed\n");
      return(NULL);
    }
  }
  
  xcpLCDWindow = XtWindow(LCD);

  if(!xcpLCDWindow) {
    (void)fprintf(stderr, "E - create LCD window failed\n");
    return(NULL); 
  }

  /*
   * Build the LCD GC (for rendering in the LCD window)
   */
  {
    XGCValues gc_values;
    gc_values.foreground = xcpOffPixel;
    gc_values.background = xcpOnPixel;
    xcpLCDgc = XCreateGC(xcpDisplay, xcpLCDWindow, GCForeground|GCBackground,
	&gc_values);
  }

  /* Trap WM_DELETE_WINDOW */
  xcpDeleteWindow = XInternAtom(XtDisplay(topWidget), "WM_DELETE_WINDOW",
                                False);
  if (xcpDeleteWindow != (Atom)0) {
    XSetWMProtocols(XtDisplay(topWidget), XtWindow(topWidget),
                    &xcpDeleteWindow, 1);
    XtAddEventHandler(topWidget, NoEventMask, True, HandleClientMessage,
                      (XtPointer)xcpDeleteWindow);
  }

  /*
   * Install signal handlers
   * and flag xcpStarted
   */
  signal(SIGHUP, xcpSignalQuit); 
  signal(SIGINT, xcpSignalQuit);
  signal(SIGQUIT, xcpSignalQuit); 
  signal(SIGTERM, xcpSignalQuit);
  xcpStarted = 1;

  return sbuf;
}

/**********************************************
 * Update the LCD screen                      *
 * sbuf is the pointer to the XImage storage, *
 * screenmap is a pointer to the screen ram   *
 * area of the Pilot.                         *
 **********************************************/
static void xcpUpdateLCD(char *sbuf, const unsigned char *screenmap,
                         const shared_img *shptr)
{
  const unsigned char VPW = shptr->VPW;
  const unsigned char POSR = shptr->POSR;
  const unsigned char PICF = shptr->PICF;
  const int depth = xcpDepth / 8 + (xcpDepth == 24);
  int backlight = shptr->Backlight ? 7 : 0;
  int twobit = PICF & 0x01;
  const unsigned char *srcstart = screenmap +
                                  ((POSR & (0x04 | !twobit << 3)) ? 1 : 0);
  int shiftstart = (6 | !twobit) -
	           (twobit + 1) * (POSR & (0x03 | !twobit << 2));
  int maskstart = (1 | twobit << 1) << shiftstart;
  unsigned char *dststart = (unsigned char *)sbuf;
  int row;
  int min_x = lcdwidth, min_y = lcdheight, max_x = 0, max_y = 0;

#ifdef WORDS_BIGENDIAN
#define GOOD_ENDIAN(x) (x)
#else
/* Wonk: sizeof(unsigned char *) must be == sizeof(uint32)  - Ian */
#define GOOD_ENDIAN(x) ((unsigned char *)(((uint32)(x))^1UL))
#endif

#define PUTPIXEL(type, dstptr, x) {                                        \
    int i, j;                                                              \
    for (i = 0; i < MagFactor; i++) {                                      \
      for (j = 0; j < MagFactor; j++) {                                    \
        type *pixdest = (type  *)(dstptr + j * lcdwidth * sizeof(type));   \
        if (*pixdest != x) {                                               \
          *pixdest = x;                                                    \
          if (bit < min_x)                                                 \
            min_x = bit;                                                   \
          if (bit > max_x)                                                 \
            max_x = bit;                                                   \
          if (row < min_y)                                                 \
            min_y = row;                                                   \
          if (row > max_y)                                                 \
            max_y = row;                                                   \
        }                                                                  \
      }                                                                    \
      dstptr += sizeof(type);                                              \
    }                                                                      \
  }

  for (row = 0; row < lcdheight; row += MagFactor) {
    const unsigned char *src = srcstart;
    unsigned char *dst = dststart;
    int shift = shiftstart;
    int mask = maskstart;
    int bit;
    for (bit = 0; bit < lcdwidth; bit += MagFactor) {
      unsigned int grey;
      unsigned long pixel;
      grey = twobit ? shptr->grpalette[(*(GOOD_ENDIAN(src)) & mask) >> shift] :
                      ((*(GOOD_ENDIAN(src)) & mask) ? 6 : 0);
      pixel = xcpGreys[grey + backlight].pixel;
      switch(depth) {
        case 1: PUTPIXEL(uint8, dst, pixel); break;
        case 2: PUTPIXEL(uint16, dst, pixel); break;
        case 4: PUTPIXEL(uint32, dst, pixel); break;
      }
      if (shift) {
        shift -= 1 + twobit;
        mask >>= 1 + twobit;
      } else {
        shift = 6 | !twobit;
        mask = (1 | twobit << 1) << (6 | !twobit);
        ++src;
      }
    }
    if ((min_x <= max_x) &&
	(row == lcdheight - MagFactor || !(row + 1 % 10))) {
      xcpPutImage(min_x, min_y, max_x - min_x + MagFactor,
                  max_y - min_y + MagFactor);
      min_x = lcdwidth;
      min_y = lcdheight;
      max_x = 0;
      max_y = 0;
    }
    srcstart += 2 * VPW;
    dststart += MagFactor * lcdwidth * depth;
  }
  XFlush(xcpDisplay);
}

#define UTIMER 50               /* how long between updates (milliseconds) */
extern void *CPU_getmemptr(CPTR addr);

static void RingBell(int percent, int pitch, int duration)
{
  XKeyboardState old;
  XKeyboardControl new;

  new.bell_percent = percent;
  new.bell_pitch = pitch;
  new.bell_duration = duration;
  XGetKeyboardControl(xcpDisplay, &old);
  XChangeKeyboardControl(xcpDisplay,
			 KBBellPercent | KBBellPitch | KBBellDuration, &new);
  XBell(xcpDisplay, 0);
  new.bell_percent = old.bell_percent;
  new.bell_pitch = old.bell_pitch;
  new.bell_duration = old.bell_duration;
  XChangeKeyboardControl(xcpDisplay,
			 KBBellPercent | KBBellPitch | KBBellDuration, &new);
  XFlush(xcpDisplay);
}

static void CreateMenus(void)
{
  extern char *id_version;

  app = mx_appearance_create(xcpDisplay, xcpScreenNum, xcpCaseWindow,
			     "lucidasans-14", 2, 3, 0, 0, NULL,
			     xcpHandleExpose);
  panel_main = mx_panel_create(xcpDisplay, app, XtNumber(main_items),
                               main_items);
  about_items[0].text = id_version;
  panel_about = mx_panel_create(xcpDisplay, app, XtNumber(about_items),
                                about_items);
  panel_ok = mx_panel_create(xcpDisplay, app, XtNumber(ok_items), ok_items);
  panel_appok = mx_panel_create(xcpDisplay, app, XtNumber(appok_items),
                                appok_items);
  panel_appfail = mx_panel_create(xcpDisplay, app, XtNumber(appfail_items),
                                  appfail_items);
}

static void tick_LCD(void)
{
  static int cleared = 0;

  if (shared->LcdPower == lcdOff) {
    /* When screen is turned off, make everything "black".  */
    if (cleared == 0) {
      XClearWindow (xcpDisplay, xcpLCDWindow);
      XFlush(xcpDisplay);
      cleared = 1;
    }
  } else {
    unsigned char *scrnmap;
    if (cleared) {
      /* The display was turned back on */
      xcpPutImage(0, 0, lcdwidth, lcdheight);
      XFlush(xcpDisplay);
    }
    /* Get current location of screen ram from dragonball register
     * LSSA, then update LCD */
    scrnmap = (unsigned char *)CPU_getmemptr(shared->lssa & ~1);
    xcpUpdateLCD(new_sbuf, scrnmap, shared);
    cleared = 0;
  }
  
  /* maybe ring the bell */
  if (shared->LcdReq == lcdBell) {
    RingBell(shared->BellAmp, shared->BellFreq, shared->BellDur);
    shared->LcdReq = lcdNone;
  }
}

static void tick_LCD2(XtPointer closure, XtIntervalId *id)
{
  XtAppContext context = (XtAppContext) closure;

  tick_LCD();
  XtAppAddTimeOut(context, UTIMER, tick_LCD2, context);
}

/***********************************************
 * This is the main event loop                 *
 * We loop in here updating the LCD screen     *
 * while mapping mouse events to xcpPenEvent's *
 ***********************************************/
void xcpEventLoop(XtAppContext context, Widget topWidget, char *sbuf,
                  shared_img *shptr)
{
  CreateMenus();

  /* Once we drop out of the event loop it means we are quitting..  */

  new_sbuf = sbuf;
  tick_LCD2(context, NULL);
  XtAppMainLoop(context);
  shptr->CpuReq = cpuExit;
  xcpCleanup();
  return;
}
