capso-screenshot-macos
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCapso Screenshot & Screen Recording Skill
Capso截图与录屏应用进阶指南
Skill by ara.so — Daily 2026 Skills collection.
Capso is a fully native, open-source macOS screenshot and screen recording app — a free alternative to CleanShot X. Built with Swift 6.0 and SwiftUI targeting macOS 15.0+. Its key strength for developers is a modular SPM architecture: 8 independent packages (, , , etc.) you can embed individually in your own app.
CaptureKitAnnotationKitOCRKit由ara.so提供的技能指南 — 属于Daily 2026 Skills系列。
Capso是一款完全原生的开源macOS截图与录屏应用,是CleanShot X的免费替代方案。基于Swift 6.0和SwiftUI开发,适配macOS 15.0+。对开发者而言,它的核心优势是模块化SPM架构:包含8个独立包(、、等),你可以将这些包单独嵌入自己的应用中。
CaptureKitAnnotationKitOCRKitInstallation
安装
Download Pre-built App
下载预构建应用
bash
undefinedbash
undefinedHomebrew (recommended)
Homebrew(推荐方式)
brew tap lzhgus/tap
brew install --cask capso
Or download the signed DMG from [GitHub Releases](https://github.com/lzhgus/Capso/releases/latest).brew tap lzhgus/tap
brew install --cask capso
或者从[GitHub Releases](https://github.com/lzhgus/Capso/releases/latest)下载签名后的DMG安装包。Build from Source
从源码构建
Requirements: Xcode 16+, macOS 15.0+, XcodeGen
bash
brew install xcodegen
git clone https://github.com/lzhgus/Capso.git
cd Capso
xcodegen generate
open Capso.xcodeproj要求: Xcode 16+、macOS 15.0+、XcodeGen
bash
brew install xcodegen
git clone https://github.com/lzhgus/Capso.git
cd Capso
xcodegen generate
open Capso.xcodeprojPress Cmd+R to build and run
按下Cmd+R构建并运行
**CLI build:**
```bash
xcodegen generate
xcodebuild -project Capso.xcodeproj \
-scheme Capso \
-configuration Release \
build
**CLI构建方式:**
```bash
xcodegen generate
xcodebuild -project Capso.xcodeproj \
-scheme Capso \
-configuration Release \
buildProject Architecture
项目架构
Capso/
├── App/ # Thin SwiftUI + AppKit shell
│ ├── CapsoApp.swift # @main entry point
│ ├── MenuBar/
│ ├── Capture/
│ ├── Recording/
│ ├── Camera/
│ ├── AnnotationEditor/
│ ├── OCR/
│ ├── QuickAccess/
│ └── Preferences/
├── Packages/
│ ├── SharedKit/ # Settings, permissions, utilities
│ ├── CaptureKit/ # ScreenCaptureKit wrapper
│ ├── RecordingKit/ # Screen recording engine
│ ├── CameraKit/ # AVFoundation webcam capture
│ ├── AnnotationKit/ # Drawing/annotation system
│ ├── OCRKit/ # Vision framework OCR
│ ├── ExportKit/ # Video/GIF/image export
│ └── EffectsKit/ # Cursor effects, click highlights
└── project.yml # XcodeGen project definitionThe app shell is intentionally thin — all logic lives in packages. This means you can pull individual packages into your own app via SPM.
Capso/
├── App/ # 轻量SwiftUI + AppKit外壳
│ ├── CapsoApp.swift # @main入口文件
│ ├── MenuBar/
│ ├── Capture/
│ ├── Recording/
│ ├── Camera/
│ ├── AnnotationEditor/
│ ├── OCR/
│ ├── QuickAccess/
│ └── Preferences/
├── Packages/
│ ├── SharedKit/ # 设置、权限、工具类
│ ├── CaptureKit/ # ScreenCaptureKit封装
│ ├── RecordingKit/ # 录屏引擎
│ ├── CameraKit/ # AVFoundation摄像头捕获
│ ├── AnnotationKit/ # 绘图/标注系统
│ ├── OCRKit/ # Vision框架OCR
│ ├── ExportKit/ # 视频/GIF/图片导出
│ └── EffectsKit/ # 光标特效、点击高亮
└── project.yml # XcodeGen项目定义文件应用外壳被设计得非常精简——所有逻辑都封装在包中。这意味着你可以通过SPM将单个包集成到自己的应用中。
Using Capso Packages in Your Own App
在自己的应用中使用Capso包
Add a package as a local or remote SPM dependency in your :
Package.swiftswift
// Package.swift
let package = Package(
name: "MyApp",
platforms: [.macOS(.v15)],
dependencies: [
// Remote (once published to a registry or via exact path)
.package(path: "../Capso/Packages/CaptureKit"),
.package(path: "../Capso/Packages/AnnotationKit"),
.package(path: "../Capso/Packages/OCRKit"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
"CaptureKit",
"AnnotationKit",
"OCRKit",
]
),
]
)在你的中添加本地或远程SPM依赖:
Package.swiftswift
// Package.swift
let package = Package(
name: "MyApp",
platforms: [.macOS(.v15)],
dependencies: [
// 远程依赖(发布到注册表或指定路径)
.package(path: "../Capso/Packages/CaptureKit"),
.package(path: "../Capso/Packages/AnnotationKit"),
.package(path: "../Capso/Packages/OCRKit"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
"CaptureKit",
"AnnotationKit",
"OCRKit",
]
),
]
)Package API Examples
包API示例
CaptureKit — Screen Capture
CaptureKit — 屏幕截图
CaptureKit wraps for area, fullscreen, and window capture.
ScreenCaptureKitswift
import CaptureKit
// Area capture
let captureManager = CaptureManager()
// Fullscreen capture
Task {
let image: NSImage = try await captureManager.captureFullscreen()
// use image
}
// Window capture — pass SCWindow from ScreenCaptureKit
Task {
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
if let window = content.windows.first {
let image: NSImage = try await captureManager.captureWindow(window)
}
}
// Area capture with a selection rect
Task {
let rect = CGRect(x: 100, y: 100, width: 800, height: 600)
let image: NSImage = try await captureManager.captureArea(rect)
}CaptureKit封装了,支持区域、全屏和窗口截图。
ScreenCaptureKitswift
import CaptureKit
// 区域截图
let captureManager = CaptureManager()
// 全屏截图
Task {
let image: NSImage = try await captureManager.captureFullscreen()
// 使用截图
}
// 窗口截图 — 传入ScreenCaptureKit的SCWindow对象
Task {
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
if let window = content.windows.first {
let image: NSImage = try await captureManager.captureWindow(window)
}
}
// 指定矩形区域截图
Task {
let rect = CGRect(x: 100, y: 100, width: 800, height: 600)
let image: NSImage = try await captureManager.captureArea(rect)
}RecordingKit — Screen Recording
RecordingKit — 屏幕录制
swift
import RecordingKit
let recorder = ScreenRecorder()
// Configure recording
var config = RecordingConfiguration()
config.includesSystemAudio = true
config.includesMicrophone = false
config.outputFormat = .mp4 // or .gif
config.quality = .maximum // .social, .web
// Start recording a region
Task {
let outputURL = URL(fileURLWithPath: "/tmp/recording.mp4")
try await recorder.startRecording(
region: CGRect(x: 0, y: 0, width: 1920, height: 1080),
to: outputURL,
configuration: config
)
}
// Pause / resume
recorder.pause()
recorder.resume()
// Stop and get final URL
Task {
let finalURL = try await recorder.stopRecording()
print("Saved to \(finalURL)")
}swift
import RecordingKit
let recorder = ScreenRecorder()
// 配置录制参数
var config = RecordingConfiguration()
config.includesSystemAudio = true
config.includesMicrophone = false
config.outputFormat = .mp4 // 或.gif
config.quality = .maximum // .social, .web
// 开始录制指定区域
Task {
let outputURL = URL(fileURLWithPath: "/tmp/recording.mp4")
try await recorder.startRecording(
region: CGRect(x: 0, y: 0, width: 1920, height: 1080),
to: outputURL,
configuration: config
)
}
// 暂停 / 恢复
recorder.pause()
recorder.resume()
// 停止录制并获取最终文件URL
Task {
let finalURL = try await recorder.stopRecording()
print("Saved to \(finalURL)")
}CameraKit — Webcam PiP
CameraKit — 画中画摄像头
swift
import CameraKit
let cameraManager = CameraManager()
// Request permission and start preview
Task {
let granted = await cameraManager.requestPermission()
guard granted else { return }
// Get AVCaptureVideoPreviewLayer for embedding in a view
let previewLayer = try await cameraManager.startCapture()
// Set PiP shape
cameraManager.pipShape = .circle // .circle, .square, .portrait, .landscape
}
// Stop capture
cameraManager.stopCapture()swift
import CameraKit
let cameraManager = CameraManager()
// 请求权限并启动预览
Task {
let granted = await cameraManager.requestPermission()
guard granted else { return }
// 获取AVCaptureVideoPreviewLayer嵌入到视图中
let previewLayer = try await cameraManager.startCapture()
// 设置画中画形状
cameraManager.pipShape = .circle // .circle, .square, .portrait, .landscape
}
// 停止捕获
cameraManager.stopCapture()AnnotationKit — Drawing & Annotation
AnnotationKit — 绘图与标注
swift
import AnnotationKit
// Create an annotation canvas over an NSImage
let sourceImage = NSImage(named: "screenshot")!
let canvas = AnnotationCanvas(image: sourceImage)
// Add annotations programmatically
let arrow = ArrowAnnotation(
from: CGPoint(x: 50, y: 50),
to: CGPoint(x: 200, y: 200),
color: .red,
strokeWidth: 3
)
canvas.addAnnotation(arrow)
let rect = RectangleAnnotation(
frame: CGRect(x: 100, y: 100, width: 300, height: 150),
color: .blue,
strokeWidth: 2,
filled: false
)
canvas.addAnnotation(rect)
let text = TextAnnotation(
text: "Look here!",
position: CGPoint(x: 110, y: 110),
fontSize: 18,
color: .white
)
canvas.addAnnotation(text)
// Undo / redo
canvas.undo()
canvas.redo()
// Export annotated image
let result: NSImage = canvas.renderToImage()swift
import AnnotationKit
// 在NSImage上创建标注画布
let sourceImage = NSImage(named: "screenshot")!
let canvas = AnnotationCanvas(image: sourceImage)
// 以编程方式添加标注
let arrow = ArrowAnnotation(
from: CGPoint(x: 50, y: 50),
to: CGPoint(x: 200, y: 200),
color: .red,
strokeWidth: 3
)
canvas.addAnnotation(arrow)
let rect = RectangleAnnotation(
frame: CGRect(x: 100, y: 100, width: 300, height: 150),
color: .blue,
strokeWidth: 2,
filled: false
)
canvas.addAnnotation(rect)
let text = TextAnnotation(
text: "Look here!",
position: CGPoint(x: 110, y: 110),
fontSize: 18,
color: .white
)
canvas.addAnnotation(text)
// 撤销 / 重做
canvas.undo()
canvas.redo()
// 导出带标注的图片
let result: NSImage = canvas.renderToImage()Screenshot Beautification (AnnotationKit)
截图美化(AnnotationKit)
swift
import AnnotationKit
let beautifier = ScreenshotBeautifier(image: rawImage)
beautifier.backgroundColor = .systemBlue // or gradient/custom
beautifier.padding = 40
beautifier.cornerRadius = 12
beautifier.shadowRadius = 20
beautifier.shadowOpacity = 0.4
let beautified: NSImage = beautifier.render()swift
import AnnotationKit
let beautifier = ScreenshotBeautifier(image: rawImage)
beautifier.backgroundColor = .systemBlue // 或渐变/自定义颜色
beautifier.padding = 40
beautifier.cornerRadius = 12
beautifier.shadowRadius = 20
beautifier.shadowOpacity = 0.4
let beautified: NSImage = beautifier.render()OCRKit — Text Recognition
OCRKit — 文字识别
swift
import OCRKit
let ocrEngine = OCREngine()
// Instant OCR on an NSImage — returns plain text
Task {
let text: String = try await ocrEngine.recognizeText(in: image)
print(text)
// Copy to clipboard
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(text, forType: .string)
}
// Visual OCR — returns bounding boxes + text for each block
Task {
let blocks: [OCRTextBlock] = try await ocrEngine.recognizeBlocks(in: image)
for block in blocks {
print("Text: \(block.text), Bounds: \(block.boundingBox)")
}
}swift
import OCRKit
let ocrEngine = OCREngine()
// 对NSImage进行即时OCR — 返回纯文本
Task {
let text: String = try await ocrEngine.recognizeText(in: image)
print(text)
// 复制到剪贴板
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(text, forType: .string)
}
// 可视化OCR — 返回每个文本块的边界框和文字
Task {
let blocks: [OCRTextBlock] = try await ocrEngine.recognizeBlocks(in: image)
for block in blocks {
print("Text: \(block.text), Bounds: \(block.boundingBox)")
}
}ExportKit — Video & GIF Export
ExportKit — 视频与GIF导出
swift
import ExportKit
let exporter = MediaExporter()
// Export recorded video with quality preset
Task {
let inputURL = URL(fileURLWithPath: "/tmp/raw_recording.mp4")
let outputURL = URL(fileURLWithPath: "/tmp/final.mp4")
try await exporter.exportVideo(
from: inputURL,
to: outputURL,
quality: .social // .maximum, .social, .web
)
}
// Export as GIF
Task {
let inputURL = URL(fileURLWithPath: "/tmp/raw_recording.mp4")
let outputURL = URL(fileURLWithPath: "/tmp/output.gif")
try await exporter.exportGIF(
from: inputURL,
to: outputURL,
fps: 15,
scale: 0.75
)
}swift
import ExportKit
let exporter = MediaExporter()
// 按质量预设导出录制视频
Task {
let inputURL = URL(fileURLWithPath: "/tmp/raw_recording.mp4")
let outputURL = URL(fileURLWithPath: "/tmp/final.mp4")
try await exporter.exportVideo(
from: inputURL,
to: outputURL,
quality: .social // .maximum, .social, .web
)
}
// 导出为GIF
Task {
let inputURL = URL(fileURLWithPath: "/tmp/raw_recording.mp4")
let outputURL = URL(fileURLWithPath: "/tmp/output.gif")
try await exporter.exportGIF(
from: inputURL,
to: outputURL,
fps: 15,
scale: 0.75
)
}SharedKit — Permissions & Settings
SharedKit — 权限与设置
swift
import SharedKit
// Check and request screen recording permission
let permissionManager = PermissionManager()
Task {
let hasScreen = await permissionManager.requestScreenRecordingPermission()
let hasCamera = await permissionManager.requestCameraPermission()
let hasMic = await permissionManager.requestMicrophonePermission()
}
// Access shared app settings
let settings = CapsoSettings.shared
settings.screenshotShortcut = "⌘⇧4"
settings.defaultSaveLocation = URL(fileURLWithPath: "/Users/me/Screenshots")
settings.showCountdownBeforeRecording = true
settings.countdownSeconds = 3swift
import SharedKit
// 检查并请求录屏权限
let permissionManager = PermissionManager()
Task {
let hasScreen = await permissionManager.requestScreenRecordingPermission()
let hasCamera = await permissionManager.requestCameraPermission()
let hasMic = await permissionManager.requestMicrophonePermission()
}
// 访问共享应用设置
let settings = CapsoSettings.shared
settings.screenshotShortcut = "⌘⇧4"
settings.defaultSaveLocation = URL(fileURLWithPath: "/Users/me/Screenshots")
settings.showCountdownBeforeRecording = true
settings.countdownSeconds = 3SwiftUI Integration Pattern
SwiftUI集成示例
Embed a capture button in a SwiftUI view:
swift
import SwiftUI
import CaptureKit
import AnnotationKit
struct ContentView: View {
@State private var capturedImage: NSImage?
@State private var showAnnotationEditor = false
private let captureManager = CaptureManager()
var body: some View {
VStack {
if let img = capturedImage {
Image(nsImage: img)
.resizable()
.scaledToFit()
.frame(maxWidth: 600)
Button("Annotate") {
showAnnotationEditor = true
}
}
Button("Capture Fullscreen") {
Task {
capturedImage = try? await captureManager.captureFullscreen()
}
}
}
.sheet(isPresented: $showAnnotationEditor) {
if let img = capturedImage {
// Hypothetical SwiftUI wrapper around AnnotationCanvas
AnnotationEditorView(image: img) { annotated in
capturedImage = annotated
showAnnotationEditor = false
}
}
}
}
}在SwiftUI视图中嵌入截图按钮:
swift
import SwiftUI
import CaptureKit
import AnnotationKit
struct ContentView: View {
@State private var capturedImage: NSImage?
@State private var showAnnotationEditor = false
private let captureManager = CaptureManager()
var body: some View {
VStack {
if let img = capturedImage {
Image(nsImage: img)
.resizable()
.scaledToFit()
.frame(maxWidth: 600)
Button("Annotate") {
showAnnotationEditor = true
}
}
Button("Capture Fullscreen") {
Task {
capturedImage = try? await captureManager.captureFullscreen()
}
}
}
.sheet(isPresented: $showAnnotationEditor) {
if let img = capturedImage {
// AnnotationCanvas的SwiftUI封装视图
AnnotationEditorView(image: img) { annotated in
capturedImage = annotated
showAnnotationEditor = false
}
}
}
}
}Running Package Tests
运行包测试
Each package is independently testable:
bash
swift test --package-path Packages/SharedKit
swift test --package-path Packages/CaptureKit
swift test --package-path Packages/AnnotationKit
swift test --package-path Packages/OCRKit
swift test --package-path Packages/RecordingKit
swift test --package-path Packages/CameraKit
swift test --package-path Packages/ExportKit
swift test --package-path Packages/EffectsKit每个包都可以独立测试:
bash
swift test --package-path Packages/SharedKit
swift test --package-path Packages/CaptureKit
swift test --package-path Packages/AnnotationKit
swift test --package-path Packages/OCRKit
swift test --package-path Packages/RecordingKit
swift test --package-path Packages/CameraKit
swift test --package-path Packages/ExportKit
swift test --package-path Packages/EffectsKitRequired Entitlements & Info.plist
必要权限与Info.plist配置
Your app using Capso packages needs these permissions:
xml
<!-- Info.plist -->
<key>NSScreenCaptureUsageDescription</key>
<string>Required for screenshot and screen recording.</string>
<key>NSCameraUsageDescription</key>
<string>Required for webcam PiP during screen recording.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Required to capture microphone audio during recording.</string>xml
<!-- App.entitlements -->
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<!-- Screen capture is runtime-only via TCC, no entitlement needed -->使用Capso包的应用需要以下权限:
xml
<!-- Info.plist -->
<key>NSScreenCaptureUsageDescription</key>
<string>Required for screenshot and screen recording.</string>
<key>NSCameraUsageDescription</key>
<string>Required for webcam PiP during screen recording.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Required to capture microphone audio during recording.</string>xml
<!-- App.entitlements -->
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<!-- 屏幕捕获仅需运行时通过TCC授权,无需权限配置 -->Common Patterns
常见实现模式
Pin Screenshot to Screen (Always-on-Top)
截图置顶显示(始终在最上层)
swift
import AppKit
func pinScreenshot(_ image: NSImage) {
let window = NSPanel(
contentRect: NSRect(x: 100, y: 100, width: image.size.width, height: image.size.height),
styleMask: [.nonactivatingPanel, .titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.level = .floating // Always on top
window.isFloatingPanel = true
window.hidesOnDeactivate = false
window.contentView = NSImageView(image: image)
window.makeKeyAndOrderFront(nil)
}swift
import AppKit
func pinScreenshot(_ image: NSImage) {
let window = NSPanel(
contentRect: NSRect(x: 100, y: 100, width: image.size.width, height: image.size.height),
styleMask: [.nonactivatingPanel, .titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.level = .floating // 始终在最上层
window.isFloatingPanel = true
window.hidesOnDeactivate = false
window.contentView = NSImageView(image: image)
window.makeKeyAndOrderFront(nil)
}Global Keyboard Shortcut (via SharedKit pattern)
全局键盘快捷键(基于SharedKit模式)
swift
import Carbon
import AppKit
// Register a global hotkey for area capture
func registerCaptureHotkey() {
NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { event in
// Check for ⌘⇧4
if event.modifierFlags.contains([.command, .shift]),
event.keyCode == 21 { // keyCode 21 = '4'
NotificationCenter.default.post(name: .startAreaCapture, object: nil)
}
}
}
extension Notification.Name {
static let startAreaCapture = Notification.Name("startAreaCapture")
}swift
import Carbon
import AppKit
// 注册区域截图的全局热键
func registerCaptureHotkey() {
NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { event in
// 检查是否按下⌘⇧4
if event.modifierFlags.contains([.command, .shift]),
event.keyCode == 21 { // keyCode 21 = '4'
NotificationCenter.default.post(name: .startAreaCapture, object: nil)
}
}
}
extension Notification.Name {
static let startAreaCapture = Notification.Name("startAreaCapture")
}Troubleshooting
问题排查
xcodegen generate
fails
xcodegen generatexcodegen generate
执行失败
xcodegen generate- Ensure XcodeGen ≥ 2.40:
brew upgrade xcodegen - Check is not modified with invalid YAML syntax
project.yml - Run for explicit path
xcodegen generate --spec project.yml
- 确保XcodeGen版本≥2.40:
brew upgrade xcodegen - 检查未被修改为无效YAML语法
project.yml - 运行指定配置文件路径
xcodegen generate --spec project.yml
"Screen Recording permission denied" at runtime
运行时提示“屏幕录制权限被拒绝”
- Go to System Settings → Privacy & Security → Screen Recording and enable Capso
- For your own app using CaptureKit, you must trigger the permission prompt first via
PermissionManager.requestScreenRecordingPermission()
- 前往系统设置 → 隐私与安全性 → 屏幕录制,启用Capso权限
- 对于使用CaptureKit的自定义应用,必须先通过触发权限请求
PermissionManager.requestScreenRecordingPermission()
Build errors with Swift 6 concurrency
Swift 6并发特性导致构建错误
- Capso targets Swift 6 strict concurrency. Ensure all closures that touch UI are
@MainActor - Add import for ScreenCaptureKit if you see warnings in Xcode 16
@preconcurrency
- Capso采用Swift 6严格并发模式,确保所有涉及UI的闭包标记为
@MainActor - 如果在Xcode 16中看到警告,为ScreenCaptureKit添加导入
@preconcurrency
GIF export is slow
GIF导出速度慢
- Lower the fps (e.g. ) and scale (e.g.
fps: 10) inscale: 0.5ExportKit - Use quality preset for faster encoding
.web
- 在中降低fps(例如
ExportKit)和缩放比例(例如fps: 10)scale: 0.5 - 使用质量预设加快编码速度
.web
Camera not showing in PiP
摄像头画中画不显示
- Verify camera permission is granted in System Settings
- Call before
CameraManager.requestPermission()startCapture() - Ensure your entitlement includes
com.apple.security.device.camera
- 在系统设置中确认摄像头权限已授予
- 在调用前先调用
startCapture()CameraManager.requestPermission() - 确保权限配置文件包含
com.apple.security.device.camera
License Note
许可证说明
Capso uses Business Source License 1.1:
- ✅ Personal use, internal company use, forking, modifying
- ❌ Selling a competing screen-capture product based on this code
- ✅ Automatically becomes Apache 2.0 in 2029 per release
When embedding packages in your own non-competing app, you are permitted under BSL 1.1.
Capso采用Business Source License 1.1:
- ✅ 个人使用、企业内部使用、复刻、修改
- ❌ 基于此代码开发竞品截图录屏产品并售卖
- ✅ 2029年自动转为Apache 2.0许可证
将包集成到非竞品应用中时,符合BSL 1.1许可证要求。