/* Wayland compositor running on top of an X serer.

Copyright (C) 2022 to various contributors.

This file is part of 12to11.

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

12to11 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with 12to11.  If not, see <https://www.gnu.org/licenses/>.  */

#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

#include <pthread.h>
#include <iconv.h>

#include "compositor.h"
#include "primary-selection-unstable-v1.h"

#include <X11/extensions/Xfixes.h>

/* X11 data transfer to and from Wayland clients.  */

typedef struct _ReadTargetsData ReadTargetsData;
typedef struct _TargetMapping TargetMapping;
typedef struct _TransferInfo TransferInfo;
typedef struct _ConversionTransferInfo ConversionTransferInfo;
typedef struct _WriteInfo WriteInfo;
typedef struct _ConversionWriteInfo ConversionWriteInfo;
typedef struct _DataConversion DataConversion;
typedef struct _TargetMappingTable TargetMappingTable;

struct _ReadTargetsData
{
  /* Array of atoms read from the selection.  */
  Atom *atoms;

  /* What selection is being read from.  */
  Atom selection;

  /* Number of atoms read.  */
  int n_atoms;
};

struct _TargetMapping
{
  /* The atom of the X target.  The top 3 bits of an XID are
     guaranteed to be 0, so the 31st bit is used to store a flag
     meaning that the next entry is a duplicate of this one, and the
     30th bit is used to store a flag containing state used by
     SendOffers.  */
  Atom atom_flag;

  /* The name of the Wayland MIME type.  */
  const char *mime_type;

  /* Optional translation function.  */
  void (*translation_func) (Time, Atom, Atom, int);
};

#define MappingAtom(mapping)		((mapping)->atom_flag & 0x1fffffff)
#define MappingFlag(mapping)		((mapping)->atom_flag & (1 << 29))
#define MappingIsNextDuplicate(mapping)	((mapping)->atom_flag & (1 << 30))
#define MappingSetFlag(mapping)		((mapping)->atom_flag |= (1 << 29))
#define MappingUnsetFlag(mapping)	((mapping)->atom_flag &= ~(1 << 29))
#define MappingIs(mapping, atom)	(MappingAtom (mapping) == (atom))

struct _DataConversion
{
  /* The MIME type of the Wayland offer.  */
  const char *mime_type;

  /* The atom describing the type of the property data.  */
  Atom type;

  /* The atom of the X target.  */
  Atom atom;

  /* An alternative GetClipboardCallback, if any.  */
  GetDataFunc (*clipboard_callback) (WriteTransfer *, Atom, Atom *,
				     struct wl_resource *,
				     void (*) (struct wl_resource *,
					       const char *, int),
				     Bool);
};

enum
  {
    NeedNewChunk      = 1,
    NeedDelayedFinish = (1 << 2),
  };

struct _TransferInfo
{
  /* The file descriptor being written to.  -1 if it was closed.  */
  int fd;

  /* Some flags.  */
  int flags;

  /* The currently buffered chunk.  */
  unsigned char *chunk;

  /* The size of the currently buffered chunk, the number of bytes
     into the chunk that have been written, and the number of bytes in
     the property after the currently buffered chunk.  */
  ptrdiff_t chunk_size, bytes_into, bytes_after;

  /* Any active file descriptor write callback.  */
  WriteFd *write_callback;
};

struct _ConversionTransferInfo
{
  /* The file descriptor being written to.  -1 if it was closed.  */
  int fd;

  /* Some flags.  */
  int flags;

  /* Conversion buffer.  */
  char *buffer, *position;

  /* The bytes remaining in the conversion buffer.  */
  size_t buffer_size;

  /* The output buffer.  */
  char output_buffer[BUFSIZ];

  /* And the amount of data used in the output buffer.  */
  size_t outsize;

  /* Any active file descriptor write callback.  */
  WriteFd *write_callback;

  /* The data format conversion context.  */
  iconv_t cd;
};

enum
  {
    IsDragAndDrop = (1 << 16),
  };

struct _WriteInfo
{
  /* The file descriptor being read from.  */
  int fd;

  /* Some flags.  */
  int flags;

  /* Any active file descriptor read callback.  */
  ReadFd *read_callback;
};

enum
  {
    ReachedEndOfFile = 1,
  };

struct _ConversionWriteInfo
{
  /* The file descriptor being read from.  -1 if it was closed.  */
  int fd;

  /* Some flags.  */
  int flags;

  /* Any active file descriptor read callback.  */
  ReadFd *read_callback;

  /* Input buffer for iconv.  */
  char inbuf[BUFSIZ];

  /* Number of bytes into the input buffer that have been read.  */
  size_t inread;

  /* Pointer to the current position in the input buffer.  Used by
     iconv.  */
  char *inptr;

  /* Iconv context for this conversion.  */
  iconv_t cd;
};

struct _TargetMappingTable
{
  /* Array of indices into direct_transfer.  */
  unsigned short *atom_buckets[16];

  /* Array of indices into direct_transfer.  */
  unsigned short *buckets[32];

  /* Number of elements in each atom bucket.  */
  unsigned short n_atom_elements[16];

  /* Number of elements in each bucket.  */
  unsigned short n_elements[32];
};

/* Base event code of the Xfixes extension.  */
static int fixes_event_base;

/* This means the next item in the targets mapping table has the same
   MIME type as this one.  */

#define Duplicate (1U << 30)

/* Map of targets that can be transferred from X to Wayland clients
   and vice versa.  */
static TargetMapping direct_transfer[] =
  {
    { XA_STRING,	"text/plain;charset=iso-9889-1"	},
    { Duplicate,	"text/plain;charset=utf-8"	},
    { XA_STRING,	"text/plain;charset=utf-8"	},
    /* These mappings are automatically generated.  */
    DirectTransferMappings
  };

/* Lookup table for such mappings.  */
static TargetMappingTable mapping_table;

/* Map of Wayland offer types to X atoms and data conversion
   functions.  */
static DataConversion data_conversions[] =
  {
    { "text/plain;charset=utf-8", 0, 0, NULL },
    { "text/plain;charset=utf-8", 0, 0,	NULL },
  };

/* The time of the last X selection change.  */
static Timestamp last_x_selection_change;

/* The time ownership was last asserted over CLIPBOARD, and the last
   time any client did that.  */
static Timestamp last_clipboard_time, last_clipboard_change;

/* The last time ownership over PRIMARY changed.  */
static Timestamp last_primary_time;

/* The currently supported selection targets.  */
static Atom *x_selection_targets;

/* The number of targets in that array.  */
static int num_x_selection_targets;

/* The currently supported primary selection targets.  */
static Atom *x_primary_targets;

/* The number of targets in that array.  */
static int num_x_primary_targets;

/* Data source currently being used to provide the X clipboard.  */
static DataSource *selection_data_source;

/* Data source currently being used to provide the X drag-and-drop
   selection.  */
static DataSource *drag_data_source;

/* Data source currently being used to provide the primary
   selection.  */
static PDataSource *primary_data_source;

#ifdef DEBUG

static void __attribute__ ((__format__ (gnu_printf, 1, 2)))
DebugPrint (const char *format, ...)
{
  va_list ap;

  va_start (ap, format);
  vfprintf (stderr, format, ap);
  va_end (ap);
}

#else
#define DebugPrint(fmt, ...) ((void) 0)
#endif

static void
Accept (struct wl_client *client, struct wl_resource *resource,
	uint32_t serial, const char *mime_type)
{
  /* Nothing has to be done here yet.  */
}

static unsigned int
HashMimeString (const char *string)
{
  unsigned int i;

  i = 3323198485ul;
  for (; *string; ++string)
    {
      i ^= *string;
      i *= 0x5bd1e995;
      i ^= i >> 15;
    }
  return i;
}

static void
SetupMappingTable (void)
{
  unsigned int idx, i, nelements;

  /* This is needed for the atoms table, since the atom indices are
     determined by the X server.  */
  XLAssert (ArrayElements (direct_transfer) <= USHRT_MAX);

  for (i = 0; i < ArrayElements (direct_transfer); ++i)
    {
      idx = HashMimeString (direct_transfer[i].mime_type) % 32;

      nelements = ++mapping_table.n_elements[idx];
      mapping_table.buckets[idx]
	= XLRealloc (mapping_table.buckets[idx],
		     nelements * sizeof (unsigned short));
      mapping_table.buckets[idx][nelements - 1] = i;

      /* Now, set up the lookup table indexed by atoms.  It is faster
	 to compare atoms than strings, so the table is smaller.  */

      idx = MappingAtom (&direct_transfer[i]) % 16;

      nelements = ++mapping_table.n_atom_elements[idx];
      mapping_table.atom_buckets[idx]
	= XLRealloc (mapping_table.atom_buckets[idx],
		     nelements * sizeof (unsigned short));
      mapping_table.atom_buckets[idx][nelements - 1] = i;
    }
}

static Bool
HasSelectionTarget (Atom atom, Bool is_primary)
{
  int i;

  if (is_primary)
    {
      /* Do this for the primary selection instead.  */

      for (i = 0; i < num_x_primary_targets; ++i)
	{
	  if (x_primary_targets[i] == atom)
	    return True;
	}

      return False;
    }

  for (i = 0; i < num_x_selection_targets; ++i)
    {
      if (x_selection_targets[i] == atom)
	return True;
    }

  return False;
}

static TargetMapping *
FindTranslationForMimeType (const char *mime_type, Bool is_primary)
{
  unsigned short *buckets, i;
  unsigned int idx;

  idx = HashMimeString (mime_type) % 32;
  buckets = mapping_table.buckets[idx];

  DebugPrint ("Looking for translation of MIME type %s\n",
	      mime_type);

  for (i = 0; i < mapping_table.n_elements[idx]; ++i)
    {
      if (!strcmp (direct_transfer[buckets[i]].mime_type,
		   mime_type)
	  && HasSelectionTarget (MappingAtom (&direct_transfer[buckets[i]]),
				 is_primary))
	{
	  DebugPrint ("Found translation for MIME type %s\n",
		      mime_type);

	  return &direct_transfer[buckets[i]];
	}
    }

  return NULL;
}

static void
FinishTransfer (TransferInfo *info)
{
  if (info->write_callback)
    XLRemoveWriteFd (info->write_callback);

  if (info->fd != -1)
    /* Close the file descriptor, letting the client know that the
       transfer completed.  */
    close (info->fd);

  /* Finally, free the info structure.  */
  XLFree (info);
}

static void
MaybeFinishDelayedTransfer (ReadTransfer *transfer,
			    TransferInfo *info)
{
  if (info->flags & NeedDelayedFinish)
    {
      DebugPrint ("Completing a delayed transfer.\n");
      FinishTransfer (info);
      CompleteDelayedTransfer (transfer);
    }
}

static void
NoticeTransferWritable (int fd, void *data, WriteFd *writefd)
{
  ReadTransfer *transfer;
  TransferInfo *info;
  long quantum;
  unsigned char *chunk;
  ptrdiff_t chunk_size, bytes_after;
  ssize_t written;

  DebugPrint ("File descriptor %d became writable\n", fd);

  transfer = data;
  info = GetTransferData (transfer);

  /* Start by reading at most this many bytes from the property.  */
  quantum = SelectionQuantum () / 4 * 4;

  if (!info->chunk)
    {
    read_chunk:
      info->flags &= ~NeedNewChunk;
      chunk = ReadChunk (transfer, quantum / 4,
			 &chunk_size, &bytes_after);
      DebugPrint ("Reading a piece of the property of size %ld\n",
		  quantum);

      /* If chunk is NULL, close fd.  The failure callback will also be
	 run soon.  */
      if (!chunk)
	{
	  DebugPrint ("Read failed\n");
	  if (info->fd != -1)
	    close (info->fd);

	  info->fd = -1;

	  MaybeFinishDelayedTransfer (transfer, info);
	  return;
	}

      /* Set info->chunk to the chunk and the chunk size.  */
      info->chunk = chunk;
      info->chunk_size = chunk_size;
      info->bytes_after = bytes_after;
      info->bytes_into = 0;

      DebugPrint ("Read actually got: %td, with %td after\n",
		  chunk_size, bytes_after);
    }

  DebugPrint ("Writing %td bytes of chunk at offset %td\n",
	      info->chunk_size - info->bytes_into,
	      info->bytes_into);

  /* Try to write the chunk into the fd.  */
  written = write (fd, info->chunk + info->bytes_into,
		   info->chunk_size - info->bytes_into);

  DebugPrint ("%zd bytes were really written; offset is now %td\n",
	      written, info->bytes_into + written);

  if (written < 0)
    {
      DebugPrint ("Some bytes could not be written: %s\n",
		  strerror (errno));

      /* Write failed with EAGAIN.  This might cause us to spin.  */
      if (errno == EAGAIN)
	return;

      /* Write failed with EPIPE.  */
      if (errno == EPIPE)
	{
	  /* EPIPE happened; set the fd to -1, skip the chunk if it
	     was not completely read, and return.  */
	  if (!info->bytes_after)
	    SkipChunk (transfer);

	  close (info->fd);
	  info->fd = -1;
	  XFree (info->chunk);
	  info->chunk = NULL;

	  DebugPrint ("EPIPE recieved while reading; cancelling transfer\n");

	  MaybeFinishDelayedTransfer (transfer, info);
	  return;
	}

      perror ("write");
      exit (1);
    }

  /* See how much was written.  */
  info->bytes_into += written;

  if (info->bytes_into == info->chunk_size)
    {
      DebugPrint ("Chunk of %td completely written; bytes left "
		  "in property: %td\n", info->chunk_size,
		  info->bytes_after);

      /* The entire read part of the chunk has been written; read a
	 new chunk, or cancel the write callback if the chunk was
	 completely read.  */

      XFree (info->chunk);
      info->chunk = NULL;

      if (info->bytes_after)
	/* There are still bytes in the property waiting to be
	   read.  */
	goto read_chunk;
      else
	{
	  /* The property has been completely read.  If a new chunk
	     already exists, read and try to write it now.  Otherwise,
	     cancel the write callback and wait for either a new
	     property to be set, or for DirectFinishCallback to be
	     called.  */

	  if (info->flags & NeedNewChunk)
	    goto read_chunk;
	  else
	    {
	      DebugPrint ("Removing write callback\n");

	      XLRemoveWriteFd (info->write_callback);
	      info->write_callback = NULL;
	      MaybeFinishDelayedTransfer (transfer, info);
	    }
	}
    }
}

static void
DirectReadCallback (ReadTransfer *transfer, Atom type, int format,
		    ptrdiff_t size)
{
  TransferInfo *info;

  info = GetTransferData (transfer);

  /* This is the start of a chunk.  If the fd was closed, simply skip
     the chunk.  */
  if (info->fd == -1)
    {
      SkipChunk (transfer);

      DebugPrint ("DirectReadCallback skipped a chunk due to the"
		  " fd being closed\n");
      return;
    }

  if (info->write_callback)
    {
      /* Make sure two chunks aren't read at the same time.  */
      XLAssert (!(info->flags & NeedNewChunk));

      DebugPrint ("DirectReadCallback received a new chunk, but the"
		  " current chunk is still being read from\n");

      info->flags |= NeedNewChunk;
      return;
    }

  /* Ask for a notification to be sent once the file descriptor
     becomes writable.  */
  XLAssert (!info->write_callback);

  DebugPrint ("DirectReadCallback is starting the write callback\n");

  info->write_callback = XLAddWriteFd (info->fd, transfer,
				       NoticeTransferWritable);
}

static Bool
DirectFinishCallback (ReadTransfer *transfer, Bool success)
{
  TransferInfo *info;

  /* The read completed.  Clean up any memory and write callbacks that
     might have been allocated or installed.  */

  info = GetTransferData (transfer);

  if (info->chunk)
    {
      /* The write callback should still exist, since this means the
	 data has not yet been fully written.  */
      XLAssert (info->write_callback != NULL);

      DebugPrint ("The transfer finished, but the chunk was not yet"
		  " completely written; the finish is being delayed.\n");
      info->flags |= NeedDelayedFinish;

      return False;
    }
  else
    DebugPrint ("The transfer finished %s\n",
		success ? "successfully" : "with failure");

  FinishTransfer (info);
  return True;
}

static void
MakeFdNonblocking (int fd)
{
  int rc;

  rc = fcntl (fd, F_GETFL);

  if (rc < 0)
    {
      DebugPrint ("Failed to make selection file descriptor"
		  " %d non-blocking.  Writing to it might hang.\n",
		  fd);
      return;
    }

  rc = fcntl (fd, F_SETFL, rc | O_NONBLOCK);

  if (rc < 0)
    {
      DebugPrint ("Failed to make selection file descriptor"
		  " %d non-blocking.  Writing to it might hang.\n",
		  fd);
      return;
    }
}

static void
PostReceiveDirect (Time time, Atom selection, Atom target, int fd)
{
  TransferInfo *info;

  info = XLCalloc (1, sizeof *info);
  info->fd = fd;

  /* Try to make the file description nonblocking.  Clients seem to
     behave fine this way.  */
  MakeFdNonblocking (fd);

  DebugPrint ("Converting selection at %lu for fd %d\n", time, fd);

  ConvertSelectionFuncs (selection, target, time,
			 info, NULL, DirectReadCallback,
			 DirectFinishCallback);
}

/* Forward declaration.  */

static void PostReceiveConversion (Time, Atom, Atom, int);

#define ReceiveBody(selection, primary)					\
  Timestamp time;							\
  TargetMapping *translation;						\
									\
  DebugPrint ("Receiving %s from X " #selection " \n",			\
	      mime_type);						\
									\
  /* Cast to intptr_t to silence warnings when the pointer type is	\
     larger than long.  */						\
  time = *(Timestamp *) wl_resource_get_user_data (resource);		\
									\
  /* Find which selection target corresponds to MIME_TYPE.  */		\
  translation = FindTranslationForMimeType (mime_type, primary);	\
									\
  if (translation)							\
    {									\
      if (!translation->translation_func)				\
	/* If a corresponding target exists, ask to receive it.  */	\
	PostReceiveDirect (time.milliseconds, selection,	       	\
			   MappingAtom (translation), fd);		\
      else								\
	/* Otherwise, use the translation function.  */			\
	translation->translation_func (time.milliseconds, selection,	\
				       MappingAtom (translation),	\
				       fd);				\
    }									\
  else									\
    close (fd)

static void
Receive (struct wl_client *client, struct wl_resource *resource,
	 const char *mime_type, int fd)
{
  ReceiveBody (CLIPBOARD, False);
}

static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
  wl_resource_destroy (resource);
}

static void
Finish (struct wl_client *client, struct wl_resource *resource)
{
  wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
			  "trying to finish foreign data offer");
}

static void
SetActions (struct wl_client *client, struct wl_resource *resource,
	    uint32_t dnd_actions, uint32_t preferred_action)
{
  wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_OFFER,
			  "trying to finish non-drag-and-drop data offer");
}

static const struct wl_data_offer_interface wl_data_offer_impl =
  {
    .accept = Accept,
    .receive = Receive,
    .destroy = Destroy,
    .finish = Finish,
    .set_actions = SetActions,
  };

static void
HandleOfferResourceDestroy (struct wl_resource *resource)
{
  Timestamp *timestamp;

  timestamp = wl_resource_get_user_data (resource);

  XLFree (timestamp);
}

static Timestamp *
AllocateTimestamp (Timestamp timestamp)
{
  Timestamp *data;

  data = XLMalloc (sizeof *data);
  *data = timestamp;

  return data;
}

static struct wl_resource *
CreateOffer (struct wl_client *client, Timestamp time)
{
  struct wl_resource *resource;

  resource = wl_resource_create (client, &wl_data_offer_interface,
				 3, 0);

  if (!resource)
    /* If allocating the resource failed, return NULL.  */
    return NULL;

  /* Otherwise, set the user_data to the time of the selection
     change.  */
  wl_resource_set_implementation (resource, &wl_data_offer_impl,
				  AllocateTimestamp (time),
				  HandleOfferResourceDestroy);
  return resource;
}

static void
ReceivePrimary (struct wl_client *client, struct wl_resource *resource,
		const char *mime_type, int fd)
{
  ReceiveBody (XA_PRIMARY, True);
}

static struct zwp_primary_selection_offer_v1_interface primary_offer_impl =
  {
    .receive = ReceivePrimary,
    .destroy = Destroy,
  };

static struct wl_resource *
CreatePrimaryOffer (struct wl_client *client, Timestamp time)
{
  struct wl_resource *resource;

  resource = wl_resource_create (client,
				 &zwp_primary_selection_offer_v1_interface,
				 1, 0);
  if (!resource)
    /* If allocating the resource failed, return NULL.  */
    return NULL;

  /* Otherwise, set the user_data to the time of the selection
     change.  */
  wl_resource_set_implementation (resource, &primary_offer_impl,
				  AllocateTimestamp (time),
				  HandleOfferResourceDestroy);
  return resource;
}

static Bool
CheckDuplicate (unsigned short index, Atom a)
{
  TargetMapping *start;

  start = &direct_transfer[index];

  /* If the flag is already set, then this type has already been
     sent.  */
  if (MappingFlag (start))
    return False;

  /* Set this entry's duplicate flag.  */
  MappingSetFlag (start);

  /* As long as the next index still refers to a duplicate of this
     item, set its duplicate flag.  */

  while (MappingIsNextDuplicate (start))
    MappingSetFlag (++start);

  /* Do the same backwards.  */

  if (index)
    {
      start = &direct_transfer[index - 1];

      while (MappingIsNextDuplicate (start))
	{
	  MappingSetFlag (start);

	  /* If this is now the start of the target mapping table,
	     break.  */
	  if (start == direct_transfer)
	    break;

	  start--;
	}
    }

  return True;
}

static void
SendOffers1 (struct wl_resource *resource, int ntargets, Atom *targets,
	     void (*send_offer_func) (struct wl_resource *, const char *))
{
  int i, j;
  unsigned int idx;
  unsigned short *buckets;

  for (i = 0; i < ntargets; ++i)
    {
      /* Offer each type corresponding to this target.  */
      idx = targets[i] % 16;

      /* N.B. that duplicates do appear in the atom buckets, which
	 is intentional.  */
      buckets = mapping_table.atom_buckets[idx];

      for (j = 0; j < mapping_table.n_atom_elements[idx]; ++j)
	{
	  if (MappingIs (&direct_transfer[buckets[j]],
			 targets[i])
	      && CheckDuplicate (buckets[j], targets[i]))
	    /* If it exists and was not previously offered, offer it
	       to the client.  */
	    send_offer_func (resource,
			     direct_transfer[buckets[j]].mime_type);
	}
    }

  /* Clear the duplicate flag of each item in the targets table that
     was touched.  */
  for (i = 0; i < ArrayElements (direct_transfer); ++i)
    MappingUnsetFlag (&direct_transfer[i]);
}

static void
SendOffers (struct wl_resource *resource, Timestamp time)
{
  if (TimestampIs (time, Earlier, last_x_selection_change))
    /* This offer is out of date.  */
    return;

  SendOffers1 (resource, num_x_selection_targets,
	       x_selection_targets, wl_data_offer_send_offer);
}

static void
SendPrimaryOffers (struct wl_resource *resource, Timestamp time)
{
  if (TimestampIs (time, Earlier, last_primary_time))
    /* This offer is out of date.  */
    return;

  SendOffers1 (resource, num_x_primary_targets,
	       x_primary_targets,
	       zwp_primary_selection_offer_v1_send_offer);
}

static void
HandleNewSelection (Time time, Atom selection, Atom *targets,
		    int ntargets)
{
  CreateOfferFuncs funcs;

  if (selection == XA_PRIMARY)
    {
      /* The primary selection changed, and now has the given
	 targets.  */

      if (TimeIs (time, Earlier, last_primary_time))
	{
	  XLFree (targets);
	  return;
	}

      /* Set up the new primary targets.  */
      XLFree (x_primary_targets);
      x_primary_targets = targets;
      num_x_primary_targets = ntargets;
      last_primary_time = TimestampFromClientTime (time);

      /* Add the right functions and set them as the foreign primary
	 selection handler at TIME.  */
      funcs.create_offer = CreatePrimaryOffer;
      funcs.send_offers = SendPrimaryOffers;

      XLSetForeignPrimary (last_primary_time, funcs);
      return;
    }

  /* Else, the selection that changed is CLIPBOARD.  */
  
  /* Ignore outdated selection changes.  */
  if (TimeIs (time, Earlier, last_x_selection_change))
    {
      /* We are responsible for deallocating targets.  */
      XLFree (targets);
      return;
    }

  /* Note that our free function explicitly ignores pointers to NULL,
     so this is safe.  */
  XLFree (x_selection_targets);
  x_selection_targets = targets;
  num_x_selection_targets = ntargets;

  last_x_selection_change = TimestampFromClientTime (time);

  /* Add the right functions and set them as the foreign selection
     handler at TIME.  */

  funcs.create_offer = CreateOffer;
  funcs.send_offers = SendOffers;

  XLSetForeignSelection (last_x_selection_change, funcs);
}

static void
TargetsReadCallback (ReadTransfer *transfer, Atom type, int format,
		     ptrdiff_t size)
{
  Atom *atoms;
  ptrdiff_t n_atoms, old, i;
  ReadTargetsData *data;

  if (type != XA_ATOM || format != 32)
    {
      SkipChunk (transfer);
      return;
    }

  /* Since format is 32, size must be a multiple of sizeof (long).  */
  atoms = (Atom *) ReadChunk (transfer, size / sizeof (long), &size,
			      NULL);

  /* Reading the property data failed.  The finish function will be
     run with success set to False soon.  */
  if (!atoms)
    return;

  data = GetTransferData (transfer);
  n_atoms = size / sizeof (long);
  old = data->n_atoms;

  /* Copy the atoms to data->atoms.  */

  if (IntAddWrapv (n_atoms, data->n_atoms,
		   &data->n_atoms))
    {
      /* Overflow.  Something is definitely wrong with the selection
	 data.  */
      data->n_atoms = 0;
      return;
    }

  data->atoms = XLRealloc (data->atoms,
			   data->n_atoms * sizeof *atoms);
  for (i = 0; i < n_atoms; ++i)
    data->atoms[old + i] = atoms[i];

  /* Use XFree, since this is Xlib-allocated memory.  */
  XFree (atoms);
}

static Bool
TargetsFinishCallback (ReadTransfer *transfer, Bool success)
{
  ReadTargetsData *data;

  data = GetTransferData (transfer);

  if (success)
    DebugPrint ("Received targets from %lu\n", data->selection);
  else
    DebugPrint ("Failed to obtain targets\n");

  if (success)
    HandleNewSelection (GetTransferTime (transfer),
			data->selection, data->atoms,
			data->n_atoms);
  else
    /* HandleNewSelection keeps data->atoms around for a while.  */
    XLFree (data->atoms);

  XLFree (data);
  return True;
}

/* Notice that the owner of CLIPBOARD has changed at TIME.  Try to get
   a list of selection targets from it, and if successful, make the
   Wayland data source current.  */

static void
NoticeClipboardChanged (Time time)
{
  ReadTargetsData *data;

  data = XLCalloc (1, sizeof *data);
  data->selection = CLIPBOARD;

  ConvertSelectionFuncs (CLIPBOARD, TARGETS, time, data,
			 NULL, TargetsReadCallback,
			 TargetsFinishCallback);
}

/* The same, but for the primary selection.  */

static void
NoticePrimaryChanged (Time time)
{
  ReadTargetsData *data;

  data = XLCalloc (1, sizeof *data);
  data->selection = XA_PRIMARY;

  ConvertSelectionFuncs (XA_PRIMARY, TARGETS, time, data,
			 NULL, TargetsReadCallback,
			 TargetsFinishCallback);
}

static void
NoticeClipboardCleared (Time time)
{
  /* Ignore outdated events.  time cannot simply be compared against
     the selection change time, because some clients do this:

       - data_device.set_selection (data_source, N)
       - data_source.delete (data_source)
       - data_device.set_selection (data_source, N)

     Which results in the selection being disowned at the same time as
     the last selection change time, but then re-owned immediately
     afterwards, leading to an out-of-date SelectionNotify event with
     owner None having a timestamp equal to that time.

     Thus, only timestamps after last_x_selection_change are
     considered as valid here if x_selection_targets is NULL.  */

  if (!x_selection_targets)
    {
      if (!TimeIs (time, Later, last_x_selection_change))
	return;
    }
  else if (TimeIs (time, Earlier, last_x_selection_change))
    return;

  DebugPrint ("CLIPBOARD was cleared at %lu\n", time);

  last_x_selection_change = TimestampFromClientTime (time);
  XLClearForeignSelection (last_x_selection_change);

  /* Free data that is no longer used.  */
  XLFree (x_selection_targets);
  x_selection_targets = NULL;
  num_x_selection_targets = 0;
}

static void
NoticePrimaryCleared (Time time)
{
  if (!x_primary_targets)
    {
      if (!TimeIs (time, Later, last_primary_time))
	return;
    }
  else if (TimeIs (time, Earlier, last_primary_time))
    return;

  DebugPrint ("PRIMARY was cleared at %lu\n", time);

  last_primary_time = TimestampFromClientTime (time);
  XLClearForeignPrimary (last_primary_time);

  /* Free data that is no longer used.  */
  XLFree (x_primary_targets);
  x_primary_targets = NULL;
  num_x_primary_targets = 0;
}

static void
HandleSelectionNotify (XFixesSelectionNotifyEvent *event)
{
  if (event->owner == selection_transfer_window)
    /* Ignore events sent when we get selection ownership.  */
    return;

  /* Use the server timestamp in the fixes selection notify event
     to synchronize the local time counter.  */
  TimestampFromServerTime (event->timestamp);

  if (event->selection == CLIPBOARD
      && TimeIs (event->selection_timestamp, Later, last_clipboard_change))
    /* This time is used to keep track of whether or not things like
       disowning the selection were successful.  */
    last_clipboard_change
      = TimestampFromClientTime (event->selection_timestamp);

  if (event->selection == XA_PRIMARY
      && TimeIs (event->selection_timestamp, Later, last_primary_time))
    last_primary_time
      = TimestampFromClientTime (event->selection_timestamp);

  if (event->owner != None
      && event->selection == CLIPBOARD)
    NoticeClipboardChanged (event->selection_timestamp);
  else if (event->selection == CLIPBOARD)
    NoticeClipboardCleared (event->selection_timestamp);
  else if (event->owner != None
	   && event->selection == XA_PRIMARY)
    NoticePrimaryChanged (event->selection_timestamp);
  else if (event->selection == XA_PRIMARY)
    NoticePrimaryCleared (event->selection_timestamp);
}

Bool
XLHandleOneXEventForXData (XEvent *event)
{
  if (event->type == fixes_event_base + XFixesSelectionNotify)
    {
      HandleSelectionNotify ((XFixesSelectionNotifyEvent *) event);

      return True;
    }

  return False;
}

static void
SelectSelectionInput (Atom selection)
{
  int mask;
  Time time;

  /* If the selection already exists, announce it to Wayland clients
     as well.  Use the current time.  */

  time = XLGetServerTimeRoundtrip ();

  if (selection == CLIPBOARD
      && XGetSelectionOwner (compositor.display, CLIPBOARD) != None)
    NoticeClipboardChanged (time);

  if (selection == XA_PRIMARY
      && XGetSelectionOwner (compositor.display, XA_PRIMARY) != None)
    NoticePrimaryChanged (time);

  mask = XFixesSetSelectionOwnerNotifyMask;
  mask |= XFixesSelectionWindowDestroyNotifyMask;
  mask |= XFixesSelectionClientCloseNotifyMask;

  XFixesSelectSelectionInput (compositor.display,
			      selection_transfer_window,
			      selection, mask);
}

static DataConversion *
GetDataConversion (Atom target)
{
  int i;

  for (i = 0; i < ArrayElements (data_conversions); ++i)
    {
      if (data_conversions[i].atom == target)
	return &data_conversions[i];
    }

  return NULL;
}

static const char *
MimeTypeFromTarget (Atom target, Bool primary)
{
  DataConversion *conversion;
  static char *string;
  Bool missing_type;

  /* TODO: replace XGetAtomName with something more efficient.  */
  if (string)
    XFree (string);
  string = NULL;

  if (!primary)
    missing_type = !XLDataSourceHasAtomTarget (selection_data_source,
					       target);
  else
    missing_type = !XLPDataSourceHasAtomTarget (primary_data_source,
						target);

  if (missing_type)
    {
      /* If the data source does not itself provide the specified
	 target, then a conversion function is in use.  */
      conversion = GetDataConversion (target);

      if (!conversion)
	/* There must be a data conversion here.  */
	abort ();

      DebugPrint ("Converting X type %lu to MIME type %s...\n",
		  conversion->type, conversion->mime_type);

      return conversion->mime_type;
    }

  string = XGetAtomName (compositor.display, target);
  return string;
}

static Atom
TypeFromTarget (Atom target, Bool primary)
{
  DataConversion *conversion;
  Bool missing_type;

  if (!primary)
    missing_type = !XLDataSourceHasAtomTarget (selection_data_source,
					       target);
  else
    missing_type = !XLPDataSourceHasAtomTarget (primary_data_source,
						target);

  if (missing_type)
    {
      /* If the data source does not itself provide the specified
	 target, then a conversion function is in use.  Use the type
	 specified in the conversion table entry.  */
      conversion = GetDataConversion (target);

      if (!conversion)
	/* There must be a data conversion here.  */
	abort ();

      return conversion->type;
    }

  /* Otherwise, assume the data type is the same as the target.  This
     is mostly intended to handle text/uri-list and similar
     formats.  */
  return target;
}

static void
NoticeTransferReadable (int fd, void *data, ReadFd *readfd)
{
  WriteTransfer *transfer;
  WriteInfo *info;

  transfer = data;

  /* Retrieve the info and make sure it hasn't been freed.  */
  info = GetWriteTransferData (transfer);
  XLAssert (info != NULL);

  DebugPrint ("Fd %d is now readable...\n", fd);

  /* Now cancel the read callback, and switch from waiting for the
     client to send something to waiting for the requestor to read the
     data.  */
  XLRemoveReadFd (info->read_callback);
  info->read_callback = NULL;

  /* And tell the selection code to start reading.  */
  StartReading (transfer);
}

static ReadStatus
ClipboardReadFunc (WriteTransfer *transfer, unsigned char *buffer,
		   ptrdiff_t buffer_size, ptrdiff_t *nbytes)
{
  WriteInfo *info;
  ssize_t size;

  /* Retrieve the info and make sure it hasn't been freed.  */
  info = GetWriteTransferData (transfer);

  if (buffer_size == -1)
    {
      DebugPrint ("ClipboardReadFunc called to free data for timeout\n");

      if (info)
	{
	  if (info->read_callback)
	    {
	      /* Cancel any read callback in progress.  */
	      XLRemoveReadFd (info->read_callback);
	      info->read_callback = NULL;
	    }

	  close (info->fd);
	  XLFree (info);
	  SetWriteTransferData (transfer, NULL);
	}

      return EndOfFile;
    }

  XLAssert (info != NULL);

  /* By the time this function is entered, the polling callback should
     be NULL.  */
  XLAssert (info->read_callback == NULL);

  DebugPrint ("ClipboardReadFunc called to read %td bytes\n",
	      buffer_size);

  /* Try to read buffer_size bytes into buffer.  */
  size = read (info->fd, buffer, buffer_size);

  /* If EOF, return that, free info, and close the pipe.  */

  if (!size)
    {
      DebugPrint ("EOF; completing transfer\n");

      if (info->read_callback)
	{
	  /* Cancel any read callback in progress.  */
	  XLRemoveReadFd (info->read_callback);
	  info->read_callback = NULL;
	}

      close (info->fd);
      XLFree (info);
      SetWriteTransferData (transfer, NULL);

      *nbytes = 0;
      return EndOfFile;
    }

  /* If an error occured, see what it was.  */

  if (size == -1)
    {
      DebugPrint ("read failed with: %s\n", strerror (errno));

      if (errno == EAGAIN)
	{
	  /* Start the read callback again.  This might make us
	     spin.  */
	  *nbytes = 0;

	  info->read_callback = XLAddReadFd (info->fd, transfer,
					     NoticeTransferReadable);
	  return ReadOk;
	}

      perror ("write");
      exit (1);
    }

  DebugPrint ("Read %zd bytes, starting the read callback again\n",
	      size);

  /* Otherwise, set nbytes to the number of bytes read, and start the
     write callback again.  */
  *nbytes = size;
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeTransferReadable);
  return ReadOk;
}

static GetDataFunc
GetClipboardCallback (WriteTransfer *transfer, Atom target,
		      Atom *type)
{
  WriteInfo *info;
  int pipe[2], rc;
  DataConversion *conversion;
  struct wl_resource *resource;

  /* If the selection data source is destroyed, the selection will be
     disowned.  */
  XLAssert (selection_data_source != NULL);

  resource = XLResourceFromDataSource (selection_data_source);

  if (!XLDataSourceHasAtomTarget (selection_data_source, target))
    {
      /* If the data source does not itself provide the specified
	 target, then a conversion function is in use.  Call the
	 clipboard callback specified in the conversion table entry,
	 if it exists.  */
      conversion = GetDataConversion (target);

      if (!conversion)
	/* There must be a data conversion here.  */
	abort ();

      if (conversion->clipboard_callback)
	return conversion->clipboard_callback (transfer, target,
					       type, resource,
					       wl_data_source_send_send,
					       False);

      /* Otherwise, use the default callback.  */
      DebugPrint ("Conversion to type %lu specified with default callback\n",
		  target);
    }

  DebugPrint ("Entered GetClipboardCallback; target is %lu\n",
	      target);

  /* First, create a non-blocking pipe.  We will give the write end to
     the client.  */
  rc = pipe2 (pipe, O_NONBLOCK);

  if (rc)
    /* Creating the pipe failed.  */
    return NULL;

  DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]);

  /* Send the write end of the pipe to the source.  */
  wl_data_source_send_send (resource,
			    MimeTypeFromTarget (target, False),
			    pipe[1]);
  close (pipe[1]);

  /* And set the prop type appropriately.  */
  *type = TypeFromTarget (target, False);

  /* Create the transfer info structure.  */
  info = XLMalloc (sizeof *info);
  info->fd = pipe[0];
  info->flags = 0;

  DebugPrint ("Adding the read callback\n");

  /* Wait for info to become readable.  */
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeTransferReadable);

  /* Set info as the transfer data.  */
  SetWriteTransferData (transfer, info);

  return ClipboardReadFunc;
}

/* Conversions between UTF-8 and Latin-1.  */


static void
NoticeConversionTransferReadable (int fd, void *data, ReadFd *readfd)
{
  WriteTransfer *transfer;
  ConversionWriteInfo *info;
  size_t nbytes;

  transfer = data;

  /* Retrieve the info and make sure it hasn't been freed.  */
  info = GetWriteTransferData (transfer);
  XLAssert (info != NULL);

  DebugPrint ("Fd %d is now readable...\n", fd);

  /* Now try to fill the conversion buffer.  */
  nbytes = read (info->fd, info->inbuf + info->inread,
		 BUFSIZ - info->inread);

  if (nbytes <= 0)
    {
      /* EOF was reached or an error occured.  Flag the transfer as
	 having reached the EOF.  */
      info->flags |= ReachedEndOfFile;
      DebugPrint ("EOF read from %d\n", fd);

      /* Cancel any read callback in progress.  */
      XLRemoveReadFd (info->read_callback);
      info->read_callback = NULL;

      /* Signal that the selection code should start reading.  */
      StartReading (transfer);
    }
  else
    {
      /* Add the number of bytes read to the number of bytes in the
	 buffer that are filled up.  */
      info->inread += nbytes;

      DebugPrint ("Read %tu bytes\n", info->inread);

      /* If the buffer is full, begin converting from it.  */
      if (info->inread == BUFSIZ)
	{
	  DebugPrint ("Buffer is now full\n");

	  /* Cancel any read callback in progress.  */
	  XLRemoveReadFd (info->read_callback);
	  info->read_callback = NULL;

	  /* Signal that the selection code should start reading.  */
	  StartReading (transfer);
	}
    }
}

static ReadStatus
ConversionReadFunc (WriteTransfer *transfer, unsigned char *buffer,
		    ptrdiff_t buffer_size, ptrdiff_t *nbytes)
{
  ConversionWriteInfo *info;
  size_t nconv, outsize;

  /* Retrieve the info and make sure it hasn't been freed.  */
  info = GetWriteTransferData (transfer);

  DebugPrint ("ClipboardReadFunc called to read %zd bytes\n",
	      buffer_size);

  if (buffer_size == -1)
    {
      DebugPrint ("ConversionReadFunc called to free data for timeout\n");

      if (info)
	{
	  if (info->read_callback)
	    {
	      /* Cancel any read callback in progress.  */
	      XLRemoveReadFd (info->read_callback);
	      info->read_callback = NULL;
	    }

	  close (info->fd);
	  iconv_close (info->cd);
	  XLFree (info);

	  SetWriteTransferData (transfer, NULL);
	}

      return False;
    }

  XLAssert (info != NULL);

  /* By the time this function is entered, the polling callback should
     be NULL.  */
  XLAssert (info->read_callback == NULL);

  /* Convert the appropriate amount of bytes into the output
     buffer.  */
  outsize = buffer_size;
  nconv = iconv (info->cd, &info->inptr, &info->inread,
		 (char **) &buffer, &outsize);

  DebugPrint ("iconv returned: %tu\n", nconv);

  if (nconv == (size_t) -1 && errno != EINVAL)
    {
      if (errno == E2BIG)
	{
	  DebugPrint ("iconv needs a bigger buffer\n");
	  *nbytes = buffer_size - outsize;

	  if (*nbytes < 1)
	    {
	      DebugPrint ("iconv failed with a buffer as large as the"
			  "max-request-size!\n");

	      goto eof;
	    }

	  memmove (info->inbuf, info->inptr, info->inread);
	  info->inptr = info->inbuf;
	  return NeedBiggerBuffer;
	}

      DebugPrint ("iconv failed with: %s; bytes written: %tu\n",
		  strerror (errno), buffer_size - outsize);

    eof:
      if (info->read_callback)
	{
	  /* Cancel any read callback in progress.  */
	  XLRemoveReadFd (info->read_callback);
	  info->read_callback = NULL;
	}

      /* The conversion failed/finished; return EOF.  */
      close (info->fd);
      iconv_close (info->cd);
      XLFree (info);

      SetWriteTransferData (transfer, NULL);
      *nbytes = buffer_size - outsize;
      return EndOfFile;
    }

  /* Otherwise, move the data that has still not been read back to the
     start.  */
  memmove (info->inbuf, info->inptr, info->inread);
  info->inptr = info->inbuf;
  DebugPrint ("iconv wrote: %tu\n", buffer_size - outsize);

  if (info->flags & ReachedEndOfFile)
    goto eof;

  /* Restore the read callback, and return the number of bytes
     read.  */
  *nbytes = buffer_size - outsize;
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeConversionTransferReadable);

  return ReadOk;
}

static GetDataFunc
GetConversionCallback (WriteTransfer *transfer, Atom target, Atom *type,
		       struct wl_resource *source,
		       void (*send_send) (struct wl_resource *, const char *,
					  int),
		       Bool is_primary)
{
  ConversionWriteInfo *info;
  int pipe[2], rc;
  iconv_t cd;

  DebugPrint ("Performing data conversion of UTF-8 string to %lu\n",
	      target);

  cd = iconv_open ("ISO-8859-1", "UTF-8");

  if (cd == (iconv_t) -1)
    /* The conversion context couldn't be initialized, so fail this
       transfer.  */
    return NULL;

  /* First, create a non-blocking pipe.  We will give the write end to
     the client.  */
  rc = pipe2 (pipe, O_NONBLOCK);

  if (rc)
    return NULL;

  /* Then, send the write end of the pipe to the data source.  */
  send_send (source, "text/plain;charset=utf-8", pipe[1]);
  close (pipe[1]);

  /* Following that, set the property type correctly.  */
  *type = XA_STRING;

  /* Create the transfer info and buffer.  */
  info = XLMalloc (sizeof *info);
  info->fd = pipe[0];
  info->flags = 0;
  info->inptr = info->inbuf;
  info->inread = 0;
  info->cd = cd;

  DebugPrint ("Adding the read callback\n");

  /* Wait for info to become readable.  */
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeConversionTransferReadable);

  /* Set info as the transfer data.  */
  SetWriteTransferData (transfer, info);

  return ConversionReadFunc;
}

static void
ConversionReadCallback (ReadTransfer *transfer, Atom type, int format,
			ptrdiff_t size)
{
  ConversionTransferInfo *info;
  unsigned char *data;

  /* Read all the data from the chunk now.  */

  data = ReadChunk (transfer, (format == 32 ? size / sizeof (long)
			       : (size + 3) / 4),
		    &size, NULL);

  if (!data)
    return;

  info = GetTransferData (transfer);

  /* We buffer up all the data from the selection and convert it
     later.  */

  info->buffer = XLRealloc (info->buffer, info->buffer_size + size);
  memcpy (info->buffer + info->buffer_size, data, size);
  info->buffer_size += size;
}

static void
FinishConversionTransfer (ReadTransfer *transfer, ConversionTransferInfo *info)
{
  DebugPrint ("Completing conversion transfer...\n");

  XLFree (info->buffer);
  iconv_close (info->cd);

  if (info->write_callback)
    XLRemoveWriteFd (info->write_callback);

  if (info->fd != -1)
    close (info->fd);

  XLFree (info);

  /* Complete the transfer itself.  */
  CompleteDelayedTransfer (transfer);
}

static void
NoticeConversionTransferWritable (int fd, void *data, WriteFd *writefd)
{
  ConversionTransferInfo *info;
  ssize_t written;
  size_t outsize, nconv, start;
  char *outbuf;

  /* The pipe is now writable.  First, try to write data from the
     buffer to the file descriptor.  */

  info = GetTransferData (data);

  /* Skip the call to write if there is nothing to do.  */

  if (info->outsize)
    {
      written = write (info->fd, info->output_buffer,
		       info->outsize);

      if (written == -1)
	{
	  DebugPrint ("write: %s\n", strerror (errno));

	  if (errno == EAGAIN)
	    /* Writing the data failed, so return.  This might cause us to
	       spin.  */
	    return;

	  if (errno != EPIPE)
	    {
	      perror ("write");
	      exit (1);
	    }

	  /* The other end of the pipe was closed by the client.  Stop
	     this transfer.  */
	  FinishConversionTransfer (data, info);
	  return;
	}

      /* Writing the data was successful.  Move the written part back to
	 the start.  */
      memmove (info->output_buffer, info->output_buffer + written,
	       info->outsize - written);
      info->outsize -= written;
    }

  /* Now, run iconv if there is still data in the input buffer.  */
  if (info->buffer_size)
    {
      outbuf = info->output_buffer + info->outsize;
      start = outsize = BUFSIZ - info->outsize;
      nconv = iconv (info->cd, &info->position, &info->buffer_size,
		     &outbuf, &outsize);

      if (nconv == (size_t) -1 && errno != EINVAL)
	{
	  /* iconv failed for various reasons.  */

	  if ((errno == E2BIG && outsize == BUFSIZ)
	      || errno != E2BIG)
	    {
	      /* BUFSIZ is either too small to hold the output contents
		 (which shouldn't happen), or some other error
		 occured.  Punt.  */
	      FinishConversionTransfer (data, info);
	      return;
	    }
	}

      /* Increase the output buffer size by the amount that was was
	 written.  */
      info->outsize += start - outsize;
      DebugPrint ("Output buffer is now %tu bytes full\n", info->outsize);
    }
  else if (!info->outsize)
    /* Everything has been written; finish the transfer.  */
    FinishConversionTransfer (data, info);
}

static Bool
ConversionFinishCallback (ReadTransfer *transfer, Bool success)
{
  ConversionTransferInfo *info;

  DebugPrint ("ConversionFinishCallback called; converting data in chunks.\n");

  /* The conversion is complete.  Start the conversion and write the
     correct amount of data into the pipe.  */

  info = GetTransferData (transfer);
  info->position = info->buffer;

  info->write_callback = XLAddWriteFd (info->fd, transfer,
				       NoticeConversionTransferWritable);
  return False;
}

static void
PostReceiveConversion (Time time, Atom selection, Atom target, int fd)
{
  ConversionTransferInfo *info;
  iconv_t cd;

  cd = iconv_open ("UTF-8", "ISO-8859-1");

  if (cd == (iconv_t) -1)
    {
      /* The conversion context couldn't be created.  Close the file
	 descriptor and fail.  */
      close (fd);
      return;
    }

  info = XLCalloc (1, sizeof *info);
  info->fd = fd;
  info->cd = cd;

  /* Try to make the file description nonblocking.  Clients seem to
     behave fine this way.  */
  MakeFdNonblocking (fd);

  DebugPrint ("Converting selection to UTF-8 at %lu for fd %d\n", time, fd);
  ConvertSelectionFuncs (selection, target, time, info, NULL,
			 ConversionReadCallback, ConversionFinishCallback);
}

/* Drag-and-drop support functions.  */


static const char *
DragMimeTypeFromTarget (Atom target)
{
  static char *string;

  /* TODO: replace XGetAtomName with something more efficient.  */
  if (string)
    XFree (string);

  string = XGetAtomName (compositor.display, target);
  return string;
}

static Atom
DragTypeFromTarget (Atom target)
{
  /* Assume the data type is the same as the target.  This is mostly
     intended to handle text/uri-list and similar formats.  */
  return target;
}

static GetDataFunc
GetDragCallback (WriteTransfer *transfer, Atom target,
		 Atom *type)
{
  WriteInfo *info;
  int pipe[2], rc;

  /* If the drag and drop data source is destroyed, the selection will
     be disowned.  */
  XLAssert (drag_data_source != NULL);

  DebugPrint ("Entered GetDragCallback; target is %lu\n",
	      target);

  /* First, create a non-blocking pipe.  We will give the write end to
     the client.  */
  rc = pipe2 (pipe, O_NONBLOCK);

  if (rc)
    /* Creating the pipe failed.  */
    return NULL;

  DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]);

  /* Send the write end of the pipe to the source.  */
  wl_data_source_send_send (XLResourceFromDataSource (drag_data_source),
			    DragMimeTypeFromTarget (target), pipe[1]);
  close (pipe[1]);

  /* And set the prop type appropriately.  */
  *type = DragTypeFromTarget (target);

  /* Create the transfer info structure.  */
  info = XLMalloc (sizeof *info);
  info->fd = pipe[0];
  info->flags = IsDragAndDrop;

  DebugPrint ("Adding the read callback\n");

  /* Wait for info to become readable.  */
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeTransferReadable);

  /* Set info as the transfer data.  */
  SetWriteTransferData (transfer, info);

  return ClipboardReadFunc;
}

Bool
XLOwnDragSelection (Time time, DataSource *source)
{
  Atom *targets;
  int ntargets;
  Bool rc;

  DebugPrint ("Trying to own XdndSelection\n");

  /* Copy targets from the data source.  */
  ntargets = XLDataSourceTargetCount (source);
  targets = XLMalloc (sizeof *targets * ntargets);
  XLDataSourceGetTargets (source, targets);

  /* Own the selection.  */
  drag_data_source = source;

  rc = OwnSelection (TimestampFromClientTime (time),
		     XdndSelection, GetDragCallback,
		     targets, ntargets);
  XLFree (targets);

  return rc;
}



void
XLNoteSourceDestroyed (DataSource *source)
{
  if (source == selection_data_source)
    {
      DebugPrint ("Disowning CLIPBOARD at %u (vs. last change %u)\n"
		  "This is being done in response to the source being destroyed.\n",
		  last_clipboard_time.milliseconds,
		  last_x_selection_change.milliseconds);

      /* Disown the selection.  */
      DisownSelection (CLIPBOARD);
    }

  if (source == drag_data_source)
    {
      DebugPrint ("Disowning XdndSelection\nThis is being done in response"
		  " to the data source being destroyed.\n");

      /* Disown the selection.  */
      DisownSelection (XdndSelection);
    }
}

void
XLNotePrimaryDestroyed (PDataSource *source)
{
  if (source == primary_data_source)
    {
      DebugPrint ("Disowning PRIMARY\nThis is being done in response"
		  " to the data source being destroyed.\n");

      /* Disown the selection.  */
      DisownSelection (XA_PRIMARY);
    }
}

static Bool
FindTargetInArray (Atom *targets, int ntargets, Atom atom)
{
  int i;

  for (i = 0; i < ntargets; ++i)
    {
      if (targets[i] == atom)
	return True;
    }

  return False;
}

/* FIXME: this should really be something other than two giant
   macros...  */

#define NoteLocalSelectionBody(callback, time_1, time_2, atom, field, foreign, n_foreign)	\
  Timestamp time;										\
  Atom *targets;										\
  int ntargets, i, n_data_conversions;								\
  Bool rc;											\
												\
  if (source == NULL)										\
    {												\
      DebugPrint ("Disowning " #atom " at %u (vs. last change %u)\n",				\
		  time_1.milliseconds, time_2.milliseconds);					\
												\
      /* Disown the selection.  */								\
      DisownSelection (atom);									\
      field = NULL;										\
												\
      /* Return whether or not the selection was actually					\
	 disowned.  */										\
      return TimestampIs (time_1, Later, time_2);						\
    }												\
												\
  time = XLSeatGetLastUserTime (seat);								\
												\
  DebugPrint ("Acquiring ownership of " #atom " at %u\n", time.milliseconds);			\
												\
  if (!time.months && !time.milliseconds)							\
    /* Nothing has yet happened on the seat.  */						\
    return False;										\
												\
  if (TimestampIs (time, Earlier, time_1)							\
      || TimestampIs (time, Earlier, time_2))							\
    /* TIME is out of date.  */									\
    return False;										\
												\
  DebugPrint ("Setting callback function for " #atom "\n");					\
												\
  /* Since the local selection is now set, free foreign selection				\
     data.  */											\
  XLFree (foreign);										\
  n_foreign = 0;										\
  foreign = NULL;										\
												\
  /* Otherwise, set the clipboard change time to TIME.  */					\
  time_1 = time;										\
  time_2 = time;										\
												\

#define NoteLocalSelectionFooter(callback, atom, field, has_target)				\
  /* Now, look to see what standard X targets the client doesn't				\
     offer, and add them to the targets array.							\
												\
     Most functioning Wayland clients will also offer the typical X				\
     selection targets such as STRING and UTF8_STRING in addition to				\
     MIME types; when they do, they perform the data conversion					\
     conversion better than us.  */								\
												\
  for (i = 0; i < ArrayElements (data_conversions); ++i)					\
    {												\
      /* If the source provides MIME typed-data for a format we know				\
	 how to convert, announce the associated selection conversion				\
	 targets.  */										\
												\
      if (has_target (source,									\
		      data_conversions[i].mime_type)						\
	  && !FindTargetInArray (targets, ntargets,						\
				 data_conversions[i].type))					\
	{											\
	  DebugPrint ("Client doesn't provide standard X conversion"				\
		      " target for %s; adding it\n",						\
		      data_conversions[i].mime_type);						\
												\
	  targets[ntargets++] = data_conversions[i].type;					\
	}											\
    }												\
												\
  /* And own the selection.  */									\
  field = source;										\
												\
  rc = OwnSelection (time, atom, callback, targets, ntargets);					\
  XLFree (targets);										\
  return rc											\

Bool
XLNoteLocalSelection (Seat *seat, DataSource *source)
{
  NoteLocalSelectionBody (GetClipboardCallback, last_clipboard_time,
			  last_x_selection_change, CLIPBOARD,
			  selection_data_source, x_selection_targets,
			  num_x_selection_targets);

  /* And copy the targets from the data source.  */
  ntargets = XLDataSourceTargetCount (source);
  n_data_conversions = ArrayElements (data_conversions);
  targets = XLMalloc (sizeof *targets * (ntargets + n_data_conversions));
  XLDataSourceGetTargets (source, targets);

  NoteLocalSelectionFooter (GetClipboardCallback, CLIPBOARD,
			    selection_data_source,
			    XLDataSourceHasTarget);
}

static GetDataFunc
GetPrimaryCallback (WriteTransfer *transfer, Atom target,
		    Atom *type)
{
  WriteInfo *info;
  int pipe[2], rc;
  DataConversion *conversion;
  struct wl_resource *resource;

  /* If the selection data source is destroyed, the selection will be
     disowned.  */
  XLAssert (primary_data_source != NULL);

  resource = XLResourceFromPDataSource (primary_data_source);

  if (!XLPDataSourceHasAtomTarget (primary_data_source, target))
    {
      /* If the data source does not itself provide the specified
	 target, then a conversion function is in use.  Call the
	 clipboard callback specified in the conversion table entry,
	 if it exists.  */
      conversion = GetDataConversion (target);

      if (!conversion)
	/* There must be a data conversion here.  */
	abort ();

      /* This is ugly but the only reasonable way to fit the following
	 function in 80 columns.  */

#define Function zwp_primary_selection_source_v1_send_send

      if (conversion->clipboard_callback)
	return conversion->clipboard_callback (transfer, target,
					       type, resource,
					       Function, True);

#undef Function

      /* Otherwise, use the default callback.  */
      DebugPrint ("Conversion to type %lu specified with default callback\n",
		  target);
    }

  DebugPrint ("Entered GetPrimaryCallback; target is %lu\n",
	      target);

  /* First, create a non-blocking pipe.  We will give the write end to
     the client.  */
  rc = pipe2 (pipe, O_NONBLOCK);

  if (rc)
    /* Creating the pipe failed.  */
    return NULL;

  DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]);

  /* Send the write end of the pipe to the source.  */
  zwp_primary_selection_source_v1_send_send (resource,
					     MimeTypeFromTarget (target, True),
					     pipe[1]);
  close (pipe[1]);

  /* And set the prop type appropriately.  */
  *type = TypeFromTarget (target, True);

  /* Create the transfer info structure.  */
  info = XLMalloc (sizeof *info);
  info->fd = pipe[0];
  info->flags = 0;

  DebugPrint ("Adding the read callback\n");

  /* Wait for info to become readable.  */
  info->read_callback = XLAddReadFd (info->fd, transfer,
				     NoticeTransferReadable);

  /* Set info as the transfer data.  */
  SetWriteTransferData (transfer, info);

  return ClipboardReadFunc;
}

Bool
XLNoteLocalPrimary (Seat *seat, PDataSource *source)
{
  /* I forgot why there are two variables for tracking the clipboard
     time, so just use one here.  */
  NoteLocalSelectionBody (GetPrimaryCallback, last_primary_time,
			  last_primary_time, XA_PRIMARY,
			  primary_data_source, x_primary_targets,
			  num_x_primary_targets);

  /* And copy the targets from the data source.  */
  ntargets = XLPDataSourceTargetCount (source);
  n_data_conversions = ArrayElements (data_conversions);
  targets = XLMalloc (sizeof *targets * (ntargets + n_data_conversions));
  XLPDataSourceGetTargets (source, targets);

  NoteLocalSelectionFooter (GetPrimaryCallback, XA_PRIMARY,
			    primary_data_source,
			    XLPDataSourceHasTarget);
}

void
XLInitXData (void)
{
  struct sigaction act;
  int rc, fixes_error_base, major, minor;

  rc = XFixesQueryExtension (compositor.display,
			     &fixes_event_base,
			     &fixes_error_base);

  if (!rc)
    {
      fprintf (stderr, "The X server does not support the "
	       "XFixes protocol extension\n");
      exit (1);
    }

  rc = XFixesQueryVersion (compositor.display,
			   &major, &minor);

  if (!rc || major < 5)
    {
      fprintf (stderr, "The X server does not support the "
	       "right version of the XFixes protocol extension\n");
      exit (1);
    }

  SelectSelectionInput (CLIPBOARD);
  SelectSelectionInput (XA_PRIMARY);

  /* Initialize atoms used in the direct transfer table.  */
  direct_transfer[1].atom_flag = UTF8_STRING | Duplicate;
  direct_transfer[2].translation_func = PostReceiveConversion;
  DirectTransferInitializer (direct_transfer, 3);

  /* Set up the direct transfer table.  */
  SetupMappingTable ();

  /* And those used in the data conversions table.  */
  data_conversions[0].atom = UTF8_STRING;
  data_conversions[0].type = UTF8_STRING;
  data_conversions[1].atom = XA_STRING;
  data_conversions[1].type = XA_STRING;
  data_conversions[1].clipboard_callback = GetConversionCallback;

  /* Ignore SIGPIPE, since we could be writing to a pipe whose reading
     end was closed by the client.  */
  memset (&act, 0, sizeof act);
  act.sa_handler = SIG_IGN;

  if (sigaction (SIGPIPE, &act, NULL))
    {
      perror ("sigaction");
      abort ();
    }
}

void
XLReceiveDataFromSelection (Time time, Atom selection, Atom target,
			    int fd)
{
  PostReceiveDirect (time, selection, target, fd);
}
