惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
爱范儿
爱范儿
The Cloudflare Blog
Y
Y Combinator Blog
B
Blog RSS Feed
Stack Overflow Blog
Stack Overflow Blog
博客园 - 叶小钗
G
Google Developers Blog
J
Java Code Geeks
P
Proofpoint News Feed
美团技术团队
Engineering at Meta
Engineering at Meta
腾讯CDC
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
WordPress大学
WordPress大学
博客园 - 聂微东
雷峰网
雷峰网
有赞技术团队
有赞技术团队
L
LangChain Blog
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 【当耐特】

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 新电脑 brew install node 之后,一个小设置可以提升对供应链投毒的防御 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用? 做了个 AI + 真人专家监督的广告投放平台 Auxora, 7 个品牌跑出 6x ROAS
微信 OSX 版+屏幕共享(Screen Sharing.app)的无法向外复制我...
phpfpm · 2026-03-26 · via V2EX

Bug Report: macOS Screen Sharing 剪贴板同步丢失标准文本类型

日期: 2026-03-26 严重程度: 中(功能性缺陷,影响跨机器工作流)


一、环境信息

项目 远程机器(被控端) 本地机器(控制端)
机型 MacBook Pro 15-inch, 2017 MacBook Pro 14-inch, 2021
处理器架构 Intel x86_64 (2.8 GHz 四核 Core i7) Apple M1 Pro (ARM64)
内存 16 GB 2133 MHz LPDDR3 16 GB
macOS 版本 Ventura 13.7.7 macOS Tahoe 26.3.1
主机名(脱敏) remote-mac.local local-mac.local
屏幕共享版本 3.0 (587.3) 6.2 (758.1)
微信版本 4.1.8.52 (Qt 框架构建) 不涉及

连接方式: 本地机器通过 macOS 自带「屏幕共享」 6.2 (758.1) 远程控制远程机器(被控端运行屏幕共享服务端 3.0 (587.3)),并开启了剪贴板共享功能。


二、问题描述

在远程机器的微信( WeChat 4.1.8.52 )中复制文字后,切换到本地机器执行粘贴( Cmd+V ),粘贴无效,无任何内容输出。

同一场景下,从远程机器的备忘录( Notes.app )复制文字,本地粘贴正常


三、复现步骤

  1. 本地机器通过屏幕共享连接远程机器,开启剪贴板共享
  2. 在远程机器微信中选中任意文字,执行复制( Cmd+C )
  3. 切换焦点到本地机器任意文本输入框
  4. 执行粘贴( Cmd+V )
  5. 结果:无内容粘贴

四、实验数据

使用自制诊断工具 capture_once.swift 在两台机器上同时监控剪贴板,复制后各自捕获原始数据。

实验 A:从远程微信复制文字

远程机器剪贴板(复制后原始状态):

=== CLIPBOARD DUMP [REMOTE] ===
time:        2026-03-26 20:41:27.707
changeCount: 1858

✅ [38B] com.apple.traditional-mac-plain-text  → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] CorePasteboardFlavorType 0x54455854   → "ai 为啥一段时间以后就会犯蠢"
❌ [28B] public.utf16-plain-text
❌ [28B] CorePasteboardFlavorType 0x75747874
✅ [38B] public.utf8-plain-text                → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] NSStringPboardType                    → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] public.text                           → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] com.trolltech.anymime.text--plain     → "ai 为啥一段时间以后就会犯蠢"

NSStringPboardType:     ✅ "ai 为啥一段时间以后就会犯蠢"
public.utf8-plain-text: ✅ "ai 为啥一段时间以后就会犯蠢"

本地机器剪贴板( Screen Sharing 同步后):

=== CLIPBOARD DUMP [LOCAL] ===
time:        2026-03-26 20:41:29.147
changeCount: 1319

✅ [38B] com.apple.traditional-mac-plain-text  → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] CorePasteboardFlavorType 0x54455854   → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] public.text                           → "ai 为啥一段时间以后就会犯蠢"
✅ [38B] com.trolltech.anymime.text--plain     → "ai 为啥一段时间以后就会犯蠢"

NSStringPboardType:     ❌ nil
public.utf8-plain-text: ❌ nil

修复结果:

⚠️  检测到 Screen Sharing bug ,执行修复...
✅ 修复完成:"ai 为啥一段时间以后就会犯蠢"

NSStringPboardType: ✅ "ai 为啥一段时间以后就会犯蠢"

实验 B:从远程备忘录复制文字(对照组)

远程机器剪贴板:

✅ [476B] public.rtf                           → (RTF 格式文本)
✅ [72B]  public.utf8-plain-text               → "销售后缀校正的列表有好几个,可以帮助校正一下吗?"
✅ [72B]  NSStringPboardType                   → "销售后缀校正的列表有好几个,可以帮助校正一下吗?"
❌ [1067B] com.apple.notes.richtext

本地机器剪贴板( Screen Sharing 同步后):

✅ [476B] public.rtf                           → (RTF 格式文本,完整)
✅ [72B]  public.utf8-plain-text               → "销售后缀校正的列表有好几个,可以帮助校正一下吗?"
✅ [72B]  NSStringPboardType                   → "销售后缀校正的列表有好几个,可以帮助校正一下吗?"

✅ 无需修复,剪贴板数据正常

五、根因分析

关键差异

微信( WeChat 4.1.8.52 )基于 Qt 框架开发,复制文字时向剪贴板写入了一个 Qt 专有类型:

com.trolltech.anymime.text--plain

com.trolltech 是 Qt 框架的历史遗留命名空间( Trolltech 是 Qt 的原始开发公司,2008 年被诺基亚收购)。

问题所在:Screen Sharing 的剪贴板同步逻辑

对比远程和本地的类型列表:

类型 远程(原始) 本地(同步后)
NSStringPboardType ✅ 38B ,有内容 缺失
public.utf8-plain-text ✅ 38B ,有内容 缺失
public.utf16-plain-text ❌ 28B ,无文本 缺失
com.apple.traditional-mac-plain-text ✅ 有内容 ✅ 有内容
com.trolltech.anymime.text--plain ✅ 有内容 ✅ 有内容
public.text ✅ 有内容 ✅ 有内容

Screen Sharing 在同步剪贴板时,**选择性地丢弃了 NSStringPboardTypepublic.utf8-plain-text**,而这两个类型是 macOS 上 Cmd+V 粘贴操作读取的标准类型。

责任归属

责任方:本地 Apple macOS Screen Sharing 6.2 (758.1)(控制端)

理由:

  1. 远程微信的剪贴板数据完整,NSStringPboardType 在远程机器上有完整内容,远程 Screen Sharing 3.0 正确发送了所有类型
  2. 剪贴板同步由控制端(本地)发起并负责接收、反序列化、写入本地 NSPasteboard,数据丢失发生在这一步
  3. 备忘录的剪贴板(不含 trolltech 类型)经同一链路同步后完全正常,说明远程发送侧没有问题
  4. 唯一差异是微信剪贴板含有 com.trolltech.anymime.text--plain 自定义类型,本地 Screen Sharing 6.2 在处理这种混合类型时,未能正确还原标准文本类型

主要责任在本地 Screen Sharing 6.2,远程 Screen Sharing 3.0 仅负责发送,已正确完成工作。微信和 Qt 框架行为符合规范,不存在问题。


六、临时修复方案

在本地机器后台运行 clipboard_fix.swift,自动检测并修复损坏的剪贴板状态:

nohup swift ~/scripts/clipboard-sync/clipboard_fix.swift > /tmp/clipboard_fix.log 2>&1 &

修复逻辑:检测到剪贴板含有 com.trolltech.anymime.text--plainNSStringPboardType 为空时,从 trolltech 类型中提取文本,重新写入标准类型。


七、建议提交 Feedback

提交对象: Apple (通过 Feedback Assistant ) 分类: macOS > Screen Sharing / Remote Desktop

标题建议:

Screen Sharing 3.0: NSStringPboardType dropped when syncing clipboard
containing Qt/Trolltech custom pasteboard types from remote Mac

复现环境:

  • 控制端:Apple Silicon Mac ,macOS 26.x (Tahoe)
  • 被控端:Intel Mac ,macOS Ventura 13.7.7
  • 屏幕共享版本:3.0 (587.3)
  • 触发应用:WeChat 4.1.8.52 ( Qt 框架)

附录:诊断工具源码

capture_once.swift

#!/usr/bin/env swift
// 启动后等待剪贴板变化,捕获到一次后立即 dump 并退出
// 用法:swift capture_once.swift [remote|local]

import AppKit
import Foundation

let label = CommandLine.arguments.count >= 2 ? CommandLine.arguments[1] : "unknown"
let pb = NSPasteboard.general
let initialCount = pb.changeCount

let df = DateFormatter()
df.dateFormat = "HH:mm:ss.SSS"

func ts() -> String { df.string(from: Date()) }

func dump() {
    let now = DateFormatter()
    now.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
    print("")
    print("=== CLIPBOARD DUMP [\(label.uppercased())] ===")
    print("time:        \(now.string(from: Date()))")
    print("host:        \(ProcessInfo.processInfo.hostName)")
    print("changeCount: \(pb.changeCount)  (初始: \(initialCount))")
    print("")
    guard let types = pb.types, !types.isEmpty else { print("(剪贴板为空)"); return }
    print("--- 所有类型 ---")
    for t in types {
        let data = pb.data(forType: t) ?? Data()
        let utf8 = String(data: data, encoding: .utf8) ?? ""
        let hasText = !utf8.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        print("\(hasText ? "✅" : "❌") [\(data.count)B] \(t.rawValue)")
        if hasText { print("   \"\(utf8.prefix(200).replacingOccurrences(of: "\n", with: "\\n"))\"") }
    }
    print("")
    print("--- 标准文本读取 ---")
    if let s = pb.string(forType: .string) { print("NSStringPboardType:     ✅ \"\(s.prefix(300))\"") }
    else { print("NSStringPboardType:     ❌ nil") }
    if let d = pb.data(forType: NSPasteboard.PasteboardType("public.utf8-plain-text")),
       let s = String(data: d, encoding: .utf8), !s.isEmpty {
        print("public.utf8-plain-text: ✅ \"\(s.prefix(300))\"")
    } else { print("public.utf8-plain-text: ❌ nil") }
    print("")
    print("=== END [\(label.uppercased())] ===")
}

let trolltechType = NSPasteboard.PasteboardType("com.trolltech.anymime.text--plain")

func needsFix() -> Bool {
    guard let types = pb.types else { return false }
    let typeSet = Set(types.map(\.rawValue))
    return typeSet.contains(trolltechType.rawValue) && pb.string(forType: .string) == nil
}

func fix() {
    guard let data = pb.data(forType: trolltechType),
          let text = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .macOSRoman),
          !text.isEmpty else {
        print("[\(ts())] ❌ 无法从 trolltech 类型提取文本"); return
    }
    pb.clearContents()
    pb.setString(text, forType: .string)
    pb.setString(text, forType: NSPasteboard.PasteboardType("public.utf8-plain-text"))
    pb.setData(data, forType: trolltechType)
    print("[\(ts())] ✅ 修复完成:\"\(text.prefix(80))\"")
}

print("[\(ts())] [\(label)] 已就绪,等待剪贴板变化(请在微信复制文字)...")

while true {
    if pb.changeCount != initialCount {
        print("[\(ts())] [\(label)] 检测到变化,捕获中...")
        dump()
        if label == "local" {
            print("\n--- 修复 ---")
            if needsFix() {
                print("[\(ts())] ⚠️  检测到 Screen Sharing bug ,执行修复...")
                fix()
                print("\n--- 修复后剪贴板 ---")
                if let s = pb.string(forType: .string) { print("NSStringPboardType: ✅ \"\(s.prefix(300))\"") }
                else { print("NSStringPboardType: ❌ 修复失败") }
            } else {
                print("[\(ts())] ✅ 无需修复,剪贴板数据正常")
            }
        }
        exit(0)
    }
    Thread.sleep(forTimeInterval: 0.1)
}

clipboard_fix.swift (后台常驻修复守护进程)

#!/usr/bin/env swift
// 持续监控本地剪贴板,自动修复 Screen Sharing 同步微信文字后的 bug
// 用法:nohup swift clipboard_fix.swift > /tmp/clipboard_fix.log 2>&1 &

import AppKit
import Foundation

let pb = NSPasteboard.general
var lastCount = pb.changeCount
let trolltechType = NSPasteboard.PasteboardType("com.trolltech.anymime.text--plain")

func ts() -> String {
    let f = DateFormatter(); f.dateFormat = "HH:mm:ss.SSS"; return f.string(from: Date())
}

func isBrokenState() -> Bool {
    guard let types = pb.types else { return false }
    let typeSet = Set(types.map(\.rawValue))
    return typeSet.contains(trolltechType.rawValue) && pb.string(forType: .string) == nil
}

print("[\(ts())] clipboard_fix 已启动")

while true {
    let currentCount = pb.changeCount
    if currentCount != lastCount {
        lastCount = currentCount
        if isBrokenState() {
            if let data = pb.data(forType: trolltechType),
               let text = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .macOSRoman),
               !text.isEmpty {
                pb.clearContents()
                pb.setString(text, forType: .string)
                pb.setString(text, forType: NSPasteboard.PasteboardType("public.utf8-plain-text"))
                pb.setData(data, forType: trolltechType)
                print("[\(ts())] ✅ 修复:\"\(text.prefix(80))\"")
            }
        }
    }
    Thread.sleep(forTimeInterval: 0.1)
}