diff --git a/src/Makefile.am b/src/Makefile.am index 5e7e8a3db..301452d43 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -243,6 +243,7 @@ libmutter_la_SOURCES += \ wayland/meta-wayland-private.h \ wayland/meta-xwayland.c \ wayland/meta-xwayland.h \ + wayland/meta-xwayland-selection.c \ wayland/meta-xwayland-private.h \ wayland/meta-wayland-buffer.c \ wayland/meta-wayland-buffer.h \ diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h index edcc60a83..c93fcf26d 100644 --- a/src/wayland/meta-wayland-private.h +++ b/src/wayland/meta-wayland-private.h @@ -33,6 +33,8 @@ #include "meta-wayland-surface.h" #include "meta-wayland-seat.h" +typedef struct _MetaXWaylandSelection MetaXWaylandSelection; + typedef struct { struct wl_list link; @@ -52,6 +54,8 @@ typedef struct char *display_name; GMainLoop *init_loop; + + MetaXWaylandSelection *selection_data; } MetaXWaylandManager; struct _MetaWaylandCompositor diff --git a/src/wayland/meta-xwayland-private.h b/src/wayland/meta-xwayland-private.h index 83b2986d3..594ab1c53 100644 --- a/src/wayland/meta-xwayland-private.h +++ b/src/wayland/meta-xwayland-private.h @@ -34,4 +34,9 @@ meta_xwayland_complete_init (void); void meta_xwayland_stop (MetaXWaylandManager *manager); +/* wl_data_device/X11 selection interoperation */ +void meta_xwayland_init_selection (void); +void meta_xwayland_shutdown_selection (void); +gboolean meta_xwayland_selection_handle_event (XEvent *xevent); + #endif /* META_XWAYLAND_PRIVATE_H */ diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c new file mode 100644 index 000000000..c2968016b --- /dev/null +++ b/src/wayland/meta-xwayland-selection.c @@ -0,0 +1,944 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* The file is loosely based on xwayland/selection.c from Weston */ + +#include "config.h" + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "meta-xwayland-private.h" +#include "meta-wayland-data-device.h" + +#define INCR_CHUNK_SIZE (128 * 1024) + +typedef struct { + MetaXWaylandSelection *selection_data; + GInputStream *stream; + GCancellable *cancellable; + MetaWindow *window; + XSelectionRequestEvent request_event; + guchar buffer[INCR_CHUNK_SIZE]; + gsize buffer_len; + guint incr : 1; +} WaylandSelectionData; + +typedef struct { + MetaXWaylandSelection *selection_data; + GOutputStream *stream; + GCancellable *cancellable; + gchar *mime_type; + guint selection : 3; + guint incr : 1; +} X11SelectionData; + +typedef struct { + Atom selection_atom; + Window window; + Window owner; + Time timestamp; + const MetaWaylandDataSource *source; + WaylandSelectionData *wayland_selection; + X11SelectionData *x11_selection; + + struct wl_listener ownership_listener; +} MetaSelectionBridge; + +struct _MetaXWaylandSelection { + MetaSelectionBridge clipboard; +}; + +static MetaSelectionBridge * +atom_to_selection_bridge (MetaWaylandCompositor *compositor, + Atom selection_atom) +{ + MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data; + + if (selection_atom == selection_data->clipboard.selection_atom) + return &selection_data->clipboard; + else + return NULL; +} + +static X11SelectionData * +x11_selection_data_new (MetaXWaylandSelection *selection_data, + int fd, + const char *mime_type) +{ + X11SelectionData *data; + + data = g_slice_new0 (X11SelectionData); + data->selection_data = selection_data; + data->stream = g_unix_output_stream_new (fd, TRUE); + data->cancellable = g_cancellable_new (); + data->mime_type = g_strdup (mime_type); + + return data; +} + +static void +x11_selection_data_free (X11SelectionData *data) +{ + g_cancellable_cancel (data->cancellable); + g_object_unref (data->cancellable); + g_object_unref (data->stream); + g_free (data->mime_type); + g_slice_free (X11SelectionData, data); +} + +static void +x11_data_write_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + MetaSelectionBridge *selection = user_data; + X11SelectionData *data = selection->x11_selection; + GError *error = NULL; + + g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &error); + + if (data->incr) + { + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + XDeleteProperty (xdisplay, selection->window, + gdk_x11_get_xatom_by_name ("_META_SELECTION")); + } + + if (error) + { + if (error->domain != G_IO_ERROR || + error->code != G_IO_ERROR_CANCELLED) + g_warning ("Error writing from X11 selection: %s\n", error->message); + + g_error_free (error); + } + + if (!data->incr) + { + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); + } +} + +static void +x11_selection_data_write (MetaSelectionBridge *selection, + guchar *buffer, + gulong len) +{ + X11SelectionData *data = selection->x11_selection; + + g_output_stream_write_async (data->stream, buffer, len, + G_PRIORITY_DEFAULT, data->cancellable, + x11_data_write_cb, selection); +} + +static MetaWaylandDataSource * +data_device_get_active_source_for_atom (MetaWaylandDataDevice *data_device, + Atom selection_atom) +{ + if (selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD")) + return data_device->selection_data_source; + else + return NULL; +} + +static WaylandSelectionData * +wayland_selection_data_new (XSelectionRequestEvent *request_event, + MetaWaylandCompositor *compositor) +{ + MetaWaylandDataDevice *data_device; + MetaWaylandDataSource *wayland_source; + MetaSelectionBridge *selection; + WaylandSelectionData *data; + const gchar *mime_type; + GError *error = NULL; + int p[2]; + + selection = atom_to_selection_bridge (compositor, request_event->selection); + + if (!selection) + return NULL; + + if (!g_unix_open_pipe (p, FD_CLOEXEC, &error)) + { + g_critical ("Failed to open pipe: %s\n", error->message); + g_error_free (error); + return NULL; + } + + data_device = &compositor->seat->data_device; + mime_type = gdk_x11_get_xatom_name (request_event->target); + + if (!g_unix_set_fd_nonblocking (p[0], TRUE, &error) || + !g_unix_set_fd_nonblocking (p[1], TRUE, &error)) + { + if (error) + { + g_critical ("Failed to make fds non-blocking: %s\n", error->message); + g_error_free (error); + } + + close (p[0]); + close (p[1]); + return NULL; + } + + wayland_source = data_device_get_active_source_for_atom (data_device, + selection->selection_atom), + meta_wayland_data_source_send (wayland_source, mime_type, p[1]); + + data = g_slice_new0 (WaylandSelectionData); + data->request_event = *request_event; + data->cancellable = g_cancellable_new (); + data->stream = g_unix_input_stream_new (p[0], TRUE); + + data->window = meta_display_lookup_x_window (meta_get_display (), + data->request_event.requestor); + + if (!data->window) + { + /* Not a managed window, set the PropertyChangeMask + * for INCR deletion notifications. + */ + XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + data->request_event.requestor, PropertyChangeMask); + } + + return data; +} + +static void +reply_selection_request (XSelectionRequestEvent *request_event, + gboolean accepted) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + XSelectionEvent event; + + memset(&event, 0, sizeof (XSelectionEvent)); + event.type = SelectionNotify; + event.time = request_event->time; + event.requestor = request_event->requestor; + event.selection = request_event->selection; + event.target = request_event->target; + event.property = accepted ? request_event->property : None; + + XSendEvent (xdisplay, request_event->requestor, + False, NoEventMask, (XEvent *) &event); +} + +static void +wayland_selection_data_free (WaylandSelectionData *data) +{ + if (!data->window) + { + MetaDisplay *display = meta_get_display (); + + meta_error_trap_push (display); + XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + data->request_event.requestor, NoEventMask); + meta_error_trap_pop (display); + } + + g_cancellable_cancel (data->cancellable); + g_object_unref (data->cancellable); + g_object_unref (data->stream); + g_slice_free (WaylandSelectionData, data); +} + +static void +wayland_selection_update_x11_property (WaylandSelectionData *data) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + + XChangeProperty (xdisplay, + data->request_event.requestor, + data->request_event.property, + data->request_event.target, + 8, PropModeReplace, + data->buffer, data->buffer_len); + data->buffer_len = 0; +} + +static void +wayland_data_read_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + MetaSelectionBridge *selection = user_data; + WaylandSelectionData *data = selection->wayland_selection; + GError *error = NULL; + gsize bytes_read; + + bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (object), + res, &error); + if (error) + { + g_warning ("Error transfering wayland clipboard to X11: %s\n", + error->message); + g_error_free (error); + + if (data) + { + reply_selection_request (&data->request_event, FALSE); + g_clear_pointer (&selection->wayland_selection, + (GDestroyNotify) wayland_selection_data_free); + } + + return; + } + + data->buffer_len = bytes_read; + + if (bytes_read == INCR_CHUNK_SIZE) + { + if (!data->incr) + { + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + guint32 incr_chunk_size = INCR_CHUNK_SIZE; + + /* Not yet in incr */ + data->incr = TRUE; + XChangeProperty (xdisplay, + data->request_event.requestor, + data->request_event.property, + gdk_x11_get_xatom_by_name ("INCR"), + 32, PropModeReplace, + (guchar *) &incr_chunk_size, 1); + reply_selection_request (&data->request_event, TRUE); + } + else + wayland_selection_update_x11_property (data); + } + else + { + if (!data->incr) + { + /* Non-incr transfer finished */ + wayland_selection_update_x11_property (data); + reply_selection_request (&data->request_event, TRUE); + } + else if (data->incr) + { + /* Incr transfer complete, setting a new property */ + wayland_selection_update_x11_property (data); + + if (bytes_read > 0) + return; + } + + g_clear_pointer (&selection->wayland_selection, + (GDestroyNotify) wayland_selection_data_free); + } +} + +static void +wayland_selection_data_read (MetaSelectionBridge *selection) +{ + WaylandSelectionData *data = selection->wayland_selection; + + g_input_stream_read_async (data->stream, data->buffer, + INCR_CHUNK_SIZE, G_PRIORITY_DEFAULT, + data->cancellable, + wayland_data_read_cb, selection); +} + +static void +meta_xwayland_selection_get_incr_chunk (MetaWaylandCompositor *compositor, + MetaSelectionBridge *selection) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + gulong nitems_ret, bytes_after_ret; + guchar *prop_ret; + int format_ret; + Atom type_ret; + + XGetWindowProperty (xdisplay, + selection->window, + gdk_x11_get_xatom_by_name ("_META_SELECTION"), + 0, /* offset */ + 0x1fffffff, /* length */ + False, /* delete */ + AnyPropertyType, + &type_ret, + &format_ret, + &nitems_ret, + &bytes_after_ret, + &prop_ret); + + if (nitems_ret > 0) + { + x11_selection_data_write (selection, prop_ret, nitems_ret); + } + else + { + /* Transfer has completed */ + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); + } + + XFree (prop_ret); +} + +static void +meta_x11_source_send (MetaWaylandDataSource *source, + const gchar *mime_type, + gint fd) +{ + MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default (); + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + MetaSelectionBridge *selection = source->user_data; + Atom type_atom; + + if (strcmp (mime_type, "text/plain;charset=utf-8") == 0) + type_atom = gdk_x11_get_xatom_by_name ("UTF8_STRING"); + else + type_atom = gdk_x11_get_xatom_by_name (mime_type); + + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); + + /* Takes ownership of fd */ + selection->x11_selection = + x11_selection_data_new (compositor->xwayland_manager.selection_data, + fd, mime_type); + + XConvertSelection (xdisplay, + selection->selection_atom, type_atom, + gdk_x11_get_xatom_by_name ("_META_SELECTION"), + selection->window, + CurrentTime); + XFlush (xdisplay); +} + +static void +meta_x11_source_target (MetaWaylandDataSource *source, + const gchar *mime_type) +{ +} + +static void +meta_x11_source_cancel (MetaWaylandDataSource *source) +{ + MetaSelectionBridge *selection = source->user_data; + + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); +} + +static const MetaWaylandDataSourceFuncs meta_x11_source_funcs = { + meta_x11_source_send, + meta_x11_source_target, + meta_x11_source_cancel +}; + +static gboolean +meta_xwayland_data_source_fetch_mimetype_list (MetaWaylandDataSource *source, + Window window, + Atom prop) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + gulong nitems_ret, bytes_after_ret, i; + Atom *atoms, type_ret, utf8_string; + int format_ret; + + utf8_string = gdk_x11_get_xatom_by_name ("UTF8_STRING"); + XGetWindowProperty (xdisplay, window, prop, + 0, /* offset */ + 0x1fffffff, /* length */ + True, /* delete */ + AnyPropertyType, + &type_ret, + &format_ret, + &nitems_ret, + &bytes_after_ret, + (guchar **) &atoms); + + if (nitems_ret == 0 || type_ret != XA_ATOM) + { + XFree (atoms); + return FALSE; + } + + for (i = 0; i < nitems_ret; i++) + { + const gchar *mime_type; + + if (atoms[i] == utf8_string) + mime_type = "text/plain;charset=utf-8"; + else + mime_type = gdk_x11_get_xatom_name (atoms[i]); + + meta_wayland_data_source_add_mime_type (source, mime_type); + } + + XFree (atoms); + + return TRUE; +} + +static void +meta_xwayland_selection_get_x11_targets (MetaWaylandCompositor *compositor, + MetaSelectionBridge *selection) +{ + MetaWaylandDataSource *data_source; + + data_source = meta_wayland_data_source_new (&meta_x11_source_funcs, + NULL, selection); + + if (meta_xwayland_data_source_fetch_mimetype_list (data_source, + selection->window, + gdk_x11_get_xatom_by_name ("_META_SELECTION"))) + { + selection->source = data_source; + + if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD")) + { + meta_wayland_data_device_set_selection (&compositor->seat->data_device, data_source, + wl_display_next_serial (compositor->wayland_display)); + } + } + else + { + meta_wayland_data_source_free (data_source); + } +} + +static void +meta_xwayland_selection_get_x11_data (MetaWaylandCompositor *compositor, + MetaSelectionBridge *selection) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + gulong nitems_ret, bytes_after_ret; + guchar *prop_ret; + int format_ret; + Atom type_ret; + + if (!selection->x11_selection) + return; + + XGetWindowProperty (xdisplay, + selection->window, + gdk_x11_get_xatom_by_name ("_META_SELECTION"), + 0, /* offset */ + 0x1fffffff, /* length */ + True, /* delete */ + AnyPropertyType, + &type_ret, + &format_ret, + &nitems_ret, + &bytes_after_ret, + &prop_ret); + + selection->x11_selection->incr = (type_ret == gdk_x11_get_xatom_by_name ("INCR")); + + if (selection->x11_selection->incr) + return; + + if (type_ret == gdk_x11_get_xatom_by_name (selection->x11_selection->mime_type)) + x11_selection_data_write (selection, prop_ret, nitems_ret); + + XFree (prop_ret); +} + +static gboolean +meta_xwayland_selection_handle_selection_notify (MetaWaylandCompositor *compositor, + XEvent *xevent) +{ + XSelectionEvent *event = (XSelectionEvent *) xevent; + MetaSelectionBridge *selection; + + selection = atom_to_selection_bridge (compositor, event->selection); + + if (!selection) + return FALSE; + + /* convert selection failed */ + if (event->property == None) + { + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); + return FALSE; + } + + if (event->target == gdk_x11_get_xatom_by_name ("TARGETS")) + meta_xwayland_selection_get_x11_targets (compositor, selection); + else + meta_xwayland_selection_get_x11_data (compositor, selection); + + return TRUE; +} + +static void +meta_xwayland_selection_send_targets (MetaWaylandCompositor *compositor, + const MetaWaylandDataSource *data_source, + Window requestor, + Atom property) +{ + Atom *targets; + gchar **p; + int i = 0; + + if (!data_source) + return; + + if (data_source->mime_types.size == 0) + return; + + /* Make extra room for TIMESTAMP/TARGETS */ + targets = g_new (Atom, data_source->mime_types.size + 2); + + wl_array_for_each (p, &data_source->mime_types) + { + targets[i++] = gdk_x11_get_xatom_by_name (*p); + } + + targets[i++] = gdk_x11_get_xatom_by_name ("TIMESTAMP"); + targets[i++] = gdk_x11_get_xatom_by_name ("TARGETS"); + + XChangeProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + requestor, property, + XA_ATOM, 32, PropModeReplace, + (guchar *) targets, i); + + g_free (targets); +} + +static void +meta_xwayland_selection_send_timestamp (MetaWaylandCompositor *compositor, + Window requestor, + Atom property, + Time timestamp) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + + XChangeProperty (xdisplay, requestor, property, + XA_INTEGER, 32, + PropModeReplace, + (guchar *) ×tamp, 1); +} + +static void +meta_xwayland_selection_send_incr_chunk (MetaWaylandCompositor *compositor, + MetaSelectionBridge *selection) +{ + if (!selection->wayland_selection) + return; + + if (selection->wayland_selection->buffer_len > 0) + wayland_selection_update_x11_property (selection->wayland_selection); + else + wayland_selection_data_read (selection); +} + +static gboolean +handle_incr_chunk (MetaWaylandCompositor *compositor, + MetaSelectionBridge *selection, + XPropertyEvent *event) +{ + if (selection->x11_selection && + selection->x11_selection->incr && + event->window == selection->owner && + event->state == PropertyNewValue && + event->atom == gdk_x11_get_xatom_by_name ("_META_SELECTION")) + { + /* X11 to Wayland */ + meta_xwayland_selection_get_incr_chunk (compositor, selection); + return TRUE; + } + else if (selection->wayland_selection && + selection->wayland_selection->incr && + event->window == selection->window && + event->state == PropertyDelete && + event->atom == selection->wayland_selection->request_event.property) + { + /* Wayland to X11 */ + meta_xwayland_selection_send_incr_chunk (compositor, selection); + return TRUE; + } + + return FALSE; +} + +static gboolean +meta_xwayland_selection_handle_property_notify (MetaWaylandCompositor *compositor, + XEvent *xevent) +{ + MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data; + XPropertyEvent *event = (XPropertyEvent *) xevent; + + return handle_incr_chunk (compositor, &selection_data->clipboard, event); +} + +static gboolean +meta_xwayland_selection_handle_selection_request (MetaWaylandCompositor *compositor, + XEvent *xevent) +{ + XSelectionRequestEvent *event = (XSelectionRequestEvent *) xevent; + MetaWaylandDataSource *data_source; + MetaSelectionBridge *selection; + + selection = atom_to_selection_bridge (compositor, event->selection); + + if (!selection) + return FALSE; + + /* We must fetch from the currently active source, not the Xwayland one */ + data_source = data_device_get_active_source_for_atom (&compositor->seat->data_device, + selection->selection_atom); + if (!data_source) + return FALSE; + + g_clear_pointer (&selection->wayland_selection, + (GDestroyNotify) wayland_selection_data_free); + + if (event->target == gdk_x11_get_xatom_by_name ("TARGETS")) + { + meta_xwayland_selection_send_targets (compositor, + data_source, + event->requestor, + event->property); + reply_selection_request (event, TRUE); + } + else if (event->target == gdk_x11_get_xatom_by_name ("TIMESTAMP")) + { + meta_xwayland_selection_send_timestamp (compositor, + event->requestor, event->property, + selection->timestamp); + reply_selection_request (event, TRUE); + } + else + { + if (data_source && + meta_wayland_data_source_has_mime_type (data_source, + gdk_x11_get_xatom_name (event->target))) + { + selection->wayland_selection = wayland_selection_data_new (event, + compositor); + + if (selection->wayland_selection) + wayland_selection_data_read (selection); + } + + if (!selection->wayland_selection) + reply_selection_request (event, FALSE); + } + + return TRUE; +} + +static gboolean +meta_xwayland_selection_handle_xfixes_selection_notify (MetaWaylandCompositor *compositor, + XEvent *xevent) +{ + XFixesSelectionNotifyEvent *event = (XFixesSelectionNotifyEvent *) xevent; + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + MetaSelectionBridge *selection; + + selection = atom_to_selection_bridge (compositor, event->selection); + + if (!selection) + return FALSE; + + if (event->owner == None) + { + if (selection->source && selection->owner != selection->window) + { + /* An X client went away, clear the selection */ + if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD")) + { + meta_wayland_data_device_set_selection (&compositor->seat->data_device, NULL, + wl_display_next_serial (compositor->wayland_display)); + } + selection->source = NULL; + } + + selection->owner = None; + } + else + { + selection->owner = event->owner; + + if (selection->owner == selection->window) + { + /* This our own selection window */ + selection->timestamp = event->timestamp; + return TRUE; + } + + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); + + XConvertSelection (xdisplay, + event->selection, + gdk_x11_get_xatom_by_name ("TARGETS"), + gdk_x11_get_xatom_by_name ("_META_SELECTION"), + selection->window, + selection->timestamp); + XFlush (xdisplay); + } + + return TRUE; +} + +gboolean +meta_xwayland_selection_handle_event (XEvent *xevent) +{ + MetaWaylandCompositor *compositor; + + compositor = meta_wayland_compositor_get_default (); + + if (!compositor->xwayland_manager.selection_data) + return FALSE; + + switch (xevent->type) + { + case SelectionNotify: + return meta_xwayland_selection_handle_selection_notify (compositor, xevent); + case PropertyNotify: + return meta_xwayland_selection_handle_property_notify (compositor, xevent); + case SelectionRequest: + return meta_xwayland_selection_handle_selection_request (compositor, xevent); + default: + { + MetaDisplay *display = meta_get_display (); + + if (xevent->type - display->xfixes_event_base == XFixesSelectionNotify) + return meta_xwayland_selection_handle_xfixes_selection_notify (compositor, xevent); + + return FALSE; + } + } +} + +static void +meta_selection_bridge_ownership_notify (struct wl_listener *listener, + void *data) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + MetaSelectionBridge *selection = + wl_container_of (listener, selection, ownership_listener); + MetaWaylandDataSource *owner = data; + + if (!owner && selection->window == selection->owner) + { + XSetSelectionOwner (xdisplay, selection->selection_atom, + None, selection->timestamp); + } + else if (selection->source != owner) + { + XSetSelectionOwner (xdisplay, + selection->selection_atom, + selection->window, + CurrentTime); + } +} + +static void +init_selection_bridge (MetaSelectionBridge *selection, + Atom selection_atom, + struct wl_signal *signal) +{ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + XSetWindowAttributes attributes; + guint mask; + + attributes.event_mask = PropertyChangeMask; + + selection->ownership_listener.notify = meta_selection_bridge_ownership_notify; + wl_signal_add (signal, &selection->ownership_listener); + + selection->selection_atom = selection_atom; + selection->window = + XCreateWindow (xdisplay, + gdk_x11_window_get_xid (gdk_get_default_root_window ()), + -1, -1, 1, 1, /* position */ + 0, /* border width */ + 0, /* depth */ + InputOnly, /* class */ + CopyFromParent, /* visual */ + CWEventMask, + &attributes); + + mask = XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask; + + XFixesSelectSelectionInput (xdisplay, selection->window, + selection_atom, mask); +} + +static void +shutdown_selection_bridge (MetaSelectionBridge *selection) +{ + wl_list_remove (&selection->ownership_listener.link); + + XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + selection->window); + g_clear_pointer (&selection->wayland_selection, + (GDestroyNotify) wayland_selection_data_free); + g_clear_pointer (&selection->x11_selection, + (GDestroyNotify) x11_selection_data_free); +} + +void +meta_xwayland_init_selection (void) +{ + MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default (); + MetaXWaylandManager *manager = &compositor->xwayland_manager; + + g_assert (manager->selection_data == NULL); + + manager->selection_data = g_slice_new0 (MetaXWaylandSelection); + + init_selection_bridge (&manager->selection_data->clipboard, + gdk_x11_get_xatom_by_name ("CLIPBOARD"), + &compositor->seat->data_device.selection_ownership_signal); +} + +void +meta_xwayland_shutdown_selection (void) +{ + MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default (); + MetaXWaylandManager *manager = &compositor->xwayland_manager; + MetaXWaylandSelection *selection = manager->selection_data; + + g_assert (selection != NULL); + + if (selection->clipboard.source) + { + meta_wayland_data_device_set_selection (&compositor->seat->data_device, NULL, + wl_display_next_serial (compositor->wayland_display)); + } + + shutdown_selection_bridge (&selection->clipboard); + + g_slice_free (MetaXWaylandSelection, selection); + manager->selection_data = NULL; +} diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c index a7beb0c14..cb7072466 100644 --- a/src/wayland/meta-xwayland.c +++ b/src/wayland/meta-xwayland.c @@ -522,6 +522,8 @@ meta_xwayland_complete_init (void) we won't reset the tty). */ XSetIOErrorHandler (x_io_error); + + meta_xwayland_init_selection (); } void @@ -529,6 +531,8 @@ meta_xwayland_stop (MetaXWaylandManager *manager) { char path[256]; + meta_xwayland_shutdown_selection (); + snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index); unlink (path); diff --git a/src/x11/events.c b/src/x11/events.c index 5d5a5dcb5..f70838b12 100644 --- a/src/x11/events.c +++ b/src/x11/events.c @@ -39,6 +39,7 @@ #ifdef HAVE_WAYLAND #include "wayland/meta-xwayland.h" #include "wayland/meta-wayland-private.h" +#include "wayland/meta-xwayland-private.h" #endif static XIEvent * @@ -1676,6 +1677,15 @@ meta_display_handle_xevent (MetaDisplay *display, } #endif +#ifdef HAVE_WAYLAND + if (meta_is_wayland_compositor () && + meta_xwayland_selection_handle_event (event)) + { + bypass_gtk = bypass_compositor = TRUE; + goto out; + } +#endif + display->current_time = event_get_time (display, event); display->monitor_cache_invalidated = TRUE;