Commit Graph

284 Commits

Author SHA1 Message Date
rustdesk
ccf9afd069 webrtc: judge the race by the resolved path, not the label; bound the ICE queue
Third review round. Two of these are regressions from the previous one.

- The RelayResponse race predicate was `is_direct_transport(result.2)`,
  which answers true for the label "WebRTC" - but WebRTC is only a
  direct path when ICE nominated a non-TURN pair. A TURN-relayed WebRTC
  result therefore committed instantly and cancelled the IPv6 attempt
  racing beside it, which is the same inversion the previous fix removed
  in the other direction. (That fix was also argued from a wrong premise:
  the site does carry an IPv6 future, pushed ~50 lines earlier than the
  relay one.) Each future now resolves whether its path is direct and
  the predicate reads that bool, matching the outer race, and the
  downstream recomputation goes away.

- policy_relay still folded in Config::is_proxy(), and that is what gets
  persisted into the peer's config as force-always-relay - so one
  session through a proxy pinned the peer to relay forever and disabled
  WebRTC for it, exactly the latch the previous round fixed for
  WebSocket. Split out peer_relay: the saved option or an explicit
  request for THIS peer, and the only part written back.

- The controlled side buffered remote ICE candidates in an unbounded
  channel while the controller caps the same buffer at 64, and draining
  one costs a JSON parse plus the ICE agent's lock. Whoever can reach a
  session's route could grow it without limit inside the long-lived
  service process. Bounded, with the overflow logged through the
  existing throttle.

- That route was also removed by key alone when an answerer finished, so
  a punch retry that built a fresh answerer under the same fingerprint
  had its live sender deleted by the previous one's cleanup - after
  which it received no candidates at all. Evict only our own sender, the
  way the session cache already guards the analogous case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
2026-09-05 15:04:13 +08:00
rustdesk
585d1fb3ca ws: decouple ICE policy from force_relay — full-ICE WebRTC over WebSocket
WebSocket support folds into force_relay because a ws tunnel kills
classic TCP/UDP punching — but that conflated transport necessity with
relay policy, and the WebRTC decisions keyed off the merged flag: a ws
client built no offerer at all without TURN, and only a Relay-only-ICE
one with it. ws deployments could never reach a direct WebRTC
connection, which is exactly the path they are supposed to live on.

Split the flag. LoginConfigHandler now tracks policy_relay (the
force-always-relay option, an explicit relay request — /r ids and
retry-via-relay included — and proxy) separately; force_relay stays
policy_relay || use_ws() and keeps governing the classic paths, so
non-ws behavior is unchanged everywhere:

- the offerer's existence and ICE policy follow policy_relay: under
  pure ws the offer gathers every candidate type and may go direct;
  under relay-by-policy it stays Relay-only ICE, TURN-gated, exactly
  as before;
- the RelayResponse race applies the prefer-P2P window under ws (a
  direct ICE path is worth delaying an already-ready relay for) while
  policy relay keeps first-success semantics;
- the request carries webrtc_all_ice (hbb_common 64b54ab) so the
  controlled side knows the offer is full-ICE: it answers with full ICE
  and no TURN requirement, while offers without the bit keep today's
  relay-only answer path on every version-skew combination.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
2026-09-05 15:04:12 +08:00
RustDesk
3fc11c0f81 port forward shared conn (#16062)
* hbb_common: bump to the port-forward-mux proto

Also latches PortForward.multiplex into login_scope_digest, which
destructures PortForward's fields exhaustively by design (a new field
must be latched or deliberately ignored to compile).

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

* port_forward_mux: window accounting and channel frame builders

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

* port_forward_mux: fix RecvWindow counter overflow on long transfers

Replace cumulative accounting (granted/received) with remaining credit
tracking to prevent u32 overflow after 4 GiB of data on a single channel.
Wire behavior is identical, but the fix allows large file transfers
without mid-stream channel closure.

Add regression test for 8 GiB transfer to verify fix.

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

* port_forward_mux: credit-windowed relay halves and channel coordinator

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

* server: PortForwardMux channel table and per-channel tasks

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

* server: multiplexed port-forward connections stay in the protobuf loop

Wire PortForwardMux into Connection: take the multiplexed path at login
when the controller sets PortForward.multiplex, route
PortForwardChannel frames to it from on_message, sweep the channel
table's targets after open/close, and clean it up on connection close.

Introduce is_port_forward() (socket-based or multiplexed) and use it
at the four sites that classify the connection, so a multiplexed
connection stays in the message loop, gets TestDelay keepalives, and
reports features.port_forward_mux in PeerInfo. The three sites that
break into the raw pipe loop or gate the keepalive still check
port_forward_socket specifically, since a multiplexed connection must
not take that path.

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

* cm: update a port-forward row's targets as tunnel channels come and go

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

* port_forward_mux: controller tunnel with a single-writer stream loop

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

* port_forward_mux: publish Muxed before spawning the tunnel loop

Publishing after spawn let a loop that dies immediately reset the state
first, so the later publish pinned it at Muxed with a dead handle
forever. Also adds a test pinning open-before-data ordering across many
concurrently opened channels, and drops an unused Clone derive.

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

* port forward: share one multiplexed tunnel across a window's listeners

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

* port forward: fix round 1 review findings

Drop the mux default-false assignment now that definite-assignment proves
every path that reads it has set it; the enable-port-forward-mux config
commit picks up the missing attribution trailers; the default-on test
pins the enable- prefix itself rather than option2bool's weaker fallback.

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

* port_forward_mux: end-to-end tests over a loopback tunnel

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

* port_forward_mux: fix bulk test's premature half-close, pin the half-close limitation

many_channels_echo_concurrently_and_a_bulk_one_does_not_starve_them dropped
its bulk write half as soon as writing finished, which shuts down the write
side of the socket and, by design (see the design doc's TCP half-close
non-goal; today's run_forward does the same), ends the whole channel. Keep
the write half alive until the reader is done so the test measures
starvation, not half-close. Add a_local_half_close_ends_the_whole_channel to
pin that limitation in code.

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

* port_forward_mux: cap send credit and other final review fixes

Fix 1 (critical): clamp SendCredit to MAX_SEND_CREDIT (= CHANNEL_WINDOW)
in both new() and add(), so a peer with tunnel permission can no longer
advertise an unbounded window and force the controlled side's unbounded
FrameSink::Direct sink to buffer unlimited target data per channel.

Fix 2: rename the "starve" test to many_channels_echo_concurrently and
drop its (untrue) starvation claim, since it opens every channel before
the bulk transfer starts. Add a_channel_opened_during_a_bulk_transfer_
is_served_promptly, which opens the small channel while the bulk one is
demonstrably mid-flight.

Fix 3: only look up the tunnel permission for `open` frames in the
PortForwardChannel arm of on_message, instead of once per data frame.

Fix 4: two rustfmt deviations in connection.rs (matches! wrapping and a
tuple literal), fixed by hand without a blanket cargo fmt run.

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

* port_forward_mux: report a refused channel's reason as an error dialog

The controlled side already answers a refused port-forward channel with
opened { success: false, message }; on the multiplexed path TunnelHandle::
on_frame only logged that message at debug and closed the channel, so the
user saw a closed connection with no explanation, worst on the RDP path
where only the RDP client's own error remained. on_frame now returns the
message the window should show, deduplicated per distinct reason (capped
at MAX_REPORTED_OPEN_ERRORS) so one page load's dozen refused connections
surface one dialog per reason instead of a dozen.

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

* Use on_error for refused-channel dialog in tunnel_loop

Redirect the refused-channel error through the standard on_error path
instead of calling msgbox directly, for consistency with other errors
in the port-forward flow.

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

* port forward: apply the whole-branch review

Correctness:
- listen(): the Legacy arm is merged with the Claimed arm. On its own it
  ignored outcome.local_eof, so a client that hung up during login still
  got a target connect, an audit record and a CM row on the controlled
  side, and ignored outcome.mux, so a peer upgraded while a legacy window
  stayed open answered as a tunnel while the controller went raw.
- Refusal dialogs are deduplicated per quiet spell (10 s) rather than per
  tunnel lifetime; the lifetime set went silent for the rest of a
  long-lived window after the first burst.
- Android's CM listener handles UpdatePortForward; it fell into `_ => {}`.
- relay_socket_to_tunnel reads into one scratch buffer per channel and
  sends an exact-size copy. A frame owning its 64 KiB read allocation
  pinned it until sent, once per byte on interactive traffic.

Consistency and cleanups:
- The controlled side's refusal text is the raw pipe's wording, RDP
  substitution included.
- connection.rs: the PortForwardChannel arm is a one-line hook, the CM
  label is pushed from the 1 s tick alone, and the unreachable inner.tx
  fall-through is gone.
- The Ready enum is removed; wait_ready() returns Option<Claim>.
- SendCredit::add wakes with notify_one alone.
- on_ui_command() replaces the two ui_receiver handlers in listen().
- TunnelHandle is no longer re-exported (unused-import warning).

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

* port forward: a legacy window stays legacy until it is reopened

Review: the merged `Claimed | Legacy` arm gave a legacy window a hot
transition to a tunnel — every accept re-negotiated, and a peer upgraded
while the window stayed open was promoted underneath live connections.
The product does not need a mode switch inside a window's lifetime, and
the transition was extra state-machine surface for nothing: reopening
the window picks up an upgraded peer.

The two arms are separate again. `Claimed` negotiates once and the
peer's answer fixes the window's mode. `Legacy` logs in for every accept
as before, asks for no tunnel — `LoginConfigHandler::port_forward_mux`
carries the request per login, so the raw pipe never has to talk to a
peer that thinks it agreed to multiplex — and ignores what the peer
reports. Both arms keep skipping a local socket that hung up during
login.

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

* hbb_common: bump to main with rustdesk/hbb_common#594 merged

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

* server: admit only INITIAL_WINDOW on a channel before opened

The demultiplexer accepted CHANNEL_WINDOW into a pending channel's
unbounded queue, four times the bound the channel task enforces once
it polls. The window now starts at INITIAL_WINDOW and is widened right
before `opened` advertises the rest.

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

* port_forward_mux: a tunnel ends when its window drops the Tunnel

The loop held its own handle and state sender, so once the window
closed nothing was left to stop it: it kept answering TestDelay and the
peer connection, CM row included, lived on until the peer went away.
`Tunnel` now owns a watch sender nobody sends on; the loop's receiver
errors when the last `Tunnel` drops, and the loop ends.

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

* port forward: one tunnel per mapping, bound to the authenticated target

The login latches `PortForward.host`/`port` into the session scope and
approval is shown that target, but a window-wide tunnel let any later
`open` name another target with only `enable-tunnel` rechecked. A
tunnel now belongs to one listener and serves the one target its login
authenticated: the controlled side refuses an `open` for any other
target, and a window with several targets uses one connection each,
approved on its own.

With one owner per tunnel the claim needs no waiters: `Establishing`,
`Claim::Wait` and `wait_ready` go, and `try_claim` becomes a plain
read. The CM label that followed a tunnel's targets goes with them; a
row shows its mapping's target, as before.

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

* port forward: the legacy comment names the mapping, not the window

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

* port_forward_mux: a window violation drops the channel on the spot

Both demultiplexers only queued a `Violation` and left the entry until
the channel task woke and exited, so a peer that kept sending past the
window queued one more entry per frame in the meantime, bounded by
nothing. The entry now goes the moment `accept` fails; later frames for
that id are unknown-channel noise.

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

* port forward: the login's target travels with the accept, not the handler

`listen()` wrote `lc.port_forward` (and, on this branch, `port_forward_mux`)
into the window's shared `LoginConfigHandler` before connecting, and
`create_login_msg` read them back only when the peer's `Hash` arrived.
Two mappings logging in at the same time could therefore swap targets:
on master that bridged a local socket to the wrong target, and with a
tunnel bound to its login's target it also left the mapping refusing
every later accept until it was recreated.

The target is now a `PortForward` carried by the interface clone that
handles one accept, passed explicitly down to `create_login_msg`; the
handler no longer has a field to race on. No lock spans the login.

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

* port_forward_mux: pin permission revocation and whole-tunnel failure in tests

Both already hold; the review asked for them to be stated. `enable-tunnel`
turned off mid-session refuses the next `open` while the live channel
keeps relaying, and a dead tunnel ends every channel on it together,
after which the next accept establishes again on the same `Tunnel`.

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

* port forward: the legacy comment names re-adding the mapping only

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

* port forward: the raw pipe runs the code it always ran

The multiplexed login had replaced `connect_and_login`, so a mapping
with the setting off, a peer without the feature, or a listener latched
`Legacy` still went through the tunnel's state machine, the capped
pre-read and the changed local-EOF rule. Feature off now means the old
code: `listen()` keeps its accept arm and `connect_and_login` as they
were, and the tunnel is a branch taken only when the setting is on, in
`establish_tunnel` with its own `connect_and_login_mux`. The one line
the raw path does differently is the target riding with the accept's
interface clone instead of the shared handler.

`get_port_forward_mux_enabled` had one caller and moves in here, so
`common.rs` is untouched.

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

* port forward: a UI login answers the challenge its own connection was given

`handle_login_from_ui` hashed the typed password against `lc.hash`, the
window's shared handler field, and the window's password prompt is
broadcast to every listener. With two mappings both waiting on that
prompt, the `Hash` that arrived last had overwritten the other's, so
one of the two answered the wrong challenge and failed to log in.
Master shares the same state and broadcasts the same way.

The `Hash` is now a parameter of the login; `Session` keeps it beside
the connection it belongs to, and the per-accept clone that
`with_port_forward` makes gets a slot of its own. `lc.hash` stays for
`handle_peer_info`, which only needs the salt, and that is per peer.

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

* port forward: a mapping without its hash waits for it before answering the prompt

The window's password prompt is broadcast to every mapping, and can
reach one whose own connection has not received its `Hash` yet. That
mapping used to answer anyway, with a digest over an empty challenge:
the peer refused it and counted a failed attempt, and the empty-salt
result was written into the shared `lc.password`, where the mapping that
prompted had just stored the right one and the next `handle_peer_info`
would persist whatever was there.

The connection's challenge is now `Option<Hash>`, `None` until
`handle_hash` runs, and `handle_login_from_ui` sends nothing without it.
The mapping that prompted stores the salted password in the shared
handler, and the waiting one logs in with that against its own challenge
when its `Hash` arrives, without prompting again.

Test: A answers its prompt, the same broadcast reaches B before its
hash, B sends nothing, B's hash arrives and its login carries B's
challenge and B's target with no dialog. It runs the real `handle_hash`
for B.

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

* port forward: the tunnel's login is the raw pipe's, asked for by a window flag

Master's fix for the shared login slots (#16069) keeps the target and
the challenge in the window's `LoginConfigHandler` and serializes the
mappings' logins with a turn lock, all inside `port_forward.rs`. This
branch had carried a broader shape of the same fix, a `with_port_forward`
on `Interface` and the target and `Hash` as parameters through the login
functions, which every caller had to follow. That is gone: `Interface`,
`Session`, `create_login_msg`, `send_login`, `handle_hash` and
`handle_login_from_ui` are as on master.

What the tunnel needs on top is one bit in the login, `multiplex`. It is
a window flag beside `port_forward` in the handler, set once in `io_loop`
before the window's mappings start, so an accept's claim and its login
read the same value; the setting takes effect for windows opened after
it changes. `connect_and_login_mux` is now master's `connect_and_login`
with the tunnel's three differences and the same `hash_arrived` and
`login_from_ui` calls. The raw pipe is master's, line for line.

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

* hbb_common: bump to main with rustdesk/hbb_common#595 merged

840c8ec..f94e3fe is that one merge: the five local settings custom
clients could not preset.

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

* port forward: the off switch gets a checkbox in Settings → General

`enable-port-forward-mux` was readable only by editing the config file.
It is a local setting of the controlling side, so it sits with the other
outgoing ones, after "Open connection in new tab", with a tooltip saying
what it does.

The two new keys are translated in every language. The three that the
mobile file manager added, "Export", "Export Logs" and "Import Folder",
were empty everywhere but five languages; they are filled in too, and
Korean's "xdp-portal-unavailable" with them.

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

* Urdu: fill the backlog of empty and missing translations

ur.rs had fallen behind: 104 keys carried an empty value and 35 keys the
other languages have were absent altogether. Both are filled in, the
missing ones in the order template.rs lists them.

Eight entries stay empty on purpose. They are keys that only ur.rs still
carries, absent from template.rs and from every other language, so their
English source cannot be recovered and nothing reads them:
remember_account_tip, os_account_desk_tip, another_user_login_*_tip,
xorg_not_found_*_tip and no_desktop_*_tip. Twelve more dead keys keep
the values they have; removing either group is a separate decision.

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

* Urdu: drop the keys template.rs no longer lists

The twenty keys removed here are absent from template.rs and from every
other language file; ur.rs was the only one still carrying them, eight
of them with no value at all. They are leftovers of features that are
gone: the plugin menu, the OS-account login prompts, the Xorg and
no-desktop errors.

ur.rs now holds exactly the template's key set, all of it translated.

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

* port forward: closing the tunnel reaches channels parked on their socket

A channel whose far end neither reads nor writes has both relays parked
on the socket, not on the inbound queue, so `close_all` dropping the
queue's sender woke neither: the socket and both tasks lived on until
the far end hung up. Both sides now hold a per-tunnel teardown signal
that `run_channel` selects on beside its own cancel, and `close_all`
sends it after clearing the map.

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

* port forward: a mapping latched to the raw pipe logs in without asking for the tunnel

The login copied the window's `port_forward_mux` into `multiplex`, so a
mapping that had latched to the raw pipe on an old peer kept asking for
the tunnel. Once that peer was upgraded it answered with a tunnel while
the controller switched to raw framing, and every later connection on
the mapping was dead until it was re-added. The login now carries its
own `port_forward_multiplex`, filled with the target under the turn
lock: the probe asks, the raw pipe does not.

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

* port forward: a channel opened as its tunnel closes still gets the teardown

`open` can straddle `close_all`: the claim passed, the frame receiver was
still alive, and the channel subscribed after the signal had gone out.
`watch::subscribe` marks earlier sends as seen, and the entry sits in a
map that was already cleared, so nothing would ever end it. The signal is
now a level: `close_all` raises it with `send_replace`, which stores even
with no channel live, and `run_channel` waits for the value rather than
for a change.

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

* port forward: the connect guard counts a live tunnel as connected

`connect_port_forward_if_needed` returned early only for a raw-pipe
socket; called again with a tunnel up it would have built a second
`PortForwardMux` and dropped every channel of the first. Not reachable
today, since the logon response is sent once, but the other checks in
this change already read `is_port_forward()`.

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

* Urdu: the two terminal clipboard keys master added

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

* port forward: a tunnel's TCP stream refuses packets over twice MAX_FRAME

The codec takes a header declaring up to 1 GiB and hands the packet up
only once it has all arrived, so the channel window bounded what the
peer may send, not what this side buffers. Both sides now cap the codec
at 2 * MAX_FRAME as soon as multiplexing is agreed: a data frame with
its envelope and MAC fits with room to spare, and a header over the cap
ends the tunnel before a byte of payload is read. TCP only; the
WebSocket and WebRTC codecs carry caps of their own.

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

* port forward: a channel id still live when the counter comes round is skipped

The controller handed out `next_id` unchecked. 2^32 opens later it lands
on a channel still up: the entry here was replaced, while the peer,
which ignores an `open` for a live id, kept routing that id to the old
socket, so the new local connection's bytes went into the old target
connection. The id is now taken under the map's lock and advanced past
any id in use.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 14:59:51 +08:00
fufesou
d1da05c4db refact: remove feature plugin-framework (#15854)
* refact: remove feature plugin-framework

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: remove unused translations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: delete settings tab observable with correct type

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-08-14 14:31:13 +08:00
rustdesk
947cb3f17b propagates the hash-handler continuation result through both connection loops, allowing incoming-only rejection to terminate the connection while preserving existing login flows. 2026-08-10 16:45:27 +08:00
fufesou
28930c0463 fix: non-E2EE show dialog (#15514)
* fix: non-E2EE show dialog

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: build web, bridge

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: direct IP access, do not snow non-E2EE dialog

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non E2EE dialog, update contents

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE, show dialog, port forward

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, port forward, ignore direct IP access

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE is_direct_ip_access()

Signed-off-by: fufesou <linlong1266@gmail.com>

* Simple refactor

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, port forward, close socket on disconnect

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, incorrect reuse of Data::Close

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-06 17:05:11 +08:00
fufesou
493b14ba78 Fix/session scope permission audit (#15469)
* fix: enforce session-scoped permissions

Restrict non-remote sessions to their allowed message types, filter
out-of-scope login options, and audit rejected or filtered messages.
Hide screenshot controls outside default remote sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: typo

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: prevent privacy mode in view-camera sessions

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: switch display, check non-view-camera

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: avoid sending unsupported messages

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, add option to control close/alarm

Signed-off-by: fufesou <linlong1266@gmail.com>

* Fix: scoped session handling for view-camera compatibility

  - Skip view-camera auto-login and display-management side effects
  - Allow harmless render broadcasts without affecting non-video sessions
  - Keep legacy view-camera management messages compatible as no-ops
  - Preserve stricter scope violations for non-video session types

Signed-off-by: fufesou <linlong1266@gmail.com>

* update libs/hbb_common

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: ignore repeated login request

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: view camera, support "Take screenshot"

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scoped messages, check update options

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, check portforward before conn type voolations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: scoped messages, reduce changes.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: keep scoped sessions compatible with render broadcasts

Allow legacy render-broadcast no-op messages for file transfer and terminal
sessions while keeping port forward and mixed options scoped. Also avoid sending
new render updates to non-video Flutter sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: scope screenshot requests by video source

Key screenshot requests by video source and display index so camera and
monitor sessions cannot consume each other's requests. Deduplicate the Flutter
render-target predicate while keeping render updates limited to video sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Harden scoped session message handling

Filter option updates by authenticated connection type,
keep legacy no-op messages compatible, and avoid noisy repeated
scope violation alarms.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Send close reason for scoped session violations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Enforce scoped session message filtering

  - filter out-of-scope messages for limited session types
  - scope option updates by authenticated connection type
  - keep render-broadcast no-op compatibility for non-video scoped sessions
  - restore view-camera screenshot handling
  - improve session scope violation audit labels
  - avoid cloning option messages on the remote hot path

Signed-off-by: fufesou <linlong1266@gmail.com>

* Fix scoped session clipboard broadcast compatibility

Treat text clipboard broadcasts as no-op compatibility messages for FileTransfer and Terminal sessions, matching existing
handler behavior and preventing optional scope-violation close from disconnecting those sessions. Keep ViewCamera and
PortForward clipboard messages subject to normal scope enforcement.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: log warn

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: restrict Flutter clipboard sync to default sessions

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments and tests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, reset sessions in login handle

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, view camera, allow clipboard noop

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-05 23:28:32 +08:00
fufesou
001848bf2f fix(fuse): umount (#15426)
* 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>
2026-06-26 16:17:44 +08:00
fufesou
88ae00ba73 refact: restart remote device, autoconnect (#15290)
* refact: restart remote device, autoconnect

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: guard restart reconnect timer after session close

Signed-off-by: fufesou <linlong1266@gmail.com>

* Simple refactor

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(restart): auto connect, comments

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-06-17 17:50:18 +08:00
littlestejan
84af60c07e Fix clipboard synchronization not fully disabled in View Only mode (#15224)
* fix: view-only clipboard sync

Signed-off-by: Setani <little_stejan@hotmail.com>

* fix: gate Android MultiClipboards handling with clipboard permissions

Signed-off-by: Setani <little_stejan@hotmail.com>

---------

Signed-off-by: Setani <little_stejan@hotmail.com>
2026-06-10 07:42:58 +08:00
rustdesk
f29dec7b13 harden switch side 2026-05-06 19:27:56 +08:00
Sergiusz Michalik
7308c448f1 fix(client): serialize X11 keyboard grab and debounce focus feedback (#14836)
* fix(client): serialize X11 keyboard grab and debounce focus feedback

When two RustDesk sessions run fullscreen on separate monitors on
Linux/X11, keyboard input gets stuck on the wrong session or stops
working entirely. This happens because each Flutter isolate calls
change_grab_status concurrently, racing on KEYBOARD_HOOKED and the
rdev grab channel.

Additionally, XGrabKeyboard causes a focus-change feedback loop:
grab shifts focus away from the Flutter window, triggering PointerExit,
which releases the grab, restoring focus, triggering PointerEnter,
which re-grabs -- cycling at ~10 Hz and blocking keyboard input.

Fix by:
- Serializing grab transitions with a mutex and tracking the owning
  session (by lc.session_id), so a stale Wait from session A cannot
  clobber session B's freshly acquired grab.
- Debouncing Wait events (300 ms) from the same session that just
  acquired the grab, breaking the X11 focus feedback loop.
- Refreshing the debounce timer on idempotent Run calls (enterView
  while already owner), keeping the grab stable during normal use.

Signed-off-by: Sergiusz Michalik <github@latens.me>

* fix(client): add deferred release and dedup for debounced Wait

When a Wait is debounced (within 300ms of grab acquisition), schedule
a deferred release thread that re-checks after the debounce window.
If no new Run refreshed the grab, the deferred thread releases it,
ensuring a genuine leave within the debounce window is not lost.

Add a deferred_pending flag to GrabOwnerState to prevent spawning
redundant threads during the X11 focus feedback loop.

Signed-off-by: Sergiusz Michalik <github@latens.me>

* fix(client): use window-scoped ID and fix deferred-release re-arming

Address PR review feedback:
- Use per-window UUID instead of connection-scoped lc.session_id so two
  windows viewing the same peer get distinct grab owners
- Reset deferred_pending on both idempotent Run refresh and owner
  handoff, so a subsequent Wait can always spawn a fresh timer
- Replace manual Default impl with derive

* fix(client): recover from poisoned mutex instead of panicking

* docs: clarify cross-platform rationale for GrabOwnerState

* fix(client): only clear deferred_pending when timer snapshot matches

* fix(client): use full u128 window ID, downgrade grab logs to debug

- Widen GrabOwnerState.owner to u128 to avoid theoretical collision
  from truncating a 128-bit UUID to 64 bits
- Downgrade all grab transition log::info! to log::debug! to reduce
  log noise during routine window switches
- Clear deferred_pending on post-debounce release path to maintain
  the "deferred_pending => timer in flight" invariant

* fix(client): gate GRAB_DEBOUNCE_MS with cfg(target_os = "linux")

* fix(grab): release grabbed keys without clobbering new owner state

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(keyboard): Simple refactor

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: Sergiusz Michalik <github@latens.me>
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
2026-04-26 22:46:41 +08:00
Copilot
02da7132e7 Fix: note dialog not shown when closing session from reconnecting screen (#14528)
* Initial plan

* Fix: show ask-for-note dialog when user clicks OK on reconnecting screen (#14527)

Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>

* fix: don't clear audit_guid during reconnect, clear it after connection established

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
2026-03-16 18:27:39 +08:00
fufesou
998b75856d feat: Add relative mouse mode (#13928)
* feat: Add relative mouse mode

- Add "Relative Mouse Mode" toggle in desktop toolbar and bind to InputModel
- Implement relative mouse movement path: Flutter pointer deltas -> `type: move_relative` -> new `MOUSE_TYPE_MOVE_RELATIVE` in Rust
- In server input service, simulate relative movement via Enigo and keep latest cursor position in sync
- Track pointer-lock center in Flutter (local widget + screen coordinates) and re-center OS cursor after each relative move
- Update pointer-lock center on window move/resize/restore/maximize and when remote display geometry changes
- Hide local cursor when relative mouse mode is active (both Flutter cursor and OS cursor), restore on leave/disable
- On Windows, clip OS cursor to the window rect while in relative mode and release clip when leaving/turning off
- Implement platform helpers: `get_cursor_pos`, `set_cursor_pos`, `show_cursor`, `clip_cursor` (no-op clip/hide on Linux for now)
- Add keyboard shortcut Ctrl+Alt+Shift+M to toggle relative mode (enabled by default, works on all platforms)
- Remove `enable-relative-mouse-shortcut` config option - shortcut is now always available when keyboard permission is granted
- Handle window blur/focus/minimize events to properly release/restore cursor constraints
- Add MOUSE_TYPE_MASK constant and unit tests for mouse event constants

Note: Relative mouse mode state is NOT persisted to config (session-only).
Note: On Linux, show_cursor and clip_cursor are no-ops; cursor hiding is handled by Flutter side.

Signed-off-by: fufesou <linlong1266@gmail.com>

* feat(mouse): relative mouse mode, exit hint

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact(relative mouse): shortcut

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-01-09 10:03:14 +08:00
21pages
4f2aea65ab require login for note (#13775)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-12-20 16:51:25 +08:00
fufesou
4ed8696d1d fix: file transfer, jobs lost if conn is not established (#13635)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-11-26 19:15:32 +08:00
fufesou
b2dff336ce fix: wayland controlled side, cursor misalignment (#13537)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-11-18 00:37:15 +08:00
21pages
296c6df462 ask for note at end of connection (#13499)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-11-13 23:35:40 +08:00
21pages
e029d00cfa edge scroll thickness adjustment (#13445)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-11-07 01:15:13 +08:00
fufesou
e3fcc6cce3 fix: file transfer, auto start on reconnect (#13329)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-10-29 15:15:05 +08:00
fufesou
a22f2108c6 refact: suppress warns on macos (#12449)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-08-18 15:09:11 +08:00
21pages
a0659a277a show TCP/UDP/IPv6 in tooltip (#12613)
* add punch type log

Signed-off-by: 21pages <sunboeasy@gmail.com>

* show TCP/UDP/IPv6 in tooltip

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Skip udp punch if udp nat port is 0

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-08-11 16:13:31 +08:00
RustDesk
5faf0ad3cf terminal works basically. (#12189)
* terminal works basically.
todo:
- persistent
- sessions restore
- web
- mobile

* missed terminal persistent option change

* android sdk 34 -> 35

* +#![cfg_attr(lt_1_77, feature(c_str_literals))]

* fixing ci

* fix ci

* fix ci for android

* try "Fix Android SDK Platform 35"

* fix android 34

* revert flutter_plugin_android_lifecycle to 2.0.17 which used in rustdesk 1.4.0

* refactor, but break something of desktop terminal (new tab showing loading)

* fix connecting...
2025-07-01 13:12:55 +08:00
fufesou
ca7b4872d9 feat, trackpad speed (#11680)
* feat, trackpad speed

Signed-off-by: fufesou <linlong1266@gmail.com>

* comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* Trackpad speed, user default value

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2025-05-09 15:36:45 +08:00
fufesou
20fcddffbd fix: build (#11611)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-04-30 23:49:18 +08:00
fufesou
c626c2414d feat: take screenshot (#11591)
* feat: take screenshot

Signed-off-by: fufesou <linlong1266@gmail.com>

* screenshot, vram temp switch capturer

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: misspelling

Signed-off-by: fufesou <linlong1266@gmail.com>

* screenshot, taking

Signed-off-by: fufesou <linlong1266@gmail.com>

* screenshot, rgba stride

Signed-off-by: fufesou <linlong1266@gmail.com>

* Bumps 1.4.0

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2025-04-30 17:23:35 +08:00
fufesou
f4bbf82363 feat: remote printer (#11231)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-03-27 15:34:27 +08:00
21pages
d1c8b331c5 Option allow-d3d-render and fix ios ci (#11107)
* option `allow-d3d-render`, default false

Add this option because it fails on some machines

Signed-off-by: 21pages <sunboeasy@gmail.com>

* only add nokhwa to windows and linux dependencies

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-03-13 09:34:13 +08:00
21pages
f0f999dc27 view camera (#11040)
* view camera

Signed-off-by: 21pages <sunboeasy@gmail.com>

* `No cameras` prompt if no cameras available,  `peerGetSessionsCount` use
connType as parameter

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix, use video_service_name rather than display_idx as key in qos,etc

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: Adwin White <adwinw01@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2025-03-10 21:06:53 +08:00
rustdesk
bdc53f0190 improve lock, https://github.com/rustdesk/rustdesk/issues/11067 2025-03-10 11:10:09 +09:00
fufesou
fbba8f0b34 refact: file copy&paste, cross platform (no macOS) (#10671)
* feat: unix, file copy&paste

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: unix file c&p, check peer version

Signed-off-by: fufesou <linlong1266@gmail.com>

* Update pubspec.yaml

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2025-02-04 20:33:02 +08:00
fufesou
1c62a28ef3 fix: build (#10364)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-25 16:36:13 +08:00
21pages
e5aa31eb4c Fix auto record outgoing sessions ignore record permission (#10294)
1. Fix auto record outgoing sessions ignore record permission
2. Stop record if record permission changed
3. Update hwcodec
4. Make video thread finish faster when connection closed

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-16 17:13:48 +08:00
zuiyu
314c93b210 Create empty dir on send files in local (#9993)
* feat: Add empty dirs on sendfiles

* Update connection.rs

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-11-23 23:09:11 +08:00
21pages
64654ee7cf seperate video decoding thread for each display (#9968)
* seperate video decoding thread for each display

1. Separate Video Decoding Thread for Each Display
2. Fix Decode Errors When Clearing the Queue
Previously, on-flight frames after clearing the queue could not be decoded successfully. This issue can be resolved by setting a discard_queue flag when sending a refresh message. The flag will be reset upon receiving a keyframe.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* update video format along with fps to flutter

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Fix keyframe interval when auto record outgoing sessions

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-22 00:02:25 +08:00
fufesou
8b710f62c8 feat: android clipboard, multi-formats (#9950)
* feat: android clipboard, multi-formats

Signed-off-by: fufesou <linlong1266@gmail.com>

* Chore

Signed-off-by: fufesou <linlong1266@gmail.com>

* Remove unused code

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-18 15:43:41 +08:00
21pages
445e9ac285 no password required for file transfer action in remote control menu (#9731)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-24 17:20:48 +08:00
fufesou
cc6f919080 feat: mobile map mode (#9717)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-23 09:28:39 +08:00
21pages
e8187588c1 auto record outgoing (#9711)
* 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>
2024-10-21 14:34:06 +08:00
fufesou
bf390611ab fix: keyboard, sciter (#9216)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-31 19:02:50 +08:00
fufesou
aa1e122532 fix: revert key events to raw key events on Linux (#9161)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-25 00:03:31 +08:00
fufesou
1d416f6626 refact: flutter keyboard, map mode (#9160)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-24 12:10:36 +08:00
21pages
1729ee337f trust this device to skip 2fa (#9012)
* trust this device to skip 2fa

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Update connection.rs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-08-12 18:08:33 +08:00
rustdesk
bbdce8d57b Send clipboard keystroke, https://github.com/rustdesk/rustdesk/discussions/5451 2024-07-25 15:21:28 +08:00
dignow
baf70da2fe fix: trackpad, reverse horizontal scroll (#8827)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-25 14:44:58 +08:00
fufesou
c5d3c7f390 Feat/android more actions (#8496)
* feat: android volume and power actions

Signed-off-by: fufesou <linlong1266@gmail.com>

* Add translations and refact action menus

Signed-off-by: fufesou <linlong1266@gmail.com>

* Remove divider

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: recover deleted translations

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-27 13:28:05 +08:00
21pages
93133b9a6c if fps is adjusted below 30, not set fps to 30 when changing image quality (#8468)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-25 17:25:50 +08:00
21pages
1765c7bbf4 fix multi display fps control (#8455)
* Calculate fps without distinguish displays, use one fps control
  because the controlled side control fps of all displays with one FPS
  variable.
* Because all displays decode frame in one thread, when there are N
  displays, the video frames received in one second is `fps * N`, so the
  calculated decode fps should be divided by N. Because the actual
  display count is not obvious in rust, when no data frame is received for 5 seconds, the display is considered inactive, and only the active display is used as the dividend.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-24 19:41:15 +08:00
rustdesk
f2a612c3d9 add voice_call start_pa 2024-06-17 10:57:25 +08:00
fufesou
7ca5a0b977 fix: switch texture, do not input os password (#8178)
* refact: switch texture, do not input os password

Signed-off-by: fufesou <linlong1266@gmail.com>

* Switch texture, refresh video

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-28 22:33:28 +08:00