Files
RustDesk b50fde6910 add the base crate and repoint the moved modules at it (#16107)
* add the base crate and repoint the moved modules at it

`libs/base` (crate `base`) takes the parts of hbb_common that only this app
uses: `fs`, `platform`, `keyboard`, `message.proto`, and 145 of the 177
`config::keys` constants. hbb_common keeps what the server names, and the 32
keys it reads itself are re-exported from `base::config::keys` so call sites
still see the full set through one path.

Sources move verbatim. The only edits inside them are `crate::` prefixes that
now have to say `hbb_common::`; `keyboard.rs` and `platform/windows.rs` are
byte-identical. The crate stays on edition 2018, the edition the moved code was
written under. `log`, `lazy_static` and `anyhow` become direct dependencies so
the bare paths in that code resolve exactly as before, and its winapi features
are spelled out rather than left to feature unification.

Two call sites outside Rust and Cargo had to follow the move: the Android
protobuf source dir, which still pointed at hbb_common/protos for message.proto,
and the three AGENTS.md entries that named hbb_common for options, protos and
file transfer.

`scrap`'s `drm` feature now forwards to `base/wayland_probe`. Left pointing at
hbb_common it would still have compiled, silently dropping the Wayland
socket-probe fallback, so that forward is verified by a build with and without
the feature.

`config::keys` carries a test asserting its names stay disjoint from the ones
hbb_common kept: the glob re-export and the local constants share a namespace,
and Rust prefers the local item silently, so a name added to both sides would
otherwise let client and server disagree with no diagnostic.

Verified: macOS and Linux, debug and release, `--all-targets`; the 177 key
constants diffed name-for-name and value-for-value; the generated protobuf types
compared before and after; every `#[cfg]` gate on a moved import checked against
its original; and every file that was `rustfmt`-clean before this change still
is, compared against master file by file. Windows is checked by inspection only
-- it cannot be compiled here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab

* one `use` per crate, and write the rule down

`fs.rs` came out of the move with two ungated `use hbb_common::` statements,
because the original single `use crate::{...}` had to give up `message_proto`
to the new crate and the rest was left in a second block. Fold it back into one.

A scan of the whole tree for the same shape finds nothing else: every other file
with more than one top-level `use base::` or `use hbb_common::` is split by a
`#[cfg]` that does not cover the whole block, or by `pub use` next to `use`.
Those are the cases that cannot merge, so AGENTS.md now states both the rule and
the exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:46:42 +08:00
..
2024-07-28 19:33:00 +08:00
2023-10-29 02:15:16 +08:00

clipboard

Copy files and text through network. Main low-level logic from FreeRDP.

To enjoy file copy and paste feature on Linux/OSX, please build with unix-file-copy-paste feature.

TODO: Move this lib to a separate project.

How it works

Terminologies:

  • cliprdr: this module
  • local: the endpoint which initiates a file copy events
  • remote: the endpoint which paste the file copied from local

The main algorithm of copying and pasting files is from Remote Desktop Protocol: Clipboard Virtual Channel Extension, and could be concluded as:

  1. local and remote notify each other that it's ready.

  2. local subscribes/listening to the system's clipboard for file copy

  3. local once got file copy event, notice the remote

  4. remote confirms receive and try pulls the file list

  5. local updates its file-list, the remote flushes pulled file list to the clipboard

  6. remote OS or desktop manager initiates a paste, making other programs reading clipboard files. Convert those reading requests to RPCs

    • on Windows, all file reading will go through the stream file API
    • on Linux/OSX, FUSE is used for converting reading requests to RPCs
      • in case of local clipboard been transferred back and leading to a dead loop, all file copy event pointing at the FUSE directory will be ignored
  7. finishing pasting all files one by one.

In a perspective of network data transferring:

sequenceDiagram
    participant l as Local
    participant r as Remote
    note over l, r: Initialize
    l ->> r: Monitor Ready
    r ->> l: Monitor Ready
    loop Get clipboard update
        l ->> r: Format List (I got update)
        r ->> l: Format List Response (notified)
        r ->> l: Format Data Request (requests file list)
        activate l
            note left of l: Retrieve file list from system clipboard
            l ->> r: Format Data Response (containing file list)
        deactivate l
        note over r: Update system clipboard with received file list
    end
    loop Some application requests copied files
        note right of r: application reads file from x to x+y
        note over r: the file is the a-th file on list
        r ->> l: File Contents Request (read file a offset x size y)
        activate l
            note left of l: Find a-th file on list, read from x to x+y
            l ->> r: File Contents Response (contents of file a offset x size y)
        deactivate l
    end

Note: In actual implementation, both sides could play send clipboard update and request file contents. There is no such limitation that only local can update clipboard and copy files to remote.

impl

windows

scene

A1->B1

B1->A1

The protocol was originally designed as an extension of the Windows RDP, so the specific message packages fits windows well.

When starting cliprdr, a thread is spawned to create an invisible window and to subscribe to OLE clipboard events. The window's callback (see cliprdr_proc in src/windows/wf_cliprdr.c) was set to handle a variety of events.

Detailed implementation is shown in pictures above.

Linux/OSX

The Cliprdr Server implementation has mainly 3 parts:

  • Clipboard Client
  • Local File list
  • FUSE server

Clipboard Client

The clipboard client has a thread polling for file urls on clipboard.

If the client found any updates of file urls, after filtering out those pointing to our FUSE directory or duplicated, send format list directly to remote.

The cliprdr server also uses clipboard client for setting clipboard, or retrieve paths from system.

Local File List

The local file list is a temporary list of file metadata. When receiving file contents PDU from peer, the server picks out the file requested and open it for reading if necessary.

Also when receiving Format Data Request PDU from remote asking for file list, the local file list should be rebuilt from file list retrieved from Clipboard Client.

Some caching and preloading could be done on it since applications are likely to read on the list sequentially.

FUSE server

The FUSE server could convert POSIX file reading request to File Contents Request/Response RPCs.

When received file list from remote, the FUSE server will figure out the file system tree and rearrange its content.

Groceries

  • The protocol was originally implemented for windows, so paths in PDU will all be converted to DOS formats in UTF-16 LE encoding, and datetimes will be converted to LDAP timestamp instead of unix timestamp

    UNIX
    /usr/bin/rustdesk
    ->
    DOS
    \usr\bin\rustdesk
    
  • To better fit for preserving permissions on unix-like platforms, a reserved area of FileDescriptor PDU

  • you may notice the mountpoint is still occupied after the application quits. That's because the FUSE server was not mounted with AUTO_UNMOUNT.

    • It's hard to implement graceful shutdown for a multi-processed program
    • AUTO_UNMOUNT was not enabled by default and requires enable user_allow_other in configure. Letting users edit such global configuration to use this feature might not be a good idea.
    • use umount() syscall to unmount will also require that option.
    • we currently directly call umount program to unmount dangling FUSE server. It worked perfectly for now.