From 0965f5b3eb6d1595142cf42ad790c1f0720b2299 Mon Sep 17 00:00:00 2001 From: oldosfan Date: Tue, 13 Sep 2022 11:33:57 +0000 Subject: [PATCH] Check in new files for primary selection support --- primary-selection-unstable-v1.xml | 225 +++++++++++ primary_selection.c | 597 ++++++++++++++++++++++++++++++ 2 files changed, 822 insertions(+) create mode 100644 primary-selection-unstable-v1.xml create mode 100644 primary_selection.c diff --git a/primary-selection-unstable-v1.xml b/primary-selection-unstable-v1.xml new file mode 100644 index 0000000..e5a39e3 --- /dev/null +++ b/primary-selection-unstable-v1.xml @@ -0,0 +1,225 @@ + + + + Copyright © 2015, 2016 Red Hat + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol provides the ability to have a primary selection device to + match that of the X server. This primary selection is a shortcut to the + common clipboard selection, where text just needs to be selected in order + to allow copying it elsewhere. The de facto way to perform this action + is the middle mouse button, although it is not limited to this one. + + Clients wishing to honor primary selection should create a primary + selection source and set it as the selection through + wp_primary_selection_device.set_selection whenever the text selection + changes. In order to minimize calls in pointer-driven text selection, + it should happen only once after the operation finished. Similarly, + a NULL source should be set when text is unselected. + + wp_primary_selection_offer objects are first announced through the + wp_primary_selection_device.data_offer event. Immediately after this event, + the primary data offer will emit wp_primary_selection_offer.offer events + to let know of the mime types being offered. + + When the primary selection changes, the client with the keyboard focus + will receive wp_primary_selection_device.selection events. Only the client + with the keyboard focus will receive such events with a non-NULL + wp_primary_selection_offer. Across keyboard focus changes, previously + focused clients will receive wp_primary_selection_device.events with a + NULL wp_primary_selection_offer. + + In order to request the primary selection data, the client must pass + a recent serial pertaining to the press event that is triggering the + operation, if the compositor deems the serial valid and recent, the + wp_primary_selection_source.send event will happen in the other end + to let the transfer begin. The client owning the primary selection + should write the requested data, and close the file descriptor + immediately. + + If the primary selection owner client disappeared during the transfer, + the client reading the data will receive a + wp_primary_selection_device.selection event with a NULL + wp_primary_selection_offer, the client should take this as a hint + to finish the reads related to the no longer existing offer. + + The primary selection owner should be checking for errors during + writes, merely cancelling the ongoing transfer if any happened. + + + + + The primary selection device manager is a singleton global object that + provides access to the primary selection. It allows to create + wp_primary_selection_source objects, as well as retrieving the per-seat + wp_primary_selection_device objects. + + + + + Create a new primary selection source. + + + + + + + Create a new data device for a given seat. + + + + + + + + Destroy the primary selection device manager. + + + + + + + + Replaces the current selection. The previous owner of the primary + selection will receive a wp_primary_selection_source.cancelled event. + + To unset the selection, set the source to NULL. + + + + + + + + Introduces a new wp_primary_selection_offer object that may be used + to receive the current primary selection. Immediately following this + event, the new wp_primary_selection_offer object will send + wp_primary_selection_offer.offer events to describe the offered mime + types. + + + + + + + The wp_primary_selection_device.selection event is sent to notify the + client of a new primary selection. This event is sent after the + wp_primary_selection.data_offer event introducing this object, and after + the offer has announced its mimetypes through + wp_primary_selection_offer.offer. + + The data_offer is valid until a new offer or NULL is received + or until the client loses keyboard focus. The client must destroy the + previous selection data_offer, if any, upon receiving this event. + + + + + + + Destroy the primary selection device. + + + + + + + A wp_primary_selection_offer represents an offer to transfer the contents + of the primary selection clipboard to the client. Similar to + wl_data_offer, the offer also describes the mime types that the data can + be converted to and provides the mechanisms for transferring the data + directly to the client. + + + + + To transfer the contents of the primary selection clipboard, the client + issues this request and indicates the mime type that it wants to + receive. The transfer happens through the passed file descriptor + (typically created with the pipe system call). The source client writes + the data in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + closes its end, at which point the transfer is complete. + + + + + + + + Destroy the primary selection offer. + + + + + + Sent immediately after creating announcing the + wp_primary_selection_offer through + wp_primary_selection_device.data_offer. One event is sent per offered + mime type. + + + + + + + + The source side of a wp_primary_selection_offer, it provides a way to + describe the offered data and respond to requests to transfer the + requested contents of the primary selection clipboard. + + + + + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + + + + + + + Destroy the primary selection source. + + + + + + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + + + + + + + + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + + + + diff --git a/primary_selection.c b/primary_selection.c new file mode 100644 index 0000000..291c52f --- /dev/null +++ b/primary_selection.c @@ -0,0 +1,597 @@ +/* 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 . */ + +#include + +#include "compositor.h" + +#include "primary-selection-unstable-v1.h" + +typedef struct _PDataDevice PDataDevice; +typedef struct _PDataSource PDataSource; +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; + + /* 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; + +/* 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->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); + + /* 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); +} + + +/* Device implementation. */ + + +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; + } + + /* 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) +{ + struct wl_resource *scratch; + + if (serial < last_change_serial) + /* This change is out of date. Do nothing. */ + return; + + /* Otherwise, set the primary selection. */ + last_change_serial = serial; + + if (primary_selection) + { + /* The primary selection already exists. Send the cancelled + event and clear the primary selection variable. */ + scratch = primary_selection->resource; + primary_selection = NULL; + + zwp_primary_selection_source_v1_send_cancelled (scratch); + } + + if (source_resource) + /* Make source_resource the new primary selection. */ + primary_selection = wl_resource_get_user_data (source_resource); + + /* 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 +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; +}