Open-source project
YaLTeR/wl-clipboard-rs avatar
YaLTeR/wl-clipboard-rs

wl-clipboard-rs gives Rust programs a Wayland clipboard without spawning a window

A safe Rust crate for working with the Wayland clipboard.

520 stars32 forksRustApache-2.0

At a glance

What is it?
wl-clipboard-rs is an Apache-2.0 or MIT licensed Rust crate for reading, writing and watching the Wayland clipboard from terminal applications and other tools with no window, built on the ext-data-control and wlr-data-control protocols. It suits utilities that need clipboard access but must not create a Wayland surface.
Who is it for?
Adopt wl-clipboard-rs if you are writing a Rust terminal application or clipboard manager on Wayland and you need the clipboard without creating a surface, and you can require a compositor that supports ext-data-control or wlr-data-control. Do not adopt it for a graphical application, since the project points you at the core Wayland clipboard protocols and crates like smithay-clipboard for that case.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The windowless case, stated precisely

wl-clipboard-rs is a safe Rust crate for working with the Wayland clipboard, and the first thing its documentation says is who it is for. It is intended to be used by terminal applications, clipboard managers and other utilities which do not spawn Wayland surfaces, that is, windows. If your application has a window, the documentation tells you to use the appropriate Wayland protocols for interacting with the Wayland clipboard instead, naming wl_data_device from the core Wayland protocol and the primary_selection protocol for the primary selection, for example via the smithay-clipboard crate.

That boundary is the design, not a limitation to work around. The Wayland clipboard has a split model. A surface based client, meaning an application with a window, gets clipboard access through the standard data device interface tied to its own surface and focus. A windowless client has no surface, so it cannot use that path, and needs a different mechanism entirely. The data-control protocols exist to serve exactly this case, and this crate is a safe binding to them.

The practical audience is therefore narrow and identifiable: a shell script replacement written in Rust, a clipboard manager daemon, a status line tool, a build script that needs to read a selection. The audience for graphical clipboard handling is explicitly pointed elsewhere. The project is Apache-2.0 or MIT licensed, the primary language is Rust, the default branch is master, and the last push was on 2026-09-28.

Two protocols, and what each one needs from the compositor

The protocol used for clipboard interaction is ext-data-control or wlr-data-control, and the requirements differ between the regular clipboard and the primary selection, which is the middle-click selection on X11 and the equivalent on Wayland.

For the regular clipboard, the compositor must support any version of either protocol. For the primary clipboard, the requirement is narrower: the compositor must support any version of ext-data-control, or the second version of the wlr-data-control protocol. The wlr-data-control protocol reached version 2, and that second version is what carries primary selection support, so a compositor with only version 1 of wlr-data-control gives you the regular clipboard and not the primary one.

This is a real compatibility surface, and it is where a lot of the friction in Wayland tooling comes from. wl-copy and wl-clipboard, the widely used C tools, have their own protocol requirements, and a crate that speaks only one protocol family will not work on a compositor that only offers the other. The documentation is honest about this and gives you a way to check at runtime, which the crate exposes as is_primary_selection_supported in the utils module. The result is a three way answer rather than a boolean. A definitive false means ext or wlr data-control is present but did not signal primary selection support, or only wlr-data-control version 1 is present. A NoSeats error means the required protocol is there but there are no seats, and a definitive answer is impossible because at least one seat is needed to check. A MissingProtocol error means the data-control protocol required for operation is not supported by the compositor at all.

The one-line summary of the difference in approach between the two protocols is that ext-data-control is the newer, compositor-agnostic protocol and wlr-data-control is the wlroots-specific one, and a crate that supports both is portable across a much wider set of compositors than one that supports only wlr.

Copying, pasting and the shape of the API

The three verbs are the three modules. Copying to the regular clipboard takes a Source, which here is raw bytes, and a MimeType of Autodetect:

rust
use wl_clipboard_rs::copy::{MimeType, Options, Source};

let opts = Options::new();
opts.copy(
    Source::Bytes("Hello world!".to_string().into_bytes().into()),
    MimeType::Autodetect,
)?;

Pasting reads through a pipe rather than a string, which is what makes large selections and binary content workable. get_contents takes a ClipboardType, a Seat and a MimeType, and returns a pipe plus a second value. The example then reads the pipe to the end and prints it as UTF-8.

The error type is where the API design shows its care. NoSeats, ClipboardEmpty and NoMimeType are all grouped into one arm and treated as uninteresting, with the comment that the clipboard is empty or does not contain text and there is nothing to worry about. Every other error is propagated. That split is a deliberate ergonomic decision: an empty clipboard is a normal state for a tool, while a communication failure is a bug, and a library that collapsed them would make every caller write the same match.

The seat parameter is worth understanding. Clipboard operations are per seat, meaning per input device, and Seat::Unspecified lets the library pick. A tool that wants to read the clipboard of the seat the user is actually typing on would name a specific seat instead, and the crate supports that.

Watching the clipboard, and cancelling a watcher

The watch module is the part that makes the crate useful for a clipboard manager rather than just a one-shot copy or paste. A Watcher is created for a ClipboardType and a Seat, and then yields ClipboardEvent values:

rust
use wl_clipboard_rs::watch::{ClipboardEvent, ClipboardType, Watcher};

let mut watcher = Watcher::new(ClipboardType::Regular, Seat::Unspecified)?;
while let Some(event) = watcher.next_event()? {
    match event {
        ClipboardEvent::Changed {
            mime_types,
            mut offer,
            ..
        } if mime_types.iter().any(|m| m == "text/plain") => {
            let mut contents = String::new();
            offer.receive("text/plain")?.read_to_string(&mut contents)?;
            println!("Clipboard changed: {contents}");
        }
        ClipboardEvent::Changed { .. } => {}
        ClipboardEvent::Cleared { .. } => println!("Clipboard cleared"),
    }
}

Two things stand out in that code. The Changed event carries the list of available mime types and an offer, so a consumer can decide whether it wants the data before pulling it, and the example filters on text/plain before receiving. The alternative arm for Changed does nothing, which is how a consumer declines a selection it has no use for. And there is a separate Cleared event, so an emptied clipboard is distinguishable from a changed one, which matters for a manager that has to drop its stored content.

Cancelling is handled through a handle rather than a flag. The documentation says you can obtain a watch::CancelHandle from watch::Watcher::cancel_handle to stop a watcher blocked in watch::Watcher::next_event from another thread. Since next_event is a blocking call, that is the only way to shut the loop down cleanly from a signal handler or a shutdown path.

There is also a separate examples/watch.rs file in the repository, so the canonical usage is available as a compilable example rather than only as documentation.

Build features, the pure Rust client, and the bundled utilities

The Rust implementation of the Wayland client is used by default, and the Cargo manifest makes the alternatives explicit. The native_lib feature links to libwayland-client.so for communication instead, and a dlopen feature is also available for loading libwayland-client.so dynamically at runtime rather than linking to it. The dlopen feature is defined as enabling native_lib together with the wayland-backend dlopen feature.

That choice has deployment consequences. The default path means the binary has no external Wayland client library dependency, which makes distribution simpler. The native_lib path means you depend on the system library being present, which is a reasonable choice on a system where you know the environment. The dlopen path is the middle ground, resolving the library at runtime, and it is what you want if your binary should work on systems with and without the system library installed.

The crate also ships terminal utilities in a separate workspace member, wl-clipboard-rs-tools. There is wl-paste and wl-copy, which implement the equivalents from wl-clipboard, and wl-clip, described as a Wayland version of xclip. The documentation points at the source files, wl_copy.rs and wl_paste.rs, as the example applications, and wl_clip.rs as the xclip equivalent.

The dependency list is short and tells you the scope. There is libc, log, rustix, os_pipe with the io_safety feature, thiserror, tree_magic_mini for MIME detection, and the Wayland crates wayland-backend, wayland-client and wayland-protocols with client and staging features plus wayland-protocols-wlr with client. The dev dependencies pull in wayland-server and the server side of the protocol crates, along with proptest and proptest-derive, which is a sign that the tests run a real Wayland server and exercise the crate against it rather than mocking the protocol.

The code of the crate itself and the example utilities is 100 percent safe Rust, and the documentation is careful to add that this does not include the dependencies. The workspace version is 0.9.4, the edition is 2021, and the keywords are wayland and clipboard. There are no GitHub releases, so the crates.io version is the version to track.

Where wl-clipboard-rs is the wrong choice

The first limitation is the protocol dependency, and it is a hard one. A compositor that implements neither ext-data-control nor wlr-data-control will not work with this crate at all, and that rules out a portion of the Wayland world. The wlroots based compositors implement wlr-data-control, and the ones implementing ext-data-control cover much of the rest, but a compositor that has neither leaves you with the core protocol path, which requires a surface.

The second limitation is that the crate deliberately does not cover graphical clients. If you are building a windowed application, the documentation sends you to wl_data_device and the primary_selection protocol, and to a different crate. That is correct guidance and it means this crate is not a general purpose Wayland clipboard library, it is a specialist one.

The third limitation is the version. The crate is at 0.9.4 with no GitHub releases, and a Wayland crate that depends on the protocol extensions has to track them, since a protocol version bump can change the generated bindings and the feature set. The workspace pins wayland-client at 0.31.11 and wayland-protocols at 0.32.9, and the staging feature is enabled on wayland-protocols, which is where unstable protocol definitions live, and that is the part most likely to move.

The realistic alternative for a terminal tool is the existing C tools. wl-copy and wl-clipboard are the de facto Wayland clipboard utilities, they are installed on most systems, and a shell script can call them rather than linking a library. The difference in approach is exactly the one Rust users weigh: the C tools are separate processes with their own startup cost and their own IPC, while this crate is a library you link, so there is no process spawn and the clipboard data stays in your process. If your tool is a shell script, calling wl-copy is simpler than adding a dependency. If your tool is a Rust binary that needs to watch the clipboard continuously, spawning a process per poll is not an option, and the watcher plus the cancel handle is why this crate exists.

Editorial conclusion

Adopt wl-clipboard-rs if you are writing a Rust terminal application or clipboard manager on Wayland and you need the clipboard without creating a surface, and you can require a compositor that supports ext-data-control or wlr-data-control. Do not adopt it for a graphical application, since the project points you at the core Wayland clipboard protocols and crates like smithay-clipboard for that case. Before you start, check which data-control protocol your target compositor provides, pin the version since the crate is at 0.9.4 with no GitHub releases, and decide between the pure Rust Wayland client and the native_lib or dlopen features based on how you ship the binary.

Frequently asked questions

What is wl-clipboard-rs and who is it for?

It is a safe Rust crate for working with the Wayland clipboard, intended for terminal applications, clipboard managers and other utilities that do not spawn Wayland surfaces. If your application has a window, the project points you at the core Wayland clipboard protocols instead.

Which Wayland protocols does wl-clipboard-rs require?

Clipboard interaction uses ext-data-control or wlr-data-control, and the regular clipboard requires the compositor to support any version of either. The primary selection needs any version of ext-data-control, or the second version of the wlr-data-control protocol, since version 1 does not carry primary selection support.

How do I copy and paste with wl-clipboard-rs?

Copying takes a Source of bytes and a MimeType, and the example uses MimeType::Autodetect. Pasting calls get_contents with a ClipboardType, a Seat and a MimeType, which returns a pipe you read to the end, and the NoSeats, ClipboardEmpty and NoMimeType errors are grouped together as uninteresting states.

How do I watch the clipboard for changes in wl-clipboard-rs?

Create a Watcher for a ClipboardType and a Seat, then loop on next_event and match the ClipboardEvent values. A Changed event carries the available mime types and an offer you can receive data from, and a Cleared event is separate, so an emptied clipboard is distinguishable from a changed one.

Is wl-clipboard-rs maintained and under what licence?

The repository is not archived and the last push was on 2026-09-28, though there are no GitHub releases. The crate version is 0.9.4 with edition 2021, and the licence is MIT OR Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE files in the repository.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. YaLTeR/wl-clipboard-rs on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yalter-wl-clipboard-rs.svg)](https://hysenlabs.com/projects/yalter-wl-clipboard-rs)

Was this useful?