forked from 12to11/12to11

* select.c (SignalConversionPerformed): Improve diagnostic messages. (ConvertSelectionMultiple): Use correct atoms. (HandleSelectionRequest): Allow selection requests with target MULTIPLE. * tests/Imakefile (SRCS12, OBJS12): Add select_helper_multiple.c and .o. (PROGRAMS): Add select_helper_multiple. (select_helper_multiple): New program. * tests/select_helper.c (main): Fix typo. * tests/select_test.c (verify_sample_text_multiple): New function. (test_single_step): Call it. * tests/svnignore.txt: Add select_helper_multiple.
1920 lines
50 KiB
C
1920 lines
50 KiB
C
/* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "compositor.h"
|
|
|
|
typedef struct _PropertyAtom PropertyAtom;
|
|
typedef struct _SelectInputData SelectInputData;
|
|
typedef struct _SelectionOwnerInfo SelectionOwnerInfo;
|
|
typedef struct _QueuedTransfer QueuedTransfer;
|
|
typedef struct _MultipleRecord MultipleRecord;
|
|
|
|
/* Structure representing a single property allocated for selection
|
|
transfers.
|
|
|
|
Since Wayland data sources are not read from a wl_surface, there is
|
|
no way to associate each transfer with a specific window. In
|
|
addition, the compositor might want to obtain selection data in the
|
|
background as well. Because of this, we must rely on all ongoing
|
|
data transfers from other owners having a unique property. */
|
|
|
|
struct _PropertyAtom
|
|
{
|
|
/* The atom in question. */
|
|
Atom atom;
|
|
|
|
/* The id used to generate the atom. */
|
|
uint32_t counter;
|
|
|
|
/* The next and last atoms in this chain. */
|
|
PropertyAtom *next, *last;
|
|
};
|
|
|
|
struct _SelectionOwnerInfo
|
|
{
|
|
/* When this selection was last owned. */
|
|
Timestamp time;
|
|
|
|
/* The targets of this atom. */
|
|
Atom *targets;
|
|
|
|
/* The number of targets. */
|
|
int ntargets;
|
|
|
|
/* The callback for this selection. NULL means the selection is no
|
|
longer owned. */
|
|
GetDataFunc (*get_transfer_function) (WriteTransfer *,
|
|
Atom, Atom *);
|
|
};
|
|
|
|
enum
|
|
{
|
|
IsFinished = 1,
|
|
IsIncr = (1 << 1),
|
|
IsWaitingForChunk = (1 << 2),
|
|
IsStarted = (1 << 3),
|
|
IsFailed = (1 << 4),
|
|
IsWaitingForDelete = (1 << 5),
|
|
IsWaitingForIncr = (1 << 6),
|
|
IsReadable = (1 << 7),
|
|
IsFlushed = (1 << 8),
|
|
};
|
|
|
|
struct _ReadTransfer
|
|
{
|
|
/* The selection owner. */
|
|
Window owner;
|
|
|
|
/* Some state associated with this transfer. */
|
|
int state;
|
|
|
|
/* The atom being used to transfer data from the selection
|
|
owner. */
|
|
PropertyAtom *property;
|
|
|
|
/* The timestamp at which the selection request was issued. */
|
|
Time time;
|
|
|
|
/* The selection and target that are being requested. */
|
|
Atom selection, target;
|
|
|
|
/* The current offset, in 4-byte multiples, into the property data.
|
|
Reset each time a new chunk of data is available. */
|
|
unsigned long read_offset;
|
|
|
|
/* The format of the property data. */
|
|
unsigned long read_format;
|
|
|
|
/* A function called once selection data begins to be read from the
|
|
selection. The second arg is the type of the selection data, and
|
|
the third arg is the format of the selection data. */
|
|
void (*data_start_func) (ReadTransfer *, Atom, int);
|
|
|
|
/* A function called once data some data can be read from the
|
|
selection. It may be called multiple times, depending on how big
|
|
the response from the owner is.
|
|
|
|
The args are as follows: a pointer to the transfer, the atom
|
|
describing the type of the data, the format of the data (see the
|
|
doc for XGetWindowProperty), and the size of the data that can be
|
|
read in bytes. */
|
|
void (*data_read_func) (ReadTransfer *, Atom, int, ptrdiff_t);
|
|
|
|
/* A function called after the selection transfer completes. The
|
|
second arg means whether or not the transfer was successful. */
|
|
Bool (*data_finish_func) (ReadTransfer *, Bool);
|
|
|
|
/* Caller-specified data for those functions. */
|
|
void *data;
|
|
|
|
/* The next and last transfers on this chain. */
|
|
ReadTransfer *next, *last;
|
|
|
|
/* A timer that times out after 5 seconds of inactivity. */
|
|
Timer *timeout;
|
|
};
|
|
|
|
struct _WriteTransfer
|
|
{
|
|
/* The next and last transfers on this chain. */
|
|
WriteTransfer *next, *last;
|
|
|
|
/* The requestor of the transfer. Spelt with an "O" because that's
|
|
what the ICCCM does. */
|
|
Window requestor;
|
|
|
|
/* Some state associated with this transfer. */
|
|
int state;
|
|
|
|
/* The selection being requested from us, the target, the property,
|
|
and the type of the data. */
|
|
Atom selection, target, property, type;
|
|
|
|
/* The time of the request. */
|
|
Time time;
|
|
|
|
/* Data buffer. */
|
|
unsigned char *buffer;
|
|
|
|
/* The size of the data buffer, and how much has been read from
|
|
it. */
|
|
ptrdiff_t size, offset;
|
|
|
|
/* The SelectionNotify event that should be sent. */
|
|
XEvent event;
|
|
|
|
/* Or a MultipleRecord that should be worked on. */
|
|
MultipleRecord *record;
|
|
|
|
/* The offset of this transfer into record->atoms. */
|
|
unsigned long multiple_offset;
|
|
|
|
/* Function called to return a piece of selection data in a buffer.
|
|
If size arg is -1, free associated data. Return False if EOF was
|
|
reached. */
|
|
GetDataFunc transfer_function;
|
|
|
|
/* User data for the write transfer. */
|
|
void *data;
|
|
|
|
/* A timer that times out after 5 seconds of inactivity. */
|
|
Timer *timeout;
|
|
|
|
#ifdef DEBUG
|
|
/* Total bytes of data written to the X server as part of this
|
|
transfer. */
|
|
size_t total_written;
|
|
#endif
|
|
};
|
|
|
|
struct _QueuedTransfer
|
|
{
|
|
/* Queued event. */
|
|
XEvent event;
|
|
|
|
/* Next and last items in the queue. */
|
|
QueuedTransfer *next, *last;
|
|
};
|
|
|
|
struct _SelectInputData
|
|
{
|
|
/* Number of requests from this requestor that are in progress. */
|
|
int refcount;
|
|
};
|
|
|
|
struct _MultipleRecord
|
|
{
|
|
/* Number of conversions that still have not been made. */
|
|
int pending;
|
|
|
|
/* The notification event that should be sent. */
|
|
XEvent event;
|
|
|
|
/* The atom pair array. Free this with XFree! */
|
|
Atom *atoms;
|
|
|
|
/* The number of atoms in the atom pair array. */
|
|
unsigned long nitems;
|
|
};
|
|
|
|
/* Window used for selection data transfer. */
|
|
Window selection_transfer_window;
|
|
|
|
/* Assoc table used to keep track of PropertyNotify selections. */
|
|
static XLAssocTable *foreign_notify_table;
|
|
|
|
/* Counter used for determining the selection property atom. */
|
|
static uint32_t prop_counter;
|
|
|
|
/* Chain of selection property atoms currently in use. */
|
|
static PropertyAtom prop_atoms;
|
|
|
|
/* Chain of selection property atoms that can be reused. */
|
|
static PropertyAtom free_list;
|
|
|
|
/* Circular queue of all outstanding selection data transfers to this
|
|
client. */
|
|
static ReadTransfer read_transfers;
|
|
|
|
/* List of all outstanding selection data transfers to other
|
|
clients. */
|
|
static WriteTransfer write_transfers;
|
|
|
|
/* Table of atoms to selection owner info. */
|
|
static XLAssocTable *selection_owner_info;
|
|
|
|
/* Circular queue of SelectionRequests, to be drained once all
|
|
outstanding write transfers finish. */
|
|
static QueuedTransfer queued_transfers;
|
|
|
|
#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
|
|
|
|
/* Allocate a new atom to use for selection property transfers. This
|
|
atom is guaranteed to not be currently in use for a selection
|
|
transfer to selection_transfer_window.
|
|
|
|
Once the transfer completes, ReleasePropAtom must be called with
|
|
the returned PropertyAtom object to allow reusing the atom in the
|
|
future. */
|
|
|
|
static PropertyAtom *
|
|
AllocPropAtom (void)
|
|
{
|
|
PropertyAtom *atom;
|
|
char name[sizeof "_XL_UXXXXXXXX" + 1];
|
|
|
|
if (free_list.next != &free_list)
|
|
{
|
|
/* There is a free atom that we can use. Remove it from the
|
|
free list. */
|
|
atom = free_list.next;
|
|
atom->next->last = atom->last;
|
|
atom->last->next = atom->next;
|
|
}
|
|
else
|
|
{
|
|
if (IntAddWrapv (prop_counter, 1, &prop_counter))
|
|
{
|
|
fprintf (stderr, "Failed to allocate selection property"
|
|
" after running out of valid values!");
|
|
abort ();
|
|
}
|
|
|
|
sprintf (name, "_XL_U%x", prop_counter);
|
|
atom = XLMalloc (sizeof *atom);
|
|
|
|
/* Use XInternAtom instead of InternAtom. These atoms should
|
|
only be interned once, so there is no point allocating memory
|
|
in the global atoms table. */
|
|
atom->atom = XInternAtom (compositor.display, name, False);
|
|
atom->counter = prop_counter;
|
|
}
|
|
|
|
/* Now, link atom onto the used list and return it. */
|
|
atom->last = &prop_atoms;
|
|
atom->next = prop_atoms.next;
|
|
|
|
prop_atoms.next->last = atom;
|
|
prop_atoms.next = atom;
|
|
|
|
return atom;
|
|
}
|
|
|
|
static void
|
|
ReleasePropAtom (PropertyAtom *atom)
|
|
{
|
|
/* Move atom from the used list onto the free list. */
|
|
|
|
atom->next->last = atom->last;
|
|
atom->last->next = atom->next;
|
|
|
|
atom->last = &free_list;
|
|
atom->next = free_list.next;
|
|
|
|
free_list.next->last = atom;
|
|
free_list.next = atom;
|
|
|
|
/* Now the atom has been released and can also be used for future
|
|
selection data transfers. */
|
|
}
|
|
|
|
static void
|
|
FinishReadTransfer (ReadTransfer *transfer, Bool success)
|
|
{
|
|
Bool delay;
|
|
|
|
if (transfer->data_finish_func
|
|
/* This means to delay deallocating the transfer for a
|
|
while. */
|
|
&& !transfer->data_finish_func (transfer, success))
|
|
delay = True;
|
|
else
|
|
delay = False;
|
|
|
|
transfer->next->last = transfer->last;
|
|
transfer->last->next = transfer->next;
|
|
|
|
ReleasePropAtom (transfer->property);
|
|
RemoveTimer (transfer->timeout);
|
|
|
|
if (!delay)
|
|
XLFree (transfer);
|
|
}
|
|
|
|
static void
|
|
HandleTimeout (Timer *timer, void *data, struct timespec time)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
transfer = data;
|
|
|
|
/* Delete the data transfer property from the window. */
|
|
XDeleteProperty (compositor.display,
|
|
selection_transfer_window,
|
|
transfer->property->atom);
|
|
|
|
/* Finish the read transfer. */
|
|
FinishReadTransfer (transfer, False);
|
|
}
|
|
|
|
static void
|
|
CancelTransferEarly (ReadTransfer *transfer)
|
|
{
|
|
/* Delete the data transfer property from the window. */
|
|
XDeleteProperty (compositor.display,
|
|
selection_transfer_window,
|
|
transfer->property->atom);
|
|
|
|
/* Mark the transfer as finished. */
|
|
transfer->state |= IsFailed | IsFinished;
|
|
}
|
|
|
|
/* After calling this function, set the appropriate functions! */
|
|
|
|
static ReadTransfer *
|
|
ConvertSelection (Atom selection, Atom target, Time time)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
/* If time is CurrentTime, obtain the time from the X server. */
|
|
if (time == CurrentTime)
|
|
time = XLGetServerTimeRoundtrip ();
|
|
|
|
transfer = XLCalloc (1, sizeof *transfer);
|
|
transfer->property = AllocPropAtom ();
|
|
transfer->time = time;
|
|
transfer->selection = selection;
|
|
transfer->target = target;
|
|
|
|
/* Link the transfer onto the list of outstanding selection
|
|
transfers. */
|
|
transfer->next = read_transfers.next;
|
|
transfer->last = &read_transfers;
|
|
|
|
/* Add a timeout for 5 seconds. */
|
|
transfer->timeout = AddTimer (HandleTimeout, transfer,
|
|
MakeTimespec (5, 0));
|
|
|
|
read_transfers.next->last = transfer;
|
|
read_transfers.next = transfer;
|
|
|
|
/* Delete the property from the window beforehand. The property
|
|
might be left over from a failed transfer. */
|
|
XDeleteProperty (compositor.display, selection_transfer_window,
|
|
transfer->property->atom);
|
|
|
|
/* Now issue the ConvertSelection request. */
|
|
XConvertSelection (compositor.display, selection, target,
|
|
transfer->property->atom,
|
|
selection_transfer_window, time);
|
|
|
|
return transfer;
|
|
}
|
|
|
|
ReadTransfer *
|
|
ConvertSelectionFuncs (Atom selection, Atom target, Time time, void *data,
|
|
void (*data_start) (ReadTransfer *, Atom, int),
|
|
void (*data_read) (ReadTransfer *, Atom, int, ptrdiff_t),
|
|
Bool (*data_finish) (ReadTransfer *, Bool))
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
transfer = ConvertSelection (selection, target, time);
|
|
transfer->data = data;
|
|
transfer->data_start_func = data_start;
|
|
transfer->data_read_func = data_read;
|
|
transfer->data_finish_func = data_finish;
|
|
|
|
return transfer;
|
|
}
|
|
|
|
/* Find the first outstanding read transfer for SELECTION, TARGET and
|
|
TIME in the queue of outstanding selection transfers. */
|
|
|
|
static ReadTransfer *
|
|
FindReadTransfer (Atom selection, Atom target, Time time)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
transfer = read_transfers.last;
|
|
|
|
while (transfer != &read_transfers)
|
|
{
|
|
if (transfer->selection == selection
|
|
&& transfer->target == target
|
|
&& transfer->time == time)
|
|
return transfer;
|
|
|
|
transfer = transfer->last;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
long
|
|
SelectionQuantum (void)
|
|
{
|
|
long request;
|
|
|
|
request = XExtendedMaxRequestSize (compositor.display);
|
|
|
|
if (!request)
|
|
request = XMaxRequestSize (compositor.display);
|
|
|
|
return request;
|
|
}
|
|
|
|
static ptrdiff_t
|
|
FormatTypeSize (int format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case 8:
|
|
return sizeof (char);
|
|
|
|
case 16:
|
|
return sizeof (short);
|
|
|
|
case 32:
|
|
return sizeof (long);
|
|
}
|
|
|
|
abort ();
|
|
}
|
|
|
|
void
|
|
FinishTransfers (void)
|
|
{
|
|
ReadTransfer *transfer, *last;
|
|
|
|
transfer = read_transfers.last;
|
|
|
|
while (transfer != &read_transfers)
|
|
{
|
|
last = transfer;
|
|
transfer = transfer->last;
|
|
|
|
if (last->state & IsFinished)
|
|
FinishReadTransfer (last, !(last->state & IsFailed));
|
|
}
|
|
}
|
|
|
|
/* Signal that the current chunk of data from TRANSFER has been
|
|
completely read. */
|
|
|
|
static void
|
|
FinishChunk (ReadTransfer *transfer)
|
|
{
|
|
if (transfer->state & IsIncr)
|
|
{
|
|
transfer->state |= IsWaitingForChunk;
|
|
return;
|
|
}
|
|
|
|
/* If INCR transfer is not in progress, post a timer to finish
|
|
TRANSFER entirely, the next time the event loop is entered. */
|
|
transfer->state |= IsFinished;
|
|
}
|
|
|
|
void
|
|
SkipChunk (ReadTransfer *transfer)
|
|
{
|
|
/* Just delete the property. */
|
|
XDeleteProperty (compositor.display,
|
|
selection_transfer_window,
|
|
transfer->property->atom);
|
|
|
|
/* And mark the chunk as finished. */
|
|
FinishChunk (transfer);
|
|
}
|
|
|
|
/* Read a chunk of data from TRANSFER. LONG_LENGTH gives the length
|
|
of the data to read. Return a pointer to the data, or NULL if
|
|
reading the data failed, and return the actual length of the data
|
|
in *NBYTES. Free the data returned using Xlib functions! */
|
|
|
|
unsigned char *
|
|
ReadChunk (ReadTransfer *transfer, int long_length, ptrdiff_t *nbytes,
|
|
ptrdiff_t *bytes_after_return)
|
|
{
|
|
unsigned char *prop_data;
|
|
Atom actual_type;
|
|
int actual_format, rc;
|
|
unsigned long nitems, bytes_after;
|
|
|
|
prop_data = NULL;
|
|
|
|
/* Now read the actual property data. */
|
|
rc = XGetWindowProperty (compositor.display, selection_transfer_window,
|
|
transfer->property->atom, transfer->read_offset,
|
|
long_length, True, AnyPropertyType, &actual_type,
|
|
&actual_format, &nitems, &bytes_after, &prop_data);
|
|
|
|
/* Reading the property data failed. Signal failure by returning
|
|
NULL. Also, cancel the whole transfer here too. */
|
|
if (!prop_data)
|
|
{
|
|
CancelTransferEarly (transfer);
|
|
return NULL;
|
|
}
|
|
|
|
if (actual_type == None
|
|
|| actual_format != transfer->read_format
|
|
|| rc != Success)
|
|
{
|
|
/* Same goes here. */
|
|
if (prop_data)
|
|
XFree (prop_data);
|
|
|
|
CancelTransferEarly (transfer);
|
|
return NULL;
|
|
}
|
|
|
|
if (!bytes_after)
|
|
/* The property has now been deleted, so finish the chunk. */
|
|
FinishChunk (transfer);
|
|
|
|
/* Now return the number of bytes read. */
|
|
*nbytes = nitems * FormatTypeSize (actual_format);
|
|
|
|
/* Bump the read offset. */
|
|
transfer->read_offset += long_length;
|
|
|
|
/* Return bytes_after to the caller. */
|
|
if (bytes_after_return)
|
|
{
|
|
if (actual_format == 32)
|
|
*bytes_after_return = bytes_after * (sizeof (long) / 4);
|
|
else
|
|
*bytes_after_return = bytes_after;
|
|
}
|
|
|
|
return prop_data;
|
|
}
|
|
|
|
static void
|
|
StartSelectionRead (ReadTransfer *transfer)
|
|
{
|
|
unsigned char *prop_data;
|
|
Atom actual_type;
|
|
int actual_format, rc;
|
|
unsigned long nitems, bytes_after;
|
|
|
|
prop_data = NULL;
|
|
|
|
/* First, figure out how big the property data is. */
|
|
rc = XGetWindowProperty (compositor.display, selection_transfer_window,
|
|
transfer->property->atom, 0, 0, True, AnyPropertyType,
|
|
&actual_type, &actual_format, &nitems, &bytes_after,
|
|
&prop_data);
|
|
|
|
if (prop_data)
|
|
XFree (prop_data);
|
|
|
|
if (rc != Success)
|
|
{
|
|
FinishReadTransfer (transfer, False);
|
|
return;
|
|
}
|
|
|
|
/* If the property is of type INCR, this is the beginning of an INCR
|
|
transfer. In this case, delete prop_data, the property, and wait
|
|
for a the property to be set with a new value by the owner. */
|
|
|
|
if (actual_type == INCR)
|
|
{
|
|
transfer->state |= IsIncr;
|
|
transfer->state |= IsWaitingForChunk;
|
|
|
|
XDeleteProperty (compositor.display, selection_transfer_window,
|
|
transfer->property->atom);
|
|
|
|
return;
|
|
}
|
|
|
|
/* If actual_type is None, the property does not exist. Signal
|
|
failure. */
|
|
if (actual_type == None || !actual_format)
|
|
{
|
|
FinishReadTransfer (transfer, False);
|
|
return;
|
|
}
|
|
|
|
/* If bytes_after is 0, the property is empty. Immediately call
|
|
data_finish_func. If the transfer is incremental, then an empty
|
|
property means the data has been fully transferred. */
|
|
if (!bytes_after)
|
|
goto finish_early;
|
|
|
|
/* Otherwise, we now know how long the selection is, and the type of
|
|
the data. Announce that information, along with the beginning of
|
|
this chunk. */
|
|
|
|
transfer->read_offset = 0;
|
|
|
|
/* Also announce the format. */
|
|
transfer->read_format = actual_format;
|
|
|
|
if (transfer->data_start_func
|
|
/* Make sure this isn't run multiple times in the case of INCR
|
|
selections. */
|
|
&& !(transfer->state & IsStarted))
|
|
transfer->data_start_func (transfer, actual_type,
|
|
actual_format);
|
|
|
|
transfer->state |= IsStarted;
|
|
|
|
if (actual_format == 32)
|
|
/* Give actual data size, depending on the size of long. */
|
|
bytes_after = bytes_after * (sizeof (long) / 4);
|
|
|
|
if (transfer->data_read_func)
|
|
transfer->data_read_func (transfer, actual_type, actual_format,
|
|
bytes_after);
|
|
|
|
return;
|
|
|
|
finish_early:
|
|
FinishReadTransfer (transfer, True);
|
|
return;
|
|
}
|
|
|
|
static ReadTransfer *
|
|
FindReadTransferByProp (Atom atom)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
transfer = read_transfers.last;
|
|
|
|
while (transfer != &read_transfers)
|
|
{
|
|
if (transfer->property->atom == atom)
|
|
return transfer;
|
|
|
|
transfer = transfer->last;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Bool
|
|
SendEvent (XEvent *event)
|
|
{
|
|
CatchXErrors ();
|
|
XSendEvent (compositor.display, event->xany.window,
|
|
False, NoEventMask, event);
|
|
return UncatchXErrors (NULL);
|
|
}
|
|
|
|
static void
|
|
SendEventUnsafe (XEvent *event)
|
|
{
|
|
XSendEvent (compositor.display, event->xany.window,
|
|
False, NoEventMask, event);
|
|
}
|
|
|
|
static Bool
|
|
CanConvertTarget (SelectionOwnerInfo *info, Atom target)
|
|
{
|
|
int i;
|
|
|
|
if (target == TARGETS
|
|
|| target == TIMESTAMP)
|
|
return True;
|
|
|
|
for (i = 0; i < info->ntargets; ++i)
|
|
{
|
|
if (target == info->targets[i])
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
static GetDataFunc
|
|
GetTransferFunction (SelectionOwnerInfo *info,
|
|
WriteTransfer *transfer,
|
|
Atom target, Atom *type)
|
|
{
|
|
return info->get_transfer_function (transfer, target, type);
|
|
}
|
|
|
|
static WriteTransfer *
|
|
FindWriteTransfer (Window requestor, Atom property,
|
|
int ignore_state)
|
|
{
|
|
WriteTransfer *transfer;
|
|
|
|
transfer = write_transfers.last;
|
|
|
|
while (transfer != &write_transfers)
|
|
{
|
|
if (transfer->requestor == requestor
|
|
&& transfer->property == property
|
|
&& (!ignore_state
|
|
|| ((transfer->state & ignore_state)
|
|
!= ignore_state)))
|
|
return transfer;
|
|
|
|
transfer = transfer->last;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Bool
|
|
FindQueuedTransfer (Window requestor, Atom property)
|
|
{
|
|
QueuedTransfer *transfer;
|
|
|
|
transfer = queued_transfers.next;
|
|
|
|
while (transfer != &queued_transfers)
|
|
{
|
|
if (transfer->event.xselectionrequest.requestor == requestor
|
|
|| transfer->event.xselectionrequest.property == property)
|
|
return True;
|
|
|
|
transfer = transfer->next;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
static void
|
|
SignalConversionPerformed (WriteTransfer *transfer)
|
|
{
|
|
if (transfer->record)
|
|
{
|
|
/* This conversion is part of a larger conversion to MULTIPLE.
|
|
Subtract this conversion from the number of conversions that
|
|
are still pending. If there are no more pending conversions,
|
|
send the SelectionNotify event and return. */
|
|
|
|
DebugPrint ("Conversion complete; %d conversion(s) are still pending\n",
|
|
transfer->record->pending - 1);
|
|
|
|
if (!(--transfer->record->pending))
|
|
{
|
|
SendEventUnsafe (&transfer->record->event);
|
|
|
|
XFree (transfer->record->atoms);
|
|
XLFree (transfer->record);
|
|
}
|
|
|
|
/* In any case, make transfer->record NULL. */
|
|
transfer->record = NULL;
|
|
}
|
|
|
|
/* If the SelectionNotify has not yet been sent, send it now. */
|
|
if (transfer->event.type)
|
|
SendEventUnsafe (&transfer->event);
|
|
transfer->event.type = 0;
|
|
}
|
|
|
|
static void
|
|
FlushTransfer (WriteTransfer *transfer, Bool force)
|
|
{
|
|
long size;
|
|
|
|
/* Flushing the buffer while waiting for property deletion is an
|
|
invalid operation. */
|
|
XLAssert (!(transfer->state & IsWaitingForDelete));
|
|
|
|
if ((transfer->state & IsStarted) || force)
|
|
{
|
|
if (force)
|
|
DebugPrint ("Forcing transfer\n");
|
|
else
|
|
DebugPrint ("Starting transfer\n");
|
|
|
|
/* If the transfer has already started, or FORCE is set, write
|
|
the property to the window. */
|
|
CatchXErrors ();
|
|
DebugPrint ("Writing property of size %zd\n",
|
|
transfer->offset);
|
|
XChangeProperty (compositor.display, transfer->requestor,
|
|
transfer->property, transfer->type, 8,
|
|
PropModeReplace, transfer->buffer,
|
|
transfer->offset);
|
|
#ifdef DEBUG
|
|
transfer->total_written += transfer->offset;
|
|
#endif
|
|
SignalConversionPerformed (transfer);
|
|
UncatchXErrors (NULL);
|
|
|
|
transfer->offset = 0;
|
|
}
|
|
else
|
|
{
|
|
DebugPrint ("Writing INCR property...\n");
|
|
|
|
/* Otherwise, write an INCR property and wait for it to be
|
|
deleted. */
|
|
size = transfer->size;
|
|
|
|
CatchXErrors ();
|
|
XChangeProperty (compositor.display, transfer->requestor,
|
|
transfer->property, INCR, 32,
|
|
PropModeReplace, (unsigned char *) &size, 1);
|
|
SignalConversionPerformed (transfer);
|
|
UncatchXErrors (NULL);
|
|
|
|
transfer->state |= IsWaitingForIncr;
|
|
}
|
|
|
|
/* Now, begin to wait for the property to be deleted. */
|
|
transfer->state |= IsWaitingForDelete;
|
|
|
|
/* And mark the transfer as having been flushed. */
|
|
transfer->state |= IsFlushed;
|
|
}
|
|
|
|
static void
|
|
SelectPropertyNotify (Window window)
|
|
{
|
|
SelectInputData *data;
|
|
|
|
data = XLLookUpAssoc (foreign_notify_table, window);
|
|
|
|
if (data)
|
|
data->refcount++;
|
|
else
|
|
{
|
|
data = XLMalloc (sizeof *data);
|
|
data->refcount = 1;
|
|
XLMakeAssoc (foreign_notify_table, window, data);
|
|
|
|
/* Actually select for input from the window. */
|
|
CatchXErrors ();
|
|
XSelectInput (compositor.display, window,
|
|
PropertyChangeMask);
|
|
UncatchXErrors (NULL);
|
|
|
|
DebugPrint ("Selecting for PropertyChangeMask on %lu\n",
|
|
window);
|
|
}
|
|
}
|
|
|
|
static void
|
|
DeselectPropertyNotify (Window window)
|
|
{
|
|
SelectInputData *data;
|
|
|
|
data = XLLookUpAssoc (foreign_notify_table, window);
|
|
XLAssert (data != NULL);
|
|
|
|
if (--data->refcount)
|
|
return;
|
|
|
|
DebugPrint ("De-selecting for PropertyChangeMask on %lu\n",
|
|
window);
|
|
|
|
CatchXErrors ();
|
|
XSelectInput (compositor.display, window, NoEventMask);
|
|
UncatchXErrors (NULL);
|
|
|
|
XLDeleteAssoc (foreign_notify_table, window);
|
|
XLFree (data);
|
|
}
|
|
|
|
/* Forward declaration. */
|
|
|
|
static void DrainQueuedTransfers (void);
|
|
|
|
static void
|
|
FreeTransfer (WriteTransfer *transfer)
|
|
{
|
|
DebugPrint ("Deallocating transfer data\n");
|
|
|
|
/* The transfer completed. Unlink it, and also deselect for
|
|
PropertyNotify events from the requestor. */
|
|
|
|
transfer->next->last = transfer->last;
|
|
transfer->last->next = transfer->next;
|
|
|
|
if (transfer->state & IsStarted)
|
|
{
|
|
#ifdef DEBUG
|
|
DebugPrint ("Writing zero-length property data; total: %zu\n",
|
|
transfer->total_written);
|
|
#endif
|
|
|
|
CatchXErrors ();
|
|
/* Write zero-length property data to complete the INCR
|
|
transfer. */
|
|
XChangeProperty (compositor.display, transfer->requestor,
|
|
transfer->property, transfer->type, 8,
|
|
PropModeReplace, NULL, 0);
|
|
UncatchXErrors (NULL);
|
|
}
|
|
|
|
if (transfer->record)
|
|
{
|
|
/* Since FreeTransfer was called without transfer->record having
|
|
being released earlier, the conversion associated with this
|
|
transfer has failed. Write the conversion failure. */
|
|
|
|
transfer->record->atoms[transfer->multiple_offset] = None;
|
|
|
|
DebugPrint ("Conversion at offset %lu failed\n",
|
|
transfer->multiple_offset);
|
|
|
|
CatchXErrors ();
|
|
XChangeProperty (compositor.display, transfer->requestor,
|
|
transfer->record->event.xselection.property,
|
|
ATOM_PAIR, 32, PropModeReplace,
|
|
(unsigned char *) transfer->record->atoms,
|
|
transfer->record->nitems);
|
|
UncatchXErrors (NULL);
|
|
|
|
/* Now, dereference the record, and send the SelectionNotify if
|
|
there are no more pending conversions. */
|
|
|
|
if (!(--transfer->record->pending))
|
|
{
|
|
DebugPrint ("Completing MULTIPLE transfer\n");
|
|
|
|
SendEventUnsafe (&transfer->record->event);
|
|
|
|
XFree (transfer->record->atoms);
|
|
XLFree (transfer->record);
|
|
}
|
|
}
|
|
|
|
/* If there are no more transfers, drain queued ones. */
|
|
if (write_transfers.next == &write_transfers)
|
|
DrainQueuedTransfers ();
|
|
|
|
DeselectPropertyNotify (transfer->requestor);
|
|
RemoveTimer (transfer->timeout);
|
|
XLFree (transfer->buffer);
|
|
XLFree (transfer);
|
|
}
|
|
|
|
static void
|
|
FinishTransferEarly (WriteTransfer *transfer)
|
|
{
|
|
/* Call the transfer function with negative values, to force it to
|
|
free data. */
|
|
|
|
transfer->transfer_function (transfer, NULL, -1, NULL);
|
|
|
|
/* Finish the transfer now. */
|
|
FreeTransfer (transfer);
|
|
}
|
|
|
|
static void
|
|
TransferFinished (WriteTransfer *transfer)
|
|
{
|
|
if (transfer->state & IsWaitingForDelete)
|
|
{
|
|
/* If we are still waiting for property deletion, simply set the
|
|
IsFinished flag. Zero-length property data will be written the
|
|
next time the property is deleted to mark the completion of
|
|
this transfer. */
|
|
transfer->state |= IsFinished;
|
|
|
|
DebugPrint ("Transfer finished; waiting for property deletion\n");
|
|
}
|
|
else if (transfer->offset
|
|
/* Even if there is no more data, we must still write
|
|
zero-length property data to complete the selection
|
|
transfer if nothing has previously been written. */
|
|
|| !(transfer->state & IsFlushed))
|
|
{
|
|
DebugPrint ("Transfer finished, but there is still property data"
|
|
" unwritten (offset %td)\n", transfer->offset);
|
|
|
|
/* There is still property data left to be written. */
|
|
FlushTransfer (transfer, True);
|
|
transfer->state |= IsFinished;
|
|
}
|
|
else
|
|
/* The transfer is really finished. */
|
|
FreeTransfer (transfer);
|
|
}
|
|
|
|
static void
|
|
TransferBecameReadable (WriteTransfer *transfer)
|
|
{
|
|
ptrdiff_t bytes_read;
|
|
ReadStatus status;
|
|
|
|
/* If we are still waiting for the INCR property to be deleted,
|
|
don't read anything, but flag the read as pending. */
|
|
|
|
if (transfer->state & IsWaitingForDelete)
|
|
{
|
|
DebugPrint ("Transfer became readable, but we are still waiting"
|
|
" for a property deletion\n");
|
|
transfer->state |= IsReadable;
|
|
return;
|
|
}
|
|
|
|
DebugPrint ("Reading from transfer function\n");
|
|
|
|
/* The data source became readable. Every time the source becomes
|
|
readable, try to fill up the transfer buffer.
|
|
|
|
If we get EOF without filling up the buffer, simply set the
|
|
property to the data and the correct type and finish the
|
|
transfer; otherwise, initiate INCR transfer. */
|
|
|
|
transfer->state &= ~IsReadable;
|
|
|
|
status = transfer->transfer_function (transfer,
|
|
(transfer->buffer
|
|
+ transfer->offset),
|
|
(transfer->size
|
|
- transfer->offset),
|
|
&bytes_read);
|
|
|
|
if (status != EndOfFile)
|
|
{
|
|
transfer->offset += bytes_read;
|
|
DebugPrint ("Read %td bytes, offset is now %td into %td\n",
|
|
bytes_read, transfer->offset, transfer->size);
|
|
|
|
/* Make sure nothing overflowed. */
|
|
XLAssert (transfer->offset <= transfer->size);
|
|
|
|
if (transfer->offset == transfer->size
|
|
/* If a bigger buffer is required, flush the transfer now,
|
|
so the entire transfer buffer can be reused. */
|
|
|| status == NeedBiggerBuffer)
|
|
{
|
|
if (status == NeedBiggerBuffer)
|
|
/* Since a bigger buffer is needed, that means there is
|
|
still data buffered up to be read. */
|
|
transfer->state |= IsReadable;
|
|
|
|
FlushTransfer (transfer, False);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugPrint ("Transfer complete, bytes read as part of EOF: %td, "
|
|
"off: %td size: %td\n", bytes_read, transfer->offset,
|
|
transfer->size);
|
|
|
|
transfer->offset += bytes_read;
|
|
TransferFinished (transfer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ConvertSelectionTargets1 (SelectionOwnerInfo *info,
|
|
Window requestor, Atom property)
|
|
{
|
|
Atom *targets;
|
|
|
|
targets = alloca ((3 + info->ntargets)
|
|
* sizeof *targets);
|
|
|
|
/* Add the required targets, TARGETS and MULTIPLE. */
|
|
targets[0] = TARGETS;
|
|
targets[1] = MULTIPLE;
|
|
targets[2] = TIMESTAMP;
|
|
|
|
/* And the rest of the targets. */
|
|
memcpy (&targets[3], info->targets,
|
|
info->ntargets * sizeof *targets);
|
|
|
|
XChangeProperty (compositor.display, requestor,
|
|
property, XA_ATOM, 32, PropModeReplace,
|
|
(unsigned char *) targets,
|
|
2 + info->ntargets);
|
|
}
|
|
|
|
static void
|
|
ConvertSelectionTargets (SelectionOwnerInfo *info, XEvent *notify)
|
|
{
|
|
|
|
/* Set the property and send the SelectionNotify event. */
|
|
CatchXErrors ();
|
|
ConvertSelectionTargets1 (info, notify->xselection.requestor,
|
|
notify->xselection.property);
|
|
SendEventUnsafe (notify);
|
|
UncatchXErrors (NULL);
|
|
}
|
|
|
|
static void
|
|
ConvertSelectionTimestamp1 (SelectionOwnerInfo *info, Window requestor,
|
|
Window property)
|
|
{
|
|
XChangeProperty (compositor.display, requestor, property,
|
|
XA_ATOM, 32, PropModeReplace,
|
|
(unsigned char *) &info->time, 1);
|
|
}
|
|
|
|
static void
|
|
ConvertSelectionTimestamp (SelectionOwnerInfo *info, XEvent *notify)
|
|
{
|
|
/* Set the property and send the SelectionNotify event. */
|
|
CatchXErrors ();
|
|
ConvertSelectionTimestamp1 (info, notify->xselection.requestor,
|
|
notify->xselection.property);
|
|
SendEventUnsafe (notify);
|
|
UncatchXErrors (NULL);
|
|
}
|
|
|
|
static void
|
|
QueueTransfer (XEvent *event)
|
|
{
|
|
QueuedTransfer *transfer;
|
|
|
|
transfer = XLMalloc (sizeof *transfer);
|
|
transfer->event = *event;
|
|
transfer->next = queued_transfers.next;
|
|
transfer->last = &queued_transfers;
|
|
queued_transfers.next->last = transfer;
|
|
queued_transfers.next = transfer;
|
|
}
|
|
|
|
static void
|
|
HandleWriteTimeout (Timer *timer, void *data, struct timespec time)
|
|
{
|
|
DebugPrint ("Transfer timeout\n");
|
|
|
|
FinishTransferEarly (data);
|
|
}
|
|
|
|
static void
|
|
ConvertSelectionMultiple (SelectionOwnerInfo *info, XEvent *event,
|
|
XEvent *notify)
|
|
{
|
|
Status rc;
|
|
unsigned char *prop_data;
|
|
Atom *atoms;
|
|
Atom actual_type;
|
|
int actual_format;
|
|
unsigned long nitems, bytes_after, i, quantum;
|
|
WriteTransfer *transfer;
|
|
Bool prop_data_changed;
|
|
MultipleRecord *record;
|
|
|
|
prop_data = NULL;
|
|
prop_data_changed = False;
|
|
|
|
/* Try to get the ATOM_PAIRs describing the targets and properties
|
|
from the source. */
|
|
CatchXErrors ();
|
|
rc = XGetWindowProperty (compositor.display,
|
|
event->xselectionrequest.requestor,
|
|
event->xselectionrequest.property,
|
|
0, 65535, False, ATOM_PAIR,
|
|
&actual_type, &actual_format,
|
|
&nitems, &bytes_after, &prop_data);
|
|
UncatchXErrors (NULL);
|
|
|
|
if (rc != Success || actual_format != 32 || nitems % 2
|
|
|| actual_type != ATOM_PAIR || !prop_data)
|
|
{
|
|
if (prop_data)
|
|
XFree (prop_data);
|
|
|
|
DebugPrint ("Failed to retrieve ATOM_PAIR parameter\n");
|
|
return;
|
|
}
|
|
|
|
atoms = (Atom *) prop_data;
|
|
|
|
DebugPrint ("Number of items in atom pair: %lu\n", nitems / 2);
|
|
|
|
for (i = 0; i < nitems; i += 2)
|
|
{
|
|
DebugPrint ("Verifying MULTIPLE transfer; target = %lu, property = %lu\n",
|
|
atoms[i + 0], atoms[i + 1]);
|
|
|
|
if (FindWriteTransfer (event->xselectionrequest.requestor, atoms[1], 0)
|
|
|| FindQueuedTransfer (event->xselectionrequest.requestor, atoms[1]))
|
|
{
|
|
DebugPrint ("Found ongoing selection transfer with same requestor "
|
|
"and property; this MULTIPLE request will have to be "
|
|
"queued.\n");
|
|
|
|
QueueTransfer (event);
|
|
|
|
if (prop_data)
|
|
XLFree (prop_data);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
record = XLMalloc (sizeof *record);
|
|
record->pending = 0;
|
|
record->event = *notify;
|
|
record->nitems = nitems;
|
|
record->atoms = atoms;
|
|
|
|
CatchXErrors ();
|
|
|
|
for (i = 0; i < nitems; i += 2)
|
|
{
|
|
/* Fortunately, we don't have to verify every transfer here to
|
|
make sure the client didn't specify duplicate property names,
|
|
as the ICCCM doesn't explicitly specify how programs should
|
|
behave in that case. Things will simply go wrong later on,
|
|
and the transfer will time out. */
|
|
|
|
DebugPrint ("Starting MULTIPLE transfer; target = %lu, property = %lu\n",
|
|
atoms[i + 0], atoms[i + 1]);
|
|
|
|
if (atoms[0] == MULTIPLE)
|
|
{
|
|
DebugPrint ("Saw nested MULTIPLE transfer; "
|
|
"such conversions are not allowed\n");
|
|
atoms[0] = None;
|
|
prop_data_changed = True;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!CanConvertTarget (info, atoms[0]))
|
|
{
|
|
DebugPrint ("Couldn't convert to target for a simple reason;"
|
|
" replacing atom with NULL\n");
|
|
atoms[0] = None;
|
|
prop_data_changed = True;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (atoms[0] == TARGETS)
|
|
{
|
|
DebugPrint ("Converting to special target TARGETS...\n");
|
|
ConvertSelectionTargets1 (info, event->xselectionrequest.requestor,
|
|
atoms[1]);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (atoms[0] == TIMESTAMP)
|
|
{
|
|
DebugPrint ("Converting to special target TIMESTAMP...\n");
|
|
ConvertSelectionTimestamp1 (info, event->xselectionrequest.requestor,
|
|
atoms[1]);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Create the write transfer and link it onto the list. */
|
|
transfer = XLCalloc (1, sizeof *transfer);
|
|
|
|
/* This seems to be a sufficiently reasonable value. */
|
|
quantum = MIN (SelectionQuantum (), 65535 * 2);
|
|
|
|
transfer->next = write_transfers.next;
|
|
transfer->last = &write_transfers;
|
|
transfer->requestor = event->xselectionrequest.requestor;
|
|
transfer->selection = event->xselectionrequest.selection;
|
|
transfer->target = atoms[i + 0];
|
|
transfer->property = atoms[i + 1];
|
|
transfer->time = event->xselectionrequest.time;
|
|
transfer->buffer = XLMalloc (quantum);
|
|
transfer->size = quantum;
|
|
|
|
/* Add a timeout for 5 seconds. */
|
|
transfer->timeout = AddTimer (HandleWriteTimeout, transfer,
|
|
MakeTimespec (5, 0));
|
|
|
|
write_transfers.next->last = transfer;
|
|
write_transfers.next = transfer;
|
|
|
|
SelectPropertyNotify (transfer->requestor);
|
|
|
|
transfer->transfer_function
|
|
= GetTransferFunction (info, transfer,
|
|
transfer->target,
|
|
&transfer->type);
|
|
|
|
if (!transfer->transfer_function)
|
|
{
|
|
/* Something failed (most probably, we ran out of fds to
|
|
make the pipe). Cancel the transfer and signal
|
|
failure. */
|
|
atoms[0] = None;
|
|
prop_data_changed = True;
|
|
|
|
FreeTransfer (transfer);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, add the transfer record. */
|
|
record->pending++;
|
|
transfer->record = record;
|
|
transfer->multiple_offset = i;
|
|
}
|
|
}
|
|
|
|
/* If prop_data changed, which happens when a conversion fails,
|
|
write its new contents back onto the requestor to let it know
|
|
which conversions could not be made. */
|
|
if (prop_data_changed)
|
|
XChangeProperty (compositor.display,
|
|
event->xselectionrequest.requestor,
|
|
event->xselectionrequest.property,
|
|
ATOM_PAIR, 32, PropModeReplace,
|
|
prop_data, nitems);
|
|
|
|
if (prop_data && !record->pending)
|
|
/* If record->pending, then the individual write transfers still
|
|
reference the atom pair data. */
|
|
XFree (prop_data);
|
|
|
|
/* If no pending conversions remain, immediately send success and
|
|
free the conversion. */
|
|
if (!record->pending)
|
|
{
|
|
SendEvent (&record->event);
|
|
XLFree (record);
|
|
}
|
|
|
|
UncatchXErrors (NULL);
|
|
}
|
|
|
|
static Bool
|
|
HandleSelectionRequest (XEvent *event)
|
|
{
|
|
XEvent notify;
|
|
WriteTransfer *transfer, *existing_transfer;
|
|
long quantum;
|
|
SelectionOwnerInfo *info;
|
|
|
|
DebugPrint ("Received SelectionRequest. Time: %lu, requestor: %lu"
|
|
", target: %lu, selection: %lu, property: %lu, serial: %lu\n",
|
|
event->xselectionrequest.time,
|
|
event->xselectionrequest.requestor,
|
|
event->xselectionrequest.target,
|
|
event->xselectionrequest.selection,
|
|
event->xselectionrequest.property,
|
|
event->xselectionrequest.serial);
|
|
|
|
notify.xselection.type = SelectionNotify;
|
|
notify.xselection.requestor = event->xselectionrequest.requestor;
|
|
notify.xselection.time = event->xselectionrequest.time;
|
|
notify.xselection.target = event->xselectionrequest.target;
|
|
notify.xselection.selection = event->xselectionrequest.selection;
|
|
notify.xselection.property = None;
|
|
|
|
info = XLLookUpAssoc (selection_owner_info,
|
|
event->xselectionrequest.selection);
|
|
|
|
if (!info
|
|
|| !info->get_transfer_function
|
|
/* The ICCCM prohibits clients from using CurrentTime, but some
|
|
do anyway. */
|
|
|| (event->xselectionrequest.time != CurrentTime
|
|
&& TimeIs (event->xselectionrequest.time, Earlier, info->time))
|
|
|| (!CanConvertTarget (info, event->xselectionrequest.target)
|
|
/* CanConvertTarget itself does not understand MULTIPLE,
|
|
because it itself is called while handling MULTIPLE. */
|
|
&& event->xselectionrequest.target != MULTIPLE))
|
|
{
|
|
DebugPrint ("Couldn't convert selection due to simple reason\n");
|
|
|
|
/* The event is out of date, or the selection cannot be
|
|
converted to the given target. Send failure. */
|
|
SendEvent (¬ify);
|
|
return True;
|
|
}
|
|
|
|
/* If a selection request with the same property and window already
|
|
exists, delay this request for later. */
|
|
existing_transfer
|
|
= FindWriteTransfer (event->xselectionrequest.requestor,
|
|
event->xselectionrequest.property,
|
|
/* Ignore write transfers that are finished
|
|
but pending property deletion. If the
|
|
existing transfer is finished, but we are
|
|
still waiting for property deletion, allow
|
|
the new transfer to take place. This is
|
|
because some very popular programs ask for
|
|
TARGETS, and then ask for STRING with the
|
|
same property, but only delete the
|
|
property for the first request after the
|
|
data for the second request arrives. This
|
|
is against the ICCCM, as it says:
|
|
|
|
The requestor should set the property
|
|
argument to the name of a property that
|
|
the owner can use to report the value of
|
|
the selection. Requestors should ensure
|
|
that the named property does not exist
|
|
on the window before issuing the
|
|
ConvertSelection request.
|
|
|
|
and:
|
|
|
|
Once all the data in the selection has
|
|
been retrieved (which may require
|
|
getting the values of several properties
|
|
-- see the section called "Use of
|
|
Selection Properties". ), the requestor
|
|
should delete the property in the
|
|
SelectionNotify request by using a
|
|
GetProperty request with the delete
|
|
argument set to True. As previously
|
|
discussed, the owner has no way of
|
|
knowing when the data has been
|
|
transferred to the requestor unless the
|
|
property is removed.
|
|
|
|
Both paragraphs mean that the property
|
|
should have been deleted by the time the
|
|
second request is made! */
|
|
IsFinished | IsWaitingForDelete);
|
|
|
|
if (existing_transfer
|
|
/* We need to look at the queue too; otherwise, events from the
|
|
future might be handled out of order, if the original write
|
|
transfer is gone, but some events are still queued. */
|
|
|| FindQueuedTransfer (event->xselectionrequest.requestor,
|
|
event->xselectionrequest.property))
|
|
{
|
|
DebugPrint ("Queueing this selection request for later, because"
|
|
" an identical transfer is already taking place\n");
|
|
QueueTransfer (event);
|
|
|
|
return True;
|
|
}
|
|
|
|
/* Otherwise, begin a selection transfer to the requestor. */
|
|
notify.xselection.property = event->xselectionrequest.property;
|
|
|
|
if (notify.xselection.property == None)
|
|
/* This is an obsolete client; use the target as the property. */
|
|
notify.xselection.property = event->xselectionrequest.target;
|
|
|
|
/* Handle special targets, such as TARGETS and TIMESTAMP. */
|
|
if (notify.xselection.target == TARGETS)
|
|
{
|
|
DebugPrint ("Converting selection to special target TARGETS\n");
|
|
ConvertSelectionTargets (info, ¬ify);
|
|
return True;
|
|
}
|
|
else if (notify.xselection.target == TIMESTAMP)
|
|
{
|
|
DebugPrint ("Converting selection to special target TIMESTAMP\n");
|
|
ConvertSelectionTimestamp (info, ¬ify);
|
|
return True;
|
|
}
|
|
else if (notify.xselection.target == MULTIPLE)
|
|
{
|
|
if (event->xselectionrequest.property == None)
|
|
{
|
|
DebugPrint ("Got malformed MULTIPLE request with no property\n");
|
|
notify.xselection.property = None;
|
|
SendEvent (¬ify);
|
|
|
|
return True;
|
|
}
|
|
|
|
DebugPrint ("Converting selection to special target MULTIPLE\n");
|
|
ConvertSelectionMultiple (info, event, ¬ify);
|
|
return True;
|
|
}
|
|
|
|
DebugPrint ("Starting selection transfer\n");
|
|
|
|
/* Create the write transfer and link it onto the list. */
|
|
transfer = XLCalloc (1, sizeof *transfer);
|
|
|
|
/* This seems to be a sufficiently reasonable value. */
|
|
quantum = MIN (SelectionQuantum (), 65535 * 2);
|
|
|
|
transfer->next = write_transfers.next;
|
|
transfer->last = &write_transfers;
|
|
transfer->requestor = event->xselectionrequest.requestor;
|
|
transfer->selection = event->xselectionrequest.selection;
|
|
transfer->target = event->xselectionrequest.target;
|
|
transfer->property = notify.xselection.property;
|
|
transfer->time = event->xselectionrequest.time;
|
|
transfer->event = notify;
|
|
transfer->buffer = XLMalloc (quantum);
|
|
transfer->size = quantum;
|
|
|
|
/* Add a timeout for 5 seconds. */
|
|
transfer->timeout = AddTimer (HandleWriteTimeout, transfer,
|
|
MakeTimespec (5, 0));
|
|
|
|
write_transfers.next->last = transfer;
|
|
write_transfers.next = transfer;
|
|
|
|
SelectPropertyNotify (transfer->requestor);
|
|
|
|
transfer->transfer_function
|
|
= GetTransferFunction (info, transfer,
|
|
transfer->target,
|
|
&transfer->type);
|
|
|
|
if (!transfer->transfer_function)
|
|
{
|
|
/* Something failed (most likely, we ran out of fds to make the
|
|
pipe). Cancel the transfer and signal failure. */
|
|
notify.xselection.property = None;
|
|
SendEvent (¬ify);
|
|
|
|
FreeTransfer (transfer);
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
static void
|
|
DrainQueuedTransfers (void)
|
|
{
|
|
QueuedTransfer temp, *item, *last;
|
|
|
|
/* If nothing is queued, return. */
|
|
|
|
if (queued_transfers.next == &queued_transfers)
|
|
return;
|
|
|
|
/* First, relink everything onto this temp sentinel. That way, if
|
|
stuff gets queued again in HandleSelectionRequest, the list
|
|
structure won't change underneath our noses. */
|
|
|
|
queued_transfers.last->next = &temp;
|
|
queued_transfers.next->last = &temp;
|
|
temp.next = queued_transfers.next;
|
|
temp.last = queued_transfers.last;
|
|
|
|
queued_transfers.next = &queued_transfers;
|
|
queued_transfers.last = &queued_transfers;
|
|
|
|
/* Then, read from the other side of the queue, and handle
|
|
everything. */
|
|
item = temp.last;
|
|
|
|
while (item != &temp)
|
|
{
|
|
last = item;
|
|
item = item->last;
|
|
|
|
DebugPrint ("Draining one request with serial: %lu\n",
|
|
last->event.xselectionrequest.serial);
|
|
|
|
HandleSelectionRequest (&last->event);
|
|
XLFree (last);
|
|
}
|
|
}
|
|
|
|
static Bool
|
|
HandleSelectionNotify (XEvent *event)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
/* If event->property is None, then the selection transfer could not
|
|
be made. Find which transfer couldn't be made by looking in the
|
|
queue for the first transfer matching event->selection,
|
|
event->target and event->time, and unlink it. */
|
|
if (event->xselection.property == None)
|
|
{
|
|
transfer = FindReadTransfer (event->xselection.selection,
|
|
event->xselection.target,
|
|
event->xselection.time);
|
|
|
|
if (!transfer)
|
|
return True;
|
|
|
|
FinishReadTransfer (transfer, False);
|
|
return True;
|
|
}
|
|
|
|
/* Otherwise, find the outstanding selection transfer using the
|
|
property. */
|
|
transfer = FindReadTransferByProp (event->xselection.property);
|
|
|
|
if (!transfer)
|
|
return True;
|
|
|
|
/* And start transferring data from it. */
|
|
StartSelectionRead (transfer);
|
|
return True;
|
|
}
|
|
|
|
static Bool
|
|
HandlePropertyDelete (XEvent *event)
|
|
{
|
|
WriteTransfer *transfer, *last;
|
|
|
|
transfer = write_transfers.last;
|
|
|
|
DebugPrint ("Handling property deletion for %lu; window %lu\n",
|
|
event->xproperty.atom, event->xproperty.window);
|
|
|
|
while (transfer != &write_transfers)
|
|
{
|
|
last = transfer->last;
|
|
|
|
/* There can be multiple finished transfers with the same
|
|
property pending deletion if a client not compliant with the
|
|
ICCCM is in use. See the large comment in
|
|
HandleSelectionRequest. */
|
|
DebugPrint ("Handling transfer %p\n", event);
|
|
|
|
if (transfer->state & IsFinished)
|
|
{
|
|
DebugPrint ("Completing transfer\n");
|
|
|
|
/* The transfer is now complete; finish it by freeing its
|
|
data, and potentially writing zero-length data. */
|
|
FreeTransfer (transfer);
|
|
}
|
|
else if (transfer->state & IsWaitingForIncr)
|
|
{
|
|
/* If transfer is waiting for the INCR property to be
|
|
deleted, mark it as started, and flush it again to write
|
|
the first piece of property data. */
|
|
|
|
DebugPrint ("Starting transfer in response to INCR property deletion\n");
|
|
|
|
transfer->state |= IsStarted;
|
|
transfer->state &= ~IsWaitingForIncr;
|
|
transfer->state &= ~IsWaitingForDelete;
|
|
|
|
/* Flush the transfer again to write the property data. */
|
|
FlushTransfer (transfer, False);
|
|
}
|
|
else
|
|
{
|
|
DebugPrint ("Continuing transfer\n");
|
|
|
|
/* Clear IsWaitingForRelease. */
|
|
transfer->state &= ~IsWaitingForDelete;
|
|
|
|
if (transfer->state & IsReadable)
|
|
{
|
|
DebugPrint ("Picking read back up from where it was left\n");
|
|
|
|
/* And signal that the transfer is readable again. */
|
|
TransferBecameReadable (transfer);
|
|
}
|
|
}
|
|
|
|
transfer = last;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
static Bool
|
|
HandlePropertyNotify (XEvent *event)
|
|
{
|
|
ReadTransfer *transfer;
|
|
|
|
if (event->xproperty.window != DefaultRootWindow (compositor.display))
|
|
/* Xlib selects for PropertyNotifyMask on the root window, which
|
|
results in a lot of noise here. */
|
|
DebugPrint ("PropertyNotify event:\n"
|
|
"serial:\t%lu\n"
|
|
"window:\t%lu\n"
|
|
"atom:\t%lu\n"
|
|
"time:\t%lu\n"
|
|
"state:\t%s\n",
|
|
event->xproperty.serial,
|
|
event->xproperty.window,
|
|
event->xproperty.atom,
|
|
event->xproperty.time,
|
|
(event->xproperty.state == PropertyNewValue
|
|
? "PropertyNewValue" : "PropertyDelete"));
|
|
|
|
if (event->xproperty.state != PropertyNewValue)
|
|
return HandlePropertyDelete (event);
|
|
|
|
/* Find the transfer corresponding to this property. */
|
|
transfer = FindReadTransferByProp (event->xproperty.atom);
|
|
|
|
if (!transfer)
|
|
return False;
|
|
|
|
/* By the time transfer->state & IsIncr, no more out-of-date
|
|
PropertyNotify events from previous selection requests can be
|
|
received. */
|
|
|
|
if (transfer->state & IsIncr
|
|
&& transfer->state & IsWaitingForChunk)
|
|
{
|
|
/* Clear the waiting-for-chunk flag, and begin reading from this
|
|
chunk. */
|
|
transfer->state &= ~IsWaitingForChunk;
|
|
StartSelectionRead (transfer);
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
Bool
|
|
HookSelectionEvent (XEvent *event)
|
|
{
|
|
if (event->xany.type == SelectionNotify)
|
|
return HandleSelectionNotify (event);
|
|
|
|
if (event->xany.type == SelectionRequest)
|
|
return HandleSelectionRequest (event);
|
|
|
|
if (event->xany.type == PropertyNotify)
|
|
return HandlePropertyNotify (event);
|
|
|
|
return False;
|
|
}
|
|
|
|
void *
|
|
GetTransferData (ReadTransfer *transfer)
|
|
{
|
|
return transfer->data;
|
|
}
|
|
|
|
Time
|
|
GetTransferTime (ReadTransfer *transfer)
|
|
{
|
|
return transfer->time;
|
|
}
|
|
|
|
void *
|
|
GetWriteTransferData (WriteTransfer *transfer)
|
|
{
|
|
return transfer->data;
|
|
}
|
|
|
|
void
|
|
SetWriteTransferData (WriteTransfer *transfer, void *data)
|
|
{
|
|
transfer->data = data;
|
|
}
|
|
|
|
void
|
|
CompleteDelayedTransfer (ReadTransfer *transfer)
|
|
{
|
|
/* Right now, we don't have to do any more than this. */
|
|
XFree (transfer);
|
|
}
|
|
|
|
Bool
|
|
OwnSelection (Timestamp time, Atom selection,
|
|
GetDataFunc (*hook) (WriteTransfer *transfer,
|
|
Atom, Atom *),
|
|
Atom *targets, int ntargets)
|
|
{
|
|
Window owner;
|
|
SelectionOwnerInfo *info;
|
|
|
|
info = XLLookUpAssoc (selection_owner_info, selection);
|
|
|
|
if (info && TimestampIs (time, Earlier, info->time))
|
|
/* The timestamp is out of date. */
|
|
return False;
|
|
|
|
XSetSelectionOwner (compositor.display, selection,
|
|
selection_transfer_window,
|
|
time.milliseconds);
|
|
|
|
/* Check if selection ownership was actually set. */
|
|
owner = XGetSelectionOwner (compositor.display, selection);
|
|
|
|
/* If ownership wasn't successfully set, return. */
|
|
if (owner != selection_transfer_window)
|
|
return False;
|
|
|
|
/* Otherwise, update or allocate the info structure. */
|
|
|
|
if (!info)
|
|
{
|
|
info = XLMalloc (sizeof *info);
|
|
|
|
XLMakeAssoc (selection_owner_info, selection,
|
|
info);
|
|
}
|
|
else
|
|
XLFree (info->targets);
|
|
|
|
info->time = time;
|
|
info->targets = XLMalloc (sizeof *info->targets * ntargets);
|
|
info->ntargets = ntargets;
|
|
info->get_transfer_function = hook;
|
|
memcpy (info->targets, targets, sizeof *targets * ntargets);
|
|
|
|
return True;
|
|
}
|
|
|
|
void
|
|
DisownSelection (Atom selection)
|
|
{
|
|
SelectionOwnerInfo *info;
|
|
|
|
info = XLLookUpAssoc (selection_owner_info, selection);
|
|
|
|
if (info && info->get_transfer_function)
|
|
{
|
|
XSetSelectionOwner (compositor.display, selection,
|
|
None, info->time.milliseconds);
|
|
|
|
/* Also free info->targets. */
|
|
XLFree (info->targets);
|
|
info->targets = NULL;
|
|
|
|
/* And set the selection to disowned. */
|
|
info->get_transfer_function = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
StartReading (WriteTransfer *transfer)
|
|
{
|
|
TransferBecameReadable (transfer);
|
|
}
|
|
|
|
void
|
|
InitSelections (void)
|
|
{
|
|
XSetWindowAttributes attrs;
|
|
int flags;
|
|
|
|
/* Set up sentinel nodes for various lists. */
|
|
prop_atoms.next = &prop_atoms;
|
|
prop_atoms.last = &prop_atoms;
|
|
free_list.next = &free_list;
|
|
free_list.last = &free_list;
|
|
read_transfers.next = &read_transfers;
|
|
read_transfers.last = &read_transfers;
|
|
write_transfers.next = &write_transfers;
|
|
write_transfers.last = &write_transfers;
|
|
queued_transfers.next = &queued_transfers;
|
|
queued_transfers.last = &queued_transfers;
|
|
|
|
/* Make the window used to transfer selection data. */
|
|
attrs.override_redirect = True;
|
|
attrs.event_mask = PropertyChangeMask;
|
|
flags = CWEventMask | CWOverrideRedirect;
|
|
|
|
selection_transfer_window
|
|
= XCreateWindow (compositor.display,
|
|
DefaultRootWindow (compositor.display),
|
|
-1, -1, 1, 1, 0, CopyFromParent, InputOnly,
|
|
CopyFromParent, flags, &attrs);
|
|
|
|
/* Make the assoc tables used to keep track of input masks and
|
|
selection data. */
|
|
foreign_notify_table = XLCreateAssocTable (32);
|
|
selection_owner_info = XLCreateAssocTable (32);
|
|
|
|
DebugPrint ("Selection transfer window is %lu\n",
|
|
selection_transfer_window);
|
|
}
|