From 1fadc2b271c674e29b2a33bac181e23995e7a304 Mon Sep 17 00:00:00 2001 From: Skyler Grey Date: Fri, 6 Dec 2024 16:43:12 +0000 Subject: [PATCH] feat(iOS): Copy all mime-types to clipboard Previously we only copied data under UTIs, but this isn't enough information for us to do a proper rich paste, and UTI-copied data of certain types can be slightly changed by the process of copying it. Luckily, iOS's clipboard allows copying data under any identifier, including our mime-types, so we can keep copying data under UTIs for compatibility but also copy data under mime-types for our own use. When we read the clipboard, we still need to read UTIs as other apps might have placed them on the keyboard, but we want to avoid overwriting any mime-type data because - as mentioned - it can be slightly changed by the copying process (if it's text or images, we need to wrap it in Apple's types before copying it to the clipboard) Signed-off-by: Skyler Grey Change-Id: Ifb13fd910d63a985ad015335658815af3bd69f6b --- ios/Mobile/DocumentViewController.mm | 63 ++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/ios/Mobile/DocumentViewController.mm b/ios/Mobile/DocumentViewController.mm index 5fef554bec71..1d649834aa46 100644 --- a/ios/Mobile/DocumentViewController.mm +++ b/ios/Mobile/DocumentViewController.mm @@ -326,6 +326,9 @@ - (bool)getClipboardContent:(out NSMutableDictionary *)content { [content setValue:[NSData dataWithBytes:outStreams[i] length:outSizes[i]] forKey:uti.identifier]; } } + + // But to preserve the data we need, we'll always also export the raw, unaltered bytes + [content setValue:[NSData dataWithBytes:outStreams[i] length:outSizes[i]] forKey:identifier]; } bResult = true; } @@ -357,6 +360,9 @@ - (bool)getClipboardContent:(out NSMutableDictionary *)content { [content setValue:[NSData dataWithBytes:outStreams[i] length:outSizes[i]] forKey:uti.identifier]; } } + + // But to preserve the data we need, we'll always also export the raw, unaltered bytes + [content setValue:[NSData dataWithBytes:outStreams[i] length:outSizes[i]] forKey:identifier]; } bResult = true; } @@ -366,21 +372,62 @@ - (bool)getClipboardContent:(out NSMutableDictionary *)content { return bResult; } +- (void)setClipboardContent:(UIPasteboard *)pasteboard { + NSMutableDictionary * pasteboardItems = [NSMutableDictionary new]; + + if (pasteboard.numberOfItems != 0) { + for (NSString * identifier in pasteboard.items[0]) + { + UTType * uti = [UTType typeWithIdentifier:identifier]; + NSString * mime = identifier; + + if (uti != nil) { + mime = uti.preferredMIMEType; + } + + if (mime == nil) { + LOG_WRN("UTI " << [identifier UTF8String] << " did not have associated mime type when deserializing clipboard, skipping..."); + continue; + } + + NSData * value = [pasteboard dataForPasteboardType:identifier]; + + if (uti != nil && [pasteboardItems objectForKey:mime] != nil) { + // We export both mime and UTI keys, don't overwrite the mime-type ones with the UTI ones + continue; + } + + if (value != nil) { + [pasteboardItems setObject:value forKey:mime]; + } + } + } + + const char * pInMimeTypes[pasteboardItems.count]; + size_t pInSizes[pasteboardItems.count]; + const char * pInStreams[pasteboardItems.count]; + + size_t i = 0; + + for (NSString * mime in pasteboardItems) { + pInMimeTypes[i] = [mime UTF8String]; + pInStreams[i] = (const char*)[pasteboardItems[mime] bytes]; + pInSizes[i] = [pasteboardItems[mime] length]; + i++; + } + + DocumentData::get(self.document->appDocId).loKitDocument->setClipboard(pasteboardItems.count, pInMimeTypes, pInSizes, pInStreams); +} + - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { if ([message.name isEqualToString:@"clipboard"]) { if ([message.body isEqualToString:@"read"]) { UIPasteboard * pasteboard = [UIPasteboard generalPasteboard]; - NSString * _Nullable plain = [pasteboard string]; - NSData * htmlPayload = [pasteboard dataForPasteboardType:UTTypeHTML.identifier]; - - NSData * plainPayload = [plain dataUsingEncoding:NSUTF8StringEncoding]; - - NSString * encodedPlainPayload = [plainPayload base64EncodedStringWithOptions:0]; - NSString * encodedHtmlPayload = [htmlPayload base64EncodedStringWithOptions:0]; + [self setClipboardContent:pasteboard]; - replyHandler([NSString stringWithFormat:@"%@ %@", encodedPlainPayload, encodedHtmlPayload], nil); + replyHandler(@"(internal)", nil); } else if ([message.body isEqualToString:@"write"]) { NSMutableDictionary * pasteboardItem = [NSMutableDictionary dictionaryWithCapacity:2]; bool success = [self getClipboardContent:pasteboardItem];