/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2017 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2011-2022 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2014-2019 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2014-2022 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.                                          */
/*                                                                        */
/**************************************************************************/

#include "X.h"
#include "Xproto.h"
#include "Xatom.h"
#include "selection.h"
#include "windowstr.h"
#include "scrnintstr.h"

#include "Agent.h"
#include "Windows.h"
#include "Atoms.h"
#include "Args.h"
#include "Trap.h"
#include "Rootless.h"
#include "Clipboard.h"
#include "Utils.h"
#include "Client.h"

#include "gcstruct.h"
#include "xfixeswire.h"
#include "X11/include/Xfixes_nxagent.h"

/*
 * Use asynchronous get property replies.
 */

#include "compext/Compext.h"

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

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

/*
 * Define this to see the clipboard content in the debug output. As
 * this can lead to information leaking it must be activated
 * explicitly!
 */
#undef PRINT_CLIPBOARD_CONTENT_ON_DEBUG

/*
 * Define these to also support special targets TEXT and COMPOUND_TEXT
 * in text-only mode. We do not have a special handling for these. See
 * https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#text_properties
 * for details.
 */
#undef SUPPORT_TEXT_TARGET
#undef SUPPORT_COMPOUND_TEXT_TARGET

/*
 * These are defined in the dispatcher.
 */

extern int NumCurrentSelections;
extern Selection *CurrentSelections;

static int agentClipboardInitialized = False;
static int clientAccum;

XlibAtom serverTransToAgentProperty;
Atom clientCutProperty;
static XlibWindow serverWindow;

const int nxagentPrimarySelection = 0;
const int nxagentClipboardSelection = 1;
const int nxagentMaxSelections = 2;

/* store the remote atom for all selections */
static XlibAtom *remoteSelectionAtoms = NULL;
static Atom *localSelectionAtoms = NULL;

/*
 * The real owner window (inside nxagent) is stored in
 * lastSelectionOwner[index].window.
 * lastSelectionOwner[index].windowPtr points to the struct that
 * contains all information about the owner window.
 * lastTimeChanged is always a local time.
 */
typedef struct _SelectionOwner
{
  ClientPtr  client;           /* local client */
  Window     window;           /* local window id */
  WindowPtr  windowPtr;        /* local window struct */
  TimeStamp  lastTimeChanged;  /* local time (server time) */
} SelectionOwner;

/*
 * This contains the last selection owner for each selection. If
 * .client is NULL the owner is outside nxagent or there is no owner.
 */

static SelectionOwner *lastSelectionOwner = NULL;

/*
 * Cache for targets the current selection owner
 * has to offer. We are storing the targets
 * after they have been converted.
*/
typedef struct _Targets
{
  Bool          type;        /* EMPTY, FOR_LOCAL, FOR_REMOTE */
  unsigned int  numTargets;
  Atom         *forLocal;    /* Atoms converted for local  -> type Atom, not XlibAtom */
  XlibAtom     *forRemote;   /* Atoms converted for remote -> type XlibAtom, not Atom */
} Targets;

#define EMPTY 0
#define FOR_REMOTE 1
#define FOR_LOCAL 2

static Targets *targetCache = NULL;

/* FIXME: can this also be stored per selection? */
static XlibAtom serverLastRequestedSelection = -1;

#define IS_LOCAL_OWNER(lsoindex) (lastSelectionOwner[lsoindex].client != NullClient)

typedef enum
{
  SelectionStageNone,
  SelectionStageQuerySize,
  SelectionStageWaitSize,
  SelectionStageQueryData,
  SelectionStageWaitData
} ClientSelectionStage;

/*
 * Needed to handle the notify selection event to be sent to the
 * waiting client once the selection property has been retrieved from
 * the real X server.
 */

typedef struct _lastClient
{
  WindowPtr     windowPtr;
  ClientPtr     clientPtr;
  Window        requestor;
  Atom          property;
  Atom          target;
  Time          time;
  Time          reqTime;
  unsigned long propertySize;
  ClientSelectionStage stage;
  int           resource;       /* nxcompext resource where collected property data is stored */
} lastClient;

static lastClient *lastClients;

typedef struct _lastServer {
  XlibWindow    requestor;
  XlibAtom      property;
  XlibAtom      target;
  Time          time;
} lastServer;

static lastServer *lastServers;

/*
 * FIXME: use (additional) Atoms.c helpers to get rid of all these
 * Atoms and strings
 */
static XlibAtom serverTARGETS;
static XlibAtom serverTIMESTAMP;
static XlibAtom serverINCR;
static XlibAtom serverMULTIPLE;
static XlibAtom serverDELETE;
static XlibAtom serverINSERT_SELECTION;
static XlibAtom serverINSERT_PROPERTY;
static XlibAtom serverSAVE_TARGETS;
static XlibAtom serverTARGET_SIZES;
#ifdef SUPPORT_TEXT_TARGET
static XlibAtom serverTEXT;
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
static XlibAtom serverCOMPOUND_TEXT;
#endif
static XlibAtom serverUTF8_STRING;
static XlibAtom serverTransFromAgentProperty;
static Atom clientTARGETS;
static Atom clientTIMESTAMP;
static Atom clientINCR;
static Atom clientMULTIPLE;
static Atom clientDELETE;
static Atom clientINSERT_SELECTION;
static Atom clientINSERT_PROPERTY;
static Atom clientSAVE_TARGETS;
static Atom clientTARGET_SIZES;
#ifdef SUPPORT_TEXT_TARGET
static Atom clientTEXT;
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
static Atom clientCOMPOUND_TEXT;
#endif
static Atom clientUTF8_STRING;

static char szAgentTARGETS[] = "TARGETS";
#ifdef SUPPORT_TEXT_TARGET
static char szAgentTEXT[] = "TEXT";
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
static char szAgentCOMPOUND_TEXT[] = "COMPOUND_TEXT";
#endif
static char szAgentTIMESTAMP[] = "TIMESTAMP";
static char szAgentINCR[] = "INCR";
static char szAgentMULTIPLE[] = "MULTIPLE";
static char szAgentDELETE[] = "DELETE";
static char szAgentINSERT_SELECTION[] = "INSERT_SELECTION";
static char szAgentINSERT_PROPERTY[] = "INSERT_PROPERTY";
static char szAgentSAVE_TARGETS[] = "SAVE_TARGETS";
static char szAgentTARGET_SIZES[] = "TARGET_SIZES";
static char szAgentUTF8_STRING[] = "UTF8_STRING";
static char szAgentNX_CUT_BUFFER_CLIENT[] = "NX_CUT_BUFFER_CLIENT";
static char szAgentCLIPBOARD[] = "CLIPBOARD";

/* Number of milliseconds to wait for a conversion from the real X server. */
#define CONVERSION_TIMEOUT 5000

/*
 * Time window (milliseconds) within to detect multiple conversion
 * calls of the same client.
 */
#define ACCUM_TIME 5000

/*
 * Some helpers for debugging output
 */

static const char * getClientSelectionStageString(int stage)
{
  switch(stage)
  {
    case SelectionStageNone:      return("None"); break;;
    case SelectionStageQuerySize: return("QuerySize"); break;;
    case SelectionStageWaitSize:  return("WaitSize"); break;;
    case SelectionStageQueryData: return("QueryData"); break;;
    case SelectionStageWaitData:  return("WaitData"); break;;
    default:                      return("UNKNOWN!"); break;;
  }
}

#ifdef DEBUG
#define printClientSelectionStage(_index) do {fprintf(stderr, "%s: Current selection stage for selection [%d] is [%s]\n", __func__, _index, getClientSelectionStageString(lastClients[_index].stage));} while (0)
#else
#define printClientSelectionStage(_index)
#endif

#define WINDOWID(ptr) (ptr) ? (ptr->drawable.id) : 0
#define CLINDEX(clientptr) (clientptr) ? (clientptr->index) : -1

#ifdef DEBUG
/*
 * See also nx-X11/lib/src/ErrDes.c
 *
 * We use our own version to avoid Xlib doing expensive calls.
 * FIXME: Must check if XGetErrorText() is really causing traffic over the wire.
 * FIXME: move this to a Utils.c or similar
 */
const char * getXErrorString(int code)
{
  switch(code)
  {
    case Success:           return("Success"); break;;
    case BadRequest:        return("BadRequest"); break;;
    case BadValue:          return("BadValue"); break;;
    case BadWindow:         return("BadWindow"); break;;
    case BadPixmap:         return("BadPixmap"); break;;
    case BadAtom:           return("BadAtom"); break;;
    case BadCursor:         return("BadCursor"); break;;
    case BadFont:           return("BadFont"); break;;
    case BadMatch:          return("BadMatch"); break;;
    case BadDrawable:       return("BadDrawable"); break;;
    case BadAccess:         return("BadAccess"); break;;
    case BadAlloc:          return("BadAlloc"); break;;
    case BadColor:          return("BadColor"); break;;
    case BadGC:             return("BadGC"); break;;
    case BadIDChoice:       return("BadIDChoice"); break;;
    case BadName:           return("BadName"); break;;
    case BadLength:         return("BadLength"); break;;
    case BadImplementation: return("BadImplementation"); break;;
    default:                return("UNKNOWN!"); break;;
  }
}
#endif

/*
 * Save the values queried from X server.
 */

XFixesAgentInfoRec nxagentXFixesInfo = { -1, -1, -1, False };

extern Display *nxagentDisplay;

static Bool isTextTarget(XlibAtom target);
static void setClientSelectionStage(int index, int stage);
static void endTransfer(int index, Bool success);
#define SELECTION_SUCCESS True
#define SELECTION_FAULT False
static void transferSelectionFromXServer(int index, int resource);
#if 0
static void resetSelectionOwnerOnXServer(void);
#endif
static void initSelectionOwnerData(int index);
static void clearSelectionOwnerData(int index);
static void storeSelectionOwnerData(int index, Selection *sel);
static Bool matchSelectionOwner(int index, ClientPtr pClient, WindowPtr pWindow);
static void setSelectionOwnerOnXServer(Selection *pSelection);
static int sendEventToClient(ClientPtr client, xEvent *pEvents);
static void sendSelectionNotifyEventToClient(ClientPtr client,
                                             Time time,
                                             Window requestor,
                                             Atom selection,
                                             Atom target,
                                             Atom property);
static Status sendSelectionNotifyEventToXServer(XSelectionEvent *event_to_send);
static void replyPendingRequestSelectionToXServer(int index, Bool success);
#ifdef DEBUG
static void printSelectionStat(int sel);
#endif
static void replyRequestSelectionToXServer(XEvent *X, Bool success);
void handlePropertyTransferFromAgentToXserver(int index, XlibAtom property);

void nxagentPrintClipboardStat(char *);

XlibAtom translateLocalToRemoteSelection(Atom local);
XlibAtom translateLocalToRemoteTarget(Atom local);

#ifdef NXAGENT_TIMESTAMP
extern unsigned long startTime;
#endif

static void printSelectionStat(int index)
{
  SelectionOwner lOwner = lastSelectionOwner[index];
  Selection curSel = CurrentSelections[index];

  fprintf(stderr, "selection [%d]:\n", index);

  fprintf(stderr, "  selection Atom                         local [%d][%s]  remote [%ld][%s]\n",
              localSelectionAtoms[index], NameForLocalAtom(localSelectionAtoms[index]),
                  remoteSelectionAtoms[index], NameForRemoteAtom(remoteSelectionAtoms[index]));
  fprintf(stderr, "  owner side                             %s\n", IS_LOCAL_OWNER(index) ? "nxagent" : "real X server/none");
  fprintf(stderr, "  lastSelectionOwner[].client            %s\n", nxagentClientInfoString(lOwner.client));
  fprintf(stderr, "  lastSelectionOwner[].window            [0x%x]\n", lOwner.window);
  if (lOwner.windowPtr)
    fprintf(stderr, "  lastSelectionOwner[].windowPtr         [%p] (-> [0x%x])\n", (void *)lOwner.windowPtr, WINDOWID(lOwner.windowPtr));
  else
    fprintf(stderr, "  lastSelectionOwner[].windowPtr         -\n");
  fprintf(stderr, "  lastSelectionOwner[].lastTimeChanged   [%u]\n", lOwner.lastTimeChanged.milliseconds);

  fprintf(stderr, "  CurrentSelections[].client             %s\n", nxagentClientInfoString(curSel.client));
  fprintf(stderr, "  CurrentSelections[].window             [0x%x]\n", curSel.window);
  if (curSel.pWin)
    fprintf(stderr, "  CurrentSelections[].pWin               [%p] (-> [0x%x])\n", (void *)curSel.pWin, WINDOWID(curSel.pWin));
  else
    fprintf(stderr, "  CurrentSelections[].pWin               -\n");
  fprintf(stderr, "  CurrentSelections[].lastTimeChanged    [%u]\n", curSel.lastTimeChanged.milliseconds);
  return;
}

static void printLastClientStat(int index)
{
  lastClient lc = lastClients[index];
  if (lc.windowPtr)
    fprintf(stderr, "  lastClients[].windowPtr    (WindowPtr) [%p] ([0x%x])\n", (void *)lc.windowPtr, WINDOWID(lc.windowPtr));
  else
    fprintf(stderr, "  lastClients[].windowPtr    (WindowPtr) -\n");
  fprintf(stderr, "  lastClients[].clientPtr    (ClientPtr) %s\n", nxagentClientInfoString(lc.clientPtr));
  fprintf(stderr, "  lastClients[].requestor       (Window) [0x%x]\n", lc.requestor);
  fprintf(stderr, "  lastClients[].property          (Atom) [% 4d][%s]\n", lc.property, NameForLocalAtom(lc.property));
  fprintf(stderr, "  lastClients[].target            (Atom) [% 4d][%s]\n", lc.target, NameForLocalAtom(lc.target));
  if (lc.time > 0)
    fprintf(stderr, "  lastClients[].time              (Time) [%u] ([%u]ms ago)\n", lc.time, GetTimeInMillis() - lc.time);
  else
    fprintf(stderr, "  lastClients[].time              (Time) [%u]\n", lc.time);
  if (lc.reqTime > 0)
    fprintf(stderr, "  lastClients[].reqTime           (Time) [%u] ([%u]ms ago)\n", lc.reqTime, GetTimeInMillis() - lc.reqTime);
  else
    fprintf(stderr, "  lastClients[].reqTime           (Time) [%u]\n", lc.reqTime);
  fprintf(stderr, "  lastClients[].propertySize     (ulong) [%lu]\n", lc.propertySize);
  fprintf(stderr, "  lastClients[].stage   (ClientSelStage) [%d][%s]\n", lc.stage, getClientSelectionStageString(lc.stage));
  fprintf(stderr, "  lastClients[].resource           (int) [%d]\n", lc.resource);
}

static void printLastServerStat(int index)
{
  lastServer ls = lastServers[index];
  fprintf(stderr, "  lastServer[].requestor    (XlibWindow) [0x%lx]\n", ls.requestor);
  fprintf(stderr, "  lastServer[].property       (XlibAtom) [% 4ld][%s]\n", ls.property, NameForRemoteAtom(ls.property));
  fprintf(stderr, "  lastServer[].target         (XlibAtom) [% 4ld][%s]\n", ls.target, NameForRemoteAtom(ls.target));
  fprintf(stderr, "  lastServer[].time               (Time) [%u]\n", ls.time);
}

static void printTargetCacheStat(int index)
{
  fprintf(stderr, "  targetCache[].type               (int) [%d]\n", targetCache[index].type);
  fprintf(stderr, "  targetCache[].forLocal        (Atom *) [%p]\n", (void *)targetCache[index].forLocal);
  fprintf(stderr, "  targetCache[].forRemote   (XlibAtom *) [%p]\n", (void *)targetCache[index].forRemote);
  fprintf(stderr, "  targetCache[].numTargets         (int) [%d]\n", targetCache[index].numTargets);
}

void nxagentDumpClipboardStat(void)
{
  fprintf(stderr, "/----- Clipboard internal status -----\n");

  fprintf(stderr, "  current time                    (Time) [%u]\n", GetTimeInMillis());
  fprintf(stderr, "  agentClipboardInitialized       (Bool) [%s]\n", agentClipboardInitialized ? "True" : "False");
  fprintf(stderr, "  clientAccum                      (int) [%d]\n", clientAccum);
  fprintf(stderr, "  nxagentMaxSelections             (int) [%d]\n", nxagentMaxSelections);
  fprintf(stderr, "  NumCurrentSelections             (int) [%d]\n", NumCurrentSelections);
  fprintf(stderr, "  serverWindow              (XlibWindow) [0x%lx]\n", serverWindow);

  fprintf(stderr, "  Clipboard mode                         ");
  switch(nxagentOption(Clipboard))
  {
    case ClipboardBoth:    fprintf(stderr, "[Both]"); break;;
    case ClipboardClient:  fprintf(stderr, "[Client]"); break;;
    case ClipboardServer:  fprintf(stderr, "[Server]"); break;;
    case ClipboardNone:    fprintf(stderr, "[None]"); break;;
    default:               fprintf(stderr, "[UNKNOWN] (FAIL!)"); break;;
  }
  fprintf(stderr, "\n");

  if (serverLastRequestedSelection == -1)
      fprintf(stderr, "  serverLastRequestedSelection           [-1](uninitialized)\n");
  else
      fprintf(stderr, "  serverLastRequestedSelection           [% 4ld][%s]\n", serverLastRequestedSelection, NameForRemoteAtom(serverLastRequestedSelection));

  fprintf(stderr, "Compile time settings\n");
#ifdef PRINT_CLIPBOARD_CONTENT_ON_DEBUG
  fprintf(stderr, "  PRINT_CLIPBOARD_CONTENT_ON_DEBUG       [enabled]\n");
#else
  fprintf(stderr, "  PRINT_CLIPBOARD_CONTENT_ON_DEBUG       [disabled]\n");
#endif
#ifdef SUPPORT_TEXT_TARGET
  fprintf(stderr, "  SUPPORT_TEXT_TARGET                    [enabled]\n");
#else
  fprintf(stderr, "  SUPPORT_TEXT_TARGET                    [disabled]\n");
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
  fprintf(stderr, "  SUPPORT_COMPOUND_TEXT_TARGET           [enabled]\n");
#else
  fprintf(stderr, "  SUPPORT_COMPOUND_TEXT_TARGET           [disabled]\n");
#endif

#define WIDTH 32
  Atom cl = 0;
  XlibAtom sv = 0;
  int len = WIDTH;

  fprintf(stderr, "Atoms                                    local%*sremote\n", WIDTH - 5, "");
  cl = clientTARGETS; sv = serverTARGETS; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  TARGETS                                [% 4d][%s]%*s [% 4ld][%s]\n", cl, NameForLocalAtom(cl), len, "", sv, NameForRemoteAtom(sv));

  cl = clientTIMESTAMP; sv = serverTIMESTAMP; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  TIMESTAMP                              [% 4d][%s]%*s [% 4ld][%s]\n", cl, NameForLocalAtom(cl), len, "", sv, NameForRemoteAtom(sv));

#ifdef SUPPORT_TEXT_TARGET
  cl = clientTEXT; sv = serverTEXT; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  TEXT                                   [% 4d][%s]%*s [% 4ld][%s]\n", cl, NameForLocalAtom(cl), len, "", sv, NameForRemoteAtom(sv));
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
  cl = clientCOMPOUND_TEXT; sv = serverCOMPOUND_TEXT; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  COMPOUND_TEXT                          [% 4d][%s]%*s [% 4ld][%s]\n", cl, NameForLocalAtom(cl), len, "", sv, NameForRemoteAtom(sv));
#endif

  cl = clientUTF8_STRING; sv = serverUTF8_STRING; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  UTF8_STRING                            [% 4d][%s]%*s [% 4ld][%s]\n", cl, NameForLocalAtom(cl), len, "", sv, NameForRemoteAtom(sv));

  sv = serverTransToAgentProperty;
  fprintf(stderr, "  serverTransToAgentProperty             - %*s[% 4ld][%s]\n", WIDTH - 2, "", sv, NameForRemoteAtom(sv));

  sv = serverTransFromAgentProperty;
  fprintf(stderr, "  serverTransFromAgentProperty           - %*s[% 4ld][%s]\n", WIDTH - 2, "", sv, NameForRemoteAtom(sv));

  cl = clientCutProperty; len = (int)(WIDTH - 9 - strlen(NameForLocalAtom(cl)));
  fprintf(stderr, "  clientCutProperty                      [% 4d][%s]%*s\n", cl, NameForLocalAtom(cl), len + 2, "-" );

  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    printSelectionStat(index);
    printLastClientStat(index);
    printLastServerStat(index);
    printTargetCacheStat(index);
  }

  fprintf(stderr, "\\------------------------------------------------------------------------------\n");
}

/*
 * Helper to handle data transfer
 */
static void resetClientSelectionStage(int index)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: Resetting selection stage for [%d]\n", __func__, index);
  #endif

  lastClients[index].stage = SelectionStageNone;
  lastClients[index].windowPtr = NULL;
  lastClients[index].clientPtr = NULL;
  lastClients[index].requestor = 0;
  lastClients[index].property = 0;
  lastClients[index].target = 0;
  lastClients[index].time = 0;
  lastClients[index].reqTime = 0;
  lastClients[index].propertySize = 0;
  lastClients[index].resource = -1;
}

static void setClientSelectionStage(int index, int stage)
{
  if (stage == SelectionStageNone)
  {
    resetClientSelectionStage(index);
  }
  else
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: Changing selection stage for [%d] from [%s] to [%s]\n", __func__, index,
                getClientSelectionStageString(lastClients[index].stage), getClientSelectionStageString(stage));
    #endif
    lastClients[index].stage = stage;
  }
}

/*
 * This is from NXproperty.c.
 */

int GetWindowProperty(WindowPtr pWin, Atom property, long longOffset, long longLength,
                          Bool delete, Atom type, Atom *actualType, int *format,
                              unsigned long *nItems, unsigned long *bytesAfter,
                                  unsigned char **propData);

/*
 * Send a SelectionNotify event to the real X server and do some error
 * handling (in DEBUG mode).
 */
static Status sendSelectionNotifyEventToXServer(XSelectionEvent *event_to_send)
{
  XlibWindow w = event_to_send->requestor;

  event_to_send->type = SelectionNotify;
  event_to_send->send_event = True;
  event_to_send->display = nxagentDisplay;

  Status result = XSendEvent(nxagentDisplay, w, False, 0L, (XEvent *)event_to_send);

  #ifdef DEBUG
  /*
   * man XSendEvent: XSendEvent returns zero if the conversion to wire
   * protocol format failed and returns nonzero otherwise.  XSendEvent
   * can generate BadValue and BadWindow errors.
   */
  if (result == 0)
  {
    fprintf(stderr, "%s: XSendEvent to [0x%lx] failed.\n", __func__, w);
  }
  else
  {
    if (result == BadValue || result == BadWindow)
    {
      fprintf(stderr, "%s: WARNING! XSendEvent to [0x%lx] failed: %s\n", __func__, w, getXErrorString(result));
    }
    else
    {
      fprintf(stderr, "%s: XSendEvent() successfully sent to [0x%lx]\n", __func__, w);
    }
  }
  #endif

  NXFlushDisplay(nxagentDisplay, NXFlushLink);

  return result;
}

static int sendEventToClient(ClientPtr client, xEvent *pEvents)
{
  return TryClientEvents(client, pEvents, 1, NoEventMask, NoEventMask, NullGrab);
}

static void sendSelectionNotifyEventToClient(ClientPtr client,
                                             Time time,
                                             Window requestor,
                                             Atom selection,
                                             Atom target,
                                             Atom property)
{
  /*
   * Check if the client is still valid.
   */
  if (clients[client -> index] != client)
  {
    #ifdef WARNING
    fprintf(stderr, "%s: WARNING! Invalid client pointer.", __func__);
    #endif

    return;
  }

  xEvent x = {0};
  x.u.u.type = SelectionNotify;
  x.u.selectionNotify.time = time;
  x.u.selectionNotify.requestor = requestor;
  x.u.selectionNotify.selection = selection;
  x.u.selectionNotify.target = target;
  x.u.selectionNotify.property = property;

  #ifdef DEBUG
  if (property == None)
    fprintf(stderr, "%s: Denying request to client %s - event time [%u].\n", __func__,
            nxagentClientInfoString(client), time);
  else
    fprintf(stderr, "%s: Sending event to client %s - event time [%u].\n", __func__,
            nxagentClientInfoString(client), time);
  #endif

  sendEventToClient(client, &x);
}

/*
 * Check if target is a valid text content type target sent by the real X
 * server, like .e.g XA_STRING or UTF8_STRING.
 */
static Bool isTextTarget(XlibAtom target)
{
  if (target == XA_STRING)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: valid target [XA_STRING].\n", __func__);
    #endif
    return True;
  }
#ifdef SUPPORT_TEXT_TARGET
  else if (target == serverTEXT)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: valid target [TEXT].\n", __func__);
    #endif
    return True;
  }
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
  else if (target == serverCOMPOUND_TEXT)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: valid target [COMPOUND_TEXT].\n", __func__);
    #endif
    return True;
  }
#endif
  else if (target == serverUTF8_STRING)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: valid target [UTF8_STRING].\n", __func__);
    #endif
    return True;
  }
  /* FIXME: add text/plain */

  #ifdef DEBUG
  fprintf(stderr, "%s: not a text target [%lu].\n", __func__, target);
  #endif
  return False;
}

static void initSelectionOwnerData(int index)
{
  lastSelectionOwner[index].client = NullClient;
  lastSelectionOwner[index].window = screenInfo.screens[0]->root->drawable.id;
  lastSelectionOwner[index].windowPtr = NULL;
  lastSelectionOwner[index].lastTimeChanged = ClientTimeToServerTime(CurrentTime);
}

/* there's no owner on nxagent side anymore */
static void clearSelectionOwnerData(int index)
{
  lastSelectionOwner[index].client = NullClient;
  lastSelectionOwner[index].window = None;
  lastSelectionOwner[index].windowPtr = NULL;
  lastSelectionOwner[index].lastTimeChanged = ClientTimeToServerTime(CurrentTime);
}

static void storeSelectionOwnerData(int index, Selection *sel)
{
  lastSelectionOwner[index].client = sel->client;
  lastSelectionOwner[index].window = sel->window;
  lastSelectionOwner[index].windowPtr = sel->pWin;
  lastSelectionOwner[index].lastTimeChanged = ClientTimeToServerTime(CurrentTime);
}

static Bool matchSelectionOwner(int index, ClientPtr pClient, WindowPtr pWindow)
{
  return ((pClient && lastSelectionOwner[index].client == pClient) ||
          (pWindow && lastSelectionOwner[index].windowPtr == pWindow));
}

/*
 * Clear relevant clipboard states if a client or window is closing.
 * Attention: does not work properly when both client AND window
 * are passed as setClientSelectionStage(None) will also clear
 * the lastClientWindowPtr!
 * This is only called from Client.c and Window.c
 */
void nxagentClearClipboard(ClientPtr pClient, WindowPtr pWindow)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: Called with client [%p] index [%d] window [%p] ([0x%x]).\n", __func__,
              (void *) pClient, CLINDEX(pClient), (void *) pWindow, WINDOWID(pWindow));
  #endif

  /* FIXME: there's almost identical code in nxagentClipboardInit */
  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    if (matchSelectionOwner(index, pClient, pWindow))
    {
      #ifdef TEST
      fprintf(stderr, "%s: Resetting state [%d] with client [%p] window [%p].\n", __func__,
                  index, (void *) pClient, (void *) pWindow);
      #endif

      clearSelectionOwnerData(index);

      setClientSelectionStage(index, SelectionStageNone);

      replyPendingRequestSelectionToXServer(index, False);
    }

    if (pWindow && pWindow == lastClients[index].windowPtr)
    {
      setClientSelectionStage(index, SelectionStageNone);
    }
  }
}

/*
 * Find the index of the lastSelectionOwner with the selection
 * sel. sel is an atom on the real X server. If the index cannot be
 * determined it will return -1.
 */
int nxagentFindRemoteSelectionIndex(XlibAtom sel)
{
  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    if (remoteSelectionAtoms[index] == sel)
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: remote selection [%ld][%s] belongs to index [%d]\n", __func__, sel, NameForRemoteAtom(sel), index);
      #endif
      return index;
    }
  }
  #ifdef DEBUG
  fprintf(stderr, "%s: remote selection [%ld][%s] does not belong to any index!\n", __func__, sel, NameForRemoteAtom(sel));
  #endif
  return -1;
}

/*
 * Find the index of CurrentSelection with the selection
 * sel. sel is a local atom. If the index cannot be
 * determined it will return -1.
 */
int nxagentFindCurrentSelectionIndex(Atom sel)
{
  /*
   * Normally you'd expect the loop going up to
   * NumCurrentSelections. But the dix code will increase that number
   * (but not nxagentMaxSelections) when drag and drop comes into
   * play. In that case this helper will report a match for other
   * selections than the ones the clipboard code knows about. The
   * subsequent code will then use a higher index which will be used
   * by the clipboard code and will lead to out of range data reads
   * (and writes!). Therefore we take nxagentMaxSelections here. The
   * startup code ensures that both arrays will refer to the same
   * selection for the first nxagentMaxSelections selection atoms.
   */

  /*  for (int index = 0; index < NumCurrentSelections; index++) */
  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    if (CurrentSelections[index].selection == sel)
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: selection [%d][%s] belongs to index [%d]\n", __func__, sel, NameForLocalAtom(sel), index);
      #endif
      return index;
    }
  }
  #ifdef DEBUG
  fprintf(stderr, "%s: selection [%d][%s] does not belong to any index!\n", __func__, sel, NameForLocalAtom(sel));
  #endif
  return -1;
}

void cacheTargetsForLocal(int index, Atom* targets, int numTargets)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: caching [%d] targets for local requests\n", __func__, numTargets);
  #endif

  SAFE_free(targetCache[index].forLocal);
  SAFE_free(targetCache[index].forRemote);
  targetCache[index].type = FOR_LOCAL;
  targetCache[index].forLocal = targets;
  targetCache[index].numTargets = numTargets;
}

void cacheTargetsForRemote(int index, XlibAtom* targets, int numTargets)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: caching [%d] targets for remote requests\n", __func__, numTargets);
  #endif

  SAFE_free(targetCache[index].forLocal);
  SAFE_free(targetCache[index].forRemote);
  targetCache[index].type = FOR_REMOTE;
  targetCache[index].forRemote = targets;
  targetCache[index].numTargets = numTargets;
}

/* This is called on init, reconnect and SelectionClear. */
void invalidateTargetCache(int index)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: invalidating target cache [%d]\n", __func__, index);
  #endif

  SAFE_free(targetCache[index].forLocal);
  SAFE_free(targetCache[index].forRemote);
  targetCache[index].type = EMPTY;
  targetCache[index].numTargets = 0;
}

void invalidateTargetCaches(void)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: invalidating all target caches\n", __func__);
  #endif

  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    SAFE_free(targetCache[index].forLocal);
    SAFE_free(targetCache[index].forRemote);
    targetCache[index].type = EMPTY;
    targetCache[index].numTargets = 0;
  }
}

/*
 * This is called from Events.c dispatch loop on reception of a
 * SelectionClear or XFixes selection event from the real X
 * server. We receive this event if someone on the real X server
 * claims the selection ownership we have/had.
 * Three versions of this routine with different parameter types.
 */
void nxagentHandleSelectionClearFromXServerByIndex(int index)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: SelectionClear event for selection index [%u].\n", __func__, index);
  #endif

  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: ignoring index -1 - doing nothing.\n", __func__);
    #endif
    return;
  }

  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return;
  }

  if (nxagentOption(Clipboard) == ClipboardServer)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard mode 'server' - doing nothing.\n", __func__);
    #endif
    return;
  }

  UpdateCurrentTime();
  TimeStamp time = ClientTimeToServerTime(CurrentTime);

  if (IS_LOCAL_OWNER(index))
  {
    /* Send a SelectionClear event to (our) previous owner. */
    xEvent x = {0};
    x.u.u.type = SelectionClear;
    x.u.selectionClear.time = time.milliseconds;
    x.u.selectionClear.window = lastSelectionOwner[index].window;
    x.u.selectionClear.atom = CurrentSelections[index].selection;

    sendEventToClient(lastSelectionOwner[index].client, &x);

    /*
     * Set the root window with the NullClient as selection owner. Our
     * clients asking for the owner via XGetSelectionOwner() will get
     * this for an answer.
     * Set the CurrentSelection data just as ProcSetSelectionOwner does.
     */
    CurrentSelections[index].lastTimeChanged = time;
    CurrentSelections[index].window = screenInfo.screens[0]->root->drawable.id;
    CurrentSelections[index].pWin = NULL;
    CurrentSelections[index].client = NullClient;

    clearSelectionOwnerData(index);

    setClientSelectionStage(index, SelectionStageNone);

    invalidateTargetCache(index);

    /*
     * Now call the callbacks. This is important, especially for
     * XFixes events to work properly. Keep in mind that this will
     * also call our own callback so we must be prepared there to not
     * communicate back to the real X server about this SelectionClear
     * event. It already knows about that...
     */
    if (SelectionCallback)
    {
        SelectionInfoRec info = {0};

        info.selection = &CurrentSelections[index];
        info.kind = SelectionSetOwner;
        CallCallbacks(&SelectionCallback, &info);
    }
  }
  else
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: selection already cleared - doing nothing.\n", __func__);
    #endif
  }
}

void nxagentHandleSelectionClearFromXServerByAtom(XlibAtom sel)
{
  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: SelectionClear event for remote selection atom [%lu][%s].\n", __func__, sel, NameForRemoteAtom(sel));
  #endif

  nxagentHandleSelectionClearFromXServerByIndex(nxagentFindRemoteSelectionIndex(sel));
}

void nxagentHandleSelectionClearFromXServer(XEvent *X)
{
  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: SelectionClear event for selection [%lu][%s] window [0x%lx] time [%lu].\n",
              __func__, X->xselectionclear.selection, NameForRemoteAtom(X->xselectionclear.selection),
                  X->xselectionclear.window, X->xselectionclear.time);
  #endif
  nxagentHandleSelectionClearFromXServerByAtom(X->xselectionclear.selection);
}

/*
 * Send a SelectionNotify event as reply to the RequestSelection
 * event X. If success is True take the property from the event, else
 * take None (which reports "failed/denied" to the requestor).
 */
static void replyRequestSelectionToXServer(XEvent *X, Bool success)
{
  XSelectionEvent eventSelection = {
    .requestor = X->xselectionrequest.requestor,
    .selection = X->xselectionrequest.selection,
    .target    = X->xselectionrequest.target,
    .time      = X->xselectionrequest.time,
    .property  = X->xselectionrequest.property
  };

  if (!success)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: denying request\n", __func__);
    #endif
    eventSelection.property = None;
  }

  sendSelectionNotifyEventToXServer(&eventSelection);
}

/*
 * This is called from Events.c dispatch loop on reception of a
 * SelectionRequest event, meaning a client of the real X server wants
 * to have the selection content. The real X server knows the nxagent
 * as selection owner. But in reality one of our windows is the owner,
 * so we must pass the request on to the real owner.
 */
void nxagentHandleSelectionRequestFromXServer(XEvent *X)
{
  XlibAtom target = X->xselectionrequest.target;

  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: Received SelectionRequestEvent from real server: selection [%ld][%s] " \
          "target [%ld][%s] requestor [display[%s]/0x%lx] destination [%ld][%s] time [%lu]\n",
          __func__,
          X->xselectionrequest.selection, NameForRemoteAtom(X->xselectionrequest.selection),
          target, NameForRemoteAtom(target),
          DisplayString(nxagentDisplay), X->xselectionrequest.requestor,
          X->xselectionrequest.property,  NameForRemoteAtom(X->xselectionrequest.property),
          X->xselectionrequest.time);
  if (X->xselectionrequest.requestor == serverWindow)
  {
    fprintf(stderr, "%s: this event has been sent by nxagent!\n", __func__);;
  }
  #endif

  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return;
  }

  /* The selection in this request is none we own. */
  int index = nxagentFindRemoteSelectionIndex(X->xselectionrequest.selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: not owning selection [%ld] - denying request.\n", __func__, X->xselectionrequest.selection);
    #endif

    replyRequestSelectionToXServer(X, False);
    return;
  }

  if (!IS_LOCAL_OWNER(index))
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: no local owner for selection [%ld] - denying request.\n", __func__, X->xselectionrequest.selection);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: lastServers[%d].requestor [0x%lx].\n", __func__, index, lastServers[index].requestor);
  #endif

  /* lastServers[index].requestor is non-NULL (= we are currently in the transfer phase) */
  if (lastServers[index].requestor != None)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: denying additional request during transfer phase.\n", __func__);
    #endif

    replyRequestSelectionToXServer(X, False);
    return;
  }

  if (target == serverTARGETS)
  {
    /*
     * In TextClipboard mode answer with a predefined list of
     * targets. This is just the previous implementation of handling
     * the clipboard.
     */
    if (nxagentOption(TextClipboard))
    {
      /*
       * The selection request target is TARGETS. The requestor is
       * asking for a list of supported data formats.
       *
       * The selection does not matter here, we will return this for
       * PRIMARY and CLIPBOARD.
       *
       * The list is aligned with the one in nxagentConvertSelection()
       * and in isTextTarget().
       */

      XlibAtom targets[] = {XA_STRING,
                            serverUTF8_STRING,
#ifdef SUPPORT_TEXT_TARGET
                            serverTEXT,
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
                            serverCOMPOUND_TEXT,
#endif
                            serverTARGETS,
                            serverTIMESTAMP};
      int numTargets = sizeof(targets) / sizeof(targets[0]);

      #ifdef DEBUG
      fprintf(stderr, "%s: Sending %d available targets:\n", __func__, numTargets);
      for (int i = 0; i < numTargets; i++)
      {
        fprintf(stderr, "%s: %ld %s\n", __func__, targets[i], NameForRemoteAtom(targets[i]));
      }
      #endif

      /*
       * Pass on the requested list by setting the property provided
       * by the requestor accordingly.
       */
      XChangeProperty(nxagentDisplay,
                      X->xselectionrequest.requestor,
                      X->xselectionrequest.property,
                      XInternAtom(nxagentDisplay, "ATOM", 0),
                      32,
                      PropModeReplace,
                      (unsigned char*)targets,
                      numTargets);

      replyRequestSelectionToXServer(X, True);
      return;
    }
    else
    {
      /*
       * Shortcut: Some applications tend to post multiple
       * SelectionRequests. Further it can happen that multiple
       * clients are interested in clipboard content. If we already
       * know the answer and no intermediate SelectionOwner event
       * occurred we can answer with the cached list of targets.
       */

      if (targetCache[index].type == FOR_REMOTE && targetCache[index].forRemote)
      {
        XlibAtom *targets = targetCache[index].forRemote;
        unsigned int numTargets = targetCache[index].numTargets;

        #ifdef DEBUG
        fprintf(stderr, "%s: Sending %d cached targets to remote requestor:\n", __func__, numTargets);
        for (int i = 0; i < numTargets; i++)
        {
          fprintf(stderr, "%s: %ld %s\n", __func__, targets[i], NameForRemoteAtom(targets[i]));
        }
        #endif

        XChangeProperty(nxagentDisplay,
                        X->xselectionrequest.requestor,
                        X->xselectionrequest.property,
                        XInternAtom(nxagentDisplay, "ATOM", 0),
                        32,
                        PropModeReplace,
                        (unsigned char *)targets,
                        numTargets);

        replyRequestSelectionToXServer(X, True);
        return;
      }
    }
  }
  else if (target == serverTIMESTAMP)
  {
    /*
     * Section 2.6.2 of the ICCCM states:
     * TIMESTAMP - To avoid some race conditions, it is important
     * that requestors be able to discover the timestamp the owner
     * used to acquire ownership. Until and unless the protocol is
     * changed so that a GetSelectionOwner request returns the
     * timestamp used to acquire ownership, selection owners must
     * support conversion to TIMESTAMP, returning the timestamp they
     * used to obtain the selection.
     *
     * FIXME: ensure we are reporting an _external_ timestamp
     * FIXME: for a 32 bit property list we need to pass a "long" array, not "char"!
     */

    XChangeProperty(nxagentDisplay,
                    X->xselectionrequest.requestor,
                    X->xselectionrequest.property,
                    XA_INTEGER,
                    32,
                    PropModeReplace,
                    (unsigned char *) &lastSelectionOwner[index].lastTimeChanged,
                    1);
    replyRequestSelectionToXServer(X, True);
    return;
  }
  else if (target == serverMULTIPLE)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [MULTIPLE] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }
  else if (target == serverDELETE)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [DELETE] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }
  else if (target == serverINSERT_SELECTION)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [INSERT_SELECTION] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }
  else if (target == serverINSERT_PROPERTY)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [INSERT_PROPERTY] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }
  else if (target == serverSAVE_TARGETS)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [SAVE_TARGETS] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }
  else if (target == serverTARGET_SIZES)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [TARGET_SIZES] - denying request.\n", __func__);
    #endif
    replyRequestSelectionToXServer(X, False);
    return;
  }

  if (nxagentOption(TextClipboard))
  {
    if (!isTextTarget(target))
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: denying request for non-text target [%ld][%s].\n", __func__,
                  target, NameForRemoteAtom(target));
      #endif

      replyRequestSelectionToXServer(X, False);

      return;
    }
    /* go on, target is acceptable */
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: target [%ld][%s].\n", __func__, target,
              NameForRemoteAtom(target));
  #endif

  /*
   * Reaching this line means the request is a normal, valid
   * request. We can process it now.
   */

  if (!nxagentOption(TextClipboard))
  {
    /*
     * Optimization: if we have a current target cache check if the
     * requested target is supported by the owner. If not we can take
     * a shortcut and deny the request immediately without doing any
     * further communication.
     */
    if (targetCache[index].type == FOR_REMOTE && targetCache[index].forRemote)
    {
      XlibAtom *targets = targetCache[index].forRemote;

      #ifdef DEBUG
      fprintf(stderr, "%s: Checking target validity\n", __func__);
      #endif
      Bool match = False;
      for (int i = 0; i < targetCache[index].numTargets; i++)
      {
        if (targets[i] == target)
        {
          match = True;
          break;
        }
      }
      if (!match)
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: target [%ld][%s] is not supported by the owner - denying request.\n",
                    __func__, X->xselectionrequest.target, NameForRemoteAtom(X->xselectionrequest.target));
        #endif
        replyRequestSelectionToXServer(X, False);
        return;
      }
    }
    else
    {
      /*
       * At this stage we know a remote client has requested a
       * selection target without having retrieved the list of
       * supported targets first.
       */
      #ifdef DEBUG
      if (target != serverTARGETS)
      {
         fprintf(stderr, "%s: WARNING: remote client has not retrieved TARGETS before asking for selection!\n",
                   __func__);
      }
      #endif
    }
  }

  /*
   * This is required for nxagentGetClipboardWindow.
   */
  serverLastRequestedSelection = X->xselectionrequest.selection;

  if (!(nxagentOption(Clipboard) == ClipboardServer ||
        nxagentOption(Clipboard) == ClipboardBoth))
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: clipboard (partly) disabled - denying request.\n", __func__);
    #endif

    /* deny the request */
    replyRequestSelectionToXServer(X, False);
    return;
  }

  /*
   * If one of our clients owns the selection we ask it to copy
   * the selection to the clientCutProperty on nxagent's root
   * window in the first step. We then later push that property's
   * content to the real X server.
   */
  if (IS_LOCAL_OWNER(index))
  {
    /*
     * Store who on the real X server requested the data and how
     * and where it wants to have it.
     */
    lastServers[index].property = X->xselectionrequest.property;
    lastServers[index].requestor = X->xselectionrequest.requestor;
    lastServers[index].target = target;
    lastServers[index].time = X->xselectionrequest.time;

    /* Prepare the request (like XConvertSelection, but locally). */
    xEvent x = {0};
    x.u.u.type = SelectionRequest;
    x.u.selectionRequest.time = GetTimeInMillis();
    x.u.selectionRequest.owner = lastSelectionOwner[index].window;
    x.u.selectionRequest.selection = CurrentSelections[index].selection;
    x.u.selectionRequest.property = clientCutProperty;
    x.u.selectionRequest.requestor = screenInfo.screens[0]->root->drawable.id; /* Fictitious window.*/

    /*
     * Don't send the same window, some programs are clever and
     * verify cut and paste operations inside the same window and
     * don't notify at all.
     *
     * x.u.selectionRequest.requestor = lastSelectionOwner[index].window;
     */

    /*
     * In TextClipboard mode simply use the previous clipboard
     * handling code.
     */
    if (nxagentOption(TextClipboard))
    {
      /* by dimbor */
      if (target != XA_STRING)
      {
        lastServers[index].target = serverUTF8_STRING;
        /* by dimbor (idea from zahvatov) */
        x.u.selectionRequest.target = clientUTF8_STRING;
      }
      else
      {
        x.u.selectionRequest.target = XA_STRING;
      }
    }
    else
    {
      x.u.selectionRequest.target = nxagentRemoteToLocalAtom(target);
    }

    /*
     * Delete property before sending the request to the client as
     * required by ICCCM.
     */
    DeleteProperty(lastSelectionOwner[index].windowPtr, clientCutProperty);

    sendEventToClient(lastSelectionOwner[index].client, &x);

    #ifdef DEBUG
    fprintf(stderr, "%s: sent SelectionRequest event to client %s property [%d][%s] " \
            "target [%d][%s] requestor [0x%x] selection [%d][%s].\n", __func__,
            nxagentClientInfoString(lastSelectionOwner[index].client),
            x.u.selectionRequest.property, NameForLocalAtom(x.u.selectionRequest.property),
            x.u.selectionRequest.target, NameForLocalAtom(x.u.selectionRequest.target),
            x.u.selectionRequest.requestor,
            x.u.selectionRequest.selection, NameForLocalAtom(x.u.selectionRequest.selection));
    #endif
    /*
     * No reply to the Xserver yet - we will do that once the answer
     * of the above sendEventToClient arrives.
     */
  }
  else
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: no local owner for selection [%ld][%s] - denying request.\n", __func__,
                X->xselectionrequest.selection, NameForRemoteAtom(X->xselectionrequest.selection));
    #endif

    /* deny the request */
    replyRequestSelectionToXServer(X, False);
  }
}

/*
 * End the current selection transfer by sending a notification to the
 * client and resetting the corresponding variables and the state
 * machine. If success is False send a None reply, meaning "request
 * denied/failed".
 * Use SELECTION_SUCCESS and SELECTION_FAULT macros for the success
 * value.
 */
static void endTransfer(int index, Bool success)
{
  if (lastClients[index].clientPtr == NULL)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: lastClients[%d].clientPtr is NULL - doing nothing.\n", __func__, index);
    #endif
  }
  else
  {
    #ifdef DEBUG
    if (success == SELECTION_SUCCESS)
      fprintf(stderr, "%s: sending notification to client %s, property [%d][%s]\n", __func__,
                  nxagentClientInfoString(lastClients[index].clientPtr), lastClients[index].property, NameForLocalAtom(lastClients[index].property));
    else
      fprintf(stderr, "%s: sending negative notification to client %s\n", __func__,
                  nxagentClientInfoString(lastClients[index].clientPtr));
    #endif

    sendSelectionNotifyEventToClient(lastClients[index].clientPtr,
                                     lastClients[index].time,
                                     lastClients[index].requestor,
                                     localSelectionAtoms[index],
                                     lastClients[index].target,
                                     success == SELECTION_SUCCESS ? lastClients[index].property : None);
  }

  /*
   * Enable further requests from clients.
   */
  setClientSelectionStage(index, SelectionStageNone);
}

static void transferSelectionFromXServer(int index, int resource)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: resource [%d] lastClients[%d].clientPtr->index [%d].\n", __func__,
              resource, index, lastClients[index].clientPtr -> index);
  #endif
  /* FIXME: can we use this instead of lastClients[index].resource? */
  if (lastClients[index].clientPtr -> index != resource)
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: WARNING! Inconsistent resource [%d] with current client %s.\n", __func__,
                 resource, nxagentClientInfoString(lastClients[index].clientPtr));
    #endif

    endTransfer(index, SELECTION_FAULT);

    return;
  }

  switch (lastClients[index].stage)
  {
    case SelectionStageQuerySize:
    {
      int result;

      printClientSelectionStage(index);

      /*
       * Don't get data yet, just get size. We skip this stage in
       * current implementation and go straight to the data.
       */

      /* Get next free resource slot. */
      int free_resource = NXGetCollectPropertyResource(nxagentDisplay);

      lastClients[index].resource = free_resource;

      if (free_resource == -1)
      {
        #ifdef WARNING
        fprintf(stderr, "%s: WARNING! Asynchronous GetProperty queue full.\n", __func__);
        #endif

        result = -1;
      }
      else
      {
        /* Collect the property and store it with index "free_resource" */
        result = NXCollectProperty(nxagentDisplay,
                                   free_resource,
                                   serverWindow,
                                   serverTransToAgentProperty,
                                   0,
                                   0,
                                   False,
                                   AnyPropertyType);
      }

      if (result == -1)
      {
        #ifdef DEBUG
        fprintf (stderr, "%s: Aborting selection notify procedure for client %s.\n", __func__,
                     nxagentClientInfoString(lastClients[index].clientPtr));
        #endif

        endTransfer(index, SELECTION_FAULT);

        return;
      }

      setClientSelectionStage(index, SelectionStageWaitSize);

      NXFlushDisplay(nxagentDisplay, NXFlushLink);

      break;
    }
    case SelectionStageQueryData:
    {
      int result;

      printClientSelectionStage(index);

      /*
       * Request the selection data now.
       */

      #ifdef DEBUG
      fprintf(stderr, "%s: Getting property content from remote server.\n", __func__);
      #endif

      /* Get next free resource slot. */
      resource = NXGetCollectPropertyResource(nxagentDisplay);

      lastClients[index].resource = resource;

      if (resource == -1)
      {
        #ifdef WARNING
        fprintf(stderr, "%s: WARNING! Asynchronous GetProperty queue full.\n", __func__);
        #endif

        result = -1;
      }
      else
      {
        /*
         * Now initiate kind of an asynchronuos GetProperty()
         * call. Once the property has been retrieved we will
         * receive an NXCollectPropertyNotify event which will then
         * be handled in
         * nxagentCollectPropertyEventFromXServer().
         */
        result = NXCollectProperty(nxagentDisplay,
                                   resource,
                                   serverWindow,
                                   serverTransToAgentProperty,
                                   0,
                                   lastClients[index].propertySize,
                                   False,
                                   AnyPropertyType);
      }

      if (result == -1)
      {
        #ifdef DEBUG
        fprintf (stderr, "%s: Aborting selection notify procedure for client %s.\n", __func__,
                     nxagentClientInfoString(lastClients[index].clientPtr));
        #endif

        endTransfer(index, SELECTION_FAULT);

        return;
      }

      setClientSelectionStage(index, SelectionStageWaitData);

      /*
       * We've seen situations where you had to move the mouse or press a
       * key to let the transfer complete. Flushing here fixed it
       */
      NXFlushDisplay(nxagentDisplay, NXFlushLink);

      break;
    }
    default:
    {
      #ifdef DEBUG
      fprintf (stderr, "%s: WARNING! Inconsistent state [%s] for selection [%d] for client %s.\n", __func__,
                   getClientSelectionStageString(lastClients[index].stage), index,
                       nxagentClientInfoString(lastClients[index].clientPtr));
      #endif

      break;
    }
  }
}

/*
   Called from Events.c/nxagentHandleCollectPropertyEvent
   This event is generated after XChangeProperty(), XDeleteProperty() or
   XGetWindowProperty(delete=True).

   Returncode:
   True: processed
   False: not processed, resource is not ours
*/
Bool nxagentCollectPropertyEventFromXServer(int resource)
{
  XlibAtom              atomReturnType;
  int                   resultFormat;
  unsigned long         ulReturnItems;
  unsigned long         ulReturnBytesLeft;
  unsigned char         *pszReturnData = NULL;

  int index = 0;

  if (resource < 0)
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: resource [%d] is invalid.\n", __func__, resource);
    #endif
    return False;
  }

  /* Determine the selection we are talking about here. */
  for (index = 0; index < nxagentMaxSelections; index++)
  {
    /*
    #ifdef DEBUG
    fprintf(stderr, "%s: lastClients[%d].resource [%d] resource [%d]\n", __func__, index, lastClients[index].resource, resource);
    #endif
    */
    if (lastClients[index].resource == resource)
    {
      #ifdef DEBUG
      fprintf (stderr, "%s: resource [%d] belongs to selection [%d].\n", __func__, resource, index);
      #endif
      break;
    }
  }

  if (index == nxagentMaxSelections)
  {
    /*
    #ifdef DEBUG
    fprintf (stderr, "%s: resource [%d] does not belong to any selection we handle.\n", __func__, resource);
    #endif
    */
    return False;
  }

  /*
   * We have received the notification so we can safely retrieve data
   * from the client structure.
   */

  int result = NXGetCollectedProperty(nxagentDisplay,
                                      resource,
                                      &atomReturnType,
                                      &resultFormat,
                                      &ulReturnItems,
                                      &ulReturnBytesLeft,
                                      &pszReturnData);

  #ifdef DEBUG
  fprintf(stderr, "%s: NXGetCollectedProperty: result [%d]\n", __func__, result);
  fprintf(stderr, "%s:   atomReturnType [%ld]\n", __func__, atomReturnType);
  fprintf(stderr, "%s:   resultFormat [%d]\n", __func__, resultFormat);
  fprintf(stderr, "%s:   ulReturnItems [%lu]\n", __func__, ulReturnItems);
  fprintf(stderr, "%s:   ulReturnBytesLeft [%lu]\n", __func__, ulReturnBytesLeft);
  #endif

  lastClients[index].resource = -1;

  /*
   * ICCCM states: "The requestor must delete the property named in
   * the SelectionNotify once all the data has been retrieved. The
   * requestor should invoke either DeleteProperty or GetProperty
   * (delete==True) after it has successfully retrieved all the data
   * in the selection."
   * FIXME: this uses serverTransToAgentProperty which is shared between
   * all the selections. Could be a problem with simultaneous transfers.
   * FIXME: NXGetCollectedProperty can return 0 and True. Some other
   * functions in this field return False as well. Clean up that
   * mess...
   */
  if (result == True && ulReturnBytesLeft == 0)
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: Retrieved property data - deleting property [%ld][%s] "
             "for ICCCM conformity.\n", __func__, serverTransToAgentProperty,
             NameForRemoteAtom(serverTransToAgentProperty));
    #endif
    XDeleteProperty(nxagentDisplay, serverWindow, serverTransToAgentProperty);
  }

  if (result == 0)
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: Failed to get reply data.\n", __func__);
    #endif

    endTransfer(index, SELECTION_FAULT);
  }
  else if (resultFormat != 8 && resultFormat != 16 && resultFormat != 32)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: WARNING! Invalid property format [%d].\n", __func__, resultFormat);
    #endif

    endTransfer(index, SELECTION_FAULT);
  }
  else
  {
    switch (lastClients[index].stage)
    {
      case SelectionStageWaitSize:
      {
        printClientSelectionStage(index);
        #ifdef DEBUG
        fprintf (stderr, "%s: Got size notify event for client %s.\n", __func__,
                     nxagentClientInfoString(lastClients[index].clientPtr));
        #endif

        if (ulReturnBytesLeft == 0)
        {
          #ifdef DEBUG
          fprintf (stderr, "%s: data size is [0] - aborting selection notify procedure.\n", __func__);
          #endif

          endTransfer(index, SELECTION_FAULT);
        }
        else
        {
          #ifdef DEBUG
          fprintf(stderr, "%s: Got property size [%lu] from remote server.\n", __func__, ulReturnBytesLeft);
          #endif

          /*
           * Request the selection data now.
           */
          lastClients[index].propertySize = ulReturnBytesLeft;
          setClientSelectionStage(index, SelectionStageQueryData);

          transferSelectionFromXServer(index, resource);
        }
        break;
      }
      case SelectionStageWaitData:
      {
        printClientSelectionStage(index);
        #ifdef DEBUG
        fprintf (stderr, "%s: Got data notify event for waiting client %s.\n", __func__,
                     nxagentClientInfoString(lastClients[index].clientPtr));
        #endif

        if (ulReturnBytesLeft != 0)
        {
          #ifdef DEBUG
          fprintf (stderr, "%s: not all content could be retrieved - [%lu] bytes left - aborting selection notify procedure.\n", __func__, ulReturnBytesLeft);
          #endif

          endTransfer(index, SELECTION_FAULT);
        }
        else
        {
          #ifdef DEBUG
          fprintf(stderr, "%s: Got property content from remote server. [%lu] items with format [%d] = [%lu] bytes.\n", __func__, ulReturnItems, resultFormat, (ulReturnItems * resultFormat/8));
          #endif

          if (lastClients[index].target == clientTARGETS)
          {
            Atom * targets = calloc(sizeof(Atom), ulReturnItems);
            if (targets == NULL)
            {
              #ifdef WARNING
              fprintf(stderr, "%s: WARNING! Could not alloc memory for clipboard targets transmission.\n", __func__);
              #endif

              /* operation failed */
              endTransfer(index, SELECTION_FAULT);
            }
            else
            {
              /* fprintf(stderr, "sizeof(Atom) [%lu], sizeof(XlibAtom) [%lu], sizeof(long) [%lu], sizeof(CARD32) [%lu] sizeof(INT32) [%lu]\n", sizeof(Atom), sizeof(XlibAtom), sizeof(long), sizeof(CARD32), sizeof(INT32)); */

              Atom *addr = targets;
              unsigned int numTargets = ulReturnItems;

              for (int i = 0; i < numTargets; i++)
              {
                XlibAtom remote = *((XlibAtom*)(pszReturnData + i*resultFormat/8));
                Atom local = nxagentRemoteToLocalAtom(remote);
                *(addr++) = local;

                #ifdef DEBUG
                fprintf(stderr, "%s: converting atom: remote [%u][%s] -> local [%u][%s]\n", __func__,
                            (unsigned int)remote, NameForRemoteAtom(remote), local, NameForLocalAtom(local));
                #endif
              }
              ChangeWindowProperty(lastClients[index].windowPtr,
                                   lastClients[index].property,
                                   MakeAtom("ATOM", 4, 1),
                                   32, PropModeReplace,
                                   ulReturnItems, (unsigned char*)targets, 1);

              cacheTargetsForLocal(index, targets, numTargets);

              endTransfer(index, SELECTION_SUCCESS);
            }
          }
          else
          {
            ChangeWindowProperty(lastClients[index].windowPtr,
                                 lastClients[index].property,
                                 nxagentRemoteToLocalAtom(atomReturnType),
                                 resultFormat, PropModeReplace,
                                 ulReturnItems, pszReturnData, 1);

            #ifdef DEBUG
            fprintf(stderr, "%s: Selection property [%d][%s] changed to resultFormat [%d] returnType [%ld][%s] len [%d]"
                    #ifdef PRINT_CLIPBOARD_CONTENT_ON_DEBUG
                    /* FIXME: only print the string if the resultFormat is 8 */
                    " value [\"%*.*s\"...] hex [0x%2.2x%2.2x%2.2x%2.2x]"
                    #endif
                    "\n", __func__,
                    lastClients[index].property,
                    validateString(NameForLocalAtom(lastClients[index].property)),
                    resultFormat,
                    atomReturnType, NameForRemoteAtom(atomReturnType),
                    (int)ulReturnItems * resultFormat / 8
                    #ifdef PRINT_CLIPBOARD_CONTENT_ON_DEBUG
                    ,(int)(min(20, ulReturnItems * resultFormat / 8)),
                    (int)(min(20, ulReturnItems * resultFormat / 8)),
                    pszReturnData,
                    pszReturnData[0], pszReturnData[1], pszReturnData[2], pszReturnData[3]
                    #endif
                   );
            #endif

            endTransfer(index, SELECTION_SUCCESS);
          }
        }
        break;
      }
      default:
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: WARNING! Inconsistent state [%s] for client %s.\n", __func__,
                    getClientSelectionStageString(lastClients[index].stage),
                        nxagentClientInfoString(lastClients[index].clientPtr));
        #endif
        break;
      }
    }
  }
  SAFE_XFree(pszReturnData);

  return True;
}

/*
 * This is _only_ called from Events.c dispatch loop on reception of a
 * SelectionNotify event from the real X server. These events are
 * sent out by nxagent itself!
 */
void nxagentHandleSelectionNotifyFromXServer(XEvent *X)
{
  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return;
  }

  XSelectionEvent *E = (XSelectionEvent *)X;
  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: Received SelectionNotify event from real X server, property " \
              "[%ld][%s] requestor [0x%lx] selection [%s] target [%ld][%s] time [%lu] send_event [%d].\n",
                  __func__, E->property, NameForRemoteAtom(E->property), E->requestor,
                      NameForRemoteAtom(E->selection), E->target,
                          NameForRemoteAtom(E->target), E->time, E->send_event);

  /* this has not been SENT by nxagent but is the answer to a request of nxagent */
  if (E->requestor == serverWindow)
  {
    fprintf(stderr, "%s: requestor is nxagent's serverWindow!\n", __func__);;
  }
  #endif

  /* determine the selection we are talking about here */
  int index = nxagentFindRemoteSelectionIndex(E->selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf (stderr, "%s: unknown selection [%ld] .\n", __func__, E->selection);
    #endif
    return;
  }

  printClientSelectionStage(index);

  /*
   * If the property is serverTransFromAgentProperty this means we are
   * transferring data FROM the agent TO the server.
   */

  if (X->xselection.property != serverTransFromAgentProperty && lastClients[index].windowPtr != NULL)
  {
    /*
     * We reach here after a paste inside the nxagent, triggered by
     * the XConvertSelection call in nxagentConvertSelection(). This
     * means that data we need has been transferred to the
     * serverTransToAgentProperty of the serverWindow (our window on
     * the real X server). We now need to transfer it to the original
     * requestor, which is stored in the lastClients[index].* variables.
     */

    #ifdef DEBUG
    nxagentDumpClipboardStat();
    #endif
    if (lastClients[index].stage == SelectionStageNone)
    {
      if (X->xselection.property == serverTransToAgentProperty)
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: Starting selection transferral for client %s.\n", __func__,
                nxagentClientInfoString(lastClients[index].clientPtr));
        #endif

        /*
         * The state machine is able to work in two phases. In the first
         * phase we get the size of property data, in the second we get
         * the actual data. We save a round-trip by requesting a prede-
         * termined amount of data in a single GetProperty and by discar-
         * ding the remaining part. This is not the optimal solution (we
         * could get the remaining part if it doesn't fit in a single
         * reply) but, at least with text, it should work in most situa-
         * tions.
         */

        setClientSelectionStage(index, SelectionStageQueryData);
        lastClients[index].propertySize = 262144;

        transferSelectionFromXServer(index, lastClients[index].clientPtr -> index);
      }
      else if (X->xselection.property == 0)
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: WARNING! Resetting selection transferral for client [%d] because of failure notification from real X server.\n", __func__,
                    CLINDEX(lastClients[index].clientPtr));
        #endif
        endTransfer(index, SELECTION_FAULT);
      }
      else
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: Unexpected property [%ld][%s] - reporting conversion failure.\n",
                    __func__, X->xselection.property, NameForRemoteAtom(X->xselection.property));
        #endif
        endTransfer(index, SELECTION_FAULT);
      }
    }
    else
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: WARNING! Resetting selection transferral for client [%d] because of unexpected stage.\n", __func__,
                  CLINDEX(lastClients[index].clientPtr));
      #endif

      endTransfer(index, SELECTION_FAULT);
    }
  }
  else
  {
    handlePropertyTransferFromAgentToXserver(index, X->xselection.property);
  }
}

void handlePropertyTransferFromAgentToXserver(int index, XlibAtom property)
{
  /*
   * If the last owner was a local one, read the
   * clientCutProperty and push the contents to the
   * lastServers[index].requestor on the real X server.
   */
  if (IS_LOCAL_OWNER(index) &&
         lastSelectionOwner[index].windowPtr != NULL &&
             property == serverTransFromAgentProperty)
  {
    Atom            atomReturnType;
    int             resultFormat;
    unsigned long   ulReturnItems;
    unsigned long   ulReturnBytesLeft;
    unsigned char   *pszReturnData = NULL;

    /* First get size values ... */
    int result = GetWindowProperty(lastSelectionOwner[index].windowPtr, clientCutProperty, 0, 0, False,
                                       AnyPropertyType, &atomReturnType, &resultFormat,
                                           &ulReturnItems, &ulReturnBytesLeft, &pszReturnData);

    #ifdef DEBUG
    fprintf(stderr, "%s: GetWindowProperty() window [0x%x] property [%d][%s] returned [%s]\n", __func__,
                lastSelectionOwner[index].window, clientCutProperty, NameForLocalAtom(clientCutProperty),
                    getXErrorString(result));
    #endif
    if (result == BadAlloc || result == BadAtom ||
            result == BadWindow || result == BadValue)
    {
      lastServers[index].property = None;
    }
    else
    {
      /* ... then use the size values for the actual request data. */
      result = GetWindowProperty(lastSelectionOwner[index].windowPtr, clientCutProperty, 0,
                                     ulReturnBytesLeft, False, AnyPropertyType, &atomReturnType,
                                         &resultFormat, &ulReturnItems, &ulReturnBytesLeft,
                                             &pszReturnData);
      #ifdef DEBUG
      fprintf(stderr, "%s: GetWindowProperty() window [0x%x] property [%d][%s] returned [%s]\n", __func__,
                  lastSelectionOwner[index].window, clientCutProperty, NameForLocalAtom(clientCutProperty),
                      getXErrorString(result));
      #endif

      if (result == BadAlloc || result == BadAtom ||
              result == BadWindow || result == BadValue)
      {
        lastServers[index].property = None;
      }
      else
      {
        if (lastServers[index].target == serverTARGETS)
        {
          #ifdef DEBUG
          fprintf(stderr, "%s: ulReturnItems [%ld]\n", __func__, ulReturnItems);
          fprintf(stderr, "%s: resultformat [%d]\n", __func__, resultFormat);
          #endif

          XlibAtom * targets = calloc(sizeof(XlibAtom), ulReturnItems);
          if (targets == NULL)
          {
            #ifdef WARNING
            fprintf(stderr, "%s: WARNING! Could not alloc memory for clipboard targets transmission.\n", __func__);
            #endif
            /* This will effectively lead to the request being answered as failed. */
            lastServers[index].property = None;
          }
          else
          {
            /* Convert the targets to remote atoms. */
            XlibAtom *addr = targets;
            unsigned int numTargets = ulReturnItems;

            for (int i = 0; i < numTargets; i++)
            {
              Atom local = *((Atom*)(pszReturnData + i*resultFormat/8));
              XlibAtom remote = nxagentLocalToRemoteAtom(local);
              *(addr++) = remote;

              #ifdef DEBUG
              fprintf(stderr, "%s: converting atom: local [%d][%s] -> remote [%ld][%s]\n", __func__,
                          local, NameForLocalAtom(local), remote, NameForRemoteAtom(remote));
              #endif
            }

            /* FIXME: do we need to take care of swapping byte order here? */
            XChangeProperty(nxagentDisplay,
                            lastServers[index].requestor,
                            lastServers[index].property,
                            XInternAtom(nxagentDisplay, "ATOM", 0),
                            32,
                            PropModeReplace,
                            (unsigned char*)targets,
                            numTargets);

            cacheTargetsForRemote(index, targets, numTargets);
          }
        }
        else
        {
          /* Fill the property on the requestor with the requested data. */
          /* The XChangeProperty source code reveals it will always
             return 1, no matter what, so no need to check the result */
          XChangeProperty(nxagentDisplay,
                          lastServers[index].requestor,
                          lastServers[index].property,
                          nxagentLocalToRemoteAtom(atomReturnType),
                          resultFormat,
                          PropModeReplace,
                          pszReturnData,
                          ulReturnItems);
          #ifdef DEBUG
          {
            fprintf(stderr, "%s: XChangeProperty sent to window [0x%lx] for property [%ld][%s] resultFormat [%d] returnType [%ld][%s] len [%d]"
                    #ifdef PRINT_CLIPBOARD_CONTENT_ON_DEBUG
                    /* FIXME: only print the string if the resultFormat is 8 */
                    " value [\"%*.*s\"...] hex [0x%2.2x%2.2x%2.2x%2.2x]"
                    #endif
                    "\n",
                    __func__,
                    lastServers[index].requestor,
                    lastServers[index].property,
                    NameForRemoteAtom(lastServers[index].property),
                    resultFormat,
                    nxagentLocalToRemoteAtom(atomReturnType), NameForLocalAtom(atomReturnType),
                    (int)ulReturnItems * resultFormat / 8
                    #ifdef PRINT_CLIPBOARD_CONTENT_ON_DEBUG
                    ,(int)(min(20, ulReturnItems * 8 / 8)),
                    (int)(min(20, ulReturnItems * 8 / 8)),
                    pszReturnData,
                    pszReturnData[0], pszReturnData[1], pszReturnData[2], pszReturnData[3]
                    #endif
                    );
          }
          #endif
        }

        /* FIXME: free it or not? */
        /*
         * SAFE_XFree(pszReturnData);
         */
      }
    }

    /*
     * Inform the initial requestor that the requested data has
     * arrived in the desired property. If we have been unable to
     * get the data from the owner XChangeProperty will not have
     * been called and lastServers[index].property will be None which
     * effectively will send a "Request denied" to the initial
     * requestor.
     */
    replyPendingRequestSelectionToXServer(index, True);
  }
}

/*
 * This is similar to replyRequestSelectionToXServer(), but gets the
 * required values from a stored request instead of an XEvent
 * structure.
 */
void replyPendingRequestSelectionToXServer(int index, Bool success)
{
  if (lastServers[index].requestor == None)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: no pending request for index [%d] - doing nothing\n", __func__, index);
    #endif
  }
  else
  {
    XSelectionEvent eventSelection = {
      .requestor = lastServers[index].requestor,
      .selection = remoteSelectionAtoms[index],
      .target    = lastServers[index].target,
      .time      = lastServers[index].time,
      .property  = success ? lastServers[index].property : None,
    };

    #ifdef DEBUG
    fprintf(stderr, "%s: Sending %s SelectionNotify event to requestor [%p].\n", __func__,
                success ? "positive" : "negative", (void *)eventSelection.requestor);
    #endif

    sendSelectionNotifyEventToXServer(&eventSelection);

    lastServers[index].requestor = None; /* allow further request */
    lastServers[index].property = 0;
    lastServers[index].target = 0;
    lastServers[index].time = 0;
  }
}

#if 0
/* FIXME: currently unused */
/*
 * Let nxagent's serverWindow acquire the selection. All requests from
 * the real X server (or its clients) will be sent to this window. The
 * real X server never communicates with our windows directly.
 */
static void resetSelectionOwnerOnXServer(void)
{
  if (lastServers[index].requestor != None)
  {
    /*
     * We are in the process of communicating back and forth between
     * real X server and nxagent's clients - let's not disturb.
     */
    #if defined(TEST) || defined(DEBUG)
    fprintf(stderr, "%s: WARNING! Requestor window [0x%x] already set.\n", __func__,
                lastServers[index].requestor);
    #endif

    /* FIXME: maybe we should put back the event that lead us here. */
    return;
  }

  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    XSetSelectionOwner(nxagentDisplay, remoteSelectionAtoms[index], serverWindow, CurrentTime);

    #ifdef DEBUG
    fprintf(stderr, "%s: Reset selection state for selection [%d].\n", __func__, index);
    #endif

    clearSelectionOwnerData(index);

    setClientSelectionStage(index, SelectionStageNone);

    invalidateTargetCache(index);

    /* Hmm, this is already None when reaching here. */
    lastServers[index].requestor = None;
  }
}
#endif

#ifdef NXAGENT_CLIPBOARD

/*
 * The callback is called from dix.
 */
void nxagentSetSelectionCallback(CallbackListPtr *callbacks, void *data,
                                   void *args)
{
  SelectionInfoRec *info = (SelectionInfoRec *)args;

  #ifdef DEBUG
  fprintf(stderr, "---------\n");
  if (info->kind == SelectionSetOwner)
  {
    fprintf(stderr, "%s: SelectionCallbackKind [SelectionSetOwner]\n", __func__);
  }
  else if (info->kind == SelectionWindowDestroy)
  {
    fprintf(stderr, "%s: SelectionCallbackKind [SelectionWindowDestroy]\n", __func__);
  }
  else if (info->kind == SelectionClientClose)
  {
    fprintf(stderr, "%s: SelectionCallbackKind [SelectionClientClose]\n", __func__);
  }
  else
  {
    fprintf(stderr, "%s: SelectionCallbackKind [unknown]\n", __func__);
  }
  #endif

  Selection * pCurSel = (Selection *)info->selection;

  int index = nxagentFindCurrentSelectionIndex(pCurSel->selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: selection [%s] can/will not be handled by the clipboard code\n", __func__, NameForLocalAtom(pCurSel->selection));
    #endif
    return;
  }

  #ifdef DEBUG
  printSelectionStat(index);
  #endif

  if (CurrentSelections[index].client == NullClient &&
      CurrentSelections[index].pWin == (WindowPtr)None &&
      CurrentSelections[index].window == screenInfo.screens[0]->root->drawable.id &&
      lastSelectionOwner[index].client == NullClient &&
      lastSelectionOwner[index].window == None &&
      lastSelectionOwner[index].windowPtr == NULL)
  {
    /*
     * No need to propagate anything to the real X server because this
     * callback was triggered by a SelectionClear from the real X
     * server. See nxagentHandleSelectionClearFromXServer
     */
    #ifdef DEBUG
    fprintf(stderr, "%s: aborting callback because it was triggered by nxagent\n", __func__);
    #endif
    return;
  }

  /*
   * Always invalidate the target cache for the relevant selection.
   * This ensures not having invalid data in the cache.
   */
  invalidateTargetCache(index);

  if (nxagentOption(Clipboard) != ClipboardNone) /* FIXME: shouldn't we also check for != ClipboardClient? */
  {
    Selection *pSel = NULL;
    Selection nullSel = {
                         .client = NullClient,
                         .window = None,
                         .pWin = NULL,
                         .selection = pCurSel->selection,
                         .lastTimeChanged = pCurSel->lastTimeChanged
    };

    if (info->kind == SelectionSetOwner)
    {
      pSel = pCurSel;
    }
    else if (info->kind == SelectionWindowDestroy)
    {
      if (pCurSel->window == lastSelectionOwner[index].window)
      {
        pSel = &nullSel;
      }
    }
    else if (info->kind == SelectionClientClose)
    {
      if (pCurSel->client == lastSelectionOwner[index].client)
      {
        pSel = &nullSel;
      }
    }
    else
    {
      #ifdef WARNING
      fprintf(stderr, "%s: WARNING: unknown kind [%d] - data corruption?\n", __func__, info->kind);
      #endif
    }

    if (pSel)
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: calling setSelectionOwnerOnXServer\n", __func__);
      #endif
      setSelectionOwnerOnXServer(pSel);
    }
  }
}
#endif

/*
 * This is called from the nxagentSetSelectionCallback, so it is using
 * local Atoms.
 */
static void setSelectionOwnerOnXServer(Selection *pSelection)
{
  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return;
  }

  int index = nxagentFindCurrentSelectionIndex(pSelection->selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: selection [%s] can/will not be handled by the clipboard code\n", __func__, NameForLocalAtom(pSelection->selection));
    #endif
    return;
  }

  #ifdef DEBUG
  printSelectionStat(index);
  #endif


  /*
   * There's an X client on the real X server waiting for a
   * reply. That reply will never come because now we are the
   * owner so let's be fair and cancel that request.
   */
  replyPendingRequestSelectionToXServer(index, False);

  /*
   * Inform the real X server that our serverWindow is the
   * clipboard owner.
   * https://www.freedesktop.org/wiki/ClipboardManager/ states
   * "In order to support peers who use the XFIXES extension to
   * watch clipboard ownership changes, clipboard owners should
   * reacquire the clipboard whenever the content or metadata (e.g
   * the list of supported targets) changes."
   * If pWin is NULL this is a SelectionClear.
   */
  #ifdef DEBUG
  if (pSelection->pWin)
  {
    fprintf(stderr, "%s: Setting selection owner to serverwindow ([0x%lx]).\n", __func__,
                serverWindow);
  }
  #endif
  XSetSelectionOwner(nxagentDisplay, remoteSelectionAtoms[index], pSelection->pWin ? serverWindow : 0, CurrentTime);

  /*
   * The real owner window (inside nxagent) is stored in
   * lastSelectionOwner[index].window.
   * lastSelectionOwner[index].windowPtr points to the struct that
   * contains all information about the owner window.
   */
  storeSelectionOwnerData(index, pSelection);

  setClientSelectionStage(index, SelectionStageNone);

  /*
   * This will be repeated on reception of the SelectionOwner
   * callback but we cannot be sure if there are any intermediate
   * requests in the queue already so better do it here, too.
   */
  invalidateTargetCache(index);

/*
FIXME

FIXME2: instead of XGetSelectionOwner we could check if the Xfixes
        SetSelectionOwner event has arrived in the event queue;
        possibly saving one roundtrip.

  if (XGetSelectionOwner(nxagentDisplay, pSelection->selection) == serverWindow)
  {
    fprintf (stderr, "%s: SetSelectionOwner OK\n", __func__);

    lastSelectionOwnerSelection = pSelection;
    lastSelectionOwnerClient = pSelection->client;
    lastSelectionOwnerWindow = pSelection->window;
    lastSelectionOwnerWindowPtr = pSelection->pWin;

    setClientSelectionStage(index, SelectionStageNone);

    lastServers[index].requestor = None;
  }
  else
  {
     fprintf (stderr, "%s: SetSelectionOwner failed\n", __func__);
  }
*/
}

/*
 * This is called from dix (ProcConvertSelection) if an nxagent client
 * issues a ConvertSelection request. So all the Atoms are local.
 * return codes:
 * 0: let dix process the request
 * 1: don't let dix process the request
 */
int nxagentConvertSelection(ClientPtr client, WindowPtr pWin, Atom selection,
                                Window requestor, Atom property, Atom target, Time time)
{
  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: client %s requests sel [%s] "
              "on window [0x%x] prop [%d][%s] target [%d][%s] time [%u].\n", __func__,
                  nxagentClientInfoString(client), NameForLocalAtom(selection), requestor,
                      property, NameForLocalAtom(property),
                          target, NameForLocalAtom(target), time);
  #endif

  /* We cannot use NameForLocalAtom() here! FIXME: Why not? */
  if (NameForAtom(target) == NULL)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: cannot find name for target Atom [%d] - returning\n", __func__, target);
    #endif
    return 1;
  }

  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return 0;
  }

  if (nxagentOption(Clipboard) == ClipboardServer)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard mode 'server' - doing nothing.\n", __func__);
    #endif
    return 0;
  }

  int index = nxagentFindCurrentSelectionIndex(selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: cannot find index for selection [%u]\n", __func__, selection);
    #endif
    return 0;
  }

  if (IS_LOCAL_OWNER(index))
  {
    /*
     * There is a client owner on the agent side, let normal dix stuff happen.
     */
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard is owned by local client - let dix process the request\n", __func__);
    #endif
    return 0;
  }

  /*
   * If lastClients[index].windowPtr is set we are waiting for an
   * answer from the real X server. If that answer takes more than 5
   * seconds we consider the conversion failed and tell our client
   * about that. The new request that lead us here is then processed.
   */
  #ifdef TEST
  fprintf(stderr, "%s: lastClients[%d].windowPtr [0x%lx].\n", __func__, index, (unsigned long)lastClients[index].windowPtr);
  #endif

  if (lastClients[index].windowPtr != NULL)
  {
    #ifdef TEST
    fprintf(stderr, "%s: lastClients[%d].windowPtr != NULL.\n", __func__, index);
    #endif

    #ifdef DEBUG
    fprintf(stderr, "%s: localSelectionAtoms[%d] [%d] - selection [%d]\n", __func__, index, localSelectionAtoms[index], selection);
    #endif

    if ((GetTimeInMillis() - lastClients[index].reqTime) >= CONVERSION_TIMEOUT)
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: timeout expired on previous request - "
                  "notifying failure to waiting client\n", __func__);
      #endif

      /* Notify the waiting client of the failure. */
      endTransfer(index, SELECTION_FAULT);

      /* Do NOT return here but process the new request instead! */
    }
    else
    {
      /*
       * We got another convert request while already waiting for an
       * answer from the real X server to a previous convert request
       * for this selection, which we cannot handle (yet). So return
       * an error for the new request.
       */
      #ifdef DEBUG
      fprintf(stderr, "%s: got new request "
                  "before timeout expired on previous request, notifying failure to client %s\n",
                      __func__, nxagentClientInfoString(client));
      #endif

      /* Notify the sender of the new request of the failure. */
      sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);

      return 1;
    }
  }

  /*
   * The selection request target is TARGETS. The requestor is asking
   * for a list of supported data formats.
   */

  if (target == clientTARGETS)
  {
    /*
     * In TextClipboard mode answer with a predefined list that was used
     * in previous versions.
     */
    if (nxagentOption(TextClipboard))
    {
      /*
       * The list is aligned with the one in
       * nxagentHandleSelectionRequestFromXServer.
       */
      Atom targets[] = {XA_STRING,
                        clientUTF8_STRING,
#ifdef SUPPORT_TEXT_TARGET
                        clientTEXT,
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
                        clientCOMPOUND_TEXT,
#endif
                        clientTARGETS,
                        clientTIMESTAMP};
      int numTargets = sizeof(targets) / sizeof(targets[0]);

      #ifdef DEBUG
      fprintf(stderr, "%s: Sending %d available targets:\n", __func__, numTargets);
      for (int i = 0; i < numTargets; i++)
      {
        fprintf(stderr, "%s: %d %s\n", __func__, targets[i], NameForLocalAtom(targets[i]));
      }
      #endif

      ChangeWindowProperty(pWin,
                           property,
                           MakeAtom("ATOM", 4, 1),
                           sizeof(Atom)*8,
                           PropModeReplace,
                           numTargets,
                           targets,
                           1);

      sendSelectionNotifyEventToClient(client, time, requestor, selection,
                                           target, property);

      return 1;
    }
    else
    {
      /*
       * Shortcut: Some applications tend to post multiple
       * SelectionRequests. Further it can happen that multiple
       * clients are interested in clipboard content. If we already
       * know the answer and no intermediate SelectionOwner event
       * occurred we can answer with the cached list of targets.
       */

      if (targetCache[index].type == FOR_LOCAL && targetCache[index].forLocal)
      {
        Atom *targets = targetCache[index].forLocal;
        int numTargets = targetCache[index].numTargets;

        #ifdef DEBUG
        fprintf(stderr, "%s: Sending %d cached targets to local client:\n", __func__, numTargets);
        for (int i = 0; i < numTargets; i++)
        {
          fprintf(stderr, "%s: %d %s\n", __func__, targets[i], NameForLocalAtom(targets[i]));
        }
        #endif

        ChangeWindowProperty(pWin,
                             property,
                             MakeAtom("ATOM", 4, 1),
                             sizeof(Atom)*8,
                             PropModeReplace,
                             numTargets,
                             targets,
                             1);

        sendSelectionNotifyEventToClient(client, time, requestor, selection,
                                             target, property);

        return 1;
      }

      /*
       * Do nothing - TARGETS will be handled like any other target
       * and passed on to the owner on the remote side.
       */
    }
  }

  /*
   * Section 2.6.2 of the ICCCM states:
   * "TIMESTAMP - To avoid some race conditions, it is important
   * that requestors be able to discover the timestamp the owner
   * used to acquire ownership. Until and unless the protocol is
   * changed so that a GetSelectionOwner request returns the
   * timestamp used to acquire ownership, selection owners must
   * support conversion to TIMESTAMP, returning the timestamp they
   * used to obtain the selection."
   */
  else if (target == clientTIMESTAMP)
  {
    /*
     * From ICCCM:
     * "If the specified property is not None, the owner should place
     * the data resulting from converting the selection into the
     * specified property on the requestor window and should set the
     * property's type to some appropriate value, which need not be
     * the same as the specified target."
     */
    ChangeWindowProperty(pWin,
                         property,
                         XA_INTEGER,
                         32,
                         PropModeReplace,
                         1,
                         (unsigned char *) &lastSelectionOwner[index].lastTimeChanged,
                         1);
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, property);
    return 1;
  }
  else if (target == clientMULTIPLE)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [MULTIPLE] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }
  else if (target == clientDELETE)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [DELETE] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }
  else if (target == clientINSERT_SELECTION)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [INSERT_SELECTION] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }
  else if (target == clientINSERT_PROPERTY)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [INSERT_PROPERTY] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }
  else if (target == clientSAVE_TARGETS)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [SAVE_TARGETS] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }
  else if (target == clientTARGET_SIZES)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: (currently) unsupported target [TARGET_SIZES] - denying request.\n", __func__);
    #endif
    sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
    return 1;
  }

  /* In TextClipboard mode we reject all non-text targets. */
  if (nxagentOption(TextClipboard))
  {
    if (!isTextTarget(translateLocalToRemoteTarget(target)))
    {
      #ifdef DEBUG
      fprintf(stderr, "%s: denying request for non-text target [%d][%s].\n", __func__,
                  target, NameForLocalAtom(target));
      #endif

      sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
      return 1;
    }
    /* Go on, target is acceptable! */
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: target [%d][%s].\n", __func__, target,
              NameForLocalAtom(target));
  #endif

  if (!nxagentOption(TextClipboard))
  {
    /*
     * Optimization: if we have a current target cache check if the
     * requested target is supported by the owner. If not we can take
     * a shortcut and deny the request immediately without doing any
     * further communication
     */
    if (targetCache[index].type == FOR_LOCAL && targetCache[index].forLocal)
    {
      Atom *targets = targetCache[index].forLocal;

      #ifdef DEBUG
      fprintf(stderr, "%s: Checking target validity\n", __func__);
      #endif
      Bool match = False;
      for (int i = 0; i < targetCache[index].numTargets; i++)
      {
        if (targets[i] == target)
        {
          match = True;
          break;
        }
      }
      if (!match)
      {
        #ifdef DEBUG
        fprintf(stderr, "%s: target [%d][%s] is not supported by the owner - denying request.\n",
                    __func__, target, NameForLocalAtom(target));
        #endif
        sendSelectionNotifyEventToClient(client, time, requestor, selection, target, None);
        return 1;
      }
    }
    else
    {
      /*
       * At this stage we know a client has asked for a selection
       * target without having retrieved the list of supported targets
       * first.
       */
      #ifdef DEBUG
      if (target != clientTARGETS)
      {
         fprintf(stderr, "%s: WARNING: client has not retrieved TARGETS before asking for selection!\n",
                   __func__);
      }
      #endif
    }
  }

  if (lastClients[index].clientPtr == client)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: same client as previous request\n", __func__);
    #endif

    if (GetTimeInMillis() - lastClients[index].reqTime < ACCUM_TIME)
    {
      /*
       * The same client made consecutive requests of clipboard content
       * with less than 5 seconds time interval between them.
       */
       #ifdef DEBUG
       fprintf(stderr, "%s: Consecutives request from client %s selection [%u] "
                  "elapsed time [%u] clientAccum [%d]\n", __func__,
                      nxagentClientInfoString(client),
                          selection, GetTimeInMillis() - lastClients[index].reqTime,
                              clientAccum);
       #endif

       clientAccum++;
    }
  }
  else
  {
    /*
     * Reset clientAccum as now another client requested the clipboard
     * content.
     */
    clientAccum = 0;
  }

  setClientSelectionStage(index, SelectionStageNone);

  /*
   * Store the original requestor, we need that later after
   * serverTransToAgentProperty has been filled with the desired
   * selection content.
   */
  lastClients[index].requestor = requestor;
  lastClients[index].windowPtr = pWin;
  lastClients[index].clientPtr = client;
  lastClients[index].time = time;
  lastClients[index].property = property;
  lastClients[index].target = target;
  /* If the last client request time is more than 5s ago update it. Why? */
  if ((GetTimeInMillis() - lastClients[index].reqTime) >= CONVERSION_TIMEOUT)
    lastClients[index].reqTime = GetTimeInMillis();

  XlibAtom remSelection = translateLocalToRemoteSelection(selection);
  XlibAtom remTarget = translateLocalToRemoteTarget(target);
  XlibAtom remProperty = serverTransToAgentProperty;

  #ifdef DEBUG
  fprintf(stderr, "%s: replacing local by remote property: [%d][%s] -> [%ld][%s]\n",
              __func__, property, NameForLocalAtom(property),
                  remProperty, "NX_CUT_BUFFER_SERVER");
  #endif

  #ifdef DEBUG
  fprintf(stderr, "%s: Sending XConvertSelection to real X server: requestor [0x%lx] target [%ld][%s] property [%ld][%s] selection [%ld][%s] time [0][CurrentTime]\n", __func__,
          serverWindow, remTarget, NameForRemoteAtom(remTarget),
              remProperty, NameForRemoteAtom(remProperty),
                  remSelection, NameForRemoteAtom(remSelection));
  #endif

  /*
   * ICCCM: "It is necessary for requestors to delete the property
   * before issuing the request so that the target can later be
   * extended to take parameters without introducing an
   * incompatibility. Also note that the requestor of a selection need
   * not know the client that owns the selection nor the window on
   * which the selection was acquired."
   */

  XDeleteProperty(nxagentDisplay, serverWindow, remProperty);

  /*
   * FIXME: ICCCM states: "Clients should not use CurrentTime for the
   * time argument of a ConvertSelection request. Instead, they should
   * use the timestamp of the event that caused the request to be
   * made."  Well, the event that that caused this came from an
   * nxagent _client_ but we are a client to the real X server, which
   * has an own time., we cannot use its time there. So what time
   * would be correct here?
   */

  UpdateCurrentTime();

  XConvertSelection(nxagentDisplay, remSelection, remTarget, remProperty,
                        serverWindow, CurrentTime);

  NXFlushDisplay(nxagentDisplay, NXFlushLink);

  /* XConvertSelection will always return 1 (check the source!), so no
     need to check the return code. */

  #ifdef DEBUG
  fprintf(stderr, "%s: Sent XConvertSelection\n", __func__);
  #endif

  return 1;
}

/*
 * FIXME: do we still need this special treatment? Can't we just
 * call nxagentLocalToRemoteAtom() everywhere?
 */
XlibAtom translateLocalToRemoteSelection(Atom local)
{
  /*
   * On the real server, the right CLIPBOARD atom is
   * XInternAtom(nxagentDisplay, "CLIPBOARD", 1), which is stored in
   * remoteSelectionAtoms[nxagentClipboardSelection]. For
   * PRIMARY there's nothing to map because that is identical on all
   * X servers (defined in Xatom.h). We do it anyway so we do not
   * require a special treatment.
   */

  XlibAtom remote = -1;

  for (int index = 0; index < nxagentMaxSelections; index++)
  {
    if (local == localSelectionAtoms[index])
    {
      remote = remoteSelectionAtoms[index];
      break;
    }
  }

  if (remote == -1)
  {
    remote = nxagentLocalToRemoteAtom(local);
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: mapping local to remote selection: [%d][%s] -> [%ld][%s]\n", __func__,
              local, NameForLocalAtom(local), remote, NameForRemoteAtom(remote));
  #endif

  return remote;
}

/* FIXME: do we still need this special treatment? Can't we just
   call nxagentLocalToRemoteAtom() everywhere? */
XlibAtom translateLocalToRemoteTarget(Atom local)
{
  /*
   * .target must be translated, too, as a client on the real
   * server is requested to fill our property and it needs to know
   * the format.
   */

  XlibAtom remote;

  /*
   * we only convert to either UTF8 or XA_STRING
#ifdef SUPPORT_TEXT_TARGET
   * despite accepting TEXT
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
   * and COMPOUND_TEXT.
#endif
   */

  if (local == clientUTF8_STRING)
  {
    remote = serverUTF8_STRING;
  }
#ifdef SUPPORT_TEXT_TARGET
  else if (local == clientTEXT)
  {
    remote = serverTEXT;
  }
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
  else if (local == clientCOMPOUND_TEXT)
  {
    remote = serverCOMPOUND_TEXT;
  }
#endif
  else if (local == clientTARGETS)
  {
    remote = serverTARGETS;
  }
  else
  {
    remote = nxagentLocalToRemoteAtom(local);
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: mapping local to remote target: [%d][%s] -> [%ld][%s]\n", __func__,
              local, NameForLocalAtom(local), remote, NameForRemoteAtom(remote));
  #endif

  return remote;
}

/*
 * This is _only_ called from ProcSendEvent in NXevents.c. It is used
 * to send a SelectionNotify event to our server window which will
 * trigger the dispatch loop in Events.c to run
 * nxagentHandleSelectionNotifyFromXServer which in turn will take
 * care of transferring the selection content from the owning client
 * to a property of the server window.
 *
 * Returning 1 here means the client request will not be further
 * handled by dix. Returning 0 means a SelectionNotify event being
 * pushed out to our clients.
 *
 * From https://tronche.com/gui/x/xlib/events/client-communication/selection.html:
 * "This event is generated by the X server in response to a
 * ConvertSelection protocol request when there is no owner for the
 * selection. When there is an owner, it should be generated by the
 * owner of the selection by using XSendEvent()."
 */
int nxagentSendNotificationToSelfViaXServer(xEvent *event)
{
  if (!agentClipboardInitialized)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: clipboard not initialized - doing nothing.\n", __func__);
    #endif
    return 0;
  }

  #ifdef DEBUG
  fprintf(stderr, "---------\n%s: Received SendNotify from client: property [%d][%s] target [%d][%s] selection [%d][%s] requestor [0x%x] time [%u].\n", __func__,
          event->u.selectionNotify.property, NameForLocalAtom(event->u.selectionNotify.property),
          event->u.selectionNotify.target, NameForLocalAtom(event->u.selectionNotify.target),
          event->u.selectionNotify.selection, NameForLocalAtom(event->u.selectionNotify.selection),
          event->u.selectionNotify.requestor, event->u.selectionNotify.time);
  #endif

  int index = nxagentFindCurrentSelectionIndex(event->u.selectionNotify.selection);
  if (index == -1)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: unknown selection [%d]\n", __func__,
                event->u.selectionNotify.selection);
    #endif
    return 0;
  }

  #ifdef DEBUG
  fprintf(stderr, "%s: lastServers[index].requestor is [0x%lx].\n", __func__, lastServers[index].requestor);
  #endif

  /*
   * If we have nested sessions there are situations where we do not
   * need to send out anything to the real X server because
   * communication happens completely between our own clients (some of
   * which can be nxagents themselves). In that case we return 0 (tell
   * dix to go on) and do nothing!
   * Be sure to not let this trigger for the failure answer (property 0).
   */
  if (!(event->u.selectionNotify.property == clientCutProperty || event->u.selectionNotify.property == 0) || lastServers[index].requestor == None)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: sent nothing - message to real X server is not required.\n", __func__);
    #endif
    return 0;
  }
  else
  {
    /*
     * If the property is 0 (reporting failure) we can directly
     * answer to the lastServer about the failure!
     */
    if (lastServers[index].requestor != None && event->u.selectionNotify.property == 0)
    {
      replyPendingRequestSelectionToXServer(index, False);
      return 1;
    }
    else
    {
      /*
       * Setup selection notify event to real server.
       *
       * .property must be a server-side Atom. As this property is only
       * set on our serverWindow and normally there are few other
       * properties except serverTransToAgentProperty, the only thing
       * we need to ensure is that the local Atom clientCutProperty
       * differs from the server-side serverTransToAgentProperty
       * Atom. The actual name is not important. To be clean here we use
       * a separate serverTransFromAgentProperty.
       */

      XSelectionEvent eventSelection = {
        .requestor = serverWindow,
        .selection = translateLocalToRemoteSelection(event->u.selectionNotify.selection),
        .target    = translateLocalToRemoteTarget(event->u.selectionNotify.target),
        .property  = serverTransFromAgentProperty,
        .time      = CurrentTime,
      };

      #ifdef DEBUG
      fprintf(stderr, "%s: remote property [%ld][%s].\n", __func__,
                  serverTransFromAgentProperty, NameForRemoteAtom(serverTransFromAgentProperty));
      #endif
      sendSelectionNotifyEventToXServer(&eventSelection);
      return 1;
    }
  }
}

/*
 * This is called from NXproperty.c to determine if a client sets the
 * property we are waiting for.
 * FIXME: in addition we should check if the client is the one we expect.
 */
WindowPtr nxagentGetClipboardWindow(Atom property)
{
  if (serverLastRequestedSelection == -1)
  {
    return NULL;
  }

  int index = nxagentFindRemoteSelectionIndex(serverLastRequestedSelection);
  if (index != -1 &&
          property == clientCutProperty &&
              lastSelectionOwner[index].windowPtr != NULL)
  {
    #ifdef DEBUG
    fprintf(stderr, "%s: Returning last [%d] selection owner window [%p] (0x%x).\n", __func__,
            localSelectionAtoms[index],
            (void *)lastSelectionOwner[index].windowPtr, WINDOWID(lastSelectionOwner[index].windowPtr));
    #endif

    return lastSelectionOwner[index].windowPtr;
  }
  else
  {
    return NULL;
  }
}

/*
 * Initialize the clipboard.
 * Returns: True for success else False
 */
Bool nxagentInitClipboard(WindowPtr pWin)
{
  #ifdef DEBUG
  fprintf(stderr, "%s: Got called.\n", __func__);
  #endif

  #ifdef NXAGENT_TIMESTAMP
  {
    fprintf(stderr, "%s: Clipboard init starts at [%lu] ms.\n", __func__,
            GetTimeInMillis() - startTime);
  }
  #endif

  agentClipboardInitialized = False;
  serverWindow = nxagentWindow(pWin);

  if (!nxagentReconnectTrap)
  {
    /* Cannot move that down to others - we need it for
     * initSelectionOwnerData! */
    /* FIXME: it is probably better to re-use the strings from Atoms.c here */
    clientTARGETS = MakeAtom(szAgentTARGETS, strlen(szAgentTARGETS), True);
#ifdef SUPPORT_TEXT_TARGET
    clientTEXT = MakeAtom(szAgentTEXT, strlen(szAgentTEXT), True);
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
    clientCOMPOUND_TEXT = MakeAtom(szAgentCOMPOUND_TEXT, strlen(szAgentCOMPOUND_TEXT), True);
#endif
    clientUTF8_STRING = MakeAtom(szAgentUTF8_STRING, strlen(szAgentUTF8_STRING), True);
    clientTIMESTAMP = MakeAtom(szAgentTIMESTAMP, strlen(szAgentTIMESTAMP), True);
    clientINCR = MakeAtom(szAgentINCR, strlen(szAgentINCR), True);
    clientMULTIPLE = MakeAtom(szAgentMULTIPLE, strlen(szAgentMULTIPLE), True);
    clientDELETE = MakeAtom(szAgentDELETE, strlen(szAgentDELETE), True);
    clientINSERT_SELECTION = MakeAtom(szAgentINSERT_SELECTION, strlen(szAgentINSERT_SELECTION), True);
    clientINSERT_PROPERTY = MakeAtom(szAgentINSERT_PROPERTY, strlen(szAgentINSERT_PROPERTY), True);
    clientSAVE_TARGETS = MakeAtom(szAgentSAVE_TARGETS, strlen(szAgentSAVE_TARGETS), True);
    clientTARGET_SIZES = MakeAtom(szAgentTARGET_SIZES, strlen(szAgentTARGET_SIZES), True);

    SAFE_free(lastSelectionOwner);
    lastSelectionOwner = (SelectionOwner *) malloc(nxagentMaxSelections * sizeof(SelectionOwner));

    if (lastSelectionOwner == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for the clipboard selections.\n");
    }
    initSelectionOwnerData(nxagentPrimarySelection);
    initSelectionOwnerData(nxagentClipboardSelection);

    SAFE_free(lastClients);
    lastClients = (lastClient *) calloc(nxagentMaxSelections, sizeof(lastClient));
    if (lastClients == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for the last clients array.\n");
    }

    SAFE_free(lastServers);
    lastServers = (lastServer *) calloc(nxagentMaxSelections, sizeof(lastServer));
    if (lastServers == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for the last servers array.\n");
    }

    SAFE_free(localSelectionAtoms);
    localSelectionAtoms = (Atom *) calloc(nxagentMaxSelections, sizeof(Atom));
    if (localSelectionAtoms == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for the local selection Atoms array.\n");
    }
    localSelectionAtoms[nxagentPrimarySelection] = XA_PRIMARY;
    localSelectionAtoms[nxagentClipboardSelection] = MakeAtom(szAgentCLIPBOARD, strlen(szAgentCLIPBOARD), True);

    SAFE_free(remoteSelectionAtoms);
    remoteSelectionAtoms = (XlibAtom *) calloc(nxagentMaxSelections, sizeof(XlibAtom));
    if (remoteSelectionAtoms == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for the remote selection Atoms array.\n");
    }

    SAFE_free(targetCache);
    targetCache = (Targets *) calloc(nxagentMaxSelections, sizeof(Targets));
    if (targetCache == NULL)
    {
      FatalError("nxagentInitClipboard: Failed to allocate memory for target cache.\n");
    }
  }

  /*
   * The clipboard selection atom can change with a new X
   * server while Primary is constant.
   */
  remoteSelectionAtoms[nxagentPrimarySelection] = XA_PRIMARY;
  remoteSelectionAtoms[nxagentClipboardSelection] = nxagentAtoms[10];   /* CLIPBOARD */

  serverTARGETS = nxagentAtoms[6];  /* TARGETS */
#ifdef SUPPORT_TEXT_TARGET
  serverTEXT = nxagentAtoms[7];  /* TEXT */
#endif
#ifdef SUPPORT_COMPOUND_TEXT_TARGET
  serverCOMPOUND_TEXT = nxagentAtoms[16]; /* COMPOUND_TEXT */
#endif
  serverUTF8_STRING = nxagentAtoms[12]; /* UTF8_STRING */
  serverTIMESTAMP = nxagentAtoms[11];   /* TIMESTAMP */
  serverINCR = nxagentAtoms[17];   /* INCR */
  serverMULTIPLE = nxagentAtoms[18];   /* MULTIPLE */
  serverDELETE = nxagentAtoms[19];   /* DELETE */
  serverINSERT_SELECTION = nxagentAtoms[20];   /* INSERT_SELECTION */
  serverINSERT_PROPERTY = nxagentAtoms[21];   /* INSERT_PROPERTY */
  serverSAVE_TARGETS = nxagentAtoms[22];   /* SAVE_TARGETS */
  serverTARGET_SIZES = nxagentAtoms[23];   /* TARGET_SIZES */

  /*
   * Server side properties to hold pasted data.
   * See nxagentSendNotificationToSelfViaXServer for an explanation
   */
  serverTransFromAgentProperty = nxagentAtoms[15]; /* NX_SELTRANS_FROM_AGENT */
  serverTransToAgentProperty = nxagentAtoms[5];  /* NX_CUT_BUFFER_SERVER */

  if (serverTransToAgentProperty == None)
  {
    #ifdef PANIC
    fprintf(stderr, "%s: PANIC! Could not create %s atom\n", __func__, "NX_CUT_BUFFER_SERVER");
    #endif

    return False;
  }

#ifdef NXAGENT_ONSTART
  /* This is probably to communicate with nomachine nxclient. */
  #ifdef TEST
  fprintf(stderr, "%s: Setting owner of selection [%d][%s] to serverWindow [0x%lx]\n", __func__,
              (int) serverTransToAgentProperty, "NX_CUT_BUFFER_SERVER", serverWindow);
  #endif

  XSetSelectionOwner(nxagentDisplay, serverTransToAgentProperty, serverWindow, CurrentTime);
#endif

  if (XQueryExtension(nxagentDisplay,
                      "XFIXES",
                      &nxagentXFixesInfo.Opcode,
                      &nxagentXFixesInfo.EventBase,
                      &nxagentXFixesInfo.ErrorBase) == 0)
  {
    ErrorF("Unable to initialize XFixes extension.\n");
  }
  else
  {
    #ifdef TEST
    fprintf(stderr, "%s: Registering for XFixesSelectionNotify events.\n", __func__);
    #endif

    for (int index = 0; index < nxagentMaxSelections; index++)
    {
      XFixesSelectSelectionInput(nxagentDisplay, serverWindow,
                                 remoteSelectionAtoms[index],
                                 XFixesSetSelectionOwnerNotifyMask |
                                 XFixesSelectionWindowDestroyNotifyMask |
                                 XFixesSelectionClientCloseNotifyMask);
    }

    nxagentXFixesInfo.Initialized = True;
  }

#ifdef NXAGENT_ONSTART
  /*
     The first paste from CLIPBOARD did not work directly after
     session start. Removing this code makes it work. It is unsure why
     it was introduced in the first place so it is possible that we
     see other effects by leaving out this code.

     Fixes X2Go bug #952, see https://bugs.x2go.org/952 for details .

  if (nxagentSessionId[0])
  {
    #ifdef TEST
    fprintf(stderr, "%s: setting the ownership of %s to %lx"
                " and registering for PropertyChangeMask events\n", __func__,
                    validateString(NameForRemoteAtom(nxagentAtoms[10])), serverWindow);
    #endif

    XSetSelectionOwner(nxagentDisplay, nxagentAtoms[10], serverWindow, CurrentTime);    // nxagentAtoms[10] is the CLIPBOARD atom
    pWin -> eventMask |= PropertyChangeMask;
    nxagentChangeWindowAttributes(pWin, CWEventMask);
  }
  */
#endif

  if (nxagentReconnectTrap)
  {
    if (nxagentOption(Clipboard) == ClipboardServer ||
        nxagentOption(Clipboard) == ClipboardBoth)
    {
      for (int index = 0; index < nxagentMaxSelections; index++)
      {
        /*
         * If we have a selection inform the (new) real Xserver and
         * claim the ownership. Note that we report our serverWindow as
         * owner, not the real window!
         */
        if (IS_LOCAL_OWNER(index) && lastSelectionOwner[index].window)
        {
          /* remoteSelectionAtoms have already been adjusted above */
          XSetSelectionOwner(nxagentDisplay, remoteSelectionAtoms[index], serverWindow, CurrentTime);
        }

        /*
         * On reconnect there cannot be any external requestor
         * waiting for a reply so clean this.
         */
        lastServers[index].requestor = None;

        /*
         * FIXME: We should reset lastClients[index].* here! Problem
         * is that local clients might still be waiting for
         * answers. Should reply with failure then.
         */

        invalidateTargetCache(index);
      }
    }
  }
  else
  {
    for (int index = 0; index < nxagentMaxSelections; index++)
    {
      clearSelectionOwnerData(index);
      resetClientSelectionStage(index);
      /* FIXME: required? move to setSelectionStage? */
      lastClients[index].reqTime = GetTimeInMillis();
      lastServers[index].requestor = None;
      invalidateTargetCache(index);
    }

    clientCutProperty = MakeAtom(szAgentNX_CUT_BUFFER_CLIENT,
                                    strlen(szAgentNX_CUT_BUFFER_CLIENT), True);
    if (clientCutProperty == None)
    {
      #ifdef PANIC
      fprintf(stderr, "%s: PANIC! "
              "Could not create %s atom.\n", __func__, szAgentNX_CUT_BUFFER_CLIENT);
      #endif

      return False;
    }
  }

  agentClipboardInitialized = True;

  #ifdef DEBUG
  fprintf(stderr, "%s: Clipboard initialization completed.\n", __func__);
  #endif

  #ifdef NXAGENT_TIMESTAMP
  {
    fprintf(stderr, "%s: Clipboard init ends at [%lu] ms.\n", __func__,
                GetTimeInMillis() - startTime);
  }
  #endif

  return True;
}
