# clip-board **Repository Path**: solidify-the-foundation/clip-board ## Basic Information - **Project Name**: clip-board - **Description**: mac的粘贴板历史 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-27 - **Last Updated**: 2026-06-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ClipBoard > 一个轻量、常驻状态栏的 macOS 剪贴板历史工具。Swift 原生实现,零第三方依赖。 ![Status](https://img.shields.io/badge/status-stable-brightgreen) ![Platform](https://img.shields.io/badge/macOS-13%2B-blue) ![Swift](https://img.shields.io/badge/Swift-5.9%2B-orange) ![License](https://img.shields.io/badge/license-MIT-lightgrey) --- ## ✨ 功能一览 ### 📋 剪贴板历史 - **多类型支持**:📝 文本 / 🖼️ 图片 / 📎 文件 / 📄 RTF 富文本 - **自动捕获**:DispatchSourceTimer 500ms 轮询,能耗几乎为 0 - **本地持久化**:JSON 防抖落盘,重启不丢历史 - **去重 + 容量裁剪**:SHA256 内容哈希,最旧条目自动淘汰 ### 🎯 高效粘贴 - **全局快捷键**(默认 `⇧⌘V`):随处一按弹出浮窗 - **模糊背景浮窗**:非激活式 NSPanel,不打断当前 App 焦点 - **实时搜索**:输入即过滤;RTF 也能按内部纯文本搜 - **自动粘贴**:选中后写回剪贴板 + 模拟 ⌘V 一步到位 - **键盘全控制**:↑/↓ 选择、回车粘贴、`⌘1`~`⌘9` 快选、`⌘P` 收藏、`⌘⌫` 删除、`Esc` 关闭 ### 🗂️ 整理与筛选 - **类型筛选 Tab**:全部 / 收藏 / 文本 / 图片 / 文件 / 富文本 - **收藏置顶**:置顶条目不会被容量淘汰 - **来源 App 显示**:每条历史显示从哪个 App 复制(含 App 图标) - **图片真实缩略图**:LRU 缓存,列表滚动丝滑 ### 🛡️ 隐私保护(三道防线) 1. **系统瞬态/敏感类型自动忽略**(`org.nspasteboard.{Transient,Concealed,AutoGenerated}Type`、1Password 等) 2. **用户自定义 App 黑名单**(按 Bundle ID) 3. **单条体积上限**(默认 8MB,防止巨图塞爆历史) ### ⚙️ 个性化 - **自定义全局快捷键**:录制任意带修饰键的组合 - **开机自启**(SMAppService) - **容量配置**(20~2000 条) - **单条最大体积配置**(1MB~32MB) --- ## 🚀 快速开始 > **要求**:macOS 13 Ventura+,Swift 5.9+(Command Line Tools 即可,无需完整 Xcode) ### 方式一:开发环境直接跑 ```bash swift run -c release ClipBoard ``` 最快速度看到效果,但不支持开机自启。 ### 方式二:打包成 .app(推荐) ```bash ./scripts/build-app.sh open build/ClipBoard.app # 或安装: cp -R build/ClipBoard.app /Applications/ ``` ### 方式三:出 DMG 分发 ```bash ./scripts/build-app.sh && ./scripts/make-dmg.sh # 输出:build/ClipBoard-0.4.0.dmg(含 Applications 软链) ``` ### 首次启动:授权辅助功能(用于自动粘贴) 1. 菜单栏图标 → 点击 "⚠️ 未授权辅助功能..." 项 2. 系统设置 → 隐私与安全 → 辅助功能 → 勾选 `ClipBoard` 3. 重启 ClipBoard > 不授权也能用,只是选中条目只写剪贴板,需要你手动 ⌘V。 #### 已经授权了,菜单还显示"未授权"? 最常见的是 **TCC「幻觉授权」**:你之前授权过 `swift run` 跑的裸二进制,后来又装了 `.app`,两者在系统看是不同的 client。一行命令解决: ```bash # 退出 ClipBoard,然后: sudo tccutil reset Accessibility com.clipboard.history # 重新启动,按提示再授权一次 open /Applications/ClipBoard.app ``` > 授权生效后**不需要重启 ClipBoard**,点一下菜单栏图标,"⚠️"提示会自动消失(菜单每次打开都会重新检测权限状态)。 --- ## ⌨️ 快捷键 ### 全局 | 快捷键 | 操作 | |--------|------| | `⇧⌘V`(可改) | 打开 / 关闭浮窗 | ### 浮窗内 | 快捷键 | 操作 | |--------|------| | `↑` / `↓` | 上下选择 | | `↵` | 粘贴选中 | | `⌘1` ~ `⌘9` | 粘贴第 N 条 | | `⌘P` | 收藏 / 取消收藏 | | `⌘⌫` | 删除选中 | | `Esc` | 关闭浮窗 | | 右键 | 收藏 / 粘贴 / 删除 菜单 | ### 状态栏菜单 | 快捷键 | 操作 | |--------|------| | `⌘,` | 打开设置 | | `⌘Q` | 退出 | --- ## 📁 数据位置 ``` ~/Library/Application Support/ClipBoard/history.json # 剪贴板历史 ``` 偏好设置存储在 UserDefaults(domain: `com.clipboard.history`)。 删除上述文件 = 清空所有历史。 --- ## 🏗️ 项目结构 ``` clip-board/ ├── PLAN.md / README.md ├── Package.swift # SPM manifest ├── Resources/Info.plist # .app bundle 元数据 ├── scripts/ │ ├── build-app.sh # 打包成 .app │ └── make-dmg.sh # 打包成 DMG └── Sources/ClipBoard/ ├── App/ # 入口 │ ├── main.swift │ └── AppDelegate.swift # 装配所有模块 ├── Core/ │ ├── Store/ │ │ ├── ClipItem.swift # 多类型 Codable 数据模型 │ │ ├── HistoryStore.swift # 线程安全 + 去重 + 裁剪 │ │ └── JSONPersistence.swift # 防抖落盘 │ ├── Shortcut/ │ │ ├── HotKey.swift # Carbon 全局快捷键 │ │ └── ShortcutManager.swift # 可配置快捷键 │ ├── Launch/LaunchAtLogin.swift # SMAppService │ └── Util/Hashing.swift # SHA256 └── Features/ ├── MenuBar/MenuBarController.swift # 状态栏图标 + 菜单 ├── Monitor/ClipboardMonitor.swift # 多类型轮询解析 ├── Panel/ # 浮窗 │ ├── PanelController.swift # NSPanel + 键盘事件 │ ├── PanelModel.swift # 筛选 + 搜索 │ ├── HistoryPanel.swift # SwiftUI 列表 │ ├── AppIconCache.swift # 来源 App 图标 │ └── ThumbnailCache.swift # 图片缩略图 LRU ├── Paste/Paster.swift # 多类型写回 + 模拟 ⌘V ├── Permission/PermissionHelper.swift # 辅助功能引导 └── Settings/ ├── UserPreferences.swift # @Published UserDefaults ├── ShortcutRecorder.swift # SwiftUI 快捷键录制 └── SettingsView.swift # 通用 / 快捷键 / 隐私 / 关于 ``` **总计**:27 个 Swift 文件,~2500 行代码,**零第三方依赖**。 --- ## 💡 关键设计 - **轮询为何不慢**:每 tick 只读一个 `Int changeCount`,99% 的 tick 都是几纳秒整数比较。`DispatchSourceTimer + leeway 100ms + qos .utility` 让系统合并定时器唤醒,能耗几乎不可测。 - **浮窗不抢焦点**:`NSPanel` 用 `.nonactivatingPanel` 风格,关闭后焦点回到目标 App,模拟 ⌘V 才能粘贴到正确位置。 - **快捷键无需特殊权限**:Carbon `RegisterEventHotKey` 是系统级 API,不依赖辅助功能。只有模拟 ⌘V 才需要 Accessibility 权限。 - **缩略图缓存**:SwiftUI List 每次重渲染都会触发 row body,对 1MB+ PNG 直接 `NSImage(data:)` 会很卡。用 `NSCache` 按 hash 缓存解码后的小图,列表滚动丝滑。 - **向后兼容的 Codable**:ClipItem 手写 `init(from:)`,新增字段用 `decodeIfPresent + 默认值`,老 JSON 也能解码。 - **持久化策略**:防抖 1 秒合并多次写入;退出前主动 flush;文件损坏自动备份,不阻塞启动。 - **开机自启识别 .app 形式**:`LaunchAtLogin.isBundled` 检测 Bundle,未打包成 .app 时 UI 自动禁用并提示。 - **设置窗口激活技巧**:临时切到 `.regular` 让窗口能聚焦,关闭后 `windowWillClose` 恢复 `.accessory`。 --- ## 🛠️ 开发 ### 编译 ```bash swift build # Debug swift build -c release # Release ``` ### 直接运行 ```bash swift run -c release ClipBoard ``` ### 设置 App 图标 放置位置: ``` Resources/AppIcon.icns ``` [`scripts/build-app.sh`](./scripts/build-app.sh) 会在打包 .app 时自动拷贝它, [`Resources/Info.plist`](./Resources/Info.plist) 的 `CFBundleIconFile = AppIcon` 已经指好了。 根据你手上的素材格式,三种生成方式: | 你手上有的 | 命令 | |----------|------| | `AppIcon.icns`(已经是终态) | `cp YourIcon.icns Resources/AppIcon.icns` | | 单张 1024×1024 PNG | `./scripts/make-icon.sh path/to/icon-1024.png` | | `AppIcon.appiconset` 目录(Xcode asset catalog) | `./scripts/icon-from-appiconset.sh path/to/AppIcon.appiconset` | | 没有圆角 / 没透明的源图(含被错认成 PNG 的 JPEG) | `swift scripts/icon-roundify.swift path/to/icon.png Resources/AppIcon.icns` | | 散的多尺寸 PNG | 改名成 [Apple 规范](https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW2) 后用 `iconutil --convert icns YourIcon.iconset --output Resources/AppIcon.icns` | > **💡 macOS 不会自动给图标加圆角**,必须自己把圆角和透明背景画进 PNG。如果源图是方块(或扩展名是 .png 实际是 JPEG),用 `icon-roundify.swift` 套一个标准 macOS Big Sur+ 蒙版(内容占 80.5%、圆角半径 18%、外侧透明): > ```bash > swift scripts/icon-roundify.swift ~/Downloads/your-icon.png Resources/AppIcon.icns > ``` > 体检源图是否合规:`sips -g hasAlpha -g format ~/Downloads/your-icon.png` > 必须 `hasAlpha: yes` + `format: png`,否则就要走 roundify。 生成后直接打包: ```bash ./scripts/build-app.sh # Finder 查看 build/ClipBoard.app 即可看到新图标 ``` ### 图标不刷新 / Launchpad 看不到 macOS 有多层图标缓存(Dock / Launchpad / IconServices),换图标后经常需要手动清。 **Launchpad 不显示图标的两个根本原因:** 1. **App 不在 Launchpad 扫描目录** — Launchpad 只扫描 `/Applications`、`/Applications/Utilities`、`~/Applications`。从 `build/` 下 open 的不会出现。 2. **图标缓存没刷新** — 即便 .app 已经更新,旧图标仍然贴在缓存里。 **完整重置流程:** ```bash # 1. 先在菜单栏退出 ClipBoard # 2. 安装到 /Applications(必须!) cp -R build/ClipBoard.app /Applications/ # 3. 清 Launchpad 布局 + Dock 缓存 defaults write com.apple.dock ResetLaunchPad -bool true killall Dock Finder # 4. 再次启动 open /Applications/ClipBoard.app ``` **顽固缓存(极少数情况):** ```bash sudo rm -rf /Library/Caches/com.apple.iconservices.store 2>/dev/null sudo find /private/var/folders/ -name com.apple.iconservices -exec rm -rf {} \; 2>/dev/null touch /Applications/ClipBoard.app killall Dock Finder ``` --- ## 📦 分发打包 按使用场景分为三档:**本机自用** / **发给朋友** / **正式分发**。 ### 一、本机自用(10 秒) 直接打包成 ad-hoc 签名的 `.app`,本机能跑,**别的电脑不行**。 ```bash ./scripts/build-app.sh open build/ClipBoard.app ``` 如果想常驻使用: ```bash cp -R build/ClipBoard.app /Applications/ open /Applications/ClipBoard.app # 然后在设置里勾选"开机自启" ``` > `build-app.sh` 在没有 `CODESIGN_ID` 环境变量时会用 `-`(ad-hoc)签名。Apple Silicon 必须签名才能运行,所以 ad-hoc 是最低要求。 --- ### 二、发给朋友(1 分钟,但有黄色提示) 打成 DMG,朋友拖拽即装。**未公证**的 DMG 在 Gatekeeper 下会弹窗警告,但可以让对方手动放行。 ```bash ./scripts/build-app.sh # 先生成 .app ./scripts/make-dmg.sh # → build/ClipBoard-0.4.0.dmg ``` 把 DMG 发给朋友,对方双击挂载,把 ClipBoard.app 拖进 Applications。 #### 对方首次打开时的处理 macOS Sequoia (15) 起,Gatekeeper 更严格,未公证 App 会出现 **"无法打开,因为 Apple 无法检查它是否包含恶意软件"**。两种解决方法(任选一): **方法 A — 系统设置放行** 1. 双击 App → 出现警告 → 点击「完成」 2. 系统设置 → 隐私与安全 → 滚动到底部 → **「仍要打开」** 3. 再双击 App → 现在多了「打开」按钮 **方法 B — 命令行解除隔离属性**(无需系统设置) ```bash xattr -d com.apple.quarantine /Applications/ClipBoard.app ``` > 原理:DMG/网络下载会被打上 `com.apple.quarantine` xattr,触发 Gatekeeper。手动移除即可。 > 想完全没警告?走第三档。 --- ### 三、正式分发(需 Apple Developer 账号,$99/年) 走完整流程:**Developer ID 签名 → 公证(Notarization)→ 装订(Staple)**。这样下载就没任何警告。 #### 准备工作(一次性) 1. 加入 [Apple Developer Program](https://developer.apple.com/programs/) 2. 在 [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list) 申请一张 **Developer ID Application** 证书并导入钥匙串 3. 在 [App-Specific Passwords](https://appleid.apple.com/account/manage) 生成一个 app-specific password(**不是 Apple ID 密码**),用于公证 API #### 完整发布命令 ```bash # 1) 用 Developer ID 签名打包 export CODESIGN_ID="Developer ID Application: Your Name (TEAMID)" ./scripts/build-app.sh ./scripts/make-dmg.sh # → build/ClipBoard-0.4.0.dmg # 2) 提交公证(首次会上传到苹果,约 1-5 分钟) xcrun notarytool submit build/ClipBoard-0.4.0.dmg \ --apple-id "you@example.com" \ --team-id "TEAMID" \ --password "abcd-efgh-ijkl-mnop" \ --wait # 3) 把公证票据装订到 DMG(让 DMG 离线也能验证) xcrun stapler staple build/ClipBoard-0.4.0.dmg # 4) 验证 spctl --assess --type open --context context:primary-signature -v build/ClipBoard-0.4.0.dmg # 应输出:accepted, source=Notarized Developer ID ``` #### 把凭证存到钥匙串(避免命令行明文密码) ```bash xcrun notarytool store-credentials "ClipBoardNotary" \ --apple-id "you@example.com" \ --team-id "TEAMID" \ --password "abcd-efgh-ijkl-mnop" # 之后只需: xcrun notarytool submit build/ClipBoard-0.4.0.dmg \ --keychain-profile "ClipBoardNotary" --wait ``` #### 常见公证失败原因 | 报错 | 原因 | 解决 | |------|------|------| | `The signature does not include a secure timestamp` | 签名时没带 `--timestamp` | `build-app.sh` 已默认加上,确认没改 | | `The binary is not signed with a valid Developer ID certificate` | 用了 ad-hoc 或 Apple Development 证书 | 用 **Developer ID Application** 证书 | | `The executable does not have the hardened runtime enabled` | 没启用 hardened runtime | 脚本已加 `--options runtime` | | `Invalid Team ID` | TEAMID 错 | 在 [Membership](https://developer.apple.com/account/#!/membership) 页面查 | --- ### 版本号升级 每次发版前,更新 [`Resources/Info.plist`](./Resources/Info.plist) 的两个键: ```xml CFBundleShortVersionString 0.5.0 CFBundleVersion 5 ``` DMG 文件名会自动取用 `CFBundleShortVersionString`。 --- ### 自动更新(可选,未实装) 如果想让用户自动收到更新,推荐接入 [Sparkle](https://sparkle-project.org/): 1. 加入 SPM 依赖:`https://github.com/sparkle-project/Sparkle` 2. 自建一个 `appcast.xml`,托管到 GitHub Pages / 自己服务器 3. 用 [generate_appcast](https://sparkle-project.org/documentation/publishing/) 工具生成 EdDSA 签名的发布信息 4. 把生成的 `appcast.xml` URL 写进 Info.plist 的 `SUFeedURL` 未来 M5 会考虑接入。 --- ### 上架 Mac App Store(可选) 需要的额外工作: - 启用 **App Sandbox**(剪贴板监听 API 仍可用,但模拟 ⌘V 等动作受限,要走 entitlement) - 替换签名证书为 **Apple Distribution** - 在 App Store Connect 创建 App 记录并上传 `.pkg`(用 `xcrun altool` 或 Xcode Organizer) - 通过 App Review(剪贴板类工具通常审核较严,需要清晰隐私说明) > 我们目前不上架,走 Developer ID 直接分发即可。 --- ## 📅 进度 | 里程碑 | 状态 | 内容 | |--------|------|------| | M1 MVP | ✅ | 状态栏 + 文本历史 + 点击回写 | | M2 可用版 | ✅ | 持久化 + 全局快捷键 + 浮窗 + 自动粘贴 | | M3 完整版 | ✅ | 图片/文件/RTF + 收藏/删除 + 类型筛选 + 偏好设置 | | M4 可分发版 | ✅ | .app 包 + 开机自启 + 自定义快捷键 + 缩略图缓存 + DMG | | M5 增强 | ⏳ | iCloud 同步 / Snippets / OCR / AI 摘要 | 详细设计请见 [PLAN.md](./PLAN.md)。 --- ## 📜 License MIT