Skip to content

Latest commit

 

History

History
439 lines (330 loc) · 12.5 KB

File metadata and controls

439 lines (330 loc) · 12.5 KB

AGENTS.md

This file documents everything an AI agent needs to know to work effectively in this codebase.

Project Overview

go-nativeclipboard is a Go library that provides native clipboard functionality using purego instead of cgo. This enables clipboard access without requiring a C compiler.

  • Language: Go 1.25.4
  • Module: github.com/aymanbagabas/go-nativeclipboard
  • Key Dependencies:
    • github.com/ebitengine/purego - For calling native APIs without cgo
  • Status: macOS, Linux, FreeBSD, and Windows implementations complete

Project Structure

.
├── clipboard.go          # Main API and public interfaces
├── clipboard_darwin.go   # macOS implementation (NSPasteboard via purego)
├── clipboard_x11.go      # X11 implementation for Linux and FreeBSD (via purego)
├── clipboard_windows.go  # Windows implementation (Win32 API via syscall)
├── clipboard_test.go     # Platform-agnostic tests
├── doc.go               # Package documentation for pkg.go.dev
├── go.mod               # Go module definition
├── go.sum               # Dependency checksums
├── README.md            # User-facing documentation
├── AGENTS.md            # This file
└── examples/            # Example programs

Dependencies

The project uses purego to call native operating system APIs without cgo:

  1. github.com/ebitengine/purego - Pure Go library for calling C functions and native APIs
    • Used to call Objective-C runtime on macOS
    • Will be used for X11/Wayland on Linux and Win32 on Windows

Previous dependencies (from golang.design/x/clipboard) have been removed as they used cgo.

Essential Commands

Building

go build ./...

Testing

# Run all tests on Linux/BSD (requires X11 and Xvfb for headless)
go test ./...

# Run specific test
go test -v -run TestWriteReadText

# Run with coverage
go test -cover ./...

# Run with race detector
go test -race ./...

# For headless Linux/BSD testing
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
export DISPLAY=:99.0
go test -v ./...

Module Management

# Download dependencies
go mod download

# Update purego to latest
go get -u github.com/ebitengine/purego
go mod tidy

Code Organization

Package Structure

  • Package name: nativeclipboard
  • Single package: No subpackages
  • Platform-specific code: Build tags separate implementations

API Design

The public API is in clipboard.go:

  • Format type with Text and Image constants
  • Format.Read() - Read clipboard data
  • Format.Write([]byte) - Write clipboard data
  • Format.Watch(context.Context) - Monitor clipboard changes

Usage: nativeclipboard.Text.Read(), nativeclipboard.Image.Write(data), etc.

All methods return errors. The package initializes automatically via func init().

Build Tags

Platform-specific files use build constraints:

//go:build darwin && !ios
//go:build (linux || freebsd) && !android
//go:build windows

X11 Implementation (Linux and FreeBSD - Complete)

Uses purego to call X11 library functions directly:

Note: Only FreeBSD is supported among BSD systems, as purego officially supports FreeBSD only.

  1. Dynamic library loading:

    • Dynamically loads libX11.so using purego.Dlopen
    • Searches common paths: libX11.so.6, libX11.so
    • FreeBSD-specific paths: /usr/local/lib, /usr/X11R6/lib
  2. X11 API:

    • XOpenDisplay - Connect to X server
    • XInternAtom - Get atom identifiers
    • XSetSelectionOwner - Claim clipboard ownership
    • XConvertSelection - Request clipboard data
    • XGetWindowProperty - Read clipboard data
    • XChangeProperty - Provide clipboard data
  3. Supported formats:

    • Text: UTF8_STRING
    • Images: image/png
  4. Requirements:

    • libX11 must be installed
    • X11 display must be available (DISPLAY environment variable)
    • For headless: use Xvfb virtual framebuffer
    • CGO_ENABLED=0 - No cgo required (pure Go)
  5. Thread safety:

    • Use runtime.LockOSThread() for thread-sensitive operations

macOS Implementation (Complete)

Uses purego's objc package to call Objective-C runtime and AppKit framework:

  1. Objective-C integration via purego/objc package:

    • objc.GetClass() - Get class by name
    • objc.RegisterName() - Register method selector
    • objc.ID.Send() - Call Objective-C methods
    • objc.Send[T]() - Call methods with typed return values
  2. Dynamic library loading:

    • AppKit framework for NSPasteboard
    • objc package auto-loads Objective-C runtime
  3. NSPasteboard API:

    • [NSPasteboard generalPasteboard] - Get clipboard
    • [pasteboard dataForType:] - Read data
    • [pasteboard setData:forType:] - Write data
    • [pasteboard changeCount] - Monitor changes
  4. Key patterns:

    // Get class
    class := objc.GetClass("NSPasteboard")
    
    // Register selector
    sel := objc.RegisterName("generalPasteboard")
    
    // Call method (returns ID)
    result := objc.ID(class).Send(sel)
    
    // Call method with typed return
    count := objc.Send[int64](obj, sel)
    
    // Call method with arguments
    data := obj.Send(sel, arg1, arg2)
  5. Thread safety:

    • Use runtime.LockOSThread() for thread-sensitive operations
    • Objective-C runtime is thread-safe but pasteboard operations benefit from thread locking

Import Style

Clean imports, no blank imports:

import (
    "context"
    "github.com/ebitengine/purego"
)

Development Guidelines

Go Version

  • Required: Go 1.25.4
  • This is a very recent Go version

Dependency Management

  • Dependencies are managed via go.mod and go.sum
  • Use go get to add new dependencies, then go mod tidy
  • Keep go.mod and go.sum in sync and committed to version control

File Organization

  • Main API in clipboard.go
  • Platform-specific implementations in clipboard_<os>.go
  • Tests in clipboard_test.go (platform-agnostic where possible)
  • Use build tags for platform-specific files

Code Style

  • Follow standard Go conventions
  • Use gofmt or go fmt ./...
  • Keep public API minimal and clear
  • Document all exported functions and types

Testing Approach

Test Organization

  • clipboard_test.go - Main test file
  • Platform-agnostic tests when possible
  • Uses standard Go testing package

Current Tests

  1. TestInit - Verifies initialization
  2. TestWriteReadText - Tests text clipboard operations
  3. TestWriteEmptyText - Tests empty clipboard
  4. TestWatch - Tests change monitoring
  5. TestMultipleReads - Tests concurrent reads

Testing Patterns

// Always initialize first
if err := Init(); err != nil {
    t.Fatalf("Init failed: %v", err)
}

// Add delays for system processing
time.Sleep(100 * time.Millisecond)

// Test with timeouts for async operations
select {
case data := <-ch:
    // process data
case <-time.After(2 * time.Second):
    t.Fatal("Timeout")
}

Platform-Specific Testing

  • macOS, Linux, and BSD tests all work with appropriate setup
  • Windows tests work natively
  • X11-based systems (Linux/BSD) require Xvfb for headless testing
  • Add build tags for platform-specific tests if needed

Platform Considerations

macOS (Complete)

Implementation: Uses Objective-C runtime via purego

  • NSPasteboard for clipboard access
  • Supports text (NSPasteboardTypeString) and images (NSPasteboardTypePNG)
  • Change monitoring via changeCount
  • Thread-safe with LockOSThread

Testing: Fully tested and working

Linux and FreeBSD (Complete)

Implementation: Unified X11 implementation via purego in clipboard_x11.go

Supported systems:

  • Linux (all distributions with X11)
  • FreeBSD (only BSD officially supported by purego)

Approach:

  • Dynamically loads libX11.so using purego.Dlopen
  • Searches multiple library paths (Linux and FreeBSD-specific)
  • Calls X11 clipboard functions directly
  • Supports CLIPBOARD selection (not PRIMARY)
  • Handles UTF8_STRING and image/png types

Requirements:

  • libX11 must be installed
    • Linux: apt install libx11-dev or dnf install libX11-devel
    • FreeBSD: pkg install xorg-libraries
    • OpenBSD: pkg_add libX11
    • NetBSD: pkgin install libX11
  • X11 display must be available (DISPLAY environment variable)
  • For headless: use Xvfb virtual framebuffer
  • CGO_ENABLED=0 - No cgo required (pure Go)

Key X11 functions used:

  • XOpenDisplay - Connect to X server
  • XInternAtom - Get atom identifiers
  • XSetSelectionOwner - Claim clipboard ownership
  • XConvertSelection - Request clipboard data
  • XGetWindowProperty - Read clipboard data
  • XChangeProperty - Provide clipboard data to requesters

Implementation notes:

  • Write operation runs in goroutine with event loop
  • Clipboard ownership maintained until overwritten
  • Uses SelectionRequest/SelectionNotify protocol
  • Supports TARGETS atom for format negotiation

Current state: Fully implemented, compiles successfully

Windows (Complete)

Implementation: Uses Win32 API via syscall

Approach:

  • Uses Go's built-in syscall package
  • Loads user32.dll and kernel32.dll dynamically
  • Supports CF_UNICODETEXT and CF_DIBV5/CF_DIB formats
  • Handles UTF-16 encoding for text

Key Win32 functions used:

  • OpenClipboard - Open clipboard for reading/writing
  • CloseClipboard - Close clipboard
  • EmptyClipboard - Clear clipboard contents
  • GetClipboardData - Read clipboard data
  • SetClipboardData - Write clipboard data
  • GetClipboardSequenceNumber - Monitor changes
  • GlobalAlloc/GlobalLock/GlobalUnlock - Memory management

Format handling:

  • Text: CF_UNICODETEXT with UTF-16 encoding
  • Images: CF_DIBV5 (Device Independent Bitmap V5) format
    • Converts PNG ↔ BITMAPV5HEADER + bitmap data
    • Handles fallback to CF_DIB for compatibility

Implementation notes:

  • Thread safety via runtime.LockOSThread()
  • Retry loop for OpenClipboard (handles contention)
  • Global memory management for clipboard data
  • Sequence number monitoring for change detection

Current state: Fully implemented, compiles successfully

Common Patterns

Objective-C via purego/objc (macOS)

Get class and selector:

class := objc.GetClass("NSPasteboard")
sel := objc.RegisterName("generalPasteboard")

Call method (returns ID):

result := objc.ID(class).Send(sel)

Call method with typed return:

count := objc.Send[int64](obj, sel)
length := objc.Send[uint64](data, sel_length)

Call method with arguments:

data := obj.Send(sel, arg1, arg2)

Load framework and get constants:

appkit, _ := purego.Dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", purego.RTLD_NOW|purego.RTLD_GLOBAL)
constPtr, _ := purego.Dlsym(appkit, "NSPasteboardTypeString")
constant := objc.ID(*(*uintptr)(unsafe.Pointer(constPtr)))

Thread Safety

runtime.LockOSThread()
defer runtime.UnlockOSThread()
// ... perform thread-sensitive operations

Error Handling

// Check for unavailable clipboard
if data == 0 {
    return nil, ErrUnavailable
}

// Check for unsupported format
switch t {
case FmtText, FmtImage:
    // supported
default:
    return nil, ErrUnsupported
}

Gotchas and Notes

Blank Imports

The current blank imports (_ "package") are unusual. This typically means:

  • The packages have init() functions that register handlers
  • Or they're being used to ensure certain code is linked into the binary
  • Future work should clarify why these are blank imports vs. actual API usage

Development Workflow

  1. Make changes to Go files
  2. Build with go build ./... to check for compile errors
  3. Add tests in *_test.go files
  4. Run tests with go test ./...
  5. Update dependencies if needed: go get -u <package> then go mod tidy
  6. Format code with gofmt or go fmt ./... (standard Go practice)

Future Considerations

As this codebase grows, consider adding:

  • CI/CD: GitHub Actions for automated testing
  • Documentation: README.md with usage examples
  • Examples: examples/ directory or example_test.go files
  • Benchmarks: *_test.go files with Benchmark* functions
  • Linting: golangci-lint or similar for code quality
  • Platform tests: Ensure clipboard operations work on all supported platforms

Additional Resources