如何将 NSPasteboard 与 kPasteboardTypeFileURLPromise 一起用于 copy/paste?

How to use NSPasteboard with kPasteboardTypeFileURLPromise for copy/paste?

我的应用程序想要向远程存储的文件的粘贴板添加一个承诺,并且可能永远不会粘贴 — 类似于粘贴从控制 VM 或其他远程系统的会话复制的文件。理想情况下,用户可以粘贴到 Finder 文件夹(或桌面)中,然后 promise 将触发并离开。我愿意处理一旦触发履行承诺的问题,但我一直无法获得触发的承诺。

我找到的所有 promise 代码都处理拖放,这不是我需要的功能(尽管可能需要来自 DnD 的某些东西才能使 promise 工作?)

我试过将 NSFilePromiseProvider 与委托一起使用,并将其添加到粘贴板。我可以使用剪贴板查看器查看粘贴板上的条目,但是当我在 Finder 中粘贴时,没有任何反应,也没有调用委托方法。我可以通过让剪贴板查看器访问条目来触发委托方法,所以我知道很多都已连接。

@interface ClipboardMacPromise : NSFilePromiseProvider<NSFilePromiseProviderDelegate>
{
    NSString* m_file;
}
@end
@implementation ClipboardMacPromise
- (id)initWithFileType:(NSString*)type andFile:(NSString*)file
{
    m_file = file;
    return [super initWithFileType:type delegate:self];
}
- (NSString *)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider fileNameForType:(NSString *)fileType
{
    return m_file;
}
- (void)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider writePromiseToURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable errorOrNil))completionHandler
{
    // Finder can't paste, so we never get here...
}
@end

NSPasteboard* pboard = [NSPasteboard generalPasteboard];
[pboard clearContents];
NSMutableArray* items = [[NSMutableArray alloc] init];
ClipboardMacPromise* promise = [[ClipboardMacPromise alloc] initWithFileType:(NSString*)kUTTypeFileURL andFile:@"dummy.txt"];
[items addObject:promise];
[pboard writeObjects:items];

我也尝试过 NSPasteboardItemNSPasteboardItemDataProvider,我在 kUTITypeFileURL 上设置了内容承诺。它在粘贴板上提供了非常相似的条目,但是当我粘贴到 finder 中时仍然没有任何动作。剪贴板查看器在访问单个粘贴板条目时将再次触发提供程序罚款。 (NSPasteboarddeclareTypes:owner: 具有相同的行为)

@interface ClipboardMacPromise : NSPasteboardItem<NSPasteboardItemDataProvider>
{
    NSString* m_file;
}
@end
@implementation ClipboardMacPromise
- (id)initWithFile:(NSString*)file
{
    m_file = file;
    id _self = [super init];
    if (_self) {
        [_self setDataProvider:_self forTypes:@[(NSString*)kPasteboardTypeFileURLPromise]];
        [_self setString:(NSString*)kUTTypeFileURL forType:(NSString*)kPasteboardTypeFilePromiseContent];
    }
    return _self;
}
- (void)pasteboard:(NSPasteboard *)pasteboard item:(NSPasteboardItem *)item provideDataForType:(NSPasteboardType)type
{
    // we don't get here when we paste in Finder because
    // Finder doesn't think there's anything to paste
    // but using a clipboard viewer, we can force the promise to
    // resolve and we do get here
}
@end

NSPasteboard* pboard = [NSPasteboard generalPasteboard];
[pboard clearContents];
NSMutableArray* items = [[NSMutableArray alloc] init];
ClipboardMacPromise* promise = [[ClipboardMacPromise alloc] initWithFile:@"file:///tmp/dummy.txt"];
[items addObject:promise];
[pboard writeObjects:items];

为了完整起见,这是我的 Carbon 尝试,因为 Pasteboard.h 似乎详细说明了它在 copy/paste 场景中应该如何工作......但它仍然没有为 Finder 提供它正在寻找的东西.生成的剪贴板条目在三个实现之间看起来非常相似。

OSStatus PasteboardPromiseKeeperProc(PasteboardRef pasteboard, PasteboardItemID item, CFStringRef flavorType, void * _Nullable context)
{
    // 6) The sender's promise callback for kPasteboardTypeFileURLPromise is called.
    string s = "dummy.txt";
    CFDataRef inData = CFDataCreate(kCFAllocatorDefault, (UInt8*)s.c_str(), s.size());
    PasteboardPutItemFlavor(pasteboard, item, flavorType, inData, 0);
    return noErr;
}

PasteboardRef p = NULL;
PasteboardCreate(kPasteboardClipboard, &p);
PasteboardClear(p);
PasteboardSetPromiseKeeper(p, &PasteboardPromiseKeeperProc, this);

// 1) The sender promises kPasteboardTypeFileURLPromise for a file yet to be created.
PasteboardPutItemFlavor(p, (PasteboardItemID)1, kPasteboardTypeFileURLPromise, kPasteboardPromisedData, 0);
// 2) The sender adds kPasteboardTypeFilePromiseContent containing the UTI describing the file's content.
PasteboardPutItemFlavor(p, (PasteboardItemID)2, kPasteboardTypeFilePromiseContent,CFStringCreateExternalRepresentation(NULL, kUTTypeFileURL, kCFStringEncodingUTF8, 0), 0);

粘贴板上好像真的有Finder要找的某个UTI,我没有。如果我将 kUTTypeFileURL 直接放在剪贴板上,看起来 finder 在提供粘贴之前实际上会检查文件是否存在(即触发 Catalina 的桌面访问提示)。

有谁知道是否可以或如何通过 Copy/Paste 而不是拖放来向 Finder 提供文件承诺?

这里的关键似乎是 Finder 要求文件实际存在于磁盘上才能为文件启用粘贴操作 URL。这个细节排除了 promises 为 copy/paste 工作的可能性——至少在 Finder 中是这样。

因此,正确的解决方案需要一个虚拟化文件系统(如 FUSE),以便可以在文件系统级别做出并实现承诺。因此,可以将临时零长度文件的集合写入磁盘,并将实际文件 URL 添加到粘贴板。这满足了 Finder 必须启用粘贴的要求。然后,当进行粘贴操作时,文件数据将从虚拟化文件系统中读取,而虚拟化文件系统又可以从远程系统中检索实际数据。 Finder none 更聪明。副本甚至会有一个内置的进度条!

Microsoft 的 Mac RDP 客户端似乎主要以这种方式工作,尽管我只能让它复制零长度文件,因此这可能比听起来更难正确。