From 9e49ceb12985697fbfc0b2fa8f86143b29cc837b Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 1 Feb 2019 12:49:46 +0300 Subject: [PATCH] data-control: add primary selection support data-control: separate out a data_offer struct This is a prerequisite to adding primary selection support. data-control: separate out data_control_source This is a prerequisite to adding primary selection support, since that doesn't use wlr_data_source, but rather wlr_primary_selection_source. Update the data-control protocol data-control: add primary selection support Merge create_offer and create_primary_offer Extract code into data_control_source_destroy() Fix pointer style Move resource neutralization to destructor Store wl_resource in the data_offer Extract data_offer destruction into a function --- include/wlr/types/wlr_data_control_v1.h | 2 + protocol/wlr-data-control-unstable-v1.xml | 89 ++++- types/wlr_data_control_v1.c | 416 ++++++++++++++++++---- 3 files changed, 434 insertions(+), 73 deletions(-) diff --git a/include/wlr/types/wlr_data_control_v1.h b/include/wlr/types/wlr_data_control_v1.h index 7d336344..32d169b4 100644 --- a/include/wlr/types/wlr_data_control_v1.h +++ b/include/wlr/types/wlr_data_control_v1.h @@ -32,9 +32,11 @@ struct wlr_data_control_device_v1 { struct wlr_seat *seat; struct wl_resource *selection_offer_resource; // current selection offer + struct wl_resource *primary_selection_offer_resource; // current primary selection offer struct wl_listener seat_destroy; struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; }; struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( diff --git a/protocol/wlr-data-control-unstable-v1.xml b/protocol/wlr-data-control-unstable-v1.xml index a5887550..3e39f2ac 100644 --- a/protocol/wlr-data-control-unstable-v1.xml +++ b/protocol/wlr-data-control-unstable-v1.xml @@ -2,6 +2,7 @@ Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted @@ -40,7 +41,7 @@ interface version number is reset. - + This interface is a manager that allows creating per-seat data device controls. @@ -68,9 +69,18 @@ appropriate destroy request has been called. + + + + + + This event can be sent when binding to the wlr_data_control_manager + global to advertise that it supports the primary selection. + + - + This interface allows a client to manage a seat's selection. @@ -79,8 +89,14 @@ - All objects created by the device will still remain valid, until their - appropriate destroy request has been called. + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. @@ -95,20 +111,19 @@ The data_offer event introduces a new wlr_data_control_offer object, - which will subsequently be used in the wlr_data_control_device.selection - event. Immediately following the wlr_data_control_device.data_offer - event, the new data_offer object will send out - wlr_data_control_offer.offer events to describe the MIME types it - offers. - - This event replaces the previous data offer, which should be destroyed - by the client. + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. - + The selection event is sent out to notify the client of a new wlr_data_control_offer for the selection for this device. The wlr_data_control_device.data_offer and the wlr_data_control_offer.offer @@ -118,6 +133,9 @@ wlr_data_control_offer or NULL is received. The client must destroy the previous selection wlr_data_control_offer, if any, upon receiving this event. + + The first selection event is sent upon binding the + wlr_data_control_device object. @@ -129,6 +147,51 @@ the client. + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + diff --git a/types/wlr_data_control_v1.c b/types/wlr_data_control_v1.c index 2b1a33bf..99dd0dae 100644 --- a/types/wlr_data_control_v1.c +++ b/types/wlr_data_control_v1.c @@ -5,51 +5,26 @@ #include #include #include +#include #include #include "util/signal.h" #include "wlr-data-control-unstable-v1-protocol.h" -#define DATA_CONTROL_MANAGER_VERSION 1 +#define DATA_CONTROL_MANAGER_VERSION 2 -struct client_data_source { - struct wlr_data_source source; +struct data_control_source { struct wl_resource *resource; + struct wl_array mime_types; bool finalized; -}; - -static const struct wlr_data_source_impl client_source_impl; - -static struct client_data_source * - client_data_source_from_source(struct wlr_data_source *wlr_source) { - assert(wlr_source->impl == &client_source_impl); - return (struct client_data_source *)wlr_source; -} - -static void client_source_send(struct wlr_data_source *wlr_source, - const char *mime_type, int fd) { - struct client_data_source *source = - client_data_source_from_source(wlr_source); - zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); - close(fd); -} - -static void client_source_destroy(struct wlr_data_source *wlr_source) { - struct client_data_source *source = - client_data_source_from_source(wlr_source); - zwlr_data_control_source_v1_send_cancelled(source->resource); - // Make the resource inert - wl_resource_set_user_data(source->resource, NULL); - free(source); -} -static const struct wlr_data_source_impl client_source_impl = { - .send = client_source_send, - .destroy = client_source_destroy, + // Only one of these is non-NULL. + struct wlr_data_source *active_source; + struct wlr_primary_selection_source *active_primary_source; }; static const struct zwlr_data_control_source_v1_interface source_impl; -static struct client_data_source *source_from_resource( +static struct data_control_source *source_from_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwlr_data_control_source_v1_interface, &source_impl)); @@ -58,7 +33,7 @@ static struct client_data_source *source_from_resource( static void source_handle_offer(struct wl_client *client, struct wl_resource *resource, const char *mime_type) { - struct client_data_source *source = source_from_resource(resource); + struct data_control_source *source = source_from_resource(resource); if (source == NULL) { return; } @@ -66,12 +41,13 @@ static void source_handle_offer(struct wl_client *client, if (source->finalized) { wl_resource_post_error(resource, ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER, - "cannot mutate offer after set_selection"); + "cannot mutate offer after set_selection or " + "set_primary_selection"); return; } const char **mime_type_ptr; - wl_array_for_each(mime_type_ptr, &source->source.mime_types) { + wl_array_for_each(mime_type_ptr, &source->mime_types) { if (strcmp(*mime_type_ptr, mime_type) == 0) { wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", mime_type); @@ -85,7 +61,7 @@ static void source_handle_offer(struct wl_client *client, return; } - char **p = wl_array_add(&source->source.mime_types, sizeof(char *)); + char **p = wl_array_add(&source->mime_types, sizeof(char *)); if (p == NULL) { free(dup_mime_type); wl_resource_post_no_memory(resource); @@ -105,18 +81,156 @@ static const struct zwlr_data_control_source_v1_interface source_impl = { .destroy = source_handle_destroy, }; +static void data_control_source_destroy(struct data_control_source *source) { + if (source == NULL) { + return; + } + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + // Prevent destructors below from calling this recursively. + wl_resource_set_user_data(source->resource, NULL); + + if (source->active_source != NULL) { + wlr_data_source_destroy(source->active_source); + } else if (source->active_primary_source != NULL) { + wlr_primary_selection_source_destroy( + source->active_primary_source); + } + + free(source); +} + static void source_handle_resource_destroy(struct wl_resource *resource) { - struct client_data_source *source = source_from_resource(resource); + struct data_control_source *source = source_from_resource(resource); + data_control_source_destroy(source); +} + + +struct client_data_source { + struct wlr_data_source source; + struct wl_resource *resource; +}; + +static const struct wlr_data_source_impl client_source_impl; + +static struct client_data_source * + client_data_source_from_source(struct wlr_data_source *wlr_source) { + assert(wlr_source->impl == &client_source_impl); + return (struct client_data_source *)wlr_source; +} + +static void client_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = + client_data_source_from_source(wlr_source); + zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy(struct wlr_data_source *wlr_source) { + struct client_data_source *client_source = + client_data_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + if (source == NULL) { return; } - wlr_data_source_destroy(&source->source); + + source->active_source = NULL; + + zwlr_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); } +static const struct wlr_data_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + + +struct client_primary_selection_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; +}; + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl; + +static struct client_primary_selection_source * + client_primary_selection_source_from_source( + struct wlr_primary_selection_source *wlr_source) { + assert(wlr_source->impl == &client_primary_selection_source_impl); + return (struct client_primary_selection_source *)wlr_source; +} + +static void client_primary_selection_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_primary_selection_source *source = + client_primary_selection_source_from_source(wlr_source); + zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_primary_selection_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_primary_selection_source *client_source = + client_primary_selection_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + + if (source == NULL) { + return; + } + + source->active_primary_source = NULL; + + zwlr_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); +} + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl = { + .send = client_primary_selection_source_send, + .destroy = client_primary_selection_source_destroy, +}; + + +struct data_offer { + struct wl_resource *resource; + struct wlr_data_control_device_v1 *device; + bool is_primary; +}; + +static void data_offer_destroy(struct data_offer *offer) { + if (offer == NULL) { + return; + } + + struct wlr_data_control_device_v1 *device = offer->device; + if (device != NULL) { + if (offer->is_primary) { + device->primary_selection_offer_resource = NULL; + } else { + device->selection_offer_resource = NULL; + } + } + + wl_resource_set_user_data(offer->resource, NULL); + free(offer); +} static const struct zwlr_data_control_offer_v1_interface offer_impl; -static struct wlr_data_control_device_v1 *control_from_offer_resource( +static struct data_offer *data_offer_from_offer_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwlr_data_control_offer_v1_interface, &offer_impl)); @@ -125,12 +239,33 @@ static struct wlr_data_control_device_v1 *control_from_offer_resource( static void offer_handle_receive(struct wl_client *client, struct wl_resource *resource, const char *mime_type, int fd) { - struct wlr_data_control_device_v1 *device = control_from_offer_resource(resource); - if (device == NULL || device->seat->selection_source == NULL) { + struct data_offer *offer = data_offer_from_offer_resource(resource); + if (offer == NULL) { + close(fd); + return; + } + + struct wlr_data_control_device_v1 *device = offer->device; + if (device == NULL) { close(fd); return; } - wlr_data_source_send(device->seat->selection_source, mime_type, fd); + + if (offer->is_primary) { + if (device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + wlr_primary_selection_source_send( + device->seat->primary_selection_source, + mime_type, fd); + } else { + if (device->seat->selection_source == NULL) { + close(fd); + return; + } + wlr_data_source_send(device->seat->selection_source, mime_type, fd); + } } static void offer_handle_destroy(struct wl_client *client, @@ -144,28 +279,39 @@ static const struct zwlr_data_control_offer_v1_interface offer_impl = { }; static void offer_handle_resource_destroy(struct wl_resource *resource) { - struct wlr_data_control_device_v1 *device = control_from_offer_resource(resource); - if (device != NULL) { - device->selection_offer_resource = NULL; - } + struct data_offer *offer = data_offer_from_offer_resource(resource); + data_offer_destroy(offer); } static struct wl_resource *create_offer(struct wlr_data_control_device_v1 *device, - struct wlr_data_source *source) { + struct wl_array *mime_types, bool is_primary) { struct wl_client *client = wl_resource_get_client(device->resource); + + struct data_offer *offer = calloc(1, sizeof(struct data_offer)); + if (offer == NULL) { + wl_client_post_no_memory(client); + return NULL; + } + + offer->device = device; + offer->is_primary = is_primary; + uint32_t version = wl_resource_get_version(device->resource); struct wl_resource *resource = wl_resource_create(client, &zwlr_data_control_offer_v1_interface, version, 0); if (resource == NULL) { return NULL; } - wl_resource_set_implementation(resource, &offer_impl, device, + + offer->resource = resource; + + wl_resource_set_implementation(resource, &offer_impl, offer, offer_handle_resource_destroy); zwlr_data_control_device_v1_send_data_offer(device->resource, resource); char **p; - wl_array_for_each(p, &source->mime_types) { + wl_array_for_each(p, mime_types) { zwlr_data_control_offer_v1_send_offer(resource, *p); } @@ -191,16 +337,103 @@ static void control_handle_set_selection(struct wl_client *client, return; } - struct client_data_source *source = NULL; + struct data_control_source *source = NULL; if (source_resource != NULL) { source = source_from_resource(source_resource); } - struct wlr_data_source *wlr_source = source ? &source->source : NULL; + if (source == NULL) { + wlr_seat_request_set_selection(device->seat, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_data_source *client_source = + calloc(1, sizeof(struct client_data_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_data_source *wlr_source = &client_source->source; + wlr_data_source_init(wlr_source, &client_source_impl); + source->active_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + wlr_seat_request_set_selection(device->seat, wlr_source, wl_display_next_serial(device->seat->display)); } +static void control_handle_set_primary_selection(struct wl_client *client, + struct wl_resource *control_resource, + struct wl_resource *source_resource) { + struct wlr_data_control_device_v1 *device = + control_from_resource(control_resource); + if (device == NULL) { + return; + } + + struct data_control_source *source = NULL; + if (source_resource != NULL) { + source = source_from_resource(source_resource); + } + + if (source == NULL) { + wlr_seat_request_set_primary_selection(device->seat, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_primary_selection_source *client_source = + calloc(1, sizeof(struct client_primary_selection_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_primary_selection_source *wlr_source = &client_source->source; + wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl); + source->active_primary_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + + wlr_seat_request_set_primary_selection(device->seat, wlr_source, + wl_display_next_serial(device->seat->display)); +} + static void control_handle_destroy(struct wl_client *client, struct wl_resource *control_resource) { wl_resource_destroy(control_resource); @@ -208,6 +441,7 @@ static void control_handle_destroy(struct wl_client *client, static const struct zwlr_data_control_device_v1_interface control_impl = { .set_selection = control_handle_set_selection, + .set_primary_selection = control_handle_set_primary_selection, .destroy = control_handle_destroy, }; @@ -216,12 +450,15 @@ static void control_send_selection(struct wlr_data_control_device_v1 *device) { if (device->selection_offer_resource != NULL) { // Make the offer inert - wl_resource_set_user_data(device->selection_offer_resource, NULL); + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); } device->selection_offer_resource = NULL; if (source != NULL) { - device->selection_offer_resource = create_offer(device, source); + device->selection_offer_resource = + create_offer(device, &source->mime_types, false); if (device->selection_offer_resource == NULL) { wl_resource_post_no_memory(device->resource); return; @@ -232,6 +469,37 @@ static void control_send_selection(struct wlr_data_control_device_v1 *device) { device->selection_offer_resource); } +static void control_send_primary_selection( + struct wlr_data_control_device_v1 *device) { + uint32_t version = wl_resource_get_version(device->resource); + if (version < ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION_SINCE_VERSION) { + return; + } + + struct wlr_primary_selection_source *source = + device->seat->primary_selection_source; + + if (device->primary_selection_offer_resource != NULL) { + // Make the offer inert + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); + } + + device->primary_selection_offer_resource = NULL; + if (source != NULL) { + device->primary_selection_offer_resource = + create_offer(device, &source->mime_types, true); + if (device->primary_selection_offer_resource == NULL) { + wl_resource_post_no_memory(device->resource); + return; + } + } + + zwlr_data_control_device_v1_send_primary_selection(device->resource, + device->primary_selection_offer_resource); +} + static void control_handle_resource_destroy(struct wl_resource *resource) { struct wlr_data_control_device_v1 *device = control_from_resource(resource); wlr_data_control_device_v1_destroy(device); @@ -251,6 +519,14 @@ static void control_handle_seat_set_selection(struct wl_listener *listener, control_send_selection(device); } +static void control_handle_seat_set_primary_selection( + struct wl_listener *listener, + void *data) { + struct wlr_data_control_device_v1 *device = + wl_container_of(listener, device, seat_set_primary_selection); + control_send_primary_selection(device); +} + void wlr_data_control_device_v1_destroy(struct wlr_data_control_device_v1 *device) { if (device == NULL) { return; @@ -259,10 +535,18 @@ void wlr_data_control_device_v1_destroy(struct wlr_data_control_device_v1 *devic // Make the resources inert wl_resource_set_user_data(device->resource, NULL); if (device->selection_offer_resource != NULL) { - wl_resource_set_user_data(device->selection_offer_resource, NULL); + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); + } + if (device->primary_selection_offer_resource != NULL) { + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); } wl_list_remove(&device->seat_destroy.link); wl_list_remove(&device->seat_set_selection.link); + wl_list_remove(&device->seat_set_primary_selection.link); wl_list_remove(&device->link); free(device); } @@ -279,19 +563,21 @@ static struct wlr_data_control_manager_v1 *manager_from_resource( static void manager_handle_create_data_source(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id) { - struct client_data_source *source = - calloc(1, sizeof(struct client_data_source)); + struct data_control_source *source = + calloc(1, sizeof(struct data_control_source)); if (source == NULL) { wl_resource_post_no_memory(manager_resource); return; } - wlr_data_source_init(&source->source, &client_source_impl); + + wl_array_init(&source->mime_types); uint32_t version = wl_resource_get_version(manager_resource); source->resource = wl_resource_create(client, &zwlr_data_control_source_v1_interface, version, id); if (source->resource == NULL) { wl_resource_post_no_memory(manager_resource); + wl_array_release(&source->mime_types); free(source); return; } @@ -335,6 +621,11 @@ static void manager_handle_get_data_device(struct wl_client *client, wl_signal_add(&device->seat->events.set_selection, &device->seat_set_selection); + device->seat_set_primary_selection.notify = + control_handle_seat_set_primary_selection; + wl_signal_add(&device->seat->events.set_primary_selection, + &device->seat_set_primary_selection); + wl_list_insert(&manager->devices, &device->link); wlr_signal_emit_safe(&manager->events.new_device, device); @@ -343,6 +634,7 @@ static void manager_handle_get_data_device(struct wl_client *client, device = control_from_resource(resource); if (device != NULL) { control_send_selection(device); + control_send_primary_selection(device); } } @@ -375,6 +667,10 @@ static void manager_bind(struct wl_client *client, void *data, uint32_t version, manager_handle_resource_destroy); wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + if (version >= ZWLR_DATA_CONTROL_MANAGER_V1_PRIMARY_SELECTION_SINCE_VERSION) { + zwlr_data_control_manager_v1_send_primary_selection(resource); + } } static void handle_display_destroy(struct wl_listener *listener, void *data) { @@ -396,8 +692,8 @@ struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( wl_signal_init(&manager->events.new_device); manager->global = wl_global_create(display, - &zwlr_data_control_manager_v1_interface, DATA_CONTROL_MANAGER_VERSION, - manager, manager_bind); + &zwlr_data_control_manager_v1_interface, + DATA_CONTROL_MANAGER_VERSION, manager, manager_bind); if (manager->global == NULL) { free(manager); return NULL;