Files
Commilitia-Drop/desktop/cmd/clipprobe/main.go
T
admin f21fa5b5e8 cdrop — 跨 OS 剪贴板与文件传输服务
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。

- 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。
- 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。
- 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。
- 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。

架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。

本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
2026-06-15 21:38:28 +08:00

102 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build darwin
// clipprobe 是一个只读诊断工具:监听本机剪贴板变化,打印每次变化时
// NSPasteboard 上挂的全部类型(UTI)。它不读取也不上传任何剪贴板内容,
// 只看「类型」,用于在真机上经验性地确定各应用(尤其密码管理器)复制时
// 打哪些 UTI,从而让 D4 的敏感内容过滤基于实测数据而非猜测。
//
// 用法:go run ./cmd/clipprobe(或 go build 后运行二进制),然后依次从
// 普通文本编辑器 / 1Password / Bitwarden / 浏览器密码自动填充 等复制,观察
// 各自打印的 UTI。含 org.nspasteboard.ConcealedType / TransientType /
// AutoGeneratedType 或厂商私有 UTI(如 com.agilebits.onepassword)者,即为
// D4 应跳过上传的敏感内容。
//
// 实现用 ebitengine/purego + objc 运行时直调 AppKit,无 cgo、可随 Wails
// 交叉编译;调用模式照搬 golang.design/x/clipboard 的 darwin 实现。
package main
import (
"fmt"
"runtime"
"time"
"unsafe"
"github.com/ebitengine/purego"
"github.com/ebitengine/purego/objc"
)
func main() {
// objc 调用必须钉在同一 OS 线程上(autorelease pool 的语义要求)。
runtime.LockOSThread()
// 加载 AppKit / Foundation,使 NSPasteboard / NSAutoreleasePool 等类可解析。
for _, fw := range []string{
"/System/Library/Frameworks/AppKit.framework/AppKit",
"/System/Library/Frameworks/Foundation.framework/Foundation",
} {
if _, err := purego.Dlopen(fw, purego.RTLD_NOW|purego.RTLD_GLOBAL); err != nil {
panic(fmt.Sprintf("clipprobe: 无法加载 %s: %v", fw, err))
}
}
pbClass := objc.GetClass("NSPasteboard")
poolClass := objc.GetClass("NSAutoreleasePool")
var (
selGeneral = objc.RegisterName("generalPasteboard")
selChangeCount = objc.RegisterName("changeCount")
selTypes = objc.RegisterName("types")
selCount = objc.RegisterName("count")
selObjectAt = objc.RegisterName("objectAtIndex:")
selUTF8 = objc.RegisterName("UTF8String")
selAlloc = objc.RegisterName("alloc")
selInit = objc.RegisterName("init")
selRelease = objc.RegisterName("release")
)
fmt.Println("clipprobe:监听剪贴板,打印每次变化的 pasteboard 类型(UTI)。Ctrl+C 退出。")
fmt.Println("依次从 普通文本 / 1Password / Bitwarden / 浏览器密码自动填充 复制,观察各自的 UTI。")
fmt.Println("含 org.nspasteboard.ConcealedType / TransientType / AutoGeneratedType")
fmt.Println("或厂商私有 UTIcom.agilebits.onepassword 等)者 = 应跳过上传的敏感内容。")
fmt.Println("(本工具只读类型、不读内容、不上传。)")
fmt.Println("--------")
last := -1
for {
pool := objc.ID(poolClass).Send(selAlloc).Send(selInit)
general := objc.ID(pbClass).Send(selGeneral)
cc := int(general.Send(selChangeCount))
if cc != last {
last = cc
types := general.Send(selTypes)
n := int(types.Send(selCount))
fmt.Printf("[changeCount=%d] %d 个类型:\n", cc, n)
for i := 0; i < n; i++ {
item := types.Send(selObjectAt, i)
cstr := item.Send(selUTF8)
fmt.Printf(" - %s\n", cString(uintptr(cstr)))
}
}
objc.ID(pool).Send(selRelease)
time.Sleep(500 * time.Millisecond)
}
}
// cString reads a NUL-terminated C string (from -[NSString UTF8String]) into a
// Go string.
func cString(p uintptr) string {
if p == 0 {
return "(nil)"
}
var b []byte
for i := 0; ; i++ {
c := *(*byte)(unsafe.Pointer(p + uintptr(i)))
if c == 0 {
break
}
b = append(b, c)
}
return string(b)
}