* fix(macos): turn privacy mode off before locking at session end
Most closes call on_close(.., true) from inside the connection loop,
which locks the screen before the loop turns privacy mode off. On macOS
the lock is a Ctrl+Cmd+Q queued on the main queue, and privacy mode's
event tap is torn down on that same queue right after it, so the screen
did not lock and the displays came back on unlocked.
On macOS a connection that owns privacy mode now turns it off before
locking. The macOS lock_screen() also checks that the screen locked
(CGSSessionScreenIsLocked) and presses the shortcut once more if not.
Fixes#15875
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(macos): check the lock once more after the last wait
The retry loop ended its last wait without checking, so a lock that
landed in the final 100 ms pressed the shortcut again or logged a false
"Failed to lock the screen". Wait in platform::wait_locked(), which also
checks at the deadline, and keep the first press as it is on master.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refact(macos): keep on_close a one-line hook for the turn-off before locking
Move the ownership check and the turn-off into privacy_mode::macos, so
the shared Connection::on_close only calls it, and log a failed turn-off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(macos): turn privacy mode off on the main queue
The blackout monitor, the display reconfiguration callback and the
deferred shutdown lock g_privacyModeMutex on the main thread, while the
turn-off held that mutex and waited for the main queue to tear the event
tap down, so the two could deadlock. The turn-off now runs before the
lock at session end, where a deadlock would also keep the lock from
being requested.
Called off the main thread with privacy mode on, MacSetPrivacyMode(false)
now runs the whole turn-off on the main queue. With privacy mode off it
returns as before, without touching the main queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(macos): leave lock_screen() as on master
Review asked to keep this PR to the ordering fix. Drop the second
press in lock_screen() and the wait_locked() helper it used, so
ordinary macOS lock requests behave as on master again. Privacy mode
is still turned off before the lock at session end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(flutter): keep CM side panels on the current Windows monitor
Set the final physical position and size together when toggling CM chat or
file panels. This avoids an intermediate resize crossing into a monitor
with a different DPI before the position is applied.
Limit the change to the Windows CM path and add a positioning regression test.
* fix(flutter): fall back when the CM window screen is unavailable
getWindowInfo() could throw before the side-panel toggle reset its guard,
which locked the chat and file buttons and dropped the incoming message
that opened the panel. A failure now takes the previous setSizeAlignment
path, which also makes the existing no-screen fallback reachable.
The test now starts from the reported setup, the window at the right
edge of a 100% monitor, and checks that each toggle is a single
setBounds, since a resize followed by a move is what crossed monitors.
The 150% case keeps covering the scaled size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* test(flutter): cover CM window info fallback
* Revert "test(flutter): cover CM window info fallback"
This reverts commit 9204e6a021.
---------
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: block sensitive heartbeat strategy options
Filter 25 sensitive server, trust, authentication, access-control,
deployment and local service settings before merging heartbeat strategy
options. Preserve the existing HTTP protocol, all 27 current server
policy options, and other unblocked options for version compatibility.
Strategy responses remain unauthenticated.
Add regression coverage for blocked writes and clears, including registry
recovery data, and preservation of existing and future policy options.
Tests: cargo test --lib hbbs_http::sync::strategy_tests --locked (2 passed)
* fix: log rejected strategy options and strengthen regression tests
* fix: block trusted-device expiration in strategy updates
A strategy setting it to 0 would make trusted devices skip 2FA forever.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: block websocket port in heartbeat strategies
* fix: preserve security policy controls in heartbeat strategies
Allow the connection-window and scope-violation policy controls.
Keep enable-trusted-devices blocked because disabling it clears existing
trusted-device grants, which re-enabling does not restore.
* fix: acknowledge heartbeat strategies after application errors
Skipping modified_at after an application error allows the server to
deliver the same strategy version repeatedly. Authentication options
(2FA, trusted-device enablement and expiration) are already blocked,
and the authentication-update path applies unrelated options first.
Keep reporting application errors, but continue processing modified_at
so the failed version is acknowledged. Log "strategy updated" only on
success. Acknowledgement does not guarantee full application; retrying
a failed strategy requires publishing a new version.
Validation: cargo test --lib hbbs_http::sync::strategy_tests --locked
(2 passed; filter coverage only, no live heartbeat verification).
* Revert "fix: acknowledge heartbeat strategies after application errors"
This reverts commit a03d235236.
* test: fix future strategy option spelling
---------
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
sciter-rs ORs flags together and transmutes the result back into a
fieldless enum (SCITER_CREATE_WINDOW_FLAGS in WindowBuilder::main_window,
EVENT_GROUPS in default_events, and raw event codes from the engine).
Newer rustc checks enum construction when debug assertions are on, so a
debug build of the Sciter UI aborts on startup with "trying to construct
an enum from an invalid value 0x84". Release builds never had the check.
Turn debug assertions off for sciter-rs in the dev profile only. CI's
Rust 1.75 accepts the override and has no such check, so it is a no-op
there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(linux): serve a greetd Wayland login screen over DRM
greetd starts its greeter with no XDG_SESSION_TYPE, so pam_systemd records
the session as Type=tty and the display server reads as x11. is_gdm_user only
knows gdm and sddm, so the DRM login-screen path never engaged at
cosmic-greeter and the greeter's --server captured over X11: a black stream.
Recognize a seat0 session with Class=greeter and Type=tty whose user has a
Wayland socket in /run/user/<uid> as a Wayland login screen, in
is_login_screen_wayland and in Desktop::is_login_wayland, so it takes the path
built for GDM and SDDM greeters. The compositor creates its socket after logind
registers the session, so a greeter first seen without one is asked again on
each refresh. drm builds only; console greeters (tuigreet, agreety) have no
socket and keep their current behavior.
Fixes#16891.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* fix(linux): drop the previous session's compositor env at a greetd greeter
Going straight from a user session to a greetd greeter kept that session's
WAYLAND_DISPLAY and bus address in Desktop, and the greeter --server was
started with them: an endpoint it is handed is never searched past, so the
compositor layout lookup failed and the uinput range fell back to the DRM
union. Clear every compositor variable when a greetd greeter is adopted.
Leave a forced display server alone, as IS_X11 and is_x11_for_drm already
do, and log a failing loginctl instead of taking it for "not a greeter" in
silence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* fix(linux): take a greetd greeter for Wayland only from its socket
Type=tty names no display server, and the protocol fallback reads the
service's own XDG_SESSION_TYPE, not the session's. A service that inherited
XDG_SESSION_TYPE=wayland therefore made is_login_wayland() answer yes for a
tty greeter that was never adopted, skipping the socket check and a forced
display server. Start a tty greeter from x11 so only
adopt_wayland_tty_greeter can make it Wayland.
Also log an unexpected error reading the runtime directory; a directory
that does not exist yet stays a silent "no socket".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* fix(linux): adopt a greetd greeter when Wayland is forced
adopt_wayland_tty_greeter skipped any forced display server, so with
RUSTDESK_FORCED_DISPLAY_SERVER=wayland the greeter stayed on the x11 path
it now starts from, while its --server was told Wayland. Only a server
forced to x11 is the operator overruling the socket; one forced to Wayland
agrees with it, so adopt the greeter and clear the previous session's
compositor variables as for an unforced one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* fix(linux): keep a greetd greeter off the Wayland login gate when x11 is forced
With RUSTDESK_FORCED_DISPLAY_SERVER=x11, adopt_wayland_tty_greeter leaves a
greetd greeter on the x11 path, but is_login_screen_wayland() still took it
for a Wayland login screen. The login gate in connection.rs then refused the
connection while the DRM verdict was not Available, where master accepted it.
The tty-greeter branch now checks the same override; the GDM/SDDM test is
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: add hover behavior to request approval submenus
* fix: use a single hover highlight in legacy popup menus
* fix: avoid duplicate menu tab stops and submenu hover flicker
* fix: skip duplicate focus stops in switch and radio menus
* fix: allow diagonal pointer movement into submenus
* drm: stop repeating an unchanged frame after 3 beats
A still screen sent one identical frame per second for ever (REPEAT_EVERY).
Each of those counts as a real frame in video_service and restarts
static_refresh, so its budget never ran out and the encoder kept re-encoding
the cached frame, about 26 times a second with h264_vaapi. x11 and pipewire
drop a repeat for good.
Send at most MAX_REPEATS (3) repeats after the last new picture. A hardware
encoder switches after 3 failed encodes, so those cover the failed-encode
case the beat is for. A new picture starts the count over.
* drm: notice a new subscriber while the screen is still
Once a still screen has used up its repeats and the static refresh
budget, the WouldBlock arm no longer reaches handle_one_frame or a
static refresh encode, where a subscriber added without a refresh
(the mobile display switch) is noticed. That client got no picture
until the screen changed.
Run the same new-subscriber check at the top of the WouldBlock arm.
* drm: switch for any new subscriber on a still screen
In the WouldBlock arm nothing is sent after the check, so also switch
when the last active subscriber has left and a new one is pending:
the snapshot would promote it with no frame ever following. Log the
expected event at trace level.
* drm: switch for a new subscriber once the encoder has run
Nothing is sent in the WouldBlock arm, so the switch is needed only
when the encoder already produced frames, even if the subscribers
that got them are gone: otherwise the new one gets no keyframe until
the screen changes. A fresh encoder needs no switch. Log the expected
event at trace level.
* drm: leave the new subscriber check in video_service as it was
This reverts the video_service.rs changes of fd872773b, de139dc97 and
ea73ed51f. A subscriber added without a refresh to a still screen is not
specific to drm, so it does not belong in this change.
Give the Start service caller one second to arrange its replacement and exit before retrying main IPC startup. Keep this workaround in the server startup thread and preserve the existing termination path when the retry fails.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): guard staged installer directories during handoff
Create restrictive staging directories and retain non-delete-sharing handles while CMD consumes packages, scripts and shortcuts. Adapt output paths and cleanup, and add a directory-replacement regression.
Validated with 11 focused Windows tests. Directory protection remains tied to the lifetime of the creating process.
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(windows): streamline installer directory guard tests
Keep installer-file tests in their original inline module and remove the duplicate CMD-copy check from the directory replacement regression. Preserve directory protection and existing handoff coverage.
Validation: Windows library test compilation, four focused tests, rustfmt and git diff --check.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): use plain rd for empty staging directories
Remove the inapplicable /q switch from non-recursive staging-directory cleanup. Keep recursive deletion confined to the runner directory.
Validated with Windows library test compilation, four focused tests and isolated CMD probes for owner termination, nonempty staging directories and exit-code preservation.
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(windows): pin installer staging directory permissions
Check effective ordinary-owner directory rights independently of the sharing guard. Clarify guard lifetime and the Rust and CMD cleanup responsibilities.
Validation: Windows library test compilation, four focused tests, rustfmt and git diff --check.
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
get_audio_input() searched HOST.devices(), which lists input and output
devices together, and took the first device whose name matched. CoreAudio
gives a USB microphone with a headphone jack two devices with the same
name, and the output one can come first. RustDesk then picked the output
device, default_input_config() failed with StreamTypeNotSupported, and the
voice call sent no audio from the macOS side ("Failed to get default input
format" every second).
Search HOST.input_devices() instead, so only devices that support input
can match the configured or default input name.
Signed-off-by: xpehter <32315536+xpehter@users.noreply.github.com>
* fix(windows): drop the named-pipe handoff that F-Secure blocks
Since #16562 the elevated cmd.exe waited on
`set /p RUSTDESK_HANDOFF=<"\\.\pipe\rustdesk-install-<uuid>"` until the
unelevated parent released the staged files' DELETE handles. F-Secure's
behavior detection closes that cmd's end of the pipe, so installation and
Stop service fail with "The pipe is being closed. (0x800700E8)"
(ERROR_NO_DATA) and the BAT never runs (#16840, #16842).
The handles exist only to delete the staged files when UAC is cancelled.
Tampering is prevented by the DACL set at creation, which grants the
unelevated owner read/execute only, and that is unchanged. Release the
handles before launching cmd and run the BAT directly. A cancelled UAC
prompt now leaves the staged BAT and shortcut in the protected directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* docs(windows): correct the handoff comments after dropping the pipe
The cleanup handle is no longer held through the UAC prompt, and the files
stay protected by their DACLs together with the parent directory's
permissions, not by the DACL alone. Describe the F-Secure failure as what
was observed: cmd's end of the pipe closed before the release signal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkiaGp2k8okvs4MpCUq
* fix(windows): stage installer inputs in guarded directories
Create private staging directories atomically with restrictive ACLs and retained handles that forbid delete sharing. Reuse the existing handle-based cleanup and cover BAT, shortcut and verified-update inputs with Windows regression tests.
Known limitation: terminating the RustDesk caller before CMD exits still releases the directory guards and can leave staging directories behind.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): clean up staging directories after batch execution
Remove each batch's own empty staging directories after its files, including when uninstall has terminated the Rust owner. Preserve the installer exit code and cover standalone batch cleanup with a regression test.
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(windows): create protected output directory in handoff test
Exercise the production directory constructor under the retained staging-directory guard while preserving the existing elevated and unelevated assertions.
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(windows): avoid protected installer fixture leftovers
Retain the production directory ACL check with native fixture cleanup. Remove the replacement and handoff tests that leave protected files after unelevated runs.
Signed-off-by: fufesou <linlong1266@gmail.com>
* revert(windows): restore installer handoff to 8ea7251e3
Restore the tracked tree to 8ea7251e35.
Remove the later guarded staging directories and cleanup/test changes,
while retaining the original direct CMD launch without a named pipe.
Reverted changes:
- 6f71c7d3a0
- a188f7b8f1
- d7fb4aff5f
- 6ace789b13
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
The Windows clipboard setter empties the clipboard and writes each format
in order, stopping at the first one that fails. With the marker written
last, a failure after an earlier format succeeded could leave unmarked
content on the clipboard. RustDesk could then read it back as a fresh local
copy and return it, causing the controller to empty its clipboard again.
For applications such as Excel, that destroys the application's copy state.
Write the marker first on Windows so a partial write stays identifiable
as RustDesk-owned. Other platforms keep appending it, so their payload
order is unchanged.
This covers a format failure before the marker is written, not a marker
that is written but later missing or unreadable.
Refs #16291
* fix(server): ignore spaces around IP whitelist entries
* fix(server): parse the IP and ID whitelists the same way, and fail closed
Both checks now read their option through one parser that accepts the
separators the settings dialogs accept (commas, semicolons, whitespace) and
drops empty entries. The IP whitelist split only at commas, so an entry
with a space around it, or a list from custom client settings separated by
semicolons or newlines, matched no address and locked the listed ones out.
A list is off only when the option is empty or holds only commas, which is
how the dialogs store a cleared list. Any other value without entries, such
as a lone space, now blocks every peer for the ID whitelist too, where it
used to let everyone in.
* fix(flutter): skip empty entries in the IP whitelist dialog
A leading or trailing comma gave an empty entry that the dialog rejected
as an invalid IP. Drop empty entries before the check, as the ID whitelist
dialog already does.
* fix(flutter): reject whitelist input that holds only separators
Dropping empty entries made the IP dialog save `,` or `;` as a cleared
whitelist, which turned the restriction off; before, that input failed
the IP check, and the server reads the same text as a list that allows
no one. Both dialogs now split through whitelistInputEntries, which
returns null for separator-only input, and show "Invalid IP" or "Invalid
ID" for it. Empty input and the Clear button still clear the whitelist.
* fix(flutter): keep a whitelist that lets no one in when its dialog is saved unchanged
A stored whitelist such as " , " is on but names no entry, so the server
lets no one in. Its dialog shows it as blank lines, and OK pressed
without an edit trimmed those to an empty field and saved a cleared
whitelist, lifting the restriction.
Both dialogs now take the shown text from whitelistDialogText and, when
the field is unchanged and whitelistLetsNoOneIn says the stored value
lets no one in, close without saving. Clearing the field and the Clear
button still clear the whitelist. The tests cover the load, unchanged
OK and save path for both cleared values (',' and '').
* style(flutter): drop a doubled blank line in whitelist_input.dart
* fix(ui): keep a whitelist that lets no one in when the Sciter dialog is saved unchanged
The Sciter IP whitelist dialog shows " , " as blank lines, trimmed the
submitted text before comparing it with what it showed, and so saved an
unchanged deny-all whitelist as a cleared one. The submitted text is now
compared first, as the Flutter dialogs do.
* test(flutter): name the round-trip test after the helper it checks
* fix(server): narrow the change to ignoring spaces around IP whitelist entries
As the review suggests, only the original fix stays: IP whitelist
entries are trimmed, so "192.168.1.0/24, 10.0.0.1" admits 10.0.0.1.
The ID whitelist keeps master's parsing and its "empty means allow"
behavior. The Flutter dialogs and the Sciter UI are back to master,
and the shared parse_whitelist and the dialog helpers are gone. The
trim lives in ip_whitelist_entries so that one regression test can pin
it. A whitespace-only IP whitelist still blocks everyone, as on master.
* Refactor IP whitelist entries handling and comments
Updated comments for clarity and adjusted whitespace handling in IP whitelist entries.
---------
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
sysinfo's Cpu::frequency() is in MHz: on Linux it is scaling_cur_freq
(kHz) divided by 1000, or the "cpu MHz" line of /proc/cpuinfo, and on
macOS its documentation says MHz. get_sysinfo divided it by 1024 before
printing GHz, so a CPU at 2885 MHz showed as 2.82GHz instead of 2.89GHz
in the device info sent to the server.
Require a dedicated child success exit code so an unmatched test filter cannot pass the GUI startup regression. Replace the ACE type binding with a local constant and drop the unused SystemServices feature.
Signed-off-by: fufesou <linlong1266@gmail.com>
* Only relay to addresses this peer offered, and bound relay uuids
A RequestRelay forwarded by the rendezvous server carries the relay
address and uuid the requester filled in, and the controlled peer
dialed that address as given. A genuine controller only echoes the
relay this peer put in its own PunchHoleSent or LocalAddr, so remember
those (plus the relay this peer would pick by itself) and drop any
request that names something else, before resolving or connecting.
Relay uuids are opaque pairing keys that arrive from the network and
are sent on to the relay, so both the controlled side (RequestRelay)
and the controller (RelayResponse) now refuse ones that are empty,
longer than 128 bytes, or carry anything but ASCII alphanumerics, '-'
and '_'. This matches the check hbbs applies before forwarding.
Known limits: a WebRTC-failure fallback that follows a peer-initiated
relay whose address hbbs rewrote for the LAN is refused; a refused
request gets no reply, so the controller sees its relay attempts time
out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkh9Gd5dwGW8vL9JNV8
* Expire reported relays after ten minutes
A relay dropped from the configuration or no longer offered by the
rendezvous server stops being an accepted RequestRelay destination once
its last report is ten minutes old. Reporting it again restarts the
clock. The list is shared by all mediators, so it is not cleared when
one starts.
The comment now says where the entries come from and that the list only
bounds where a request can send this peer; it does not authenticate the
request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkh9Gd5dwGW8vL9JNV8
* Test relay expiry through the relay request check
The expiry tests only called the cache helpers, so a request check that
stopped honouring the expiry would still pass them. check_relay_request
now takes the time like the helpers do, and the request test asserts an
expired relay is refused and accepted again once re-reported. Passing a
later time instead of recording an earlier one avoids Instant underflow
on Windows hosts up for less than ten minutes.
Also formats this PR's own relay check and expiry tests with rustfmt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAxkkh9Gd5dwGW8vL9JNV8
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(lang): pick Hebrew and Indonesian from legacy Android locale codes
The Android service translates its notifications and floating window
menu with Locale.getDefault().toString(). Android's Locale reports
Hebrew and Indonesian with their legacy ISO 639 codes, iw_IL and in_ID,
unless both the device and the app's target SDK are Android 15 or
later. resolve_lang took "iw" and "in" as language codes, found no
table, and fell back to English.
Map "iw" to "he" and "in" to "id", next to the Kazakh and Georgian
mappings.
* fix(lang): say which Android versions report the legacy codes
* fix(ipc): authenticate Windows named pipe servers
Restrict main and service pipe instance creation and authenticate the
connected server before exchanging IPC data. Open clients with data access
that excludes FILE_CREATE_PIPE_INSTANCE.
Require SYSTEM ownership for service channels. For main IPC, also accept a
same-user server with a restricted DACL, matching session and executable,
and sufficient elevation. This includes the UI hosting main IPC while the
service is unavailable, without relying on installation or service config.
Elevated clients reject non-elevated servers, and SYSTEM clients reject
user-owned pipes. New clients reject old permissive listeners until those
processes restart on the fixed version.
Add native Windows regression tests for instance-creation permissions,
DACL rejection before application data, and normal client communication.
Signed-off-by: fufesou <linlong1266@gmail.com>
* refactor(ipc): clarify Windows pipe security policy
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(ipc): bound the Windows server rejection test
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(ipc): bound the complete Windows client communication test
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): keep portable GUI alive on IPC startup failure
Only attempt GUI fallback hosting when the main IPC pipe is absent. Bind its listener before marking hosting active or starting rendezvous, and return on bind failure without terminating the GUI.
Preserve IPC authentication and explicit server-process behavior. Cover rejected listeners, bind collisions, and standalone IPC communication with Windows regression tests.
Signed-off-by: fufesou <linlong1266@gmail.com>
* refactor(windows): simplify portable IPC startup fallback
Rely on exclusive listener creation instead of a separate pipe-presence probe. Keep GUI bind failures recoverable and require a successful bind before starting host services.
Retain the subprocess regression for bind-failure process survival, removing redundant GUI tests and their helper parameters and delay.
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(port-forward): keep the KCP handle alive and send a close reason
A port forward whose connection was won by a UDP punch (KCP) died right
after login. `Client::start` returns, next to the stream, the KCP handle
that drives the stream's UDP I/O, and both login functions dropped it on
return, so the stream failed with "stream closed" on first use. The handle
now goes with the stream to whichever task owns it: the raw pipe's task,
`run_legacy`'s task, or `tunnel_loop`.
Closing such a stream needs care as well. KCP's FIN is sent by the
handle's own tasks and dies with the handle, so the peer would hold the
connection until its own timeout. A KCP tunnel closed from this side now
sends a close reason first, as the remote session does. The port-forward
window's io_loop passes Close on to every mapping and waits 100ms before
its runtime is dropped, so the tunnels get to send it; a login that
receives the Close now ends instead of swallowing it.
Known limits: the raw pipe (a peer without mux) cannot carry a close
reason after `set_raw`, and exits during login send none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(port-forward): say what the close-reason send bound covers
The bound on sending the close reason keeps that step inside the window's
wait, but a data send already in flight in another arm of `tunnel_loop` is
not bounded by it and holds the close until it returns. The comment said
the whole close fit; say which part does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The LaunchDaemon ran `/bin/sh -c .../Contents/MacOS/service`, so macOS
attributes the background item to /bin/sh: Login Items shows "sh", and
AssociatedBundleIdentifiers cannot link it to the app because the
launched executable is not signed by the app's team. Start the signed
service binary itself.
sh exec'd the single command anyway, so the running process is the
same. App names are limited to letters, digits, '-' and '_', so the
path never needed shell parsing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(windows): support secure installer temp junctions
Preserve Windows Temp when persistent ACLs are available, validate resolved junction targets, and fall back to ProgramData only when the target volume lacks ACL support. Guard parent directories while creating protected installer files and cover the Windows sharing behavior with regression tests.
Refs rustdesk/rustdesk#16841
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): simplify installer temp junction fallback
Signed-off-by: fufesou <linlong1266@gmail.com>
* test(windows): cover installer staging path selection
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(lang): correct typos in the Indonesian translation
Fix misspellings such as "kd", "halam", "ketik", "kostum" and
"kadaluwarsa", and the capitalization of "Anda" and "kamu" in the
middle of a sentence. Keys and placeholders are unchanged.
Signed-off-by: deagus <dedeaskyt121@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(lang): translate the Indonesian elevation username hint
The value was "panduan_elevasi_nama_pengguna", a translated copy of the
key rather than a sentence, and it showed as the hint of the username
field in the elevation dialog. Translate the English text instead. The
double backslash is a literal one, as in en.rs.
Signed-off-by: deagus <dedeaskyt121@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Signed-off-by: deagus <dedeaskyt121@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#16827 made FrameRaw copy each frame while the Image is still valid, and
then copy it a second time into the capturer in take(). Swap the owned
buffer into dst instead: both callers own dst and only read it until
their next call, so the two buffers alternate and take() holds the lock
for constant time. A taken frame is copied once, as before #16827.
Release the buffer whenever capture is enabled or disabled. FrameRaw is
global, so it would otherwise keep a full frame after capture stops.
Note why the would_block_if_equal check never fires, so that nobody
revives it by accident.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Flutter 3.44's Material 3 TextButton gives icons inside it an 18px default
(3.24.5 has none, so they stay 24px). The approval row sits in a TextButton,
so under M3 on 3.44 its arrow renders at 18px while the inset is computed
for 24px, and the test's ThemeData() case fails by 3px on the macOS arm64
job, which runs the Dart tests on 3.44. The app sets useMaterial3: false,
where neither version shrinks the icon; the three M2 cases still pin the
alignment and pass on both 3.24.5 and 3.44.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every portal handshake failure is tagged and gets its own message, so the
untagged errors that still reach this fallback at login are internal ones
(GStreamer init, a poisoned lock). Reporting them as "x11 expected" sent
users off to switch to X11 for faults X11 does not fix, and hid the real
error. Send the error itself, as the Ubuntu 21+ branch already does.
No regression test: CI runs on Ubuntu 24.04, where map_err_scrap takes the
Ubuntu branch and returns the raw error both before and after this change.
Refs #16829
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
O FrameRaw guardava apenas o ponteiro do ByteBuffer do Image recebido via
JNI. O Kotlin fecha o Image logo após onVideoFrameUpdate, devolvendo o
buffer ao ImageReader; quando a thread de vídeo chamava take(), o
SurfaceFlinger já podia estar desenhando o quadro seguinte no mesmo
buffer, gerando regiões pretas/parciais na imagem remota em qualquer codec
(visível em Android x86, ex.: Bliss OS 13 em Intel UHD).
Agora update() copia o quadro para um buffer próprio enquanto o Image
ainda é válido, e take() copia desse buffer. O áudio usa a mesma
estrutura e passa a ter a mesma garantia.
* feat: add connections without sending passwords
Add Without password actions and pass the selection through desktop,
mobile and Web sessions. Omit the password when constructing LoginRequest,
preserve saved credentials, and reuse the existing approval and OS login
dialogs. Keep protocol and controlled-side authentication changes separate.
* fix: preserve login method across session windows
* fix: skip password persistence for consent connections
* refactor: unify request approval menus and naming
* chore: move request approval translation key to the end
* i18n: translate request approval menu label
* fix: align request approval menu layout and icons
* fix: avoid duplicate focus stops in request approval menus
* fix: distinguish request approval submenus on mobile
* fix: open overflowing submenus to the left
* Revert "fix: distinguish request approval submenus on mobile"
This reverts commit 7f0e50d4e4.
Update the text for "Forget Password" to "Clear saved password" to avoid confusion with password recovery. Both Flutter and legacy Sciter UIs use this key and share the updated translations.
Keep the existing translation key, UI calls, and password-clearing behavior unchanged. Leave Italian translations untouched.
Signed-off-by: mblbr <150243082+mblbr@users.noreply.github.com>
The iOS launch screen and the Flutter view controller's storyboard both
hard-coded a pure white background, so the app flashes bright white on
every cold start, even in dark mode. Use systemBackgroundColor so the
launch/loading screen is black in dark mode and white in light mode.
* fix(ci): build macOS arm64 with Flutter 3.44
Flutter 3.24.5's macOS engine can crash in attributedSubstringForProposedRange
with an out-of-bounds range on an empty string (#16817). The fix
(flutter/flutter#153157) landed after 3.24 and was never cherry-picked, and
3.44 has the bounds check. So macOS arm64 now builds with Flutter 3.44.9, the
same way Windows arm64 does: the 3.44 bridge artifact and the shared source
patch script. The script uses GNU `sed -i`, which BSD sed on macOS rejects,
so the step puts GNU sed first on PATH. The 3.24 dropdown patch is skipped,
because 3.44 already has that fix.
macOS x86_64 stays on 3.24.5: 3.44's FlutterMacOS needs macOS 10.15 there,
and the build targets 10.14. The Dart tests move to the x86_64 job, so CI
still runs them against the 3.24.5 sources that most platforms ship.
Refs #16817
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ci): keep CocoaPods on macOS arm64 and run Dart tests on both arches
Flutter 3.44 turns on Swift Package Manager by default. On the first
`flutter build macos` it would migrate Runner.xcodeproj and Runner.xcscheme,
and build file_selector_macos, path_provider_foundation, url_launcher_macos
and video_player_avfoundation through SwiftPM while the other 12 plugins stay
on CocoaPods. If the migration fails, the build fails. All 16 macOS plugins
still ship a podspec, so turning SwiftPM off keeps arm64 on the CocoaPods-only
path the 3.24.5 build uses, and this change only swaps the engine.
The Dart tests now run on both macOS jobs: x86_64 covers the 3.24.5 sources
most platforms ship, and arm64 covers the patched 3.44 sources it ships.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci: update comments that still call Windows arm64 the only Flutter 3.44 build
The FLUTTER_WINDOWS_ARM_VERSION note, the Windows source-patch step and the
header of apply_flutter_3.44_source_patches.sh still said Windows arm64 is
the only Flutter 3.44 target. They now mention macOS arm64 too, and the
script header records that macOS needs GNU sed for its `sed -i` calls.
Comments only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: make test_RustDesk_interval_sleep deterministic
The test recorded tick times as wall-clock seconds, rounded to the nearest
second, and asserted all 5 were unique. If the runner stalled for close to a
second, two correct ticks rounded to the same value and the test failed
(macOS arm64 CI on #16821: left 5, right 4).
It now runs on tokio's paused clock and checks what ThrottledInterval
guarantees: ticks are at least 0.9 * period apart. A plain tokio interval
bursts its missed ticks with no gap, so a lost throttle still fails. This
needs tokio's test-util feature for the existing dev-dependency. It adds no
crates, so Cargo.lock is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Limit oversized remote clipboard text on Android
* Add write permission
* Increase MAX_CLIPBOARD_TEXT_CODE_UNITS to 1MB
* fix: revert unrelated workflow permission changes
* fix(android): address review feedback on clipboard size and exception handling
- Replace UTF-16 character count check with a serialized Parcel size check capped at 800 KB, leaving headroom for Android Binder transaction overhead.
- Wrap setPrimaryClip() in a try-catch to safely absorb direct and runtime-wrapped Binder exceptions (e.g. TransactionTooLargeException) without crashing.
- Assign lastUpdatedClipData only after a successful clipboard write to preserve consistent synchronization bookkeeping on failure.
Fill install-directory-not-empty-tip (added in #16783) for every
language, including it.rs at the maintainer's request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(windows): warn before removing installation directory
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): show inline installation directory warning
Signed-off-by: fufesou <linlong1266@gmail.com>
* refactor(windows): simplify installation directory warning
Key the warning widget by its path and remove the optional widget test suite.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): limit installation warnings to the selected directory
Share install-directory-not-empty-tip between inline and confirmation warnings, and remove the separate previous-installation warning.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(windows): skip directory warnings when reinstalling in place
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* Clarify auto login requirements in Portuguese
Updated the auto login message to clarify requirements.
* Fix translation for terminal admin login tip
Updated the translation for the terminal admin login tip in Portuguese.
res/job.py sign_files exited 0 when a file was left unsigned: a sign
task that never reached "done" only broke out of the current directory,
and a failed download of the signed file was ignored, keeping the
unsigned original in place. Both now exit non-zero, so the Windows
signing steps fail the job.
Retry requests.get/post on connection-level errors with the same wrapper
the signing service's client uses, rewinding upload streams before each
retry. Tunable via HTTP_RETRY_MAX_TRIES and HTTP_RETRY_SLEEP_SECS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: restore readiness after service restart and macOS update
Wake rendezvous retry waits when a manual restart is requested. For app-only macOS updates with an in-process backend, wait for the old process to exit before relaunching the updated app so it cannot attach to the departing backend.
Signed-off-by: fufesou <linlong1266@gmail.com>
* test: cover service restart and macOS update readiness
Signed-off-by: fufesou <linlong1266@gmail.com>
* test: remove macOS relaunch script regression test
Signed-off-by: fufesou <linlong1266@gmail.com>
* docs: explain macOS update relaunch ordering
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix: isolate macOS update relaunch process group
Signed-off-by: fufesou <linlong1266@gmail.com>
* docs: clarify macOS relaunch process group isolation
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix: clarify macOS update restart failure
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* Build Windows custom client packers in CI
Verify payload signatures, build templates for all three Windows architectures, and include the packers in the unsigned release archive.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(ci): preserve unsigned Windows releases without signing
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(ci): align template packer DPI manifests
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>