hjkl-clipboard
Cross-platform clipboard library with rich types, async support, and context-aware backend selection (native desktop, OSC 52 over SSH / tmux)
Part of the hjkl monorepo — a vim-modal editor in Rust.
No arboard dependency. Hand-rolled backends for every supported platform give
real selection ownership, rich MIME types, and an async API that doesn't require
picking a runtime.
Platform support
| Platform | Backend |
|---|---|
| Linux Wayland | ext_data_control_v1 (KDE 6.2+, wlroots, etc.) |
| Linux Wayland | zwp_primary_selection_v1 for PRIMARY |
| Linux X11 | XCB via dlopen, INCR + auto-SAVE_TARGETS |
| macOS | NSPasteboard via raw objc_msgSend |
| Windows | Win32 CF_UNICODETEXT, CF_DIBV5 + PNG |
| OSC 52 (fallback) | SSH sessions, GNOME, any TTY |
Backend selection
Clipboard::new() chooses a backend from the current context, in order:
HJKL_CLIPBOARDoverride (case-insensitive):osc52— force the terminal OSC 52 backend anywhere (useful in headless / CI / PTY environments, or to avoid contending on the single shared macOS pasteboard).native/auto— bypass the SSH heuristic and use the local-desktop probe below.wayland/x11(Linux),macos,windows— force a specific native backend. A named backend that can't initialize (e.g.x11with no display) falls through to the context default rather than erroring.
- SSH session (
SSH_TTY,SSH_CONNECTION, orSSH_CLIENTset) → OSC 52. Over SSH the machine's native clipboard belongs to the remote host, not you; the OSC 52 escape is relayed by your terminal emulator to your local clipboard. Inside tmux (TMUXset) the sequence is wrapped in the DCS passthrough automatically. Relying on SSH X11 forwarding to reach your local clipboard natively? SetHJKL_CLIPBOARD=x11. - Local desktop probe:
- Linux: Wayland → X11 → OSC 52.
- macOS:
NSPasteboard(always available). - Windows: Win32 (always available).
- Other: OSC 52.
OSC 52 is write-only, so over SSH get returns UnsupportedMime and callers
should fall back to their own register/history (see
OSC 52 paste over SSH).
Quick start
Add to Cargo.toml:
= "0.4"
Sync API
use ;
Async API (runtime-agnostic)
See examples/async_basic.rs for a runnable version
using pollster as a zero-dep executor.
use ;
async
Custom MIME types and URI list helpers
See examples/custom_mime.rs for a runnable version.
use ;
use PathBuf;
Backend detection (diagnostics)
See examples/backend_detect.rs for a runnable
version.
use ;
Capability-gated calls
Capabilities is cheap to query and lets callers skip ops that would just
return UnsupportedMime or UnsupportedAsync from the backend (Wayland / X11
calls go through a thread hop):
use ;
Custom backends
Backend is a public trait. Implement it directly, or use one of the bundled
extension types:
backend::mock::MockBackend— in-memory test backend with configurablekind+capabilities. Recordsset/clear, programmableget/availableresponses. Both sync and async paths.backend::ssh_aware::SshAwareBackend— decorator that wraps anyBox<dyn Backend>and falls back to OSC 52 on write failures (BackendUnavailable/UnsupportedMime/NoDisplay/FocusRequired). Capabilities are the union of inner + OSC 52.
use ;
use MockBackend;
let mock = new;
mock.preset_get;
let handle = mock.handle;
let cb = with_backend;
cb.set.unwrap;
assert_eq!;
assert_eq!;
PRIMARY selection (Linux only)
On Linux both Selection::Clipboard and Selection::Primary are supported.
Other platforms return UnsupportedMime for PRIMARY ops.
use ;
Caveats
GNOME / no ext_data_control_v1
GNOME's Mutter compositor does not implement the ext_data_control_v1 (or
wlr_data_control_v1) protocol. Without the data-control extension the library
cannot own a Wayland selection. When running in a TTY (SSH or local terminal)
the library falls back to OSC 52 automatically. When running inside a GNOME
session with no TTY available, Clipboard::new() returns
ClipboardError::FocusRequired.
OSC 52 paste over SSH
OSC 52 is a write-only path. There is no standardized way to request the
terminal's clipboard back over the escape sequence; get and available return
UnsupportedMime for the OSC 52 backend.
Paste support (the terminal echoing OSC 52 clipboard contents back to the application) requires opt-in terminal configuration:
| Terminal | Paste support |
|---|---|
| kitty | Yes (default) |
| WezTerm | Yes (default) |
| iTerm2 | Yes (opt-in) |
| xterm | No |
| most others | No |
OSC 52 payload cap
OSC 52 payloads are capped at ~74 000 base64 bytes (a widely accepted safe
limit). Larger payloads return ClipboardError::PayloadTooLarge.
macOS / Windows async
set_async / get_async / clear_async / available_async on macOS and
Windows use the native backend (NSPasteboard / Win32). Because both backends are
synchronous, the async methods wrap the call in std::future::ready — they
complete without spawning a thread and return immediately without yielding to
the executor. This is correct and efficient for the call frequency typical of
clipboard use.
Build prerequisites
| Platform | Requirement |
|---|---|
| Linux | libxcb.so.1 at runtime (loaded via dlopen, not link-time) |
| macOS | None — AppKit/Foundation/libobjc are system frameworks |
| Windows | None — user32/kernel32 are always present |
On Arch: pacman -S libxcb. On Debian/Ubuntu: apt install libxcb1.
Feature flags
None currently. All backends are compiled in unconditionally for each supported target.
Documentation
Contributing
See the monorepo CONTRIBUTING guide.
License
MIT — see LICENSE.