12to11/data_device.c
hujianwei 0bdc502068 Fix text input bugs with grabbed popups and bugs found by new tests
* 12to11-test.xml (test_manager) <error>: Add
invalid_user_time.
<get_serial, serial> New request and event.
(test_seat_controller) <set_last_user_time>: New request.

* compositor.h (struct _TextInputFuncs): Make `filter_input'
return keycode.
* data_device.c (DestroyReference): Check if reference device is
detached before unlinking reference.
* seat.c (LookupKeysym): Delete function.
(DispatchKey): Use keycodes instead.
(XLSeatExplicitlyGrabSurface): Avoid using owner-events keyboard
grab.

* select.c (struct _SelectionOwnerInfo, HandleSelectionRequest):
Allow CurrentTime in selection requests.  Compensate for
wraparound as well.
(OwnSelection, DisownSelection): Use Timestamp instead of Time.
* test.c (GetSerial): New function.
(test_manager_impl): Add new function.
* test_seat.c (SetLastUserTime): New function.
(seat_controller_impl): Add new function.
* tests/Imakefile (LOCAL_LIBRARIES): Remove GBM and DRM.
(SRCS10, OBJS10, SRCS11, OBJS11): New variables.
(dmabuf_test): Only link this program with GBM and DRM.
(PROGRAMS): Add select_test, select_helper.
(select_test, select_helper): New programs.

* tests/README: Document that select_test needs to be run under
vfb.
* tests/run_tests.sh: Add TODO note.
* tests/svnignore.txt: Add select_test and select_helper.
* tests/test_harness.c (handle_test_manager_serial): New
function.
(test_manager_listener): Add new function.
(open_test_display): Clear display->seat.
(test_get_serial): New function.

* tests/test_harness.h (struct test_display): New function
`serial'.

* text_input.c (CreateIC): Improve debugging code.
(CalculateKeycodeForEvent): Move earlier.
(FilterInputCallback): Handle keycodes here as well.
(XLTextInputDispatchCoreEvent): Add more debugging code.

* xdata.c (SelectSelectionInput): Obtain server time here.
(XLOwnDragSelection, NoteLocalSelectionFooter): Convert times to
Timestamp.
2022-11-12 03:51:21 +00:00

1611 lines
40 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 server.
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 <string.h>
#include "compositor.h"
typedef struct _DataSource DataSource;
typedef struct _DataDeviceReference DataDeviceReference;
typedef struct _DataOffer DataOffer;
typedef struct _DataDestroyCallback DataDestroyCallback;
enum
{
IsDragAndDrop = 1,
IsMimeTypeAccepted = (1 << 2),
IsActionSent = (1 << 3),
IsFinished = (1 << 4),
};
enum
{
ActionsSet = 1,
ActionsSent = (1 << 2),
TypeAccepted = (1 << 3),
Version3Supported = (1 << 4),
};
struct _DataDestroyCallback
{
/* The next and last destroy callbacks in this list. */
DataDestroyCallback *next, *last;
/* Function called when the surface is destroyed. */
void (*destroy_func) (void *data);
/* Data for the surface. */
void *data;
};
struct _DataOffer
{
/* The next data offer object in this list. */
DataOffer *next, *last;
/* The DataSource corresponding to this data offer. */
DataSource *source;
/* Some state and flags. */
int state;
/* The last action sent. -1 if invalid. */
int last_action;
/* The struct wl_resource of this data offer. */
struct wl_resource *resource;
/* The serial of the data device entry in response to which this
object was created. */
uint32_t dnd_serial;
};
struct _DataDeviceReference
{
/* The next and last data device references. */
DataDeviceReference *next, *last;
/* The associated data device. */
DataDevice *device;
/* The associated struct wl_resource. */
struct wl_resource *resource;
};
struct _DataSource
{
/* List of const char *, which are the MIME types offered by this
data source. */
XLList *mime_types;
/* List of atoms corresponding to those MIME types, in the same
order. */
XIDList *atom_types;
/* Number of corresponding MIME types. */
int n_mime_types;
/* The resource associated with this data source. */
struct wl_resource *resource;
/* List of data offers associated with this data source. */
DataOffer offers;
/* Some flags associated with this data source. */
int state;
/* Drag-and-drop actions supported by this data source. */
uint32_t actions;
/* The data device from which this data source is being dragged. */
DataDevice *drag_device;
/* The destroy callback associated with that data device. */
DataDestroyCallback *drag_device_callback;
/* List of destroy callbacks. */
DataDestroyCallback destroy_callbacks;
};
struct _DataDevice
{
/* The associated seat. */
Seat *seat;
/* The number of references to this data device. */
int refcount;
/* Linked list of references to this data device. */
DataDeviceReference references;
/* The drag and drop operation state. supported_actions is the mask
consisting of actions supported by the target. */
uint32_t supported_actions;
/* This is the mask containing actions preferred by the target. */
uint32_t preferred_action;
/* This is the "serial" of the last enter event. */
uint32_t dnd_serial;
/* List of destroy callbacks. */
DataDestroyCallback destroy_callbacks;
};
/* The data device manager global. */
static struct wl_global *global_data_device_manager;
/* The current selection. */
static DataSource *current_selection_data;
/* A sentinel value that means a foreign selection is in use. */
static DataSource foreign_selection_key;
/* Functions to call to obtain wl_data_offer resources and send
associated data for foreign selections. */
static CreateOfferFuncs foreign_selection_functions;
/* Time the foreign selection was changed. */
static Timestamp foreign_selection_time;
/* When it changed. */
static uint32_t last_selection_change_serial;
/* Generic destroy callback implementation. */
static DataDestroyCallback *
AddDestroyCallbackAfter (DataDestroyCallback *start,
void (*destroy_func) (void *),
void *data)
{
DataDestroyCallback *callback;
callback = XLMalloc (sizeof *callback);
callback->last = start;
callback->next = start->next;
start->next->last = callback;
start->next = callback;
callback->destroy_func = destroy_func;
callback->data = data;
return callback;
}
static void
FreeDestroyCallbacks (DataDestroyCallback *start)
{
DataDestroyCallback *next, *last;
next = start->next;
while (next != start)
{
last = next;
next = last->next;
last->destroy_func (last->data);
XLFree (last);
}
}
static void
CancelDestroyCallback (DataDestroyCallback *start)
{
start->next->last = start->last;
start->last->next = start->next;
XLFree (start);
}
/* Data offer implementation. */
static void
FreeDataOffer (DataOffer *offer)
{
/* Mark this offer as invalid by setting the resource's user_data to
NULL. */
if (offer->resource)
wl_resource_set_user_data (offer->resource, NULL);
/* Unlink the offer. */
offer->last->next = offer->next;
offer->next->last = offer->last;
/* Free the offer. */
XLFree (offer);
}
static void
Accept (struct wl_client *client, struct wl_resource *resource,
uint32_t serial, const char *mime_type)
{
DataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
return;
wl_data_source_send_target (offer->source->resource,
mime_type);
if (mime_type)
{
offer->state |= IsMimeTypeAccepted;
offer->source->state |= TypeAccepted;
}
else
{
offer->state &= ~IsMimeTypeAccepted;
offer->source->state &= ~TypeAccepted;
}
}
static void
Receive (struct wl_client *client, struct wl_resource *resource,
const char *mime_type, int fd)
{
DataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
{
#ifdef DEBUG
fprintf (stderr, "wl_client@%p is trying to receive from an outdated"
" wl_data_offer@%u\n", client, wl_resource_get_id (resource));
#endif
close (fd);
return;
}
if (offer->state & IsFinished)
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
"trying to receive from finished offer");
return;
}
#ifdef DEBUG
fprintf (stderr, "wl_client@%p is now receiving from wl_data_offer@%u\n",
client, wl_resource_get_id (resource));
#endif
wl_data_source_send_send (offer->source->resource, mime_type, fd);
close (fd);
}
static void
DestroyDataOffer (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
Finish (struct wl_client *client, struct wl_resource *resource)
{
DataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
/* The data source was destroyed... */
return;
if (!(offer->state & IsDragAndDrop))
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
"trying to finish non-drag-and-drop data offer");
return;
}
/* The serial or drag device is not tested here, since CancelDrag
detaches the attached drag-and-drop data device before finish is
called in response to a drop. */
if (!(offer->state & IsMimeTypeAccepted))
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
"trying to finish drag and drop offer with nothing"
" accepted");
return;
}
if (!(offer->state & IsActionSent))
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
"trying to finish drag and drop offer with no action"
" sent");
return;
}
if (offer->state & IsFinished)
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
"trying to finish drag and drop offer which was"
" already finished");
return;
}
offer->state |= IsFinished;
if (wl_resource_get_version (offer->source->resource) < 3)
return;
if (offer->source->state & Version3Supported
&& (!(offer->source->state & ActionsSent)
|| !(offer->source->state & TypeAccepted)))
{
/* The drag and drop operation is no longer eligible for
successful completion. Cancel it and return. */
wl_data_source_send_cancelled (offer->source->resource);
return;
}
wl_data_source_send_dnd_finished (offer->source->resource);
}
static void
UpdateDeviceActions (DataDevice *device, DataSource *source)
{
uint32_t action, intersection;
unsigned int modifiers;
DataOffer *offer;
modifiers = XLSeatGetEffectiveModifiers (device->seat);
/* How actions are resolved. First, the preferred action is matched
against the actions supported by the source, and used if it is
supported. If it is not supported by the data source, then the
following actions are tried in order: copy, move, ask, none. If
none of those are supported, then no action is used. The
preferred action is ignored if the Shift key is held down, in
which case Move is also preferred to copy. */
if (!(modifiers & ShiftMask)
&& device->preferred_action & source->actions)
action = device->preferred_action;
else
{
intersection = (device->supported_actions
& source->actions);
/* Now, try the following actions in order, preferring move to
copy if Shift is held down. */
if (modifiers & ShiftMask
&& intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
else
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
}
/* Send the action to all attached data offers. */
offer = source->offers.next;
while (offer != &source->offers)
{
if (!(offer->state & IsDragAndDrop))
goto next;
if (offer->dnd_serial < device->dnd_serial)
goto next;
if (offer->last_action != action)
wl_data_offer_send_action (offer->resource, action);
offer->last_action = action;
offer->state |= IsActionSent;
next:
offer = offer->next;
}
/* Set flags on the source indicating that an action has been set,
unless action is 0, in which case clear it. */
if (action)
source->state |= ActionsSent;
else
source->state &= ~ActionsSent;
/* Send the new action to the data source. */
if (wl_resource_get_version (source->resource) >= 3)
wl_data_source_send_action (source->resource, action);
}
static void
DataOfferSetActions (struct wl_client *client, struct wl_resource *resource,
uint32_t dnd_actions, uint32_t preferred_action)
{
DataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
/* The data source was destroyed... */
return;
if (!(offer->state & IsDragAndDrop))
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_OFFER,
"trying to finish non-drag-and-drop data offer");
return;
}
if (!offer->source->drag_device)
/* The data device has been destroyed. */
return;
if (offer->dnd_serial < offer->source->drag_device->dnd_serial)
/* The data offer is out of data and effectively inert. */
return;
if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
| WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
| WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK))
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_ACTION,
"invalid action or action mask among: %u",
dnd_actions);
return;
}
if (preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK
&& preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
&& preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
&& preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE)
{
wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_ACTION,
"invalid action not in enum: %u",
preferred_action);
return;
}
offer->source->drag_device->supported_actions = dnd_actions;
offer->source->drag_device->preferred_action = preferred_action;
UpdateDeviceActions (offer->source->drag_device, offer->source);
}
static const struct wl_data_offer_interface wl_data_offer_impl =
{
.accept = Accept,
.receive = Receive,
.destroy = DestroyDataOffer,
.finish = Finish,
.set_actions = DataOfferSetActions,
};
static void
HandleOfferResourceDestroy (struct wl_resource *resource)
{
DataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
/* The offer was made inert. */
return;
offer->resource = NULL;
FreeDataOffer (offer);
}
static struct wl_resource *
AddDataOffer (struct wl_client *client, DataSource *source)
{
DataOffer *offer;
struct wl_resource *resource;
resource = wl_resource_create (client, &wl_data_offer_interface,
/* 0 means to allocate a new resource
ID server-side. */
3, 0);
if (!resource)
return NULL;
offer = XLCalloc (1, sizeof *offer);
offer->next = source->offers.next;
offer->last = &source->offers;
source->offers.next->last = offer;
source->offers.next = offer;
offer->resource = resource;
offer->source = source;
wl_resource_set_implementation (resource, &wl_data_offer_impl,
offer, HandleOfferResourceDestroy);
return resource;
}
/* Data device and source implementations. */
/* Forward declaration. */
static void SendDataOffers (void);
static void
HandleDragDeviceDestroyed (void *data)
{
DataSource *data_source;
data_source = data;
data_source->drag_device = NULL;
data_source->drag_device_callback = NULL;
if (wl_resource_get_version (data_source->resource) >= 3)
wl_data_source_send_cancelled (data_source->resource);
}
static void
HandleSourceResourceDestroy (struct wl_resource *resource)
{
DataSource *data_source;
DataOffer *offer, *last;
data_source = wl_resource_get_user_data (resource);
/* If data_source is currently the selection, remove it. */
if (data_source == current_selection_data)
{
current_selection_data = NULL;
/* Send the updated data to clients. */
SendDataOffers ();
}
/* Tell the X selection code that this data source has been
destroyed. */
XLNoteSourceDestroyed (data_source);
XLListFree (data_source->mime_types, XLFree);
XIDListFree (data_source->atom_types, NULL);
/* Make inert and release all data offers on this data source. */
offer = data_source->offers.next;
while (offer != &data_source->offers)
{
last = offer;
offer = offer->next;
FreeDataOffer (last);
}
/* Free the destroy callback for the data device. */
if (data_source->drag_device_callback)
CancelDestroyCallback (data_source->drag_device_callback);
/* Run all destroy callbacks for this data source. */
FreeDestroyCallbacks (&data_source->destroy_callbacks);
XLFree (data_source);
}
static const char *
FindAtom (DataSource *source, Atom atom)
{
XIDList *tem;
XLList *tem1;
/* Find the MIME type corresponding to an atom in source.
Return NULL if the atom is not there.
source->mime_types must be the same length as
source->atom_types. */
tem = source->atom_types;
tem1 = source->mime_types;
for (; tem; tem = tem->next, tem1 = tem1->next)
{
if (tem->data == atom)
return tem1->data;
}
return NULL;
}
static void
Offer (struct wl_client *client, struct wl_resource *resource,
const char *mime_type)
{
Atom atom;
DataSource *data_source;
DataOffer *offer;
data_source = wl_resource_get_user_data (resource);
/* It is more efficient to record both atoms and strings in the data
source, since its contents will be offered to X and Wayland
clients. */
atom = InternAtom (mime_type);
/* If the type was already offered, simply return. */
if (FindAtom (data_source, atom))
return;
/* Otherwise, link the atom and the mime type onto the list
simultaneously. */
#ifdef DEBUG
fprintf (stderr, "Offering: %s (X atom: %lu) from wl_data_source@%u\n",
mime_type, atom, wl_resource_get_id (resource));
#endif
data_source->atom_types = XIDListPrepend (data_source->atom_types, atom);
data_source->mime_types = XLListPrepend (data_source->mime_types,
XLStrdup (mime_type));
data_source->n_mime_types++;
/* Send the new MIME type to any attached offers. */
offer = data_source->offers.next;
while (offer != &data_source->offers)
{
wl_data_offer_send_offer (offer->resource, mime_type);
offer = offer->next;
}
}
static void
SetActions (struct wl_client *client, struct wl_resource *resource,
uint32_t actions)
{
DataSource *source;
source = wl_resource_get_user_data (resource);
if (source->state & ActionsSet)
{
wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
"actions already set on this offer or it has"
" already been used.");
return;
}
if (actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
| WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
| WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK))
{
wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
"unknown actions specified (mask value %u)",
actions);
return;
}
source->state |= ActionsSet;
source->actions = actions;
}
static void
DestroySource (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static const struct wl_data_source_interface wl_data_source_impl =
{
.offer = Offer,
.set_actions = SetActions,
.destroy = DestroySource,
};
static void
CreateDataSource (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
DataSource *source;
source = XLSafeMalloc (sizeof *source);
if (!source)
{
wl_resource_post_no_memory (resource);
return;
}
memset (source, 0, sizeof *source);
source->resource = wl_resource_create (client, &wl_data_source_interface,
wl_resource_get_version (resource),
id);
if (!source->resource)
{
wl_resource_post_no_memory (resource);
XLFree (source);
return;
}
source->offers.next = &source->offers;
source->offers.last = &source->offers;
source->destroy_callbacks.next = &source->destroy_callbacks;
source->destroy_callbacks.last = &source->destroy_callbacks;
wl_resource_set_implementation (source->resource, &wl_data_source_impl,
source, HandleSourceResourceDestroy);
}
static void
UpdateSingleReferenceWithForeignOffer (struct wl_client *client,
DataDeviceReference *reference)
{
struct wl_resource *resource;
Timestamp time;
time = foreign_selection_time;
resource = foreign_selection_functions.create_offer (client, time);
if (!resource)
return;
/* Make the data offer known to the client. */
wl_data_device_send_data_offer (reference->resource, resource);
/* Tell the foreign selection provider to send supported resources
to the client. */
foreign_selection_functions.send_offers (resource, time);
/* Finally, tell the client that the offer is a selection. */
wl_data_device_send_selection (reference->resource, resource);
}
static void
UpdateForSingleReference (DataDeviceReference *device)
{
struct wl_resource *resource;
struct wl_client *client;
XLList *type;
if (!current_selection_data)
{
wl_data_device_send_selection (device->resource,
NULL);
return;
}
client = wl_resource_get_client (device->resource);
if (current_selection_data == &foreign_selection_key)
{
/* This means a foreign selection is in use. */
UpdateSingleReferenceWithForeignOffer (client, device);
return;
}
resource = AddDataOffer (client, current_selection_data);
if (!resource)
return;
/* First, introduce the data offer to the client. */
wl_data_device_send_data_offer (device->resource, resource);
/* Send all the offered MIME types. */
type = current_selection_data->mime_types;
for (; type; type = type->next)
wl_data_offer_send_offer (resource, type->data);
/* Finally, tell the client it is a selection. */
wl_data_device_send_selection (device->resource, resource);
}
static void
SendDataOffersForDevice (DataDevice *device)
{
DataDeviceReference *reference;
struct wl_client *client;
reference = device->references.next;
while (reference != &device->references)
{
client = wl_resource_get_client (reference->resource);
if (XLSeatIsClientFocused (device->seat, client))
UpdateForSingleReference (reference);
reference = reference->next;
}
}
static void
SendDataOffers (void)
{
XLList *seat;
DataDevice *device;
for (seat = live_seats; seat; seat = seat->next)
{
device = XLSeatGetDataDevice (seat->data);
if (device)
SendDataOffersForDevice (device);
}
}
static void
DestroyReference (DataDeviceReference *reference)
{
/* If reference->device is NULL, then the data device itself has
been destroyed. */
if (reference->device)
{
reference->next->last = reference->last;
reference->last->next = reference->next;
}
XLFree (reference);
}
static void
ReleaseReferences (DataDevice *device)
{
DataDeviceReference *reference;
reference = device->references.next;
while (reference != &device->references)
{
reference->device = NULL;
reference = reference->next;
}
}
static void
DestroyBacking (DataDevice *device)
{
if (--device->refcount)
return;
ReleaseReferences (device);
FreeDestroyCallbacks (&device->destroy_callbacks);
XLFree (device);
}
static DataDevice *
GetDataDeviceInternal (Seat *seat)
{
DataDevice *device;
device = XLSeatGetDataDevice (seat);
if (!device)
{
device = XLCalloc (1, sizeof *device);
device->seat = seat;
device->references.next = &device->references;
device->references.last = &device->references;
device->destroy_callbacks.next = &device->destroy_callbacks;
device->destroy_callbacks.last = &device->destroy_callbacks;
XLSeatSetDataDevice (seat, device);
}
return device;
}
static DataDeviceReference *
AddReferenceTo (DataDevice *device, struct wl_resource *resource)
{
DataDeviceReference *reference;
reference = XLCalloc (1, sizeof *reference);
reference->next = device->references.next;
reference->last = &device->references;
reference->resource = resource;
device->references.next->last = reference;
device->references.next = reference;
reference->device = device;
return reference;
}
static void
StartDrag (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *source_resource,
struct wl_resource *origin_resource,
struct wl_resource *icon_resource, uint32_t serial)
{
DataDeviceReference *device;
Surface *icon, *origin;
DataSource *source;
device = wl_resource_get_user_data (resource);
if (!device->device || !device->device->seat)
/* This device is inert, since the seat has been deleted. */
return;
if (icon_resource)
icon = wl_resource_get_user_data (icon_resource);
else
icon = NULL;
origin = wl_resource_get_user_data (origin_resource);
source = wl_resource_get_user_data (source_resource);
if (source == current_selection_data)
{
/* The specification doesn't say anything about this! */
wl_resource_post_error (source_resource,
WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
"trying to drag the selection");
return;
}
if (source->drag_device)
{
/* The specification doesn't say anything about this! */
wl_resource_post_error (source_resource,
WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
"trying to drag a data source that is"
" already being dragged");
return;
}
/* If the icon surface isn't the right type, throw an error. */
if (icon && icon->role_type != AnythingType
&& icon->role_type != DndIconType)
{
wl_resource_post_error (resource, WL_DATA_DEVICE_ERROR_ROLE,
"the given surface already has/had"
" another role");
return;
}
/* Now make it impossible to set this source as the selection. */
source->state |= ActionsSet;
XLSeatBeginDrag (device->device->seat, source, origin,
icon, serial);
}
static void
SetSelection (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *source_resource, uint32_t serial)
{
DataSource *source;
DataDeviceReference *device;
#ifdef DEBUG
if (source_resource)
fprintf (stderr, "wl_client@%p is setting the selection to "
"wl_data_source@%u, at %u\n",
client, wl_resource_get_id (source_resource), serial);
#endif
/* Note that both serial and last_selection_change_serial are
unsigned. Remember two's complement arithmetic. -1 is
0xffffffff, while -4294967295 overflows twice and is
0x00000001. */
if (serial - last_selection_change_serial > UINT32_MAX / 2)
{
#ifdef DEBUG
fprintf (stderr, "wl_client@%p could not set the selection, "
"because the selection changed. (%u < %u)\n",
client, serial, last_selection_change_serial);
#endif
return;
}
device = wl_resource_get_user_data (resource);
if (!device->device || !device->device->seat)
/* This device is inert, since the seat has been deleted. */
return;
/* Set the last selection change serial. This enables us to avoid
race conditions caused by multiple clients simultaneously setting
the clipboard according to different events, by keeping track of
which event is newer. */
last_selection_change_serial = serial;
if (source_resource)
source = wl_resource_get_user_data (source_resource);
else
source = NULL;
/* If the data source is destined for drag and drop, report an
error. */
if (source && source->state & ActionsSet)
{
wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
"trying to set dnd source as the selection");
return;
}
/* Try to own the X selection. If it fails, refrain from changing
the current selection data. */
if (!XLNoteLocalSelection (device->device->seat, source))
return;
if (current_selection_data && current_selection_data != source)
{
/* If the current selection data is already set, cancel it. */
if (current_selection_data != &foreign_selection_key)
wl_data_source_send_cancelled (current_selection_data->resource);
}
if (current_selection_data != source)
{
current_selection_data = source;
/* Create data offer objects for the new selection data and send
it to clients. */
SendDataOffers ();
}
}
static void
Release (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static const struct wl_data_device_interface wl_data_device_impl =
{
.start_drag = StartDrag,
.set_selection = SetSelection,
.release = Release,
};
static void
HandleDeviceResourceDestroy (struct wl_resource *resource)
{
DataDeviceReference *reference;
reference = wl_resource_get_user_data (resource);
DestroyReference (reference);
}
static void
GetDataDevice (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *seat_resource)
{
struct wl_resource *device_resource;
Seat *seat;
DataDevice *device;
DataDeviceReference *reference;
device_resource = wl_resource_create (client, &wl_data_device_interface,
wl_resource_get_version (resource),
id);
if (!device_resource)
{
wl_resource_post_no_memory (resource);
return;
}
seat = wl_resource_get_user_data (seat_resource);
device = GetDataDeviceInternal (seat);
reference = AddReferenceTo (device, device_resource);
wl_resource_set_implementation (device_resource, &wl_data_device_impl,
reference, HandleDeviceResourceDestroy);
}
static struct wl_data_device_manager_interface wl_data_device_manager_impl =
{
.create_data_source = CreateDataSource,
.get_data_device = GetDataDevice,
};
static void
HandleBind (struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct wl_resource *resource;
resource
= wl_resource_create (client, &wl_data_device_manager_interface,
version, id);
if (!resource)
{
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (resource,
&wl_data_device_manager_impl,
NULL, NULL);
}
void
XLInitDataDevice (void)
{
global_data_device_manager
= wl_global_create (compositor.wl_display,
&wl_data_device_manager_interface,
3, NULL, HandleBind);
}
void
XLRetainDataDevice (DataDevice *device)
{
device->refcount++;
}
void
XLReleaseDataDevice (DataDevice *device)
{
DestroyBacking (device);
}
void
XLDataDeviceClearSeat (DataDevice *device)
{
device->seat = NULL;
}
void
XLDataDeviceHandleFocusChange (DataDevice *device)
{
SendDataOffersForDevice (device);
}
void
XLSetForeignSelection (Timestamp time, CreateOfferFuncs functions)
{
uint32_t serial;
if (TimestampIs (time, Earlier, foreign_selection_time))
return;
serial = wl_display_next_serial (compositor.wl_display);
/* Use this serial to prevent clients from changing the selection
again until the next event is sent. */
last_selection_change_serial = serial;
foreign_selection_time = time;
foreign_selection_functions = functions;
if (current_selection_data
&& current_selection_data != &foreign_selection_key)
wl_data_source_send_cancelled (current_selection_data->resource);
/* Use a special value of current_selection_data to mean that
foreign selections are in use. */
current_selection_data = &foreign_selection_key;
/* Send new data offers to current clients. */
SendDataOffers ();
}
void
XLClearForeignSelection (Timestamp time)
{
if (TimestampIs (time, Earlier, foreign_selection_time))
return;
if (current_selection_data == &foreign_selection_key)
{
current_selection_data = NULL;
SendDataOffers ();
}
else if (current_selection_data)
{
/* The foreign selection was cleared, meaning CLIPBOARD has been
disowned after the last foreign or local selection was set,
and thus the local selection must be cleared as well. */
wl_data_source_send_cancelled (current_selection_data->resource);
current_selection_data = NULL;
SendDataOffers ();
}
foreign_selection_time = time;
}
int
XLDataSourceTargetCount (DataSource *source)
{
return source->n_mime_types;
}
void
XLDataSourceGetTargets (DataSource *source, Atom *targets)
{
int i;
XIDList *list;
list = source->atom_types;
for (i = 0; i < source->n_mime_types; ++i)
{
targets[i] = list->data;
list = list->next;
}
}
struct wl_resource *
XLResourceFromDataSource (DataSource *source)
{
return source->resource;
}
Bool
XLDataSourceHasAtomTarget (DataSource *source, Atom target)
{
XIDList *list;
for (list = source->atom_types; list; list = list->next)
{
if (list->data == target)
return True;
}
return False;
}
Bool
XLDataSourceHasTarget (DataSource *source, const char *mime_type)
{
XLList *list;
for (list = source->mime_types; list; list = list->next)
{
if (!strcmp (list->data, mime_type))
return True;
}
return False;
}
void
XLDataDeviceMakeOffers (Seat *seat, DndOfferFuncs funcs, Surface *surface,
int x, int y)
{
DataDevice *device;
DataDeviceReference *reference;
struct wl_resource *resource;
struct wl_client *client;
int version;
uint32_t serial;
device = XLSeatGetDataDevice (seat);
client = wl_resource_get_client (surface->resource);
reference = device->references.next;
serial = wl_display_next_serial (compositor.wl_display);
while (reference != &device->references)
{
/* If this reference to the data device belongs to the client,
continue. */
if (wl_resource_get_client (reference->resource)
== wl_resource_get_client (surface->resource))
{
version = wl_resource_get_version (reference->resource);
/* Create the offer. */
resource = funcs.create (client, version);
if (resource)
{
/* Actually send the data offer to the client. */
wl_data_device_send_data_offer (reference->resource,
resource);
/* And data offers. */
funcs.send_offers (resource);
/* And send the entry event. */
wl_data_device_send_enter (reference->resource,
serial, surface->resource,
wl_fixed_from_double (x),
wl_fixed_from_double (y),
resource);
}
}
reference = reference->next;
}
}
void
XLDataDeviceSendEnter (Seat *seat, Surface *surface, double x, double y,
DataSource *source)
{
DataDevice *device;
DataDeviceReference *reference;
uint32_t serial;
struct wl_resource *resource;
struct wl_client *client;
DataOffer *offer;
XLList *type;
device = XLSeatGetDataDevice (seat);
if (!device)
/* No data device has been created for this seat yet. */
return;
reference = device->references.next;
serial = wl_display_next_serial (compositor.wl_display);
client = wl_resource_get_client (surface->resource);
device->dnd_serial = serial;
/* Clear the selected actions. */
device->supported_actions = 0;
device->preferred_action = 0;
/* And some flags. */
if (source)
source->state = 0;
while (reference != &device->references)
{
/* If this reference to the data device belongs to the client,
continue. */
if (wl_resource_get_client (reference->resource) == client)
{
if (source)
{
/* First, create a data offer corresponding to the data
source if it exists. */
resource = AddDataOffer (client, source);
if (!resource)
/* Allocation of the resource failed. */
goto next;
offer = wl_resource_get_user_data (resource);
offer->dnd_serial = serial;
offer->last_action = -1;
/* Mark the offer as a drag-and-drop offer. */
offer->state |= IsDragAndDrop;
/* Introduce the data offer to the client. */
wl_data_device_send_data_offer (reference->resource, resource);
/* Send all the offered data types to the client. */
type = source->mime_types;
for (; type; type = type->next)
wl_data_offer_send_offer (resource, type->data);
/* Send the source actions. */
wl_data_offer_send_source_actions (resource, source->actions);
/* If the data device supports version 3 or later, set
the flag. */
if (wl_resource_get_version (resource) >= 3)
source->state |= Version3Supported;
}
wl_data_device_send_enter (reference->resource,
serial, surface->resource,
wl_fixed_from_double (x),
wl_fixed_from_double (y),
source ? resource : NULL);
}
next:
reference = reference->next;
}
}
void
XLDataDeviceSendMotion (Seat *seat, Surface *surface,
double x, double y, Time time)
{
DataDevice *device;
DataDeviceReference *reference;
device = XLSeatGetDataDevice (seat);
if (!device)
/* No data device has been created for this seat yet. */
return;
reference = device->references.next;
while (reference != &device->references)
{
/* If this reference to the data device belongs to the client,
continue. */
if (wl_resource_get_client (reference->resource)
== wl_resource_get_client (surface->resource))
wl_data_device_send_motion (reference->resource, time,
wl_fixed_from_double (x),
wl_fixed_from_double (y));
reference = reference->next;
}
}
void
XLDataDeviceSendLeave (Seat *seat, Surface *surface, DataSource *source)
{
DataDevice *device;
DataDeviceReference *reference;
device = XLSeatGetDataDevice (seat);
if (!device)
/* No data device has been created for this seat yet. */
return;
reference = device->references.next;
/* This serial doesn't actually mean anything. It's only used to
invalidate previous data offers. */
device->dnd_serial = wl_display_next_serial (compositor.wl_display);
while (reference != &device->references)
{
/* If this reference to the data device belongs to the client,
continue. */
if (wl_resource_get_client (reference->resource)
== wl_resource_get_client (surface->resource))
wl_data_device_send_leave (reference->resource);
reference = reference->next;
}
if (source)
{
/* Clear the selected actions. */
device->supported_actions = 0;
device->preferred_action = 0;
/* Since the pointer has left the source, clear several
flags. */
source->state = 0;
/* Send the new effective action to the source. */
if (wl_resource_get_version (source->resource) >= 3)
wl_data_source_send_action (source->resource,
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
/* Also tell the source that there is no longer any
target accepted. */
wl_data_source_send_target (source->resource, NULL);
}
}
void
XLDataDeviceSendDrop (Seat *seat, Surface *surface)
{
DataDevice *device;
DataDeviceReference *reference;
device = XLSeatGetDataDevice (seat);
if (!device)
/* No data device has been created for this seat yet. */
return;
reference = device->references.next;
while (reference != &device->references)
{
/* If this reference to the data device belongs to the client,
continue. */
if (wl_resource_get_client (reference->resource)
== wl_resource_get_client (surface->resource))
wl_data_device_send_drop (reference->resource);
reference = reference->next;
}
}
void
XLDataSourceAttachDragDevice (DataSource *source, DataDevice *device)
{
if (source->drag_device)
{
CancelDestroyCallback (source->drag_device_callback);
source->drag_device_callback = NULL;
}
source->drag_device = device;
if (device)
source->drag_device_callback
= AddDestroyCallbackAfter (&device->destroy_callbacks,
HandleDragDeviceDestroyed,
source);
}
void *
XLDataSourceAddDestroyCallback (DataSource *source,
void (*destroy_func) (void *),
void *data)
{
return AddDestroyCallbackAfter (&source->destroy_callbacks,
destroy_func, data);
}
void
XLDataSourceCancelDestroyCallback (void *key)
{
CancelDestroyCallback (key);
}
void
XLDataSourceSendDropPerformed (DataSource *source)
{
if (wl_resource_get_version (source->resource) >= 3)
wl_data_source_send_dnd_drop_performed (source->resource);
}
void
XLDataSourceSendDropCancelled (DataSource *source)
{
if (wl_resource_get_version (source->resource) >= 3)
wl_data_source_send_cancelled (source->resource);
}
Bool
XLDataSourceCanDrop (DataSource *source)
{
/* If version 3 is supported, require that an action has been sent
and a data type has been accepted. Otherwise, always do the
drop. */
if (source->state & Version3Supported)
return (source->state & ActionsSent
&& source->state & TypeAccepted);
return True;
}
uint32_t
XLDataSourceGetSupportedActions (DataSource *source)
{
return source->actions;
}
XLList *
XLDataSourceGetMimeTypeList (DataSource *source)
{
return source->mime_types;
}
void
XLDataSourceUpdateDeviceActions (DataSource *drag_source)
{
UpdateDeviceActions (drag_source->drag_device, drag_source);
}