summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbrecht Schlosser <albrechts.fltk@online.de>2022-07-02 15:44:46 +0200
committerAlbrecht Schlosser <albrechts.fltk@online.de>2022-07-02 15:44:46 +0200
commitef72df0dc7c3c48373c52085a855ac6ce6df4868 (patch)
tree778004e96f82e206f04bd20e56c229e349581c65 /src
parentc5556291624eec58ed9de186474dfcc858dca691 (diff)
Improve X11 selection data transfer (INCR) protocol (#451)
This improves reading large selections and fixes one more bug. - Fix reading selection data size of clients that don't send the size. These clients don't respect the ICCCM. Using a default size instead. - Improve memory allocation by pre-allocating at least 4 MB and extending by 4 MB to reduce the number of realloc() operations. This may waste up to 4 MB buffer space but this is only an issue if clients don't send an appropriate size (see above). - Limit the initial allocation to 200 MB in case clients send higher values. This is very unlikely and might be a bug in these clients. Default extension as above would apply anyway. - Add more comments and optional debug statements (commented out). Note: reading selections (clipboard) from other clients using the INCR protocol is implemented but the opposite (providing large selections (clipboard) by using the INCR protocol is not.
Diffstat (limited to 'src')
-rw-r--r--src/Fl_x.cxx57
1 files changed, 46 insertions, 11 deletions
diff --git a/src/Fl_x.cxx b/src/Fl_x.cxx
index c07a69e09..38ab036c0 100644
--- a/src/Fl_x.cxx
+++ b/src/Fl_x.cxx
@@ -1025,13 +1025,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)) {
@@ -1047,7 +1059,7 @@ 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 {
XGetWindowProperty(fl_display, selevent.requestor, selevent.property, offset, 70000, True,
@@ -1055,11 +1067,16 @@ static long getIncrData(uchar* &data, const XSelectionEvent& selevent, size_t lo
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);
@@ -1089,6 +1106,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;
}
@@ -1316,9 +1334,26 @@ int fl_handle(const XEvent& thisevent)
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;