emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master e961a31507 4/4: Speed up handling X selection requests


From: Po Lu
Subject: master e961a31507 4/4: Speed up handling X selection requests
Date: Thu, 1 Dec 2022 01:34:01 -0500 (EST)

branch: master
commit e961a31507e6fba86a5d45fec7fa616e80028882
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Speed up handling X selection requests
    
    * etc/NEWS: Announce speedup.
    * src/alloc.c (garbage_collect): Call mark_xselect.
    * src/xftfont.c (xftfont_end_for_frame): Fix crash on display IO
    error.
    * src/xselect.c (struct selection_data, struct transfer): New
    structures.
    (outstading_transfers): New variable.
    (SELECTED_EVENTS, x_selection_request_lisp_error): Stop checking
    cs->nofree.
    (x_catch_errors_unwind): Delete function.
    (c_size_for_format, x_size_for_format, selection_data_for_offset)
    (selection_data_size, transfer_selecting_event)
    (x_continue_selection_transfer, x_remove_selection_transfers)
    (x_selection_transfer_timeout): New functions.
    (x_reply_selection_request): When handling selection requests,
    never wait for property notifications synchronously.  Instead,
    write out the selection data as the client reads it from the
    event loop.
    (x_handle_selection_request, x_convert_selection)
    (x_handle_property_notify, x_get_window_property)
    (lisp_data_to_selection_data): Don't ever use pointers to Lisp
    string data! Instead, just store the string object itself.
    (syms_of_xselect): Initialize outstanding transfer list.
    (syms_of_xselect_for_pdumper):
    * src/xterm.c (x_delete_display): Remove outstanding selection
    transfers.
    * src/xterm.h: Update prototypes.
---
 etc/NEWS      |   5 +
 src/alloc.c   |   1 +
 src/xftfont.c |   6 +
 src/xselect.c | 782 ++++++++++++++++++++++++++++++++++++++++------------------
 src/xterm.c   |   5 +
 src/xterm.h   |   6 +
 6 files changed, 565 insertions(+), 240 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index f675d6b45f..9b8edde515 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,11 @@ applies, and please also update docstrings as needed.
 
 * Changes in Emacs 30.1
 
+** X selection requests are now handled much faster and asynchronously.
+This means it should be less necessary to disable the likes of
+`select-active-regions' when Emacs is running over a slow network
+connection.
+
 
 * Editing Changes in Emacs 30.1
 
diff --git a/src/alloc.c b/src/alloc.c
index ff8b4b40aa..e443acd72f 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -6220,6 +6220,7 @@ garbage_collect (void)
 
 #ifdef HAVE_X_WINDOWS
   mark_xterm ();
+  mark_xselect ();
 #endif
 
 #ifdef HAVE_NS
diff --git a/src/xftfont.c b/src/xftfont.c
index 6043ef9f94..b9686db2a7 100644
--- a/src/xftfont.c
+++ b/src/xftfont.c
@@ -628,6 +628,12 @@ xftfont_shape (Lisp_Object lgstring, Lisp_Object direction)
 static int
 xftfont_end_for_frame (struct frame *f)
 {
+  /* XftDrawDestroy tries to access dpyinfo->display, which could've
+     been destroyed by now, causing Emacs to crash.  The alternative
+     is to leak the XftDraw, but that's better than a crash.  */
+  if (!FRAME_X_DISPLAY (f))
+    return 0;
+
   block_input ();
   XftDraw *xft_draw;
 
diff --git a/src/xselect.c b/src/xselect.c
index 844ef7220a..c635f84065 100644
--- a/src/xselect.c
+++ b/src/xselect.c
@@ -77,18 +77,20 @@ static void x_send_client_event (Lisp_Object, Lisp_Object, 
Lisp_Object,
 #define TRACE0(fmt)            (void) 0
 #define TRACE1(fmt, a0)                (void) 0
 #define TRACE2(fmt, a0, a1)    (void) 0
+#define TRACE3(fmt, a0, a1, a2) (void) 0
 #endif
 
 /* Bytes needed to represent 'long' data.  This is as per libX11; it
    is not necessarily sizeof (long).  */
 #define X_LONG_SIZE 4
 
-/* If this is a smaller number than the max-request-size of the display,
-   emacs will use INCR selection transfer when the selection is larger
-   than this.  The max-request-size is usually around 64k, so if you want
-   emacs to use incremental selection transfers when the selection is
-   smaller than that, set this.  I added this mostly for debugging the
-   incremental transfer stuff, but it might improve server performance.
+/* If this is a smaller number than the max-request-size of the
+   display, Emacs will use INCR selection transfer when the selection
+   is larger than this.  The max-request-size is usually around 64k,
+   so if you want emacs to use incremental selection transfers when
+   the selection is smaller than that, set this.  I added this mostly
+   for debugging the incremental transfer stuff, but it might improve
+   server performance.
 
    This value cannot exceed INT_MAX / max (X_LONG_SIZE, sizeof (long))
    because it is multiplied by X_LONG_SIZE and by sizeof (long) in
@@ -101,7 +103,9 @@ static void x_send_client_event (Lisp_Object, Lisp_Object, 
Lisp_Object,
 static int
 selection_quantum (Display *display)
 {
-  long mrs = XExtendedMaxRequestSize (display);
+  long mrs;
+
+  mrs = XExtendedMaxRequestSize (display);
 
   if (!mrs)
     mrs = XMaxRequestSize (display);
@@ -472,19 +476,62 @@ x_decline_selection_request (struct selection_input_event 
*event)
 
 struct selection_data
 {
+  /* Pointer to the selection data.  */
   unsigned char *data;
+
+  /* A Lisp_Object containing the selection data.  This is either
+     Qnil, or `data' is NULL.  If non-nil, then this must be a string
+     whose contents will be written out verbatim.  */
+  Lisp_Object string;
+
+  /* The size, in number of items, of the selection data.
+     The value is meaningless if string is non-nil.  */
   ptrdiff_t size;
+
+  /* The format of the selection data.  */
   int format;
+
+  /* The type of the selection data.  */
   Atom type;
-  bool nofree;
+
+  /* The property describing the selection data.  */
   Atom 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;
+
+  /* The next piece of selection data in the current selection request
+     stack frame.  This can be NULL.  */
   struct selection_data *next;
 };
 
+/* Structure representing a single outstanding selection request (or
+   subrequest if MULTIPLE is being used.)  */
+
+struct transfer
+{
+  /* The requestor of this transfer.  */
+  Window requestor;
+
+  /* The current offset in items into the selection data, and the
+     number of items to send with each ChangeProperty request.  */
+  size_t offset, items_per_request;
+
+  /* The display info associated with the transfer.  */
+  struct x_display_info *dpyinfo;
+
+  /* The converted selection data.  */
+  struct selection_data data;
+
+  /* The next and last selection transfers on this list.  */
+  struct transfer *next, *last;
+
+  /* The atimer for the timeout.  */
+  struct atimer *timeout;
+
+  /* Flags.  */
+  int flags;
+};
+
+#define SELECTED_EVENTS 1
+
 struct x_selection_request
 {
   /* The last element in this stack.  */
@@ -511,6 +558,46 @@ struct x_selection_request
 
 struct x_selection_request *selection_request_stack;
 
+/* List of all outstanding selection transfers which are currently
+   being processed.  */
+
+struct transfer outstanding_transfers;
+
+
+
+struct prop_location
+{
+  int identifier;
+  Display *display;
+  Window window;
+  Atom property;
+  int desired_state;
+  bool arrived;
+  struct prop_location *next;
+};
+
+static int prop_location_identifier;
+
+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;
+}
+
+
+
 static void
 x_push_current_selection_request (struct selection_input_event *se,
                                  struct x_display_info *dpyinfo)
@@ -554,7 +641,7 @@ x_selection_request_lisp_error (void)
   for (cs = frame->converted_selections; cs; cs = next)
     {
       next = cs->next;
-      if (! cs->nofree && cs->data)
+      if (cs->data)
        xfree (cs->data);
       xfree (cs);
     }
@@ -564,250 +651,400 @@ x_selection_request_lisp_error (void)
     x_decline_selection_request (frame->request);
 }
 
-static void
-x_catch_errors_unwind (void)
+
+
+static size_t
+c_size_for_format (int format)
 {
-  block_input ();
-  x_uncatch_errors ();
-  unblock_input ();
+  switch (format)
+    {
+    case 8:
+      return sizeof (char);
+
+    case 16:
+      return sizeof (short);
+
+    case 32:
+      return sizeof (long);
+    }
+
+  emacs_abort ();
 }
-
 
-/* 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.  */
+static size_t
+x_size_for_format (int format)
+{
+  switch (format)
+    {
+    case 8:
+      return 1;
 
-/* Keep a list of the property changes that are awaited.  */
+    case 16:
+      return 2;
 
-struct prop_location
+    case 32:
+      return 4;
+    }
+
+  emacs_abort ();
+}
+
+/* Return a pointer to the remaining part of the selection data, given
+   a pointer to a struct selection_data and an offset in items.  Place
+   the number of items remaining in REMAINING.  Garbage collection
+   must not happen, or the returned pointer becomes invalid.  */
+
+static unsigned char *
+selection_data_for_offset (struct selection_data *data,
+                          long offset, size_t *remaining)
 {
-  int identifier;
-  Display *display;
-  Window window;
-  Atom property;
-  int desired_state;
-  bool arrived;
-  struct prop_location *next;
-};
+  unsigned char *base;
+  size_t size;
 
-static int prop_location_identifier;
+  if (!NILP (data->string))
+    {
+      base = SDATA (data->string);
+      size = SBYTES (data->string);
+    }
+  else
+    {
+      base = data->data;
+      size = data->size;
+    }
 
-static Lisp_Object property_change_reply;
+  if (offset >= size)
+    {
+      *remaining = 0;
+      return NULL;
+    }
 
-static struct prop_location *property_change_reply_object;
+  base += (offset * c_size_for_format (data->format));
+  *remaining = size - offset;
+  return base;
+}
 
-static struct prop_location *property_change_wait_list;
+/* Return the size, in bytes transferred to the X server, of
+   data->size items of selection data in data->format-bit
+   quantities.  */
+
+static size_t
+selection_data_size (struct selection_data *data)
+{
+  size_t scratch;
+
+  if (!NILP (data->string))
+    return SBYTES (data->string);
+
+  switch (data->format)
+    {
+    case 8:
+      return (size_t) data->size;
+
+    case 16:
+      if (INT_MULTIPLY_WRAPV (data->size, 2, &scratch))
+       return SIZE_MAX;
+
+      return scratch;
+
+    case 32:
+      if (INT_MULTIPLY_WRAPV (data->size, 4, &scratch))
+       return SIZE_MAX;
+
+      return scratch;
+    }
+
+  /* The specified format is invalid.  */
+  emacs_abort ();
+}
+
+/* Return whether or not another outstanding selection transfer is
+   still selecting for events on the specified requestor window.  */
+
+static bool
+transfer_selecting_event (struct x_display_info *dpyinfo,
+                         Window requestor)
+{
+  struct transfer *next;
+
+  next = outstanding_transfers.next;
+  for (; next != &outstanding_transfers; next = next->next)
+    {
+      if (next->requestor == requestor
+         && next->dpyinfo == dpyinfo)
+       return true;
+    }
+
+  return false;
+}
+
+/* Cancel the specified selection transfer.  When called by
+   `start_transfer', the transfer may be partially formed.  */
 
 static void
-set_property_change_object (struct prop_location *location)
+x_cancel_selection_transfer (struct transfer *transfer)
 {
-  /* 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;
+  xfree (transfer->data.data);
+
+  if (transfer->next)
+    {
+      transfer->next->last = transfer->last;
+      transfer->last->next = transfer->next;
+    }
+
+  if (transfer->flags & SELECTED_EVENTS
+      && !transfer_selecting_event (transfer->dpyinfo,
+                                   transfer->requestor)
+      /* This can be called from x_delete_display.  */
+      && transfer->dpyinfo->display)
+    XSelectInput (transfer->dpyinfo->display,
+                 transfer->requestor, NoEventMask);
+
+  cancel_atimer (transfer->timeout);
+  xfree (transfer);
 }
 
-
-/* Send the reply to a selection request event EVENT.  */
+static void
+x_selection_transfer_timeout (struct atimer *atimer)
+{
+  struct transfer *transfer;
 
-#ifdef TRACE_SELECTION
-static int x_reply_selection_request_cnt;
-#endif  /* TRACE_SELECTION */
+  transfer = atimer->client_data;
+  x_cancel_selection_transfer (transfer);
+}
+
+/* Start a selection transfer to write the specified selection data to
+   its requestor.  If the data is small enough, write it to the
+   requestor window and return.  Otherwise, start INCR transfer and
+   begin listening for PropertyNotify events on the requestor.  */
 
 static void
-x_reply_selection_request (struct selection_input_event *event,
-                           struct x_display_info *dpyinfo)
+x_start_selection_transfer (struct x_display_info *dpyinfo, Window requestor,
+                           struct selection_data *data)
 {
-  XEvent reply_base;
-  XSelectionEvent *reply = &(reply_base.xselection);
-  Display *display = SELECTION_EVENT_DISPLAY (event);
-  Window window = SELECTION_EVENT_REQUESTOR (event);
-  ptrdiff_t bytes_remaining;
-  int max_bytes = selection_quantum (display);
-  specpdl_ref count = SPECPDL_INDEX ();
-  struct selection_data *cs;
-  struct x_selection_request *frame;
+  struct transfer *transfer;
+  intmax_t timeout;
+  intmax_t secs;
+  int nsecs;
+  size_t remaining, max_size;
+  unsigned char *xdata;
+  unsigned long data_size;
+
+  timeout = max (0, x_selection_timeout);
+  secs = timeout / 1000;
+  nsecs = (timeout % 1000) * 1000000;
+
+  if ((Atom *) data->data
+      == &selection_request_stack->conversion_fail_tag)
+    return;
 
-  frame = selection_request_stack;
+  transfer = xzalloc (sizeof *transfer);
+  transfer->requestor = requestor;
+  transfer->dpyinfo = dpyinfo;
 
-  reply->type = SelectionNotify;
-  reply->display = display;
-  reply->requestor = window;
-  reply->selection = SELECTION_EVENT_SELECTION (event);
-  reply->time = SELECTION_EVENT_TIME (event);
-  reply->target = SELECTION_EVENT_TARGET (event);
-  reply->property = SELECTION_EVENT_PROPERTY (event);
-  if (reply->property == None)
-    reply->property = reply->target;
+  transfer->timeout = start_atimer (ATIMER_RELATIVE,
+                                   make_timespec (secs, nsecs),
+                                   x_selection_transfer_timeout,
+                                   transfer);
 
-  block_input ();
-  /* The protected block contains wait_for_property_change, which can
-     run random lisp code (process handlers) or signal.  Therefore, we
-     put the x_uncatch_errors call in an unwind.  */
-  record_unwind_protect_void (x_catch_errors_unwind);
-  x_catch_errors (display);
+  /* Note that DATA is copied into transfer.  DATA->data is then set
+     to NULL, giving the struct transfer ownership over the selection
+     data.  */
 
-  /* 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)
-    {
-      if (cs->property == None)
-       continue;
+  transfer->data = *data;
+  data->data = NULL;
 
-      bytes_remaining = cs->size;
-      bytes_remaining *= cs->format >> 3;
-      if (bytes_remaining <= max_bytes)
-       {
-         /* Send all the data at once, with minimal handshaking.  */
-         TRACE1 ("Sending all %"pD"d bytes", bytes_remaining);
-         XChangeProperty (display, window, cs->property,
-                          cs->type, cs->format, PropModeReplace,
-                          cs->data, cs->size);
-       }
-      else
-       {
-         /* Send an INCR tag to initiate incremental transfer.  */
-         long value[1];
-
-         TRACE2 ("Start sending %"pD"d bytes incrementally (%s)",
-                 bytes_remaining, XGetAtomName (display, cs->property));
-         cs->wait_object
-           = expect_property_change (display, window, cs->property,
-                                     PropertyDelete);
-
-         /* XChangeProperty expects an array of long even if long is
-            more than 32 bits.  */
-         value[0] = min (bytes_remaining, X_LONG_MAX);
-         XChangeProperty (display, window, cs->property,
-                          dpyinfo->Xatom_INCR, 32, PropModeReplace,
-                          (unsigned char *) value, 1);
-         XSelectInput (display, window, PropertyChangeMask);
-       }
+  /* Finally, transfer now holds a reference to data->string, if it is
+     present.  GC cannot be allowed to happen until this function
+     returns.  */
+  data->string = Qnil;
+
+  /* Now, try to write the selection data.  If it is bigger than
+     selection_quantum (dpyinfo->display), start incremental transfer
+     and link the transfer onto the list of pending selections.
+     Otherwise, write the transfer at once.  */
+
+  max_size = selection_quantum (dpyinfo->display);
+
+  TRACE3 (" x_start_selection_transfer: transferring to 0x%lx.  "
+         "transfer consists of %zu bytes, quantum being %zu",
+         requestor, selection_data_size (&transfer->data),
+         max_size);
+
+  if (selection_data_size (&transfer->data) > max_size)
+    {
+      /* Begin incremental selection transfer.  First, calculate how
+        many elements it is ok to write for every ChangeProperty
+        request.  */
+      transfer->items_per_request
+       = (max_size / x_size_for_format (transfer->data.format));
+      TRACE1 (" x_start_selection_transfer: starting incremental"
+             " selection transfer, with %zu items per request",
+             transfer->items_per_request);
+
+      /* Next, link the transfer onto the list of pending selection
+        transfers.  */
+      transfer->next = outstanding_transfers.next;
+      transfer->last = &outstanding_transfers;
+      transfer->next->last = transfer;
+      transfer->last->next = transfer;
+
+      /* Now, write the INCR property to begin incremental selection
+        transfer.  offset is currently 0.  */
+
+      data_size = selection_data_size (&transfer->data);
+
+      x_ignore_errors_for_next_request (dpyinfo);
+      XChangeProperty (dpyinfo->display, requestor,
+                      transfer->data.property,
+                      dpyinfo->Xatom_INCR, 32, PropModeReplace,
+                      (unsigned char *) &data_size, 1);
+
+      /* This assumes that Emacs is not selecting for any other events
+        from the requestor!
+
+         If the holder of some manager selections (i.e. the settings
+         manager) asks Emacs for selection data, things will subtly go
+         wrong.  */
+      XSelectInput (dpyinfo->display, requestor, PropertyChangeMask);
+      transfer->flags |= SELECTED_EVENTS;
+      x_stop_ignoring_errors (dpyinfo);
     }
+  else
+    {
+      /* Write the property data now.  */
+      xdata = selection_data_for_offset (&transfer->data,
+                                        0, &remaining);
+      eassert (remaining <= INT_MAX);
+
+      TRACE1 (" x_start_selection_transfer:  writing"
+             " %zu elements directly to requestor window",
+             remaining);
+
+      x_ignore_errors_for_next_request (dpyinfo);
+      XChangeProperty (dpyinfo->display, requestor,
+                      transfer->data.property,
+                      transfer->data.type,
+                      transfer->data.format,
+                      PropModeReplace, xdata, remaining);
+      x_stop_ignoring_errors (dpyinfo);
+
+      /* Next, get rid of the transfer.  */
+      x_cancel_selection_transfer (transfer);
+    }
+}
 
-  /* Now issue the SelectionNotify event.  */
-  XSendEvent (display, window, False, 0, &reply_base);
-  XFlush (display);
+/* Write out the next piece of data that is part of the specified
+   selection transfer.  If no more data remains to be written, write
+   the EOF property and complete the transfer.  */
 
-#ifdef TRACE_SELECTION
-  {
-    char *sel = XGetAtomName (display, reply->selection);
-    char *tgt = XGetAtomName (display, reply->target);
-    TRACE3 ("Sent SelectionNotify: %s, target %s (%d)",
-           sel, tgt, ++x_reply_selection_request_cnt);
-    if (sel) XFree (sel);
-    if (tgt) XFree (tgt);
-  }
-#endif /* TRACE_SELECTION */
+static void
+x_continue_selection_transfer (struct transfer *transfer)
+{
+  size_t remaining;
+  unsigned char *xdata;
 
-  /* 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;
-       bool had_errors_p = x_had_errors_p (display);
+  xdata = selection_data_for_offset (&transfer->data,
+                                    transfer->offset,
+                                    &remaining);
+  remaining = min (remaining, transfer->items_per_request);
 
-        /* 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 ();
+  if (!remaining)
+    {
+      /* The transfer is finished.  Write zero-length property data to
+        signal EOF and remove the transfer.  */
+      TRACE0 (" x_continue_selection_transfer: writing 0 items to"
+             " indicate EOF");
+      x_ignore_errors_for_next_request (transfer->dpyinfo);
+      XChangeProperty (transfer->dpyinfo->display,
+                      transfer->requestor,
+                      transfer->data.property,
+                      transfer->data.type,
+                      transfer->data.format,
+                      PropModeReplace,
+                      NULL, 0);
+      x_stop_ignoring_errors (transfer->dpyinfo);
+      TRACE0 (" x_continue_selection_transfer: done sending incrementally");
+
+      x_cancel_selection_transfer (transfer);
+    }
+  else
+    {
+      TRACE2 (" x_continue_selection_transfer: writing %zu items"
+             "; current offset is %zu", remaining, transfer->offset);
+      eassert (remaining <= INT_MAX);
+
+      x_ignore_errors_for_next_request (transfer->dpyinfo);
+      XChangeProperty (transfer->dpyinfo->display,
+                      transfer->requestor,
+                      transfer->data.property,
+                      transfer->data.type,
+                      transfer->data.format,
+                      PropModeReplace, xdata,
+                      remaining);
+      x_stop_ignoring_errors (transfer->dpyinfo);
+      transfer->offset += remaining;
+    }
+}
 
-       bytes_remaining = cs->size;
-       bytes_remaining *= format_bytes;
+void
+x_remove_selection_transfers (struct x_display_info *dpyinfo)
+{
+  struct transfer *next, *last;
 
-       /* Wait for the requestor to ack by deleting the property.
-          This can run Lisp code (process handlers) or signal.  */
-       if (! had_errors_p)
-         {
-           TRACE1 ("Waiting for ACK (deletion of %s)",
-                   XGetAtomName (display, cs->property));
-           wait_for_property_change (cs->wait_object);
-         }
-       else
-         unexpect_property_change (cs->wait_object);
+  next = outstanding_transfers.next;
+  while (next != &outstanding_transfers)
+    {
+      last = next;
+      next = next->next;
 
-       while (bytes_remaining)
-         {
-           int i = ((bytes_remaining < max_bytes)
-                    ? bytes_remaining
-                    : max_bytes) / format_bytes;
-           block_input ();
-
-           cs->wait_object
-             = expect_property_change (display, window, cs->property,
-                                       PropertyDelete);
-
-           TRACE1 ("Sending increment of %d elements", i);
-           TRACE1 ("Set %s to increment data",
-                   XGetAtomName (display, cs->property));
-
-           /* Append the next chunk of data to the property.  */
-           XChangeProperty (display, window, cs->property,
-                            cs->type, cs->format, PropModeAppend,
-                            cs->data, i);
-           bytes_remaining -= i * format_bytes;
-           cs->data += i * ((cs->format == 32) ? sizeof (long)
-                            : format_bytes);
-           XFlush (display);
-           had_errors_p = x_had_errors_p (display);
-            /* See comment above about property_change_reply.  */
-            set_property_change_object (cs->wait_object);
-           unblock_input ();
-
-           if (had_errors_p) break;
-
-           /* Wait for the requestor to ack this chunk by deleting
-              the property.  This can run Lisp code or signal.  */
-           TRACE1 ("Waiting for increment ACK (deletion of %s)",
-                   XGetAtomName (display, cs->property));
-           wait_for_property_change (cs->wait_object);
-         }
+      if (last->dpyinfo == dpyinfo)
+       x_cancel_selection_transfer (last);
+    }
+}
 
-       /* 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))
-         XSelectInput (display, window, 0);
-
-       TRACE1 ("Set %s to a 0-length chunk to indicate EOF",
-               XGetAtomName (display, cs->property));
-       XChangeProperty (display, window, cs->property,
-                        cs->type, cs->format, PropModeReplace,
-                        cs->data, 0);
-       TRACE0 ("Done sending incrementally");
-      }
+/* Send the reply to a selection request event EVENT.  */
 
-  /* rms, 2003-01-03: I think I have fixed this bug.  */
-  /* The window we're communicating with may have been deleted
-     in the meantime (that's a real situation from a bug report).
-     In this case, there may be events in the event queue still
-     referring to the deleted window, and we'll get a BadWindow error
-     in XTread_socket when processing the events.  I don't have
-     an idea how to fix that.  gerd, 2001-01-98.   */
-  /* 2004-09-10: XSync and UNBLOCK so that possible protocol errors are
-     delivered before uncatch errors.  */
-  XSync (display, False);
-  unblock_input ();
+static void
+x_reply_selection_request (struct selection_input_event *event,
+                           struct x_display_info *dpyinfo)
+{
+  XEvent message;
+  struct selection_data *cs;
+  struct x_selection_request *frame;
 
-  /* GTK queues events in addition to the queue in Xlib.  So we
-     UNBLOCK to enter the event loop and get possible errors delivered,
-     and then BLOCK again because x_uncatch_errors requires it.  */
   block_input ();
-  /* This calls x_uncatch_errors.  */
-  unbind_to (count, Qnil);
+  frame = selection_request_stack;
+
+  message.xselection.type = SelectionNotify;
+  message.xselection.display = dpyinfo->display;
+  message.xselection.requestor = SELECTION_EVENT_REQUESTOR (event);
+  message.xselection.selection = SELECTION_EVENT_SELECTION (event);
+  message.xselection.time = SELECTION_EVENT_TIME (event);
+  message.xselection.target = SELECTION_EVENT_TARGET (event);
+  message.xselection.property = SELECTION_EVENT_PROPERTY (event);
+
+  if (message.xselection.property == None)
+    message.xselection.property = message.xselection.target;
+
+  /* For each of the converted selections, start a write transfer from
+     Emacs to the requestor.  */
+  for (cs = frame->converted_selections; cs; cs = cs->next)
+    x_start_selection_transfer (dpyinfo,
+                               SELECTION_EVENT_REQUESTOR (event),
+                               cs);
+
+
+  /* Send the SelectionNotify event to the requestor, telling it that
+     the property data has arrived.  */
+  x_ignore_errors_for_next_request (dpyinfo);
+  XSendEvent (dpyinfo->display, SELECTION_EVENT_REQUESTOR (event),
+             False, NoEventMask, &message);
+  x_stop_ignoring_errors (dpyinfo);
   unblock_input ();
 }
-
-/* Handle a SelectionRequest event EVENT.
-   This is called from keyboard.c when such an event is found in the queue.  */
+
+/* Handle a SelectionRequest event EVENT.  This is called from
+   keyboard.c when such an event is found in the queue.  */
 
 static void
 x_handle_selection_request (struct selection_input_event *event)
@@ -916,11 +1153,12 @@ x_handle_selection_request (struct selection_input_event 
*event)
          if (!subsuccess)
            ASET (multprop, 2*j+1, Qnil);
        }
+
       /* Save conversion results */
       lisp_data_to_selection_data (dpyinfo, multprop, &cs);
 
-      /* If cs.type is ATOM, change it to ATOM_PAIR.  This is because
-        the parameters to a MULTIPLE are ATOM_PAIRs.  */
+      /* If cs.type is ATOM, change it to ATOM_PAIR.  This is
+        because the parameters to a MULTIPLE are ATOM_PAIRs.  */
 
       if (cs.type == XA_ATOM)
        cs.type = dpyinfo->Xatom_ATOM_PAIR;
@@ -929,11 +1167,14 @@ x_handle_selection_request (struct selection_input_event 
*event)
                       cs.type, cs.format, PropModeReplace,
                       cs.data, cs.size);
       success = true;
+
+      xfree (cs.data);
     }
   else
     {
       if (property == None)
        property = SELECTION_EVENT_TARGET (event);
+
       success = x_convert_selection (selection_symbol,
                                     target_symbol, property,
                                     false, dpyinfo,
@@ -942,14 +1183,14 @@ x_handle_selection_request (struct selection_input_event 
*event)
 
  DONE:
 
-  if (pushed)
-    selection_request_stack->converted = true;
-
   if (success)
     x_reply_selection_request (event, dpyinfo);
   else
     x_decline_selection_request (event);
 
+  if (pushed)
+    selection_request_stack->converted = true;
+
   /* Run the `x-sent-selection-functions' abnormal hook.  */
   if (!NILP (Vx_sent_selection_functions)
       && !BASE_EQ (Vx_sent_selection_functions, Qunbound))
@@ -960,6 +1201,7 @@ x_handle_selection_request (struct selection_input_event 
*event)
  REALLY_DONE:
 
   unbind_to (count, Qnil);
+  return;
 }
 
 /* Perform the requested selection conversion, and write the data to
@@ -995,11 +1237,10 @@ x_convert_selection (Lisp_Object selection_symbol,
          cs->data = ((unsigned char *)
                      &selection_request_stack->conversion_fail_tag);
          cs->size = 1;
+         cs->string = Qnil;
          cs->format = 32;
          cs->type = XA_ATOM;
-         cs->nofree = true;
          cs->property = property;
-         cs->wait_object = NULL;
          cs->next = frame->converted_selections;
          frame->converted_selections = cs;
        }
@@ -1010,11 +1251,11 @@ x_convert_selection (Lisp_Object selection_symbol,
   /* Otherwise, record the converted selection to binary.  */
   cs = xmalloc (sizeof *cs);
   cs->data = NULL;
-  cs->nofree = true;
+  cs->string = Qnil;
   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;
 }
@@ -1274,6 +1515,10 @@ void
 x_handle_property_notify (const XPropertyEvent *event)
 {
   struct prop_location *rest;
+  struct transfer *next;
+#ifdef TRACE_SELECTION
+  char *name;
+#endif
 
   for (rest = property_change_wait_list; rest; rest = rest->next)
     {
@@ -1283,9 +1528,16 @@ x_handle_property_notify (const XPropertyEvent *event)
          && rest->display == event->display
          && rest->desired_state == event->state)
        {
+#ifdef TRACE_SELECTION
+         name = XGetAtomName (event->display, event->atom);
+
          TRACE2 ("Expected %s of property %s",
                  (event->state == PropertyDelete ? "deletion" : "change"),
-                 XGetAtomName (event->display, event->atom));
+                 name ? name : "unknown");
+
+         if (name)
+           XFree (name);
+#endif
 
          rest->arrived = true;
 
@@ -1297,6 +1549,26 @@ x_handle_property_notify (const XPropertyEvent *event)
          return;
        }
     }
+
+  /* Look for a property change for an outstanding selection
+     transfer.  */
+  next = outstanding_transfers.next;
+  while (next != &outstanding_transfers)
+    {
+      if (next->dpyinfo->display == event->display
+         && next->requestor == event->window
+         && next->data.property == event->atom
+         && event->state == PropertyDelete)
+       {
+         TRACE1 ("Expected PropertyDelete event arrived from the"
+                 " requestor window %lx", next->requestor);
+
+         x_continue_selection_transfer (next);
+         return;
+       }
+
+      next = next->next;
+    }
 }
 
 static void
@@ -1450,10 +1722,10 @@ x_get_window_property (Display *display, Window window, 
Atom property,
   /* Maximum value for TOTAL_SIZE.  It cannot exceed PTRDIFF_MAX - 1
      and SIZE_MAX - 1, for an extra byte at the end.  And it cannot
      exceed LONG_MAX * X_LONG_SIZE, for XGetWindowProperty.  */
-  ptrdiff_t total_size_max =
-    ((min (PTRDIFF_MAX, SIZE_MAX) - 1) / x_long_size < LONG_MAX
-     ? min (PTRDIFF_MAX, SIZE_MAX) - 1
-     : LONG_MAX * x_long_size);
+  ptrdiff_t total_size_max
+    = ((min (PTRDIFF_MAX, SIZE_MAX) - 1) / x_long_size < LONG_MAX
+       ? min (PTRDIFF_MAX, SIZE_MAX) - 1
+       : LONG_MAX * x_long_size);
 
   block_input ();
 
@@ -1949,7 +2221,6 @@ lisp_data_to_selection_data (struct x_display_info 
*dpyinfo,
   Lisp_Object type = Qnil;
 
   eassert (cs != NULL);
-  cs->nofree = false;
 
   if (CONSP (obj) && SYMBOLP (XCAR (obj)))
     {
@@ -1959,8 +2230,10 @@ lisp_data_to_selection_data (struct x_display_info 
*dpyinfo,
        obj = XCAR (obj);
     }
 
+  /* This is not the same as declining.  */
+
   if (EQ (obj, QNULL) || (EQ (type, QNULL)))
-    {                          /* This is not the same as declining */
+    {
       cs->format = 32;
       cs->size = 0;
       cs->data = NULL;
@@ -1971,12 +2244,14 @@ lisp_data_to_selection_data (struct x_display_info 
*dpyinfo,
       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;
+      cs->size = -1;
+      cs->data = NULL;
+      cs->string = obj;
     }
   else if (SYMBOLP (obj))
     {
@@ -3002,10 +3277,11 @@ syms_of_xselect (void)
 
   reading_selection_reply = Fcons (Qnil, Qnil);
   staticpro (&reading_selection_reply);
-
   staticpro (&property_change_reply);
 
-  /* FIXME: Duplicate definition in nsselect.c.  */
+  outstanding_transfers.next = &outstanding_transfers;
+  outstanding_transfers.last = &outstanding_transfers;
+
   DEFVAR_LISP ("selection-converter-alist", Vselection_converter_alist,
               doc: /* An alist associating X Windows selection-types with 
functions.
 These functions are called to convert the selection, with three args:
@@ -3126,3 +3402,29 @@ syms_of_xselect_for_pdumper (void)
   prop_location_identifier = 0;
   property_change_reply = Fcons (Qnil, Qnil);
 }
+
+void
+mark_xselect (void)
+{
+  struct transfer *next;
+  struct x_selection_request *frame;
+  struct selection_data *cs;
+
+  /* Mark all the strings being used as selection data.  A string that
+     is still reachable is always reachable via either the selection
+     request stack or the list of outstanding transfers.  */
+
+  next = outstanding_transfers.next;
+  while (next != &outstanding_transfers)
+    {
+      mark_object (next->data.string);
+      next = next->next;
+    }
+
+  frame = selection_request_stack;
+  for (; frame; frame = frame->last)
+    {
+      for (cs = frame->converted_selections; cs; cs = cs->next)
+       mark_object (cs->string);
+    }
+}
diff --git a/src/xterm.c b/src/xterm.c
index 44fad6e8d5..18879669cd 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -30600,8 +30600,13 @@ x_delete_display (struct x_display_info *dpyinfo)
       last = ie;
     }
 
+  /* Delete selection requests bound for dpyinfo from the keyboard
+     buffer.  */
   x_delete_selection_requests (dpyinfo);
 
+  /* And remove any outstanding selection transfers.  */
+  x_remove_selection_transfers (dpyinfo);
+
   if (next_noop_dpyinfo == dpyinfo)
     next_noop_dpyinfo = dpyinfo->next;
 
diff --git a/src/xterm.h b/src/xterm.h
index ee429e9c68..6420457a88 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1798,6 +1798,8 @@ extern void x_handle_property_notify (const 
XPropertyEvent *);
 extern void x_handle_selection_notify (const XSelectionEvent *);
 extern void x_handle_selection_event (struct selection_input_event *);
 extern void x_clear_frame_selections (struct frame *);
+extern void x_remove_selection_transfers (struct x_display_info *);
+
 extern Lisp_Object x_atom_to_symbol (struct x_display_info *, Atom);
 extern Atom symbol_to_x_atom (struct x_display_info *, Lisp_Object);
 
@@ -1829,6 +1831,10 @@ extern Atom x_intern_cached_atom (struct x_display_info 
*, const char *,
 extern char *x_get_atom_name (struct x_display_info *, Atom, bool *)
   ATTRIBUTE_MALLOC ATTRIBUTE_DEALLOC_FREE;
 
+extern void mark_xselect (void);
+
+/* Misc definitions.  */
+
 #ifdef USE_GTK
 extern bool xg_set_icon (struct frame *, Lisp_Object);
 extern bool xg_set_icon_from_xpm_data (struct frame *, const char **);



reply via email to

[Prev in Thread] Current Thread [Next in Thread]