The clipboard listener is one per process, started by the first session to
log in, and its broadcast went to every session - one whose login was still
waiting on a password, 2FA or the peer's consent included. What the user
copied meanwhile went to a machine that had not admitted them; the peer
drops it unread before authorization, but it is in that peer's hands.
A session now records the PeerInfo that answers its login, the moment it is
in, and is passed over until then - and again from the first act of every
connection round, so a reconnect waiting on its password is passed over too.
connection_round_state cannot stand in for that: it says Connected as soon
as the transport is up.
The unauthenticated cap on the other side is how this came up: a clipboard
over 128 KiB ended such a login with "Reset by the peer". The small case had
always gone through quietly.
A test puts two sessions in the table, one with its PeerInfo in and one
without, and broadcasts: the first receives, the second not until its
PeerInfo is in too.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
* fix(flutter): shrink the unzoomed remote cursor by DPR on macOS and Linux
With "Zoom cursor" off in Adaptive or Custom view, the remote cursor
bitmap was registered at scale 1.0. NSCursor and GdkCursor treat the
bitmap size as logical pixels, so on a HiDPI controller the cursor was
drawn DPR times larger than in Original view (which already passes
1/DPR) and than on Windows (whose cursor path is in physical pixels).
A HiDPI remote such as KDE Wayland sends a 48-64 px bitmap, which then
showed up 3-4x too big on a Retina Mac.
Scale the bitmap by 1/DPR in that case, and scale the Flutter-painted
cursor used while the peer moves the mouse the same way so its size
does not jump. The new branch is an identity at DPR 1 and the Windows
paths are untouched.
Fixes https://github.com/rustdesk/rustdesk/discussions/15363
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
* fix(flutter): check the cursor height against the min cursor size
`_checkUpdateScale` computed the scaled height from `width`, so the
min-size clamp never looked at the height.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
* fix(flutter): keep the painted cursor hotspot in place when zoom cursor is off
`CursorPaint` subtracted the hotspot in remote pixels and then scaled it
by the canvas scale, but drew the image at scale 1.0, so the hotspot
landed hotx * (1 - scale) logical pixels away from the remote cursor
position. Cursors with a centered hotspot (I-beam, crosshair) were off
by up to half their size in Adaptive view. Subtract the hotspot after
scaling the position instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
* fix(flutter): read the live DPR and clamp the painted cursor like the native one
CanvasModel caches devicePixelRatio and only refreshes it when the view
style changes, so after the window moves to a monitor with a different
DPR the unzoomed cursor kept the previous monitor's scale. Read it from
MediaQuery instead, which also rebuilds the cursor when it changes.
The native path clamps the scaled bitmap to kMinCursorSize; apply the
same clamp to the painted cursor so a small cursor does not change size
when the peer moves the mouse.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
* build(cursor): declare existing Zstd dependency for bounded decoding
* fix(cursor): validate and bound received cursor images
* fix(cursor): preserve thin cursor sizes when scaling
* fix(cursor): limit view changes to native cursor sizing
* fix(cursor): apply long-edge minimum to Web cursor sizing
* fix: preserve Linux cursor alpha and match Windows Custom scale
* fix(cursor): keep scaled buffers and raster dimensions in sync
* fix(cursor): preserve Windows peer alpha when resizing
* fix(cursor): preserve mixed alpha during downsampling
* comments
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): match desktop cursor size to peer display density
* refactor(cursor): clarify desktop and web scale branches
* fix(cursor): correct adaptive display scaling and fixed cursor size
* fix(cursor): apply all-display adaptive density on every desktop
* fix(cursor): normalize unzoomed all-display cursor density
* fix(cursor): synchronize original view on DPR changes
* fix(cursor): match original zoom to the active renderer
* fix: align Wayland software scrolling with input coordinates
* fix: preserve scroll offsets when switching scrolling modes
* fix: correct cursor size and pointer mapping with custom scale
Apply the hovered Linux display density to Custom cursor zoom in All Displays. Refresh scroll fractions after layout so changing the Custom percentage uses current scrollbar extents and detached controllers.
Validated with macOS and Windows component tests, formatting, and static analysis.
* fix(linux): pad tall native cursors to prevent clipping
* fix(cursor): preserve macOS point size in unzoomed views
* fix(linux): pad rectangular cursors to square canvases
* fix(cursor): divide dpr on Linux -> macOS
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix: cursor size test
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): cursor size of controlled side macOS
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): limit received cursor allocations
* test(cursor): retain reverse raster transition coverage
* fix(cursor): bound compressed cursor input
* fix(cursor): restrict the scaled size of the cursor
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): align hotspots with resized raster dimensions
Calculate each hotspot axis from the actual raster-to-source ratio.
Update existing boundary tests and run cursor tests in Flutter CI.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): align Sciter hotspots with raster dimensions
Calculate native and overlay hotspots from the final raster size.
Preserve input coordinate scaling and cursor refresh order.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(cursor): use `1.0` instead of `1.0/dpr` on Linux -> macOS, Zoom off, Scale adaptive
The mouse cursor currently appears somewhat large.
However, this is difficult to adjust because the cursor size
is fixed while the window size varies; its relative size
depends on the specific desktop environment.
We can modify it if users actually provide feedback.
Ideally, we should check the "Zoom cursor" option.
Further adjustments may also be needed later based on cursor density.
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: rustdesk <info@rustdesk.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* 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>
* feat(clipboard): optionally sync clipboard between connected sessions
Clipboard content received from a remote session is written to the local
clipboard with an owner marker, so the client clipboard loop deliberately
skips re-broadcasting it to avoid echo loops. As a result, text copied in
one remote window could not be pasted in another connected remote window.
Add an opt-in local option (allow-sync-clipboard-between-sessions) that
relays Clipboard/MultiClipboards messages received from one session to
all other connected sessions, excluding the source session. Per-session
clipboard permissions and view-only mode are still respected via the
existing send path, and the owner marker on the receiving peers prevents
any echo back.
Desktop (flutter) only; file clipboard is not affected.
* fix(lang): propagate sync-clipboard-between-sessions-tip to all locale files
Add the new key to template.rs and every locale file per the localization
convention, move the en.rs entry to the end of the list, and drop comments
that only restated the names next to them.
* fix(lang): add the 'Sync clipboard between sessions' label to the localization catalog
The checkbox label goes through translate(), so add it to template.rs
and every locale file so non-English locales can translate it. en.rs is
skipped since the English display text is identical to the key.
* fix(clipboard): check the source session's full clipboard permission before relaying
The relay was gated only by the incoming clipboard_allowed check
(!disable_clipboard && !view_only). Gate it with
is_text_clipboard_required() instead, which additionally respects the
source session's server_clipboard_enabled and server_keyboard_enabled
state, matching the predicate already applied to destination sessions.
A message arriving after the source permission was revoked (or from a
non-conforming peer) is no longer propagated to other sessions. The
existing local update_clipboard behavior is unchanged.
* fix(lang): translate the new clipboard sync entries in all locale files
Fill the 'Sync clipboard between sessions' label and its tooltip in
every locale file instead of leaving them blank, following each file's
existing terminology. template.rs keeps the empty master entries.
* fix(android): close outgoing sessions when the task is swiped away
Swiping RustDesk away from recents destroys the UI but does not
necessarily end the process: when MainService is running (screen share
enabled, or started at boot) the process survives, and with it the
native io_loop of any active outgoing session.
That orphaned io_loop keeps echoing TestDelay (client.rs handle_test_delay
runs entirely on the network thread, no UI involved), which keeps
refreshing last_recv_time on the controlled side. Its 30s inactivity
timeout in server/connection.rs therefore never fires, so the remote
session stays established with no UI left to close it, and the peer
cannot be reconnected to.
Close client sessions from Service.onTaskRemoved, which fires only on
explicit task removal -- not on Home or backgrounding, so ordinary
backgrounding is unaffected. The service itself keeps running, so
incoming connections and the device staying reachable are unchanged.
This complements 152c5c71b, which covered the route-pop path via
dispose(); dispose() does not run when the task is removed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(android): also close sessions on activity destroy
Review follow-up. onTaskRemoved only reaches MainService, but the
accessibility InputService keeps the process alive on its own: a user
with input control enabled and screen sharing off has a surviving
process after a swipe while MainService is not running, so the callback
never fires and the session still outlives its UI. onTaskRemoved cannot
cover that -- InputService is bound by the system, not started, so the
callback is not delivered there.
Close from MainActivity.onDestroy() as well, which runs while the
process is still alive regardless of which service keeps it up. Guarded
on isFinishing so a destroy for recreation (configuration change, "don't
keep activities") does not tear down a live session. Both paths are
idempotent.
Also drop the now-wrong "on task removed" wording from the Rust log,
which has two distinct callers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(android): release held keys before draining the session map
close_all_sessions drained SESSIONS first, then called
release_remote_keys. The release path sends through get_cur_session(),
which resolves against SESSIONS, so every generated key-up was dropped
after take_remote_keys() had already cleared TO_RELEASE: a key held as
the task is removed stays down on the controlled side until its own
timeout, with the state lost locally.
Release first, while a session is still registered. It is a no-op when
no key is held, so the previous is_empty() guard is not needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix disabled installation bypass
Prevent install.exe and --install from opening the install flow when disable-installation is set.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Refine disabled installation handling for portable clients
Document why --install must be filtered from both Rust and Flutter runner arguments for portable wrappers such as no-install.exe. Remove redundant UI-
layer installation checks because the install entry points are already gated upstream.
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* fix(clipboard): clean up stale Linux FUSE mounts
Recover Linux file clipboard FUSE mount points before remounting and stop treating a cached
context as valid when the mount has already gone away.
This fixes the desktop file manager copy failure that shows dialogs such as
"Error while copying a" and "There was an error copying the file into xxx".
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): fuse, reduce dups
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix: clear Linux file clipboard before unmounting FUSE
Ensure Linux client teardown clears RustDesk file clipboard URLs while
the FUSE context is still available. Also prefer fusermount before
umount to avoid noisy unprivileged teardown attempts.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): return and log errors
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* Add option auto record outgoing session
* In the same connection, all displays and all windows share the same
recording state.
todo:
Android check external storage permission
Known issue:
* Sciter old issue, stop the process directly without stop record, the record file can't play.
Signed-off-by: 21pages <sunboeasy@gmail.com>
Because rgba buffer render doesn't support multi display, when switch to
multi display, it is possible that rgba.valid has been set to valid but
nextRgab is not called, when switching back to single display,
rgba.valid is still true.
Fix by rgba buffer, rgba texture and gpu texture using different messages.
Signed-off-by: 21pages <sunboeasy@gmail.com>