arboard
Cross-platform clipboard access for text and images
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
arboard
Cross-platform clipboard library by 1Password for reading and writing text and images. Provides a unified API across macOS, Windows, and Linux (X11/Wayland).
Crate: https://crates.io/crates/arboard Docs: https://docs.rs/arboard/latest/arboard/
Key Types
Clipboard
The main entry point. Create one instance to interact with the system clipboard.
use arboard::Clipboard;
let mut clipboard = Clipboard::new()?;
Important: Clipboard::new() returns Result<Clipboard, Error> - it can fail if:
- Clipboard is not supported on the platform/environment
- Another process is holding the clipboard (rare)
ImageData
Stores raw RGBA pixel data for clipboard images.
use arboard::ImageData;
use std::borrow::Cow;
let image = ImageData {
width: 100,
height: 100,
bytes: Cow::Owned(vec![0u8; 100 * 100 * 4]), // RGBA: 4 bytes per pixel
};
Pixel Format:
- Row-major order (left-to-right, top-to-bottom)
- 4 channels per pixel: Red, Green, Blue, Alpha
- Each channel is 1 byte (0-255)
- Total bytes =
width * height * 4
Error
Non-exhaustive enum with these variants:
-
ContentNotAvailable- Clipboard empty or wrong format -
ClipboardNotSupported- Platform/environment doesn't support clipboard -
ClipboardOccupied- Another process is using the clipboard -
ConversionFailure- Image/text couldn't be converted -
Unknown { description }- Catch-all for other errors
Usage in script-kit-gpui
The codebase uses arboard for:
-
Clipboard History Monitoring (
src/clipboard_history/monitor.rs)- Polls clipboard every 50-500ms
- Captures both text and images
- Uses content hashing for deduplication
-
Copy to Clipboard (
src/clipboard_history/clipboard.rs)- Restores entries from history
- Handles both text and image content types
-
Selected Text Operations (
src/selected_text.rs)- Save/restore clipboard during text replacement
- Clipboard-based paste simulation
-
Text Injection (
src/text_injector.rs)- Snippet expansion via clipboard + Cmd+V
- Preserves original clipboard contents
Pattern: Save and Restore
script-kit-gpui consistently uses this pattern when temporarily using the clipboard:
use arboard::Clipboard;
use anyhow::{Context, Result};
fn paste_via_clipboard(text: &str) -> Result<()> {
let mut clipboard = Clipboard::new().context("Failed to access clipboard")?;
// 1. Save original contents
let original = clipboard.get_text().ok();
// 2. Set new content
clipboard.set_text(text).context("Failed to set clipboard")?;
// 3. Perform operation (e.g., simulate Cmd+V)
simulate_paste()?;
// 4. Restore original (best effort)
if let Some(original_text) = original {
let _ = clipboard.set_text(&original_text);
}
Ok(())
}
Text Operations
Get Text
let mut clipboard = Clipboard::new()?;
match clipboard.get_text() {
Ok(text) => println!("Got: {}", text),
Err(arboard::Error::ContentNotAvailable) => println!("Empty or not text"),
Err(e) => eprintln!("Error: {}", e),
}
Set Text
let mut clipboard = Clipboard::new()?;
clipboard.set_text("Hello, world!")?;
// Also accepts String, &String, Cow<str>
clipboard.set_text(String::from("owned"))?;
Set HTML
// HTML with plain text fallback
clipboard.set_html(
"<b>Bold</b> text",
Some("Bold text"), // Alt text for apps that don't support HTML
)?;
Image Operations
Get Image
let mut clipboard = Clipboard::new()?;
match clipboard.get_image() {
Ok(image) => {
println!("{}x{} image, {} bytes",
image.width, image.height, image.bytes.len());
}
Err(arboard::Error::ContentNotAvailable) => {
println!("No image on clipboard");
}
Err(e) => eprintln!("Error: {}", e),
}
Set Image
use arboard::{Clipboard, ImageData};
use std::borrow::Cow;
let mut clipboard = Clipboard::new()?;
// Create a 2x2 red/green/blue/white test image
let pixels = vec![
255, 0, 0, 255, // Red pixel
0, 255, 0, 255, // Green pixel
0, 0, 255, 255, // Blue pixel
255, 255, 255, 255, // White pixel
];
let image = ImageData {
width: 2,
height: 2,
bytes: Cow::Owned(pixels),
};
clipboard.set_image(image)?;
Convert to Owned
Use to_owned_img() when you need to store the image beyond the clipboard's lifetime:
let image = clipboard.get_image()?;
let owned: ImageData<'static> = image.to_owned_img();
// Now safe to use after clipboard is dropped
Platform Specifics
macOS
- Uses
NSPasteboardviaobjc2 - Images stored as
NSImageobjects - Most reliable platform for clipboard operations
- Efficient change detection via
changeCount
Windows
- Uses
clipboard-wincrate - Important: Clipboard is a global object, only one thread can open it at a time
- Parallel operations may fail with
ClipboardOccupied - Image formats:
CF_DIB,CF_BITMAP - Recommended: Avoid creating Clipboard objects on multiple threads
Linux (X11/Wayland)
- Critical: Clipboard content is "hosted" by the application
- When last
Clipboardinstance is dropped, content may become unavailable to other apps! - Use
SetExtLinuxtrait for persistence options:
use arboard::{Clipboard, SetExtLinux};
let mut clipboard = Clipboard::new()?;
clipboard.set()
.wait() // Keep clipboard available after app exits (forks background process)
.text("Persistent text")?;
- Primary vs Clipboard selections (X11):
-
LinuxClipboardKind::Clipboard- Ctrl+C/Ctrl+V (default) -
LinuxClipboardKind::Primary- Middle-click paste
-
Builder API
For advanced operations, use the builder pattern:
let mut clipboard = Clipboard::new()?;
// Get with options
let text = clipboard.get()
.text()?;
// Set with options
clipboard.set()
.text("content")?;
// Clear
clipboard.clear()?;
Integration with image Crate
script-kit-gpui converts between arboard::ImageData and the image crate:
use arboard::ImageData;
use image::RgbaImage;
use std::borrow::Cow;
// ImageData -> RgbaImage
fn to_rgba_image(img: &ImageData) -> Option<RgbaImage> {
RgbaImage::from_raw(
img.width as u32,
img.height as u32,
img.bytes.to_vec(),
)
}
// RgbaImage -> ImageData
fn from_rgba_image(rgba: &RgbaImage) -> ImageData<'static> {
ImageData {
width: rgba.width() as usize,
height: rgba.height() as usize,
bytes: Cow::Owned(rgba.as_raw().clone()),
}
}
Anti-patterns
Don't hold Clipboard across await points
// BAD: Clipboard held during async operation
let mut clipboard = Clipboard::new()?;
let text = clipboard.get_text()?;
some_async_operation().await; // Other processes blocked!
clipboard.set_text(&modified)?;
// GOOD: Drop clipboard before await
let text = {
let mut clipboard = Clipboard::new()?;
clipboard.get_text()?
};
some_async_operation().await;
{
let mut clipboard = Clipboard::new()?;
clipboard.set_text(&modified)?;
}
Don't assume content type
// BAD: Panics if clipboard has image
let text = clipboard.get_text().unwrap();
// GOOD: Handle both content types
if let Ok(text) = clipboard.get_text() {
handle_text(&text);
} else if let Ok(image) = clipboard.get_image() {
handle_image(&image);
}
Don't poll clipboard content directly on Linux
// BAD: Expensive on Linux (reads full payload)
loop {
let content = clipboard.get_text();
thread::sleep(Duration::from_millis(100));
}
// GOOD: Use OS-level change detection when available
// (macOS: NSPasteboard.changeCount, Windows: clipboard sequence number)
Don't forget to handle errors
// BAD: Silent failure
let _ = clipboard.set_text("text");
// GOOD: Log or propagate errors
clipboard.set_text("text").context("Failed to set clipboard")?;
Don't create ImageData with wrong byte count
// BAD: Bytes don't match dimensions
let image = ImageData {
width: 100,
height: 100,
bytes: Cow::Owned(vec![0u8; 1000]), // Should be 40000!
};
// GOOD: Validate or compute correctly
let width = 100;
let height = 100;
let bytes = vec![0u8; width * height * 4];
let image = ImageData { width, height, bytes: Cow::Owned(bytes) };
Error Handling Best Practices
use arboard::{Clipboard, Error};
use anyhow::{Context, Result};
fn clipboard_operation() -> Result<String> {
let mut clipboard = Clipboard::new()
.context("Failed to access clipboard")?;
match clipboard.get_text() {
Ok(text) => Ok(text),
Err(Error::ContentNotAvailable) => {
// Empty clipboard is often expected
Ok(String::new())
}
Err(Error::ClipboardOccupied) => {
// Retry logic might help
anyhow::bail!("Clipboard busy, try again")
}
Err(e) => {
anyhow::bail!("Clipboard error: {}", e)
}
}
}
Feature Flags
-
image-data(default) - Enable image support -
wayland-data-control- Use wl-clipboard protocol on Wayland
[dependencies]
arboard = { version = "3.6", default-features = false } # Text only
arboard = { version = "3.6", features = ["image-data"] } # With images
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-02-03
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!