Source of gemsvnc.c


/* gemsvnc.c -- a framebuffer server for GE Medical Systems, but still under the GPL */

/* gemsvnc was derivved from x11vnc.c, a small clone of x0rfbserver by HexoNet */

/* Modified by rj@elilabs.com into gemsvnc.c Wed Nov  6 09:45:25 CST 2002 */

/* -------------------------------------------------------------------------------------------------------------------------------- */

/* License
   -------

   This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License <http://www.gnu.org/licenses/gpl.txt>
   for more details.

   You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* -------------------------------------------------------------------------------------------------------------------------------- */

/* This program is a VNC server <http://www.uk.research.att.com/vnc/> that relays the existing framebuffer of another X-server,
   typically the console X-server, to a remote vncviewer, preferably a tightVNC <http://www.tightvnc.org/> vncviewer.  It was
   derrived from the x11vnc program included with the LibVNCServer library <http://libvncserver.sourceforge.net/>, but enhanced by
   the addition of the tiled scanning technique incorporated into the x0rfbserver <http://www.hexonet.de/software/x0rfbserver/>
   program from HexoNet <http://www.hexonet.de/>.  The actual tile scanning code was not taken from x0rfbserver, as x0rfbserver
   was written in C++, while LibVNCServer and its accompanying x11vnc were written in C; the tile scanning was implemented as new
   code, but using the approach taken in x0rfbserver.

   An enhancement was made to the tile scanning algorithm by permitting the tile width and height to be specified as command line
   options; the original x0rfbserver had tiles fixed at 32x32 pixels.  Since each tile is probed for changes along a single
   horizontal or vertical scan line at a time, x0rfbserver had a statically initialized array that contained an interlace pattern
   for the 32x32 tile.  The gemsvnc program has an interlace pattern generating algorithm that works for any arbitrary number of
   scan lines.  This algorithm has been tested for up to 100,000 scan lines and appeared to give reasonable results.

   Work is presently underway to develop support for special rectangular regions of the framebuffer.  These regions will support
   atomic updating of a special rectangle intended for photographic type images that generally change everywhere at once within
   the rectangle, the masking of input events from the remote viewer within a rectangle, and the hiding of rectangular regions
   from the remote viewer.  These last 2 capabilities will provide visual cues to the remote viewer that the rectangular region is
   receiving special processing.  The hidden regions will be filled with a solid color to indicate that they are hidden, and the
   input-inhibited regions will be tinted with a transparent color to indicate that inputs will be ignored within the region.  */


#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/extensions/XTest.h>

#ifndef NO_SHM
#include <X11/extensions/XShm.h>
#include <sys/shm.h>
#endif

#include <string.h>

#define KEYSYM_H
#undef Bool
#define KeySym RFBKeySym

#include "rfb.h"

#include "ilace.h"                                               /* interlace sequence generator */
#include "commands.h"                                            /* rectangle command processor */

#define min(i, j) ((i) < (j) ? (i) : (j))                        /* return the minimum of 2 numbers */

/* max() is already defined elsewhere <sigh> */
/* #define max(i, j) ((i) > (j) ? (i) : (j)) */                  /* return the maximum of 2 numbers */

#define clip(lo, var, hi) max((lo), min((var), (hi)))            /* clip var to lie between lo and hi */

void init_commands(void);
void exit(int return_code);

/* These statics are defined to make a quasi-object -- this file represents one unique instance of a framebuffer server. */

int lim_data;                                                    /* needed by malloc.c, which was stolen from xemacs-21.1.14 since
                                                                    the generic Linux malloc was acting up. */

static char* backupImage;                                        /* backup framebuffer */
static int backupImageSize;                                      /* its size in bytes */
static char* markupImage;                                        /* the image we send to the client viewer, with cursor and rectangles */
static rfbScreenInfoPtr live_screen;                             /* the live X-server's framebuffer, et al */
static int rowstride;                                            /* number of bytes per line in our backup framebuffer */
static int bpp;                                                  /* bytes per pixel */
static int xscreen;
static int depth;                                                /* depth in bits per pixel */

static int blocked_or_guarded = FALSE;                           /* TRUE if mouse is in special region */

static XImage* framebufferImage = NULL;                          /* only used to init framebuffer at startup, not shm */
static XImage* scan_row_image = NULL;                            /* used to look for framebuffer changes */
static XImage* scan_col_image = NULL;                            /* used to look for framebuffer changes */
static XImage* tile_image = NULL;                                /* used to update backup framebuffer */
static XImage* right_edge_image = NULL;                          /* used to update backup framebuffer */
static XImage* bottom_edge_image = NULL;                         /* used to update backup framebuffer */
static XImage* corner_image = NULL;                              /* used to update backup framebuffer */

static Bool getImage_firstTime = TRUE;                           /* initialize first time thru */
static int SHM_major_version;
static int SHM_minor_version;
static int SHM_shared_pixmaps_ok;

static int scan_count = 16;                                      /* how many framebuffer probes between input event checks */

static int tilewidth = 32;                                       /* tile width */
static int tileheight = 32;                                      /* tile height */

Display* dpy = 0;
int window;
Bool gotInput = FALSE;
Bool viewOnly = FALSE;
Bool sharedMode = FALSE;

Bool disconnectAfterFirstClient = TRUE;

/* keyboard handling */
/* #define KBDDEBUG */

char modifiers[0x100];
KeyCode keycodes[0x100];
KeyCode leftShiftCode;
KeyCode rightShiftCode;
KeyCode altGrCode;


void init_keycodes() {                                           /* keyboard initialization stuff */
  KeySym key;
  KeySym* keymap;
  int i;
  int j;
  int minkey;
  int maxkey;
  int syms_per_keycode;

  memset(modifiers, -1, sizeof(modifiers));

  XDisplayKeycodes(dpy, &minkey, &maxkey);
  keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), &syms_per_keycode);

#ifdef KBDDEBUG
  fprintf(stderr, "minkey=%d, maxkey=%d, syms_per_keycode=%d\n", minkey, maxkey, syms_per_keycode);
#endif

  for (i = minkey; i <= maxkey; i++) {
    for (j = 0; j < syms_per_keycode; j++) {
      key = keymap[(i - minkey)*syms_per_keycode + j];

#ifdef KBDDEBUG
      fprintf(stderr, "keymap(i=0x%x, j=%d)==0x%lx\n", i, j, key);
#endif

      if (key >= ' ' && key < 0x100 && i == XKeysymToKeycode(dpy, key)) {
        keycodes[key] = i;
        modifiers[key] = j;

#ifdef KBDDEBUG
        fprintf(stderr, "key 0x%lx (%c): keycode=0x%x, modifier=%d\n", key, (char)key, i, j);
#endif

      }
    }
  }

  leftShiftCode = XKeysymToKeycode(dpy, XK_Shift_L);
  rightShiftCode = XKeysymToKeycode(dpy, XK_Shift_R);
  altGrCode = XKeysymToKeycode(dpy, XK_Mode_switch);

#ifdef KBDDEBUG
  fprintf(stderr, "leftShift=0x%x, rightShift=0x%x, altGr=0x%x\n", leftShiftCode, rightShiftCode, altGrCode);
#endif

  XFree ((char*) keymap);
}


static Bool shutDownServer = 0;


/* the hooks */

void clientGone(rfbClientPtr cl) {
  shutDownServer = -1;
}


enum rfbNewClientAction newClient(rfbClientPtr cl) {
  if (disconnectAfterFirstClient) {
    cl->clientGoneHook = clientGone;
  }
  if (viewOnly) {
    cl->clientData = (void*)-1;
  } else {
    cl->clientData = (void*)0;
  }
  return(RFB_CLIENT_ACCEPT);
}

#define LEFTSHIFT 1
#define RIGHTSHIFT 2
#define ALTGR 4
char ModifierState = 0;


/* This function adjusts the modifiers according to mod (as from modifiers) and ModifierState. */

void tweakModifiers(char mod, Bool down) {
  Bool isShift = ModifierState & (LEFTSHIFT | RIGHTSHIFT);

#ifdef KBDDEBUG
  fprintf(stderr, "tweakModifiers: 0x%x %s\n", mod, down?"down":"up");
#endif

  if (mod < 0) {
    return;
  }

  if (isShift && mod != 1) {
    if (ModifierState & LEFTSHIFT) {
      XTestFakeKeyEvent(dpy, leftShiftCode, !down, CurrentTime);
    }
    if (ModifierState & RIGHTSHIFT) {
      XTestFakeKeyEvent(dpy, rightShiftCode, !down, CurrentTime);
    }
  }

  if (!isShift && mod == 1) {
    XTestFakeKeyEvent(dpy, leftShiftCode, down, CurrentTime);
  }

  if (ModifierState & ALTGR && mod != 2) {
    XTestFakeKeyEvent(dpy, altGrCode, !down, CurrentTime);
  }

  if (!(ModifierState & ALTGR) && mod == 2) {
    XTestFakeKeyEvent(dpy, altGrCode, down, CurrentTime);
  }
}


void keyboard(Bool down, KeySym keySym, rfbClientPtr cl) {
  if ((int)(cl->clientData) == -1) {
    return;                                                      /* viewOnly */
  }

  if (blocked_or_guarded) {                                      /* mouse in a blocked or guarded rectangle? */
    return;                                                      /* yes, do not allow keystrokes! */
  }

#define ADJUSTMOD(sym, state)      \
  if (keySym == sym) {             \
    if (down) {                    \
      ModifierState |= state;      \
    } else {                       \
      ModifierState &= ~state;     \
    }                              \
  }

  ADJUSTMOD(XK_Shift_L, LEFTSHIFT)
  ADJUSTMOD(XK_Shift_R, RIGHTSHIFT)
  ADJUSTMOD(XK_Mode_switch, ALTGR)

#ifdef KBDDEBUG
    fprintf(stderr, "keyboard: down=%s, keySym=0x%lx (%s), ModState=0x%x\n",
            down ? "down" : "up", keySym, XKeysymToString(keySym), ModifierState);
#endif

  if (keySym >= ' ' && keySym < 0x100) {
    KeyCode k;
    if (down) {
      tweakModifiers(modifiers[keySym], True);
    }
    k = keycodes[keySym];
    if (k != NoSymbol) {
      XTestFakeKeyEvent(dpy, k, down, CurrentTime);
      gotInput = TRUE;
    }
    if (down) {
      tweakModifiers(modifiers[keySym], False);
    }
    gotInput = TRUE;
  } else {
    KeyCode k = XKeysymToKeycode(dpy, keySym);
    if (k != NoSymbol) {
      XTestFakeKeyEvent(dpy, k, down, CurrentTime);
      gotInput = TRUE;
    }
  }
}


                                                      
void mouse(int buttonMask, int x, int y, rfbClientPtr cl) {      /* update real X-server with vncviewer's mouse data */
  /* buttonMask: state of remote mouse buttons */
  /* x: remote mouse x position */
  /* y: remote mouse y position */
  /* cl: the client vncviewer's state vector */

  static int oldButtonMask = 0;                                  /* remember previous button status */
  int m;                                                         /* mask for a single mouse button */
  int i;                                                         /* loop index */

  if ((int)cl->clientData == -1) {                               /* view only -- remote cannot control? */
    return;                                                      /* yes, skip setting mouse data */
  }

  XTestFakeMotionEvent(dpy,                                      /* stuff remote mouse position data into real X-server */
                       0,
                       x,
                       y,
                       CurrentTime);

  if (!blocked_or_guarded) {                                     /* make sure mouse buttons are allowed! */

    for (i = 0,                                                  /* for each button on the remote mouse */
           m = 1;
         i < 5;
         i++,
           m <<= 1) {

      if ((oldButtonMask & m) != (buttonMask & m)) {             /* has button changed since we last looked at it? */

        XTestFakeButtonEvent(dpy,                                /* yes, stuff new button data into X-server */
                             i + 1,
                             (buttonMask & m) ? True : False,
                             CurrentTime);
      }
    }
  }

  oldButtonMask = buttonMask;                                    /* remember button status for next time */
  gotInput = TRUE;                                               /* flag that we got some new input */
  defaultPtrAddEvent(buttonMask,x,y,cl);                         /* README says must call this for proper cursor handling? */
}


/* the X11 interaction */


Bool useSHM = TRUE;                                              /* flag indicates whether or not to use shared memory */


void getImage(int bpp, Display* dpy, int xscreen, XImage** i, int x, int y, int width, int height) { /* get an X11 display image, shared or not */
  /* bpp: bits per pixel */
  /* dpy: the X display */
  /* xscreen: and screen */
  /* i: the X11 image */
  /* x, y: its location */
  /* width, height: and size */

  XShmSegmentInfo* shminfo;                                      /* pointer to shared memory info for SHM stuff */

  if (width == 0) {                                              /* do nothing for degenerate images */
    return;
  }
  if (height == 0) {
    return;
  }

  if (useSHM) {                                                  /* if using X11 MIT shared memory extension */

    if (getImage_firstTime) {
      getImage_firstTime = FALSE;

      shminfo = (XShmSegmentInfo*)malloc(sizeof(XShmSegmentInfo)); /* make a shared memory info structure */

      *i = XShmCreateImage(dpy,                                  /* try to create an X11 shared memory image */
                           DefaultVisual(dpy, xscreen),
                           bpp,
                           ZPixmap,
                           NULL,
                           shminfo,
                           width,
                           height);

      if (*i == 0) {                                             /* if could not create shared memory image */
        useSHM = FALSE;                                          /* say no shared memory */

        getImage(bpp,                                            /* and try again */
                 dpy,
                 xscreen,
                 i,
                 x,
                 y,
                 width,
                 height);

        return;
      }

      shminfo->shmid = shmget(IPC_PRIVATE,                       /* create the shared memory segment */
                              (*i)->bytes_per_line * (*i)->height,
                              IPC_CREAT | 0777);

      shminfo->shmaddr = (*i)->data                              /* attach that segment to our process */
        = (char *)shmat(shminfo->shmid, 0, 0);

      shminfo->readOnly = False;                                 /* read-write mode -- *MANDATORY* !!! */

      XShmAttach(dpy, shminfo);                                  /* hook it up and make it live! */
    } /* end if getImage_firstTime */

    XShmGetImage(dpy,                                            /* get the live framebuffer data */
                 window,
                 *i,
                 x,
                 y,
                 AllPlanes);

  } else {                                                       /* not using X11 MIT shared memory extension at all */

    if (*i != NULL) {                                            /* do we already have an old image? */
      (*i)->f.destroy_image(*i);                                 /* yes, clean up old framebuffer memory */
    }

    *i = XGetImage(dpy,                                          /* get a new image the slow way */
                   window,
                   x,
                   y,
                   width,
                   height,
                   AllPlanes,
                   ZPixmap);
  }
}


static int fb_high;                                              /* how many pixels high the framebuffer is */
static int fb_wide;                                              /* how many pixels wide the framebuffer is */

static int tiles_high;                                           /* how many tiles high the framebuffer is */
static int tiles_wide;                                           /* how many tiles wide the framebuffer is */


static char* dirty_bits;                                         /* points to an array of flags, one per tile */

void reset_dirty_bits(void) {                                    /* turn off all the tiles' dirty bits */
  int i;
  for (i = 0; i < tiles_high*tiles_wide; i++) {
    dirty_bits[i] = 0;
  }
}

void construct_dirty_bits(int x, int y) {                        /* construct and initialize the dirty_bits */
  dirty_bits = (char*)malloc(x*y);
  reset_dirty_bits();
}

void destruct_dirty_bits() {                                     /* destruct the dirty bits */
  free(dirty_bits);
}

void set_dirty_bit(int x, int y) {                               /* set the dirty bit for a pixel */
  dirty_bits[x + y*tiles_wide] = TRUE;
}   

Bool test_dirty_bit(int x, int y) {                              /* check to see if a dirty bit is set */
  return (Bool)dirty_bits[x + y*tiles_wide];
}

void reset_dirty_bit(int x, int y) {                             /* reset the dirty bit for a pixel */
  dirty_bits[x + y*tiles_wide] = FALSE;
}


void mark_tiles_in_rectangle_as_dirty(int ulx, int uly, int lrx, int lry) { /* mark all tiles that intersect a rectangle as
                                                                               dirty */
  int x1 = ulx/tilewidth;
  int y1 = uly/tileheight;
  int x2 = (lrx + tilewidth - 1)/tilewidth;
  int y2 = (lry + tileheight - 1)/tileheight;
  int x;
  int y;
  for (y = y1; y < y2; y++) {                                    /* for all the tiles in that image */
    for (x = x1; x < x2; x++) {
      set_dirty_bit(x, y);                                       /* mark that tile dirty */
    }
  }
}


void mark_tiles_in_rectangle_as_clean(int ulx, int uly, int lrx, int lry) { /* mark all tiles that intersect a rectangle as
                                                                               clean */
  int x1 = ulx/tilewidth;
  int y1 = uly/tileheight;
  int x2 = (lrx + tilewidth - 1)/tilewidth;
  int y2 = (lry + tileheight - 1)/tileheight;
  int x;
  int y;
  for (y = y1; y < y2; y++) {                                    /* for all the tiles in that image */
    for (x = x1; x < x2; x++) {
      reset_dirty_bit(x, y);                                     /* mark that tile clean */
    }
  }
}


int image_is_dirty_P(rectangle* r) {                             /* test an image to see if it has changed */
  int x1 = r->ul_x/tilewidth;
  int y1 = r->ul_y/tileheight;
  int x2 = (r->lr_x + tilewidth - 1)/tilewidth;
  int y2 = (r->lr_y + tileheight - 1)/tileheight;
  int x;
  int y;
  for (y = y1; y < y2; y++) {                                    /* for all the tiles in that image */
    for (x = x1; x < x2; x++) {
      if (test_dirty_bit(x, y)) {                                /* if any tiles have changed */
        return TRUE;                                             /* then the image has changed */
      }
    }
  }
  return FALSE;                                                  /* otherwise the image has not changed */
}


void do_image(rectangle* r) {                                    /* process a single image rectangle */

  /* FIXME Overlapping image rectangles create an ambiguity, as it is not obvious which rectangle should be updated.  For now, at
     least, only the first such rectangle encountered will be updated, as it is difficult to do otherwise without undue
     complexity.  The best rule of thumb is to avoid overlapping image category rectangles!  Of course, if the rest of the other
     rectangle has changed, at least one other tile that intersects it will be marked dirty, so it will get refreshed anyway.  I
     guess it is a moot point... */

  if (image_is_dirty_P(r)) {                                     /* has the image changed? */

    int x1 = r->ul_x/tilewidth;
    int y1 = r->ul_y/tileheight;
    int x2 = (r->lr_x + tilewidth - 1)/tilewidth;
    int y2 = (r->lr_y + tileheight - 1)/tileheight;

    mark_tiles_in_rectangle_as_clean(r->ul_x,                    /* yes, do not update it a tile at a time */
                                     r->ul_y,
                                     r->lr_x,
                                     r->lr_y);

    rfbMarkRectAsModified(live_screen,                           /* but rather as a single rectangle */
                          x1*tilewidth,
                          y1*tileheight,
                          x2*tilewidth,
                          y2*tileheight);
  }
}


void do_atomic_updates(void) {                                   /* process all the rectangles on the image list */
  rectangle* r;
  state_rectangle_list_iterator* context;

  for (r = initial_rectangle_list_iterator(image_list, &context); /* for each image on the image list */
       continue_rectangle_list_iterator(&context);
       r = next_rectangle_list_iterator(&context)) {

    do_image(r);                                                 /* update that image atomically if it has changed */
  }
}


static char* region = NULL;                                      /* the save area for where we draw the bogus mouse cursor */

static int regionX = 0;                                          /* and its location and dimensions */
static int regionY = 0;
static int regionW = 0;
static int regionH = 0;

void saveRegion(int x, int y, int w, int h) {                    /* save the region where we will put the bogus mouse cursor */
  int py;
  int i;

  if (region != NULL) {                                          /* if there is a region, get rid of it */
    free(region);
  }

  region = (char*)malloc(w*h*bpp);                               /* get a fresh new region of the right size */

  if (region == NULL) {                                          /* if we failed to get one, just forget it FIXME ??? */
    return;
  }

  for (py = y,                                                   /* for each row in the region */
         i = 0;
       py < y + h;
       py++,
         i += w*bpp) {

    memcpy(region + i,                                           /* save that row */
           markupImage + py*rowstride + x*bpp,
           w*bpp );
  }

  regionX = x;                                                   /* remember where it came from so we can restore it */
  regionY = y;
  regionW = w;
  regionH = h;
}


void restoreRegion() {                                           /* restore the region we saved where the bogus mouse cursor went */
  int py;
  int i;

  if (region == NULL) {                                          /* if there is no such region, just forget it */
    return;
  }

  for (py = regionY,                                             /* for each row in the region */
         i = 0;
       py < regionY+regionH;
       py++,
         i += regionW * bpp) {

    memcpy(markupImage + py*rowstride + regionX*bpp,             /* restore that row */
           region + i,
           regionW*bpp);
  }

  free(region);                                                  /* return the region to the free pool */
  region = NULL;                                                 /* and say there is no region anymore */
}


#define POINTER_WIDTH   18                                       /* bogus mouse cursor dimensions */
#define POINTER_HEIGHT  18

char normal_pointer_map[] =                                      /* a pointer */
"                  "
" ..               "
" .+.              "
" .++.             "
" .+++.            "
" .++++.           "
" .+++++.          "
" .++++++.         "
" .+++++++.        "
" .++++++++.       "
" .+++++....       "
" .++.++.          "
" .+. .++.         "
" ..  .++.         "
"      .++.        "
"      .++.        "
"       .+..       "
"        .         ";

int normal_hotspot_x = 1;
int normal_hotspot_y = 2;


char guard_pointer_map[] =                                       /* "NO" sign */
"       ....       "
"    ...++++...    "
"   .++++++++++.   "
"  .+++++..+++++.  "
" .++++..  ..+++ . "
" .++++.     .+++. "
" .+++++.     .++. "
".++..+++.    .+++."
".++. .+++.    .++."
".++.  .+++.   .++."
".++.   .+++.  .++."
" .++.   .+++..++. "
" .++.    .+++.++. "
" .+++.    .+++++. "
"  .+++..   .+++.  "
"   .++++..++++.   "
"    ...++++...    "
"       ....       ";

int guard_hotspot_x = 9;
int guard_hotspot_y = 9;


char block_pointer_map[] =                                       /* crying smiley face */
"       ....       "
"    ...++++...    "
"   .++++++++++.   "
"  .++++++++++++.  "
" .+++++++++++++ . "
" .+.++.+++.+++.+. "
" .++..+++++.+.++. "
".+++..++++++.++++."
".++.++.++++.+.+++."
".+.++++. +.+++.++."
".++.+++++++++++++."
" .+.+++....+++++. "
" ..+...++++..+++. "
" .+++.+++++++.++. "
".+++++.++++++++.  "
" .+++.++++++++.   "
"  .+...++++...    "
"   .   ....       ";

int block_hotspot_x = 9;
int block_hotspot_y = 9;

char* pointerMap;
int hotspot_x;
int hotspot_y;

void paintMousePointer(int x, int y) {                           /* put a bogus imitation mouse cursor at (x,y) */
  int mx = min(fb_wide - x, POINTER_WIDTH);
  int my = min(fb_high - y, POINTER_HEIGHT);
  int px;
  int py;
  int ofs;

  saveRegion(x,y,mx,my);                                         /* save where mouse cursor goes so we can restore it later */

  for (py = 0; py < my; py++) {                                  /* for each row in the mouse cursor image */
    for (px = 0; px < mx; px++) {                                /* for each pixel in that row */
      ofs = (x + px)*(bpp) + (y + py)*rowstride;                 /* offset into framebuffer */
      switch (pointerMap[px + py*POINTER_WIDTH]) {               /* fetch the corresponding pixel for the mouse cursor image */

      case '.':                                                  /* draw that pixel for the outline */
        switch (depth) {                                         /* depending on haw many bits per pixel we are running */
                                                                 /* here we use black */
        case  8:
          markupImage[ofs]   = 0;
          break;

        case 16:
          markupImage[ofs]   = 0;
          markupImage[ofs+1] = 0;
          break;

        case 24:
          markupImage[ofs]   = 0;
          markupImage[ofs+1] = 0;
          markupImage[ofs+2] = 0;
          break;

        case 32:
          markupImage[ofs]   = 0;
          markupImage[ofs+1] = 0;
          markupImage[ofs+2] = 0;
          markupImage[ofs+3] = 0;
          break;
        }
        break;

      case '+':                                                  /* draw that pixel for the inside */
        switch (depth) {                                         /* depending on haw many bits per pixel we are running */
                                                                 /* here we use white */
        case  8:
          markupImage[ofs]   = 255;
          break;

        case 16:
          markupImage[ofs]   = 255;
          markupImage[ofs+1] = 255;
          break;

        case 24:
          markupImage[ofs]   = 255;
          markupImage[ofs+1] = 255;
          markupImage[ofs+2] = 255;
          break;

        case 32:
          markupImage[ofs]   = 255;
          markupImage[ofs+1] = 255;
          markupImage[ofs+2] = 255;
          markupImage[ofs+3] = 255;
          break;
        }
        break;

      default: break;                                            /* catch all for any other character, especially space */
      }
    }
  }
}

static int old_root_x = 0;
static int old_root_y = 0;
static int bogus_mouse_cursor_was_clobbered;


void show_bogus_mouse_cursor(void) {                             /* draw the bogus mouse cursor into the backup framebuffer if needed */
  Window root_return;
  Window child_return;
  int root_x;
  int root_y;
  int win_x;
  int win_y;
  unsigned int mask_return;

  if (bogus_mouse_cursor_was_clobbered) {

    paintMousePointer(regionX, regionY);

    mark_tiles_in_rectangle_as_dirty(regionX,
                                     regionY,
                                     regionX + regionW,
                                     regionY + regionH);         /* send it to the viewer */     
  }
  
  XQueryPointer(dpy,                                             /* find out where the X-server's mouse is */
                window,
                &root_return,
                &child_return,
                &root_x,
                &root_y,
                &win_x,
                &win_y,
                &mask_return);

  if (root_x != old_root_x                                       /* do we need to re-draw the bogus mouse cursor because it moved? */
      || root_y != old_root_y) {

    restoreRegion();                                             /* yes, restore backup framebuffer at old mouse location */

    mark_tiles_in_rectangle_as_dirty(regionX,
                                     regionY,
                                     regionX + regionW,
                                     regionY + regionH);         /* send it to the viewer */

    paintMousePointer(root_x - hotspot_x,                        /* draw the mouse cursor in the proper place */
                      root_y - hotspot_y);

    old_root_x = root_x;                                         /* remember where the mouse cursor is now */
    old_root_y = root_y;

    mark_tiles_in_rectangle_as_dirty(regionX,
                                     regionY,
                                     regionX + regionW,
                                     regionY + regionH);         /* send it to the viewer */
  }
}


int pixel_is_in_mouse_region_P(int x, int y) {                   /* is a pixel inside the current mouse cursor region? */
  int x_intersects;
  int y_intersects;
  int in_region;

  if (region == NULL) {                                          /* is there any saved region? */
    return FALSE;                                                /* no, therefore the pixel cannot possibly be inside of it! */
  }

  x_intersects = (regionX <= x) && (x < (regionX + regionW));    /* does the horizontal line thru the pixel intersect the mouse region? */
  y_intersects = (regionY <= y) && (y < (regionY + regionH));    /* does the vertical line thru the pixel intersect the mouse region? */

  in_region = x_intersects && y_intersects;                      /* if both lines intersect, then the pixel is in the region */

  return in_region;
  
}


/* probe the framebuffer looking for changes */

static unsigned int* horizscanlines;                             /* interlace pattern for scanning horizontally within a tile */

void probe_horizontal(void) {                                    /* probe horizontally, looking for changed spots */

  int interlace;                                                 /* which scan line in a tile to test */
  int x;                                                         /* pixel x coord */
  int y;                                                         /* pixel y coord */
  int i;                                                         /* tile column index */
  static int j = 0;                                              /* tile row index, persistent between calls */
  static int probe_y = 0;                                        /* the horizontal interlace line we are examining */
  int k;
  int p;
  unsigned char* src;
  unsigned char* dst;

  /* Scan a width by 1 image, comparing a 1 tilewidth chunk at a time.  If a difference is discovered, mark the tile as dirty,
     then move ahead to the next tile.  Do this for each row of tiles in the live framebuffer.  Note that only one scan line is
     being checked in each row of tiles. */

  if (j >= tiles_high) {                                         /* for each row of tiles */
    j = 0;                                                       /* start over with row zero */
    probe_y++;                                                   /* move on to the next interlaced scan row next time */
    probe_y %= tileheight;                                       /* wrap around to stay within tile size */
  }

  interlace = horizscanlines[probe_y];                           /* choose a line to scan */

  y = clip(0, j*tileheight + interlace, fb_high - 1);            /* the y coord of the scan row for all tiles in this row of tiles */

  getImage(depth,                                                /* read the scan row from the live X-server */
           dpy,
           xscreen,
           &scan_row_image,
           0,                                                    /* x, y */
           y,
           fb_wide,                                              /* width, height */
           1); 

  for (i = 0; i < tiles_wide; i++) {                             /* for each tile in the horizontal row */
    if (test_dirty_bit(i, j)) {                                  /* is it already known to be dirty? */
      continue;                                                  /* yes, skip over it */
    }
    for (k = 0; k < tilewidth; k++) {                            /* for each pixel in this scan line in this tile */
      x = clip(0, i*tilewidth + k, fb_wide - 1);                 /* x coord of left edge of tile */
      src = scan_row_image->data + x*bpp;                        /* starting position in live scan row buffer */
      dst = backupImage + y*rowstride + x*bpp;                   /* no, starting position in backup frame buffer */

      for (p = 0; p < bpp; p++) {                                /* for each byte int the pixel */
        if (src[p] != dst[p]) {                          /* any difference detected? */

          /* FIXME put logic here to detect "special rectangles" and handle them accordingly */

          set_dirty_bit(i, j);                                   /* yes, flag it! */
          break;
        }
      }
    }
  }

  j++;
}


static unsigned int* vertscanlines;                              /* interlace pattern for scanning vertically within a tile */

void probe_vertical(void) {                                      /* probe vertically, looking for changed spots */

  int interlace;                                                 /* which scan line in a tile to test */
  int x;                                                         /* pixel x coord */
  int y;                                                         /* pixel y coord */
  static int i = 0;                                              /* tile column index, persistent between calls */
  static int probe_x = 0;                                        /* the vertically interlace line we are examining */
  int j;                                                         /* tile row index */
  int k;
  int p;
  unsigned char* src;
  unsigned char* dst;

  /* Scan a 1 by height image, comparing a 1 tileheight chunk at a time.  If a difference is discovered, mark the tile as dirty,
     then move ahead to the next tile.  Do the above for each column of tiles in the live framebuffer.  Note that only one scan
     line is being checked in each column of tiles. */

  if (i >= tiles_wide) {                                         /* for each column of tiles */
    i = 0;                                                       /* start over with column zero */
    probe_x++;                                                   /* move on to the next interlaced scan column next time */
  }

  probe_x %= tilewidth;                                          /* wrap around to stay within tile size */
  interlace = vertscanlines[probe_x];                            /* choose a line to scan */

  x = clip(0, i*tilewidth + interlace, fb_wide - 1);             /* the y coord of scan column in this column of tiles */

  getImage(depth,                                                /* read a scan column from the live X-server */
           dpy,
           xscreen,
           &scan_col_image,
           x,                                                    /* x, y */
           0,
           1,                                                    /* width, height */
           fb_high);

  for (j = 0; j < tiles_high; j++) {                             /* for each tile in the vertical column */
    if (test_dirty_bit(i, j)) {                                  /* is it already known to be dirty? */
      continue;                                                  /* yes, skip over it */
    }
    for (k = 0 ; k < tileheight; k++) {                          /* for each pixel in this scan line in this tile */
      y = clip(0, j*tileheight + k, fb_high - 1);                /* y coord of top edge of tile */
      src = scan_col_image->data + y*scan_col_image->bytes_per_line; /* starting position in live scan col framebuffer */
      dst = backupImage + y*rowstride + x*bpp;                   /* no, starting position in backup frame buffer */

      for (p = 0; p < bpp; p++) {                                /* for each byte int the pixel */
        if ( src[p] != dst[p]) {

          /* FIXME put logic here to detect "special rectangles" and handle them accordingly */

          set_dirty_bit(i, j);
          break;
        }
      }
    }
  }

  i++;
}


int rectangles_are_disjoint_P(int a_ul_x, int a_ul_y, int a_lr_x, int a_lr_y, int b_ul_x, int b_ul_y, int b_lr_x, int b_lr_y) { /* are 2 rectangles disjoint? */
  /* a_ul_x, a_ul_y, a_lr_x, a_lr_y: rectangle a */
  /* b_ul_x, b_ul_y, b_lr_x, b_lr_y: rectangle b */

  if (a_lr_x < b_ul_x) {
    return TRUE;                                                 /* a is entirely to the left of b */
  }
  if (a_lr_y < b_ul_y) {
    return TRUE;                                                 /* a is entirely above b */
  }
  if (b_lr_x < a_ul_x) {
    return TRUE;                                                 /* b is entirely to the left of a */
  }
  if (b_lr_y < a_ul_y) {
    return TRUE;                                                 /* b is entirely above a */
  }
  return FALSE;                                                  /* none of the above, they must intersect, so they are not disjoint */
}


int rectangles_intersect_P(int a_ul_x, int a_ul_y, int a_lr_x, int a_lr_y, int b_ul_x, int b_ul_y, int b_lr_x, int b_lr_y) { /* do 2 rectangles intersect? */
  /* a_ul_x, a_ul_y, a_lr_x, a_lr_y: rectangle a */
  /* b_ul_x, b_ul_y, b_lr_x, b_lr_y: rectangle b */

  return !rectangles_are_disjoint_P(                             /* they intersect if they are not disjoint */
                                    a_ul_x,                      /* rectangle a */
                                    a_ul_y,
                                    a_lr_x,
                                    a_lr_y,
                                    b_ul_x,                      /* rectangle b */
                                    b_ul_y,
                                    b_lr_x,
                                    b_lr_y);
}


void copy_tile(int i, int j) {                                   /* copy a dirty tile to our backup framebuffer */
  int x0 = clip(0, i*tilewidth, fb_wide);                        /* upper left corner of tile */
  int y0 = clip(0, j*tileheight, fb_high);
  int xn = clip(0, x0 + tilewidth, fb_wide);                     /* lower right corner of tile */
  int yn = clip(0, y0 + tileheight, fb_high);
  int wide = (xn - x0);                                          /* width of tile, or partial tile if on edge */
  int high = (yn - y0);                                          /* height of tile, or partial tile if on edge */
  int y;
  unsigned char* src;
  unsigned char* dst1;
  unsigned char* dst2;
  int len;
  int k;
  XImage** image;

  /* Let TW = tilewidth, TH = tileheight, w = fb_wide, and h = fb_high.  The framebuffer, fb, is divided into tiles such that for
     a w by h fb, there are ceil(w/TW)*ceil(h/TH) tiles.  There are floor(w/TW)*floor(h/TH) tiles that are a full TW*TH pixels in
     size.  There are floor(w/TW) tiles that are on the bottom edge but not on the lower right corner that are T*b pixels, there
     are floor(h/TH) tiles on the right edge but not on the lower right corner that are r*T pixels, and there is a single tile on
     the lower right corner that is r*b pixels.  Now that we know the dimensions of all possible tiles, full or partial, we are
     ready to handle each possible case that can arise. */
  
  if (wide == tilewidth && high == tileheight) {                 /* is this a full sized tile? */
    image = &tile_image;                                         /* yes, full sized */

  } else if (wide == tilewidth) {                                /* not full sized, but full width? */
    image = &bottom_edge_image;                                  /* yes, bottom edge */

  } else if (high == tileheight) {                               /* maybe right edge? */
    image = &right_edge_image;                                   /* yes, right edge */

  } else {                                                       /* none of the above, must be lower right corner */
    image = &corner_image;                                       /* yes, lower right corner */
  }

  getImage(depth,                                                /* fetch the tile from the live X-server */
           dpy,
           xscreen,
           image,
           x0,
           y0,
           wide,
           high);

  if (region != NULL) {                                          /* is a bogus mouse cursor being displayed now? */
    if (rectangles_intersect_P(                                  /* yes, will the copy_tile clobber the bogus mouse cursor? */
                               x0,                               /* tile rectangle */
                               y0,
                               xn,
                               yn,
                               regionX,                          /* bogus mouse cursor rectangle */
                               regionY,
                               regionX + regionW,
                               regionY + regionH)) {

      bogus_mouse_cursor_was_clobbered = TRUE;                   /* yes, remember that so we can redraw it later */
      restoreRegion();                                           /* remove the mouse cursor before we clobber it */
    }
  }

  for (y = y0; y < yn; y++) {                                    /* copy each pixel row in the tile */
    src = (*image)->data + (y - y0)*(*image)->bytes_per_line;    /* source address for copy */
    dst1 = backupImage + y*rowstride + x0*bpp;                   /* backup destination address for copy */
    dst2 = markupImage + y*rowstride + x0*bpp;                   /* markup destination address for copy */
    len = wide*bpp;

    for (k = 0; k < len; k++) {                                  /* copy tile from live to both backup and markup */
      dst1[k] = src[k];
      dst2[k] = src[k];
    }
  }
}


static unsigned char opaque_color[4] = { 0, 127, 255, 0 };       /* NOTE: *MUST* match with 'opaque_color_string' initial value! */

void color_opaque_rectangle(int ulx, int uly, int lrx, int lry) { /* color an opaque rectangle for blocked regions */
  int x;
  int y;
  unsigned char* dst;
  int i;
  rectangle* raw = construct_rectangle(ulx, uly, lrx, lry, "");
  rectangle* screen = construct_rectangle(0, 0, fb_wide - 1, fb_high - 1, "");
  rectangle* clipped = intersection_of_2_rectangles(raw, screen, "");

  ulx = clipped->ul_x;
  uly = clipped->ul_y;
  lrx = clipped->lr_x;
  lry = clipped->lr_y;

  if (rectangles_intersect_P(ulx,                                /* this opaque rectangle */
                             uly,
                             lrx,
                             lry,
                             regionX,                            /* bogus mouse cursor rectangle */
                             regionY,
                             regionX + regionW,
                             regionY + regionH)) {
    
    bogus_mouse_cursor_was_clobbered = TRUE;                     /* yes, remember that so we can redraw it later */
    restoreRegion();                                             /* remove the mouse cursor before we clobber it */
  }

  for (y = uly; y <= lry; y++) {                                 /* for each row in the rectangle*/
    for (x = ulx; x <= lrx; x++) {                               /* for each pixel in that row */
      dst = markupImage + y*rowstride + x*bpp;                   /* compute that pixel's address */
      for (i = 0; i < bpp; i++) {                                /* for each byte in the pixel */
        dst[i] = opaque_color[i];                                /* color that byte opaque */
      }
    }
  }
}


static unsigned char transparent_color[4] = { 0, 63, 0, 0 };     /* NOTE: *MUST* match with 'transparent_color_string' initial value! */

void color_transparent_rectangle(int ulx, int uly, int lrx, int lry) { /* color a transparent rectangle for guarded regions */
  int x;
  int y;
  unsigned char* src;
  unsigned char* dst;
  int i;
  rectangle* raw = construct_rectangle(ulx, uly, lrx, lry, "");
  rectangle* screen = construct_rectangle(0, 0, fb_wide - 1, fb_high - 1, "");
  rectangle* clipped = intersection_of_2_rectangles(raw, screen, "");

  ulx = clipped->ul_x;
  uly = clipped->ul_y;
  lrx = clipped->lr_x;
  lry = clipped->lr_y;

  if (rectangles_intersect_P(ulx,                                /* this transparent rectangle */
                             uly,
                             lrx,
                             lry,
                             regionX,                            /* bogus mouse cursor rectangle */
                             regionY,
                             regionX + regionW,
                             regionY + regionH)) {
    
    bogus_mouse_cursor_was_clobbered = TRUE;                     /* yes, remember that so we can redraw it later */
    restoreRegion();                                             /* remove the mouse cursor before we clobber it */
  }

  for (y = uly; y <= lry; y++) {                                 /* for each row in the rectangle*/
    for (x = ulx; x <= lrx; x++) {                               /* for each pixel in that row */
      src = backupImage + y*rowstride + x*bpp;                   /* compute that pixel's unmarked-up address */
      dst = markupImage + y*rowstride + x*bpp;                   /* compute that pixel's marked-up address */
      for (i = 0; i < bpp; i++) {                                /* for each byte in the pixel */
        dst[i] = (src[i] + transparent_color[i])/2;              /* color that byte transparent */
      }
    }
  }
}


void send_changed_tiles_to_viewer(void) {                        /* send all changed tiles to the VNC viewer */
  rectangle* r;
  state_rectangle_list_iterator* context;
  int x_ul;
  int y_ul;
  int x_lr;
  int y_lr;
  int cursor_x;
  int cursor_y;
  int i;
  int j;

  /* Copy all tiles in which the probes detected changes from the live framebuffer into our backup copy of it. */

  bogus_mouse_cursor_was_clobbered = FALSE;                      /* assume bogus mouse cursor will be left untouched by copy_tile
                                                                    operations */
  blocked_or_guarded = FALSE;                                    /* assume not in special region */

  for (j = 0 ; j < tiles_high; j++) {                            /* loop thru rows of tiles */
    for (i = 0; i < tiles_wide; i++) {                           /* loop thru tiles in a row */
      if (test_dirty_bit(i, j)) {                                /* is this tile dirty? */
        copy_tile(i, j);                                         /* yes, copy that tile to the backup buffer */
      }
    }
  }

  pointerMap = normal_pointer_map;                               /* assume normal bogus mouse cursor applies */
  hotspot_x = normal_hotspot_x;
  hotspot_y = normal_hotspot_y;
  cursor_x = regionX + hotspot_x;
  cursor_y = regionY + hotspot_y;

  /* Color the blocked and guarded regions.  */
  
  for (r = initial_rectangle_list_iterator(guard_list, &context); /* search the guard list */
       continue_rectangle_list_iterator(&context);
       r = next_rectangle_list_iterator(&context)) {

    x_ul = r->ul_x;
    y_ul = r->ul_y;
    x_lr = r->lr_x;
    y_lr = r->lr_y;
    
    color_transparent_rectangle(x_ul, y_ul, x_lr, y_lr);         /* color transparent guard regions first */

    /* FIXME The logic below should check whether the mouse cursor image rectangle intersects the guarded rectangle for the
       purpose of setting the bogus_mouse_cursor_was_clobbered flag, but it needs to check whether the mouse cursor's hot-spot is
       inside the guarded rectangle for the purpose of changing the mouse cursor's image. */

    if (point_is_inside_rectangle_P(cursor_x, cursor_y, r)) {    /* is the mouse in this rectangle? */
      bogus_mouse_cursor_was_clobbered = TRUE;                   /* yes, remember that so we can redraw it later */
      restoreRegion();                                           /* remove the mouse cursor before we clobber it */
      pointerMap = guard_pointer_map;                            /* make the guard cursor the current cursor */
      hotspot_x = guard_hotspot_x;
      hotspot_y = guard_hotspot_y;
      blocked_or_guarded = TRUE;                                 /* say no keystrokes or mouse buttons */
    }
  }

  for (r = initial_rectangle_list_iterator(block_list, &context); /* search the  block list */
       continue_rectangle_list_iterator(&context);
       r = next_rectangle_list_iterator(&context)) {

    x_ul = r->ul_x;
    y_ul = r->ul_y;
    x_lr = r->lr_x;
    y_lr = r->lr_y;
    
    color_opaque_rectangle(x_ul, y_ul, x_lr, y_lr);              /* color opaque block regions next */

    /* FIXME Ditto for the below logic regarding the comments on fixing the guard rectangles.  The same mouse cursor problem
       exists here too. */

    if (point_is_inside_rectangle_P(cursor_x, cursor_y, r)) {    /* is the mouse in this rectangle? */
      bogus_mouse_cursor_was_clobbered = TRUE;                   /* yes, remember that so we can redraw it later */
      restoreRegion();                                           /* remove the mouse cursor before we clobber it */
      pointerMap = block_pointer_map;                            /* make the block cursor the current cursor */
      hotspot_x = block_hotspot_x;
      hotspot_y = block_hotspot_y;
      blocked_or_guarded = TRUE;                                 /* say no keystrokes or mouse buttons */
    }
  }

  show_bogus_mouse_cursor();                                     /* display the bogus mouse cursor last */

  do_atomic_updates();                                           /* update image rectangles first */

  /* Send all those changed tiles to any client vnc viewers that are connected to this server. */

  for (j = 0 ; j < tiles_high; j++) {                            /* loop thru rows of tiles */
    for (i = 0; i < tiles_wide; i++) {                           /* loop thru tiles in a row */
      if (test_dirty_bit(i, j)) {                                /* is this tile dirty? */

        x_ul = clip(0, i*tilewidth, fb_wide);                    /* yes, upper left corner of tile */
        y_ul = clip(0, j*tileheight, fb_high);
        x_lr = clip(0, x_ul + tilewidth, fb_wide);               /* lower right corner of tile */
        y_lr = clip(0, y_ul + tileheight, fb_high);

        rfbMarkRectAsModified(live_screen, x_ul, y_ul, x_lr, y_lr); /* send it to the VNC viewer! */

        reset_dirty_bit(i, j);                                   /* say we have cleaned this tile */
      }
    }
  }
}


#define PROBE_HORIZ (1)                                          /* probe along the horizontal axis */
#define PROBE_VERT (-1)                                          /* probe along the vertical axis */
static int probe_axis = PROBE_HORIZ;                             /* which way do we probe on this time?  horiz or vert? */

void checkForImageUpdates() {                                    /* scan for changes to the live framebuffer */
  int i;

  for (i = 0; i < scan_count; i++) {                             /* scan for changes for a while... */

    if (probe_axis == PROBE_HORIZ) {                             /* which direction are we probing this time? */
      probe_horizontal();                                        /* horizontal, do it */
      probe_axis = PROBE_VERT;                                   /* say go the other way next time */

    } else {
      probe_vertical();                                          /* vertical, do it */
      probe_axis = PROBE_HORIZ;                                  /* say go the other way next time */
    }
  }

  send_changed_tiles_to_viewer();                                /* send all the dirty tiles to the VNC viewer */
}


/* ---------------- The Main Program ---------------- */

int main(int argc, char** argv) {

  int i;
  int c = 0;
  rfbScreenInfoPtr screen;

  int max_input_events_before_update = 8;

  /* visual cue colors for blocked and guarded special rectangles */
  /* NOTE: These valuse *MUST* match the values in 'opaque_color' and 'transparent_color' above for the defaults to work
     properly! */
  char* opaque_color_string = "#ff7f00";                         /* blocked regions default to "orange" */
  char* transparent_color_string = "#003f00";                    /* guarded regions default to "sunglasses green" */
  XColor temp_color_spec;

  int maxMsecsToConnect = 60000;                                 /* a maximum of 60 seconds to connect */
  /* FIXME maxMsecsToConnect is out of calibration.  On linux intel PC, this is 20 sec, not 5 sec.  What is wrong? */

  int updateCounter;                                             /* about every 50 ms a screen update should be made. */
  /* FIXME Is this a scan of the X-server framebuffer to see if an update needs to be sent to the vncviewer? */

  updateCounter = 1;                                             /* default value, see -update option below */


  /* ---------------- Parse The Command Line Arguments In Reverse Order... ---------------- */

  for (i = argc - 1; i > 0; i--) {                               /* for each argument on the command line... */

    if (!strcmp(argv[i], "-help")) {                             /* -help */
      fprintf(stderr, 
              "Usage: gemsvnc {options}, where the options are either libvncserver options\n"
              "                          or gems-specific options.\n"
              "\n"
              "These are the gemsvnc specific options:\n"
              "\n"
              "-help              Display this help message.\n"
              "-display           Specify the display to attach to.\n"
              "-wait4client       Specify the time-out to use when waiting for a client to\n"
              "                     connect.\n"
              "-update            Force an update of all connected clients.\n"
              "-noshm             Do not use the MIT X shared memory (SHM) extension even if\n"
              "                     it is available.\n"
              "-runforever        Do not time out waiting for a client, and do not exit\n"
              "                     after the last client has disconnected.\n"
              "-viewonly          Only permit the remote view to view, but not to interact\n"
              "                     with the display being served.\n"
              "-shared            Allow multiple clients to be connected simultaneously.\n"
              "-scans             Specify the number of the framebuffer between sampling for\n"
              "                     input events or sending framebuffer updates.\n"
              "-tw                Specify the width in pixels of the tiles the framebuffer\n"
              "                     is divided into.\n"
              "-th                Specify the height in pixels of the tiles the framebuffer\n"
              "                     is divided into.\n"
              "-bc                Specify the blocked region opaque color, default is orange.\n"
              "-gc                Specify the guarded region transparent color, default is green.\n"
              "-events            Specify the maximum number of input events before a screen\n"
              "                     update is forced.\n"
              "\n"
              "These are the libvncserver specific options:\n"
              "\n"
              );
    }


        if (i < argc - 1 && strcmp(argv[i], "-display")==0) {    /* -display */
          fprintf(stderr, "Using display %s\n", argv[i + 1]);
          dpy = XOpenDisplay(argv[i + 1]);
          if (dpy == 0) {
            fprintf(stderr, "Couldn't connect to display \"%s\".\n", argv[i + 1]);
            exit(1);

          }

        } else if (i < argc - 1 && strcmp(argv[i], "-wait4client") == 0) { /* -wait4client */
          maxMsecsToConnect = atoi(argv[i + 1]);

        } else if (i < argc - 1 && strcmp(argv[i], "-update") == 0) { /* -update */
          updateCounter = atoi(argv[i + 1]);

        } else if (strcmp(argv[i], "-noshm") == 0) {             /* -noshm */
          useSHM = FALSE;

        } else if (strcmp(argv[i], "-runforever") == 0) {        /* -runforever */
          disconnectAfterFirstClient = FALSE;

        } else if (strcmp(argv[i], "-viewonly") == 0) {          /* -viewonly */
          viewOnly = TRUE;

        } else if (strcmp(argv[i], "-shared") == 0) {            /* -shared */
          sharedMode = TRUE;
        } else if (strcmp(argv[i], "-scans") == 0) {             /* -scans */
          scan_count = atoi(argv[i + 1]);
        } else if (strcmp(argv[i], "-tw") == 0) {                /* -tw */
          tilewidth = atoi(argv[i + 1]);
        } else if (strcmp(argv[i], "-th") == 0) {                /* -th */
          tileheight = atoi(argv[i + 1]);
        } else if (strcmp(argv[i], "-bc") == 0) {                /* -bc */
          opaque_color_string = argv[i + 1];                     /* just save the string for now, as we must open the display
                                                                    first to resolve it */
        } else if (strcmp(argv[i], "-gc") == 0) {                /* -gc */
          transparent_color_string = argv[i + 1];                /* just save the string for now, as we must open the display
                                                                    first to resolve it */
        } else if (strcmp(argv[i], "-events") == 0) {            /* max number of inputs before screen update */
          max_input_events_before_update = atoi(argv[i + 1]);
        }
  }

  /* ---------------- End Of Parsing Command Line Arguments, Initialize Everything ---------------- */

  pointerMap = normal_pointer_map;                               /* specify which bogus mouse cursor to use */
  hotspot_x = normal_hotspot_x;
  hotspot_y = normal_hotspot_y;

  vertscanlines = ilace(tilewidth);                              /* generate the vertical interlace sequence */
  horizscanlines = ilace(tileheight);                            /* generate the horizontal interlace sequence */

  if (dpy == 0) {                                                /* open default DISPLAY if none already opened */
    dpy = XOpenDisplay("");
  }
  if (dpy == 0) {                                                /* could not open default either */
    fprintf(stderr, "Couldn't open display!\n");
    exit(2);                                                     /* DIE! */
  }

  xscreen = DefaultScreen(dpy);                                  /* default screen of selected DISPLAY */
  window = RootWindow(dpy, xscreen);                             /* root window of that screen */
  depth = DefaultDepth(dpy, xscreen);                            /* depth in bits per pixel */
  depth = 32;                                                    /* FIXME crocked for debug */
  bpp = depth/8;                                                 /* bytes per pixel */
  fb_high = DisplayHeight(dpy, xscreen);                         /* how many pixels high is the framebuffer */
  fb_wide = DisplayWidth(dpy, xscreen);                          /* how many pixels wide is the framebuffer */
  tiles_high = (fb_high + tileheight - 1)/tileheight;            /* how many tiles high is the framebuffer */
  tiles_wide = (fb_wide + tilewidth - 1)/tilewidth;              /* how many tiles wide is the framebuffer */

  init_keycodes();                                               /* initialize the keyboard */

  if (useSHM) {                                                  /* is shared memory explicitly disabled? */
    useSHM = XShmQueryVersion(dpy,                               /* no, determine if it is available */
                              &SHM_major_version,
                              &SHM_minor_version,
                              &SHM_shared_pixmaps_ok);

    if (useSHM) {                                                /* did we fidn the SHM extension? */
      fprintf(stderr, "Using SHM %d.%d", SHM_major_version, SHM_minor_version); /* yes, say so */
      if (SHM_shared_pixmaps_ok) {                               /* pixmaps too? */
        fprintf(stderr, ", shared pixmaps supported");           /* yes */
      } else {
        fprintf(stderr, ", shared pixmaps not supported");       /* no */
      }
      fprintf(stderr, ".\n");
    }
  }

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* get the initial state of the live X-server's framebuffer */
           dpy,
           xscreen,
           &framebufferImage,
           0,
           0,
           fb_wide,
           fb_high);

  screen = rfbGetScreen(&argc, argv,
                        framebufferImage->width,
                        framebufferImage->height,
                        framebufferImage->bits_per_pixel,
                        8,
                        framebufferImage->bits_per_pixel/8);

  screen->paddedWidthInBytes = framebufferImage->bytes_per_line;

  screen->rfbServerFormat.bitsPerPixel = framebufferImage->bits_per_pixel;
  screen->rfbServerFormat.depth = framebufferImage->depth;
  /* rfbEndianTest = framebufferImage->bitmap_bit_order != MSBFirst; */  /* FIXME Is this needed in a mixed network? */
  screen->rfbServerFormat.trueColour = TRUE;

  if (screen->rfbServerFormat.bitsPerPixel == 8) {               /* using a color map? */

    if (CellsOfScreen(ScreenOfDisplay(dpy, xscreen))) {          /* 8 bits per pixel, and using a color map */

      XColor color[256];                                         /* the pallette */
      int i;
      screen->colourMap.count = 256;
      screen->rfbServerFormat.trueColour = FALSE;
      screen->colourMap.is16 = TRUE;
      for (i = 0; i < 256; i++) {                                /* initialize the pallette */
        color[i].pixel=i;
      }
      XQueryColors(dpy, DefaultColormap(dpy, xscreen), color, 256);
      screen->colourMap.data.shorts = (short*)malloc(3*sizeof(short)*screen->colourMap.count);
      for (i = 0; i < screen->colourMap.count; i++) {
        screen->colourMap.data.shorts[i*3 + 0] = color[i].red;
        screen->colourMap.data.shorts[i*3 + 1] = color[i].green;
        screen->colourMap.data.shorts[i*3 + 2] = color[i].blue;
      }

    } else {                                                     /* 8 bits per pixel, but not using a color map */

      screen->rfbServerFormat.redShift = 0;
      screen->rfbServerFormat.greenShift = 2;
      screen->rfbServerFormat.blueShift = 5;
      screen->rfbServerFormat.redMax   = 3;
      screen->rfbServerFormat.greenMax = 7;
      screen->rfbServerFormat.blueMax  = 3;
    }

  } else {                                                       /* not 8 bits per pixel, so not using a color map */

    screen->rfbServerFormat.redShift = 0;                        /* red */
    if (framebufferImage->red_mask) {
      while (!(framebufferImage->red_mask & (1 << screen->rfbServerFormat.redShift))) {
        screen->rfbServerFormat.redShift++;
      }
    }
    screen->rfbServerFormat.redMax   = framebufferImage->red_mask   >> screen->rfbServerFormat.redShift;

    screen->rfbServerFormat.greenShift = 0;                      /* green */
    if (framebufferImage->green_mask) {
      while (!(framebufferImage->green_mask & (1 << screen->rfbServerFormat.greenShift))) {
        screen->rfbServerFormat.greenShift++;
      }
    }
    screen->rfbServerFormat.greenMax = framebufferImage->green_mask >> screen->rfbServerFormat.greenShift;

    screen->rfbServerFormat.blueShift = 0;                       /* blue */
    if (framebufferImage->blue_mask) {
      while (!(framebufferImage->blue_mask & (1 << screen->rfbServerFormat.blueShift))) {
        screen->rfbServerFormat.blueShift++;
      }
    }
    screen->rfbServerFormat.blueMax  = framebufferImage->blue_mask  >> screen->rfbServerFormat.blueShift;
  }

  backupImageSize = screen->height*screen->paddedWidthInBytes;   /* length in bytes of backup framebuffer */
  backupImage = malloc(backupImageSize);                         /* allocate the backup framebuffer */
  memcpy(backupImage, framebufferImage->data, backupImageSize);  /* copy live image into it */
  markupImage = malloc(backupImageSize);                         /* allocate the markup framebuffer */
  memcpy(markupImage, framebufferImage->data, backupImageSize);  /* copy live image into it */

  screen->frameBuffer = markupImage;
  screen->cursor = 0;
  screen->newClientHook = newClient;

  screen->kbdAddEvent = keyboard;
  screen->ptrAddEvent = mouse;

  if (sharedMode) {
    screen->rfbAlwaysShared = TRUE;
  }

  screen->rfbDeferUpdateTime = 1;
  updateCounter /= screen->rfbDeferUpdateTime;

  rfbInitServer(screen);                                         /* now that the data structure is ready, start the rfb server */

  /* static, so we don't have to pass these all over the place!  These *SHOULD* be instance variables if this were C++! */
  
  live_screen = screen;                                          /* the live framebuffer */
  rowstride = framebufferImage->bytes_per_line;                  /* byte per line in our backup framebuffer */

  construct_dirty_bits(tiles_wide, tiles_high);                  /* a dirty bit (represented as a byte) for each tile */

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a scan row */
           dpy,
           xscreen,
           &scan_row_image,
           0,
           0,
           fb_wide,
           1);

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a scan column */
           dpy,
           xscreen,
           &scan_col_image,
           0,
           0,
           1,
           fb_high);

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a tile */
           dpy,
           xscreen,
           &tile_image,                                          /* full sized tile */
           0,
           0,
           tilewidth,
           tileheight);

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a tile */
           dpy,
           xscreen,
           &right_edge_image,                                    /* right edge partial tile */
           0,
           0,
           fb_wide - tilewidth*(fb_wide/tilewidth),
           tileheight);

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a tile */
           dpy,
           xscreen,
           &bottom_edge_image,                                   /* bottom edge partial tile */
           0,
           0,
           tilewidth,
           fb_high - tileheight*(fb_high/tileheight));

  getImage_firstTime = TRUE;                                     /* force initialization */
  getImage(depth,                                                /* try to hook up with X shared memory for a tile */
           dpy,
           xscreen,
           &corner_image,                                        /* lower right corner partial tile */
           0,
           0,
           fb_wide - tilewidth*(fb_wide/tilewidth),
           fb_high - tileheight*(fb_high/tileheight));

  if (XParseColor(dpy,                                           /* set the guarded color */
                  DefaultColormap(dpy, xscreen),
                  transparent_color_string,
                  &temp_color_spec)) {

    transparent_color[2] = temp_color_spec.red >> 8;
    transparent_color[1] = temp_color_spec.green >> 8;
    transparent_color[0] = temp_color_spec.blue >> 8;

  } else {
    fprintf(stderr, "Invalid color spec for -gc option, using default.\n");
  }

  if (XParseColor(dpy,                                           /* set the blocked color */
                  DefaultColormap(dpy, xscreen),
                  opaque_color_string,
                  &temp_color_spec)) {

    opaque_color[2] = temp_color_spec.red >> 8;
    opaque_color[1] = temp_color_spec.green >> 8;
    opaque_color[0] = temp_color_spec.blue >> 8;

  } else {
    fprintf(stderr, "Invalid color spec for -bc option, using default.\n");
  }

  init_commands();                                               /* initialize the command interpreter */

  /* Signal readiness. */

  printf ("GEMS VNC Server ready\n");
  fflush(stdout);

  c = 0;
  for(;;) {                                                      /* the big loop -- we do this until something makes us exit the
                                                                    program */

    /* Process the time-out timer */

    if (screen->rfbClientHead) {                                 /* any clients connected? */
      maxMsecsToConnect = 1<<16;                                 /* yes, reset time-out to "a *VERY* long time" :-) */
    } else {
      maxMsecsToConnect -= screen->rfbDeferUpdateTime;           /* no, count down the time-out timer */
      if (maxMsecsToConnect < 0) {                               /* did it time out? */
        fprintf(stderr, "Maximum time to connect reached. Exiting.\n"); /* yes, bellyache about it */
        XTestDiscard(dpy);                                       /* throw away any pending requests -- who cares anyway? */
        exit(2);                                                 /* and quit */
      }
    }

    for (c = 0; c < max_input_events_before_update; c++) {       /* process input events from the remote viewer */
      
      rfbProcessEvents(screen, -1);                              /* handle any mouse or keyboard stuff from the remote */
      
      if (shutDownServer) {                                      /* shut the server down if requested */
        free(backupImage);
        free(markupImage);
        rfbScreenCleanup(screen);
        XFree(dpy);
        exit(0);
      }

      if (gotInput == FALSE) {                                   /* stop looking for input events if we did not get one */
        break;
      }
    }

    commander();                                                 /* process any rectangle commands */

    gotInput = FALSE;                                            /* clear flag for next time */
    c = 0;                                                       /* reset video update timer */

    checkForImageUpdates();                                      /* check for any changes to the framebuffer and relay them to the */
  }
}