Skip to content

feat(agent): add clipboard-get and clipboard-set operations - #1863

Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/agent-clipboard
Sep 1, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/agent-clipboard

Conversation

@glamberson

Copy link
Copy Markdown
Contributor

Resolves the TODO in ironrdp-rpc's Request enum for clipboard support: read the remote clipboard text and set it, so an LLM (or any daemon caller) can copy and paste to and from the session.

The backend is deliberately text only. A headless daemon has no host clipboard, so it holds the last text pushed by clipboard-set as its local content and the last text received from the remote as its remote content, both behind a lock shared with the daemon's IPC handlers. client_capabilities() advertises no file transfer flags, matching that scope.

The clipboard Cargo feature on ironrdp-daemon's ironrdp-client dependency was previously off, so the CLIPRDR channel fell back to StubClipboard on non-Windows and did nothing with remote clipboard content. This enables it and supplies a real backend factory.

Verified live against a running RDP server: clipboard-set text landed in the server's own OS clipboard, and a clipboard change made directly on the server side was correctly fetched by clipboard-get.

@github-actions github-actions Bot added kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/medium Behavioral change that does not substantially alter a core public API size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure labels Sep 1, 2026
@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

text is a good first step, but it would be great to support images as well, even in the daemon

@glamberson

Copy link
Copy Markdown
Contributor Author

Marc-André Moreau (@mamoreau-devolutions) Agreed. I'll get that done shortly.

Wires an in-memory CLIPRDR backend into ironrdp-daemon: clipboard-get prints
the last text received from the remote clipboard, clipboard-set writes the
local clipboard text and advertises it to the remote. Plain CF_UNICODETEXT
only, no file transfer, no HTML or image formats. Resolves the TODO left in
ironrdp-rpc's Request enum.

A headless daemon has no host clipboard of its own, so the backend just
holds the last text pushed by clipboard-set as its local content and the
last text received from the remote as its remote content, both behind a
lock shared with the daemon's IPC handlers. client_capabilities() advertises
no file-transfer flags, matching that scope.

Enables the clipboard Cargo feature on ironrdp-daemon's ironrdp-client
dependency, which was previously off there, so the CLIPRDR channel fell
back to StubClipboard and did nothing with remote clipboard content.

Verified live against a real RDP server end to end: clipboard-set text
landed in the server's OS clipboard, and a clipboard change made directly
on the server's side was correctly fetched by clipboard-get.
@github-actions github-actions Bot added scope/cross-cutting Spans multiple architectural boundaries scope/ffi Affects native or .NET bindings labels Sep 1, 2026
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 3955003 into Devolutions:master Sep 1, 2026
44 checks passed
@glamberson
Greg Lamberson (glamberson) deleted the feat/agent-clipboard branch September 2, 2026 13:14
Greg Lamberson (glamberson) added a commit to lamco-admin/IronRDP that referenced this pull request Sep 3, 2026
Extends the daemon clipboard-get/clipboard-set operations added in Devolutions#1863 to
images: clipboard-get-image writes the last image received from the remote
clipboard to a PNG file, clipboard-set-image reads a PNG file and advertises
it to the remote as CF_DIB/CF_DIBV5.

The daemon stores images as PNG bytes and converts to/from the wire's DIB
byte layout via ironrdp-cliprdr-format's existing png_to_cf_dib(v5) and
dib(v5)_to_png. Both DIB variants are advertised when offering an image
(DIBV5 is richer but not every peer understands it); on paste, DIBV5 is
preferred over DIB, and image is preferred over text when the remote offers
both.

Replaces ClipboardState's two Option<String> fields with a small
ClipboardContent enum (Text/Image) so local and remote clipboard content is
a single logical item, matching how each clipboard-set* call replaces
whatever was there before.

New Request::ClipboardGetImage/ClipboardSetImage and Payload::ClipboardImage
wire variants. Adds the two new variants to ironrdp-activex's exhaustive
match on Request, same treatment as the existing ClipboardGet/ClipboardSet
arm (unavailable through the ActiveX automation surface).

Behavior change: clipboard-get now prints (empty) if the last remote copy
was an image rather than text, since local/remote content is a single
logical item. Previously it always reflected the last text.

Review round (CBenoit, Copilot): Seven issues addressed.

- MAX_CLIPBOARD_IMAGE_BYTES is now derived from ironrdp-rpc's own
  transport::MAX_MESSAGE_LEN (16 MiB) minus framing headroom, instead of an
  unrelated 64 MiB that the RPC transport could never actually carry.
- Fixed a real, pre-existing bug in ironrdp-cliprdr-format::bitmap::
  decode_png: It requested ALPHA | EXPAND but not STRIP_16, and this png
  crate version declares a GRAY_TO_RGB transform flag but never implements
  it, so grayscale and 16-bit-per-channel PNGs decoded to a buffer layout
  the RGBA-only conversion downstream silently misread. Added STRIP_16, a
  manual GrayscaleAlpha-to-RGBA expansion, a MAX_BUFFER_SIZE re-check for
  that expansion, and grayscale/16-bit regression tests.
- The CLIPRDR backend now serializes paste requests: on_remote_copy no
  longer issues a second SendInitiatePaste while one is outstanding (a
  stale response could otherwise be misinterpreted as the answer to a
  newer request, since FormatDataResponse carries no correlation ID). A
  newer copy is queued and issued once the outstanding one resolves, or
  after a 5-second timeout if the peer never answers.
- The daemon now checks a converted remote image against
  MAX_CLIPBOARD_IMAGE_BYTES before storing it, so a legitimate but
  oversized image from the remote can't get stuck in a state where
  clipboard-get-image can never send it back.
- clipboard_set/clipboard_set_image now write local and advertise to a
  connected session as one atomic step under the clipboard mutex, so
  concurrent IPC callers can't interleave their state write and their
  advertisement.
- clipboard-set-image reads at most MAX_CLIPBOARD_IMAGE_BYTES + 1 bytes via
  Read::take instead of loading the whole file before checking its size.
- Extended the Debug-redaction test to also cover the pre-existing text
  variants, which the original PR body claimed but the test didn't
  exercise.

cargo xtask check fmt/lints/tests/typos/locks all pass. Not yet verified
against a live remote client; that check is still owed before merge.

This branch was previously deployed

1 inactive deployment
llm-providers — 0a0f85f9 Deployed Sep 1, 2026 by glamberson via Classify pull request #4626
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/medium Behavioral change that does not substantially alter a core public API scope/cross-cutting Spans multiple architectural boundaries scope/ffi Affects native or .NET bindings size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure

Development

Successfully merging this pull request may close these issues.

2 participants