wayland: Add X11/wayland selection interoperation
This piece of code hooks in both wl_data_device and the relevant X selection events, an X11 Window is set up so it can act as the clipboard owner when any wayland client owns the selection, reacting to SelectionRequest events, and returning the data from the wayland client FD to any X11 requestor through X properties. In the opposite direction, SelectionNotify messages are received, which results in the property contents being converted then written into the wayland requestor's FD. This code also takes care of the handling incremental transfers through the INCR property type, reading/writing data chunk by chunk. https://bugzilla.gnome.org/show_bug.cgi?id=738312
This commit is contained in:
parent
4b5f5abb4f
commit
4fc1811c15
@ -243,6 +243,7 @@ libmutter_la_SOURCES += \
|
|||||||
wayland/meta-wayland-private.h \
|
wayland/meta-wayland-private.h \
|
||||||
wayland/meta-xwayland.c \
|
wayland/meta-xwayland.c \
|
||||||
wayland/meta-xwayland.h \
|
wayland/meta-xwayland.h \
|
||||||
|
wayland/meta-xwayland-selection.c \
|
||||||
wayland/meta-xwayland-private.h \
|
wayland/meta-xwayland-private.h \
|
||||||
wayland/meta-wayland-buffer.c \
|
wayland/meta-wayland-buffer.c \
|
||||||
wayland/meta-wayland-buffer.h \
|
wayland/meta-wayland-buffer.h \
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
#include "meta-wayland-surface.h"
|
#include "meta-wayland-surface.h"
|
||||||
#include "meta-wayland-seat.h"
|
#include "meta-wayland-seat.h"
|
||||||
|
|
||||||
|
typedef struct _MetaXWaylandSelection MetaXWaylandSelection;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
struct wl_list link;
|
struct wl_list link;
|
||||||
@ -52,6 +54,8 @@ typedef struct
|
|||||||
char *display_name;
|
char *display_name;
|
||||||
|
|
||||||
GMainLoop *init_loop;
|
GMainLoop *init_loop;
|
||||||
|
|
||||||
|
MetaXWaylandSelection *selection_data;
|
||||||
} MetaXWaylandManager;
|
} MetaXWaylandManager;
|
||||||
|
|
||||||
struct _MetaWaylandCompositor
|
struct _MetaWaylandCompositor
|
||||||
|
@ -34,4 +34,9 @@ meta_xwayland_complete_init (void);
|
|||||||
void
|
void
|
||||||
meta_xwayland_stop (MetaXWaylandManager *manager);
|
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 */
|
#endif /* META_XWAYLAND_PRIVATE_H */
|
||||||
|
944
src/wayland/meta-xwayland-selection.c
Normal file
944
src/wayland/meta-xwayland-selection.c
Normal file
@ -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 <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <glib-unix.h>
|
||||||
|
#include <gio/gunixoutputstream.h>
|
||||||
|
#include <gio/gunixinputstream.h>
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <meta/errors.h>
|
||||||
|
#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;
|
||||||
|
}
|
@ -522,6 +522,8 @@ meta_xwayland_complete_init (void)
|
|||||||
we won't reset the tty).
|
we won't reset the tty).
|
||||||
*/
|
*/
|
||||||
XSetIOErrorHandler (x_io_error);
|
XSetIOErrorHandler (x_io_error);
|
||||||
|
|
||||||
|
meta_xwayland_init_selection ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -529,6 +531,8 @@ meta_xwayland_stop (MetaXWaylandManager *manager)
|
|||||||
{
|
{
|
||||||
char path[256];
|
char path[256];
|
||||||
|
|
||||||
|
meta_xwayland_shutdown_selection ();
|
||||||
|
|
||||||
snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index);
|
snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index);
|
||||||
unlink (path);
|
unlink (path);
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
#ifdef HAVE_WAYLAND
|
#ifdef HAVE_WAYLAND
|
||||||
#include "wayland/meta-xwayland.h"
|
#include "wayland/meta-xwayland.h"
|
||||||
#include "wayland/meta-wayland-private.h"
|
#include "wayland/meta-wayland-private.h"
|
||||||
|
#include "wayland/meta-xwayland-private.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static XIEvent *
|
static XIEvent *
|
||||||
@ -1676,6 +1677,15 @@ meta_display_handle_xevent (MetaDisplay *display,
|
|||||||
}
|
}
|
||||||
#endif
|
#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->current_time = event_get_time (display, event);
|
||||||
display->monitor_cache_invalidated = TRUE;
|
display->monitor_cache_invalidated = TRUE;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user