]> git.eshelyaron.com Git - emacs.git/commitdiff
Rewrite PGTK selection code from scratch
authorPo Lu <luangruo@yahoo.com>
Tue, 21 Jun 2022 14:03:42 +0000 (22:03 +0800)
committerPo Lu <luangruo@yahoo.com>
Tue, 21 Jun 2022 14:05:21 +0000 (22:05 +0800)
* src/frame.c (delete_frame): Clear selections and swallow
special events.

* src/keyboard.c (kbd_buffer_get_event, process_special_events):
Also handle selection events on PGTK.

* src/keyboard.h (union buffered_input_event): Include selection
events on PGTK.

* src/pgtkselect.c (symbol_to_gtk_clipboard, LOCAL_SELECTION):
New functions and macros.
(selection_type_to_quarks, get_func, clear_func): Delete
functions.
(pgtk_selection_init, pgtk_selection_lost):
(pgtk_selection_usable): New functions.
(Fpgtk_own_selection_internal, Fpgtk_disown_selection_internal)
(Fpgtk_selection_exists_p, Fpgtk_selection_owner_p)
(Fpgtk_get_selection_internal): Complete rewrite.
(syms_of_pgtkselect): Update defsyms and add more hooks.

* src/pgtkselect.h: Delete file.

* src/pgtkterm.c (evq_enqueue): Set last user time based on the
event.
(pgtk_any_window_to_frame, button_event): Fix coding style.
(pgtk_set_event_handler): Add selection events.
(pgtk_find_selection_owner, pgtk_selection_event): New
functions.
(pgtk_term_init): Remove call to `pgtk_selection_init'.

* src/pgtkterm.h (struct pgtk_display_info): New field
`display'.
(enum selection_input_event): New struct.  New macros for
accessing its fields.

src/frame.c
src/keyboard.c
src/keyboard.h
src/pgtkselect.c
src/pgtkselect.h [deleted file]
src/pgtkterm.c
src/pgtkterm.h

index c21461d49fe00e565cebccdcd7f264dfc6c731a5..c2f2f8e464211885f8afff49d07d1289cc414e39 100644 (file)
@@ -2176,6 +2176,17 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
     x_clear_frame_selections (f);
 #endif
 
+#ifdef HAVE_PGTK
+  if (FRAME_PGTK_P (f))
+    {
+      /* Do special selection events now, in case the window gets
+        destroyed by this deletion.  Does this run Lisp code?  */
+      swallow_events (false);
+
+      pgtk_clear_frame_selections (f);
+    }
+#endif
+
   /* Free glyphs.
      This function must be called before the window tree of the
      frame is deleted because windows contain dynamically allocated
index c41727d6c6e4544e683aade4c54c38a0c4f9a207..6bc2afd40acdcaafaef3d98c3a35a389650be398 100644 (file)
@@ -4003,14 +4003,19 @@ kbd_buffer_get_event (KBOARD **kbp,
       case SELECTION_REQUEST_EVENT:
       case SELECTION_CLEAR_EVENT:
        {
-#ifdef HAVE_X11
+#if defined HAVE_X11 || HAVE_PGTK
          /* Remove it from the buffer before processing it,
             since otherwise swallow_events will see it
             and process it again.  */
          struct selection_input_event copy = event->sie;
          kbd_fetch_ptr = next_kbd_event (event);
          input_pending = readable_events (0);
+
+#ifdef HAVE_X11
          x_handle_selection_event (&copy);
+#else
+         pgtk_handle_selection_event (&copy);
+#endif
 #else
          /* We're getting selection request events, but we don't have
              a window system.  */
@@ -4381,7 +4386,7 @@ process_special_events (void)
       if (event->kind == SELECTION_REQUEST_EVENT
          || event->kind == SELECTION_CLEAR_EVENT)
        {
-#ifdef HAVE_X11
+#if defined HAVE_X11 || defined HAVE_PGTK
 
          /* Remove the event from the fifo buffer before processing;
             otherwise swallow_events called recursively could see it
@@ -4406,7 +4411,12 @@ process_special_events (void)
                   moved_events * sizeof *kbd_fetch_ptr);
          kbd_fetch_ptr = next_kbd_event (kbd_fetch_ptr);
          input_pending = readable_events (0);
+
+#ifdef HAVE_X11
          x_handle_selection_event (&copy);
+#else
+         pgtk_handle_selection_event (&copy);
+#endif
 #else
          /* We're getting selection request events, but we don't have
              a window system.  */
index 6ae2dc9c4c65caa4ddae77e6556575e3457e3fd8..507d80c2975cb2e6f68c4ba79fde0d77ebe6aa87 100644 (file)
@@ -27,6 +27,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 # include "xterm.h"            /* for struct selection_input_event */
 #endif
 
+#ifdef HAVE_PGTK
+#include "pgtkterm.h"          /* for struct selection_input_event */
+#endif
+
 INLINE_HEADER_BEGIN
 
 /* Most code should use this macro to access Lisp fields in struct kboard.  */
@@ -226,7 +230,7 @@ union buffered_input_event
 {
   ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH;
   struct input_event ie;
-#ifdef HAVE_X11
+#if defined HAVE_X11 || defined HAVE_PGTK
   struct selection_input_event sie;
 #endif
 };
index 76901b9eb1de80b6e82f117560a0c0932503be40..a0168c9fad8d7b2ed84c4e61014697f81d720d60 100644 (file)
@@ -17,16 +17,6 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
-/* FIXME: this file needs a major rewrite to replace the use of GTK's
-   own high-level GtkClipboard API with the GDK selection API:
-
-   https://developer-old.gnome.org/gdk3/stable/gdk3-Selections.html
-
-   That way, most of the code can be shared with X, and non-text
-   targets along with drag-and-drop can be supported.  GDK implements
-   selections according to the ICCCM, as on X, but its selection API
-   will work on any supported window system.  */
-
 /* This should be the first include, as it may set up #defines affecting
    interpretation of even the system includes.  */
 #include <config.h>
@@ -35,21 +25,37 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "pgtkterm.h"
 #include "termhooks.h"
 #include "keyboard.h"
-#include "pgtkselect.h"
-#include <gdk/gdk.h>
-
-static GQuark quark_primary_data = 0;
-static GQuark quark_primary_size = 0;
-static GQuark quark_secondary_data = 0;
-static GQuark quark_secondary_size = 0;
-static GQuark quark_clipboard_data = 0;
-static GQuark quark_clipboard_size = 0;
-
-/* ==========================================================================
-
-    Internal utility functions
-
-   ========================================================================== */
+#include "atimer.h"
+#include "blockinput.h"
+
+/* This file deliberately does not implement INCR, since it adds a
+   bunch of extra code for no real gain, as PGTK isn't supposed to
+   support X11 anyway.  */
+
+/* Advance declaration of structs.  */
+struct selection_data;
+struct prop_location;
+
+static void pgtk_decline_selection_request (struct selection_input_event *);
+static bool pgtk_convert_selection (Lisp_Object, Lisp_Object, GdkAtom, bool,
+                                   struct pgtk_display_info *);
+static bool waiting_for_other_props_on_window (GdkDisplay *, GdkWindow *);
+#if 0
+static struct prop_location *expect_property_change (GdkDisplay *, GdkWindow *,
+                                                     GdkAtom, int);
+#endif
+static void unexpect_property_change (struct prop_location *);
+static void wait_for_property_change (struct prop_location *);
+static Lisp_Object pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *,
+                                                         GdkWindow *, GdkAtom,
+                                                         Lisp_Object, GdkAtom, bool);
+static Lisp_Object selection_data_to_lisp_data (struct pgtk_display_info *,
+                                               const unsigned char *,
+                                               ptrdiff_t, GdkAtom, int);
+static void lisp_data_to_selection_data (struct pgtk_display_info *, Lisp_Object,
+                                        struct selection_data *);
+static Lisp_Object pgtk_get_local_selection (Lisp_Object, Lisp_Object,
+                                            bool, struct pgtk_display_info *);
 
 /* From a Lisp_Object, return a suitable frame for selection
    operations.  OBJECT may be a frame, a terminal object, or nil
@@ -98,398 +104,1662 @@ frame_for_pgtk_selection (Lisp_Object object)
   return NULL;
 }
 
-static GtkClipboard *
-symbol_to_gtk_clipboard (GtkWidget * widget, Lisp_Object symbol)
+#define LOCAL_SELECTION(selection_symbol, dpyinfo)                     \
+  assq_no_quit (selection_symbol, dpyinfo->terminal->Vselection_alist)
+
+static GdkAtom
+symbol_to_gdk_atom (Lisp_Object sym)
 {
-  GdkAtom atom;
+  if (NILP (sym))
+    return GDK_NONE;
 
-  CHECK_SYMBOL (symbol);
-  if (NILP (symbol))
-    {
-      atom = GDK_SELECTION_PRIMARY;
-    }
-  else if (EQ (symbol, QCLIPBOARD))
+  if (EQ (sym, QPRIMARY))
+    return GDK_SELECTION_PRIMARY;
+  if (EQ (sym, QSECONDARY))
+    return GDK_SELECTION_SECONDARY;
+  if (EQ (sym, QCLIPBOARD))
+    return GDK_SELECTION_CLIPBOARD;
+
+  if (!SYMBOLP (sym))
+    emacs_abort ();
+
+  return gdk_atom_intern (SSDATA (SYMBOL_NAME (sym)), FALSE);
+}
+
+static Lisp_Object
+gdk_atom_to_symbol (GdkAtom atom)
+{
+  return intern (gdk_atom_name (atom));
+}
+
+\f
+
+/* Do protocol to assert ourself as a selection owner.
+   FRAME shall be the owner; it must be a valid GDK frame.
+   Update the Vselection_alist so that we can reply to later requests for
+   our selection.  */
+
+static void
+pgtk_own_selection (Lisp_Object selection_name, Lisp_Object selection_value,
+                   Lisp_Object frame)
+{
+  struct frame *f = XFRAME (frame);
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  guint32 timestamp = gtk_get_current_event_time ();
+  GdkAtom selection_atom = symbol_to_gdk_atom (selection_name);
+  Lisp_Object targets;
+  ptrdiff_t i, ntargets;
+  GtkTargetEntry *gtargets;
+
+  if (timestamp == GDK_CURRENT_TIME)
+    timestamp = dpyinfo->last_user_time;
+
+  /* Assert ownership over the selection.  Ideally we would use the
+     GDK selection API for this as well, but it just doesn't work on
+     Wayland.  */
+
+  if (!gdk_selection_owner_set_for_display (dpyinfo->display,
+                                           FRAME_GDK_WINDOW (f),
+                                           selection_atom,
+                                           timestamp, TRUE))
+    signal_error ("Could not assert ownership over selection", selection_name);
+
+  /* Update the local cache */
+  {
+    Lisp_Object selection_data;
+    Lisp_Object prev_value;
+
+    selection_data = list4 (selection_name, selection_value,
+                           INT_TO_INTEGER (timestamp), frame);
+    prev_value = LOCAL_SELECTION (selection_name, dpyinfo);
+
+    tset_selection_alist
+      (dpyinfo->terminal,
+       Fcons (selection_data, dpyinfo->terminal->Vselection_alist));
+
+    /* If we already owned the selection, remove the old selection
+       data.  Don't use Fdelq as that may quit.  */
+    if (!NILP (prev_value))
+      {
+       /* We know it's not the CAR, so it's easy.  */
+       Lisp_Object rest = dpyinfo->terminal->Vselection_alist;
+       for (; CONSP (rest); rest = XCDR (rest))
+         if (EQ (prev_value, Fcar (XCDR (rest))))
+           {
+             XSETCDR (rest, XCDR (XCDR (rest)));
+             break;
+           }
+      }
+  }
+
+  /* Announce the targets to the display server.  This isn't required
+     on X, but is on Wayland.  */
+
+  targets = pgtk_get_local_selection (selection_name, QTARGETS,
+                                     true, dpyinfo);
+
+  /* GC must not happen inside this segment.  */
+  block_input ();
+  gtk_selection_clear_targets (FRAME_GTK_WIDGET (f), selection_atom);
+
+  if (VECTORP (targets))
     {
-      atom = GDK_SELECTION_CLIPBOARD;
+      gtargets = xzalloc (sizeof *gtargets * ASIZE (targets));
+      ntargets = 0;
+
+      for (i = 0; i < ASIZE (targets); ++i)
+       {
+         if (SYMBOLP (AREF (targets, i)))
+           gtargets[ntargets++].target
+             = SSDATA (SYMBOL_NAME (AREF (targets, i)));
+       }
+
+      gtk_selection_add_targets (FRAME_GTK_WIDGET (f),
+                                selection_atom, gtargets,
+                                ntargets);
+
+      xfree (gtargets);
     }
-  else if (EQ (symbol, QPRIMARY))
+  unblock_input ();
+}
+
+static Lisp_Object
+pgtk_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type,
+                         bool local_request, struct pgtk_display_info *dpyinfo)
+{
+  Lisp_Object local_value, tem;
+  Lisp_Object handler_fn, value, check;
+
+  local_value = LOCAL_SELECTION (selection_symbol, dpyinfo);
+
+  if (NILP (local_value)) return Qnil;
+
+  /* TIMESTAMP is a special case.  */
+  if (EQ (target_type, QTIMESTAMP))
     {
-      atom = GDK_SELECTION_PRIMARY;
+      handler_fn = Qnil;
+      value = XCAR (XCDR (XCDR (local_value)));
     }
-  else if (EQ (symbol, QSECONDARY))
+  else
     {
-      atom = GDK_SELECTION_SECONDARY;
+      /* Don't allow a quit within the converter.
+        When the user types C-g, he would be surprised
+        if by luck it came during a converter.  */
+      specpdl_ref count = SPECPDL_INDEX ();
+      specbind (Qinhibit_quit, Qt);
+
+      CHECK_SYMBOL (target_type);
+      handler_fn = Fcdr (Fassq (target_type, Vselection_converter_alist));
+
+      if (CONSP (handler_fn))
+       handler_fn = XCDR (handler_fn);
+
+      tem = XCAR (XCDR (local_value));
+
+      if (STRINGP (tem))
+       {
+         local_value = Fget_text_property (make_fixnum (0),
+                                           target_type, tem);
+
+         if (!NILP (local_value))
+           tem = local_value;
+       }
+
+      if (!NILP (handler_fn))
+       value = call3 (handler_fn, selection_symbol,
+                      (local_request
+                       ? Qnil
+                       : target_type),
+                      tem);
+      else
+       value = Qnil;
+      value = unbind_to (count, value);
     }
-  else if (EQ (symbol, Qt))
+
+  /* Make sure this value is of a type that we could transmit
+     to another client.  */
+
+  check = value;
+  if (CONSP (value)
+      && SYMBOLP (XCAR (value)))
+    check = XCDR (value);
+
+  if (STRINGP (check)
+      || VECTORP (check)
+      || SYMBOLP (check)
+      || INTEGERP (check)
+      || NILP (value))
+    return value;
+  /* Check for a value that CONS_TO_INTEGER could handle.  */
+  else if (CONSP (check)
+          && INTEGERP (XCAR (check))
+          && (INTEGERP (XCDR (check))
+              ||
+              (CONSP (XCDR (check))
+               && INTEGERP (XCAR (XCDR (check)))
+               && NILP (XCDR (XCDR (check))))))
+    return value;
+
+  signal_error ("Invalid data returned by selection-conversion function",
+               list2 (handler_fn, value));
+}
+
+static void
+pgtk_decline_selection_request (struct selection_input_event *event)
+{
+  gdk_selection_send_notify (SELECTION_EVENT_REQUESTOR (event),
+                            SELECTION_EVENT_SELECTION (event),
+                            SELECTION_EVENT_TARGET (event),
+                            GDK_NONE, SELECTION_EVENT_TIME (event));
+}
+
+struct selection_data
+{
+  unsigned char *data;
+  ptrdiff_t size;
+  int format;
+  GdkAtom type;
+  bool nofree;
+  GdkAtom property;
+
+  /* This can be set to non-NULL during x_reply_selection_request, if
+     the selection is waiting for an INCR transfer to complete.  Don't
+     free these; that's done by unexpect_property_change.  */
+  struct prop_location *wait_object;
+  struct selection_data *next;
+};
+
+struct pgtk_selection_request
+{
+  /* The last element in this stack.  */
+  struct pgtk_selection_request *last;
+
+  /* Its display info.  */
+  struct pgtk_display_info *dpyinfo;
+
+  /* Its selection input event.  */
+  struct selection_input_event *request;
+
+  /* Linked list of the above (in support of MULTIPLE targets).  */
+  struct selection_data *converted_selections;
+
+  /* "Data" to send a requestor for a failed MULTIPLE subtarget.  */
+  GdkAtom conversion_fail_tag;
+
+  /* Whether or not conversion was successful.  */
+  bool converted;
+};
+
+/* Stack of selections currently being processed.
+   NULL if all requests have been fully processed.  */
+
+struct pgtk_selection_request *selection_request_stack;
+
+static void
+pgtk_push_current_selection_request (struct selection_input_event *se,
+                                    struct pgtk_display_info *dpyinfo)
+{
+  struct pgtk_selection_request *frame;
+
+  frame = xmalloc (sizeof *frame);
+  frame->converted = false;
+  frame->last = selection_request_stack;
+  frame->request = se;
+  frame->dpyinfo = dpyinfo;
+  frame->converted_selections = NULL;
+  frame->conversion_fail_tag = GDK_NONE;
+
+  selection_request_stack = frame;
+}
+
+static void
+pgtk_pop_current_selection_request (void)
+{
+  struct pgtk_selection_request *tem;
+
+  tem = selection_request_stack;
+  selection_request_stack = selection_request_stack->last;
+
+  xfree (tem);
+}
+
+/* Used as an unwind-protect clause so that, if a selection-converter signals
+   an error, we tell the requestor that we were unable to do what they wanted
+   before we throw to top-level or go into the debugger or whatever.  */
+
+static void
+pgtk_selection_request_lisp_error (void)
+{
+  struct selection_data *cs, *next;
+  struct pgtk_selection_request *frame;
+
+  frame = selection_request_stack;
+
+  for (cs = frame->converted_selections; cs; cs = next)
     {
-      atom = GDK_SELECTION_SECONDARY;
+      next = cs->next;
+      if (! cs->nofree && cs->data)
+       xfree (cs->data);
+      xfree (cs);
     }
-  else
+  frame->converted_selections = NULL;
+
+  if (!frame->converted && frame->dpyinfo->display)
+    pgtk_decline_selection_request (frame->request);
+}
+
+/* This stuff is so that INCR selections are reentrant (that is, so we can
+   be servicing multiple INCR selection requests simultaneously.)  I haven't
+   actually tested that yet.  */
+
+/* Keep a list of the property changes that are awaited.  */
+
+struct prop_location
+{
+  int identifier;
+  GdkDisplay *display;
+  GdkWindow *window;
+  GdkAtom property;
+  int desired_state;
+  bool arrived;
+  struct prop_location *next;
+};
+
+#if 0
+
+static int prop_location_identifier;
+
+#endif
+
+static Lisp_Object property_change_reply;
+
+static struct prop_location *property_change_reply_object;
+
+static struct prop_location *property_change_wait_list;
+
+static void
+set_property_change_object (struct prop_location *location)
+{
+  /* Input must be blocked so we don't get the event before we set these.  */
+  if (!input_blocked_p ())
+    emacs_abort ();
+
+  XSETCAR (property_change_reply, Qnil);
+  property_change_reply_object = location;
+}
+
+\f
+/* Send the reply to a selection request event EVENT.  */
+
+static void
+pgtk_reply_selection_request (struct selection_input_event *event,
+                             struct pgtk_display_info *dpyinfo)
+{
+  GdkDisplay *display = SELECTION_EVENT_DISPLAY (event);
+  GdkWindow *window = SELECTION_EVENT_REQUESTOR (event);
+  ptrdiff_t bytes_remaining;
+  struct selection_data *cs;
+  struct pgtk_selection_request *frame;
+
+  frame = selection_request_stack;
+
+  block_input ();
+  /* Loop over converted selections, storing them in the requested
+     properties.  If data is large, only store the first N bytes
+     (section 2.7.2 of ICCCM).  Note that we store the data for a
+     MULTIPLE request in the opposite order; the ICCM says only that
+     the conversion itself must be done in the same order. */
+  for (cs = frame->converted_selections; cs; cs = cs->next)
     {
-      atom = 0;
-      error ("Bad selection");
+      if (cs->property == GDK_NONE)
+       continue;
+
+      bytes_remaining = cs->size;
+      bytes_remaining *= cs->format >> 3;
+
+      gdk_property_change (window, cs->property,
+                          cs->type, cs->format,
+                          GDK_PROP_MODE_APPEND,
+                          cs->data, cs->size);
     }
 
-  return gtk_widget_get_clipboard (widget, atom);
+  /* Now issue the SelectionNotify event.  */
+  gdk_selection_send_notify (window,
+                            SELECTION_EVENT_SELECTION (event),
+                            SELECTION_EVENT_TARGET (event),
+                            SELECTION_EVENT_PROPERTY (event),
+                            SELECTION_EVENT_TIME (event));
+  gdk_display_flush (display);
+
+  /* Finish sending the rest of each of the INCR values.  This should
+     be improved; there's a chance of deadlock if more than one
+     subtarget in a MULTIPLE selection requires an INCR transfer, and
+     the requestor and Emacs loop waiting on different transfers.  */
+  for (cs = frame->converted_selections; cs; cs = cs->next)
+    if (cs->wait_object)
+      {
+       int format_bytes = cs->format / 8;
+
+        /* Must set this inside block_input ().  unblock_input may read
+           events and setting property_change_reply in
+           wait_for_property_change is then too late.  */
+        set_property_change_object (cs->wait_object);
+       unblock_input ();
+
+       bytes_remaining = cs->size;
+       bytes_remaining *= format_bytes;
+
+       /* Wait for the requestor to ack by deleting the property.
+          This can run Lisp code (process handlers) or signal.  */
+       wait_for_property_change (cs->wait_object);
+
+       /* Now write a zero-length chunk to the property to tell the
+          requestor that we're done.  */
+       block_input ();
+       if (! waiting_for_other_props_on_window (display, window))
+         gdk_window_set_events (window, 0);
+       gdk_property_change (window, cs->property, cs->type, cs->format,
+                            GDK_PROP_MODE_REPLACE, cs->data, 0);
+      }
+
+  gdk_display_sync (display);
+  unblock_input ();
 }
 
+\f
+
+/* Handle a SelectionRequest event EVENT.
+   This is called from keyboard.c when such an event is found in the queue.  */
+
 static void
-selection_type_to_quarks (GdkAtom type, GQuark * quark_data,
-                         GQuark * quark_size)
+pgtk_handle_selection_request (struct selection_input_event *event)
 {
-  if (type == GDK_SELECTION_PRIMARY)
+  guint32 local_selection_time;
+  struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event);
+  GdkAtom selection = SELECTION_EVENT_SELECTION (event);
+  Lisp_Object selection_symbol = gdk_atom_to_symbol (selection);
+  GdkAtom target = SELECTION_EVENT_TARGET (event);
+  Lisp_Object target_symbol = gdk_atom_to_symbol (target);
+  GdkAtom property = SELECTION_EVENT_PROPERTY (event);
+  Lisp_Object local_selection_data;
+  bool success = false;
+  specpdl_ref count = SPECPDL_INDEX ();
+  bool pushed;
+  Lisp_Object alias, tem;
+
+  alias = Vpgtk_selection_alias_alist;
+
+  FOR_EACH_TAIL_SAFE (alias)
     {
-      *quark_data = quark_primary_data;
-      *quark_size = quark_primary_size;
+      tem = Qnil;
+
+      if (CONSP (alias))
+       tem = XCAR (alias);
+
+      if (CONSP (tem)
+         && EQ (XCAR (tem), selection_symbol)
+         && SYMBOLP (XCDR (tem)))
+       {
+         selection_symbol = XCDR (tem);
+         break;
+       }
     }
-  else if (type == GDK_SELECTION_SECONDARY)
+
+  pushed = false;
+
+  if (!dpyinfo)
+    goto DONE;
+
+  local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo);
+
+  /* Decline if we don't own any selections.  */
+  if (NILP (local_selection_data)) goto DONE;
+
+  /* Decline requests issued prior to our acquiring the selection.  */
+  CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))),
+                  guint32, local_selection_time);
+  if (SELECTION_EVENT_TIME (event) != GDK_CURRENT_TIME
+      && local_selection_time > SELECTION_EVENT_TIME (event))
+    goto DONE;
+
+  block_input ();
+  pushed = true;
+  pgtk_push_current_selection_request (event, dpyinfo);
+  record_unwind_protect_void (pgtk_pop_current_selection_request);
+  record_unwind_protect_void (pgtk_selection_request_lisp_error);
+  unblock_input ();
+
+  if (EQ (target_symbol, QMULTIPLE))
     {
-      *quark_data = quark_secondary_data;
-      *quark_size = quark_secondary_size;
+      /* For MULTIPLE targets, the event property names a list of atom
+        pairs; the first atom names a target and the second names a
+        non-GDK_NONE property.  */
+      GdkWindow *requestor = SELECTION_EVENT_REQUESTOR (event);
+      Lisp_Object multprop;
+      ptrdiff_t j, nselections;
+      struct selection_data cs;
+
+      if (property == GDK_NONE)
+       goto DONE;
+
+      multprop = pgtk_get_window_property_as_lisp_data (dpyinfo,
+                                                       requestor,
+                                                       property,
+                                                       QMULTIPLE,
+                                                       selection,
+                                                       true);
+
+      if (!VECTORP (multprop) || ASIZE (multprop) % 2)
+       goto DONE;
+
+      nselections = ASIZE (multprop) / 2;
+      /* Perform conversions.  This can signal.  */
+      for (j = 0; j < nselections; j++)
+       {
+         Lisp_Object subtarget = AREF (multprop, 2*j);
+         GdkAtom subproperty = symbol_to_gdk_atom (AREF (multprop, 2 * j + 1));
+         bool subsuccess = false;
+
+         if (subproperty != GDK_NONE)
+           subsuccess = pgtk_convert_selection (selection_symbol, subtarget,
+                                                subproperty, true, dpyinfo);
+         if (!subsuccess)
+           ASET (multprop, 2*j+1, Qnil);
+       }
+      /* Save conversion results */
+      lisp_data_to_selection_data (dpyinfo, multprop, &cs);
+      gdk_property_change (requestor, property,
+                          cs.type, cs.format,
+                          GDK_PROP_MODE_REPLACE,
+                          cs.data, cs.size);
+      success = true;
     }
-  else if (type == GDK_SELECTION_CLIPBOARD)
+  else
     {
-      *quark_data = quark_clipboard_data;
-      *quark_size = quark_clipboard_size;
+      if (property == GDK_NONE)
+       property = SELECTION_EVENT_TARGET (event);
+
+      success = pgtk_convert_selection (selection_symbol,
+                                       target_symbol, property,
+                                       false, dpyinfo);
     }
+
+ DONE:
+
+  if (pushed)
+    selection_request_stack->converted = true;
+
+  if (success)
+    pgtk_reply_selection_request (event, dpyinfo);
   else
-    /* FIXME: Is it safe to use 'error' here? */
-    error ("Unknown selection type.");
+    pgtk_decline_selection_request (event);
+
+  /* Run the `pgtk-sent-selection-functions' abnormal hook.  */
+  if (!NILP (Vpgtk_sent_selection_functions)
+      && !BASE_EQ (Vpgtk_sent_selection_functions, Qunbound))
+    CALLN (Frun_hook_with_args, Qpgtk_sent_selection_functions,
+          selection_symbol, target_symbol, success ? Qt : Qnil);
+
+  unbind_to (count, Qnil);
 }
 
-static void
-get_func (GtkClipboard * cb, GtkSelectionData * data, guint info,
-         gpointer user_data_or_owner)
+/* Perform the requested selection conversion, and write the data to
+   the converted_selections linked list, where it can be accessed by
+   x_reply_selection_request.  If FOR_MULTIPLE, write out
+   the data even if conversion fails, using conversion_fail_tag.
+
+   Return true if (and only if) successful.  */
+
+static bool
+pgtk_convert_selection (Lisp_Object selection_symbol,
+                       Lisp_Object target_symbol, GdkAtom property,
+                       bool for_multiple, struct pgtk_display_info *dpyinfo)
 {
-  GObject *obj = G_OBJECT (user_data_or_owner);
-  const char *str;
-  int size;
-  GQuark quark_data, quark_size;
+  Lisp_Object lisp_selection;
+  struct selection_data *cs;
+  struct pgtk_selection_request *frame;
+
+  lisp_selection
+    = pgtk_get_local_selection (selection_symbol, target_symbol,
+                               false, dpyinfo);
 
-  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
-                           &quark_size);
+  frame = selection_request_stack;
 
-  str = g_object_get_qdata (obj, quark_data);
-  size = GPOINTER_TO_SIZE (g_object_get_qdata (obj, quark_size));
-  gtk_selection_data_set_text (data, str, size);
+  /* A nil return value means we can't perform the conversion.  */
+  if (NILP (lisp_selection)
+      || (CONSP (lisp_selection) && NILP (XCDR (lisp_selection))))
+    {
+      if (for_multiple)
+       {
+         cs = xmalloc (sizeof *cs);
+         cs->data = ((unsigned char *)
+                     &selection_request_stack->conversion_fail_tag);
+         cs->size = 1;
+         cs->format = 32;
+         cs->type = GDK_SELECTION_TYPE_ATOM;
+         cs->nofree = true;
+         cs->property = property;
+         cs->wait_object = NULL;
+         cs->next = frame->converted_selections;
+         frame->converted_selections = cs;
+       }
+
+      return false;
+    }
+
+  /* Otherwise, record the converted selection to binary.  */
+  cs = xmalloc (sizeof *cs);
+  cs->data = NULL;
+  cs->nofree = true;
+  cs->property = property;
+  cs->wait_object = NULL;
+  cs->next = frame->converted_selections;
+  frame->converted_selections = cs;
+  lisp_data_to_selection_data (dpyinfo, lisp_selection, cs);
+  return true;
 }
 
+\f
+
+/* Handle a SelectionClear event EVENT, which indicates that some
+   client cleared out our previously asserted selection.
+   This is called from keyboard.c when such an event is found in the queue.  */
+
 static void
-clear_func (GtkClipboard * cb, gpointer user_data_or_owner)
+pgtk_handle_selection_clear (struct selection_input_event *event)
 {
-  GObject *obj = G_OBJECT (user_data_or_owner);
-  GQuark quark_data, quark_size;
+  GdkAtom selection = SELECTION_EVENT_SELECTION (event);
+  guint32 changed_owner_time = SELECTION_EVENT_TIME (event);
 
-  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
-                           &quark_size);
+  Lisp_Object selection_symbol, local_selection_data;
+  guint32 local_selection_time;
+  struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event);
+  Lisp_Object Vselection_alist;
 
-  g_object_set_qdata (obj, quark_data, NULL);
-  g_object_set_qdata (obj, quark_size, 0);
-}
+  if (!dpyinfo) return;
 
+  selection_symbol = gdk_atom_to_symbol (selection);
+  local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo);
 
-/* ==========================================================================
+  /* Well, we already believe that we don't own it, so that's just fine.  */
+  if (NILP (local_selection_data)) return;
 
-    Functions used externally
+  CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))),
+                  guint32, local_selection_time);
 
-   ========================================================================== */
+  /* We have reasserted the selection since this SelectionClear was
+     generated, so we can disregard it.  */
+  if (changed_owner_time != GDK_CURRENT_TIME
+      && local_selection_time > changed_owner_time)
+    return;
+
+  /* Otherwise, really clear.  Don't use Fdelq as that may quit.  */
+  Vselection_alist = dpyinfo->terminal->Vselection_alist;
+  if (EQ (local_selection_data, CAR (Vselection_alist)))
+    Vselection_alist = XCDR (Vselection_alist);
+  else
+    {
+      Lisp_Object rest;
+      for (rest = Vselection_alist; CONSP (rest); rest = XCDR (rest))
+       if (EQ (local_selection_data, CAR (XCDR (rest))))
+         {
+           XSETCDR (rest, XCDR (XCDR (rest)));
+           break;
+         }
+    }
+  tset_selection_alist (dpyinfo->terminal, Vselection_alist);
+
+  /* Run the `pgtk-lost-selection-functions' abnormal hook.  */
+  CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions, selection_symbol);
+
+  redisplay_preserve_echo_area (20);
+}
+
+void
+pgtk_handle_selection_event (struct selection_input_event *event)
+{
+  if (event->kind != SELECTION_REQUEST_EVENT)
+    pgtk_handle_selection_clear (event);
+  else
+    pgtk_handle_selection_request (event);
+}
+
+/* Clear all selections that were made from frame F.
+   We do this when about to delete a frame.  */
 
 void
-pgtk_selection_init (void)
+pgtk_clear_frame_selections (struct frame *f)
+{
+  Lisp_Object frame;
+  Lisp_Object rest;
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  struct terminal *t = dpyinfo->terminal;
+
+  XSETFRAME (frame, f);
+
+  /* Delete elements from the beginning of Vselection_alist.  */
+  while (CONSP (t->Vselection_alist)
+        && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (t->Vselection_alist)))))))
+    {
+      /* Run the `pgtk-lost-selection-functions' abnormal hook.  */
+      CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions,
+            Fcar (Fcar (t->Vselection_alist)));
+
+      tset_selection_alist (t, XCDR (t->Vselection_alist));
+    }
+
+  /* Delete elements after the beginning of Vselection_alist.  */
+  for (rest = t->Vselection_alist; CONSP (rest); rest = XCDR (rest))
+    if (CONSP (XCDR (rest))
+       && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (XCDR (rest))))))))
+      {
+       CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions,
+              XCAR (XCAR (XCDR (rest))));
+       XSETCDR (rest, XCDR (XCDR (rest)));
+       break;
+      }
+}
+
+/* True if any properties for DISPLAY and WINDOW
+   are on the list of what we are waiting for.  */
+
+static bool
+waiting_for_other_props_on_window (GdkDisplay *display, GdkWindow *window)
+{
+  for (struct prop_location *p = property_change_wait_list; p; p = p->next)
+    if (p->display == display && p->window == window)
+      return true;
+  return false;
+}
+
+/* Add an entry to the list of property changes we are waiting for.
+   DISPLAY, WINDOW, PROPERTY, STATE describe what we will wait for.
+   The return value is a number that uniquely identifies
+   this awaited property change.  */
+
+/* Currently unused -- uncomment later if we decide to implement INCR
+   transfer for X.  */
+
+#if 0
+
+static struct prop_location *
+expect_property_change (GdkDisplay *display, GdkWindow *window,
+                        GdkAtom property, int state)
+{
+  struct prop_location *pl = xmalloc (sizeof *pl);
+  pl->identifier = ++prop_location_identifier;
+  pl->display = display;
+  pl->window = window;
+  pl->property = property;
+  pl->desired_state = state;
+  pl->next = property_change_wait_list;
+  pl->arrived = false;
+  property_change_wait_list = pl;
+  return pl;
+}
+
+#endif
+
+/* Delete an entry from the list of property changes we are waiting for.
+   IDENTIFIER is the number that uniquely identifies the entry.  */
+
+static void
+unexpect_property_change (struct prop_location *location)
 {
-  if (quark_primary_data == 0)
+  struct prop_location *prop, **pprev = &property_change_wait_list;
+
+  for (prop = property_change_wait_list; prop; prop = *pprev)
     {
-      quark_primary_data = g_quark_from_static_string ("pgtk-primary-data");
-      quark_primary_size = g_quark_from_static_string ("pgtk-primary-size");
-      quark_secondary_data =
-       g_quark_from_static_string ("pgtk-secondary-data");
-      quark_secondary_size =
-       g_quark_from_static_string ("pgtk-secondary-size");
-      quark_clipboard_data =
-       g_quark_from_static_string ("pgtk-clipboard-data");
-      quark_clipboard_size =
-       g_quark_from_static_string ("pgtk-clipboard-size");
+      if (prop == location)
+       {
+         *pprev = prop->next;
+         xfree (prop);
+         break;
+       }
+      else
+       pprev = &prop->next;
     }
 }
 
+/* Remove the property change expectation element for IDENTIFIER.  */
+
+static void
+wait_for_property_change_unwind (void *loc)
+{
+  struct prop_location *location = loc;
+
+  unexpect_property_change (location);
+  if (location == property_change_reply_object)
+    property_change_reply_object = 0;
+}
+
+/* Actually wait for a property change.
+   IDENTIFIER should be the value that expect_property_change returned.  */
+
+static void
+wait_for_property_change (struct prop_location *location)
+{
+  specpdl_ref count = SPECPDL_INDEX ();
+
+  /* Make sure to do unexpect_property_change if we quit or err.  */
+  record_unwind_protect_ptr (wait_for_property_change_unwind, location);
+
+  /* See comment in x_reply_selection_request about setting
+     property_change_reply.  Do not do it here.  */
+
+  /* If the event we are waiting for arrives beyond here, it will set
+     property_change_reply, because property_change_reply_object says so.  */
+  if (! location->arrived)
+    {
+      intmax_t timeout = max (0, 5000);
+      intmax_t secs = timeout / 1000;
+      int nsecs = (timeout % 1000) * 1000000;
+
+      wait_reading_process_output (secs, nsecs, 0, false,
+                                  property_change_reply, NULL, 0);
+
+      if (NILP (XCAR (property_change_reply)))
+       error ("Timed out waiting for property-notify event");
+    }
+
+  unbind_to (count, Qnil);
+}
+
+/* Called from the big filter in response to a PropertyNotify
+   event.  */
+
 void
-pgtk_selection_lost (GtkWidget * widget, GdkEventSelection * event,
-                    gpointer user_data)
+pgtk_handle_property_notify (GdkEventProperty *event)
 {
-  GQuark quark_data, quark_size;
+  struct prop_location *rest;
+  GdkDisplay *dpy;
+
+  dpy = gdk_window_get_display (event->window);
 
-  selection_type_to_quarks (event->selection, &quark_data, &quark_size);
+  for (rest = property_change_wait_list; rest; rest = rest->next)
+    {
+      if (!rest->arrived
+         && rest->property == event->atom
+         && rest->window == event->window
+         && rest->display == dpy
+         && rest->desired_state == event->state)
+       {
+         rest->arrived = true;
 
-  g_object_set_qdata (G_OBJECT (widget), quark_data, NULL);
-  g_object_set_qdata (G_OBJECT (widget), quark_size, 0);
+         /* If this is the one wait_for_property_change is waiting for,
+            tell it to wake up.  */
+         if (rest == property_change_reply_object)
+           XSETCAR (property_change_reply, Qt);
+
+         return;
+       }
+    }
 }
 
-static bool
-pgtk_selection_usable (void)
+static void
+pgtk_display_selection_waiting_message (struct atimer *timer)
 {
-  if (pgtk_enable_selection_on_multi_display)
-    return true;
+  Lisp_Object val;
 
-  /* Gdk uses `gdk_display_get_default' when handling selections, so
-     selections don't work properly when Emacs is connected to
-     multiple displays.  */
+  val = build_string ("Waiting for reply from selection owner...");
+  message3_nolog (val);
+}
 
-  GdkDisplayManager *dpyman = gdk_display_manager_get ();
-  GSList *list = gdk_display_manager_list_displays (dpyman);
-  int len = g_slist_length (list);
-  g_slist_free (list);
-  return len < 2;
+static void
+pgtk_cancel_atimer (void *atimer)
+{
+  cancel_atimer (atimer);
 }
 
-/* ==========================================================================
+\f
+/* Variables for communication with pgtk_handle_selection_notify.  */
+static GdkAtom reading_which_selection;
+static Lisp_Object reading_selection_reply;
+static GdkWindow *reading_selection_window;
 
-    Lisp Defuns
+/* Do protocol to read selection-data from the window server.
+   Converts this to Lisp data and returns it.
+   FRAME is the frame whose window shall request the selection.  */
 
-   ========================================================================== */
+static Lisp_Object
+pgtk_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type,
+                           Lisp_Object time_stamp, Lisp_Object frame)
+{
+  struct frame *f = XFRAME (frame);
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  GdkWindow *requestor_window = FRAME_GDK_WINDOW (f);
+  guint32 requestor_time = dpyinfo->last_user_time;
+  GdkAtom selection_atom = symbol_to_gdk_atom (selection_symbol);
+  GdkAtom type_atom = (CONSP (target_type)
+                      ? symbol_to_gdk_atom (XCAR (target_type))
+                      : symbol_to_gdk_atom (target_type));
+  struct atimer *delayed_message;
+  struct timespec message_interval;
+  specpdl_ref count;
+
+  count = SPECPDL_INDEX ();
+
+  if (!FRAME_LIVE_P (f))
+    return unbind_to (count, Qnil);
+
+  if (!NILP (time_stamp))
+    CONS_TO_INTEGER (time_stamp, guint32, requestor_time);
+
+  block_input ();
+  /* Prepare to block until the reply has been read.  */
+  reading_selection_window = requestor_window;
+  reading_which_selection = selection_atom;
+  XSETCAR (reading_selection_reply, Qnil);
+
+  gdk_selection_convert (requestor_window, selection_atom,
+                        type_atom, requestor_time);
+  unblock_input ();
+
+  /* It should not be necessary to stop handling selection requests
+     during this time.  In fact, the SAVE_TARGETS mechanism requires
+     us to handle a clipboard manager's requests before it returns
+     GDK_SELECTION_NOTIFY. */
+
+  message_interval = make_timespec (1, 0);
+  delayed_message = start_atimer (ATIMER_RELATIVE, message_interval,
+                                 pgtk_display_selection_waiting_message,
+                                 NULL);
+  record_unwind_protect_ptr (pgtk_cancel_atimer, delayed_message);
+
+  /* This allows quits.  Also, don't wait forever.  */
+  intmax_t timeout = max (0, 5000);
+  intmax_t secs = timeout / 1000;
+  int nsecs = (timeout % 1000) * 1000000;
+
+  wait_reading_process_output (secs, nsecs, 0, false,
+                              reading_selection_reply, NULL, 0);
+
+  if (NILP (XCAR (reading_selection_reply)))
+    error ("Timed out waiting for reply from selection owner");
+  if (EQ (XCAR (reading_selection_reply), Qlambda))
+    return unbind_to (count, Qnil);
+
+  /* Otherwise, the selection is waiting for us on the requested property.  */
+  return unbind_to (count,
+                   pgtk_get_window_property_as_lisp_data (dpyinfo,
+                                                          requestor_window,
+                                                          GDK_NONE,
+                                                          target_type,
+                                                          selection_atom,
+                                                          false));
+}
 
+/* Subroutines of pgtk_get_window_property_as_lisp_data */
 
-DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal, Spgtk_own_selection_internal, 2, 3, 0,
-       doc: /* Assert an X selection of type SELECTION and value VALUE.
-SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
-\(Those are literal upper-case symbol names, since that's what X expects.)
-VALUE is typically a string, or a cons of two markers, but may be
-anything that the functions on `selection-converter-alist' know about.
+static ptrdiff_t
+pgtk_size_for_format (gint format)
+{
+  switch (format)
+    {
+    case 8:
+      return sizeof (unsigned char);
+    case 16:
+      return sizeof (unsigned short);
+    case 32:
+      return sizeof (unsigned long);
+
+    default:
+      emacs_abort ();
+    }
+}
 
-FRAME should be a frame that should own the selection.  If omitted or
-nil, it defaults to the selected frame. */)
-  (Lisp_Object selection, Lisp_Object value, Lisp_Object frame)
+/* Use xfree, not g_free, to free the data obtained with this function.  */
+
+static void
+pgtk_get_window_property (GdkWindow *window, unsigned char **data_ret,
+                         ptrdiff_t *bytes_ret, GdkAtom *actual_type_ret,
+                         int *actual_format_ret, unsigned long *actual_size_ret)
 {
-  Lisp_Object successful_p = Qnil;
-  Lisp_Object target_symbol, rest;
-  GtkClipboard *cb;
-  struct frame *f;
-  GQuark quark_data, quark_size;
+  gint length, actual_format;
+  unsigned char *data;
+  ptrdiff_t element_size;
+  void *xdata;
+  GdkAtom actual_type;
+  unsigned long i;
+  unsigned int *idata;
+  unsigned long *ldata;
+
+  data = NULL;
+
+  length = gdk_selection_property_get (window, &data,
+                                      &actual_type,
+                                      &actual_format);
+
+  if (!data)
+    {
+      *data_ret = NULL;
+      *actual_type_ret = GDK_NONE;
+      *bytes_ret = 0;
+      *actual_format_ret = 8;
+      *actual_size_ret = 0;
 
-  check_window_system (NULL);
+      return;
+    }
 
-  if (!pgtk_selection_usable ())
-    return Qnil;
+  if (actual_type == GDK_SELECTION_TYPE_ATOM
+      || actual_type == gdk_atom_intern_static_string ("ATOM_PAIR"))
+    {
+      /* GDK should not allow anything else.  */
+      eassert (actual_format == 32);
 
-  if (NILP (frame))
-    frame = selected_frame;
-  if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
-    error ("pgtk selection unavailable for this frame");
-  f = XFRAME (frame);
+      length = length / sizeof (GdkAtom);
+      xdata = xmalloc (sizeof (GdkAtom) * length);
+      memcpy (xdata, data, 1 + length * sizeof (GdkAtom));
+
+      g_free (data);
+
+      *data_ret = xdata;
+      *actual_type_ret = actual_type;
+      *bytes_ret = length * sizeof (GdkAtom);
+      *actual_format_ret = 32;
+      *actual_size_ret = length;
 
-  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
-  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
-                           &quark_size);
+      return;
+    }
+
+  element_size = pgtk_size_for_format (actual_format);
+  length = length / element_size;
 
-  /* We only support copy of text.  */
-  target_symbol = QTEXT;
-  if (STRINGP (value))
+  /* Add an extra byte on the end.  GDK guarantees that it is
+     NULL.  */
+  xdata = xmalloc (1 + element_size * length);
+  memcpy (xdata, data, 1 + element_size * length);
+
+  if (actual_format == 32 && LONG_WIDTH > 32)
     {
-      GtkTargetList *list;
-      GtkTargetEntry *targets;
-      gint n_targets;
-      GtkWidget *widget;
+      ldata = (typeof (ldata)) data;
+      idata = xdata;
 
-      list = gtk_target_list_new (NULL, 0);
-      gtk_target_list_add_text_targets (list, 0);
+      for (i = 0; i < length; ++i)
+       idata[i] = ldata[i];
 
-      {
-       /* text/plain: Strings encoded by Gtk are not correctly decoded by Chromium(Wayland). */
-       GdkAtom atom_text_plain = gdk_atom_intern ("text/plain", false);
-       gtk_target_list_remove (list, atom_text_plain);
-      }
+      /* There is always enough space in idata.  */
+      idata[length] = 0;
+      *bytes_ret = sizeof *idata * length;
+    }
+  else
+    /* I think GDK itself prevents element_size from exceeding the
+       length at which this computation fails.  */
+    *bytes_ret = element_size * length;
+
+  /* Now free the original `data' allocated by GDK.  */
+  g_free (data);
+
+  *data_ret = xdata;
+  *actual_type_ret = GDK_NONE;
+  *actual_size_ret = length;
+  *actual_format_ret = actual_format;
+  *actual_type_ret = actual_type;
+}
+
+static Lisp_Object
+pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *dpyinfo,
+                                      GdkWindow *window, GdkAtom property,
+                                      Lisp_Object target_type, GdkAtom selection_atom,
+                                      bool for_multiple)
+{
+  GdkAtom actual_type;
+  int actual_format;
+  unsigned long actual_size;
+  unsigned char *data = 0;
+  ptrdiff_t bytes = 0;
+  Lisp_Object val;
+  GdkDisplay *display = dpyinfo->display;
+
+  pgtk_get_window_property (window, &data, &bytes,
+                           &actual_type, &actual_format,
+                           &actual_size);
+
+  if (!data)
+    {
+      if (for_multiple)
+       return Qnil;
+
+      if (gdk_selection_owner_get_for_display (display, selection_atom))
+       {
+         AUTO_STRING (format, "Selection owner couldn't convert: %s");
+         CALLN (Fmessage, format,
+                actual_type
+                ? list2 (target_type,
+                         gdk_atom_to_symbol (actual_type))
+                : target_type);
+         return Qnil;
+       }
+      else
+       {
+         AUTO_STRING (format, "No selection: %s");
+         CALLN (Fmessage, format,
+                gdk_atom_to_symbol (selection_atom));
+         return Qnil;
+       }
+    }
 
-      targets = gtk_target_table_new_from_list (list, &n_targets);
+  if (!for_multiple && property != GDK_NONE)
+    gdk_property_delete (window, property);
 
-      int size = SBYTES (value);
-      gchar *str = xmalloc (size + 1);
-      memcpy (str, SSDATA (value), size);
-      str[size] = '\0';
+  /* It's been read.  Now convert it to a lisp object in some semi-rational
+     manner.  */
+  val = selection_data_to_lisp_data (dpyinfo, data, bytes,
+                                    actual_type, actual_format);
 
-      widget = FRAME_GTK_WIDGET (f);
-      g_object_set_qdata_full (G_OBJECT (widget), quark_data, str, xfree);
-      g_object_set_qdata_full (G_OBJECT (widget), quark_size,
-                              GSIZE_TO_POINTER (size), NULL);
+  /* Use xfree, not g_free, because pgtk_get_window_property calls
+     xmalloc itself.  */
+  xfree (data);
+  return val;
+}
+
+\f
+
+/* These functions convert from the selection data read from the
+   server into something that we can use from Lisp, and vice versa.
+
+       Type:   Format: Size:           Lisp Type:
+       -----   ------- -----           -----------
+       *       8       *               String
+       ATOM    32      1               Symbol
+       ATOM    32      > 1             Vector of Symbols
+       *       16      1               Integer
+       *       16      > 1             Vector of Integers
+       *       32      1               if small enough: fixnum
+                                       otherwise: bignum
+       *       32      > 1             Vector of the above
+
+   When converting an object to C, it may be of the form (SYMBOL
+   . <data>) where SYMBOL is what we should claim that the type is.
+   Format and representation are as above.
+
+   Important: When format is 32, data should contain an array of int,
+   not an array of long as GDK returns.  Unless TYPE is also
+   GDK_SELECTION_TYPE_ATOM, in which case data should be an array of
+   GdkAtom.  This makes a difference when sizeof (long) != sizeof
+   (int).  */
+
+static Lisp_Object
+selection_data_to_lisp_data (struct pgtk_display_info *dpyinfo,
+                            const unsigned char *data,
+                            ptrdiff_t size, GdkAtom type, int format)
+{
+  if (type == gdk_atom_intern_static_string ("NULL"))
+    return QNULL;
+  /* Convert any 8-bit data to a string, for compactness.  */
+  else if (format == 8)
+    {
+      Lisp_Object str, lispy_type;
+
+      str = make_unibyte_string ((char *) data, size);
+      /* Indicate that this string is from foreign selection by a text
+        property `foreign-selection' so that the caller of
+        x-get-selection-internal (usually x-get-selection) can know
+        that the string must be decode.  */
+      if (type == gdk_atom_intern_static_string ("COMPOUND_TEXT"))
+       lispy_type = QCOMPOUND_TEXT;
+      else if (type == gdk_atom_intern_static_string ("UTF8_STRING"))
+       lispy_type = QUTF8_STRING;
+      else
+       lispy_type = QSTRING;
+
+      Fput_text_property (make_fixnum (0), make_fixnum (size),
+                         Qforeign_selection, lispy_type, str);
+      return str;
+    }
+  /* Convert a single atom to a Lisp_Symbol.  Convert a set of atoms to
+     a vector of symbols.  */
+  else if (format == 32
+          && (type == GDK_SELECTION_TYPE_ATOM
+              /* Treat ATOM_PAIR type similar to list of atoms.  */
+              || type == gdk_atom_intern_static_string ("ATOM_PAIR")))
+    {
+      ptrdiff_t i;
+      GdkAtom *idata = (GdkAtom *) data;
 
-      if (gtk_clipboard_set_with_owner (cb,
-                                       targets, n_targets,
-                                       get_func, clear_func,
-                                       G_OBJECT (FRAME_GTK_WIDGET (f))))
+      if (size == sizeof (GdkAtom))
+       return gdk_atom_to_symbol (idata[0]);
+      else
        {
-         successful_p = Qt;
+         Lisp_Object v = make_nil_vector (size / sizeof (GdkAtom));
+
+         for (i = 0; i < size / sizeof (GdkAtom); i++)
+           ASET (v, i, gdk_atom_to_symbol (idata[i]));
+         return v;
        }
-      gtk_clipboard_set_can_store (cb, NULL, 0);
+    }
+
+  /* Convert a single 16-bit number or a small 32-bit number to a Lisp_Int.
+     If the number is 32 bits and won't fit in a Lisp_Int, convert it
+     to a bignum.
 
-      gtk_target_table_free (targets, n_targets);
-      gtk_target_list_unref (list);
+     INTEGER is a signed type, CARDINAL is unsigned.
+     Assume any other types are unsigned as well.
+   */
+  else if (format == 32 && size == sizeof (int))
+    {
+      if (type == GDK_SELECTION_TYPE_INTEGER)
+        return INT_TO_INTEGER (((int *) data) [0]);
+      else
+        return INT_TO_INTEGER (((unsigned int *) data) [0]);
+    }
+  else if (format == 16 && size == sizeof (short))
+    {
+      if (type == GDK_SELECTION_TYPE_INTEGER)
+        return make_fixnum (((short *) data) [0]);
+      else
+        return make_fixnum (((unsigned short *) data) [0]);
+    }
+  /* Convert any other kind of data to a vector of numbers, represented
+     as above (as an integer, or a cons of two 16 bit integers.)
+   */
+  else if (format == 16)
+    {
+      ptrdiff_t i;
+      Lisp_Object v = make_uninit_vector (size / 2);
+
+      if (type == GDK_SELECTION_TYPE_INTEGER)
+        {
+          for (i = 0; i < size / 2; i++)
+            {
+              short j = ((short *) data) [i];
+              ASET (v, i, make_fixnum (j));
+            }
+        }
+      else
+        {
+          for (i = 0; i < size / 2; i++)
+            {
+             unsigned short j = ((unsigned short *) data) [i];
+              ASET (v, i, make_fixnum (j));
+            }
+        }
+      return v;
     }
+  else
+    {
+      ptrdiff_t i;
+      Lisp_Object v = make_nil_vector (size / sizeof (gint));
+
+      if (type == GDK_SELECTION_TYPE_INTEGER)
+        {
+          for (i = 0; i < size / sizeof (gint); i++)
+            {
+              int j = ((gint *) data) [i];
+              ASET (v, i, INT_TO_INTEGER (j));
+            }
+        }
+      else
+        {
+          for (i = 0; i < size / sizeof (gint); i++)
+            {
+             unsigned int j = ((unsigned int *) data) [i];
+              ASET (v, i, INT_TO_INTEGER (j));
+            }
+        }
+      return v;
+    }
+}
+
+/* Convert OBJ to an X long value, and return it as unsigned long.
+   OBJ should be an integer or a cons representing an integer.
+   Treat values in the range X_LONG_MAX + 1 .. X_ULONG_MAX as X
+   unsigned long values: in theory these values are supposed to be
+   signed but in practice unsigned 32-bit data are communicated via X
+   selections and we need to support that.  */
+static unsigned long
+cons_to_gdk_long (Lisp_Object obj)
+{
+  if (G_MAXUINT32 <= INTMAX_MAX
+      || NILP (Fnatnump (CONSP (obj) ? XCAR (obj) : obj)))
+    return cons_to_signed (obj, 0, min (G_MAXUINT32, INTMAX_MAX));
+  else
+    return cons_to_unsigned (obj, G_MAXUINT32);
+}
+
+/* Use xfree, not XFree, to free the data obtained with this function.  */
+
+static void
+lisp_data_to_selection_data (struct pgtk_display_info *dpyinfo,
+                            Lisp_Object obj, struct selection_data *cs)
+{
+  Lisp_Object type = Qnil;
 
-  if (!BASE_EQ (Vpgtk_sent_selection_hooks, Qunbound))
+  eassert (cs != NULL);
+  cs->nofree = false;
+
+  if (CONSP (obj) && SYMBOLP (XCAR (obj)))
     {
-      /* FIXME: Use run-hook-with-args!  */
-      for (rest = Vpgtk_sent_selection_hooks; CONSP (rest);
-          rest = Fcdr (rest))
-       call3 (Fcar (rest), selection, target_symbol, successful_p);
+      type = XCAR (obj);
+      obj = XCDR (obj);
+      if (CONSP (obj) && NILP (XCDR (obj)))
+       obj = XCAR (obj);
     }
 
+  if (EQ (obj, QNULL) || (EQ (type, QNULL)))
+    {                          /* This is not the same as declining */
+      cs->format = 32;
+      cs->size = 0;
+      cs->data = NULL;
+      type = QNULL;
+    }
+  else if (STRINGP (obj))
+    {
+      if (SCHARS (obj) < SBYTES (obj))
+       /* OBJ is a multibyte string containing a non-ASCII char.  */
+       signal_error ("Non-ASCII string must be encoded in advance", obj);
+      if (NILP (type))
+       type = QSTRING;
+      cs->format = 8;
+      cs->size = SBYTES (obj);
+      cs->data = SDATA (obj);
+      cs->nofree = true;
+    }
+  else if (SYMBOLP (obj))
+    {
+      void *data = xmalloc (sizeof (GdkAtom) + 1);
+      GdkAtom *x_atom_ptr = data;
+      cs->data = data;
+      cs->format = 32;
+      cs->size = 1;
+      cs->data[sizeof (GdkAtom)] = 0;
+      *x_atom_ptr = symbol_to_gdk_atom (obj);
+      if (NILP (type)) type = QATOM;
+    }
+  else if (RANGED_FIXNUMP (SHRT_MIN, obj, SHRT_MAX))
+    {
+      void *data = xmalloc (sizeof (short) + 1);
+      short *short_ptr = data;
+      cs->data = data;
+      cs->format = 16;
+      cs->size = 1;
+      cs->data[sizeof (short)] = 0;
+      *short_ptr = XFIXNUM (obj);
+      if (NILP (type)) type = QINTEGER;
+    }
+  else if (INTEGERP (obj)
+          || (CONSP (obj) && INTEGERP (XCAR (obj))
+              && (FIXNUMP (XCDR (obj))
+                  || (CONSP (XCDR (obj))
+                      && FIXNUMP (XCAR (XCDR (obj)))))))
+    {
+      void *data = xmalloc (sizeof (unsigned long) + 1);
+      unsigned long *x_long_ptr = data;
+      cs->data = data;
+      cs->format = 32;
+      cs->size = 1;
+      cs->data[sizeof (unsigned long)] = 0;
+      *x_long_ptr = cons_to_gdk_long (obj);
+      if (NILP (type)) type = QINTEGER;
+    }
+  else if (VECTORP (obj))
+    {
+      /* Lisp_Vectors may represent a set of ATOMs;
+        a set of 16 or 32 bit INTEGERs;
+        or a set of ATOM_PAIRs (represented as [[A1 A2] [A3 A4] ...]
+       */
+      ptrdiff_t i;
+      ptrdiff_t size = ASIZE (obj);
+
+      if (SYMBOLP (AREF (obj, 0)))
+       /* This vector is an ATOM set */
+       {
+         void *data;
+         GdkAtom *x_atoms;
+         if (NILP (type)) type = QATOM;
+         for (i = 0; i < size; i++)
+           if (!SYMBOLP (AREF (obj, i)))
+             signal_error ("All elements of selection vector must have same type", obj);
+
+         cs->data = data = xnmalloc (size, sizeof *x_atoms);
+         x_atoms = data;
+         cs->format = 32;
+         cs->size = size;
+         for (i = 0; i < size; i++)
+           x_atoms[i] = symbol_to_gdk_atom (AREF (obj, i));
+       }
+      else
+       /* This vector is an INTEGER set, or something like it */
+       {
+         int format = 16;
+         int data_size = sizeof (short);
+         void *data;
+         unsigned long *x_atoms;
+         short *shorts;
+         if (NILP (type)) type = QINTEGER;
+         for (i = 0; i < size; i++)
+           {
+             if (! RANGED_FIXNUMP (SHRT_MIN, AREF (obj, i), SHRT_MAX))
+               {
+                 /* Use sizeof (long) even if it is more than 32 bits.
+                    See comment in x_get_window_property and
+                    x_fill_property_data.  */
+                 data_size = sizeof (long);
+                 format = 32;
+                 break;
+               }
+           }
+         cs->data = data = xnmalloc (size, data_size);
+         x_atoms = data;
+         shorts = data;
+         cs->format = format;
+         cs->size = size;
+         for (i = 0; i < size; i++)
+           {
+             if (format == 32)
+               x_atoms[i] = cons_to_gdk_long (AREF (obj, i));
+             else
+               shorts[i] = XFIXNUM (AREF (obj, i));
+           }
+       }
+    }
+  else
+    signal_error (/* Qselection_error */ "Unrecognized selection data", obj);
+
+  cs->type = symbol_to_gdk_atom (type);
+}
+
+static Lisp_Object
+clean_local_selection_data (Lisp_Object obj)
+{
+  if (CONSP (obj)
+      && INTEGERP (XCAR (obj))
+      && CONSP (XCDR (obj))
+      && FIXNUMP (XCAR (XCDR (obj)))
+      && NILP (XCDR (XCDR (obj))))
+    obj = Fcons (XCAR (obj), XCDR (obj));
+
+  if (CONSP (obj)
+      && INTEGERP (XCAR (obj))
+      && FIXNUMP (XCDR (obj)))
+    {
+      if (BASE_EQ (XCAR (obj), make_fixnum (0)))
+       return XCDR (obj);
+      if (BASE_EQ (XCAR (obj), make_fixnum (-1)))
+       return make_fixnum (- XFIXNUM (XCDR (obj)));
+    }
+  if (VECTORP (obj))
+    {
+      ptrdiff_t i;
+      ptrdiff_t size = ASIZE (obj);
+      Lisp_Object copy;
+      if (size == 1)
+       return clean_local_selection_data (AREF (obj, 0));
+      copy = make_nil_vector (size);
+      for (i = 0; i < size; i++)
+       ASET (copy, i, clean_local_selection_data (AREF (obj, i)));
+      return copy;
+    }
+  return obj;
+}
+
+DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal,
+       Spgtk_own_selection_internal, 2, 3, 0,
+       doc: /* Assert a selection of type SELECTION and value VALUE.
+SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
+\(Those are literal upper-case symbol names, since that's what GDK expects.)
+VALUE is typically a string, or a cons of two markers, but may be
+anything that the functions on `selection-converter-alist' know about.
+
+FRAME should be a frame that should own the selection.  If omitted or
+nil, it defaults to the selected frame.  */)
+  (Lisp_Object selection, Lisp_Object value, Lisp_Object frame)
+{
+  if (NILP (frame)) frame = selected_frame;
+  if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
+    error ("GDK selection unavailable for this frame");
+
+  CHECK_SYMBOL (selection);
+  if (NILP (value)) error ("VALUE may not be nil");
+  pgtk_own_selection (selection, value, frame);
   return value;
 }
 
+/* Request the selection value from the owner.  If we are the owner,
+   simply return our selection value.  If we are not the owner, this
+   will block until all of the data has arrived.  */
 
-DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal,
-       Spgtk_disown_selection_internal, 1, 2, 0,
-       doc: /* If we own the selection SELECTION, disown it.
-Disowning it means there is no such selection.
+DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal,
+       Spgtk_get_selection_internal, 2, 4, 0,
+       doc: /* Return text selected from some X window.
+SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
+\(Those are literal upper-case symbol names, since that's what X expects.)
+TARGET-TYPE is the type of data desired, typically `STRING'.
+
+TIME-STAMP is the time to use in the XConvertSelection call for foreign
+selections.  If omitted, defaults to the time for the last event.
 
 TERMINAL should be a terminal object or a frame specifying the X
 server to query.  If omitted or nil, that stands for the selected
 frame's display, or the first available X display.  */)
-  (Lisp_Object selection, Lisp_Object terminal)
+  (Lisp_Object selection_symbol, Lisp_Object target_type,
+   Lisp_Object time_stamp, Lisp_Object terminal)
 {
+  Lisp_Object val = Qnil;
+  Lisp_Object maybe_alias;
   struct frame *f = frame_for_pgtk_selection (terminal);
-  GtkClipboard *cb;
 
-  if (!pgtk_selection_usable ())
-    return Qnil;
+  CHECK_SYMBOL (selection_symbol);
+  CHECK_SYMBOL (target_type);
 
+  if (EQ (target_type, QMULTIPLE))
+    error ("Retrieving MULTIPLE selections is currently unimplemented");
   if (!f)
-    return Qnil;
+    error ("GDK selection unavailable for this frame");
 
-  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+  /* Quitting inside this function is okay, so we don't have to use
+     FOR_EACH_TAIL_SAFE.  */
+  maybe_alias = Fassq (selection_symbol, Vpgtk_selection_alias_alist);
 
-  gtk_clipboard_clear (cb);
+  if (!NILP (maybe_alias))
+    {
+      selection_symbol = XCDR (maybe_alias);
+      CHECK_SYMBOL (selection_symbol);
+    }
 
-  return Qt;
+  val = pgtk_get_local_selection (selection_symbol, target_type, true,
+                                 FRAME_DISPLAY_INFO (f));
+
+  if (NILP (val) && FRAME_LIVE_P (f))
+    {
+      Lisp_Object frame;
+      XSETFRAME (frame, f);
+      return pgtk_get_foreign_selection (selection_symbol, target_type,
+                                        time_stamp, frame);
+    }
+
+  if (CONSP (val) && SYMBOLP (XCAR (val)))
+    {
+      val = XCDR (val);
+      if (CONSP (val) && NILP (XCDR (val)))
+       val = XCAR (val);
+    }
+  return clean_local_selection_data (val);
 }
 
+DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal,
+       Spgtk_disown_selection_internal, 1, 3, 0,
+       doc: /* If we own the selection SELECTION, disown it.
+Disowning it means there is no such selection.
 
-DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p, 0, 2, 0,
-       doc: /* Whether there is an owner for the given X selection.
-SELECTION should be the name of the selection in question, typically
-one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.  (X expects
-these literal upper-case names.)  The symbol nil is the same as
-`PRIMARY', and t is the same as `SECONDARY'.
+Sets the last-change time for the selection to TIME-OBJECT (by default
+the time of the last event).
 
 TERMINAL should be a terminal object or a frame specifying the X
 server to query.  If omitted or nil, that stands for the selected
-frame's display, or the first available X display.
-
-On Nextstep, TERMINAL is unused.  */)
-  (Lisp_Object selection, Lisp_Object terminal)
+frame's display, or the first available X display.  */)
+  (Lisp_Object selection, Lisp_Object time_object, Lisp_Object terminal)
 {
+  guint32 timestamp;
+  GdkAtom selection_atom;
   struct frame *f = frame_for_pgtk_selection (terminal);
-  GtkClipboard *cb;
+  struct pgtk_display_info *dpyinfo;
 
-  if (!pgtk_selection_usable ())
+  if (!f)
     return Qnil;
 
-  if (!f)
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  CHECK_SYMBOL (selection);
+
+  /* Don't disown the selection when we're not the owner.  */
+  if (NILP (LOCAL_SELECTION (selection, dpyinfo)))
     return Qnil;
 
-  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+  selection_atom = symbol_to_gdk_atom (selection);
 
-  return gtk_clipboard_wait_is_text_available (cb) ? Qt : Qnil;
-}
+  block_input ();
+  if (NILP (time_object))
+    timestamp = dpyinfo->last_user_time;
+  else
+    CONS_TO_INTEGER (time_object, guint32, timestamp);
+  gdk_selection_owner_set_for_display (dpyinfo->display, NULL,
+                                      selection_atom, timestamp,
+                                      TRUE);
+  unblock_input ();
 
+  return Qt;
+}
 
-DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p, 0, 2, 0,
-       doc: /* Whether the current Emacs process owns the given X Selection.
+DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p,
+       0, 2, 0,
+       doc: /* Whether the current Emacs process owns the given selection.
 The arg should be the name of the selection in question, typically one of
 the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.
-\(Those are literal upper-case symbol names, since that's what X expects.)
+\(Those are literal upper-case symbol names, since that's what GDK expects.)
 For convenience, the symbol nil is the same as `PRIMARY',
 and t is the same as `SECONDARY'.
 
-TERMINAL should be a terminal object or a frame specifying the X
+TERMINAL should be a terminal object or a frame specifying the GDK
 server to query.  If omitted or nil, that stands for the selected
-frame's display, or the first available X display.
-
-On Nextstep, TERMINAL is unused.  */)
+frame's display, or the first available X display.  */)
   (Lisp_Object selection, Lisp_Object terminal)
 {
   struct frame *f = frame_for_pgtk_selection (terminal);
-  GtkClipboard *cb;
-  GObject *obj;
-  GQuark quark_data, quark_size;
-
-  if (!pgtk_selection_usable ())
-    return Qnil;
 
-  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
-  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
-                           &quark_size);
+  CHECK_SYMBOL (selection);
+  if (NILP (selection)) selection = QPRIMARY;
+  if (EQ (selection, Qt)) selection = QSECONDARY;
 
-  obj = gtk_clipboard_get_owner (cb);
-
-  return obj && g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil;
+  if (f && !NILP (LOCAL_SELECTION (selection, FRAME_DISPLAY_INFO (f))))
+    return Qt;
+  else
+    return Qnil;
 }
 
+DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p,
+       0, 2, 0,
+       doc: /* Whether there is an owner for the given selection.
+SELECTION should be the name of the selection in question, typically
+one of the symbols `PRIMARY', `SECONDARY', `CLIPBOARD', or
+`CLIPBOARD_MANAGER' (GDK expects these literal upper-case names.)  The
+symbol nil is the same as `PRIMARY', and t is the same as `SECONDARY'.
 
-DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal,
-       Spgtk_get_selection_internal, 2, 3, 0,
-       doc: /* Return text selected from some program.
-SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
-\(Those are literal upper-case symbol names, since that's what X expects.)
-TARGET-TYPE is the type of data desired, typically `STRING'.
-
-TERMINAL should be a terminal object or a frame specifying the X
+TERMINAL should be a terminal object or a frame specifying the GDK
 server to query.  If omitted or nil, that stands for the selected
-frame's display, or the first available display.  */)
-  (Lisp_Object selection_symbol, Lisp_Object target_type,
-   Lisp_Object terminal)
+frame's display, or the first available X display.  */)
+  (Lisp_Object selection, Lisp_Object terminal)
 {
+  GdkWindow *owner;
+  GdkAtom atom;
   struct frame *f = frame_for_pgtk_selection (terminal);
-  GtkClipboard *cb;
+  struct pgtk_display_info *dpyinfo;
 
-  CHECK_SYMBOL (selection_symbol);
-  CHECK_SYMBOL (target_type);
+  CHECK_SYMBOL (selection);
+  if (NILP (selection)) selection = QPRIMARY;
+  if (EQ (selection, Qt)) selection = QSECONDARY;
 
-  if (EQ (target_type, QMULTIPLE))
-    error ("Retrieving MULTIPLE selections is currently unimplemented");
   if (!f)
-    error ("PGTK selection unavailable for this frame");
-
-  if (!pgtk_selection_usable ())
     return Qnil;
 
-  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection_symbol);
-
-  GdkAtom target_atom = gdk_atom_intern (SSDATA (SYMBOL_NAME (target_type)), false);
-  GtkSelectionData *seldata = gtk_clipboard_wait_for_contents (cb, target_atom);
+  dpyinfo = FRAME_DISPLAY_INFO (f);
 
-  if (seldata == NULL)
-    return Qnil;
+  if (!NILP (LOCAL_SELECTION (selection, dpyinfo)))
+    return Qt;
 
-  const guchar *sd_data = gtk_selection_data_get_data (seldata);
-  int sd_len = gtk_selection_data_get_length (seldata);
-  int sd_format = gtk_selection_data_get_format (seldata);
-  GdkAtom sd_type = gtk_selection_data_get_data_type (seldata);
+  atom = symbol_to_gdk_atom (selection);
+  if (atom == 0) return Qnil;
+  block_input ();
+  owner = gdk_selection_owner_get_for_display (dpyinfo->display, atom);
+  unblock_input ();
+  return (owner ? Qt : Qnil);
+}
 
-  if (sd_format == 8)
-    {
-      Lisp_Object str, lispy_type;
+/* Called to handle GDK_SELECTION_NOTIFY events.
+   If it's the selection we are waiting for, stop waiting
+   by setting the car of reading_selection_reply to non-nil.
+   We store t there if the reply is successful, lambda if not.  */
 
-      str = make_unibyte_string ((char *) sd_data, sd_len);
-      /* Indicate that this string is from foreign selection by a text
-        property `foreign-selection' so that the caller of
-        x-get-selection-internal (usually x-get-selection) can know
-        that the string must be decode.  */
-      if (sd_type == gdk_atom_intern ("COMPOUND_TEXT", false))
-       lispy_type = QCOMPOUND_TEXT;
-      else if (sd_type == gdk_atom_intern ("UTF8_STRING", false))
-       lispy_type = QUTF8_STRING;
-      else if (sd_type == gdk_atom_intern ("text/plain;charset=utf-8", false))
-       lispy_type = Qtext_plain_charset_utf_8;
-      else
-       lispy_type = QSTRING;
-      Fput_text_property (make_fixnum (0), make_fixnum (sd_len),
-                         Qforeign_selection, lispy_type, str);
+void
+pgtk_handle_selection_notify (GdkEventSelection *event)
+{
+  /* GDK doesn't populate event->requestor, contrary to what the ICCCM
+     says should be done with SelectionNotify events.  */
 
-      gtk_selection_data_free (seldata);
-      return str;
-    }
+  if (event->selection != reading_which_selection)
+    return;
 
-  gtk_selection_data_free (seldata);
-  return Qnil;
+  XSETCAR (reading_selection_reply,
+          (event->property != GDK_NONE ? Qt : Qlambda));
 }
 
 void
@@ -499,7 +1769,19 @@ syms_of_pgtkselect (void)
   DEFSYM (QSECONDARY, "SECONDARY");
   DEFSYM (QTEXT, "TEXT");
   DEFSYM (QFILE_NAME, "FILE_NAME");
+  DEFSYM (QSTRING, "STRING");
+  DEFSYM (QINTEGER, "INTEGER");
+  DEFSYM (QTIMESTAMP, "TIMESTAMP");
+  DEFSYM (QTEXT, "TEXT");
   DEFSYM (QMULTIPLE, "MULTIPLE");
+  DEFSYM (QNULL, "NULL");
+  DEFSYM (QATOM, "ATOM");
+  DEFSYM (QTARGETS, "TARGETS");
+
+  DEFSYM (Qpgtk_sent_selection_functions,
+         "pgtk-sent-selection-functions");
+  DEFSYM (Qpgtk_lost_selection_functions,
+         "pgtk-lost-selection-functions");
 
   DEFSYM (Qforeign_selection, "foreign-selection");
   DEFSYM (QUTF8_STRING, "UTF8_STRING");
@@ -513,6 +1795,32 @@ syms_of_pgtkselect (void)
   defsubr (&Spgtk_selection_exists_p);
   defsubr (&Spgtk_selection_owner_p);
 
+  DEFVAR_LISP ("selection-converter-alist", Vselection_converter_alist,
+              doc: /* SKIP: real doc in xselect.c.  */);
+  Vselection_converter_alist = Qnil;
+
+  DEFVAR_LISP ("pgtk-lost-selection-functions", Vpgtk_lost_selection_functions,
+              doc: /* A list of functions to be called when Emacs loses a selection.
+\(This happens when some other client makes its own selection
+or when a Lisp program explicitly clears the selection.)
+The functions are called with one argument, the selection type
+\(a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD').  */);
+  Vpgtk_lost_selection_functions = Qnil;
+
+  DEFVAR_LISP ("pgtk-sent-selection-functions", Vpgtk_sent_selection_functions,
+              doc: /* A list of functions to be called when Emacs answers a selection request.
+The functions are called with three arguments:
+  - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');
+  - the selection-type which Emacs was asked to convert the
+    selection into before sending (for example, `STRING' or `LENGTH');
+  - a flag indicating success or failure for responding to the request.
+We might have failed (and declined the request) for any number of reasons,
+including being asked for a selection that we no longer own, or being asked
+to convert into a type that we don't know about or that is inappropriate.
+This hook doesn't let you change the behavior of Emacs's selection replies,
+it merely informs you that they have happened.  */);
+  Vpgtk_sent_selection_functions = Qnil;
+
   DEFVAR_LISP ("pgtk-sent-selection-hooks", Vpgtk_sent_selection_hooks,
               doc: /* A list of functions to be called when Emacs answers a selection request
 The functions are called with four arguments:
@@ -533,4 +1841,19 @@ This may cause crashes due to a GTK bug, which assumes that clients
 will connect to a single display.  It might also cause selections to
 not arrive at the correct display.  */);
   pgtk_enable_selection_on_multi_display = false;
+
+  DEFVAR_LISP ("pgtk-selection-alias-alist", Vpgtk_selection_alias_alist,
+    doc: /* List of selections to alias to another.
+It should be an alist of a selection name to another.  When a
+selection request arrives for the first selection, Emacs will respond
+as if the request was meant for the other.
+
+Note that this does not affect setting or owning selections.  */);
+  Vpgtk_selection_alias_alist = Qnil;
+
+  reading_selection_reply = Fcons (Qnil, Qnil);
+  staticpro (&reading_selection_reply);
+
+  property_change_reply = Fcons (Qnil, Qnil);
+  staticpro (&property_change_reply);
 }
diff --git a/src/pgtkselect.h b/src/pgtkselect.h
deleted file mode 100644 (file)
index fd9910b..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Definitions and headers for selection of pure Gtk+3.
-   Copyright (C) 1989, 1993, 2005, 2008-2022 Free Software Foundation,
-   Inc.
-
-This file is part of GNU Emacs.
-
-GNU Emacs 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 3 of the License, or (at
-your option) any later version.
-
-GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
-
-
-#include "dispextern.h"
-#include "frame.h"
-
-#ifdef HAVE_PGTK
-
-#include <gtk/gtk.h>
-
-extern void pgtk_selection_init (void);
-extern void pgtk_selection_lost (GtkWidget *, GdkEventSelection *, gpointer);
-
-#endif /* HAVE_PGTK */
index da958a6664ab51e78a49391089cec350251b7d76..91874ff58a570d969b1752ac0f6967c9330ca985 100644 (file)
@@ -61,7 +61,6 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "buffer.h"
 #include "font.h"
 #include "xsettings.h"
-#include "pgtkselect.h"
 #include "emacsgtkfixed.h"
 
 #ifdef GDK_WINDOWING_WAYLAND
@@ -290,6 +289,9 @@ static void
 evq_enqueue (union buffered_input_event *ev)
 {
   struct event_queue_t *evq = &event_q;
+  struct frame *frame;
+  struct pgtk_display_info *dpyinfo;
+
   if (evq->cap == 0)
     {
       evq->cap = 4;
@@ -303,6 +305,27 @@ evq_enqueue (union buffered_input_event *ev)
     }
 
   evq->q[evq->nr++] = *ev;
+
+  if (ev->ie.kind != SELECTION_REQUEST_EVENT
+      && ev->ie.kind != SELECTION_CLEAR_EVENT)
+    {
+      frame = NULL;
+
+      if (WINDOWP (ev->ie.frame_or_window))
+       frame = WINDOW_XFRAME (XWINDOW (ev->ie.frame_or_window));
+
+      if (FRAMEP (ev->ie.frame_or_window))
+       frame = XFRAME (ev->ie.frame_or_window);
+
+      if (frame)
+       {
+         dpyinfo = FRAME_DISPLAY_INFO (frame);
+
+         if (dpyinfo->last_user_time < ev->ie.timestamp)
+           dpyinfo->last_user_time = ev->ie.timestamp;
+       }
+    }
+
   raise (SIGIO);
 }
 
@@ -4809,16 +4832,16 @@ pgtk_any_window_to_frame (GdkWindow *window)
     return NULL;
 
   FOR_EACH_FRAME (tail, frame)
-  {
-    if (found)
-      break;
-    f = XFRAME (frame);
-    if (FRAME_PGTK_P (f))
-      {
-       if (pgtk_window_is_of_frame (f, window))
-         found = f;
-      }
-  }
+    {
+      if (found)
+       break;
+      f = XFRAME (frame);
+      if (FRAME_PGTK_P (f))
+       {
+         if (pgtk_window_is_of_frame (f, window))
+           found = f;
+       }
+    }
 
   return found;
 }
@@ -5868,8 +5891,7 @@ construct_mouse_click (struct input_event *result,
 }
 
 static gboolean
-button_event (GtkWidget *widget,
-             GdkEvent *event,
+button_event (GtkWidget *widget, GdkEvent *event,
              gpointer *user_data)
 {
   union buffered_input_event inev;
@@ -6174,6 +6196,8 @@ pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data)
   evq_enqueue (&inev);
 }
 
+static gboolean pgtk_selection_event (GtkWidget *, GdkEvent *, gpointer);
+
 void
 pgtk_set_event_handler (struct frame *f)
 {
@@ -6225,14 +6249,20 @@ pgtk_set_event_handler (struct frame *f)
                    G_CALLBACK (button_event), NULL);
   g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "scroll-event",
                    G_CALLBACK (scroll_event), NULL);
-  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
-                   G_CALLBACK (pgtk_selection_lost), NULL);
   g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "configure-event",
                    G_CALLBACK (configure_event), NULL);
   g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-data-received",
                    G_CALLBACK (drag_data_received), NULL);
   g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "draw",
                    G_CALLBACK (pgtk_handle_draw), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "property-notify-event",
+                   G_CALLBACK (pgtk_selection_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
+                   G_CALLBACK (pgtk_selection_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-request-event",
+                   G_CALLBACK (pgtk_selection_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-notify-event",
+                   G_CALLBACK (pgtk_selection_event), NULL);
   g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "event",
                    G_CALLBACK (pgtk_handle_event), NULL);
 }
@@ -6292,6 +6322,73 @@ same_x_server (const char *name1, const char *name2)
          && (*name2 == '.' || *name2 == '\0'));
 }
 
+static struct frame *
+pgtk_find_selection_owner (GdkWindow *window)
+{
+  Lisp_Object tail, tem;
+  struct frame *f;
+
+  FOR_EACH_FRAME (tail, tem)
+    {
+      f = XFRAME (tem);
+
+      if (FRAME_PGTK_P (f)
+         && (FRAME_GDK_WINDOW (f) == window))
+       return f;
+    }
+
+  return NULL;
+}
+
+static gboolean
+pgtk_selection_event (GtkWidget *widget, GdkEvent *event,
+                     gpointer user_data)
+{
+  struct frame *f;
+  union buffered_input_event inev;
+
+  if (event->type == GDK_PROPERTY_NOTIFY)
+    pgtk_handle_property_notify (&event->property);
+  else if (event->type == GDK_SELECTION_CLEAR
+          || event->type == GDK_SELECTION_REQUEST)
+    {
+      f = pgtk_find_selection_owner (event->selection.window);
+
+      if (f)
+       {
+         EVENT_INIT (inev.ie);
+
+         inev.sie.kind = (event->type == GDK_SELECTION_CLEAR
+                          ? SELECTION_CLEAR_EVENT
+                          : SELECTION_REQUEST_EVENT);
+
+         SELECTION_EVENT_DPYINFO (&inev.sie) = FRAME_DISPLAY_INFO (f);
+         SELECTION_EVENT_SELECTION (&inev.sie) = event->selection.selection;
+         SELECTION_EVENT_TIME (&inev.sie) = event->selection.time;
+
+         if (event->type == GDK_SELECTION_REQUEST)
+           {
+             /* FIXME: when does GDK destroy the requestor GdkWindow
+                object?
+
+                It would make sense to wait for the transfer to
+                complete.  But I don't know if GDK actually does
+                that.  */
+             SELECTION_EVENT_REQUESTOR (&inev.sie) = event->selection.requestor;
+             SELECTION_EVENT_TARGET (&inev.sie) = event->selection.target;
+             SELECTION_EVENT_PROPERTY (&inev.sie) = event->selection.property;
+           }
+
+         evq_enqueue (&inev);
+         return TRUE;
+       }
+    }
+  else if (event->type == GDK_SELECTION_NOTIFY)
+    pgtk_handle_selection_notify (&event->selection);
+
+  return FALSE;
+}
+
 /* Open a connection to X display DISPLAY_NAME, and return
    the structure that describes the open display.
    If we cannot contact the display, return null.  */
@@ -6525,8 +6622,6 @@ pgtk_term_init (Lisp_Object display_name, char *resource_name)
 
   xsettings_initialize (dpyinfo);
 
-  pgtk_selection_init ();
-
   pgtk_im_init (dpyinfo);
 
   g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-added",
index e31e62ae193953e07f0dd40a2d5d68d7ec641f43..86578be6b566f72b77c752a28a25c9bbbf3c705c 100644 (file)
@@ -127,8 +127,14 @@ struct pgtk_display_info
   /* The generic display parameters corresponding to this PGTK display. */
   struct terminal *terminal;
 
-  /* This says how to access this display in Gdk.  */
-  GdkDisplay *gdpy;
+  union
+  {
+    /* This says how to access this display through GDK.  */
+    GdkDisplay *gdpy;
+
+    /* An alias defined to make porting X code easier.  */
+    GdkDisplay *display;
+  };
 
   /* This is a cons cell of the form (NAME . FONT-LIST-CACHE).  */
   Lisp_Object name_list_element;
@@ -210,6 +216,9 @@ struct pgtk_display_info
   /* Time of last mouse movement.  */
   Time last_mouse_movement_time;
 
+  /* Time of last user interaction.  */
+  guint32 last_user_time;
+
   /* The scroll bar in which the last motion event occurred.  */
   void *last_mouse_scroll_bar;
 
@@ -443,10 +452,11 @@ enum
                                    FRAME_GTK_OUTER_WIDGET (f) :                \
                                    FRAME_GTK_WIDGET (f))
 
-/* aliases */
 #define FRAME_PGTK_VIEW(f)         FRAME_GTK_WIDGET (f)
 #define FRAME_X_WINDOW(f)          FRAME_GTK_OUTER_WIDGET (f)
 #define FRAME_NATIVE_WINDOW(f)     GTK_WINDOW (FRAME_X_WINDOW (f))
+#define FRAME_GDK_WINDOW(f)                    \
+  (gtk_widget_get_window (FRAME_GTK_WIDGET (f)))
 
 #define FRAME_X_DISPLAY(f)        (FRAME_DISPLAY_INFO (f)->gdpy)
 
@@ -484,6 +494,49 @@ enum
 #define FRAME_CR_SURFACE_DESIRED_HEIGHT(f) \
   ((f)->output_data.pgtk->cr_surface_desired_height)
 
+
+/* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
+   or SELECTION_CLEAR_EVENT, then its contents are really described
+   by this structure.  */
+
+/* For an event of kind SELECTION_REQUEST_EVENT,
+   this structure really describes the contents.  */
+
+struct selection_input_event
+{
+  ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH;
+  struct pgtk_display_info *dpyinfo;
+  /* We spell it with an "o" here because X does.  */
+  GdkWindow *requestor;
+  GdkAtom selection, target, property;
+  guint32 time;
+};
+
+/* Unlike macros below, this can't be used as an lvalue.  */
+INLINE GdkDisplay *
+SELECTION_EVENT_DISPLAY (struct selection_input_event *ev)
+{
+  return ev->dpyinfo->display;
+}
+#define SELECTION_EVENT_DPYINFO(eventp) \
+  ((eventp)->dpyinfo)
+/* We spell it with an "o" here because X does.  */
+#define SELECTION_EVENT_REQUESTOR(eventp)      \
+  ((eventp)->requestor)
+#define SELECTION_EVENT_SELECTION(eventp)      \
+  ((eventp)->selection)
+#define SELECTION_EVENT_TARGET(eventp) \
+  ((eventp)->target)
+#define SELECTION_EVENT_PROPERTY(eventp)       \
+  ((eventp)->property)
+#define SELECTION_EVENT_TIME(eventp)   \
+  ((eventp)->time)
+
+extern void pgtk_handle_selection_event (struct selection_input_event *);
+extern void pgtk_clear_frame_selections (struct frame *);
+extern void pgtk_handle_property_notify (GdkEventProperty *);
+extern void pgtk_handle_selection_notify (GdkEventSelection *);
+
 /* Display init/shutdown functions implemented in pgtkterm.c */
 extern struct pgtk_display_info *pgtk_term_init (Lisp_Object display_name,
                                                 char *resource_name);
@@ -493,7 +546,7 @@ extern void pgtk_term_shutdown (int sig);
 extern void pgtk_clear_frame (struct frame *f);
 extern char *pgtk_xlfd_to_fontname (const char *xlfd);
 
-/* Implemented in pgtkfns. */
+/* Implemented in pgtkfns.c.  */
 extern void pgtk_set_doc_edited (void);
 extern const char *pgtk_get_defaults_value (const char *key);
 extern const char *pgtk_get_string_resource (XrmDatabase rdb,