Building a cross-platform clipboard manager presents unique challenges, especially when dealing with different operating systems' native APIs. In this series, we'll develop cclip
, a clipboard manager from scratch that works across macOS, Linux, and Windows. We'll use Go as our primary language, leveraging CGO to interface with native APIs through C and Objective-C.
This first part focuses on establishing the foundation for our cross-platform implementation, setting up the project structure, and defining clean functions that will allow us to implement platform-specific behaviors while maintaining a consistent API across our application.
Project Overview
cclip
is designed to be a lightweight, efficient clipboard manager that runs in the background, monitoring and storing clipboard changes while providing a simple API for clipboard operations. Key features include:
- Clean API for clipboard operations (Read, Write, Watch)
- Real-time clipboard monitoring
- Initially with a web-based UI using Server-Sent Events (SSE) for real-time updates
- Later, once the core functionality is completed, native UIs for each platform
- Support for text and images
- Persistent clipboard history storage
- History full-text search
- Smart clipboard history search using LLMs πͺ
- Integration with local LLM models served by Ollama
- Context-aware searches for images and text
Architecture
The clipboard manager is designed both as a standalone application and as a reusable Go package. It leverages Go's build constraints through filename conventions to implement platform-specific behaviors while providing a clean, importable API for other projects. The project is structured around these main components:
- Platform Agnostic Core (
cclip.go
): Defines the package's public API and orchestrates clipboard operations across platforms, making it importable by other Go projects - Platform Bridges: CGO-based implementations for each OS (
cclip_darwin.go
,cclip_linux.go
,cclip_windows.go
) - Storage Layer (
store/
): Handles persistent clipboard history - LLM Integration (
llm/
): Manages interactions with Ollama for smart searches - Main Application (
cmd/cclip/
): Entry point to thecclip
clipboard manager application
1cclip/
2βββ cclip.go # Platform-agnostic public API
3βββ cclip_darwin.go # macOS native implementation
4βββ cclip_linux.go # Linux native implementation
5βββ cclip_windows.go # Windows native implementation
6βββ cmd/
7β βββ cclip/
8β βββ main.go # Application entry point
9βββ go.mod
10βββ store/
11β βββ store.go # Clipboard history persistence
12βββ llm/
13 βββ llm.go # LLM integration for smart searches
Core Interface Design
At the heart of cclip
is a set of simple, clear functions that abstract clipboard operations across different platforms. These package-level functions delegate to platform-specific implementations through build-constrained files.
The Public API
1package cclip
2
3// Write data to the clipboard.
4func Write(data []byte) error
5
6// Read the current content of the clipboard. If clipboard is empty,
7// or there was an error reading, the returned value will be nil.
8func Read() []byte
9
10// Handler handles new data that was written to the clipboard.
11type Handler func(ctx context.Context, data []byte)
12
13// Watch for new changes in the clipboard. This is a blocking call until
14// context cancellation or error.
15func Watch(ctx context.Context, checkInterval time.Duration, handler Handler)
This API design offers:
- Simple, straightforward functions for clipboard operations
- Byte slices for maximum flexibility in data handling
- Context-aware watching with customizable check intervals
- Callback-based handling of clipboard changes
Usage Example
This is how the cclip package
can be used. Exactly how we'll use it when implementing the standalone cclip app
.
1// Example usage of the package.
2
3// Write to clipboard.
4data := []byte("Hello, clipboard!")
5if err := cclip.Write(data); err != nil {
6 log.Fatal(err)
7}
8
9// Read current content.
10if content := cclip.Read(); content != nil {
11 fmt.Printf("Clipboard contains: %s\n", content)
12}
13
14// Watch for changes.
15ctx := context.Background()
16cclip.Watch(ctx, time.Second, func(ctx context.Context, data []byte) {
17 fmt.Printf("Clipboard changed: %s\n", data)
18})
Build and Run
Now that we have a clear understanding of cclip
's architecture and public API, let's go through the steps to compile and run a basic version of our clipboard manager on different platforms. This will have us ready to start coding the implementation later.
Ensure you have Go installed. Download it from the official Go website.
Create Project
To start, create a new directory for your project and navigate into it:
1mkdir cclip
2cd cclip
Next, initialize a new Go module. This will set up the structure needed to manage dependencies:
1go mod init cclip
Now your project is ready for development! Let's start by creating the necessary files and directories in the next section.
Platform-Specific Implementations
Each supported platform (macOS, Linux, and Windows) provides its own implementation through build-constrained files. Create the file cclip/cclip_darwin.go
with the following content:
1package cclip
2
3import "fmt"
4
5func read() []byte {
6 // macOS-specific implementation using native APIs.
7 fmt.Println("macOS: clipboard read")
8 return nil
9}
10
11func write(data []byte) error {
12 // macOS-specific implementation using native APIs.
13 fmt.Println("macOS: clipboard write")
14 return nil
15}
16
17func watch(ctx context.Context, checkInterval time.Duration, handler Handler) {
18 // macOS-specific implementation using native APIs.
19 fmt.Println("macOS: clipboard watch")
20}
The Linux (cclip/cclip_linux.go
) and Windows (cclip/cclip_windows.go
) implementations follow the same pattern, each using their respective native APIs.
Create the files with the same content as the macOS one, except for the comments and log message. Update them accordingly.
Coordinating Platform-Specific Code in cclip.go
The package-level functions in cclip/cclip.go
orchestrate the above platform-specific implementations. Create the file with the following content:
1package cclip
2
3import (
4 "context"
5 "time"
6)
7
8// Write data to the clipboard.
9func Write(data []byte) error {
10 // Calls platform-specific implementation.
11 return write(data)
12}
13
14// Read the current content of the clipboard. If clipboard is empty,
15// or there was an error reading, the returned value will be nil.
16func Read() []byte {
17 // Calls platform-specific implementation.
18 return read()
19}
20
21// Handler handles new data that was written to the clipboard.
22type Handler func(ctx context.Context, data []byte)
23
24// Watch for new changes in the clipboard. This is a blocking call until
25// context cancellation or error.
26func Watch(ctx context.Context, checkInterval time.Duration, handler Handler) {
27 // Calls platform-specific implementation.
28 watch(ctx, checkInterval, handler)
29}
Application Entry Point
This is the entry point to the cclip
application. Create the file cclip/cmd/cclip/main.go
with the following content:
1package main
2
3import (
4 "context"
5 "time"
6
7 "cclip"
8)
9
10func main() {
11 cclip.Read()
12 cclip.Write(nil)
13 cclip.Watch(context.TODO(), 500*time.Millisecond, nil)
14}
Run the cclip
app, and see it output the messages of each clipboard operation:
1β― go run ./cmd/cclip
2macOS: clipboard read
3macOS: clipboard write
4macOS: clipboard watch
Depending on the operating system you're running, you should get the platform-specific placeholder message we defined.
Next Steps
In the next post, βBuild a cross-platform clipboard manager: Part 2 β Native macOS support,β we'll dive into implementing the clipboard operations (Write
, Read
, and Watch
) specifically for macOS. We'll leverage CGO to call native AppKit APIs written in C/Objective-C, bridging them with our Go code. By the end of Part 2, you'll have a fully functional macOS clipboard manager that seamlessly integrates with the native clipboard system.
Future parts will focus on adding Linux and Windows support, exploring storage and UI enhancements, and integrating local LLM models to achieve advanced, context-aware clipboard search. Stay tuned!