12to11/primary_selection.c
hujianwei be117ac93d Improve serial wraparound checking
* data_device.c (SetSelection):
* primary_selection.c (SetSelection): Handle selection serial
wraparound.
2022-11-11 11:16:01 +00:00

797 lines
19 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 <string.h>
#include "compositor.h"
#include "primary-selection-unstable-v1.h"
typedef struct _PDataDevice PDataDevice;
typedef struct _PDataOffer PDataOffer;
enum
{
WasSentOffer = 1,
};
struct _PDataDevice
{
/* The seat associated with this data device. */
Seat *seat;
/* The destroy callback associated with that seat. */
void *seat_destroy_key;
/* Some flags. */
int flags;
/* The wl_resource associated with this data device. */
struct wl_resource *resource;
/* The next and last data devices. */
PDataDevice *next, *last;
};
struct _PDataOffer
{
/* The data source associated with this offer. */
PDataSource *source;
/* The resource associated with this offer. */
struct wl_resource *resource;
/* The next and last data offers associated with the data
source. */
PDataOffer *next, *last;
};
struct _PDataSource
{
/* The wl_resource associated with this data source. */
struct wl_resource *resource;
/* List of all data offers attached to this source. */
PDataOffer offers;
/* List of all MIME types provided by this source. */
XLList *mime_types;
/* List of atoms provided by this source. */
XIDList *atom_types;
/* Number of MIME types provided by this source. */
int n_mime_types;
};
/* The global primary selection manager. */
static struct wl_global *global_primary_selection_device_manager;
/* The data source currently providing the primary selection. */
static PDataSource *primary_selection;
/* The last time the primary selection changed. */
static uint32_t last_change_serial;
/* All data devices. */
static PDataDevice all_devices;
/* A special PDataSource meaning that the primary selection is a
foreign selection. */
static PDataSource foreign_selection_key;
/* Functions to call to obtain zwp_primary_selection_v1_offer
resources and send associated data for foreign selections. */
static CreateOfferFuncs foreign_selection_functions;
/* The time the foreign selection was set. */
static Timestamp foreign_selection_time;
/* Forward declaration. */
static void NoticeChanged (void);
/* Data offer implementation. */
static void
Receive (struct wl_client *client, struct wl_resource *resource,
const char *mime_type, int32_t fd)
{
PDataOffer *offer;
offer = wl_resource_get_user_data (resource);
if (!offer)
{
/* This data offer is inert. */
close (fd);
return;
}
zwp_primary_selection_source_v1_send_send (offer->source->resource,
mime_type, fd);
close (fd);
}
static void
DestroyOffer (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static struct zwp_primary_selection_offer_v1_interface offer_impl =
{
.receive = Receive,
.destroy = DestroyOffer,
};
static void
FreeDataOffer (PDataOffer *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
HandleOfferResourceDestroy (struct wl_resource *resource)
{
PDataOffer *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, PDataSource *source)
{
PDataOffer *offer;
struct wl_resource *resource;
resource = wl_resource_create (client,
&zwp_primary_selection_offer_v1_interface,
/* 0 means to allocate a new resource
ID server-side. */
1, 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, &offer_impl,
offer, HandleOfferResourceDestroy);
return resource;
}
/* Data source implementation. */
static Bool
FindType (PDataSource *source, const char *mime_type)
{
XLList *tem;
for (tem = source->mime_types; tem; tem = tem->next)
{
if (!strcmp (tem->data, mime_type))
return True;
}
return False;
}
static void
Offer (struct wl_client *client, struct wl_resource *resource,
const char *mime_type)
{
PDataSource *source;
source = wl_resource_get_user_data (resource);
/* If the type was already offered, simply return. */
if (FindType (source, mime_type))
return;
/* Otherwise, add the MIME type to the list of types provided by
this source. */
source->mime_types = XLListPrepend (source->mime_types,
XLStrdup (mime_type));
source->atom_types = XIDListPrepend (source->atom_types,
InternAtom (mime_type));
source->n_mime_types++;
}
static void
DestroySource (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static struct zwp_primary_selection_source_v1_interface source_impl =
{
.offer = Offer,
.destroy = DestroySource,
};
static void
HandleSourceResourceDestroy (struct wl_resource *resource)
{
PDataSource *source;
PDataOffer *offer, *last;
source = wl_resource_get_user_data (resource);
/* Now free every data offer. */
offer = source->offers.next;
while (offer != &source->offers)
{
last = offer;
offer = offer->next;
FreeDataOffer (last);
}
/* And free the MIME types offered by the source. */
XLListFree (source->mime_types, XLFree);
XIDListFree (source->atom_types, NULL);
/* Maybe disown the primary selection. */
XLNotePrimaryDestroyed (source);
/* If source is the primary selection, clear it and send the change
to all clients. */
if (source == primary_selection)
{
primary_selection = NULL;
NoticeChanged ();
}
/* And the source itself. */
XLFree (source);
}
struct wl_resource *
XLResourceFromPDataSource (PDataSource *source)
{
return source->resource;
}
Bool
XLPDataSourceHasAtomTarget (PDataSource *source, Atom target)
{
XIDList *list;
for (list = source->atom_types; list; list = list->next)
{
if (list->data == target)
return True;
}
return False;
}
Bool
XLPDataSourceHasTarget (PDataSource *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;
}
int
XLPDataSourceTargetCount (PDataSource *source)
{
return source->n_mime_types;
}
void
XLPDataSourceGetTargets (PDataSource *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;
}
}
/* Device implementation. */
static void
UpdateSingleReferenceWithForeignOffer (struct wl_client *client,
PDataDevice *reference)
{
struct wl_resource *scratch, *resource;
Timestamp time;
time = foreign_selection_time;
resource = foreign_selection_functions.create_offer (client, time);
scratch = reference->resource;
if (!resource)
return;
/* Make the data offer known to the client. */
zwp_primary_selection_device_v1_send_data_offer (scratch, 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. */
zwp_primary_selection_device_v1_send_selection (scratch, resource);
/* Set WasSentOffer since an offer was sent. */
reference->flags |= WasSentOffer;
}
static void
UpdateForSingleReference (PDataDevice *reference)
{
struct wl_resource *scratch, *offer;
struct wl_client *client;
XLList *type;
if (!reference->seat)
/* This data device is inert... */
return;
client = wl_resource_get_client (reference->resource);
if (!XLSeatIsClientFocused (reference->seat, client))
/* The client is not focused. */
return;
scratch = reference->resource;
if (!primary_selection)
{
/* There is no primary selection. Send a NULL selection. */
zwp_primary_selection_device_v1_send_selection (scratch, NULL);
return;
}
/* If the primary selection is foreign, call the foreign selection
functions. */
if (primary_selection == &foreign_selection_key)
{
UpdateSingleReferenceWithForeignOffer (client, reference);
return;
}
/* Otherwise, create a data offer for that client. */
offer = AddDataOffer (client, primary_selection);
if (!offer)
/* Allocation of the offer failed. */
return;
/* Introduce the offer to the client. */
zwp_primary_selection_device_v1_send_data_offer (scratch, offer);
/* Send all the offered MIME types. */
type = primary_selection->mime_types;
for (; type; type = type->next)
zwp_primary_selection_offer_v1_send_offer (offer, type->data);
/* And tell the client it is the primary selection. */
zwp_primary_selection_device_v1_send_selection (scratch, offer);
/* Set or clear WasSentOffer based on whether or not an offer was
sent. */
if (offer)
reference->flags |= WasSentOffer;
else
reference->flags &= ~WasSentOffer;
}
void
XLPrimarySelectionHandleFocusChange (Seat *seat)
{
PDataDevice *device;
struct wl_client *client;
struct wl_resource *scratch;
/* The focus changed. Send NULL data offers to any non-focused data
device that was previously sent a valid data offer, and send data
offers to any focused data device that was not. */
device = all_devices.next;
while (device != &all_devices)
{
if (!device->seat)
/* Inert device. */
goto next;
scratch = device->resource;
client = wl_resource_get_client (scratch);
if (device->flags & WasSentOffer
&& !XLSeatIsClientFocused (device->seat, client))
{
/* The device was previously sent a data offer, but is no
longer focused. Send NULL and clear WasSentOffer. */
zwp_primary_selection_device_v1_send_selection (scratch,
NULL);
device->flags &= ~WasSentOffer;
}
else if (!(device->flags & WasSentOffer)
&& primary_selection
&& XLSeatIsClientFocused (device->seat, client))
/* The device is now focused, there is a primary selection,
and the device was not sent a data offer. Send the missing
data offer now. */
UpdateForSingleReference (device);
next:
device = device->next;
}
}
static void
NoticeChanged (void)
{
PDataDevice *device;
/* Send data offers to each data device. */
device = all_devices.next;
while (device != &all_devices)
{
UpdateForSingleReference (device);
device = device->next;
}
}
static void
SetSelection (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *source_resource, uint32_t serial)
{
PDataDevice *device;
PDataSource *source;
struct wl_resource *scratch;
device = wl_resource_get_user_data (resource);
if (!device->seat)
/* This device is inert, since the seat has been deleted. */
return;
/* Note that both serial and last_change_serial are unsigned.
Remember two's complement arithmetic. */
if (serial - last_change_serial > UINT32_MAX / 2)
/* This change is out of date. Do nothing. */
return;
/* Otherwise, set the primary selection. */
last_change_serial = serial;
if (source_resource)
source = wl_resource_get_user_data (source_resource);
else
source = NULL;
/* Try to own the X primary selection. If it fails, refrain from
changing the current selection data. */
if (!XLNoteLocalPrimary (device->seat, source))
return;
if (primary_selection)
{
/* The primary selection already exists. Send the cancelled
event and clear the primary selection variable. */
scratch = primary_selection->resource;
if (primary_selection != &foreign_selection_key)
/* The special foreign selection has no associated
wl_resource. */
zwp_primary_selection_source_v1_send_cancelled (scratch);
/* Clear primary_selection. */
primary_selection = NULL;
}
if (source_resource)
/* Make source_resource the new primary selection. */
primary_selection = source;
/* Now, send the updated primary selection to clients. */
NoticeChanged ();
}
static void
DestroyDevice (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static struct zwp_primary_selection_device_v1_interface device_impl =
{
.set_selection = SetSelection,
.destroy = DestroyDevice,
};
static void
HandleSeatDestroy (void *data)
{
PDataDevice *device;
device = data;
/* The seat destroy listener must be cancelled manually. */
XLSeatCancelDestroyListener (device->seat_destroy_key);
device->seat_destroy_key = NULL;
device->seat = NULL;
}
static void
HandleDeviceResourceDestroy (struct wl_resource *resource)
{
PDataDevice *device;
device = wl_resource_get_user_data (resource);
/* Cancel the seat destroy listener should it exist. */
if (device->seat)
XLSeatCancelDestroyListener (device->seat_destroy_key);
/* Unlink the device. */
device->last->next = device->next;
device->next->last = device->last;
XLFree (device);
}
/* Device manager implementation. */
static void
CreateSource (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
PDataSource *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, &zwp_primary_selection_source_v1_interface,
wl_resource_get_version (resource), id);
if (!source->resource)
{
XLFree (source);
wl_resource_post_no_memory (resource);
return;
}
source->offers.next = &source->offers;
source->offers.last = &source->offers;
wl_resource_set_implementation (source->resource, &source_impl,
source, HandleSourceResourceDestroy);
}
static void
GetDevice (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *seat)
{
PDataDevice *device;
device = XLSafeMalloc (sizeof *device);
if (!device)
{
wl_resource_post_no_memory (resource);
return;
}
memset (device, 0, sizeof *device);
device->resource
= wl_resource_create (client,
&zwp_primary_selection_device_v1_interface,
wl_resource_get_version (resource), id);
if (!device->resource)
{
XLFree (device);
wl_resource_post_no_memory (resource);
return;
}
device->seat = wl_resource_get_user_data (seat);
if (XLSeatIsInert (device->seat))
device->seat = NULL;
else
device->seat_destroy_key
= XLSeatRunOnDestroy (device->seat, HandleSeatDestroy,
device);
/* Link the device into the chain of all devices. */
device->next = all_devices.next;
device->last = &all_devices;
all_devices.next->last = device;
all_devices.next = device;
wl_resource_set_implementation (device->resource, &device_impl,
device, HandleDeviceResourceDestroy);
/* If the primary selection is set and the client is focused, send
the data offer now. */
if (primary_selection && device->seat
&& XLSeatIsClientFocused (device->seat, client))
UpdateForSingleReference (device);
}
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static struct zwp_primary_selection_device_manager_v1_interface manager_impl =
{
.create_source = CreateSource,
.get_device = GetDevice,
.destroy = Destroy,
};
static void
HandleBind (struct wl_client *client, void *data, uint32_t version,
uint32_t id)
{
struct wl_resource *resource;
resource
= wl_resource_create (client,
&zwp_primary_selection_device_manager_v1_interface,
version, id);
if (!resource)
{
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (resource, &manager_impl, NULL, NULL);
}
void
XLSetForeignPrimary (Timestamp time, CreateOfferFuncs functions)
{
uint32_t serial;
struct wl_resource *scratch;
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_change_serial = serial;
foreign_selection_time = time;
foreign_selection_functions = functions;
if (primary_selection
&& primary_selection != &foreign_selection_key)
{
scratch = primary_selection->resource;
zwp_primary_selection_source_v1_send_cancelled (scratch);
}
/* Use a special value of primary_selection to mean that foreign
selections are in use. */
primary_selection = &foreign_selection_key;
/* Send new data offers to current clients. */
NoticeChanged ();
}
void
XLClearForeignPrimary (Timestamp time)
{
struct wl_resource *scratch;
if (TimestampIs (time, Earlier, foreign_selection_time))
return;
if (primary_selection == &foreign_selection_key)
{
primary_selection = NULL;
NoticeChanged ();
}
else if (primary_selection)
{
/* The foreign selection was cleared; that means PRIMARY has
been disowned after the last foreign or local selection was
set, which means the local selection must be cleared as
well. */
scratch = primary_selection->resource;
zwp_primary_selection_source_v1_send_cancelled (scratch);
/* Clear the primary selection and notice a change. */
primary_selection = NULL;
NoticeChanged ();
}
foreign_selection_time = time;
}
void
XLInitPrimarySelection (void)
{
global_primary_selection_device_manager
= wl_global_create (compositor.wl_display,
&zwp_primary_selection_device_manager_v1_interface,
1, NULL, HandleBind);
all_devices.next = &all_devices;
all_devices.last = &all_devices;
}