From daf081b888a092cdccb6e185096949c944aa47ee Mon Sep 17 00:00:00 2001 From: rfm Date: Mon, 26 Feb 2024 21:24:11 +0000 Subject: [PATCH] Rewrite code providing X selection property value using INCR. --- Tools/xpbs.m | 421 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 279 insertions(+), 142 deletions(-) diff --git a/Tools/xpbs.m b/Tools/xpbs.m index babdcafc..e592989e 100644 --- a/Tools/xpbs.m +++ b/Tools/xpbs.m @@ -129,6 +129,23 @@ +@interface Incremental : NSObject +{ + const char *pname; + const char *tname; +@public + NSData *data; + NSInteger offset; + int format; + int chunk; + Atom xType; + Window window; + Atom property; +} ++ (Incremental*) incrProperty: (Atom)p forRequestor: (Window)w create: (BOOL)c; +- (void) destroy; +@end + @interface XPbOwner : NSObject { NSPasteboard *_pb; @@ -177,7 +194,7 @@ + (void) xFixesSelectionNotify: (XFixesSelectionNotifyEvent*)xEvent; #endif - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent; - (Time) xTimeByAppending; -- (BOOL) xSendData: (unsigned char*) data format: (int) format +- (BOOL) xSendData: (NSData*) data format: (int) format items: (int) numItems type: (Atom) xType to: (Window) window property: (Atom) property; @end @@ -461,11 +478,43 @@ + (void) xPropertyNotify: (XPropertyEvent*)xEvent o = [self ownerByXPb: xEvent->atom]; if (o == nil) { - char *name = XGetAtomName(xDisplay, xEvent->atom); - NSDebugLLog(@"Pbs", @"Property notify for unknown property - '%s'.", - name); - XFree(name); - return; + Incremental *i; + + i = [Incremental incrProperty: xEvent->atom + forRequestor: xEvent->window + create: NO]; + if (i) + { + if (PropertyDelete == xEvent->state) + { + NSUInteger length = [i->data length]; + NSUInteger remain = length - i->offset; + NSUInteger size = (remain > i->chunk) ? i->chunk : remain; + + NSDebugLLog(@"Pbs", @"Sending %@", i); + XChangeProperty(xDisplay, i->window, i->property, + i->xType, i->format, PropModeReplace, + ((const unsigned char *)[i->data bytes]) + i->offset, + (((int)size * 8) / i->format)); + i->offset += size; + if (0 == size) + { + /* We just sent the final (empty) part of the data. + */ + NSDebugLLog(@"Pbs", @"Destroy %@", i); + [i destroy]; + } + } + return; + } + else + { + char *name = XGetAtomName(xDisplay, xEvent->atom); + NSDebugLLog(@"Pbs", @"Property notify for unknown property - '%s'.", + name); + XFree(name); + return; + } } if (xEvent->window != (Window)xAppWin) @@ -474,9 +523,10 @@ + (void) xPropertyNotify: (XPropertyEvent*)xEvent return; } - if (xEvent->time != 0) + if (xEvent->time > 0) { - NSDebugLLog(@"Pbs", @"Property notify time: %lu", (unsigned long)xEvent->time); + NSDebugLLog(@"Pbs", + @"Property append notify time: %lu", (unsigned long)xEvent->time); [o setTimeOfLastAppend: xEvent->time]; } } @@ -778,11 +828,33 @@ - (Atom) xPb return _xPb; } -static BOOL appendFailure; +static BOOL changePropertyFailure; +static XErrorEvent changePropertyError; static int xErrorHandler(Display *d, XErrorEvent *e) { - appendFailure = YES; + changePropertyFailure = YES; + changePropertyError = *e; + + if (GSDebugSet(@"Pbs")) + { + char buf[256]; + + buf[sizeof(buf)-1] = '\0'; + XGetErrorText(d, e->error_code, buf, sizeof(buf)-1); + NSLog(@"xErrorHandler type %d: %s (%d)\n" + @"\tResource ID: 0x%lx\n" + @"\tSerial Num: %lu\n" + @"\tError code: %u\n" + @"\tRequest op code: %u major, %u minor", + e->type, buf, e->error_code, + e->resourceid, + e->serial, + e->error_code, + e->request_code, + e->minor_code); + } + return 0; } @@ -903,17 +975,17 @@ - (NSMutableData*) getSelectionData: (XSelectionEvent*)xEvent do { status = XGetWindowProperty(xDisplay, - xEvent->requestor, - xEvent->property, - long_offset, // offset - long_length, - False, // Aug 2011 - changed to False (don't delete property) - req_type, - &actual_type, - &actual_format, - &number_items, - &bytes_remaining, - &data); + xEvent->requestor, + xEvent->property, + long_offset, // offset + long_length, + False, // Aug 2011 - changed to False (don't delete property) + req_type, + &actual_type, + &actual_format, + &number_items, + &bytes_remaining, + &data); if (GSDebugSet(@"Pbs")) { @@ -1214,10 +1286,19 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent { NSArray *types = [_pb types]; Atom xType = XG_NULL; - unsigned char *data = 0; + NSData *data = nil; int format = 0; int numItems = 0; + if (GSDebugSet(@"Pbs")) + { + char *t = XGetAtomName(xDisplay, xEvent->target); + + NSLog(@"xProvideSelection: %s (%lud)", + t, xEvent->target); + XFree(t); + } + if (xEvent->target == XG_TARGETS) { unsigned numTypes = 0; @@ -1265,65 +1346,44 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent xType = XA_ATOM; format = 32; numItems = numTypes; - data = malloc(numTypes*sizeof(Atom)); - memcpy(data, xTypes, numTypes*sizeof(Atom)); + data = [NSData dataWithBytes: (const void*)xTypes + length: numTypes*sizeof(Atom)]; } else if (xEvent->target == XG_TIMESTAMP) { xType = XA_INTEGER; format = 32; numItems = 1; - data = malloc(sizeof(int)); - memcpy(data, &_timeOfSetSelectionOwner, sizeof(int)); + data = [NSData dataWithBytes: (const void*)_timeOfSetSelectionOwner + length: sizeof(_timeOfSetSelectionOwner)]; } else if (xEvent->target == XG_USER) { - NSString *s = NSUserName(); - NSData *d = nil; + NSString *s = NSUserName(); xType = XG_TEXT; format = 8; - d = [s dataUsingEncoding: NSISOLatin1StringEncoding]; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + data = [s dataUsingEncoding: NSISOLatin1StringEncoding]; + numItems = [data length]; } else if (xEvent->target == XG_OWNER_OS) { - NSString *s = [[NSProcessInfo processInfo] operatingSystemName]; - NSData *d = nil; + NSString *s = [[NSProcessInfo processInfo] operatingSystemName]; xType = XG_TEXT; format = 8; - d = [s dataUsingEncoding: NSISOLatin1StringEncoding]; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + data = [s dataUsingEncoding: NSISOLatin1StringEncoding]; + numItems = [data length]; } else if ((xEvent->target == XG_HOST_NAME) - || (xEvent->target == XG_HOSTNAME)) + || (xEvent->target == XG_HOSTNAME)) { - NSString *s = [[NSProcessInfo processInfo] hostName]; - NSData *d = nil; + NSString *s = [[NSProcessInfo processInfo] hostName]; xType = XG_TEXT; format = 8; - d = [s dataUsingEncoding: NSISOLatin1StringEncoding]; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + data = [s dataUsingEncoding: NSISOLatin1StringEncoding]; + numItems = [data length]; } else if (xEvent->target == AnyPropertyType) { @@ -1398,10 +1458,10 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent } } } - else if ((xEvent->target == XG_COMPOUND_TEXT) && - [types containsObject: NSStringPboardType]) + else if ((xEvent->target == XG_COMPOUND_TEXT) + && [types containsObject: NSStringPboardType]) { - NSString *s = [_pb stringForType: NSStringPboardType]; + NSString *s = [_pb stringForType: NSStringPboardType]; const char *d; int status; @@ -1417,21 +1477,24 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent XCompoundTextStyle, &textProperty); if (status == Success) { + NSMutableData *m; + NSAssert(textProperty.format == 8, @"textProperty.format == 8"); numItems = textProperty.nitems; - data = malloc(numItems + 1); - memcpy(data, textProperty.value, numItems + 1); + m = [NSMutableData dataWithCapacity: numItems + 1]; + [m setLength: numItems + 1]; + memcpy([m mutableBytes], textProperty.value, numItems + 1); XFree((void *)textProperty.value); + data = m; } } } - else if (((xEvent->target == XG_UTF8_STRING) || - (xEvent->target == XA_STRING) || - (xEvent->target == XG_TEXT)) && - [types containsObject: NSStringPboardType]) + else if (((xEvent->target == XG_UTF8_STRING) + || (xEvent->target == XA_STRING) + || (xEvent->target == XG_TEXT)) + && [types containsObject: NSStringPboardType]) { - NSString *s = [_pb stringForType: NSStringPboardType]; - NSData *d = nil; + NSString *s = [_pb stringForType: NSStringPboardType]; xType = xEvent->target; format = 8; @@ -1442,75 +1505,48 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent */ if (xType == XG_UTF8_STRING) { - d = [s dataUsingEncoding: NSUTF8StringEncoding]; + data = [s dataUsingEncoding: NSUTF8StringEncoding]; } else if ((xType == XA_STRING) || (xType == XG_TEXT)) { - d = [s dataUsingEncoding: NSISOLatin1StringEncoding]; + data = [s dataUsingEncoding: NSISOLatin1StringEncoding]; } - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + numItems = [data length]; } - else if ((xEvent->target == XG_FILE_NAME) && - [types containsObject: NSFilenamesPboardType]) + else if ((xEvent->target == XG_FILE_NAME) + && [types containsObject: NSFilenamesPboardType]) { NSArray *names = [_pb propertyListForType: NSFilenamesPboardType]; - NSString *file = [[names lastObject] stringByStandardizingPath]; - NSURL *url = [[NSURL alloc] initWithScheme: NSURLFileScheme - host: @"localhost" - path: file]; - NSString *s = [url absoluteString]; - NSData *d; + NSString *file = [[names lastObject] stringByStandardizingPath]; + NSURL *url = [[NSURL alloc] initWithScheme: NSURLFileScheme + host: @"localhost" + path: file]; + NSString *s = [url absoluteString]; RELEASE(url); - d = [s dataUsingEncoding: NSISOLatin1StringEncoding]; + data = [s dataUsingEncoding: NSISOLatin1StringEncoding]; xType = xEvent->target; format = 8; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + numItems = [data length]; } else if (((xEvent->target == XG_MIME_RTF) - || (xEvent->target == XG_MIME_APP_RTF) - || (xEvent->target == XG_MIME_TEXT_RICHTEXT)) - && [types containsObject: NSRTFPboardType]) + || (xEvent->target == XG_MIME_APP_RTF) + || (xEvent->target == XG_MIME_TEXT_RICHTEXT)) + && [types containsObject: NSRTFPboardType]) { - NSData *d = [_pb dataForType: NSRTFPboardType]; - + data = [_pb dataForType: NSRTFPboardType]; xType = xEvent->target; format = 8; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + numItems = [data length]; } - else if ((xEvent->target == XG_MIME_TIFF) && - [types containsObject: NSTIFFPboardType]) + else if ((xEvent->target == XG_MIME_TIFF) + && [types containsObject: NSTIFFPboardType]) { - NSData *d = [_pb dataForType: NSTIFFPboardType]; - + data = [_pb dataForType: NSTIFFPboardType]; xType = xEvent->target; format = 8; - if (d != nil) - { - numItems = [d length]; - data = malloc(numItems + 1); - if (data) - [d getBytes: data]; - } + numItems = [data length]; } // FIXME: Support more types else @@ -1526,11 +1562,36 @@ - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent to: xEvent->requestor property: xEvent->property]; } -- (BOOL) xSendData: (unsigned char*) data format: (int) format +- (BOOL) xSendData: (NSData*) data format: (int) format items: (int) numItems type: (Atom) xType to: (Window) window property: (Atom) property { - BOOL status = NO; + static int32_t chunk = 0; + BOOL status = NO; + + if (GSDebugSet(@"Pbs")) + { + char *t = XGetAtomName(xDisplay, xType); + char *p = XGetAtomName(xDisplay, property); + + NSLog(@"xSendData:format:items:type:to:property:" + @" %d, %d, '%s' (%lu), %lu, '%s' (%lu)", + format, numItems, t, xType, window, p, property); + XFree(p); + XFree(t); + } + + /* Assume properties ebigger than a quarter of the maximum + * request size need to use INCR (ICCCM section 2.5) + */ + if (0 == chunk) + { + chunk = XExtendedMaxRequestSize(xDisplay) / 4; + if (0 == chunk) + { + chunk = XMaxRequestSize(xDisplay) / 4; + } + } /* * If we have managed to convert data of the appropritate type, we must now @@ -1539,34 +1600,38 @@ - (BOOL) xSendData: (unsigned char*) data format: (int) format * manager puts a limit on the data size we can use. * This is not thread-safe - but I think that's a general problem with X. */ - if (data != 0 && numItems != 0 && format != 0) + if (data && numItems != 0 && format != 0) { - int (*oldHandler)(Display*, XErrorEvent*); - int mode = PropModeReplace; - int pos = 0; - int maxItems = 4096 * 8 / format; - - appendFailure = NO; - oldHandler = XSetErrorHandler(xErrorHandler); + int maxItems = chunk * 8 / format; - while (appendFailure == NO && pos < numItems) - { - if (pos + maxItems > numItems) - { - maxItems = numItems - pos; - } + if (numItems > maxItems) + { + Incremental *i; + + /* We have too much data to set in the property in one go, + * so we use the INCR protocol to send chunks. + */ + i = [Incremental incrProperty: property + forRequestor: window + create: YES]; + ASSIGN(i->data, data); + i->offset = 0; + i->format = format; + i->chunk = chunk; + i->xType = xType; + + NSDebugLLog(@"Pbs", @"Starting %@", i); + + XChangeProperty(xDisplay, window, property, + XG_INCR, 32, PropModeReplace, (const unsigned char*)&chunk, 1); + } + else + { XChangeProperty(xDisplay, window, property, - xType, format, mode, &data[pos*format/8], maxItems); - mode = PropModeAppend; - pos += maxItems; - XSync(xDisplay, False); - } - free(data); - XSetErrorHandler(oldHandler); - if (appendFailure == NO) - { - status = YES; + xType, format, PropModeReplace, + (const unsigned char *)[data bytes], numItems); } + status = YES; } return status; } @@ -1637,6 +1702,78 @@ - (Time) xTimeByAppending @end +@implementation Incremental + +static NSMutableArray *active = nil; + ++ (Incremental*) incrProperty: (Atom)p forRequestor: (Window)w create: (BOOL)c +{ + NSUInteger pos = [active count]; + Incremental *i = nil; + + while (pos-- > 0) + { + i = [active objectAtIndex: pos]; + if (i->window == w && i->property == p) + { + return AUTORELEASE(RETAIN(i)); + } + } + i = nil; + if (c) + { + if (nil == active) + { + active = [NSMutableArray new]; + } + i = [self new]; + i->window = w; + i->property = p; + [active addObject: i]; + + /* We need property deletion events from the window. + */ + XSelectInput(xDisplay, i->window, PropertyChangeMask); + } + return AUTORELEASE(i); +} + +- (void) dealloc +{ + RELEASE(data); + if (pname) XFree((void*)pname); + if (tname) XFree((void*)tname); + DEALLOC +} + +- (NSString*) description +{ + if (pname == NULL) + { + pname = XGetAtomName(xDisplay, property); + } + if (tname == NULL) + { + tname = XGetAtomName(xDisplay, xType); + } + return [NSString stringWithFormat: + @"", + tname, pname, window, (unsigned long long)offset, + (unsigned long long)[data length]]; +} + +- (void) destroy +{ + if (window != xAppWin) + { + /* No longer interested in events from this window. + */ + XSelectInput(xDisplay, xAppWin, 0); + } + [active removeObjectIdenticalTo: self]; +} +@end + // This are copies of functions from XGContextEvent.m.