ab57afdec2
把桌面 P2P 数据面从 WebView 内 JS WebRTC 移到 Go 进程的 pion,逃离 WebKit/WebView2 写死的 256KB SCTP 接收窗与隐藏窗口的渲染器节流。后端零改动、与现有客户端互通(线协议逐字节对齐)。真机实测原生↔原生 / 原生↔iOS(经有线)稳定 7-10MB/s,对比旧 JS 路径的 1.5MB/s 还塌缩是质变。设计与阶段见 desktop/NATIVE-TRANSFER.md。
- engine 包(纯 Go,仅 pion+stdlib、零 Wails 依赖,供将来 gomobile 复用于 iOS):pion 收发,逐字节对齐 web 引擎线协议(DataChannel cdrop-file ordered、文本帧 meta/done/ack、二进制分片、信令 {type,sdp?,candidate?});SetSCTPMaxReceiveBufferSize 顶到 4MB;mDNS QueryOnly 解析对端 .local 候选实现同内网 host↔host 直连;接收端直接写盘(去 base64 桥 / OPFS);进程内端到端测试 + 诊断日志(候选/ICE 态/选中对/发送停滞)。
- app.go:Bind P2PStartOutgoing / StartIncoming / HandleSignal / Cancel + 原生取文件 PickFileForSend + 中继回退读盘片 ReadFileSlice;引擎回调经 EventsEmit p2p:* 反向通知(进度/状态/信令/落盘路径/候选对)。
- JS p2pBackend 抽象(p2pNative.ts):桌面按对端把会话委派给 Go 引擎,transfer.ts / hub.ts 零改动;HTTP 全留 JS(出站信令 POST、状态机 /p2p·接收端 /done·/fail);监听 p2p:* 事件驱动 store、phase 与 iceStats。
- 发送取文件改原生路径:selectedFile 模型 File→FileSource(桌面带绝对 path → Go 直接读盘走原生;浏览器拖放无 path → 回退 JS)。Composer dropzone 在桌面打开原生文件对话框。
- 完成语义修复:接收端完成不立即拆连接(续发最终 ack、留 SSE 终态 Cancel 拆、60s 兜底、终态不可被后续 close 翻转)——修「iOS→mac 实际成功却被误标失败」。
- 剪贴板暂存路径修复:富文本经 Universal Clipboard 暂存为 .rtfd,pasteboard 纯文本表示有时取到该暂存路径——uploadClipboard 共享守卫拦截 + mac 原生读改从 RTF / RTFD 派生纯文本。
77 lines
3.4 KiB
Objective-C
77 lines
3.4 KiB
Objective-C
#import <Cocoa/Cocoa.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// cdropPasteboardChangeCount returns the general pasteboard's monotonic change
|
|
// counter. Polling it is the cheap way to detect a copy without a callback API.
|
|
long cdropPasteboardChangeCount(void) {
|
|
return (long)[[NSPasteboard generalPasteboard] changeCount];
|
|
}
|
|
|
|
// cdropPasteboardReadText returns a malloc'd UTF-8 copy of the pasteboard's
|
|
// plain-text payload (caller frees), or NULL when there is no text (image /
|
|
// file only). It sets *sensitive to 1 when a privacy marker type is present —
|
|
// org.nspasteboard.{Concealed,Transient,AutoGenerated}Type — so the upload
|
|
// policy can skip it. (Most password managers set no marker; this is a
|
|
// best-effort secondary guard behind the server's short TTL.)
|
|
// cdropIsStagingPath 判断字符串是否为 Universal Clipboard / Handoff 的暂存文件路径——富文本
|
|
// 复制时 pasteboard 的纯文本表示有时是 .../shared-pasteboard/.../xxx.rtfd 这样的路径而非内容,
|
|
// 绝不能当剪贴板文本上传同步。
|
|
static BOOL cdropIsStagingPath(NSString *s) {
|
|
if (s == nil) { return NO; }
|
|
return ([s rangeOfString:@"/shared-pasteboard/"].location != NSNotFound) ||
|
|
([s rangeOfString:@"com.apple.coreservices.useractivityd"].location != NSNotFound);
|
|
}
|
|
|
|
// cdropPlainFromRich 从 RTF / RTFD 富文本派生纯文本(复制富文本时纯文本类型可能缺失或为暂存
|
|
// 路径)。取不到返回 nil。
|
|
static NSString *cdropPlainFromRich(NSPasteboard *pb) {
|
|
NSData *data = [pb dataForType:NSPasteboardTypeRTFD];
|
|
NSAttributedString *as = nil;
|
|
if (data) {
|
|
as = [[NSAttributedString alloc] initWithData:data
|
|
options:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
|
|
documentAttributes:nil error:nil];
|
|
}
|
|
if (as == nil) {
|
|
data = [pb dataForType:NSPasteboardTypeRTF];
|
|
if (data) {
|
|
as = [[NSAttributedString alloc] initWithData:data
|
|
options:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}
|
|
documentAttributes:nil error:nil];
|
|
}
|
|
}
|
|
return as ? as.string : nil;
|
|
}
|
|
|
|
char *cdropPasteboardReadText(int *sensitive) {
|
|
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
|
*sensitive = 0;
|
|
for (NSString *type in pb.types) {
|
|
if ([type isEqualToString:@"org.nspasteboard.ConcealedType"] ||
|
|
[type isEqualToString:@"org.nspasteboard.TransientType"] ||
|
|
[type isEqualToString:@"org.nspasteboard.AutoGeneratedType"]) {
|
|
*sensitive = 1;
|
|
break;
|
|
}
|
|
}
|
|
NSString *s = [pb stringForType:NSPasteboardTypeString];
|
|
// 纯文本缺失或取到的是 Handoff 暂存路径 → 从 RTF/RTFD 派生真正的文本。
|
|
if (s == nil || cdropIsStagingPath(s)) {
|
|
s = cdropPlainFromRich(pb);
|
|
}
|
|
if (s == nil || cdropIsStagingPath(s)) { return NULL; } // 仍拿不到文本:当非文本忽略
|
|
const char *utf8 = [s UTF8String];
|
|
if (utf8 == NULL) { return NULL; }
|
|
return strdup(utf8);
|
|
}
|
|
|
|
// cdropPasteboardWriteText replaces the pasteboard's contents with plain text.
|
|
void cdropPasteboardWriteText(const char *text) {
|
|
NSString *s = [NSString stringWithUTF8String:text];
|
|
if (s == nil) { return; }
|
|
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
|
[pb clearContents];
|
|
[pb setString:s forType:NSPasteboardTypeString];
|
|
}
|