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

			       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)
   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 <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>
#ifdef NEED_SELECT_H
#include <sys/select.h>
#endif

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/xpm.h>
#include <X11/extensions/XShm.h> /* MIT Shared Memory Extensions */
#include <X11/extensions/shape.h>


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

#include "libmx.h"

/*****************************************************************************
 *                                                                           *
 * 			   Global Variables                                  *
 *                                                                           *
 *****************************************************************************/
XShmSegmentInfo xcpSHMInfo;	/* info about shm pixmap (if using shm) */
int xcpUsingShm = 0;		/* set True if we are using MIT shm ext. */
int xcpStarted = 0;		/* set True once XCPilot is started */
int xcpQuit = 0;		/* set True if XCopilot must quit */
Display *xcpDisplay;		/* X Display connection */
Screen *xcpScreen;		/* X screen */
int xcpScreenNum;		/* screen number */
Window xcpLCDWindow;		/* window for Copilot LCD image */
Window xcpCaseWindow;		/* window for case */
XImage *xcpLCDImage;		/* XImage of LCD screen */
GC xcpLCDgc;			/* GC for rendering LCD XImage */
XColor xcpGreys[14];		/* Greyscales for LCD */
int xcpDepth;			/* Pixel depth of XImage */
int PixelDouble;		/* do pixeldoubling? */
static int (*oldErrorfunc)();
static int hitError;
static Atom xcpDeleteWindow;

/* Lazy screen writes */
char *last_screen_buffer = NULL;
int must_display = 1;
int screen_needed_update = 1;
int screen_size = 0;

int shmError(Display *dis, XErrorEvent *err);

extern char *BackgroundColorName;
extern char *BacklightColorName;

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

/*************************************
 * Menu Items                        *
 *                                   *
 * These structures hold the entries *
 * for menus                         *
 *************************************/
#define MAIN_N_ITEMS 6		/* Main Menu */
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},
};

#define ABOUT_N_ITEMS 7		/* About panel */
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_Center, "  Version "XCOPILOT_VERSION" by Ian Goldberg  ", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "Copilot by Greg Hewgill", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
};

#define APPOK_N_ITEMS 4		/* App load OK status panel */
mx_menu_item appok_items[] = {
  {MXItemFlag_Center, "XCopilot", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
  {MXItemFlag_Center, "  App Loaded  ", 0},
  {MXItemFlag_Left | MXItemFlag_Disabled, " ", 0},
};

#define APPFAIL_N_ITEMS 4	/* App load fail panel */
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},
};

#define OK_N_ITEMS 1		/* an OK button */
mx_menu_item ok_items[] = {
  {MXItemFlag_Center, "OK", 0},
};

/***************************************
 * This is a signal handler. It simply *
 * sets xcpQuit True, which causes the *
 * event loop to terminate             *
 ***************************************/
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           *
 ********************************************************/
int xcpCheckXshm(Display *display)
{
  int major, minor, ignore;
  int pixmaps;

  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;
  }
}

/************************************************
 * 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 ;
char *xcpInitialize(int no_x_shm, int pixeldouble)
{
  int depth, pad;
  char *sbuf = NULL;
  XEvent ev;
  int i;
  static const unsigned short greylevel[7] =
    { 65535, 49152, 45056, 32768, 20480, 16384, 0};

  PixelDouble = 1;
  if(pixeldouble) PixelDouble = 2;
  casewidth *= PixelDouble;
  caseheight *= PixelDouble;
  lcdwidth *= PixelDouble;
  lcdheight *= PixelDouble;

  /*
   * Attempt to open a display connection
   * to the default display
   */
  xcpDisplay = XOpenDisplay(NULL);
  if(!xcpDisplay) {
    printf("E - open display failed\n");
    return NULL;
  }

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

  /*
   * Create a window for the case
   */
  xcpCaseWindow = XCreateSimpleWindow(xcpDisplay, DefaultRootWindow(xcpDisplay),
				    0, 0, casewidth, caseheight, 0,
				    XWhitePixel(xcpDisplay, xcpScreenNum),
				    XBlackPixel(xcpDisplay, xcpScreenNum));
  if(!xcpCaseWindow) {
    (void)printf("E - create case window failed\n");
    return(NULL); 
  }

  /* Trap WM_DELETE_WINDOW */
  xcpDeleteWindow = XInternAtom(xcpDisplay, "WM_DELETE_WINDOW", False);
  if (xcpDeleteWindow != (Atom)0) {
    XSetWMProtocols(xcpDisplay, xcpCaseWindow, &xcpDeleteWindow, 1);
  }

  /*
   * 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) {
    XColor exact;
    XAllocNamedColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
	BackgroundColorName, &xcpGreys[0], &exact);
  } else {
    xcpGreys[0].red = 0x9000;
    xcpGreys[0].green = 0xb300;
    xcpGreys[0].blue = 0x9800;

    XAllocColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
	&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]));
    XAllocColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
		xcpGreys+i);
  }
  /* Make the "backlit" background */
  if (BacklightColorName) {
    XColor exact;
    XAllocNamedColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
	BacklightColorName, &xcpGreys[7], &exact);
  } else {
    xcpGreys[7].red = 0x8000;
    xcpGreys[7].green = 0xffff;
    xcpGreys[7].blue = 0x8000;

    XAllocColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
	&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]));
    XAllocColor(xcpDisplay, DefaultColormap(xcpDisplay, xcpScreenNum),
		xcpGreys+i);
  }

  xcpLCDWindow = XCreateSimpleWindow(xcpDisplay, xcpCaseWindow,
				     32*PixelDouble, 33*PixelDouble,
				     lcdwidth, lcdheight, 0, 
				     xcpOffPixel, xcpOffPixel);
  if(!xcpLCDWindow) {
    (void)printf("E - create LCD window failed\n");
    return(NULL); 
  }

  /*
   * Tell window manager about our window
   * Fill out the Hints structure and hand
   * it to the window manager
   */
  {
    XSizeHints hints;
    XWMHints wmhints;
    char *window_name = "xcopilot";
    char *icon_name = "xcopilot";
    XTextProperty windowName, iconName;
    
    hints.flags = PSize | PMaxSize | PMinSize;
    hints.min_width = hints.max_width = hints.width = casewidth;
    hints.min_height = hints.max_height = hints.height = caseheight;
    wmhints.input = True;
    wmhints.flags = InputHint;
    XStringListToTextProperty(&window_name, 1, &windowName);
    XStringListToTextProperty(&icon_name  , 1, &iconName);
    XSetWMProperties(xcpDisplay, xcpCaseWindow, &windowName, &iconName,
		     NULL, 0, &hints, &wmhints, NULL);
  }

  /*
   * Display the pixmap of the case
   */
  {
    XpmColorSymbol symbols[10];
    XpmAttributes case_attributes;
    Pixmap case_bitmap, case_mask;
    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.numsymbols   = 0;
    case_attributes.valuemask    = 0;
    case_attributes.valuemask   |= XpmReturnPixels;
    case_attributes.valuemask   |= XpmReturnInfos;
    case_attributes.valuemask   |= XpmReturnExtensions;
    case_attributes.valuemask   |= XpmCloseness; /* close enough..   */
    case_attributes.closeness    = 40000;        /* ..is good enough */

    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(PixelDouble>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)printf("E - XCreateImage (case) Failed\n");
          return(NULL);
        }

        for(i=0;i<casewidth/2;++i)
          for(j=0;j<caseheight/2;++j) {
	    pixel = XGetPixel(case_img, i, j);
            for(k=0;k<PixelDouble;++k)
              for(l=0;l<PixelDouble;++l)
		XPutPixel(case_img_, i*PixelDouble+k, j*PixelDouble+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)printf("E - XCreateImage (case) Failed\n");
          return(NULL);
        }

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

        XDestroyImage(case_img_mask);
	case_img_mask = case_img_mask_;
      }
      case_bitmap = XCreatePixmap(xcpDisplay, xcpCaseWindow,
				casewidth, caseheight, xcpDepth);
      XPutImage(xcpDisplay, case_bitmap, DefaultGC(xcpDisplay, xcpScreenNum),
		  case_img, 0,0,0,0, casewidth, caseheight);
      case_mask = XCreatePixmap(xcpDisplay, xcpCaseWindow,
				casewidth, caseheight, 1);
      if (case_img_mask) {
	XGCValues values;
	GC maskgc = XCreateGC(xcpDisplay, case_mask, 0, &values);
#if 1
	XPutImage(xcpDisplay, case_mask, maskgc,
		      case_img_mask, 0,0,0,0, casewidth, caseheight);
#endif
        XFreeGC(xcpDisplay, maskgc);
      }
      XSetWindowBackgroundPixmap(xcpDisplay, xcpCaseWindow, case_bitmap);
      if (case_img_mask) {
	  XShapeCombineMask(xcpDisplay, xcpCaseWindow, ShapeBounding, 0, 0,
	    case_mask, ShapeSet);
	  XDestroyImage(case_img_mask); /* Free XImage since we're done */
      }
      XDestroyImage(case_img); /* Free XImage since we're done */
      XFreePixmap(xcpDisplay, case_bitmap); /* Free Pixmap since we're done */
      XFreePixmap(xcpDisplay, case_mask);
    }
    XClearWindow(xcpDisplay, xcpCaseWindow);
  }

  /*
   * Set the event mask for the case window
   * and raise it. Note that we don't expect
   * events from the LCD window, we get all
   * events from the case window
   */
  XSelectInput(xcpDisplay, xcpCaseWindow, ExposureMask | OwnerGrabButtonMask |
    ButtonPressMask | ButtonMotionMask | ButtonReleaseMask | KeyPressMask);
  XMapRaised(xcpDisplay, xcpCaseWindow);
  XNextEvent(xcpDisplay, &ev); /* always toss first event */

 /*
  * 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);
	    }
	}
    }
  }

  if (!xcpUsingShm) {
    fprintf(stderr, "X - Shared memory unavailable, using regular images\n");
    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);
    }
  }
  last_screen_buffer = malloc(screen_size);
  if (!last_screen_buffer) {
    fprintf(stderr, "E - malloc\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);
  }

  /*
   * Raise the LCD window. Note that we only expect
   * Exposure events from this window
   */
  XSelectInput(xcpDisplay, xcpLCDWindow, ExposureMask);
  XMapRaised(xcpDisplay, xcpLCDWindow);
  XNextEvent(xcpDisplay, &ev);

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

  return sbuf;
}

/*********************************
 * Release allocated X resources *
 * in preparation for exit       *
 *********************************/
void xcpCleanup(void)
{
  if (xcpStarted) {
    if (xcpLCDImage) {
      XDestroyImage(xcpLCDImage);
    }
    if (xcpUsingShm) {
      XShmDetach(xcpDisplay, &xcpSHMInfo);
      if (xcpSHMInfo.shmaddr) {
	shmdt(xcpSHMInfo.shmaddr);
      }
      if (xcpSHMInfo.shmid > 0) {
	shmctl(xcpSHMInfo.shmid, IPC_RMID, 0);
      }
    }
  }
}

/***************************************
 * Put the LCD image on the LCD window *
 ***************************************/
int xcpPutImage(void) {
  screen_needed_update = must_display ||
    memcmp(last_screen_buffer, xcpLCDImage->data, screen_size);
  if (!screen_needed_update) {
    return 1;
  }
  must_display = 0;
  memcpy(last_screen_buffer, xcpLCDImage->data, screen_size);
  if (xcpUsingShm) {
    XShmPutImage(xcpDisplay, xcpLCDWindow, xcpLCDgc, xcpLCDImage, 0, 0, 0, 0, 
		 lcdwidth, lcdheight, False);
  } else {
    XPutImage(xcpDisplay, xcpLCDWindow, xcpLCDgc, xcpLCDImage,  0, 0, 0, 0,
	      lcdwidth, lcdheight);
  }
  return 1;
}

/**********************************************
 * Update the LCD screen                      *
 * sbuf is the pointer to the XImage storage, *
 * screenmap is a pointer to the screen ram   *
 * area of the Pilot.                         *
 ** %%% assumes PixelDouble = 1 or 2         **
 **********************************************/
void xcpUpdateLCD(char *sbuf, unsigned char *screenmap, 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;

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

    if (PICF & 0x01) {
	/* 2-bit mode */
	unsigned char *srcstart = screenmap + ((POSR & 0x04) ? 1 : 0);
	int shiftstart = 6 - 2 * (POSR & 0x03);
	int maskstart = 3 << shiftstart;
	unsigned char *dststart = (unsigned char *)sbuf;
	int row;
	for (row = 0; row < lcdheight; row+=PixelDouble) {
	    unsigned char *src = srcstart;
	    unsigned char *dst = dststart;
	    int shift = shiftstart;
	    int mask = maskstart;
	    int bit;
	    for (bit = 0; bit < lcdwidth; bit+=PixelDouble) {
		unsigned int grey = (*(GOOD_ENDIAN(src)) & mask) >> shift;
		unsigned long pixel = xcpGreys[shptr->grpalette[grey]+backlight].pixel;
		switch(depth) {
		case 1:
		    *((unsigned char *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned char *)(dst+lcdwidth)) = pixel;
			dst += 1;
			*((unsigned char *)dst) = pixel;
			*((unsigned char *)(dst+lcdwidth)) = pixel;
		    }
		    dst += 1;
		    break;
		case 2:
		    *((unsigned short *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned short *)(dst+lcdwidth*2)) = pixel;
			dst += 4;
			*((unsigned short *)dst) = pixel;
			*((unsigned short *)(dst+lcdwidth*2)) = pixel;
		    }
		    dst += 2;
		    break;
		case 4:
		    *((unsigned long *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned long *)(dst+lcdwidth*4)) = pixel;
			dst += 4;
			*((unsigned long *)dst) = pixel;
			*((unsigned long *)(dst+lcdwidth*4)) = pixel;
		    }
		    dst += 4;
		    break;
		}
		if (shift) {
		    shift -= 2;
		    mask >>= 2;
		} else {
		    shift = 6;
		    mask = 3 << 6;
		    ++src;
		}
	    }
	    srcstart += 2*VPW;
	    dststart += lcdwidth * depth;
	    if(PixelDouble>1) dststart += lcdwidth * depth;
	}
    } else {
	/* 1-bit mode */
	unsigned char *srcstart = screenmap + ((POSR & 0x08) ? 1 : 0);
	int shiftstart = 7 - (POSR & 0x07);
	int maskstart = 1 << shiftstart;
	unsigned char *dststart = (unsigned char *)sbuf;
	int row;
	for (row = 0; row < lcdheight; row+=PixelDouble) {
	    unsigned char *src = srcstart;
	    unsigned char *dst = dststart;
	    int shift = shiftstart;
	    int mask = maskstart;
	    int bit;
	    for (bit = 0; bit < lcdwidth; bit+=PixelDouble) {
		unsigned int pix = (*(GOOD_ENDIAN(src)) & mask) ? 6 : 0;
		unsigned long pixel = xcpGreys[pix+backlight].pixel;
		switch(depth) {
		case 1:
		    *((unsigned char *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned char *)(dst+lcdwidth)) = pixel;
			dst += 1;
			*((unsigned char *)dst) = pixel;
			*((unsigned char *)(dst+lcdwidth)) = pixel;
		    }
		    dst += 1;
		    break;
		case 2:
		    *((unsigned short *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned short *)(dst+lcdwidth*2)) = pixel;
			dst += 2;
			*((unsigned short *)dst) = pixel;
			*((unsigned short *)(dst+lcdwidth*2)) = pixel;
		    }
		    dst += 2;
		    break;
		case 4:
		    *((unsigned long *)dst) = pixel;
		    if (PixelDouble>1) {
			*((unsigned long *)(dst+lcdwidth*4)) = pixel;
			dst += 4;
			*((unsigned long *)dst) = pixel;
			*((unsigned long *)(dst+lcdwidth*4)) = pixel;
		    }
		    dst += 4;
		    break;
		}
		if (shift) {
		    shift -= 1;
		    mask >>= 1;
		} else {
		    shift = 7;
		    mask = 1 << 7;
		    ++src;
		}
	    }
	    srcstart += 2*VPW;
	    dststart += lcdwidth * depth;
	    if(PixelDouble>1) dststart += lcdwidth * depth;
	}
    }
    xcpPutImage();
}

#define UTIMER 50000		/* how long between updates (useconds) */
extern void *CPU_getmemptr(CPTR addr);
extern int dbg_loadapp(FILE *out, FILE *in, char *cmd, char *line,
		       shared_img *shptr);

/************************************************************
 * 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                   *
 ************************************************************/
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/PixelDouble) - 30;
  img->peny = (int)(y/PixelDouble) - 30;
}

/************************************************************
 * 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                             *
 ************************************************************/
void xcpKeyEvent(shared_img *img, int down, int key)
{
  img->keydown = down;
  img->key = key;
  img->run_updateisr = 1;
}

/*********************************************
 * Convert a coordinate to a key number. The *
 * coordinates are for the realistic looking *
 * pilot case pixmap.                        *
 * If the coordinates do not correspond to a *
 * key, then -1 is returned                  *
 *********************************************/
int xcpKeyFromCoord(int x, int y)
{
  int key = -1;
  if (y >= PixelDouble*276 && y < PixelDouble*318) {
    if (x >= PixelDouble*0 && x < PixelDouble*17) {
      key = 0; /* power */
    } else if (x >= PixelDouble*25 && x < PixelDouble*53) {
      key = 3; /* datebook */
    } else if (x >= PixelDouble*64 && x < PixelDouble*92) {
      key = 4; /* phone */
    } else if (x >= PixelDouble*100 && x < PixelDouble*125) {
      if (y >= PixelDouble*280 && y < PixelDouble*292) {
	key = 1; /* up */
      } else if (y >= PixelDouble*302 && y < PixelDouble*314) {
	key = 2; /* down */
      }
    } else if (x >= PixelDouble*135 && x < PixelDouble*162) {
      key = 5; /* todo */
    } else if (x >= PixelDouble*174 && x < PixelDouble*200) {
      key = 6; /* memo */
    }
  }
  /*
  fprintf(stderr, "X - Key is %d (%d, %d)\n", key, x, y);
  fflush(stderr);
  */
  return key;
}


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;
  }
}

/***********************************************
 * This is the main event loop                 *
 * We loop in here updating the LCD screen     *
 * while mapping mouse events to xcpPenEvent's *
 ***********************************************/
void xcpEventLoop(char *sbuf, shared_img *shptr)
{
  extern char *id_version;
  XEvent event;
  int cleared = 0;
  int PenTracking = 0;
  int KeyTracking = 0;
  /*
   * These flags are set if we are tracking a pen event or a key event
   * respectively. That is, if the mouse button went down on the pen
   * area or on a key. When the button goes up, regardless of where the
   * mouse currently is, we must release the last event.
   */
  int LastKey = -1;
  /*
   * This is the key number of the last key event. When we do a key up,
   * we must use the key number that we generated a key down event for
   */

  struct timeval tv, otv;
  long ut;
  unsigned char *scrnmap;
  mx_appearance *app;		/* appearance structure form menu */
				/* panels for menus */
  mx_panel *panel_main, *panel_about, *panel_ok;
  mx_panel *panel_appok, *panel_appfail;
  int x, y;

  int want_screen_update = 1;
  int skip_screen_update = 0;
  int skip_screen_update_last = 1;

  app = mx_appearance_create(xcpDisplay, xcpScreenNum, xcpCaseWindow,
			     "lucidasans-14", 2, 3, 0, 0, NULL, xcpPutImage);
  panel_main = mx_panel_create(xcpDisplay, app, MAIN_N_ITEMS, main_items);
  about_items[0].text = id_version;
  panel_about = mx_panel_create(xcpDisplay, app, ABOUT_N_ITEMS, about_items);
  panel_ok = mx_panel_create(xcpDisplay, app, OK_N_ITEMS, ok_items);
  panel_appok = mx_panel_create(xcpDisplay, app, APPOK_N_ITEMS, appok_items);
  panel_appfail = mx_panel_create(xcpDisplay, app, APPFAIL_N_ITEMS, appfail_items);

  gettimeofday(&otv, NULL);
  while (!xcpQuit && shptr->CpuReq != cpuExit) {
    if (shptr->LcdPower == lcdOff) {
      /*
       * When screen is turned off, make everything "black".
       */
      if (cleared == 0) {
	XClearWindow (xcpDisplay, xcpLCDWindow);
	cleared ++;
      }
    } else if (want_screen_update) {
      /*
       * Get current location of screen ram from
       * dragonball register LSSA, then update LCD
       */
      if (cleared) {
	/* The display was turned back on */
	must_display = 1;
      }
      if (must_display || (want_screen_update && --skip_screen_update < 0)) {
	  scrnmap = (unsigned char *)CPU_getmemptr(shptr->lssa & ~1);
	  xcpUpdateLCD(sbuf, scrnmap, shptr);
	  cleared = 0;
	  if (screen_needed_update) {
	    skip_screen_update = 0;
	    skip_screen_update_last = 1;
	  } else {
	    if (skip_screen_update_last < 10) {
		skip_screen_update_last *= 2;
	    }
	    skip_screen_update = skip_screen_update_last;
	  }
      }
    }

    /*
     * maybe ring the bell
     */
    if (shptr->LcdReq == lcdBell) {
      XKeyboardState old;
      XKeyboardControl new;

      new.bell_percent = shptr->BellAmp;
      new.bell_pitch = shptr->BellFreq;
      new.bell_duration = shptr->BellDur;
      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);
      shptr->LcdReq = lcdNone;
    }

    /*
     * Check for pending events
     */
    XFlush(xcpDisplay);
    while (XEventsQueued(xcpDisplay, QueuedAfterReading)) {
      XNextEvent(xcpDisplay, &event);
      switch(event.type) {
      case KeyPress:
	xcpKBtoASCII(shptr, &event);
	break;
      case Expose:
        must_display = 1;
	break;
      case ButtonPress:		/* a mouse button was pressed */
	x = event.xbutton.x_root;
	y = event.xbutton.y_root;
	if (event.xbutton.button == 1) { /* left button - pen down */
	  PenTracking = 0;
	  KeyTracking = 0;
	  if (event.xbutton.y < PixelDouble*260) { /* pen digitizer event */
	    xcpPenEvent(shptr, 1, event.xbutton.x, event.xbutton.y);
	    PenTracking = 1;
	  } else {
	    LastKey = xcpKeyFromCoord(event.xbutton.x, event.xbutton.y);
	    if (LastKey >= 0) {
	      xcpKeyEvent(shptr, 1, LastKey);
	      KeyTracking = 1;
	    }
	  }
	} else if (event.xbutton.button == 3) { /* right button - exit */
	  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, shptr) == 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 */
	    shptr->CpuReq = cpuExit;
	    break;
	  default:
	  }
	}
	break;
      case ButtonRelease:		/* a mouse button was released */
	if (event.xbutton.button == 1) { /* left button - pen up */
	  if (PenTracking) {
	    xcpPenEvent(shptr, 0, event.xbutton.x, event.xbutton.y);
	  } else if (KeyTracking) {
	    xcpKeyEvent(shptr, 0, LastKey);
	  }
	}
	break;
      case MotionNotify:	/* mouse moved with button down */
	xcpPenEvent(shptr, 1, event.xbutton.x, event.xbutton.y);
	break;
      case ClientMessage:
        if (event.xclient.data.l[0] == xcpDeleteWindow) {
	    shptr->CpuReq = cpuExit;
	}
	break;
      default:
	break;
      }
    }
    /*
     * now work out how long to delay for
     * so we don't refresh display too fast
     */
    gettimeofday(&tv, NULL);
    ut = tv.tv_usec - otv.tv_usec;
    if(tv.tv_sec > otv.tv_sec)
      ut += 1000000;
    if(UTIMER - ut > 0) {
      int fd, rc;
      fd_set rd_set;
      unsigned long usecs = UTIMER - ut;
      tv.tv_sec  = usecs / 1000000L;
      tv.tv_usec = usecs % 1000000L;
      fd = ConnectionNumber(xcpDisplay);
      FD_ZERO(&rd_set);
      FD_SET(fd, &rd_set);
      rc = select (fd+1, &rd_set, 0, 0, &tv);
      if (rc == 0) {
	want_screen_update = 1;
      }
    } else {
	gettimeofday(&otv, 0);
	want_screen_update = 0;
    }
  }

  /*
   * Once we drop out the event loop
   * it means we are quitting..
   */
  /*
  fprintf(stderr, "X - Exiting\n");
  */
  xcpCleanup();
  shptr->CpuReq = cpuExit;
  return;
}

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