Skip to content

feat(cliprdr): add a Wayland data-control clipboard client - #2055

Merged
Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/cliprdr-native-data-control
Oct 8, 2026
Merged

Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/cliprdr-native-data-control

Conversation

@glamberson

@glamberson Greg Lamberson (glamberson) commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

This adds the data-control clipboard client I committed to on #2016. It lives in ironrdp-cliprdr-native as the data_control module, so a Linux CLIPRDR backend can be built on it.

The client speaks ext-data-control-v1 and wlr-data-control-unstable-v1 and uses whichever the compositor offers. One thread owns the Wayland connection and DataControl is the handle to call from anywhere. It has no async runtime.

It supports delayed rendering. A type advertised without data raises a TransferRequest when something pastes it, which maps onto the CLIPRDR Format Data Response. Dropping the request without answering it closes the paste with no data. A selection this client set itself isn't reported back as a local copy, so a backend doesn't echo its own clipboard to the server. The client tells its own selection from another client's copy by one private MIME type that it advertises next to the real ones, so other clients see that type in the list they are offered.

Reads are capped at 100 MiB and fail with a timeout if the source stalls for 5 seconds. Pastes are bounded too. At most 16 are served at once, a paste whose data isn't supplied within 5 seconds is closed with no data (an answer that comes later is kept for the next paste), and a reader that takes nothing for 5 seconds is dropped. MIME matching tolerates charset parameters. GNOME's Mutter offers no data-control protocol, so connecting returns an Unsupported error there.

This isn't wired into ironrdp-client. #2016 adds a Linux backend, and it can use this module instead of arboard.

Dependencies are wayland-client, wayland-protocols, wayland-protocols-wlr and nix, plus visibility behind the __test feature, all of which are already in the lockfile, so Cargo.lock only gains the new dependency edges. The state tests are in ironrdp-testsuite-extra behind the __test feature, because inline tests aren't built for this crate.

The client is a port of the standalone lamco-data-control crate. The checks that need a compositor are in ironrdp-testsuite-extra as ignored tests, because they change the clipboard of the compositor they run against. They do nothing unless IRONRDP_DATA_CONTROL_LIVE=1 is set, and the docs at the top of the live test module say how to run them against an isolated compositor. I ran them against a KWin instance, which offers wlr-data-control only, so the ext-data-control-v1 path has no live run.

@github-actions github-actions Bot added risk/medium Behavioral change that does not substantially alter a core public API size/XXL Size: 1300 or more counted lines or 50 or more files labels Sep 30, 2026
@github-actions github-actions Bot added the triage/overlap Possible overlap with another pull request; advisory only label Sep 30, 2026
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

This pull request may overlap with #2016.

Both touch Linux clipboard support in ironrdp-cliprdr-native via a Wayland data-control client: this PR adds the data_control module (client, dispatch, state, worker for ext-data-control-v1 and wlr-data-control-unstable-v1) with delayed rendering, while #2016 builds a Linux CLIPRDR backend on a shared Wayland data-control client, which appears to be this same component. Shared scope for human assessment.

This notice is advisory only. Automated review continues as usual, and how these pull requests relate is for maintainers and authors to decide.

Note

LLM-assisted content (no human feedback).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds a self-contained Wayland data-control clipboard client (ext-data-control-v1 / wlr-data-control-unstable-v1) to ironrdp-cliprdr-native, with a worker-thread architecture, a public DataControl/Content/TransferRequest API, delayed rendering, MIME charset-tolerant matching, and state-machine tests behind a __test feature. Independent inspection confirms the specialists' code-level observations: Shared::own_source_live is written in four places and read nowhere; read() can return Ok(Some(empty)) when the offer races or disappears because the worker dispatches Wayland events before commands and EOF maps to success; source-side cached_data uses a one-directional MIME fallback weaker than find_mime_match; dispatch.rs mechanically duplicates four handlers per protocol; test-only accessors restate field access; and lock-poisoning is handled inconsistently (recovering in client.rs, silently skipped in state.rs). The unwired-public-API scope question is real but depends on #2016 timin…

Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/lib.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/client.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/data_control/dispatch.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
@github-actions github-actions Bot added ai-reviewed/1 One automated review completed needs-author-action The pull request author is the current next actor labels Oct 1, 2026
@glamberson
Greg Lamberson (glamberson) force-pushed the feat/cliprdr-native-data-control branch from d57e221 to 5efe512 Compare October 6, 2026 19:21
@github-actions github-actions Bot added automation-failed Exact-head automated classification or review failed or was unavailable risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny and removed needs-author-action The pull request author is the current next actor risk/medium Behavioral change that does not substantially alter a core public API labels Oct 6, 2026
@glamberson

Copy link
Copy Markdown
Contributor Author

I pushed an update, rebased on current master. It answers the seven review threads and changes a few things the threads didn't cover, which I found while checking them against the code.

The guard that keeps the client from reporting its own selection back now counts the echoes of the selections it sets instead of comparing MIME types, so another client's copy with the same types isn't swallowed. It has tests now.

Pastes are bounded. Before, every paste got its own writer thread with a blocking write and no limit, and a paste waiting for its data had no deadline. Now at most 16 are served at once, a paste whose data isn't supplied within 5 seconds is closed with no data, a late answer is kept for the next paste, and a reader that takes nothing for 5 seconds is dropped. Dropping a TransferRequest without answering it closes its paste at once, where before the transfer stayed pending and later pastes waited behind it.

The tests moved from ironrdp-testsuite-core to ironrdp-testsuite-extra, because ironrdp-cliprdr-native is in the extra tier and ARCHITECTURE.md allows no extra tier dependency in the core suite. I also removed an expect(unreachable_pub) that made clippy fail for this crate without the __test feature, and brought the inline comments in line with STYLE.md.

@glamberson

Copy link
Copy Markdown
Contributor Author

I pushed another update, still one commit on current master.

The guard that keeps the client from reporting its own selection back no longer counts echoes. The client now advertises one private MIME type next to the types of its own selections, and a selection that carries it belongs to the client, in whichever order the compositor delivers things and whatever types another client's copy offers. Other clients can see that type in the list of types they are offered.

The module docs link to the two protocol XML files and to MS-RDPECLIP 2.2.5.2. The docs of connect and read say that they block, and that the read timeout measures silence and not the total time. The fallback from one protocol to the other is logged at debug level.

The compositor checks I had run by hand are now in ironrdp-testsuite-extra as ignored tests. They need an isolated compositor and IRONRDP_DATA_CONTROL_LIVE=1, and the docs of the live test module say how to run them.

@CBenoit

Benoît Cortier (CBenoit) commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

PR automation is failing because of a picky-krb 0.12.5 incompatibility, fixed on master by #2074. Please rebase on master to fix it.

Update: no rebase needed anymore. picky-krb 0.12.5 was yanked from crates.io (re-released as 0.13.0), so the API check builds again without changes to this branch. PR automation has been re-run here and passes.

@github-actions github-actions Bot added risk/medium Behavioral change that does not substantially alter a core public API and removed risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny automation-failed Exact-head automated classification or review failed or was unavailable labels Oct 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new data_control module is well-built and its review-era correctness issues (echo guard, read race, charset lookup, poisoned locks, paste bounds) are fixed in the head with tests. I verified each specialist claim against the head. Published: the speculative Options/Preference/protocol/update_data public surface (no consumer per the author's own comment, merged from two findings); read() returning an empty success when the worker dies mid-transfer; the last-resort same-base charset fallback that can silently serve mismatched encodings; and three behavior-preserving compressions in state.rs. Rejected the in-crate-tests alternative: the workspace deliberately sets test=false ('keep tests in testsuite crates' FIXMEs across crates), so the testsuite-extra placement is conventional and the visibility dep is optional behind __test. Refined UpdateSourceData from a question to a conclusion: the author's comment lists #2016's API usage and update_data is absent, so the path is unconsumed.

Comment thread crates/ironrdp-cliprdr-native/src/data_control/options.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/client.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/mime.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs Outdated
Comment thread crates/ironrdp-cliprdr-native/src/data_control/state.rs
@github-actions github-actions Bot added ai-reviewed/2 Two automated reviews completed needs-author-action The pull request author is the current next actor labels Oct 7, 2026
@github-actions github-actions Bot removed the ai-reviewed/1 One automated review completed label Oct 7, 2026
Add the data_control module to ironrdp-cliprdr-native so a Linux CLIPRDR
backend can read and set the clipboard without a window. It speaks
ext-data-control-v1 and wlr-data-control-unstable-v1 and uses whichever
the compositor offers.

One thread owns the Wayland connection and DataControl is the handle to
call from anywhere. A type advertised without data raises a
TransferRequest when something pastes it, which maps onto the CLIPRDR
Format Data Response. Dropping the request without answering it closes
the paste with no data. A selection the client set itself isn't reported
back as a local copy. It is told from another client's copy by a private
MIME type that the client advertises next to the real ones. Reads are
capped at 100 MiB and time out when the source stalls for 5 seconds.
Pastes are bounded as well. At most 16 are served at once, a paste whose
data isn't supplied within 5 seconds is closed with no data, and a
reader that takes nothing for 5 seconds is dropped.

The state tests live in ironrdp-testsuite-extra behind the __test
feature, because inline tests aren't built for this crate. The checks
that need a compositor are there too, as ignored tests.
@glamberson
Greg Lamberson (glamberson) force-pushed the feat/cliprdr-native-data-control branch from 85be787 to 37658f7 Compare October 8, 2026 02:40
@github-actions github-actions Bot added needs-review A human reviewer is the current next actor and removed needs-author-action The pull request author is the current next actor labels Oct 8, 2026
@glamberson

Copy link
Copy Markdown
Contributor Author

I pushed an update that answers the seven open review threads. Each has its own reply, and it's still one commit.

The one behaviour change is in read(). When the worker had stopped before the request was sent, the pipe closed with nothing in it and the read returned an empty success. The worker now records that it stopped as it drops, and a read that finds its pipe closed with no data reports Error::Stopped. Data that had already arrived is still returned.

I kept Options, Preference, protocol() and update_data, because the standalone lamco-data-control crate they were ported from ships them, and update_data now has tests. The docs of find_mime_match and read() say that a read can return bytes in another charset than the one requested, and how to see which type will be read before reading. The state code has two helpers for the teardown that was written out several times, PendingOffer is gone, and complete_transfer looks the transfer up once.

I didn't rebase. Master has moved by three commits that touch none of these files.

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you

@CBenoit
Benoît Cortier (CBenoit) merged commit a1efb0b into Devolutions:master Oct 8, 2026
43 checks passed
@github-actions github-actions Bot removed the needs-review A human reviewer is the current next actor label Oct 8, 2026

This branch was successfully deployed

1 active deployment
llm-providers — 37658f7f Deployed Oct 8, 2026 by glamberson via Classify pull request #1866
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/2 Two automated reviews completed risk/medium Behavioral change that does not substantially alter a core public API size/XXL Size: 1300 or more counted lines or 50 or more files triage/overlap Possible overlap with another pull request; advisory only

Development

Successfully merging this pull request may close these issues.

2 participants