From ba9c56e37ed874cc3e5694c343ab21b54f504d62 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 8 Aug 2026 08:41:16 +0200 Subject: [PATCH 1/3] gpbs: timeout INCR transfer receive to stop pasteboard hangs gpbs blocks forever in the X selection incremental (INCR) receive loop when the selection owner dies or stalls mid-transfer, making the whole pasteboard unresponsive and forcing gpbs to be killed. Wait on the X connection descriptor with a 5s deadline before each INCR chunk; on timeout abandon the transfer and discard the partial data instead of blocking on XNextEvent indefinitely. --- Tools/xpbs.m | 86 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/Tools/xpbs.m b/Tools/xpbs.m index 4dd2b583..0f4514a4 100644 --- a/Tools/xpbs.m +++ b/Tools/xpbs.m @@ -40,6 +40,9 @@ #if HAVE_XFIXES #include #endif +#include +#include +#include /* * Non-predefined atoms that are used in the X selection mechanism @@ -344,6 +347,12 @@ + (BOOL) initializePasteboard return NO; } + /* Direct synchronous X errors (e.g. from an over-eager XChangeProperty) to + * our handler so they are captured rather than terminating the server via + * the default Xlib error handler. + */ + XSetErrorHandler(xErrorHandler); + /* * Set up atoms for use in X selection mechanism. */ @@ -1285,24 +1294,62 @@ - (void) xSelectionNotify: (XSelectionEvent*)xEvent { [md setCapacity: size * 10]; } - while (wait) - { - XNextEvent(xDisplay, &event); - - if (event.type == PropertyNotify - && event.xproperty.state == PropertyNewValue) - { + { + /* The X selection owner may die or stall mid-transfer. Without + * a timeout, XNextEvent below would block forever and gpbs would + * stop answering pasteboard requests, forcing it to be killed. + * So wait on the X connection descriptor for the next chunk with + * a deadline and abandon the transfer if nothing arrives. + */ + NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 5.0]; + int xfd = XConnectionNumber(xDisplay); + BOOL timedOut = NO; + + while (wait) + { + if (XQLength(xDisplay) == 0) + { + fd_set rfds; + struct timeval tv; + NSTimeInterval remain = [limit timeIntervalSinceNow]; + + if (remain <= 0.0) + { + NSDebugLLog(@"Pbs", + @"Timed out waiting for INCR data for %@", _name); + timedOut = YES; + break; /* Timeout */ + } + FD_ZERO(&rfds); + FD_SET(xfd, &rfds); + tv.tv_sec = (long)remain; + tv.tv_usec = (long)((remain - (NSTimeInterval)tv.tv_sec) + * 1000000.0); + if (select(xfd + 1, &rfds, NULL, NULL, &tv) <= 0) + { + NSDebugLLog(@"Pbs", + @"Timed out waiting for INCR data for %@", _name); + timedOut = YES; + break; /* Timeout or error */ + } + } + + XNextEvent(xDisplay, &event); + + if (event.type == PropertyNotify + && event.xproperty.state == PropertyNewValue) + { long length; /* Getting the property data also deletes the property, * telling the other end to send the next chunk. * An empty chunk indicates end of transfer. */ - if ((length = [self getSelectionData: xEvent + if ((length = [self getSelectionData: xEvent type: &actual_type size: size into: md]) > 0) - { + { if (GSDebugSet(@"INCR")) { char *name = XGetAtomName(xDisplay, actual_type); @@ -1311,13 +1358,20 @@ - (void) xSelectionNotify: (XSelectionEvent*)xEvent XFree(name); } count++; - } - else - { - wait = NO; - } - } - } + } + else + { + wait = NO; + } + } + } + if (timedOut) + { + /* Discard the partial data so a truncated transfer is not + * presented as complete pasteboard contents. */ + [md setLength: 0]; + } + } if ([md length] == 0) { md = nil; From 24b484b34878f1799ae1677d8362823ced181886 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 8 Aug 2026 08:50:17 +0200 Subject: [PATCH 2/3] gpbs: add forward declaration for xErrorHandler The error handler is referenced in +initializePasteboard before its definition; GCC (as used on CI) rejects the implicit declaration. Declare it explicitly so the build succeeds everywhere. --- Tools/xpbs.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/xpbs.m b/Tools/xpbs.m index 0f4514a4..92ee1238 100644 --- a/Tools/xpbs.m +++ b/Tools/xpbs.m @@ -324,6 +324,7 @@ @interface XDragPbOwner : XPbOwner #if HAVE_XFIXES static int xFixesEventBase; #endif +static int xErrorHandler(Display *d, XErrorEvent *e); @implementation XPbOwner From a8b0889595fcfd75345170f8e8f07280dec5855b Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 8 Aug 2026 12:31:45 +0200 Subject: [PATCH 3/3] Increase timeout to 20 seconds Allow for big images from Gimp not to time out --- Tools/xpbs.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/xpbs.m b/Tools/xpbs.m index 92ee1238..d7be6217 100644 --- a/Tools/xpbs.m +++ b/Tools/xpbs.m @@ -1302,7 +1302,7 @@ - (void) xSelectionNotify: (XSelectionEvent*)xEvent * So wait on the X connection descriptor for the next chunk with * a deadline and abandon the transfer if nothing arrives. */ - NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 5.0]; + NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 20.0]; int xfd = XConnectionNumber(xDisplay); BOOL timedOut = NO;