12to11/xdata.c
hujianwei d2abef20c4 Pacify more compiler warnings
* text_input.c (EncodeIMString):
* xdata.c (GetConversionCallback, PostReceiveConversion): Pacify
GCC suspecting memory leaks after iconv_open returns -1.
2022-11-17 02:45:33 +00:00

2343 lines
59 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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");
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
#endif
if (cd == (iconv_t) -1)
/* The conversion context couldn't be initialized, so fail this
transfer. */
return NULL;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
/* 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)
{
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
#endif
/* The conversion context couldn't be created. Close the file
descriptor and fail. */
close (fd);
return;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
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);
}