/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXAGENT, NX protocol compression and NX extensions to this software    */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE which comes in the source       */
/* distribution.                                                          */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

/*

Copyright 1993 by Davor Matic

Permission to use, copy, modify, distribute, and sell this software
and its documentation for any purpose is hereby granted without fee,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation.  Davor Matic makes no representations about
the suitability of this software for any purpose.  It is provided "as
is" without express or implied warranty.

*/

#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdbool.h>

#include <nx-X11/X.h>
#include <nx-X11/Xproto.h>
#include "screenint.h"
#include "input.h"
#include "misc.h"
#include "scrnintstr.h"
#include "servermd.h"
#include "windowstr.h"
#include "dixstruct.h"

#ifdef WATCH
#include "unistd.h"
#endif

#include <nx/NXalert.h>

#include "Agent.h"
#include "Display.h"
#include "Visual.h"
#include "Options.h"
#include "Error.h"
#include "Init.h"
#include "Args.h"
#include "Image.h"
#include "Utils.h"

#define Pixmap XlibPixmap
#include "Icons.h"
#undef Pixmap

#include "Render.h"
#include "Font.h"
#include "Reconnect.h"
#include "Events.h"
#include "Dialog.h"
#include "Client.h"
#include "Splash.h"
#include "Screen.h"
#include "Handlers.h"
#include "Split.h"

#include <nx/NX.h>
#include "compext/Compext.h"

#include NXAGENT_ICON_NAME
#include X2GOAGENT_ICON_NAME

/*
 * Set here the required log level.
 */

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG
#undef  WATCH

Display *nxagentDisplay = NULL;
XVisualInfo *nxagentVisuals = NULL;
Bool nxagentTrue24 = False;

int nxagentNumVisuals;
int nxagentXConnectionNumber;

int nxagentIOErrorHandler(Display *disp);

static Bool nxagentDisplayInfoSaved = False;
static Display *nxagentDisplayBackup = NULL;
static XlibGC nxagentBitmapGCBackup = NULL;
static XVisualInfo *nxagentVisualsRecBackup;
static int nxagentNumVisualsRecBackup;
static int nxagentNumDefaultColormapsRecBackup;
static int *nxagentDepthsRecBackup;
static int nxagentNumDepthsRecBackup;
static int nxagentDefaultDepthRecBackup;
static int nxagentDisplayWidthRecBackup;
static int nxagentDisplayHeightRecBackup;
static Bool nxagentRenderEnableRecBackup;
static Bool *nxagentVisualHasBeenIgnored;

static enum
{
  NOTHING = 0,
  OPENED,
  GOT_VISUAL_INFO,
  ALLOC_DEF_COLORMAP,
  GOT_DEPTH_LIST,
  GOT_PIXMAP_FORMAT_LIST,
  EVERYTHING_DONE
} reconnectDisplayState;

int nxagentDefaultVisualIndex;
Colormap *nxagentDefaultColormaps = NULL;
int nxagentNumDefaultColormaps;
int *nxagentDepths = NULL;
int nxagentNumDepths;
XPixmapFormatValues *nxagentPixmapFormats = NULL;
XPixmapFormatValues *nxagentRemotePixmapFormats = NULL;
int nxagentNumPixmapFormats;
int nxagentRemoteNumPixmapFormats;
Pixel nxagentBlackPixel;
Pixel nxagentWhitePixel;
Drawable nxagentDefaultDrawables[MAXDEPTH + 1];
Pixmap nxagentScreenSaverPixmap;

/*
 * Also used in Cursor.c. There are huge problems using GC
 * definition. This is to be reworked.
 */

XlibGC nxagentBitmapGC;

#ifdef NX_CONFINE_WINDOW
/*
 * The "confine" window is used in the nxagentConstrainCursor
 * procedure. We are currently overriding the original Xnest
 * behaviour. It is unclear what this window is used for.
 */

Window nxagentConfineWindow;
#endif

Pixmap nxagentIconPixmap;
Pixmap nxagentIconShape;
Bool useXpmIcon = False;

Bool nxagentMakeIcon(Display *disp, Pixmap *nxIcon, Pixmap *nxMask);


static void nxagentInitVisuals(void);
static void nxagentSetDefaultVisual(void);
static void nxagentInitDepths(void);
static void nxagentInitPixmapFormats(void);

static int nxagentCheckForDefaultDepthCompatibility(void);
static int nxagentCheckForDepthsCompatibility(void);
static int nxagentCheckForPixmapFormatsCompatibility(void);
static int nxagentInitAndCheckVisuals(int flexibility);
static int nxagentCheckForColormapsCompatibility(int flexibility);

/*
 * Save Internal implementation Also called in Reconnect.c.
 */

Display *nxagentInternalOpenDisplay(char *disp);

#ifdef NXAGENT_TIMESTAMP
unsigned long startTime;
#endif

/*
 * This is located in connection.c.
 */

extern void RejectWellKnownSockets(void);
extern Bool nxagentReportWindowIds;

int nxagentServerOrder(void)
{
  int whichbyte = 1;

  if (*((char *) &whichbyte))
      return LSBFirst;

  return MSBFirst;
}

/*
 * FIXME: This error handler is not printing anything in the session
 * log. This is OK once the session is started, because the error is
 * handled by the other layers, but not before that point, as the
 * agent would die without giving any feedback to the user (or, worse,
 * to the NX server). We should check how many requests have been
 * handled for this display and print a message if the display dies
 * before the session is up and running.
 */

/*
 * FIXME: This should be moved to Error.c, The other handlers should
 * be probably moved to Handlers.c.
 */

int nxagentIOErrorHandler(Display *disp)
{
  #ifdef TEST
  fprintf(stderr, "nxagentIOErrorHandler: Got I/O error with nxagentException.ioError [%d].\n",
              nxagentException.ioError);
  #endif

  nxagentException.ioError++;

  #ifdef TEST
  fprintf(stderr, "nxagentIOErrorHandler: Set nxagentException.ioError to [%d].\n",
              nxagentException.ioError);
  #endif

  return 1;
}

/*
 * Force a shutdown of any connection attempt while connecting to the
 * remote display.  This is needed to avoid a hang up in case of
 * loopback connections to our own listening sockets.
 */

static void nxagentRejectConnection(int signal)
{
  #ifdef TEST
  fprintf(stderr, "nxagentRejectConnection: Going to reject client connections.\n");
  #endif

  RejectWellKnownSockets();

  #ifdef TEST
  fprintf(stderr, "nxagentRejectConnection: Setting new alarm to 5 seconds from now.\n");
  #endif

  /*
   * A further timeout is unlikely to happen in the case of loopback
   * connections.
   */

  alarm(5);
}

/*
 * Ignore the signal if the NX transport is not running.
 */

static void nxagentSigusrHandler(int signal)
{
  #ifdef TEST
  fprintf(stderr, "nxagentSigusrHandler: Nothing to do with signal [%d].\n",
              signal);
  #endif
}

static void nxagentSighupHandler(int signal)
{
  #ifdef TEST
  fprintf(stderr, "nxagentSighupHandler: Handling signal with state [%s] transport [%d] server "
              "generation [%ld].\n", DECODE_SESSION_STATE(nxagentSessionState),
                  NXTransRunning(NX_FD_ANY), serverGeneration);
  #endif

  if (signal != SIGHUP)
  {
    #ifdef PANIC
    fprintf(stderr, "nxagentSighupHandler: PANIC! Invalid signal [%d] received in state [%s].\n",
                signal, DECODE_SESSION_STATE(nxagentSessionState));
    #endif

    return;
  }

  if (dispatchException & DE_TERMINATE)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentSighupHandler: Ignoring the signal while terminating the session.\n");
    #endif

    return;
  }
  else if (nxagentSessionState == SESSION_UP)
  {
    if (nxagentOption(Persistent))
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSighupHandler: Handling the signal by disconnecting the agent.\n");
      #endif

      nxagentException.sigHup++;
    }
    else
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSighupHandler: Ignoring the signal with persistency disabled.\n");
      #endif
    }

    return;
  }
  else if (nxagentSessionState == SESSION_STARTING)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentSighupHandler: Handling the signal by aborting the session.\n");
    #endif

    nxagentException.sigHup++;

    return;
  }
  else if (nxagentSessionState == SESSION_DOWN)
  {
    if (NXTransRunning(NX_FD_ANY) == 1)
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSighupHandler: Handling the signal by aborting the reconnection.\n");
      #endif
    }
    else
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSighupHandler: Handling the signal by resuming the session.\n");
      #endif
    }

    nxagentException.sigHup++;

    return;
  }

  #ifdef WARNING
  fprintf(stderr, "nxagentSighupHandler: WARNING! Ignoring the signal in state [%s].\n",
              DECODE_SESSION_STATE(nxagentSessionState));
  #endif
}

static void nxagentSigchldHandler(int signal)
{
  int pid = 0;
  int status;

  #ifdef TEST
  fprintf(stderr, "nxagentSigchldHandler: Going to check the children processes.\n");
  #endif

  int options = WNOHANG | WUNTRACED;

  /*
   * Try with the pid of the dialog process.  Leave the other children
   * unaffected.
   */

  if (nxagentRootlessDialogPid)
  {
    pid = waitpid(nxagentRootlessDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (Rootless dialog).\n",
                  nxagentRootlessDialogPid);
      #endif

      pid = nxagentRootlessDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentPulldownDialogPid)
  {
    pid = waitpid(nxagentPulldownDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (Pulldown dialog).\n",
                  nxagentPulldownDialogPid);
      #endif

      pid = nxagentPulldownDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentKillDialogPid)
  {
    pid = waitpid(nxagentKillDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (Kill dialog).\n",
                  nxagentKillDialogPid);
      #endif

      pid = nxagentKillDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentSuspendDialogPid)
  {
    pid = waitpid(nxagentSuspendDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (Suspend dialog).\n",
                  nxagentSuspendDialogPid);
      #endif

      pid = nxagentSuspendDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentFontsReplacementDialogPid)
  {
    pid = waitpid(nxagentFontsReplacementDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (Fonts replacement).\n",
                  nxagentFontsReplacementDialogPid);
      #endif

      pid = nxagentFontsReplacementDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentEnableRandRModeDialogPid)
  {
    pid = waitpid(nxagentEnableRandRModeDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (EnableRandRMode dialog).\n",
                  nxagentEnableRandRModeDialogPid);
      #endif

      pid = nxagentEnableRandRModeDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentDisableRandRModeDialogPid)
  {
    pid = waitpid(nxagentDisableRandRModeDialogPid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (DisableRandRMode dialog).\n",
                  nxagentDisableRandRModeDialogPid);
      #endif

      pid = nxagentDisableRandRModeDialogPid = 0;
    }
  }

  if (pid == 0 && nxagentEnableDeferModePid)
  {
    pid = waitpid(nxagentEnableDeferModePid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (EnableDeferMode dialog).\n",
                  nxagentEnableDeferModePid);
      #endif

      pid = nxagentEnableDeferModePid = 0;
    }
  }

  if (pid == 0 && nxagentDisableDeferModePid)
  {
    pid = waitpid(nxagentDisableDeferModePid, &status, options);

    if (pid == -1 && errno == ECHILD)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Got ECHILD waiting for child %d (DisableDeferMode dialog).\n",
                  nxagentDisableDeferModePid);
      #endif

      pid = nxagentDisableDeferModePid = 0;
    }
  }

  if (pid == -1)
  {
    FatalError("Got error '%s' waiting for the child.\n", strerror(errno));
  }

  if (pid > 0)
  {
    if (WIFSTOPPED(status))
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentSigchldHandler: Child process [%d] was stopped "
                  "with signal [%d].\n", pid, (WSTOPSIG(status)));
      #endif
    }
    else
    {
      #ifdef TEST

      if (WIFEXITED(status))
      {
        fprintf(stderr, "nxagentSigchldHandler: Child process [%d] exited "
                    "with status [%d].\n", pid, (WEXITSTATUS(status)));
      }
      else if (WIFSIGNALED(status))
      {
        fprintf(stderr, "nxagentSigchldHandler: Child process [%d] died "
                    "because of signal [%d].\n", pid, (WTERMSIG(status)));
      }

      #endif

      nxagentResetDialog(pid);
    }
  }
  else if (pid == 0)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentSigchldHandler: Forwarding the signal to the NX transport.\n");
    #endif

    NXTransSignal(SIGCHLD, NX_SIGNAL_RAISE);
  }

  return;
}

Display *nxagentInternalOpenDisplay(char *disp)
{
  /*
   * Stop the smart schedule timer since it uses SIGALRM as we do.
   */

  nxagentStopTimer();

  /*
   * Install the handler rejecting a possible loopback connection.
   */
/*
FIXME: Should print a warning if the user tries to let the agent
       impersonate the same display as the display where the agent is
       supposed to connect.  We actually handle this by means of
       RejectWellKnownSockets() but without giving a friendly
       explanation for the error to the user.
*/

  struct sigaction newAction = {
    .sa_handler = nxagentRejectConnection
  };

  sigfillset(&newAction.sa_mask);

  newAction.sa_flags = 0;

  int result;
  struct sigaction oldAction;

  while (((result = sigaction(SIGALRM, &newAction,
             &oldAction)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set alarm for rejecting connections.");
  }

  alarm(10);

  #ifdef TEST
  fprintf(stderr, "nxagentInternalOpenDisplay: Going to open the display [%s].\n",
              disp);
  #endif

  Display *newDisplay = XOpenDisplay(disp);

  alarm(0);

  while (((result = sigaction(SIGALRM, &oldAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't restore alarm for rejecting connections.");
  }

  #ifdef TEST
  fprintf(stderr, "nxagentInternalOpenDisplay: Setting the NX flush policy to immediate.\n");
  #endif

  NXSetDisplayPolicy(nxagentDisplay, NXPolicyImmediate);

  #ifdef TEST
  fprintf(stderr, "nxagentInternalOpenDisplay: Function returned display at [%p].\n",
              (void *) newDisplay);
  #endif

  return newDisplay;
}

static void nxagentDisplayBlockHandler(Display *disp, int reason)
{
  if (nxagentDisplay != NULL)
  {
    /*
     * Don't allow the smart schedule to interrupt the agent while
     * waiting for the remote display.
     */

    #ifdef DEBUG
    fprintf(stderr, "nxagentDisplayBlockHandler: BLOCK! Stopping the smart schedule timer.\n");
    #endif

    nxagentStopTimer();

    if (reason == NXBlockRead)
    {
      #ifdef DEBUG
      fprintf(stderr, "nxagentDisplayBlockHandler: BLOCK! Display is blocking for [read].\n");
      #endif
    }
    else
    {
      #ifdef DEBUG
      fprintf(stderr, "nxagentDisplayBlockHandler: BLOCK! Display is blocking for [write].\n");
      #endif

      nxagentBlocking = True;

      if (!SmartScheduleSignalEnable)
      {

        /*
         * Let the dispatch attend the next client.
         */

        #ifdef DEBUG
        fprintf(stderr, "nxagentDisplayBlockHandler: BLOCK! Yielding with agent blocked.\n");
        #endif

        nxagentDispatch.start = GetTimeInMillis();

        nxagentDispatch.in  = nxagentBytesIn;
        nxagentDispatch.out = nxagentBytesOut;

      }

      /*
       * Give a chance to the next client.
       */

      isItTimeToYield = 1;
    }
  }
}

static void nxagentDisplayWriteHandler(Display *disp, int length)
{
  if (nxagentDisplay != NULL)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentDisplayWriteHandler: WRITE! Called with [%d] bytes written.\n",
                length);
    #endif

    /*
     * Notify the dispatch handler.
     */

    nxagentDispatchHandler(NULL, 0, length);

    if (nxagentOption(LinkType) == LINK_TYPE_NONE)
    {
      nxagentFlush = GetTimeInMillis();
    }
  }
}

static CARD32 nxagentRateTime = 5000;
static CARD32 nxagentLastTime;
static unsigned int nxagentRate = 0;

int nxagentGetDataRate(void)
{
  return nxagentRate;
}

static void nxagentDisplayFlushHandler(Display *disp, int length)
{
  if (nxagentDisplay != NULL)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentDisplayFlushHandler: FLUSH! Called with [%d] bytes flushed.\n",
                length);
    #endif

    nxagentCongestion = NXDisplayCongestion(nxagentDisplay);

    #ifdef TEST
    fprintf(stderr, "nxagentDisplayFlushHandler: FLUSH! Current congestion level is [%d].\n",
                nxagentCongestion);
    #endif

    if (nxagentOption(LinkType) != LINK_TYPE_NONE)
    {
      nxagentFlush = GetTimeInMillis();

      CARD32 time = nxagentFlush - nxagentLastTime;

      if (time < nxagentRateTime)
      {
        nxagentRate = ((nxagentRate * (nxagentRateTime - time) +
                          length) * 1000) / nxagentRateTime;
      }
      else
      {
        nxagentRate = (length * 1000) / nxagentRateTime;
      }

      nxagentLastTime = nxagentFlush;
    }
  }
}

/*
 * From the changelog for nx-X11-3.0.0-4: "Added the
 * _NXDisplayErrorPredicate function in XlibInt.c. It is actually a
 * pointer to a function called whenever Xlib is going to perform a
 * network operation. If the function returns true, the call will be
 * aborted and Xlib will return the control to the application. It is
 * up to the application to set the XlibDisplayIO- Error flag after
 * the _NXDisplayErrorPredicate returns true. The function can be used
 * to activate additional checks, besides the normal failures detected
 * by Xlib on the display socket. For example, the application can set
 * the function to verify if an interrupt was received or if any other
 * event occurred mandating the end of the session."
 */
static int nxagentDisplayErrorPredicate(Display *disp, int error)
{
  #ifdef TEST
  fprintf(stderr, "nxagentDisplayErrorPredicate: CHECK! Error is [%d] with [%d][%d][%d][%d][%d].\n",
              ((error == 1) || (dispatchException & DE_RESET) != 0 ||
                  (dispatchException & DE_TERMINATE) != 0 || nxagentException.sigHup > 0 ||
                      nxagentException.ioError > 0), error, (dispatchException & DE_RESET) != 0,
                          (dispatchException & DE_TERMINATE), nxagentException.sigHup > 0,
                              nxagentException.ioError > 0);
  #endif

  if (error == 0)
  {
    if ((dispatchException & DE_RESET) != 0 ||
            (dispatchException & DE_TERMINATE))
    {
      return 1;
    }
    else if (nxagentException.sigHup > 0 ||
                 nxagentException.ioError > 0)
    {
      NXForceDisplayError(disp);

      return 1;
    }
  }

  return error;
}

void nxagentInstallDisplayHandlers(void)
{
  /*
   * If the display was already opened, be sure all structures are
   * freed.
   */

  nxagentResetDisplayHandlers();

  /*
   * We want the Xlib I/O error handler to return, instead of quitting
   * the application. Using setjmp()/longjmp() leaves the door open to
   * unexpected bugs when dealing with interaction with the other X
   * server layers.
   */

  NXHandleDisplayError(1);

  NXSetDisplayBlockHandler(nxagentDisplayBlockHandler);

  NXSetDisplayWriteHandler(nxagentDisplayWriteHandler);

  NXSetDisplayFlushHandler(nxagentDisplayFlushHandler, NULL);

  /*
   * Override the default Xlib error handler.
   */

  XSetIOErrorHandler(nxagentIOErrorHandler);

  /*
   * Let Xlib become aware of our interrupts. In theory we don't need
   * to have the error handler installed during the normal operations
   * and could simply let the dispatcher handle the interrupts. In
   * practice it's better to have Xlib invalidating the display as
   * soon as possible rather than incurring in the risk of entering a
   * loop that doesn't care checking the display errors explicitly.
   */

  #ifdef TEST
  fprintf(stderr, "nxagentInstallDisplayHandlers: Installing the error function predicate.\n");
  #endif

  NXSetDisplayErrorPredicate(nxagentDisplayErrorPredicate);
}

void nxagentPostInstallDisplayHandlers(void)
{
  /*
   * This is executed after having opened the display, once we know
   * the display address.
   */

  if (nxagentDisplay != NULL)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentPostInstallDisplayHandlers: Initializing the NX display internals.\n");
    #endif

    NXInitDisplay(nxagentDisplay);
/*
FIXME: What is the most appropriate number of elements?

    NXInitCache(nxagentDisplay, 128);
*/
    NXInitCache(nxagentDisplay, 256);

    NXSetDisplayFlushHandler(nxagentDisplayFlushHandler, nxagentDisplay);
  }

  /*
   * Handler for the Xlib protocol errors.
   */

  XSetErrorHandler(nxagentErrorHandler);
}

void nxagentResetDisplayHandlers(void)
{
  if (nxagentDisplay != NULL)
  {
    /*
     * Free the internal nxcompext structures.
     */

    NXResetDisplay(nxagentDisplay);

    /*
     * Remove the display descriptor from the listened sockets.
     */

    nxagentRemoveXConnection();

    /*
     * Restart the suspended clients.
     */

    nxagentWakeupByReconnect();

    nxagentReleaseAllSplits();
  }

  /*
   * Reset the display to a healty state.
   */

  nxagentBuffer     = 0;
  nxagentBlocking   = False;
  nxagentCongestion = 0;

  /*
   * Reset the counter of synchronization requests pending.
   */

  nxagentTokens.soft    = 0;
  nxagentTokens.hard    = 0;
  nxagentTokens.pending = 0;

  /*
   * Reset the current dispatch information.
   */

  nxagentDispatch.client = UNDEFINED;
  nxagentDispatch.in     = 0;
  nxagentDispatch.out    = 0;
  nxagentDispatch.start  = 0;
}

void nxagentInstallSignalHandlers(void)
{
  #ifdef TEST
  fprintf(stderr, "nxagentInstallSignalHandlers: Installing the agent signal handlers.\n");
  #endif

  /*
   * Keep the default X server's handlers for SIGINT and SIGTERM and
   * restore the other signals of interest to our defaults.
   */

  struct sigaction newAction;

  /*
   * By default nxcomp installs its signal handlers.  We need to
   * ensure that SIGUSR1 and SIGUSR2 are ignored if the NX transport
   * is not running.
   */

  newAction.sa_handler = nxagentSigusrHandler;

  sigfillset(&newAction.sa_mask);

  newAction.sa_flags = 0;

  int result;

  while (((result = sigaction(SIGUSR1, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for user signal 1.");
  }

  while (((result = sigaction(SIGUSR2, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for user signal 2.");
  }

  /*
   * Reset the SIGALRM to the default.
   */

  nxagentStopTimer();

  newAction.sa_handler = SIG_DFL;

  sigfillset(&newAction.sa_mask);

  while (((result = sigaction(SIGALRM, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for alarm signal.");
  }

  /*
   * Let the smart schedule set the SIGALRM handler again.
   */

  nxagentInitTimer();

  /*
   * Install our own handler for the SIGHUP.
   */

  newAction.sa_handler = nxagentSighupHandler;

  sigfillset(&newAction.sa_mask);

  newAction.sa_flags = 0;

  while (((result = sigaction(SIGHUP, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for session suspend.");
  }

  /*
   * We need to be notified about our children.
   */

  newAction.sa_handler = nxagentSigchldHandler;

  sigfillset(&newAction.sa_mask);

  newAction.sa_flags = 0;

  while (((result = sigaction(SIGCHLD, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for children.");
  }
}

void nxagentPostInstallSignalHandlers(void)
{
  #ifdef TEST
  fprintf(stderr, "nxagentPostInstallSignalHandlers: Dealing with the proxy signal handlers.\n");
  #endif

  /*
   * Reconfigure our signal handlers to work well with the NX
   * transport.
   *
   * Let our handlers manage the SIGINT and SIGTERM.  The following
   * calls will tell the NX transport to restore the old handlers
   * (those originally installed by us or the X server).
   */

  NXTransSignal(SIGINT,  NX_SIGNAL_DISABLE);
  NXTransSignal(SIGTERM, NX_SIGNAL_DISABLE);

  /*
   * Also tell the proxy to ignore the SIGHUP.
   */

  NXTransSignal(SIGHUP, NX_SIGNAL_DISABLE);

  /*
   * Both the proxy and the agent need to catch their children, so
   * we'll have to send the signal to transport.
   */

  NXTransSignal(SIGCHLD, NX_SIGNAL_DISABLE);

  /*
   * Let the NX transport take care of SIGUSR1 and SIGUSR2.
   */
}

void nxagentResetSignalHandlers(void)
{
  /*
   * Reset the signal handlers to a well known state.
   */

  #ifdef TEST
  fprintf(stderr, "nxagentResetSignalHandlers: Resetting the agent the signal handlers.\n");
  #endif

  /*
   * Reset the SIGALRM to the default.
   */

  nxagentStopTimer();

  struct sigaction newAction = {
    .sa_handler = SIG_DFL
  };

  sigfillset(&newAction.sa_mask);

  int result;
  while (((result = sigaction(SIGALRM, &newAction,
             NULL)) == -1) && (errno == EINTR));

  if (result == -1)
  {
    FatalError("Can't set the handler for alarm signal.");
  }

  /*
   * Let the smart schedule set the SIGALRM handler again.
   */

  nxagentInitTimer();
}

/*
 * currently unused it seems
 */
void nxagentOpenConfineWindow(void)
{
#ifdef NX_CONFINE_WINDOW
  nxagentConfineWindow = XCreateWindow(nxagentDisplay,
                                       DefaultRootWindow(nxagentDisplay),
                                       0, 0, 1, 1, 0, 0,
                                       InputOnly,
                                       CopyFromParent,
                                       0L, NULL);

  if (nxagentReportWindowIds) {
    fprintf(stderr, "NXAGENT_WINDOW_ID: CONFINEMENT_WINDOW,WID:[0x%x]\n",
              nxagentConfineWindow);
  }

  #ifdef DEBUG
  {
    char *winname = NULL;
    if (-1 != asprintf(&winname, "%s Confine", nxagentWindowName))
    {
      Xutf8SetWMProperties(nxagentDisplay, nxagentConfineWindow,
                               winname, winname, NULL , 0 , NULL, NULL, NULL);
      SAFE_free(winname);
    }
  }
  #endif

  #ifdef TEST
  fprintf(stderr, "%s: Created agent's confine window with id [0x%x].\n", __func__, nxagentConfineWindow);
  #endif
#endif
}

void nxagentOpenDisplay(int argc, char *argv[])
{
  if (!nxagentDoFullGeneration)
    return;

  #ifdef NXAGENT_TIMESTAMP
  startTime = GetTimeInMillis();
  fprintf(stderr, "Display: Opening the display on real X server with time [%d] ms.\n",
          GetTimeInMillis() - startTime);
  #endif

  /*
   * Initialize the reconnector only in the case of persistent
   * sessions.
   */

  if (nxagentOption(Persistent))
  {
    nxagentInitReconnector();
  }

  if (*nxagentDisplayName == '\0')
  {
    snprintf(nxagentDisplayName, NXAGENTDISPLAYNAMELENGTH, "%s", XDisplayName(NULL));
  }

  nxagentCloseDisplay();

  nxagentInstallSignalHandlers();

  nxagentInstallDisplayHandlers();

  nxagentDisplay = nxagentInternalOpenDisplay(nxagentDisplayName);

  nxagentPostInstallSignalHandlers();

  nxagentPostInstallDisplayHandlers();

  if (nxagentDisplay == NULL)
  {
/*
FIXME: The agent should never exit the program with a FatalError() but
       rather use a specific function that may eventually call
       FatalError() on its turn.
*/
    FatalError("Unable to open display '%s'.\n", nxagentDisplayName);
  }

  if (nxagentSynchronize)
    XSynchronize(nxagentDisplay, True);

  nxagentXConnectionNumber = XConnectionNumber(nxagentDisplay);

  #ifdef TEST
  fprintf(stderr, "nxagentOpenDisplay: Display image order is [%d] bitmap order is [%d].\n",
              ImageByteOrder(nxagentDisplay), BitmapBitOrder(nxagentDisplay));

  fprintf(stderr, "nxagentOpenDisplay: Display scanline unit is [%d] scanline pad is [%d].\n",
              BitmapUnit(nxagentDisplay), BitmapPad(nxagentDisplay));
  #endif

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 1.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
#1   U  1	1	256 bits (0 KB) ->	150 bits (0 KB) ->	256/1 -> 150/1	= 1.707:1
#20     1	1	119104 bits (15 KB) ->	28 bits (0 KB) ->	119104/1 -> 28/1	= 4253.714:1
#98     2		512 bits (0 KB) ->	84 bits (0 KB) ->	256/1 -> 42/1	= 6.095:1
*/

  sleep(60);

  #endif

  #ifdef NXAGENT_TIMESTAMP
  fprintf(stderr, "Display: Display on real X server opened with time [%d] ms.\n",
          GetTimeInMillis() - startTime);
  #endif

  nxagentUseNXTrans =
      nxagentPostProcessArgs(nxagentDisplayName, nxagentDisplay,
                                 DefaultScreenOfDisplay(nxagentDisplay));

  /*
   * Processing the arguments all the timeouts have been set. Now we
   * have to change the screen-saver timeout.
   */

  nxagentSetScreenSaverTime();

  nxagentInitVisuals();

  nxagentNumDefaultColormaps = nxagentNumVisuals;
  nxagentDefaultColormaps = (Colormap *)malloc(nxagentNumDefaultColormaps *
                                             sizeof(Colormap));

  for (int i = 0; i < nxagentNumDefaultColormaps; i++)
  {
    nxagentDefaultColormaps[i] = XCreateColormap(nxagentDisplay,
                                               DefaultRootWindow(nxagentDisplay),
                                               nxagentVisuals[i].visual,
                                               AllocNone);
  }

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 4.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
N/A
*/

  sleep(30);

  #endif

  nxagentBlackPixel = BlackPixel(nxagentDisplay, DefaultScreen(nxagentDisplay));
  nxagentWhitePixel = WhitePixel(nxagentDisplay, DefaultScreen(nxagentDisplay));

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 5.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
N/A
*/

  sleep(30);

  #endif

  /*
   * Initialize the agent's event mask that will be requested for the
   * root and all the top level windows. If the nested window is a
   * child of an existing window, we will need to receive
   * StructureNotify events. If we are going to manage the changes in
   * root window's visibility we'll also need VisibilityChange events.
   */

/*
FIXME: Use of nxagentParentWindow is strongly deprecated.  We need
       also to clarify which events are selected in the different
       operating modes.
*/

  nxagentInitDefaultEventMask();

  /*
   * Initialize the pixmap depths and formats.
   */

  nxagentInitDepths();

  nxagentInitPixmapFormats();
  (void) nxagentCheckForPixmapFormatsCompatibility();

  /*
   * Create a pixmap for each depth matching the local supported
   * formats with format available on the remote display.
   */

  nxagentSetDefaultDrawables();

  #ifdef RENDER
  if (nxagentRenderEnable)
  {
    nxagentRenderExtensionInit();
  }
  #endif

  /*
   * This GC is referenced in Cursor.c. It can be probably removed.
   */

  nxagentBitmapGC = XCreateGC(nxagentDisplay, nxagentDefaultDrawables[1], 0L, NULL);

  /*
   * Note that this "confine window" is useless at the moment as we
   * reimplement nxagentConstrainCursor() to skip the "constrain"
   * stuff.
   */

  #ifdef TEST
  fprintf(stderr, "nxagentOpenDisplay: Going to create agent's confine window.\n");
  #endif

  nxagentOpenConfineWindow();

  if (!(nxagentUserGeometry.flag & XValue))
  {
    nxagentChangeOption(RootX, 0);
  }

  if (!(nxagentUserGeometry.flag & YValue))
  {
    nxagentChangeOption(RootY, 0);
  }

  if (nxagentParentWindow == 0)
  {
    if (!(nxagentUserGeometry.flag & WidthValue))
    {
      if (nxagentOption(Fullscreen) || nxagentOption(Rootless))
      {
        nxagentChangeOption(RootWidth, DisplayWidth(nxagentDisplay, DefaultScreen(nxagentDisplay)));
      }
      else
      {
        nxagentChangeOption(RootWidth, 3 * DisplayWidth(nxagentDisplay,
                                           DefaultScreen(nxagentDisplay)) / 4);
      }
    }

    if (!(nxagentUserGeometry.flag & HeightValue))
    {
      if (nxagentOption(Fullscreen) || nxagentOption(Rootless))
      {
        nxagentChangeOption(RootHeight, DisplayHeight(nxagentDisplay, DefaultScreen(nxagentDisplay)));
      }
      else
      {
        nxagentChangeOption(RootHeight, 3 * DisplayHeight(nxagentDisplay,
                                            DefaultScreen(nxagentDisplay)) / 4);
      }
    }
  }

  if (!nxagentUserBorderWidth)
  {
    nxagentChangeOption(BorderWidth, 1);
  }

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 5.1.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
N/A
*/

  sleep(30);

  #endif

  useXpmIcon = nxagentMakeIcon(nxagentDisplay, &nxagentIconPixmap, &nxagentIconShape);

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 5.2.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
#84     2		512 bits (0 KB) ->	76 bits (0 KB) ->	256/1 -> 38/1	= 6.737:1
*/

  sleep(30);

  #endif

  #ifdef WATCH

  fprintf(stderr, "nxagentOpenDisplay: Watchpoint 6.\n");

/*
Reply   Total	Cached	Bits In			Bits Out		Bits/Reply	  Ratio
------- -----	------	-------			--------		----------	  -----
N/A
*/

  sleep(30);

  #endif

  #ifdef NXAGENT_TIMESTAMP
  fprintf(stderr, "Display: Open of the display finished with time [%d] ms.\n",
          GetTimeInMillis() - startTime);
  #endif

  if (nxagentOption(Persistent))
  {
    reconnectDisplayState = EVERYTHING_DONE;
  }
}

void nxagentSetDefaultVisual(void)
{
  if (nxagentUserDefaultClass || nxagentUserDefaultDepth)
  {
    nxagentDefaultVisualIndex = UNDEFINED;

    for (int i = 0; i < nxagentNumVisuals; i++)
    {
      if ((!nxagentUserDefaultClass ||
           nxagentVisuals[i].class == nxagentDefaultClass)
          &&
          (!nxagentUserDefaultDepth ||
           nxagentVisuals[i].depth == nxagentDefaultDepth))
      {
        nxagentDefaultVisualIndex = i;
        break;
      }
    }

    if (nxagentDefaultVisualIndex == UNDEFINED)
    {
      FatalError("Unable to find desired default visual.\n");
    }
  }
  else
  {
    XVisualInfo vi = {
      .visualid = XVisualIDFromVisual(DefaultVisual(nxagentDisplay,
                                          DefaultScreen(nxagentDisplay)))
    };

    nxagentDefaultVisualIndex = 0;

    for (int i = 0; i < nxagentNumVisuals; i++)
    {
      if (vi.visualid == nxagentVisuals[i].visualid)
      {
        nxagentDefaultVisualIndex = i;
      }
    }
  }
}

void nxagentInitVisuals(void)
{
  long mask = VisualScreenMask;
  XVisualInfo vi = {
    .screen = DefaultScreen(nxagentDisplay),
    .depth = DefaultDepth(nxagentDisplay, DefaultScreen(nxagentDisplay))
  };
  int viNumList;
  XVisualInfo *viList = XGetVisualInfo(nxagentDisplay, mask, &vi, &viNumList);
  nxagentVisuals = (XVisualInfo *) malloc(viNumList * sizeof(XVisualInfo));
  nxagentNumVisuals = 0;

  for (int i = 0; i < viNumList; i++)
  {
    if (viList[i].depth == vi.depth)
    {
      if (nxagentVisuals != NULL)
      {
        memcpy(nxagentVisuals + nxagentNumVisuals, viList + i, sizeof(XVisualInfo));
      }

      #ifdef DEBUG
      fprintf(stderr, "nxagentInitVisuals: Visual:\n");
      fprintf(stderr, "\tdepth = %d\n", nxagentVisuals[nxagentNumVisuals].depth);
      fprintf(stderr, "\tclass = %d\n", nxagentVisuals[nxagentNumVisuals].class);
      fprintf(stderr, "\tmask = (%lu,%lu,%lu)\n",
                  nxagentVisuals[nxagentNumVisuals].red_mask,
                      nxagentVisuals[nxagentNumVisuals].green_mask,
                          nxagentVisuals[nxagentNumVisuals].blue_mask);
      fprintf(stderr, "\tcolormap size = %d\n", nxagentVisuals[nxagentNumVisuals].colormap_size);
      fprintf(stderr, "\tbits_per_rgb = %d\n", nxagentVisuals[nxagentNumVisuals].bits_per_rgb);
      #endif

      nxagentNumVisuals++;
    }
  }

  if (nxagentVisuals != NULL)
  {
    XVisualInfo *new = (XVisualInfo *) realloc(nxagentVisuals,
                                                   nxagentNumVisuals * sizeof(XVisualInfo));
    /* nxagentVisuals being NULL is covered below */
    if (new)
      nxagentVisuals = new;
    else
      SAFE_free(nxagentVisuals);
  }

  SAFE_XFree(viList);

  if (nxagentNumVisuals == 0 || nxagentVisuals == NULL)
  {
    FatalError("Unable to find any visuals.\n");
  }

  nxagentSetDefaultVisual();
}

void nxagentInitDepths(void)
{
  nxagentDepths = XListDepths(nxagentDisplay, DefaultScreen(nxagentDisplay),
                                  &nxagentNumDepths);

  if (nxagentDepths == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "nxagentInitDepths: PANIC! Failed to get available depths.\n");
    #endif

    FatalError("Failed to get available depths and pixmap formats.");
  }
  #ifdef TEST
  else
  {
    fprintf(stderr, "nxagentInitDepths: Got [%d] available depths:\n",
                nxagentNumDepths);

    for (int i = 0; i < nxagentNumDepths; i++)
    {
      fprintf(stderr, " [%d]", nxagentDepths[i]);
    }

    fprintf(stderr, ".\n");
  }
  #endif
}

void nxagentInitPixmapFormats(void)
{
  /*
   * Formats are created with no care of which are supported on the
   * real display. Creating only formats supported by the remote end
   * makes troublesome handling migration of session from a display to
   * another.
   */

  nxagentNumPixmapFormats = 0;
  nxagentRemoteNumPixmapFormats = 0;

/*
XXX: Some X server doesn't list 1 among available depths...
*/

  nxagentPixmapFormats = malloc((nxagentNumDepths + 1) * sizeof(XPixmapFormatValues));

  for (int i = 1; i <= MAXDEPTH; i++)
  {
    int depth = 0;

    if (i == 1)
    {
      depth = 1;
    }
    else
    {
      for (int j = 0; j < nxagentNumDepths; j++)
      {
        if (nxagentDepths[j] == i)
        {
          depth = i;
          break;
        }
      }
    }

    if (depth != 0)
    {
      if (nxagentNumPixmapFormats >= MAXFORMATS)
      {
        FatalError("nxagentInitPixmapFormats: MAXFORMATS is too small for this server.\n");
      }

      nxagentPixmapFormats[nxagentNumPixmapFormats].depth = depth;
      nxagentPixmapFormats[nxagentNumPixmapFormats].bits_per_pixel = nxagentBitsPerPixel(depth);
      nxagentPixmapFormats[nxagentNumPixmapFormats].scanline_pad = BITMAP_SCANLINE_PAD;

      #ifdef TEST
      fprintf(stderr, "nxagentInitPixmapFormats: Set format [%d] to depth [%d] "
                  "bits per pixel [%d] scanline pad [%d].\n", nxagentNumPixmapFormats,
                      depth, nxagentPixmapFormats[nxagentNumPixmapFormats].bits_per_pixel,
                          BITMAP_SCANLINE_PAD);
      #endif

      nxagentNumPixmapFormats++;
    }
  }

  /*
   * we need to filter the list of remote pixmap formats by our
   * supported depths, just like above. If we do not perform this step
   * nxagentCheckForPixmapFormatsCompatibility will fail when
   * tolerance is "strict" (the default). This becomes evident when
   * Xephyr or Xnest are used as the real X server. They normally show
   * only two supported depths but 7 supported pixmap formats (which
   * could be a bug there).
   */

  int tmpnum = 0;
  XPixmapFormatValues *tmp = XListPixmapFormats(nxagentDisplay, &tmpnum);

  if (tmp == NULL)
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentInitPixmapFormats: WARNING! Failed to get available remote pixmap formats.\n");
    #endif

    nxagentRemotePixmapFormats = NULL;
  }
  else
  {
    #ifdef TEST
    fprintf(stderr, "nxagentInitPixmapFormats: Got [%d] available remote pixmap formats:\n",
                tmpnum);

    for (int i = 0; i < tmpnum; i++)
    {
      fprintf(stderr, "nxagentInitPixmapFormats: Found remote pixmap format [%d]: depth [%d] "
                  "bits_per_pixel [%d] scanline_pad [%d].\n", i, tmp[i].depth,
                      tmp[i].bits_per_pixel, tmp[i].scanline_pad);

    }
    #endif

    SAFE_XFree(tmp);

    nxagentRemotePixmapFormats = malloc((nxagentNumDepths + 1) * sizeof(XPixmapFormatValues));

    for (int i = 1; i <= MAXDEPTH; i++)
    {
      int depth = 0;

      if (i == 1)
      {
        depth = 1;
      }
      else
      {
        for (int j = 0; j < nxagentNumDepths; j++)
        {
          if (nxagentDepths[j] == i)
          {
            depth = i;
            break;
          }
        }
      }

      if (depth != 0)
      {
        if (nxagentRemoteNumPixmapFormats >= MAXFORMATS)
        {
          FatalError("nxagentInitPixmapFormats: MAXFORMATS is too small for this remote server.\n");
        }

        nxagentRemotePixmapFormats[nxagentRemoteNumPixmapFormats].depth = depth;
        nxagentRemotePixmapFormats[nxagentRemoteNumPixmapFormats].bits_per_pixel = nxagentBitsPerPixel(depth);
        nxagentRemotePixmapFormats[nxagentRemoteNumPixmapFormats].scanline_pad = BITMAP_SCANLINE_PAD;

        #ifdef TEST
        fprintf(stderr, "nxagentInitPixmapFormats: Suitable remote format [%d] to depth [%d] "
                    "bits per pixel [%d] scanline pad [%d].\n", nxagentRemoteNumPixmapFormats,
                        depth, nxagentRemotePixmapFormats[nxagentRemoteNumPixmapFormats].bits_per_pixel,
                            BITMAP_SCANLINE_PAD);
        #endif

        nxagentRemoteNumPixmapFormats++;
      }
    }

    #ifdef TEST
    for (int i = 0; i < nxagentRemoteNumPixmapFormats; i++)
    {
      fprintf(stderr, "nxagentInitPixmapFormats: Remote pixmap format [%d]: depth [%d] "
                  "bits_per_pixel [%d] scanline_pad [%d].\n", i, nxagentRemotePixmapFormats[i].depth,
                      nxagentRemotePixmapFormats[i].bits_per_pixel, nxagentRemotePixmapFormats[i].scanline_pad);
    }
    #endif
  }
}

void nxagentSetDefaultDrawables(void)
{
  for (int i = 0; i <= MAXDEPTH; i++)
  {
    nxagentDefaultDrawables[i] = None;
  }

  for (int i = 0; i < nxagentNumPixmapFormats; i++)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentSetDefaultDrawables: Checking remote pixmap format [%d] with depth [%d] "
                "bits per pixel [%d] scanline pad [%d].\n", i, nxagentPixmapFormats[i].depth,
                    nxagentPixmapFormats[i].bits_per_pixel, nxagentPixmapFormats[i].scanline_pad);
    #endif

    if (nxagentPixmapFormats[i].depth == 24)
    {
      if (nxagentPixmapFormats[i].bits_per_pixel == 24)
      {
        #ifdef TEST
        fprintf(stderr, "nxagentSetDefaultDrawables: WARNING! Assuming remote pixmap "
                    "format [%d] as true 24 bits.\n", i);
        #endif

        nxagentTrue24 = True;
      }
    }

    for (int j = 0; j < nxagentNumDepths; j++)
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSetDefaultDrawables: Checking depth at index [%d] with pixmap depth [%d] "
                  "and display depth [%d].\n", j, nxagentPixmapFormats[i].depth, nxagentDepths[j]);
      #endif

      if ((nxagentPixmapFormats[i].depth == 1 ||
              nxagentPixmapFormats[i].depth == nxagentDepths[j]) &&
                  nxagentDefaultDrawables[nxagentPixmapFormats[i].depth] == None)
      {
        nxagentDefaultDrawables[nxagentPixmapFormats[i].depth] =
            XCreatePixmap(nxagentDisplay, DefaultRootWindow(nxagentDisplay),
                              1, 1, nxagentPixmapFormats[i].depth);

        #ifdef TEST
        fprintf(stderr, "nxagentSetDefaultDrawables: Created default drawable [%lu] for depth [%d].\n",
                    nxagentDefaultDrawables[nxagentPixmapFormats[i].depth], nxagentPixmapFormats[i].depth);
        #endif
      }
    }

    if (nxagentDefaultDrawables[nxagentPixmapFormats[i].depth] == None)
    {
      #ifdef TEST
      fprintf(stderr, "nxagentSetDefaultDrawables: WARNING! Forcing default drawable for depth [%d].\n",
                  nxagentPixmapFormats[i].depth);
      #endif

      nxagentDefaultDrawables[nxagentPixmapFormats[i].depth] =
          XCreatePixmap(nxagentDisplay, DefaultRootWindow(nxagentDisplay),
                            1, 1, nxagentPixmapFormats[i].depth);

      #ifdef TEST
      fprintf(stderr, "nxagentSetDefaultDrawables: Created default drawable [%lu] for depth [%d].\n",
                  nxagentDefaultDrawables[nxagentPixmapFormats[i].depth], nxagentPixmapFormats[i].depth);
      #endif
    }
  }
}

void nxagentCloseDisplay(void)
{
  #ifdef TEST
  fprintf(stderr, "nxagentCloseDisplay: Called with full generation [%d] and display [%p].\n",
              nxagentDoFullGeneration, (void *) nxagentDisplay);
  #endif

  if (!nxagentDoFullGeneration ||
          nxagentDisplay == NULL)
  {
    return;
  }

  /*
   * If nxagentDoFullGeneration is true, all the X resources will be
   * destroyed upon closing the display connection, so there is no
   * real need to generate additional traffic
   */

  SAFE_free(nxagentDefaultColormaps);
  SAFE_free(nxagentDepths);
  SAFE_XFree(nxagentVisuals);
  SAFE_XFree(nxagentPixmapFormats);
  SAFE_XFree(nxagentRemotePixmapFormats);

  nxagentFreeFontCache();
/*
FIXME: Is this needed?

  nxagentFreeFontMatchStuff();
*/

  /*
   * Free the image cache. This is useful for detecting memory leaks.
   */

  if (nxagentDisplay != NULL)
  {
    NXFreeCache(nxagentDisplay);
    NXResetDisplay(nxagentDisplay);
  }

  /*
   * Kill all the running dialogs.
   */

  nxagentTerminateDialogs();

  #ifdef TEST
  fprintf(stderr, "nxagentCloseDisplay: Setting the display to NULL.\n");
  #endif

  XCloseDisplay(nxagentDisplay);

  nxagentDisplay = NULL;
}

Bool nxagentMakeIcon(Display *disp, Pixmap *nxIcon, Pixmap *nxMask)
{
  char** agentIconData;

  /*
   * selecting x2go icon when running as X2Go agent
   */
  if (nxagentX2go)
  {
    agentIconData = x2goagentIconData;
  }
  else
  {
    agentIconData = nxagentIconData;
  }

  XlibPixmap IconPixmap;
  XlibPixmap IconShape;
  if (XpmSuccess == XpmCreatePixmapFromData(disp,
                                            DefaultRootWindow(disp),
                                            agentIconData,
                                            &IconPixmap,
                                            &IconShape,
                                            NULL))
  {
    *nxIcon = IconPixmap;
    *nxMask = IconShape;

    return True;
  }
  else
  {
    #ifdef TEST
    fprintf(stderr, "%s: Xpm operation failed.\n", __func__);
    #endif

    return False;
  }
}

Bool nxagentXServerGeometryChanged(void)
{
  return  (WidthOfScreen(DefaultScreenOfDisplay(nxagentDisplay)) !=
               WidthOfScreen(DefaultScreenOfDisplay(nxagentDisplayBackup))) ||
                    (HeightOfScreen(DefaultScreenOfDisplay(nxagentDisplay)) !=
                         HeightOfScreen(DefaultScreenOfDisplay(nxagentDisplayBackup)));
}

void nxagentBackupDisplayInfo(void)
{
  if (nxagentDisplayInfoSaved)
  {
    return;
  }

  /*
   * Since we need the display structure in order to behave correctly
   * when no X connection is available, we must always have a good
   * display record.  It can be discarded only when a new X connection
   * is available, so we store it in order to destroy whenever the
   * reconnection succeeds.
   */

  nxagentDisplayBackup = nxagentDisplay;
  nxagentBitmapGCBackup = nxagentBitmapGC;
  nxagentDepthsRecBackup = nxagentDepths;
  nxagentNumDepthsRecBackup = nxagentNumDepths;
  nxagentNumDefaultColormapsRecBackup = nxagentNumDefaultColormaps;
  nxagentVisualsRecBackup = nxagentVisuals;
  nxagentNumVisualsRecBackup = nxagentNumVisuals;
  SAFE_free(nxagentVisualHasBeenIgnored);
  nxagentVisualHasBeenIgnored = malloc(nxagentNumVisuals * sizeof(Bool));
  nxagentDefaultDepthRecBackup = DefaultDepth(nxagentDisplay, DefaultScreen(nxagentDisplay));
  nxagentDisplayWidthRecBackup = DisplayWidth(nxagentDisplay, DefaultScreen(nxagentDisplay));
  nxagentDisplayHeightRecBackup = DisplayHeight(nxagentDisplay, DefaultScreen(nxagentDisplay));
  nxagentRenderEnableRecBackup = nxagentRenderEnable;

  nxagentDisplayInfoSaved = True;
}

void nxagentCleanupBackupDisplayInfo(void)
{
  SAFE_free(nxagentDepthsRecBackup);
  SAFE_free(nxagentVisualsRecBackup);
  SAFE_free(nxagentVisualHasBeenIgnored);

  nxagentNumDefaultColormapsRecBackup = 0;
  nxagentDefaultDepthRecBackup = 0;
  nxagentDisplayWidthRecBackup = 0;
  nxagentDisplayHeightRecBackup = 0;

  if (nxagentDisplayBackup)
  {
    XCloseDisplay(nxagentDisplayBackup);
    nxagentDisplayBackup = NULL;
  }

  if (nxagentBitmapGCBackup)
  {
    if (nxagentDisplayBackup)
    {
      XFreeGC(nxagentDisplayBackup, nxagentBitmapGCBackup);
    }
    else
    {
      SAFE_free(nxagentBitmapGCBackup);
    }

    nxagentBitmapGCBackup = NULL;
  }

  nxagentDisplayInfoSaved = False;
}

void nxagentDisconnectDisplay(void)
{
  switch (reconnectDisplayState)
  {
    case EVERYTHING_DONE:
      if (nxagentBitmapGC &&
              nxagentBitmapGCBackup &&
                  (nxagentBitmapGC != nxagentBitmapGCBackup))
      {
        XFreeGC(nxagentDisplay, nxagentBitmapGC);
      }

      nxagentBitmapGC = nxagentBitmapGCBackup;
    case GOT_PIXMAP_FORMAT_LIST:
    case GOT_DEPTH_LIST:
    case ALLOC_DEF_COLORMAP:
      if (nxagentDefaultColormaps)
      {
        for (int i = 0; i < nxagentNumDefaultColormaps; i++)
        {
          nxagentDefaultColormaps[i] = None;
        }
      }
    case GOT_VISUAL_INFO:
    case OPENED:
      /*
       * Actually we need the nxagentDisplay structure in order to let
       * the agent go when no X connection is available.
       */

      if (nxagentDisplay &&
              nxagentDisplayBackup &&
                  (nxagentDisplay != nxagentDisplayBackup))
      {
        XCloseDisplay(nxagentDisplay);
      }
    case NOTHING:
      nxagentDisplay = nxagentDisplayBackup;
      break;
    default:
      FatalError("Display is in unknown state. Can't continue.");
  }

  reconnectDisplayState = NOTHING;
}

static int nxagentCheckForDefaultDepthCompatibility(void)
{
  /*
   * Depending on the (reconnect) tolerance checks value, this
   * function checks stricter or looser:
   *   - "Strict" means that the old and new default depth values
   *     must match exactly.
   *   - "Safe" or "Risky" means that the default depth values might differ,
   *     but the new default depth value must be at least as
   *     high as the former default depth value. This is
   *     recommended, because it allows clients with a
   *     higher default depth value to still connect, but
   *     not lose functionality.
   *   - "Bypass" means that all of these checks are essentially
   *     deactivated. This is probably a very bad idea.
   */

  int dDepth = DefaultDepth(nxagentDisplay, DefaultScreen(nxagentDisplay));

  const unsigned int tolerance = nxagentOption(ReconnectTolerance);

  if (ToleranceChecksBypass <= tolerance)
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDefaultDepthCompatibility: WARNING! Not proceeding with any checks, "
                    "because tolerance [%u] higher than or equal [%u]. New default depth value "
                    "is [%d], former default depth value is [%d].\n", tolerance,
            ToleranceChecksBypass, dDepth, nxagentDefaultDepthRecBackup);
    #endif
    return 1;
  }

  if (nxagentDefaultDepthRecBackup == dDepth)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentCheckForDefaultDepthCompatibility: New default depth [%d] "
                "matches with old default depth.\n", dDepth);
    #endif
    return 1;
  }
  else if ((ToleranceChecksSafe <= tolerance) && (nxagentDefaultDepthRecBackup < dDepth))
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDefaultDepthCompatibility: WARNING! New default depth [%d] "
                    "higher than the old default depth [%d] at tolerance [%u].\n", dDepth,
            nxagentDefaultDepthRecBackup, tolerance);
    #endif
    return 1;
  }
  else
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDefaultDepthCompatibility: WARNING! New default depth [%d] "
                "doesn't match with old default depth [%d] at tolerance [%u].\n", dDepth,
            nxagentDefaultDepthRecBackup, tolerance);
    #endif
    return 0;
  }
}

static int nxagentCheckForDepthsCompatibility(void)
{
  /*
   * Depending on the (reconnect) tolerance checks value, this
   * function checks stricter or looser:
   *   - "Strict" means that the number of old and new depths must
   *     match exactly and every old depth value must be
   *     available in the new depth array.
   *   - "Safe" means that the number of depths might diverge,
   *     but all former depth must also be included in the
   *     new depth array. This is recommended, because
   *     it allows clients with more depths to still
   *     connect, but not lose functionality.
   *   - "Risky" means that the new depths array is allowed to be
   *     smaller than the old depths array, but at least
   *     one depth value must be included in both.
   *     This is potentially unsafe.
   *   - "Bypass" or higher means that all of these checks are
   *     essentially deactivated. This is a very bad idea.
   */

  const unsigned int tolerance = nxagentOption(ReconnectTolerance);

  if (ToleranceChecksBypass <= tolerance)
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDepthsCompatibility: WARNING! Not proceeding with any checks, "
                    "because tolerance [%u] higher than or equal [%u]. Number of newly available depths "
                    "is [%d], number of old depths is [%d].\n", tolerance, ToleranceChecksBypass,
            nxagentNumDepths, nxagentNumDepthsRecBackup);
    #endif
    return 1;
  }

  if ((ToleranceChecksStrict == tolerance) && (nxagentNumDepths != nxagentNumDepthsRecBackup))
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDepthsCompatibility: WARNING! No tolerance allowed and "
                    "number of new available depths [%d] doesn't match with number of old "
                    "depths [%d].\n", nxagentNumDepths,
            nxagentNumDepthsRecBackup);
    #endif
    return 0;
  }

  if ((ToleranceChecksSafe == tolerance) && (nxagentNumDepths < nxagentNumDepthsRecBackup))
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDepthsCompatibility: WARNING! Tolerance [%u] not "
                    "high enough and number of new available depths [%d] "
                    "lower than number of old depths [%d].\n", tolerance,
            nxagentNumDepths, nxagentNumDepthsRecBackup);
    #endif
    return 0;
  }

  /*
   * By now the tolerance is either:
   *   - "Strict" and both depth numbers match
   *   - "Safe" and:
   *     o the number of old and new depths matches exactly, or
   *     o the number of old depths is lower than the number
   *       of new depths
   *   - "Risky"
   */

  bool compatible = true;
  bool one_match = false;
  int total_matches = 0;

  /*
   * FIXME: within this loop, we try to match all "new" depths
   *        against the "old" depths. Depending upon the flexibility
   *        value, either all "new" depths must have a corresponding
   *        counterpart in the "old" array, or at least one value
   *        must be included in both.
   *        Is this safe enough though?
   *        Shouldn't we better try to match entries in the "old"
   *        depths array against the "new" depths array, such that
   *        we know that all "old" values are covered by "new"
   *        values? Or is it more important that "new" values are
   *        covered by "old" ones, with potentially more "old"
   *        values lingering around that cannot be displayed by the
   *        connected client?
   *
   * This section probably needs a revisit at some point in time.
   */
  for (int i = 0; i < nxagentNumDepths; ++i)
  {
    bool matched = false;

    for (int j = 0; j < nxagentNumDepthsRecBackup; ++j)
    {
      if (nxagentDepths[i] == nxagentDepthsRecBackup[j])
      {
        matched = true;
        one_match = true;
        ++total_matches;

        break;
      }
    }

    if ((ToleranceChecksRisky > tolerance) && (!matched))
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentCheckForDepthsCompatibility: WARNING! Tolerance [%u] too low and "
                      "failed to match available depth [%d].\n", tolerance, nxagentDepths[i]);
      #endif

      compatible = false;

      break;
    }
  }

  /*
   * At Risky tolerance, only one match is necessary to be "compatible".
   */
  if (ToleranceChecksRisky == tolerance)
  {
    compatible = one_match;
  }

  int ret = (!(!compatible));

  if (compatible)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentCheckForDepthsCompatibility: Internal depths match with "
                "remote depths at tolerance [%u].\n", tolerance);
    #endif

    if (total_matches != nxagentNumDepths)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentCheckForDepthsCompatibility: only some [%d] of the new depths [%d] "
                  "match with old depths [%d] at tolerance [%u].\n", total_matches, nxagentNumDepths,
              nxagentNumDepthsRecBackup, tolerance);
      #endif
    }
  }
  else
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForDepthsCompatibility: WARNING! New available depths [%d] don't match "
                    "with old depths [%d] at tolerance [%u]. Only [%d] depth values matched.\n",
            nxagentNumDepths, nxagentNumDepthsRecBackup, tolerance, total_matches);
    #endif
  }

  return (ret);
}

static int nxagentCheckForPixmapFormatsCompatibility(void)
{
  /*
   * Depending on the (reconnect) tolerance checks value, this
   * function checks stricter or looser:
   *   - "Strict" means that the number of internal and external
   *     pixmap formats must match exactly and every
   *     internal pixmap format must be available in the
   *     external pixmap format array.
   *   - "Safe" means that the number of pixmap formats might
   *     diverge, but all internal pixmap formats must
   *     also be included in the external pixmap formats
   *     array. This is recommended, because it allows
   *     clients with more pixmap formats to still connect,
   *     but not lose functionality.
   *   - "Risky" means that the internal pixmap formats array is
   *     allowed to be smaller than the external pixmap
   *     formats array, but at least one pixmap format must
   *     be included in both. This is potentially unsafe.
   *   - "Bypass" or higher means that all of these checks are
   *     essentially deactivated. This is a very bad idea.
   */

  const unsigned int tolerance = nxagentOption(ReconnectTolerance);

  if (ToleranceChecksBypass <= tolerance)
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: WARNING! Not proceeding with any checks, "
                    "because tolerance [%u] higher than or equal [%u]. Number of internally available "
                    "pixmap formats is [%d], number of externally available pixmap formats is [%d].\n",
            tolerance, ToleranceChecksBypass, nxagentNumPixmapFormats, nxagentRemoteNumPixmapFormats);
    #endif
    return 1;
  }

  if ((ToleranceChecksStrict == tolerance) && (nxagentNumPixmapFormats != nxagentRemoteNumPixmapFormats))
  {
    #ifdef DEBUG
    fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: WARNING! No tolerance allowed and number "
                    "of internal pixmap formats [%d] doesn't match with number of remote formats [%d].\n",
            nxagentNumPixmapFormats, nxagentRemoteNumPixmapFormats);
    #endif
    return 0;
  }

  if ((ToleranceChecksSafe == tolerance) && (nxagentNumPixmapFormats > nxagentRemoteNumPixmapFormats))
  {
    #ifdef DEBUG
    fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: WARNING! Tolerance [%u] too low "
                    "and number of internal pixmap formats [%d] higher than number of external formats [%d].\n",
            tolerance, nxagentNumPixmapFormats, nxagentRemoteNumPixmapFormats);
    #endif
    return 0;
  }

  /*
   * By now the tolerance is either:
   *   - "Strict"
   *   - "Safe" and:
   *     o the number of internal and external pixmap formats
   *       matches exactly, or
   *     o the number of external pixmap formats is higher than
   *       the number of internal pixmap formats,
   *   - "Risky"
   */

  bool compatible = true;
  int total_matches = 0;

  for (int i = 0; i < nxagentNumPixmapFormats; ++i)
  {
    bool matched = false;

    for (int j = 0; j < nxagentRemoteNumPixmapFormats; ++j)
    {
      if (nxagentPixmapFormats[i].depth == nxagentRemotePixmapFormats[j].depth &&
          nxagentPixmapFormats[i].bits_per_pixel == nxagentRemotePixmapFormats[j].bits_per_pixel &&
          nxagentPixmapFormats[i].scanline_pad == nxagentRemotePixmapFormats[j].scanline_pad)
      {
        matched = true;
        ++total_matches;

        break;
      }
    }

    if ((ToleranceChecksRisky > tolerance) && (!matched))
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: WARNING! Tolerance [%u] too low "
                      "and failed to match internal pixmap format (depth [%d] bpp [%d] pad [%d]).\n",
              tolerance, nxagentPixmapFormats[i].depth, nxagentPixmapFormats[i].bits_per_pixel,
              nxagentPixmapFormats[i].scanline_pad);
      #endif

      compatible = false;
    }
  }

  int ret = !(!(compatible));

  if (compatible)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: Internal pixmap formats match with "
                    "remote pixmap formats at tolerance [%u].\n", tolerance);
    #endif

    if (total_matches != nxagentNumPixmapFormats)
    {
      #ifdef WARNING
      fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: Only some [%d] of the internal "
                      "pixmap formats [%d] match with external pixmap formats [%d] at tolerance [%u].\n",
              total_matches, nxagentNumPixmapFormats, nxagentRemoteNumPixmapFormats, tolerance);
      #endif
    }
  }
  else
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForPixmapFormatsCompatibility: WARNING! Internally available "
                    "pixmap formats [%d] don't match with external pixmap formats [%d] "
                    "at tolerance [%u]. Only [%d] depth values matched.\n",
            nxagentNumPixmapFormats, nxagentRemoteNumPixmapFormats, tolerance, total_matches);
    #endif
  }

  return (ret);
}

static int nxagentInitAndCheckVisuals(int flexibility)
{
  /* FIXME: does this also need work? */

  bool compatible = true;

  long viMask = VisualScreenMask;
  XVisualInfo viTemplate = {
    .screen = DefaultScreen(nxagentDisplay),
    .depth = DefaultDepth(nxagentDisplay, DefaultScreen(nxagentDisplay)),
  };
  int viNumList;
  XVisualInfo *viList = XGetVisualInfo(nxagentDisplay, viMask, &viTemplate, &viNumList);

  XVisualInfo *newVisuals = malloc(sizeof(XVisualInfo) * nxagentNumVisuals);

  for (int i = 0; i < nxagentNumVisuals; i++)
  {
    bool matched = false;

    for (int n = 0; n < viNumList; n++)
    {
      if (nxagentCompareVisuals(nxagentVisuals[i], viList[n]) == 1)
      {
/*
FIXME: Should the visual be ignored in this case?  We can flag the
       visuals with inverted masks, and use this information to switch
       the masks when contacting the remote X server.
*/
        if (nxagentVisuals[i].red_mask == viList[n].blue_mask &&
                nxagentVisuals[i].blue_mask == viList[n].red_mask)
        {
          #ifdef WARNING
          fprintf(stderr, "nxagentInitAndCheckVisuals: WARNING! Red and blue mask inverted. "
                      "Forcing matching.\n");
          #endif
        }

        matched = true;

        nxagentVisualHasBeenIgnored[i] = FALSE;

        memcpy(newVisuals + i, viList + n, sizeof(XVisualInfo));

        break;
      }
    }

    if (!matched)
    {
      if (nxagentVisuals[i].class == DirectColor)
      {
        #ifdef WARNING
        fprintf(stderr, "nxagentInitAndCheckVisuals: WARNING! Ignoring not matched DirectColor visual.\n");
        #endif

        nxagentVisualHasBeenIgnored[i] = TRUE;

        memcpy(newVisuals + i, nxagentVisuals + i, sizeof(XVisualInfo));
      }
      else
      {
        #ifdef DEBUG
        fprintf(stderr, "nxagentInitAndCheckVisuals: WARNING! Failed to match this visual:\n");
        fprintf(stderr, "\tdepth = %d\n", nxagentVisuals[i].depth);
        fprintf(stderr, "\tclass = %d\n", nxagentVisuals[i].class);
        fprintf(stderr, "\tmask = (%ld,%ld,%ld)\n",
                    nxagentVisuals[i].red_mask,
                        nxagentVisuals[i].green_mask,
                            nxagentVisuals[i].blue_mask);
        fprintf(stderr, "\tcolormap size = %d\n", nxagentVisuals[i].colormap_size);
        fprintf(stderr, "\tbits_per_rgb = %d\n", nxagentVisuals[i].bits_per_rgb);
        #endif

        compatible = false;

        break;
      }
    }
  }

  SAFE_XFree(viList);

  if (compatible)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentInitAndCheckVisuals: New visuals match with old visuals.\n");
    #endif

    nxagentVisuals = newVisuals;
  }
  else
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentInitAndCheckVisuals: New visuals don't match with old visuals.\n");
    #endif

    SAFE_free(newVisuals);
  }

  return compatible;
}

static int nxagentCheckForColormapsCompatibility(int flexibility)
{
  /* FIXME: does this also need work? */
  if (nxagentNumDefaultColormaps == nxagentNumDefaultColormapsRecBackup)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentCheckForColormapsCompatibility: Number of new colormaps [%d] "
                "matches with old colormaps.\n", nxagentNumDefaultColormaps);
    #endif
    return 1;
  }
  else
  {
    #ifdef WARNING
    fprintf(stderr, "nxagentCheckForColormapsCompatibility: WARNING! Number of new colormaps [%d] "
                "doesn't match with old colormaps [%d].\n", nxagentNumDefaultColormaps,
                    nxagentNumDefaultColormapsRecBackup);
    #endif
    return 0;
  }
}

Bool nxagentReconnectDisplay(void *p0)
{
  int flexibility = *(int*)p0;

  #if defined(NXAGENT_RECONNECT_DEBUG) || defined(NXAGENT_RECONNECT_DISPLAY_DEBUG)
  fprintf(stderr, "nxagentReconnectDisplay\n");
  #endif

  if (reconnectDisplayState)
  {
    fprintf(stderr, "nxagentReconnectDisplay: Trying to reconnect a session "
                "uncleanly disconnected\n");

    return False;
  }

  /*
   * Reset the values to their defaults.
   */

  nxagentPackMethod     = -1;
  nxagentPackQuality    = -1;
  nxagentSplitThreshold = -1;

  nxagentRemoteMajor = -1;

  nxagentInstallSignalHandlers();

  nxagentInstallDisplayHandlers();

  nxagentDisplay = nxagentInternalOpenDisplay(nxagentDisplayName);

  nxagentPostInstallSignalHandlers();

  nxagentPostInstallDisplayHandlers();

  if (nxagentDisplay == NULL)
  {
    nxagentSetReconnectError(FAILED_RESUME_DISPLAY_ALERT,
                                 "Couldn't open the display.");

    return False;
  }

  nxagentAddXConnection();

  /*
   * Display is now open.
   */

  reconnectDisplayState = OPENED;

  #ifdef NXAGENT_TIMESTAMP

  fprintf(stderr, "Display: Open of the display finished with time [%d] ms.\n",
          GetTimeInMillis() - startTime);

  #endif

  if (!nxagentCheckForDefaultDepthCompatibility())
  {
    nxagentSetReconnectError(FAILED_RESUME_DISPLAY_ALERT,
                                 "Default display depth doesn't match.");

    return False;
  }

  nxagentUseNXTrans = nxagentPostProcessArgs(nxagentDisplayName, nxagentDisplay,
                                                 DefaultScreenOfDisplay(nxagentDisplay));

  /*
   * After processing the arguments all the timeout values have been
   * set. Now we have to change the screen-saver timeout.
   */

  nxagentSetScreenSaverTime();

  /*
   * Init and compare the visuals.
   */

  if (!nxagentInitAndCheckVisuals(flexibility))
  {
    nxagentSetReconnectError(FAILED_RESUME_VISUALS_ALERT,
                                 "Couldn't restore the required visuals.");

    return False;
  }

  reconnectDisplayState = GOT_VISUAL_INFO;

  nxagentSetDefaultVisual();

  nxagentInitAlphaVisual();

  /*
   * Re-allocate the colormaps.
   */

  nxagentNumDefaultColormaps = nxagentNumVisuals;

  {
    Colormap * tmp = (Colormap *) realloc(nxagentDefaultColormaps,
                                              nxagentNumDefaultColormaps * sizeof(Colormap));
    if (tmp == NULL)
    {
      SAFE_free(nxagentDefaultColormaps);
      FatalError("Can't allocate memory for the default colormaps\n");
    }
    else
    {
      nxagentDefaultColormaps = tmp;
    }
  }

  reconnectDisplayState = ALLOC_DEF_COLORMAP;

  for (int i = 0; i < nxagentNumDefaultColormaps; i++)
  {
    if (nxagentVisualHasBeenIgnored[i])
    {
      nxagentDefaultColormaps[i] = (XID)0;
    }
    else
    {
      nxagentDefaultColormaps[i] = XCreateColormap(nxagentDisplay,
                                                   DefaultRootWindow(nxagentDisplay),
                                                   nxagentVisuals[i].visual,
                                                   AllocNone);
    }
  }

  nxagentCheckForColormapsCompatibility(flexibility);

  /*
   * Check the display depth.
   */

  nxagentInitDepths();

  reconnectDisplayState = GOT_DEPTH_LIST;

  if (!nxagentCheckForDepthsCompatibility())
  {
    nxagentSetReconnectError(FAILED_RESUME_DEPTHS_ALERT,
                                 "Couldn't restore all the required depths.");

    return False;
  }

  /*
   * nxagentPixmapFormats and nxagentRemotePixmapFormats
   * will be reallocated in nxagentInitPixmapFormats().
   */

  SAFE_XFree(nxagentPixmapFormats);
  SAFE_XFree(nxagentRemotePixmapFormats);

  /*
   * Check if all the required pixmap formats are supported.
   */

  nxagentInitPixmapFormats();

  if (!nxagentCheckForPixmapFormatsCompatibility())
  {
    nxagentSetReconnectError(FAILED_RESUME_PIXMAPS_ALERT,
                                 "Couldn't restore all the required pixmap formats.");

    return False;
  }

  reconnectDisplayState = GOT_PIXMAP_FORMAT_LIST;

  /*
   * Create a pixmap for each depth matching the local supported
   * formats with format available on the remote display.
   */

  nxagentSetDefaultDrawables();

  #ifdef RENDER

  if (nxagentRenderEnable)
  {
    nxagentRenderExtensionInit();
  }

  if (nxagentRenderEnableRecBackup != nxagentRenderEnable)
  {
    nxagentRenderEnable = nxagentRenderEnableRecBackup;

    nxagentSetReconnectError(FAILED_RESUME_RENDER_ALERT,
                                 "Render extension not available or incompatible version.");

    return False;
  }

  #endif

  nxagentBlackPixel = BlackPixel(nxagentDisplay, DefaultScreen(nxagentDisplay));
  nxagentWhitePixel = WhitePixel(nxagentDisplay, DefaultScreen(nxagentDisplay));

  /*
   * Initialize the agent's event mask that will be requested for the
   * root or all the top level windows. If the nested window is a
   * child of an existing window we will need to receive
   * StructureNotify events. If we are going to manage the changes in
   * root window's visibility we'll also need VisibilityChange events.
   */

  nxagentInitDefaultEventMask();

  nxagentBitmapGC = XCreateGC(nxagentDisplay, nxagentDefaultDrawables[1], 0L, NULL);

  #ifdef TEST
  fprintf(stderr, "nxagentReconnectDisplay: Going to create agent's confine window.\n");
  #endif

  nxagentOpenConfineWindow();

  useXpmIcon = nxagentMakeIcon(nxagentDisplay, &nxagentIconPixmap, &nxagentIconShape);

  /*
   * Everything went fine. We can continue handling our clients.
   */

  reconnectDisplayState = EVERYTHING_DONE;

  return True;
}

void nxagentAddXConnection(void)
{
  nxagentXConnectionNumber = XConnectionNumber(nxagentDisplay);

  #ifdef TEST
  fprintf(stderr, "nxagentAddXConnection: Adding the X connection [%d] "
              "to the device set.\n", nxagentXConnectionNumber);
  #endif

  SetNotifyFd(XConnectionNumber(nxagentDisplay), nxagentNotifyConnection, X_NOTIFY_READ, NULL);
}

void nxagentRemoveXConnection(void)
{
  #ifdef TEST
  fprintf(stderr, "nxagentRemoveXConnection: Removing the X connection [%d] "
              "from the device set.\n", nxagentXConnectionNumber);
  #endif

  RemoveNotifyFd(nxagentXConnectionNumber);
}

/*
 * Force an I/O error and wait until the NX transport is gone. It must
 * be called before suspending or terminating a session to ensure that
 * the NX transport is terminated first.
 */

void nxagentWaitDisplay(void)
{
  /*
   * Disable the smart scheduler's interrupts.
   */

  #ifdef DEBUG
  fprintf(stderr, "nxagentWaitDisplay: Stopping the smart schedule timer.\n");
  #endif

  nxagentStopTimer();

  if (nxagentDisplay != NULL)
  {
    #ifdef TEST
    fprintf(stderr, "nxagentWaitDisplay: Going to shutdown the X connection [%d].\n",
                nxagentXConnectionNumber);
    #endif

    NXForceDisplayError(nxagentDisplay);

    XSync(nxagentDisplay, 0);
  }

  #ifdef TEST
  fprintf(stderr, "nxagentWaitDisplay: Going to wait for the NX transport.\n");
  #endif

  NXTransDestroy(NX_FD_ANY);

  #ifdef TEST
  fprintf(stderr, "nxagentWaitDisplay: The NX transport is not running.\n");
  #endif

  /*
   * Be sure the signal handlers are in a known state.
   */

  nxagentResetSignalHandlers();
}

/*
 * This has not to do with the remote display but with the X server
 * that the agent is impersonating.  We have it here to be consistent
 * with the other cleanup procedures which have mainly to do with the
 * Xlib display connection.
 */

void nxagentAbortDisplay(void)
{
  /*
   * Be sure the X server socket in .X11-unix is deleted otherwise
   * other users may to become unable to run a session on the same
   * display.
   */

  #ifdef TEST
  fprintf(stderr, "nxagentAbortDisplay: Cleaning up the X server sockets.\n");
  #endif

  CloseWellKnownConnections();
}
