Skip to content

feat(cliprdr): add CliprdrBackend::on_format_list_response(ok) hook#1300

Open
clintcan wants to merge 1 commit into
Devolutions:masterfrom
clintcan:feat/cliprdr-on-format-list-response-hook
Open

feat(cliprdr): add CliprdrBackend::on_format_list_response(ok) hook#1300
clintcan wants to merge 1 commit into
Devolutions:masterfrom
clintcan:feat/cliprdr-on-format-list-response-hook

Conversation

@clintcan
Copy link
Copy Markdown

Add a non-breaking backend hook that fires when the remote responds to a FormatList we sent (an outbound advertise of our own clipboard contents):

fn on_format_list_response(&mut self, _ok: bool) {}

Called from handle_format_list_response on both branches:

  • Okon_format_list_response(true)
  • Failon_format_list_response(false) (after Cliprdr has cleared local_file_list per MS-RDPECLIP 3.1.5.2.4)

Default impl is a no-op, so this is non-breaking for existing backends.

Why

Without this signal, a backend has no way to learn whether an outbound FormatList was accepted or rejected. That's a real problem because the Fail branch silently wipes local_file_list / local_file_list_format_id per the spec — so a rejected advertise breaks the paste with no recovery and no way to detect it.

Worse: a naive timed-retry strategy can actively wipe an already-Ok state by sending a follow-up advertise that happens to be rejected. Observed live (without the hook, so no way to stop):

advertise #1 → Fail
advertise #2 (retry) → Ok (file present on remote clipboard)
advertise #3 (retry) → Fail   ← wiped the accepted state from #2
                                paste then fails

With the hook, a backend can retry on Fail and stop the moment Ok arrives, preserving the success.

Use case / verification

Used in macrdp (a Rust RDP server for macOS) to implement reliable Mac→Windows file copy. mstsc commonly rejects the first FormatList right after an in-session Cmd-C (it's still processing the input) and accepts one ~0.5–1s later. With this hook, the macrdp poller's retry loop fires retries on Fail and stops on Ok (via a shared generation/locked-gen pair), fixing the silent-paste-failure case while still recovering from a transient initial Fail. Verified end-to-end with real mstsc.

Related

Pairs naturally with the upcoming PreferredDropEffect and FD_PROGRESSUI PRs to deliver the full "Mac→Windows clipboard file paste with native progress dialog" UX, but is independently useful and can be merged on its own.

Add a non-breaking hook that fires when the remote responds to a
FormatList we sent (i.e. an outbound advertise of our own clipboard
contents):

  fn on_format_list_response(&mut self, _ok: bool) {}

Called from handle_format_list_response on both branches:
- Ok  -> on_format_list_response(true)
- Fail -> on_format_list_response(false)  (after Cliprdr has cleared
                                            local_file_list per the spec)

Default impl is a no-op, so this is non-breaking for existing backends.

### Why

Without this signal, a backend has no way to learn whether an outbound
FormatList advertise was accepted or rejected. That matters because
the Fail branch silently wipes `local_file_list` / `local_file_list_format_id`
(MS-RDPECLIP 3.1.5.2.4), so a rejected advertise breaks the paste with
no recovery. A naive timed-retry strategy can also actively WIPE an
already-Ok state by sending a follow-up advertise that happens to be
rejected — observed live (with no hook to stop it):

  advertise Devolutions#1 -> Fail
  advertise Devolutions#2 (retry) -> Ok (file present)
  advertise Devolutions#3 (retry) -> Fail (wiped the accepted state)
                                ^ paste fails

With the hook, a backend can retry on Fail AND stop the moment Ok
arrives, so the success is preserved.

### Use case

Used in https://github.com/clintcan/macrdp (a Rust RDP server for
macOS) to implement reliable Mac→Windows file copy: mstsc commonly
rejects the first FormatList right after an in-session Cmd-C
(it's still processing the input) and accepts one a moment later.
The macrdp poller's retry loop now stops the instant an Ok lands,
fixing the silent-paste-failure case while still recovering from a
transient initial Fail.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant