diff --git a/src/meson.build b/src/meson.build index b5e8835ab..be4c5f27d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -385,6 +385,8 @@ mutter_sources = [ 'x11/meta-x11-display.c', 'x11/meta-x11-display-private.h', 'x11/meta-x11-errors.c', + 'x11/meta-x11-selection.c', + 'x11/meta-x11-selection-private.h', 'x11/meta-x11-selection-input-stream.c', 'x11/meta-x11-selection-input-stream-private.h', 'x11/meta-x11-selection-output-stream.c', diff --git a/src/x11/events.c b/src/x11/events.c index 1eec8db0b..e925e482e 100644 --- a/src/x11/events.c +++ b/src/x11/events.c @@ -40,6 +40,7 @@ #include "meta/meta-x11-errors.h" #include "x11/meta-startup-notification-x11.h" #include "x11/meta-x11-display-private.h" +#include "x11/meta-x11-selection-private.h" #include "x11/meta-x11-selection-input-stream-private.h" #include "x11/meta-x11-selection-output-stream-private.h" #include "x11/window-x11.h" @@ -1726,6 +1727,8 @@ process_selection_event (MetaX11Display *x11_display, gboolean handled = FALSE; GList *l; + handled |= meta_x11_selection_handle_event (x11_display, event); + for (l = x11_display->selection.input_streams; l && !handled; l = l->next) handled |= meta_x11_selection_input_stream_xevent (l->data, event); diff --git a/src/x11/meta-x11-display-private.h b/src/x11/meta-x11-display-private.h index 30c969242..cf6bd4082 100644 --- a/src/x11/meta-x11-display-private.h +++ b/src/x11/meta-x11-display-private.h @@ -30,6 +30,7 @@ #include "backends/meta-monitor-manager-private.h" #include "core/display-private.h" +#include "core/meta-selection-source.h" #include "meta/common.h" #include "meta/types.h" #include "meta/meta-x11-display.h" @@ -125,6 +126,9 @@ struct _MetaX11Display struct { Window xwindow; + MetaSelectionSource *owners[META_N_SELECTION_TYPES]; + GCancellable *cancellables[META_N_SELECTION_TYPES]; + GList *input_streams; GList *output_streams; } selection; diff --git a/src/x11/meta-x11-display.c b/src/x11/meta-x11-display.c index 8ce12b994..af8ae7a8d 100644 --- a/src/x11/meta-x11-display.c +++ b/src/x11/meta-x11-display.c @@ -61,6 +61,7 @@ #include "x11/events.h" #include "x11/group-props.h" +#include "x11/meta-x11-selection-private.h" #include "x11/window-props.h" #include "x11/xprops.h" @@ -105,6 +106,8 @@ meta_x11_display_dispose (GObject *object) meta_x11_display_ungrab_keys (x11_display); + meta_x11_selection_shutdown (x11_display); + if (x11_display->ui) { meta_ui_free (x11_display->ui); @@ -1327,6 +1330,7 @@ meta_x11_display_new (MetaDisplay *display, GError **error) set_x11_bell_is_audible (x11_display, meta_prefs_bell_is_audible ()); meta_x11_startup_notification_init (x11_display); + meta_x11_selection_init (x11_display); return x11_display; } diff --git a/src/x11/meta-x11-selection-private.h b/src/x11/meta-x11-selection-private.h new file mode 100644 index 000000000..9e42751bb --- /dev/null +++ b/src/x11/meta-x11-selection-private.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#ifndef META_X11_SELECTION_H +#define META_X11_SELECTION_H + +#include "core/meta-selection.h" +#include "x11/meta-x11-display-private.h" + +gboolean meta_x11_selection_handle_event (MetaX11Display *display, + XEvent *event); + +void meta_x11_selection_init (MetaX11Display *x11_display); +void meta_x11_selection_shutdown (MetaX11Display *x11_display); + +#endif /* META_X11_SELECTION_H */ diff --git a/src/x11/meta-x11-selection.c b/src/x11/meta-x11-selection.c new file mode 100644 index 000000000..5b87c424c --- /dev/null +++ b/src/x11/meta-x11-selection.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#include "config.h" + +#include + +#include "x11/meta-selection-source-x11-private.h" +#include "x11/meta-x11-selection-output-stream-private.h" +#include "x11/meta-x11-selection-private.h" + +static gboolean +atom_to_selection_type (Display *xdisplay, + Atom selection, + MetaSelectionType *selection_type) +{ + if (selection == XInternAtom (xdisplay, "PRIMARY", False)) + *selection_type = META_SELECTION_PRIMARY; + else if (selection == XInternAtom (xdisplay, "CLIPBOARD", False)) + *selection_type = META_SELECTION_CLIPBOARD; + else if (selection == XInternAtom (xdisplay, "XdndSelection", False)) + *selection_type = META_SELECTION_DND; + else + return FALSE; + + return TRUE; +} + +static Atom +selection_to_atom (MetaSelectionType type, + Display *xdisplay) +{ + Atom atom; + + switch (type) + { + case META_SELECTION_PRIMARY: + atom = XInternAtom (xdisplay, "PRIMARY", False); + break; + case META_SELECTION_CLIPBOARD: + atom = XInternAtom (xdisplay, "CLIPBOARD", False); + break; + case META_SELECTION_DND: + atom = XInternAtom (xdisplay, "XdndSelection", False); + break; + default: + g_warn_if_reached (); + atom = None; + break; + } + + return atom; +} + +static GBytes * +mimetypes_to_bytes (GList *mimetypes, + Display *xdisplay) +{ + gint i = 0, len = g_list_length (mimetypes) + 2; + Atom *atoms = g_new0 (Atom, len); + GList *l; + + for (l = mimetypes; l; l = l->next) + atoms[i++] = XInternAtom (xdisplay, l->data, False); + + atoms[i++] = XInternAtom (xdisplay, "TARGETS", False); + atoms[i++] = XInternAtom (xdisplay, "TIMESTAMP", False); + g_assert (i == len); + + return g_bytes_new_take (atoms, len * sizeof (Atom)); +} + +static void +send_selection_notify (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 +write_mimetypes_cb (GOutputStream *stream, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + + g_output_stream_write_bytes_finish (stream, res, &error); + g_output_stream_close (stream, NULL, NULL); + + if (error) + { + g_warning ("Could not fetch selection mimetypes: %s\n", error->message); + g_error_free (error); + } +} + +static void +transfer_cb (MetaSelection *selection, + GAsyncResult *res, + GOutputStream *output) +{ + GError *error = NULL; + + if (!meta_selection_transfer_finish (selection, res, &error)) + { + g_warning ("Error writing data to X11 selection: %s", error->message); + g_error_free (error); + } + + g_output_stream_close (output, NULL, NULL); + g_object_unref (output); +} + +static gboolean +meta_x11_selection_handle_selection_request (MetaX11Display *x11_display, + XEvent *xevent) +{ + XSelectionRequestEvent *event = (XSelectionRequestEvent *) xevent; + MetaSelectionType selection_type; + MetaSelection *selection; + GOutputStream *output; + GList *mimetypes; + + if (!atom_to_selection_type (x11_display->xdisplay, event->selection, &selection_type)) + return FALSE; + if (x11_display->selection.xwindow != event->owner) + return FALSE; + + selection = meta_display_get_selection (meta_get_display ()); + + if (event->target == gdk_x11_get_xatom_by_name ("TARGETS")) + { + GBytes *bytes; + + mimetypes = meta_selection_get_mimetypes (selection, selection_type); + + if (!mimetypes) + { + send_selection_notify (event, FALSE); + return FALSE; + } + + output = meta_x11_selection_output_stream_new (x11_display, event->requestor, + gdk_x11_get_xatom_name (event->selection), + gdk_x11_get_xatom_name (event->target), + gdk_x11_get_xatom_name (event->property), + "ATOM", 32, event->time); + + bytes = mimetypes_to_bytes (mimetypes, x11_display->xdisplay); + g_list_free_full (mimetypes, g_free); + + g_output_stream_write_bytes_async (output, + bytes, + G_PRIORITY_DEFAULT, + NULL, + (GAsyncReadyCallback) write_mimetypes_cb, + output); + g_bytes_unref (bytes); + return TRUE; + } + else if (event->target == gdk_x11_get_xatom_by_name ("DELETE")) + { + /* DnD only, this is just handled through other means on our non-x11 + * sources, so just go with it. + */ + send_selection_notify (event, TRUE); + } + else + { + gboolean has_target; + + mimetypes = meta_selection_get_mimetypes (selection, selection_type); + has_target = g_list_find_custom (mimetypes, + gdk_x11_get_xatom_name (event->target), + (GCompareFunc) g_strcmp0) != NULL; + g_list_free_full (mimetypes, g_free); + + if (has_target) + { + output = meta_x11_selection_output_stream_new (x11_display, + event->requestor, + gdk_x11_get_xatom_name (event->selection), + gdk_x11_get_xatom_name (event->target), + gdk_x11_get_xatom_name (event->property), + gdk_x11_get_xatom_name (event->target), + 8, event->time); + + meta_selection_transfer_async (selection, + selection_type, + gdk_x11_get_xatom_name (event->target), + -1, + output, + NULL, + (GAsyncReadyCallback) transfer_cb, + output); + return TRUE; + } + else + send_selection_notify (event, FALSE); + } + + return FALSE; +} + +typedef struct +{ + MetaX11Display *x11_display; + MetaSelection *selection; + MetaSelectionType selection_type; +} SourceNewData; + +static void +source_new_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + MetaSelectionSource *source; + SourceNewData *data = user_data; + MetaSelection *selection = data->selection; + MetaSelectionType selection_type = data->selection_type; + MetaX11Display *x11_display = data->x11_display; + GError *error = NULL; + + source = meta_selection_source_x11_new_finish (res, &error); + if (source) + { + meta_selection_set_owner (selection, selection_type, source); + g_set_object (&x11_display->selection.owners[selection_type], source); + } + else + { + g_warning ("Could not create selection source for X11: %s", + error->message); + } + + g_free (data); +} + +static gboolean +meta_x11_selection_handle_xfixes_selection_notify (MetaX11Display *x11_display, + XEvent *xevent) +{ + XFixesSelectionNotifyEvent *event = (XFixesSelectionNotifyEvent *) xevent; + Display *xdisplay = x11_display->xdisplay; + MetaSelectionType selection_type; + MetaSelection *selection; + + if (!atom_to_selection_type (xdisplay, event->selection, &selection_type)) + return FALSE; + + selection = meta_display_get_selection (meta_get_display ()); + + if (x11_display->selection.cancellables[selection_type]) + { + g_cancellable_cancel (x11_display->selection.cancellables[selection_type]); + g_clear_object (&x11_display->selection.cancellables[selection_type]); + } + + x11_display->selection.cancellables[selection_type] = g_cancellable_new (); + + if (event->owner == None) + { + if (x11_display->selection.owners[selection_type]) + { + /* An X client went away, clear the selection */ + meta_selection_unset_owner (selection, selection_type, + x11_display->selection.owners[selection_type]); + } + } + else if (event->owner != x11_display->selection.xwindow) + { + SourceNewData *data; + + data = g_new (SourceNewData, 1); + data->x11_display = x11_display; + data->selection = selection; + data->selection_type = selection_type; + + meta_selection_source_x11_new_async (x11_display, + event->owner, + event->timestamp, + event->selection, + x11_display->selection.cancellables[selection_type], + source_new_cb, + data); + } + + return TRUE; +} + +gboolean +meta_x11_selection_handle_event (MetaX11Display *x11_display, + XEvent *xevent) +{ + if (xevent->type == SelectionRequest) + return meta_x11_selection_handle_selection_request (x11_display, xevent); + else if (xevent->type - x11_display->xfixes_event_base == XFixesSelectionNotify) + return meta_x11_selection_handle_xfixes_selection_notify (x11_display, xevent); + + return FALSE; +} + +static void +owner_changed_cb (MetaSelection *selection, + MetaSelectionType selection_type, + MetaSelectionSource *new_owner, + MetaX11Display *x11_display) +{ + Display *xdisplay = x11_display->xdisplay; + + if (new_owner && !META_IS_SELECTION_SOURCE_X11 (new_owner)) + { + if (x11_display->selection.cancellables[selection_type]) + { + g_cancellable_cancel (x11_display->selection.cancellables[selection_type]); + g_clear_object (&x11_display->selection.cancellables[selection_type]); + } + + /* If the owner is non-X11, claim the selection on our selection + * window, so X11 apps can interface with it. + */ + XSetSelectionOwner (xdisplay, + selection_to_atom (selection_type, xdisplay), + x11_display->selection.xwindow, + META_CURRENT_TIME); + } +} + +void +meta_x11_selection_init (MetaX11Display *x11_display) +{ + XSetWindowAttributes attributes = { 0 }; + MetaDisplay *display = meta_get_display (); + guint mask, i; + + attributes.event_mask = PropertyChangeMask | SubstructureNotifyMask; + attributes.override_redirect = True; + + x11_display->selection.xwindow = + XCreateWindow (x11_display->xdisplay, + x11_display->xroot, + -1, -1, 1, 1, + 0, /* border width */ + 0, /* depth */ + InputOnly, /* class */ + CopyFromParent, /* visual */ + CWEventMask | CWOverrideRedirect, + &attributes); + + mask = XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask; + + for (i = 0; i < META_N_SELECTION_TYPES; i++) + { + XFixesSelectSelectionInput (x11_display->xdisplay, + x11_display->selection.xwindow, + selection_to_atom (i, x11_display->xdisplay), + mask); + } + + g_signal_connect (meta_display_get_selection (display), + "owner-changed", + G_CALLBACK (owner_changed_cb), + x11_display); +} + +void +meta_x11_selection_shutdown (MetaX11Display *x11_display) +{ + MetaDisplay *display = meta_get_display (); + + g_signal_handlers_disconnect_by_func (meta_display_get_selection (display), + owner_changed_cb, + x11_display); + if (x11_display->selection.xwindow != None) + { + XDestroyWindow (x11_display->xdisplay, x11_display->selection.xwindow); + x11_display->selection.xwindow = None; + } +}