Blob Blame History Raw
From 7ed06a7b65c6f91da50b6664c742208ce520a983 Mon Sep 17 00:00:00 2001
From: Albrecht Schlosser <albrechts.fltk@online.de>
Date: Sat, 2 Jul 2022 16:08:20 +0200
Subject: [PATCH] Improve X11 selection data transfer (INCR) protocol (issue
 451)

This improves reading large selections and fixes one more bug.

Backported from 'master' (fltk 1.4.0). For more information see
  commit c5556291624eec58ed9de186474dfcc858dca691,
  commit ef72df0dc7c3c48373c52085a855ac6ce6df4868 and issue 451
  https://github.com/fltk/fltk/issues/451
---
 src/Fl_x.cxx | 64 ++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 49 insertions(+), 15 deletions(-)

diff --git a/src/Fl_x.cxx b/src/Fl_x.cxx
index c8b9c06..a3ec6f2 100644
--- a/src/Fl_x.cxx
+++ b/src/Fl_x.cxx
@@ -1275,13 +1275,25 @@ static bool getNextEvent(XEvent *event_return)
 
 static long getIncrData(uchar* &data, const XSelectionEvent& selevent, size_t lower_bound) {
   // fprintf(stderr,"Incremental transfer starting due to INCR property\n");
+  // fprintf(stderr, "[getIncrData:%d] lower_bound [in ] =%10ld\n", __LINE__, lower_bound);
+  const size_t alloc_min =   4 * 1024 * 1024; // min. initial allocation
+  const size_t alloc_max = 200 * 1024 * 1024; // max. initial allocation
+  const size_t alloc_inc =   4 * 1024 * 1024; // (min.) increase if necessary
   size_t total = 0;
+  size_t data_size = lower_bound + 1;
+  if (data_size < alloc_min) {
+    data_size = alloc_min;
+  } else if (data_size > alloc_max) {
+    data_size = alloc_max;
+  }
+  // fprintf(stderr, "[getIncrData:%d] initial alloc.    =%10ld\n", __LINE__, data_size);
+
   XEvent event;
   XDeleteProperty(fl_display, selevent.requestor, selevent.property);
-  data = (uchar*)realloc(data, lower_bound);
+  data = (uchar*)realloc(data, data_size);
   if (!data) {
-    // fprintf(stderr, "[getIncrData:%d] realloc() FAILED, size = %ld\n", __LINE__, lower_bound);
-    Fl::fatal("Clipboard data transfer failed, size %ld too large.", lower_bound);
+    // fprintf(stderr, "[getIncrData:%d] realloc() FAILED, size = %ld\n", __LINE__, data_size);
+    Fl::fatal("Clipboard data transfer failed, size %ld is too large.", data_size);
   }
   for (;;) {
     if (!getNextEvent(&event)) {
@@ -1297,20 +1309,24 @@ static long getIncrData(uchar* &data, const XSelectionEvent& selevent, size_t lo
       unsigned long bytes_after;
       unsigned char* prop = 0;
       long offset = 0;
-      size_t num_bytes;
+      size_t num_bytes = 0;
       //size_t slice_size = 0;
-      do
-      {
+      do {
 	XGetWindowProperty(fl_display, selevent.requestor, selevent.property, offset, 70000, True,
 			   AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
 	num_bytes = nitems * (actual_format / 8);
 	offset += num_bytes/4;
 	// slice_size += num_bytes;
-        if (total + num_bytes > lower_bound) {
-	  data = (uchar*)realloc(data, total + num_bytes);
+        if (total + num_bytes + bytes_after + 1 > data_size) {
+          data_size += alloc_inc;
+          if (total + num_bytes + bytes_after + 1 > data_size)
+            data_size = total + num_bytes + bytes_after + 1;
+          // printf(" -- realloc(%9ld), total=%10ld, num_bytes=%7ld, bytes_after=%7ld (%7ld), required=%10ld\n",
+          //        data_size, total, num_bytes, bytes_after, num_bytes + bytes_after, total + num_bytes + bytes_after + 1);
+          data = (uchar*)realloc(data, data_size);
           if (!data) {
-            // fprintf(stderr, "[getIncrData():%d] realloc() FAILED, size = %ld\n", __LINE__, total + num_bytes);
-            Fl::fatal("Clipboard data transfer failed, size %ld too large.", total + num_bytes);
+            // fprintf(stderr, "[getIncrData():%d] realloc() FAILED, size = %ld\n", __LINE__, data_size);
+            Fl::fatal("Clipboard data transfer failed, size %ld is too large.", data_size);
 	  }
 	}
         memcpy(data + total, prop, num_bytes);
@@ -1340,6 +1356,7 @@ static long getIncrData(uchar* &data, const XSelectionEvent& selevent, size_t lo
     }
   }
   XDeleteProperty(fl_display, selevent.requestor, selevent.property);
+  // fprintf(stderr, "[getIncrData:%d] total data  [out] =%10ld\n", __LINE__, (long)total);
   return (long)total;
 }
 
@@ -1488,20 +1505,37 @@ fprintf(stderr,"\n");*/
 	return true;
       }
 	if (actual == fl_INCR) {
-	  // an X11 "integer" (32 bit), the "lower bound" of the clipboard size (see ICCCM)
-	  size_t lower_bound = (*(unsigned long *)portion) & 0xFFFFFFFF;
-	  // fprintf(stderr, "[fl_handle:%d] INCR: lower_bound = %ld\n", __LINE__, lower_bound);
+	  // From ICCCM: "The contents of the INCR property will be an integer, which
+	  // represents a lower bound on the number of bytes of data in the selection."
+	  //
+	  // However, some X clients don't set the integer ("lower bound") in the INCR
+	  // property, hence 'count' below is zero and we must not access '*portion'.
+	  // Debug:
+#if (0)
+	  fprintf(stderr,
+	        "[fl_handle(SelectionNotify/INCR):%d] actual=%ld (INCR), format=%d, count=%ld, remaining=%ld",
+	        __LINE__, actual, format, count, remaining);
+	  if (portion && count > 0) {
+	  fprintf(stderr,
+	          ", portion=%p (%ld)", portion, *(long*)portion);
+	  }
+	  fprintf(stderr, "\n");
+#endif
+	  size_t lower_bound = 0;
+	  if (portion && count > 0) {
+	    lower_bound = *(unsigned long *)portion;
+	  }
 	  bytesread = getIncrData(sn_buffer, xevent.xselection, lower_bound);
 	  XFree(portion);
 	  break;
 	}
 	// Make sure we got something sane...
       if ((portion == NULL) || (format != 8) || (count == 0)) {
-	if (portion) { XFree(portion); portion = 0; }
+	if (portion) XFree(portion);
         return true;
       }
       sn_buffer = (unsigned char*)realloc(sn_buffer, bytesread+count+remaining+1);
-      memcpy(sn_buffer+bytesread, portion, count);
+      memcpy(sn_buffer + bytesread, portion, count);
       if (portion) { XFree(portion); portion = 0; }
       bytesread += count;
       // Cannot trust data to be null terminated