Skip to content

Commit

Permalink
feat(iOS): Copy all mime-types to clipboard
Browse files Browse the repository at this point in the history
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 <skyler.grey@collabora.com>
Change-Id: Ifb13fd910d63a985ad015335658815af3bd69f6b
  • Loading branch information
Minion3665 committed Dec 18, 2024
1 parent 31c7c6c commit 1fadc2b
Showing 1 changed file with 55 additions and 8 deletions.
63 changes: 55 additions & 8 deletions ios/Mobile/DocumentViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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];
Expand Down

0 comments on commit 1fadc2b

Please sign in to comment.