From 8163f7e1e21d9b039319cc3e38993811bfbeea4f Mon Sep 17 00:00:00 2001 From: emersion Date: Thu, 24 Jan 2019 12:04:41 +0100 Subject: [PATCH] primary-selection-v1: copy from gtk-primary-selection --- include/wlr/types/meson.build | 1 + include/wlr/types/wlr_primary_selection_v1.h | 52 ++ meson.build | 2 +- protocol/meson.build | 1 + rootston/desktop.c | 2 + types/meson.build | 1 + types/wlr_gtk_primary_selection.c | 4 +- types/wlr_primary_selection_v1.c | 501 +++++++++++++++++++ 8 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 include/wlr/types/wlr_primary_selection_v1.h create mode 100644 types/wlr_primary_selection_v1.c diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index 82715ad1b..fe39db364 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -28,6 +28,7 @@ install_headers( 'wlr_pointer_gestures_v1.h', 'wlr_pointer.h', 'wlr_presentation_time.h', + 'wlr_primary_selection_v1.h', 'wlr_primary_selection.h', 'wlr_region.h', 'wlr_relative_pointer_v1.h', diff --git a/include/wlr/types/wlr_primary_selection_v1.h b/include/wlr/types/wlr_primary_selection_v1.h new file mode 100644 index 000000000..e6bde8a3d --- /dev/null +++ b/include/wlr/types/wlr_primary_selection_v1.h @@ -0,0 +1,52 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRIMARY_SELECTION_V1_H +#define WLR_TYPES_WLR_PRIMARY_SELECTION_V1_H + +#include +#include + +struct wlr_primary_selection_v1_device_manager { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list devices; // wlr_primary_selection_v1_device::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +/** + * A device is a per-seat object used to set and get the current selection. + */ +struct wlr_primary_selection_v1_device { + struct wlr_primary_selection_v1_device_manager *manager; + struct wlr_seat *seat; + struct wl_list link; // wlr_primary_selection_v1_device_manager::devices + struct wl_list resources; // wl_resource_get_link + + struct wl_list offers; // wl_resource_get_link + + struct wl_listener seat_destroy; + struct wl_listener seat_focus_change; + struct wl_listener seat_set_primary_selection; + + void *data; +}; + +struct wlr_primary_selection_v1_device_manager * + wlr_primary_selection_v1_device_manager_create(struct wl_display *display); +void wlr_primary_selection_v1_device_manager_destroy( + struct wlr_primary_selection_v1_device_manager *manager); + +#endif diff --git a/meson.build b/meson.build index 4d68bd6cc..56c3f6a3f 100644 --- a/meson.build +++ b/meson.build @@ -53,7 +53,7 @@ add_project_arguments('-DWL_HIDE_DEPRECATED', language: 'c') wayland_server = dependency('wayland-server', version: '>=1.16') wayland_client = dependency('wayland-client') wayland_egl = dependency('wayland-egl') -wayland_protos = dependency('wayland-protocols', version: '>=1.16') +wayland_protos = dependency('wayland-protocols', version: '>=1.17') egl = dependency('egl') glesv2 = dependency('glesv2') drm = dependency('libdrm', version: '>=2.4.95') diff --git a/protocol/meson.build b/protocol/meson.build index c9cc5be97..295da6c5f 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -18,6 +18,7 @@ protocols = [ [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], [wl_protocol_dir, 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/primary-selection/primary-selection-unstable-v1.xml'], [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], [wl_protocol_dir, 'unstable/text-input/text-input-unstable-v3.xml'], diff --git a/rootston/desktop.c b/rootston/desktop.c index fcb530cf2..ff9544d5a 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1087,6 +1088,7 @@ struct roots_desktop *desktop_create(struct roots_server *server, desktop->pointer_gestures = wlr_pointer_gestures_v1_create(server->wl_display); + wlr_primary_selection_v1_device_manager_create(server->wl_display); wlr_data_control_manager_v1_create(server->wl_display); return desktop; diff --git a/types/meson.build b/types/meson.build index f2e41b582..04b90311e 100644 --- a/types/meson.build +++ b/types/meson.build @@ -51,6 +51,7 @@ lib_wlr_types = static_library( 'wlr_pointer_gestures_v1.c', 'wlr_pointer.c', 'wlr_presentation_time.c', + 'wlr_primary_selection_v1.c', 'wlr_primary_selection.c', 'wlr_region.c', 'wlr_relative_pointer_v1.c', diff --git a/types/wlr_gtk_primary_selection.c b/types/wlr_gtk_primary_selection.c index a7b159b03..5228014c5 100644 --- a/types/wlr_gtk_primary_selection.c +++ b/types/wlr_gtk_primary_selection.c @@ -358,7 +358,7 @@ static void device_destroy(struct wlr_gtk_primary_selection_device *device) { static const struct gtk_primary_selection_device_manager_interface device_manager_impl; -struct wlr_gtk_primary_selection_device_manager *manager_from_resource( +static struct wlr_gtk_primary_selection_device_manager *manager_from_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, >k_primary_selection_device_manager_interface, &device_manager_impl)); @@ -387,7 +387,7 @@ static void device_manager_handle_create_source(struct wl_client *client, source_resource_handle_destroy); } -void device_manager_handle_get_device(struct wl_client *client, +static void device_manager_handle_get_device(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *seat_resource) { struct wlr_seat_client *seat_client = diff --git a/types/wlr_primary_selection_v1.c b/types/wlr_primary_selection_v1.c new file mode 100644 index 000000000..1a6411ea3 --- /dev/null +++ b/types/wlr_primary_selection_v1.c @@ -0,0 +1,501 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "primary-selection-unstable-v1-protocol.h" +#include "util/signal.h" + +#define DEVICE_MANAGER_VERSION 1 + +static const struct zwp_primary_selection_offer_v1_interface offer_impl; + +static struct wlr_primary_selection_v1_device *device_from_offer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_offer_v1_interface, &offer_impl)); + return wl_resource_get_user_data(resource); +} + +static void offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int32_t fd) { + struct wlr_primary_selection_v1_device *device = + device_from_offer_resource(resource); + if (device == NULL || device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + + wlr_primary_selection_source_send(device->seat->primary_selection_source, + mime_type, fd); +} + +static void offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_offer_v1_interface offer_impl = { + .receive = offer_handle_receive, + .destroy = offer_handle_destroy, +}; + +static void offer_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wlr_primary_selection_v1_device *device_from_resource( + struct wl_resource *resource); + +static void create_offer(struct wl_resource *device_resource, + struct wlr_primary_selection_source *source) { + struct wlr_primary_selection_v1_device *device = + device_from_resource(device_resource); + assert(device != NULL); + + struct wl_client *client = wl_resource_get_client(device_resource); + uint32_t version = wl_resource_get_version(device_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_offer_v1_interface, version, 0); + if (resource == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + wl_resource_set_implementation(resource, &offer_impl, device, + offer_handle_resource_destroy); + + wl_list_insert(&device->offers, wl_resource_get_link(resource)); + + zwp_primary_selection_device_v1_send_data_offer(device_resource, resource); + + char **p; + wl_array_for_each(p, &source->mime_types) { + zwp_primary_selection_offer_v1_send_offer(resource, *p); + } + + zwp_primary_selection_device_v1_send_selection(device_resource, resource); +} + +static void destroy_offer(struct wl_resource *resource) { + if (device_from_offer_resource(resource) == NULL) { + return; + } + + // Make the offer inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); +} + + +struct client_data_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; + bool finalized; +}; + +static void client_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = (struct client_data_source *)wlr_source; + zwp_primary_selection_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_data_source *source = (struct client_data_source *)wlr_source; + zwp_primary_selection_source_v1_send_cancelled(source->resource); + // Make the source resource inert + wl_resource_set_user_data(source->resource, NULL); + free(source); +} + +static const struct wlr_primary_selection_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + +static const struct zwp_primary_selection_source_v1_interface source_impl; + +static struct client_data_source *client_data_source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + if (source->finalized) { + wlr_log(WLR_DEBUG, "Offering additional MIME type after set_selection"); + } + + const char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, &source->source.mime_types) { + if (strcmp(*mime_type_ptr, mime_type) == 0) { + wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", + mime_type); + return; + } + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->source.mime_types, sizeof(*p)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_source_v1_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void source_resource_handle_destroy(struct wl_resource *resource) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + wlr_primary_selection_source_destroy(&source->source); +} + + +static const struct zwp_primary_selection_device_v1_interface device_impl; + +static struct wlr_primary_selection_v1_device *device_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_device_v1_interface, &device_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_handle_set_selection(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *source_resource, + uint32_t serial) { + struct wlr_primary_selection_v1_device *device = + device_from_resource(resource); + if (device == NULL) { + return; + } + + struct client_data_source *client_source = NULL; + if (source_resource != NULL) { + client_source = client_data_source_from_resource(source_resource); + } + + struct wlr_primary_selection_source *source = NULL; + if (client_source != NULL) { + client_source->finalized = true; + source = &client_source->source; + } + + wlr_seat_request_set_primary_selection(device->seat, source, serial); +} + +static void device_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_device_v1_interface device_impl = { + .set_selection = device_handle_set_selection, + .destroy = device_handle_destroy, +}; + +static void device_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +static void device_resource_send_selection(struct wl_resource *resource, + struct wlr_primary_selection_source *source) { + assert(device_from_resource(resource) != NULL); + + if (source != NULL) { + create_offer(resource, source); + } else { + zwp_primary_selection_device_v1_send_selection(resource, NULL); + } +} + +static void device_send_selection( + struct wlr_primary_selection_v1_device *device) { + struct wlr_seat_client *seat_client = + device->seat->keyboard_state.focused_client; + if (seat_client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &device->resources) { + if (wl_resource_get_client(resource) == seat_client->client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } + } +} + +static void device_destroy(struct wlr_primary_selection_v1_device *device); + +static void device_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_destroy); + device_destroy(device); +} + +static void device_handle_seat_focus_change(struct wl_listener *listener, + void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_focus_change); + // TODO: maybe make previous offers inert, or set a NULL selection for + // previous client? + device_send_selection(device); +} + +static void device_handle_seat_set_primary_selection( + struct wl_listener *listener, void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_set_primary_selection); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &device->offers) { + destroy_offer(resource); + } + + device_send_selection(device); +} + +static struct wlr_primary_selection_v1_device *get_or_create_device( + struct wlr_primary_selection_v1_device_manager *manager, + struct wlr_seat *seat) { + struct wlr_primary_selection_v1_device *device; + wl_list_for_each(device, &manager->devices, link) { + if (device->seat == seat) { + return device; + } + } + + device = calloc(1, sizeof(struct wlr_primary_selection_v1_device)); + if (device == NULL) { + return NULL; + } + device->manager = manager; + device->seat = seat; + + wl_list_init(&device->resources); + wl_list_insert(&manager->devices, &device->link); + + wl_list_init(&device->offers); + + device->seat_destroy.notify = device_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &device->seat_destroy); + + device->seat_focus_change.notify = device_handle_seat_focus_change; + wl_signal_add(&seat->keyboard_state.events.focus_change, + &device->seat_focus_change); + + device->seat_set_primary_selection.notify = + device_handle_seat_set_primary_selection; + wl_signal_add(&seat->events.set_primary_selection, + &device->seat_set_primary_selection); + + return device; +} + +static void device_destroy(struct wlr_primary_selection_v1_device *device) { + if (device == NULL) { + return; + } + wl_list_remove(&device->link); + wl_list_remove(&device->seat_destroy.link); + wl_list_remove(&device->seat_focus_change.link); + wl_list_remove(&device->seat_set_primary_selection.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &device->offers) { + destroy_offer(resource); + } + wl_resource_for_each_safe(resource, resource_tmp, &device->resources) { + // Make the resource inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); + } + free(device); +} + + +static const struct zwp_primary_selection_device_manager_v1_interface + device_manager_impl; + +static struct wlr_primary_selection_v1_device_manager *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_device_manager_v1_interface, &device_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_manager_handle_create_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)); + if (source == NULL) { + wl_client_post_no_memory(client); + return; + } + wlr_primary_selection_source_init(&source->source, &client_source_impl); + + uint32_t version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + &zwp_primary_selection_source_v1_interface, version, id); + if (source->resource == NULL) { + free(source); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(source->resource, &source_impl, source, + source_resource_handle_destroy); +} + +static void device_manager_handle_get_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + struct wlr_primary_selection_v1_device_manager *manager = + manager_from_resource(manager_resource); + + struct wlr_primary_selection_v1_device *device = + get_or_create_device(manager, seat_client->seat); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_device_v1_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &device_impl, device, + device_handle_resource_destroy); + wl_list_insert(&device->resources, wl_resource_get_link(resource)); + + if (device->seat->keyboard_state.focused_client == seat_client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } +} + +static void device_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_primary_selection_device_manager_v1_interface + device_manager_impl = { + .create_source = device_manager_handle_create_source, + .get_device = device_manager_handle_get_device, + .destroy = device_manager_handle_destroy, +}; + +static void device_manager_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +static void primary_selection_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_primary_selection_v1_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_device_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &device_manager_impl, manager, + device_manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_primary_selection_v1_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_primary_selection_v1_device_manager_destroy(manager); +} + +struct wlr_primary_selection_v1_device_manager * + wlr_primary_selection_v1_device_manager_create( + struct wl_display *display) { + struct wlr_primary_selection_v1_device_manager *manager = + calloc(1, sizeof(struct wlr_primary_selection_v1_device_manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &zwp_primary_selection_device_manager_v1_interface, DEVICE_MANAGER_VERSION, + manager, primary_selection_device_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_list_init(&manager->resources); + wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_primary_selection_v1_device_manager_destroy( + struct wlr_primary_selection_v1_device_manager *manager) { + if (manager == NULL) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + wl_global_destroy(manager->global); + free(manager); +}