diff options
-rw-r--r-- | .editorconfig | 1 | ||||
-rw-r--r-- | include/wlr/types/wlr_primary_selection.h | 31 | ||||
-rw-r--r-- | protocol/gtk-primary-selection.xml | 225 | ||||
-rw-r--r-- | protocol/meson.build | 2 | ||||
-rw-r--r-- | types/meson.build | 1 | ||||
-rw-r--r-- | types/wlr_primary_selection.c | 131 |
6 files changed, 391 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig index 64f18915..b6b6a367 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,4 @@ trim_trailing_whitespace = true [*.xml] indent_style = space indent_size = 2 +tab_width = 8 diff --git a/include/wlr/types/wlr_primary_selection.h b/include/wlr/types/wlr_primary_selection.h new file mode 100644 index 00000000..edbaf81e --- /dev/null +++ b/include/wlr/types/wlr_primary_selection.h @@ -0,0 +1,31 @@ +#ifndef WLR_TYPES_WLR_PRIMARY_SELECTION_H +#define WLR_TYPES_WLR_PRIMARY_SELECTION_H + +#include <wayland-server.h> + +struct wlr_primary_selection_device_manager { + struct wl_global *global; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_primary_selection_source { + struct wl_resource *resource; + + struct wl_array mime_types; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_primary_selection_device_manager * + wlr_primary_selection_device_manager_create(struct wl_display *display); +void wlr_primary_selection_device_manager_destroy( + struct wlr_primary_selection_device_manager *manager); + +#endif diff --git a/protocol/gtk-primary-selection.xml b/protocol/gtk-primary-selection.xml new file mode 100644 index 00000000..02cab94f --- /dev/null +++ b/protocol/gtk-primary-selection.xml @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="gtk_primary_selection"> + <copyright> + 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. + </copyright> + + <description summary="Primary selection protocol"> + 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. + </description> + + <interface name="gtk_primary_selection_device_manager" version="1"> + <description summary="X primary selection emulation"> + 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. + </description> + + <request name="create_source"> + <description summary="create a new primary selection source"> + Create a new primary selection source. + </description> + <arg name="id" type="new_id" interface="gtk_primary_selection_source"/> + </request> + + <request name="get_device"> + <description summary="create a new primary selection device"> + Create a new data device for a given seat. + </description> + <arg name="id" type="new_id" interface="gtk_primary_selection_device"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device manager"> + Destroy the primary selection device manager. + </description> + </request> + </interface> + + <interface name="gtk_primary_selection_device" version="1"> + <request name="set_selection"> + <description summary="set the primary selection"> + 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. + </description> + <arg name="source" type="object" interface="gtk_primary_selection_source" allow-null="true"/> + <arg name="serial" type="uint" summary="serial of the event that triggered this request"/> + </request> + + <event name="data_offer"> + <description summary="introduce a new wp_primary_selection_offer"> + 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. + </description> + <arg name="offer" type="new_id" interface="gtk_primary_selection_offer"/> + </event> + + <event name="selection"> + <description summary="advertise a new primary selection"> + 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. + </description> + <arg name="id" type="object" interface="gtk_primary_selection_offer" allow-null="true"/> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device"> + Destroy the primary selection device. + </description> + </request> + </interface> + + <interface name="gtk_primary_selection_offer" version="1"> + <description summary="offer to transfer primary selection contents"> + 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 source + will transferthat the + data can be converted to and provides the mechanisms for transferring the + data directly to the client. + </description> + + <request name="receive"> + <description summary="request that the data is transferred"> + 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. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection offer"> + Destroy the primary selection offer. + </description> + </request> + + <event name="offer"> + <description summary="advertise offered mime type"> + 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. + </description> + <arg name="mime_type" type="string"/> + </event> + </interface> + + <interface name="gtk_primary_selection_source" version="1"> + <description summary="offer to replace the contents of the primary selection"> + 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. + </description> + + <request name="offer"> + <description summary="add an offered mime type"> + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + </description> + <arg name="mime_type" type="string"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection source"> + Destroy the primary selection source. + </description> + </request> + + <event name="send"> + <description summary="send the primary selection contents"> + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </event> + + <event name="cancelled"> + <description summary="request for primary selection contents was canceled"> + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + </description> + </event> + </interface> +</protocol> diff --git a/protocol/meson.build b/protocol/meson.build index ff54815a..f6aca5f5 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -23,6 +23,7 @@ wayland_scanner_client = generator( protocols = [ [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], 'gamma-control.xml', + 'gtk-primary-selection.xml', 'screenshooter.xml', 'server-decoration.xml', ] @@ -30,6 +31,7 @@ protocols = [ client_protocols = [ [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], 'gamma-control.xml', + 'gtk-primary-selection.xml', 'screenshooter.xml', 'server-decoration.xml', ] diff --git a/types/meson.build b/types/meson.build index 4669165a..82031e89 100644 --- a/types/meson.build +++ b/types/meson.build @@ -12,6 +12,7 @@ lib_wlr_types = static_library( 'wlr_output.c', 'wlr_output_layout.c', 'wlr_pointer.c', + 'wlr_primary_selection.c', 'wlr_region.c', 'wlr_screenshooter.c', 'wlr_seat.c', diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c new file mode 100644 index 00000000..7ed4a402 --- /dev/null +++ b/types/wlr_primary_selection.c @@ -0,0 +1,131 @@ +#define _XOPEN_SOURCE 700 +#include <stdlib.h> +#include <string.h> +#include <gtk-primary-selection-protocol.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/util/log.h> + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct wlr_primary_selection_source *source = + wl_resource_get_user_data(resource); + + char **p = wl_array_add(&source->mime_types, sizeof(*p)); + if (p) { + *p = strdup(mime_type); + } + if (p == NULL || *p == NULL) { + wl_resource_post_no_memory(resource); + } +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct gtk_primary_selection_source_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void source_resource_handle_destroy( struct wl_resource *resource) { + struct wlr_primary_selection_source *source = + wl_resource_get_user_data(resource); + wl_signal_emit(&source->events.destroy, source); + free(source); +} + +static void device_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + //struct wlr_primary_selection_device_manager *manager = + // wl_resource_get_user_data(manager_resource); + + struct wlr_primary_selection_source *source = + calloc(1, sizeof(struct wlr_primary_selection_source)); + if (source == NULL) { + wl_client_post_no_memory(client); + return; + } + + int version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + >k_primary_selection_source_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); + + wl_signal_init(&source->events.destroy); + wl_array_init(&source->mime_types); + + wlr_log(L_DEBUG, "new gtk_primary_selection_source %p (res %p)", source, + source->resource); +} + +static void device_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct gtk_primary_selection_device_manager_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 primary_selection_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_primary_selection_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + >k_primary_selection_device_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &device_manager_impl, manager, + NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_primary_selection_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_primary_selection_device_manager_destroy(manager); +} + +struct wlr_primary_selection_device_manager * + wlr_primary_selection_device_manager_create( + struct wl_display *display) { + struct wlr_primary_selection_device_manager *manager = + calloc(1, sizeof(struct wlr_primary_selection_device_manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + >k_primary_selection_device_manager_interface, 1, manager, + primary_selection_device_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_primary_selection_device_manager_destroy( + struct wlr_primary_selection_device_manager *manager) { + if (manager == NULL) { + return; + } + // TODO: free wl_resources + wl_global_destroy(manager->global); + free(manager); +} |