Commit Graph
11570 Commits
Author SHA1 Message Date
Rafał Sikora 398364fa18 fix(ios): keep background music playing during remote sessions (#16660)
Configure the iOS audio session with the ambient category so remote
playback mixes with other apps while respecting Silent mode and lock.
Log configuration errors and leave audio activation to playback.

Signed-off-by: Rafal <mrsikorarafal@gmail.com>
2026-10-23 11:23:08 +08:00
rustdeskandClaude Opus 5.5 828d1101af chore(deps): pin hwcodec to rustdesk-org/hwcodec#47
Move the locked hwcodec revision from febec27 to ec38eba, the merge of
rustdesk-org/hwcodec#47: GPU query failure handling, Windows resource
handling and recoverable fragmented MP4 recording. hwcodec's own
dependencies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:48:28 +08:00
RustDeskandClaude Opus 5.5 3ec041644d fix(linux): gtk sudo shows no false "Sorry, try again." and retries with sudo-rs (#16652)
* fix(linux): no false "Sorry, try again." in gtk sudo after a PAM info line

The gtk sudo dialog takes the password before sudo starts and drops it
when sudo prints a non-prompt line first, such as pam_fprintd's "Place
your finger on the fingerprint reader". The real password prompt then
came up labelled as a retry, "Sorry, try again.", although no password
had been sent, so users took it as a rejected password.

Treat the next prompt as a first prompt when the typed password is
dropped. The dialog comes back prefilled and without the error.

Keeping the password and sending it to the next prompt instead is not
safe: when sudo authenticates without a prompt (fingerprint, NOPASSWD),
use_pty, the default since sudo 1.9.14, holds the tty in raw mode while
the command runs, so any output line ending in ':' would receive it.

Fixes #16647

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(linux): let gtk sudo retry after sudo-rs rejects a password

sudo-rs reports a rejected password with "sudo: Authentication failed,
try again." and then asks again. The line starts with "sudo:", so
ui_parent treated it as fatal: it showed it in an error dialog and
exited instead of offering a retry, as it does after classic sudo's
"Sorry, try again." (the second log in #16647).

Exempt exactly that line from the fatal check. It then goes through
the ordinary non-prompt branch, so the held-password drop rule still
applies. sudo-rs's final "maximum N incorrect authentication attempts"
stays fatal, and so does every other sudo:/su: line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:27:35 +08:00
RustDeskandClaude Opus 5.5 d97351ad73 Custom websocket port (#16658)
* feat: let custom client settings set websocket-port

A domain endpoint becomes ws(s)://<domain>/ws/id with no port, so a
WebSocket reverse proxy that cannot listen on 443 (or 80) is
unreachable. hbb_common adds websocket-port for that; list it in
KEYS_SETTINGS so default-settings and override-settings carry it.

The hbb_common bump also brings in 20d5019 (quiets smithay client
toolkit logs, rustdesk/hbb_common#620).

https://github.com/rustdesk/rustdesk-server-pro/discussions/1029

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update hbb_common for the websocket-port warning throttle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update hbb_common: match websocket-port only against configured servers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pin hbb_common to the merged websocket-port change

rustdesk/hbb_common#626 is merged as edefa08. The range from master's
pin 5d52530 also includes 20d5019 (quiets smithay client toolkit logs,
rustdesk/hbb_common#620).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:58:00 +08:00
fufesou 2b6fedfc8e fix(windows): protect installer handoff and use native service control (#16562)
* fix(windows): use native APIs for manual service control

rustdesk/rustdesk#16557

Replace elevated service-control batch scripts with SCM operations
and native tray shortcuts. Preserve SYSTEM configuration import
and propagate helper failures.

* fix(windows): preserve service state during manual control

Keep existing service registration and synchronize failed operations with SCM state. Run the installed legacy importer directly as SYSTEM, bound its exit waits, and keep auxiliary cleanup errors separate from service lifecycle results.

* fix(windows): preserve legacy service import without debug privilege

* fix(windows): replace installer certutil and findstr

Use native SHA-256 verification and shortcut decoding, and execute the protected batch script in a single cmd process. Preserve protected staging, verification receipts and failure exit codes.

Restore the master service-control flow and remove the separate SCM and Task Scheduler implementation to keep the antivirus fix focused and preserve legacy configuration import.

Validation: Windows Rust library build, nine focused tests, and isolated single-cmd success, failure, tamper rejection and cleanup checks passed. Bitdefender validation remains pending.

* fix(windows): preserve installer helper path compatibility

* fix(windows): preserve caret-only installer helper paths

* fix(windows): protect installer data and use native service control

Stage installer scripts, shortcuts and verified updates with restricted ACLs
before elevation. Remove the elevated portable-verifier launch while keeping
certutil and findstr out of the shared installer handoff.

Use the installed Flutter runner for native normal-service create/start and
stop/delete operations. Preserve existing registrations on repeated setup,
legacy configuration import, and compatibility with older installed runners.
Report installer failures and document the security and compatibility reasons.

Validation: Windows Rust test build and Release runner build passed; six
isolated service checks and runner exit-code checks passed. Focused Rust
installer/update tests passed 8/9; the directory-cleanup ACL test also fails in
the earlier binary. Final service lifecycle and Bitdefender testing is pending.

* fix(windows): clean staged installer files on UAC cancellation

Retain DELETE handles until the elevated cmd connects to a local pipe, then
release the handles before allowing the BAT to run. This preserves protected
staging while allowing cancellation cleanup and cmd file access.

Handle interrupted handoffs, supply the byte-count pointer required on
Windows 7, and explain early service dispatch in the C++ runner. Keep the
staging tests inline with their implementation.

Validation: Windows Rust test build, focused installer-file and handoff
tests, rustfmt, and staged diff checks passed. The pre-existing directory
ACL test failure is unchanged. Actual UAC cancellation, Windows 7 runtime,
and final-build Bitdefender validation remain pending.

* fix(windows): roll back new service registration on start failure

Delete a registration created by this attempt if startup fails, while preserving existing services and already-running success. Keep the original startup error and report rollback failures separately.

Validation: eight isolated service checks and the Release Flutter runner build passed. Real service startup failure and final-build Bitdefender validation remain pending.

* fix(windows): continue uninstall after service stop errors

Log an unsuccessful stop and still request deletion so the uninstall
script can reach its existing taskkill and cleanup commands. Treat an
already marked service as a satisfied deletion request, while preserving
other deletion errors and the normal stop wait.

Validation: all twelve isolated service-control checks passed, including
already-marked, stop-rejected, stop-timeout and deletion-denied cases.
The Release Flutter runner build and existing argument/exit-code checks
passed. Real SCM failure scenarios and Bitdefender remain unverified.

* fix(windows): finish installer setup before service startup

Complete SAS policy and printer setup before native service activation so a startup failure cannot skip independent installation steps. Preserve the startup error, rollback and legacy command sequences, and document uninstall_service's continuation result.

Validation: Windows Rust test compilation, four isolated command cases and two existing installer handoff tests passed. CodeRabbit completed with no findings. Actual installer, SCM and Bitdefender validation remains pending.

* fix(windows): use native service control for Sciter

Share the existing native service operations with Sciter and dispatch
--service-control before UI initialization. Keep Flutter's entry point and
the legacy commands for a different installed executable unchanged.

Validated the Windows x64 Sciter build, Flutter-feature test build,
x86 C++ bridge compilation, and real EXE/BAT error propagation. Final
Bitdefender installation and service lifecycle validation remain pending.

* fix(windows): propagate negative service-control exit codes

Treat every nonzero service-control exit status as a failure so crash and loader error codes cannot be reported as success. Verified six real cmd.exe exit-code cases; CodeRabbit review completed with no findings. Actual service crashes were not exercised.

* fix(windows): handle UAC cancellation and scope service rollback

Preserve the typed elevation error and its diagnostic details so canceled UAC prompts do not show an installation-failure dialog.

Use create-service for normal installation and create-service-with-rollback for manual Start Service. Retain a new registration after a normal installation startup error; roll back only the manual path that restores stop-service=Y.

Validation: Windows Rust test compilation, Release Flutter runner and Sciter builds, cancellation/error-chain checks, isolated service-result cases, and real executable argument/exit propagation checks passed. Actual UAC cancellation and Bitdefender were not rerun.
2026-10-22 13:34:49 +08:00
CatzTimesandCatzTimes f010d33ca6 fix(android): wheel scrolling from the controller does not reach Android peers (#16409)
The controller sends a wheel tick as a MouseEvent whose type bits are
MOUSE_TYPE_WHEEL (src/common.rs) with the scroll delta in y, but
InputService only handles the legacy WHEEL_DOWN/WHEEL_UP masks
(523331/963) that current controllers never send. As a result the
mouse wheel does nothing on every Android controlled device.

Handle MOUSE_TYPE_WHEEL with the same stroke emulation the legacy
masks use, as a purely additive branch: a 90px vertical stroke
(MOUSE_WHEEL_STEP, three quarters of the legacy WHEEL_STEP - the full
120px felt too far per tick to reviewers on real devices) over
WHEEL_DURATION at the current cursor position, with the same top-edge
guard and queue consumption, mapped with the controller's sign
convention (y < 0 = wheel down; y == 0, a horizontal tick, is
ignored). The branch is gated to API 24+ like the rest of the gesture
path - below that, legacy devices take the Shizuku backend or a
no-op, not this class. The legacy WHEEL_DOWN/WHEEL_UP implementations
are untouched.

Community reports matching this gap on modern devices: #9321 (Pixel
8, Android 14) and #1466 (Android tablet).

Signed-off-by: CatzTimes <CatzTimes@users.noreply.github.com>
Co-authored-by: CatzTimes <CatzTimes@users.noreply.github.com>
2026-10-22 11:41:56 +08:00
Leo Camus bd92623972 fix(linux): match the xrandr output name at the start of its line (#16590)
The pattern resolutions() and current_resolution() build from the
output name is not anchored, so DP-1 also matches the eDP-1 line. On a
laptop where xrandr lists eDP-1 first, the external DP-1 reports the
built-in panel's current mode and mode list.
2026-10-21 18:59:13 +08:00
21pages 4e93a41ecc Tune MediaCodec static refresh budgets (#16579)
* Tune MediaCodec static refresh budgets

Use H.264 budgets 50/20/20/10 and H.265 budgets 90/20/20/20 from MT6855 1080p measurements with early stopping at 80% of observed-best improvement, not full convergence.

Retain the existing H.265 low-quality budget because the line-pattern sample did not confirm convergence within 150 repeat calls. Keep the existing ten latency-draining repeats, other backends, and refresh timing unchanged.

Extend output-budget and backend-selection regression coverage. Static refresh tests pass with hwcodec (17 tests) and default features (16 tests).

* fix(video): retain the largest static refresh budget per frame
2026-10-21 16:02:32 +08:00
21pages ce54e33c71 fix(ffmpeg): handle QSV encoder stalls and undersized buffers (#16303)
* fix(ffmpeg): bound QSV encoder retries

Limit QSV frame submission to 100 retries and synchronization to 10
retries so persistent SDK warnings return an error to the caller. Retain
queued buffers when synchronization reaches its retry limit, and allow
later calls to retry without latching an error.

Wait 1 ms between retryable submission warnings on Windows: the original
av_usleep(500) becomes Sleep(0), exhausting the retry budget before even
a brief device-busy interval can end. Preserve non-Windows wait behavior
and the existing negative SDK result handling.

Validated against FFmpeg 7.1.1 on Intel UHD 730 with 42 fault/control
cases, synchronization-timeout recovery and close cleanup, real H.264
and HEVC encoding, and separate QSV hardware decoding. Four concurrent
1080p encoders completed 2400 frames without errors.

* fix(ffmpeg): handle undersized QSV HEVC buffers

Reserve at least three bytes per aligned pixel for HEVC output, following
Intel's documented workaround for underestimated bitstream capacity. Check
the size before assigning it to AVPacket's integer capacity.

Report negative synchronization results before reading output metadata or
draining the FIFO, keeping failed tasks owned until encoder close.

On Intel UHD 730, the existing code fails at input 242 with real desktop
captures; the patched code encodes and decodes all 1,200 inputs. Inputs and
PTS match, and the first 241 encoded frames are byte-identical. H.264 also
passes 300 frames, and restoring the small buffer verifies error handling.

* fix(ffmpeg): refresh QSV HEVC capacity after reset and shorten sync waits

* fix(ffmpeg): apply QSV buffer sizing to H.264

* fix: backport FFmpeg hybrid MP4 finalization fix

Backport FFmpeg commit 36896af64a9ecf6835758186e7161a2e954c364d so hybrid MP4 finalization writes the final moov before hiding the original fragmented index. Preserve recoverable fragments when the final index cannot be written.
2026-10-21 16:00:34 +08:00
RustDeskandClaude Opus 5.5 2016f48dd7 fix(file-transfer): keyboard navigation in the desktop file list (#16642)
* fix(file-transfer): keyboard navigation in the desktop file list

Up/Down never moved the selection in the desktop file transfer list. The
arrow keys fell through to directional focus traversal instead, which
moved focus out of the list and stopped type-to-search until the mouse
entered the list again.

Handle the navigation keys on the list, as the Sciter grid did:
- Up/Down move the selection one row; Shift extends or shrinks a range
  from its anchor.
- PageUp/PageDown go to the edge of the visible rows first, then a page
  further; Home/End go to the first/last row. Shift extends with each.
- Enter/numpad Enter opens the selected folder, like a double click.
- Left/Right are consumed so focus does not jump to the other pane.

Type-to-search now ignores control characters. On macOS and Linux Enter
carries "\r", which matched no name and cleared the selection before
Enter could open it.

Fixes #16635

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(file-transfer): keep default keys on focused file list children

The navigation bindings wrapped the whole file list, sort headers
included, so a header focused with Tab lost Enter to sort and the arrow
keys to move between headers: Enter ran the open-folder binding instead.

Handle the bindings only while the list itself has primary focus and let
keys on a focused child take their default path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(file-transfer): focus the file list when a row is clicked

The navigation keys only act while the file list holds primary focus.
Keyboard focus was handed to the list only when the mouse entered it,
so after Tab moved focus to a sort header, clicking a row left the
arrow keys inert until the mouse left and re-entered the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:50:38 +08:00
fufesou b44b71cfab Fix/download path validation (#16601)
* fix: validate download destinations and peer filename rules

Reject unsafe or linked download destinations and validate empty-directory
paths before creating them. Bind overwrite approval to accepted transfer
entries and propagate receiver errors to the UI.

Validate remote listings using the peer's filename rules so Unix names
remain browsable on Windows. Reject locally unsafe basenames before
starting downloads from either the Flutter or legacy UI.

Finish the previous file before validating the next digest so rejecting
the next destination preserves the file already received.

Require confirmation before restarting interrupted downloads with stale
staging files. Preserve matching resumes and cover receiver recovery.

Retain explicit overwrite rejection for peers older than 1.1.10 that
cannot negotiate confirmation.

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

* comments

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

* fix: use async I/O for download preparation and resume

Use Tokio filesystem APIs for download setup and resume metadata checks. Run the Windows link-count query on the blocking pool and flush digest writes before returning.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-10-21 11:55:00 +08:00
mvtbh eef1096395 Update fa.rs (#16587) 2026-10-21 11:45:01 +08:00
8b61033178 Update pl.rs (#16588)
* Update pl.rs

Update Polish language (shortcuts screen).

* Update src/lang/pl.rs

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update src/lang/pl.rs

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update pl.rs

* Update pl.rs

Corrected typo.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2026-10-21 11:44:46 +08:00
Alex Rijckaert c29bef18bb Update Dutch translations for keyboard shortcuts (#16625) 2026-10-21 11:43:30 +08:00
fathur c6258f4166 Fix typo in Indonesian translation (#16630)
* docs(id): translate misuse disclaimer section

* docs(id): add Flathub download badge

* i18n(id): fix typo in Indonesian translation

* fix broken translation tuple in id.rs

* i18n(id): fix typo in Indonesian translation
2026-10-21 11:43:15 +08:00
bovirus 989beee17f Update Italian language (#16633) 2026-10-21 11:42:56 +08:00
Ricardo Simõesandgreptile-apps[bot] 4b1821d1ff Update pt pt translation + README.md to include Portuguese (Portugal) (docs/README-PTPT.md) (#16634)
* Update pt_PT.rs

* Add files via upload

* Update README-PTPT.md

* Update README-PTPT.md

* Update README.md to include new pt-PT (docs/README-PTPT.md)

* Update docs/README-PTPT.md

Corrected link libs/base

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update docs/README-PTPT.md

Corrected "área de trabalho" to "área de transferência"

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update pt_PT.rs

Fix api_server_connection_tip and api_server_empty_tip: translate from en.rs source

* Fix relative links in README-PTPT.md (remove extra docs/ prefix)

* Fix section anchors in README-PTPT.md to match PT-PT headings

* Fix typos and gender agreement in README-PTPT.md disclaimer

* Use European Portuguese terms in README-PTPT.md screenshot alt text

* Translate shortcut instructions from en.rs source

* Update pt_PT.rs

Align shortcut and API server strings with en.rs source (keep {} placeholders)

* Revert JumpLink to original value (out of scope for this PR

* Replace remaining Brazilian Portuguese terms in README-PTPT.md

Replaces leftover Brazilian Portuguese wording with European Portuguese
equivalents (e.g. "conosco" -> "connosco", "controle" -> "controlo",
"conexão" -> "ligação", "Você tem" -> "Tem", "amigável" -> "acessível",
"desktop" -> "ambiente de trabalho"), consistent with the terminology
already used in src/lang/pt_PT.rs.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-21 11:42:44 +08:00
Shaan Majid 3202d1764c chore(deps): update webpki-roots and rustls-webpki in Cargo.lock (#16639)
webpki-roots and webpki-root-certs 1.0.10 drop emSign ECC Root CA - G3, whose
eMudhra intermediates had their private keys compromised. rustls-webpki
0.103.16 rejects chains through those intermediates (RUSTSEC-2026-0382,
RUSTSEC-2026-0383); it needs rustls-pki-types >= 1.12.

Signed-off-by: Shaan Majid <70789625+shaanmajid@users.noreply.github.com>
2026-10-21 11:42:17 +08:00
fufesou c7f212df2c fix: warn about insecure custom API server connections (#16616)
* fix: warn about insecure custom API server connections

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

* fix: limit API server warning to HTTP addresses

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

* fix: resolve API warnings against effective server configuration

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

* fix: reuse common sync for API server previews

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

* fix: keep server settings usable on API preview errors

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-10-20 22:52:48 +08:00
fathur 7f5a401947 Update README-ID translations (#16626)
* docs(id): translate misuse disclaimer section

* docs(id): add Flathub download badge
2026-10-20 22:50:42 +08:00
Leo Camus bbffc1de5a fix: bracket a bare IPv6 ID server when deriving the API server (#16624)
With the API server field empty, get_api_server_ builds the URL from the
ID server as "http://{host}:21114". For an ID server entered as a bare
IPv6 address such as 2001:db8::1, which test_if_valid_server accepts,
increase_port leaves it unchanged and the result is
http://2001:db8::1:21114. That does not parse as a URL (InvalidPort), so
login and every other API request fail.

Wrap the address in brackets in that branch only. Hostnames, IPv4 and
"[addr]:port" take the same path as before.
2026-10-20 22:12:00 +08:00
Leo Camus 4b92080e3e fix(x11): keep the RandR monitors reply alive while iterating it (#16595)
DisplayIter::next_screen built the monitor iterator from the
xcb_randr_get_monitors reply and freed the reply on the next line.
The iterator's data pointer points into that reply, so next() read
every monitor's name atom, position and size from freed memory, and
xcb_randr_monitor_info_next() walked it too.

Keep the reply with the iterator and free it when moving to the next
screen or when the DisplayIter is dropped.
2026-10-20 17:29:30 +08:00
Chris21xandchg 0cbeb04730 Forward audio latency environment to Linux user server (#16597)
* Forward audio latency environment to Linux user server

Preserve PULSE_LATENCY_MSEC and PIPEWIRE_LATENCY when the root service spawns the per-user RustDesk server.

The systemd service defines these variables, but they were not included in the explicit environment passed to run_as_user(), causing the user server and connection manager to fall back to higher audio latency.

Forward the existing values without changing their defaults.

* Add regression test for Linux audio latency forwarding

---------

Co-authored-by: chg <chg@vis-ag.ch>
2026-10-20 17:13:11 +08:00
fathur 1042a26deb Add Flutter code section to README-ID (#16600)
* Add Flutter code section to README-ID

Added a section for Flutter code related to desktop and mobile platforms.

* Fix link formatting for Flutter code section
2026-10-20 16:13:13 +08:00
solokot 53996e3c40 Update ru.rs (#16570) 2026-10-16 11:18:21 +08:00
RustDesk e400bb7554 Enforce lockfile during pub get in build script 2026-10-15 22:39:27 +08:00
21pages 392188596c fix(video): improve clarity after scrolling by re-encoding static frames (#16128)
* fix(video): re-encode static frames for up to 10 seconds to improve clarity

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

fix(video): limit static re-encoding to 300 frames instead of 10 seconds

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

refactor(video): move static frame refresh into a dedicated module

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

fix(video): preserve stuck-encoder detection state across static repeats

test(video): cover cached texture lifetime across capture restarts

fix(video): reduce static re-encoding limit to 100 frames

fix(qos): lower qos_trace logging to trace level

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

fix(video): skip static frame re-encoding for AV1

perf(video): reuse VRAM encoder input for static refresh

Remove the DXGI repeat texture cache and use hwcodec's repeat entry point to avoid copying each captured frame. Isolate static refresh failures, pause after three errors, and resume after a real frame is sent successfully.

Cover cached input ownership, repeat failure recovery, refresh limits, and normal encoder failover with regression tests.

fix(vram): ignore cached SDK codec backends

chore(deps): temporarily use hwcodec re-encode branch

perf(video): wait 200ms before starting static refresh

chore(deps): update hwcodec for FFI exception handling

chore(deps): update hwcodec to 0.8.0

perf(video): start static refresh after 100ms

chore(deps): update hwcodec for exception-safe cleanup

chore(deps): update hwcodec for device-loss handling and noexcept FFI

chore(deps): update hwcodec to bbfc306

chore(qos): remove verbose diagnostics while retaining trace logs

* update hwcodec

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

* refactor(scrap): keep repeat encoding in the vram module

* perf(vram): avoid intermediate frame vectors

  - Drain normal and repeated encode output into protobuf messages.
  - Cover null-texture encoding failures.
  - Clarify the count-based static refresh budget.
  - Update hwcodec to fix RAM decode buffers on resolution changes.

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

* fix(scrap): preserve hardware encoder error codes

* test(video): remove uptime dependency from static refresh tests

* feat(video): configure static repeat limits by codec and quality

* fix(video): exempt VPx and AV1 from static repeat failure limit

* fix(video): count only successful static repeat frames

* fix(video): refine static repeat scheduling and VPx frame handling

Allow static refinement after a VP8/VP9 captured frame produces no output
without enabling it after a genuine encoding error. Carry this distinction
alongside the send result so the latest cached frame can still be sent when
capture becomes idle.

Keep VP8/VP9 repeats at a minimum of 100ms. Other codecs wait at least 100ms
initially, then follow the QoS frame interval aligned with the capture loop.
Use four quality bands, with VP8/VP9 limits of 30/20/20/0 and the tuned
H264/H265 limits. Include only production behavior and regression tests.

Validation: 16 static_refresh tests passed with hwcodec,vram,flutter enabled,
covering latest-frame recovery, genuine errors, timing, and repeat budgets.

* fix(video): tune static repeat limits by codec and backend

Use the measured obvious-improvement checkpoints for VP8/VP9 and separate
NVENC, QSV, and AMF H.264/HEVC limits for each quality tier. The checkpoints
recover 80% of the observed error improvement across the tested inputs.

Resolve the hardware backend from EncoderCfg during StaticRefresh
construction, including the VRAM adapter vendor. Keep the existing limits
for other hardware backends.

Validation: 19 static_refresh tests passed with hwcodec,vram,flutter.
The repeat measurements cover 192 series with no encode/decode errors.

* fix(video): refine static refresh budgets and cap repeat FPS

Add VideoToolbox HEVC budgets and tune quality bands from repeat-frame comparisons. Cap static refresh at 30 FPS and use the speed preset consistently at quality 0.5.

* remove unused variables

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

* fix(video): repair static refresh tests and throttle repeat logs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-10-15 20:17:49 +08:00
RustDeskandClaude Opus 5.5 c0750fb50c fix(client): report one error when both WebRTC race legs fail (#16563)
Both legs of race_transports_prefer_webrtc send their own punch request
to the same rendezvous server for the same peer, so a refusal there
(offline, unknown ID, key mismatch, a server-supplied other_failure)
fails both with the same text. Joining them as "WebRTC failed: X;
fallback failed: X" showed it twice and broke the UI's whole-string
matching: the offline auto-retry needs exactly "Remote desktop is
offline", the message box translates the text as one string, and
check_if_retry hides the retry button for any text containing "failed".

Return the error of the leg that failed last, as select_ok did before
the race existed, and log the other one once.


Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:52:19 +08:00
fufesou f705902ec3 fix(windows): move installer scripts into protected temp directories (#16554)
* fix(windows): move installer scripts into protected temp directories

Create a private directory under Windows Temp with its final DACL in
CreateDirectoryW. Grant Administrators and SYSTEM full access and use
OWNER RIGHTS to suppress the ordinary creator's implicit WRITE_DAC.

Keep the existing elevated system-cmd copy and SHA-256 verification flow.
Keep auxiliary files in the same directory, report cleanup failures, and
check command length before creating the directory.

This avoids PowerShell, helper executables, and application startup or DLL
linking changes. The Windows Temp parent must retain its standard protection
against deletion by unprivileged users. UAC cancellation can leave an empty
protected directory; the error reports its path.

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

* fix(windows): preserve installer results when cleanup fails

Keep the original installer exit code and report remaining temporary directories or inspection errors as warnings with their paths. Include the directory path in creation errors.

Restore the existing handoff assertions in a separate test module, add a locked-directory regression for successful and failed installers, and remove the unrelated ERRORLEVEL environment change.

Validation: scoped formatting and whitespace checks passed. Native cmd probes reproduced the failure before the fix and preserved both exit codes afterwards. No project build or Rust test execution was performed.
Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(windows): clarify installer cleanup warnings

Describe protected directories as possibly remaining when cleanup cannot be verified. Remove the cleanup-only test added during review while retaining existing handoff coverage.

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

* test(windows): keep installer handoff tests inline

Restore the inline test module and original helper order to reduce the diff against master. Keep the directory cleanup assertions and original bootstrap checks.

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

* fix(windows): include paths in installer directory check errors

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

* fix(windows): clear inherited ERRORLEVEL before installation

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

* fix(windows): avoid duplicate directory error messages

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

* test(windows): verify installer directory permissions

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-10-15 17:33:35 +08:00
RustDeskandClaude Opus 5.5 0d36ac0cba fix(linux): stop using the window in onWindowClose once GTK destroyed it (#16561)
On Linux, GTK destroys the window on a close request unless prevent-close
is on. When the main window is the last active one, onActiveWindowChanged()
turns prevent-close off and calls close(), yet onWindowClose() still went
on to hide() the destroyed window, and the close event that close() emits
re-entered onWindowClose() to save the frame through isMaximized(). The cm
and install windows never turn prevent-close on, so their window is always
gone by the time onWindowClose() runs.

With window_manager before cf4aef05 those calls segfaulted in
gtk_window_is_maximized; since then they fail with an unhandled
PlatformException(window_destroyed). Return early in both places instead.
Other platforms and sub windows keep the old path.

Fixes #16512


Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:33:42 +08:00
RustDeskandClaude Opus 5.5 f1fa432bc8 Fix fdroid bridge version (#16560)
* fix(fdroid): generate the bridge with the Flutter version bridge.yml uses

#15139 moved the bridge Flutter version from bridge.yml's env into its
job matrix, so build_fdroid.sh read `.env.FLUTTER_VERSION` as "null".
The F-Droid recipe worked around that by pinning it to 3.24.5, which
equals ANDROID_FLUTTER_VERSION, and the script only generated the bridge
when the two versions differed, so the build failed on the missing
src/bridge_generated.rs.

Read the version from the default bridge-artifact matrix entry (3.22.3),
and always generate the bridge: the build needs it whether or not the
two Flutter versions match.

Fixes #16549

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

* fix(fdroid): keep the recipe's version sed from overriding the bridge Flutter

The F-Droid recipe works around the old "null" read with
`sed 's/${FLUTTER_BRIDGE_VERSION}/<version>/'`, and its auto-update
copies that line into every new version. The 1.5.0 recipe pinned 3.24.5
and then 3.47.7, but flutter_rust_bridge codegen only works with the
Flutter version bridge.yml uses (3.22.3). Pass the unbraced variable to
prepare_flutter so that sed no longer matches the call.

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

* fix(fdroid): raise the Gradle heap for the AGP 8 build

#15603 moved the Android build to Gradle 8.11.1 / AGP 8.10.1 with
compileSdk 36 and desugaring. The first F-Droid 1.5.0 build got past the
bridge and the Rust library, then failed in assembleRelease with "Gradle
build daemon has been stopped: since the JVM garbage collector is
thrashing", because gradle.properties still caps the heap at 1 GiB. CI
has long raised it to 2g; do the same here. A recipe that already
rewrites the -Xmx1024M setting keeps its own value.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:42:39 +08:00
Maison da Silva 538126d42c Update Portuguese translations for keyboard shortcuts (#16555) 2026-10-15 12:06:47 +08:00
bovirus 9c6d3fcb4f Update Italian language (#16551)
* Update Italian language

* Update Italian language
2026-10-15 12:06:36 +08:00
Alex Rijckaert c230c57139 Update Dutch translation (#16540) 2026-10-15 12:05:52 +08:00
Leo Camus e614de83fc fix: read host= from an exe path with non-ASCII characters (#16556)
get_custom_server_from_string located "host=" in s.to_lowercase() and
sliced s with that byte index. Lowercasing can change a character's byte
length ("İ" is 2 bytes, "i̇" is 3), and on Windows the string is the full
exe path, so a profile such as C:\Users\İsmail shifted the slice past the
"h" and host came back empty. The client then fell back to the default
rendezvous server.

"host=" is ASCII, so search s.to_ascii_lowercase(), which keeps byte
offsets.
2026-10-15 10:30:06 +08:00
21pages e910029f51 feat: use the configured proxy for WebSocket connections (#16386)
* feat: add opt-in WebSocket proxy support

Add a WebSocket proxy option to the proxy dialog and custom client settings.
Keep WebSocket connections direct by default and use the configured proxy
without direct fallback when enabled.

Preserve relay targets regardless of the proxy address family and restart
connections when the option changes. Update hbb_common for proxy support.

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

* update hbb_common

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

* fix: remove WebSocket proxy opt-in

* chore: restore proxy settings UI and translations

* fix: update hbb_common for proxy read progress

* fix: update hbb_common proxy connections and IPv6 parsing

* fix: update hbb_common WebSocket local address

* update to rustdesk/hbb_common

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-10-14 22:49:35 +08:00
rustdeskandClaude Opus 5.5 256dc9bc12 docs: scope the -p rule to macOS and Windows
Linux CI runs cargo test --workspace, so a Linux-gated test in another
crate already runs there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R
2026-10-14 16:39:57 +08:00
RustDeskandClaude Opus 5.5 b1761ee676 Ci run all dart tests (#16328)
* ci: run every Dart test, not three files

The step ran only the three cursor test files, so the other nine files
under flutter/test (terminal, file model, autocomplete, ...) never ran
on any pull request. With no path the runner takes the whole directory.

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

* ci: run the Rust tests CI could not reach

cargo test --workspace runs with the default features on x86_64 Linux
only, so the tests behind `windows`, `not(target_os = "linux")`,
`unix-file-copy-paste` and drmtap_dl's `tests` module never ran: the
platform, portable-service and audio tests, the clipboard crate's file
copy-paste and Windows file-name tests, and the drmtap loader's.

The macOS and Windows build jobs now run `cargo test --lib` after the
build with the build's own feature set, plus the clipboard crate. The
Linux job's list of named test runs gains drmtap_dl and the clipboard
feature. AGENTS.md says where tests go and what CI runs.

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

* ci: drop the skips for tests that no longer exist

#16539 removed test_get_cursor_pos and test_get_key_state, the only
tests these skips matched.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-14 16:31:58 +08:00
Mariano Abad d011aa3ea6 fix(wayland): a pending portal request no longer stalls the server (#16542)
* fix(wayland): a pending portal request no longer stalls the server

The screencast portal waits for the user while its picker is open, up to
three minutes. check_init waited under the CAP_DISPLAY_INFO write lock on
a tokio worker and get_capturables under RDP_SESSION_INFO. Every desktop
login read the display state on a worker behind them, and the session
close when the last connection went waited for the slot on a worker too.
With the server on 4 cpus a second login was enough: new connections
were accepted by the kernel and not served until the request ended
(#15973).

The locks keep their role: the write lock still serializes inits and
orders them with clear(), and the slot still orders the session close
calls after a pending request. What changes is where the waiting happens.
The part of check_init under the write lock moves, as is, to
init_displays(), run in spawn_blocking. is_inited and the first look in
check_init use try_read; when a writer holds the lock, is_inited answers
not inited yet and check_init redoes its look in spawn_blocking, then
goes on as before. get_displays_and_primary takes its read guard in
spawn_blocking, is_rdp_session_hold uses try_lock, close_session from the
ipc listener runs in spawn_blocking, and try_close_session from
AuthedConnID::drop on its own thread.

* fix(wayland): throttle the log when the session close thread cannot start

A peer can drop connections at any rate, so the error logged when
AuthedConnID::drop cannot spawn the thread for try_close_session goes
through throttled_log!, once a minute, like the auto-login logs of this
file (greptile on #16542).

* fix(wayland): count the remote connections again in the close thread

The session close moved to its own thread in this pr, so a remote that
comes in after the count and before the thread runs could have the
session it reuses closed under it. The thread counts the remote
connections again right before try_close_session, which brings the
window back to the one the close had on the dropping thread (coderabbit
on #16542).

* fix(wayland): one portal session close at a time, for remote and camera connections only

The close that AuthedConnID::drop runs off the dropping thread spawned one
OS thread per drop that left no remote connection, whatever the connection
type, and while a portal request waits for the user each of those threads
waited for the session slot. Now only a remote or view camera connection
leaving asks for the close (their video service is the one that opens the
portal session), no thread starts when the slot is free and empty (drm,
x11, nothing left to close), and one close runs at a time: a close asked
while one runs is left to it. The logic moves to wayland.rs; connection.rs
keeps a hook.
2026-10-14 16:26:46 +08:00
RustDeskandClaude Opus 5.5 9997b7a3b8 test: remove tests that do not pin user-visible behavior (#16539)
* test: remove tests that do not pin user-visible behavior

Apply the AGENTS.md test rules to the existing Rust and Flutter suites.
A test is kept when it is the only coverage of user- or API-visible
behavior, the regression test of a reported or fixed bug, a security
boundary (one test per rejection class), or a check against an external
reference. Tests that assert private state or the order of internal
steps, pin review-round corner cases, or repeat what a kept test already
covers are removed.

Rust: 679 -> 298 tests. Dart: 118 -> 45 tests.

Test-only helpers left without callers go with them
(applyLastOnlineStateForTesting, terminalKeyboardRowWidth,
debugBufferedInputCount, tokio's test-util dev feature), and cfg gates
and visibility that existed only for the deleted tests are narrowed.
Runtime behavior is unchanged.

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

* test: keep tests that pin security and user-visible behavior

Restore six tests that review showed are the only coverage of their
behavior:

- update metadata rejects an artifact file name with path components;
- ICE candidates from another session are dropped;
- KCP delivers every multi-segment frame queued before a close;
- set_files rejects an empty name in a multi-file transfer;
- a lopsided elongated cursor keeps its centre hotspot (#16242);
- toggling Show hidden during a pending same-path read issues a new read.

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

* test: keep offline update and key-exchange downgrade rejections

Restore three tests that are the only coverage of their rejection class:

- offline update metadata rejects a signed artifact with an http or
  credential-bearing URL;
- offline verification rejects a renamed local file and a version that
  does not match the release;
- a key-exchange pick lowered in transit leaves both sides unable to
  decrypt, so a v0 fallback after a failed v1 decrypt cannot pass
  unnoticed.

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

* test: keep the decomposed-grapheme and reconnect-cancel tests

Each catches a plausible wrong change that passes every kept test:
mapping the first scalar under a locked Ctrl turns é into \x05,
and a reconnect that does not fail pending reads leaves them hanging
until the 30 s timeout.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:22 +08:00
Leo Camus 76ab280ae9 fix(terminal): trim the replay buffer when output has no newline (#16545)
* fix(terminal): trim the replay buffer when output has no newline

OutputBuffer::append returned early when the last line was incomplete
and the new chunk had no newline, so the trim loop never ran. A program
that writes without newlines (a minified file through cat, a \r
progress bar) grew the replay buffer past MAX_OUTPUT_BUFFER_SIZE
without bound, one 4 KiB PTY read at a time, until a newline arrived.

Signed-off-by: leoca <leo.camus23@gmail.com>

* fix(terminal): keep recent output when trimming a line without newline

Trimming popped the whole incomplete line once it passed
MAX_OUTPUT_BUFFER_SIZE, so the reconnect replay could come back empty.
Newline-free output now goes into a new chunk once the incomplete one
holds DEFAULT_RECONNECT_BUFFER_BYTES, and trimming drops the oldest
chunks while the newest bytes stay. The test checks get_recent.

Signed-off-by: leoca <leo.camus23@gmail.com>

* fix(terminal): trim an oversized incomplete line to its newest half

Splitting the incomplete line into chunks made get_recent return whole
chunks only, so a reconnect could replay as little as the newest read.
Keep one line and, when it alone is over MAX_OUTPUT_BUFFER_SIZE, drop
its oldest bytes down to half the cap. The copy then happens once per
half buffer instead of on every append, and the replay keeps the
newest DEFAULT_RECONNECT_BUFFER_BYTES.

Signed-off-by: leoca <leo.camus23@gmail.com>

---------

Signed-off-by: leoca <leo.camus23@gmail.com>
2026-10-14 12:35:53 +08:00
HeZheng 29a160fc7e fix(mobile): prevent late mouse-down after floating button release (#16541)
Signed-off-by: zhenghe <zhenghe2009@gmail.com>
2026-10-14 11:26:13 +08:00
Mariano Abad 49a9574f97 fix(drm): pace the producer to the rate the encoder takes, with the panel refresh as floor (#16423)
* fix(drm): pace the producer to the rate the encoder takes, with the panel refresh as floor

The root producer slept a fixed 33 ms after every grab, so every DRM stream topped
out at 30 fps whatever the client asked (rustdesk#16175). Measured on a
1920x1080@60 i915 box with the client at Custom 60 and QoS settled at 60: 30
grabs, 30 converted, 29.8 sent. A shorter fixed interval reaches the rate but
oversamples: at 8 ms the same box grabbed and converted 117/s for 60 sent.

The consumer now tells the producer the rate it encodes at, in the ack byte it
already sends per converted frame: 0x01 as before, or 0x80|fps. Every producer
counts ack bytes and never read their value, so an older service sees exactly
the same credit and keeps its 33 ms; the JSON handshake and the ack count and
timing do not change. The rate is the encoder's spf (what frame() is called
with), lowered to what the encoder achieves when it is busy longer than spf per
frame: the time from one frame() call to the next, less what the call spent
waiting for a frame, so a late producer cannot talk its own demand down.

The producer replaces the post-work sleep with a deadline: the encoder's period
above 30 fps, the old 33 ms from 30 down to 15, and half the encoder's period
below 15, never faster than the panel (the exact CRTC refresh from libdrmtap
0.5.9's drmtap_crtc_refresh, re-read once a second; with an older library the
whole-hertz refresh the enumeration already read, so no extra connector probe).
The worker still wakes at least every 33 ms, because each tick also reads the
cursor and a slow frame rate must not slow the cursor down. Without a demand (an
older consumer, or before the first frame() call) the old 33 ms sleep stays.
MAX_STALLED and the WouldBlock retry keep their 5 s budget, pinned by a const
assertion.

Tests: the ack byte round trip and that it is one byte on the wire, the drain
(seeded byte streams: every byte one credit, the newest demand wins), the pacing
interval table (panel floor, below 30 and below 15, a 23.976 Hz panel), the panel
period rounded up, the deadline and its overrun, the wake that keeps the cursor
at 33 ms, the demand from spf and from a busy encoder, the busy time without the
wait (pure and through frame()), the demand log once per connection, the
whole-hertz fallback, and the reader's refresh answer. Each rule has a mutation
that turns its test red.

* ci: run the drm pacing tests in the drm step

drm_pacing_tests (src/ipc/drm.rs) and crtc_refresh_tests (libs/scrap
drm_reader.rs) match none of the existing filters.

* test(drm): keep three tests, on outputs and not on internal state

Per AGENTS.md (Tests), keep one to three tests, on outputs and not on internal
state: the demand ack as it goes on the wire, the producer interval for a
demand and a panel, and the exact refresh the reader returns.

* test(drm): the ack test goes through the producer drain

Per review: send_frame_ack(60) read back through drain_frame_acks gives one
credit and a demand of 60, so dropping the demand read in the producer turns
the test red. The interval test also pins drm_panel_period(148352, 2475) to
16_683_294 ns.

* fix(drm): time the encoder only after a frame() call that returned a frame

Since #16450 frame() drops a repeat as WouldBlock. The time after such a
call is video_service sleeping out its spf, and after a timeout it is
about zero once the wait is subtracted. Neither follows an encode, so
neither is a sample of the encoder pace now. Uses returned_at from #16450;
no new state.

* fix(drm): ask for the old 33 ms tick while the screen is still

Since #16450 a still screen is grabbed at the demand and dropped in
frame(), twice as often as master at 60. After 100 ms without a new
frame the demand is capped at 30, the old producer tick, and the next
new frame lifts it. 100 ms is three of those ticks, so a producer paced
at 30 in motion never reads as still. Uses returned_at; no new state.

* test(drm): a dropped repeat or timeout is no encoder time; a still screen asks for the old tick

Per review: the demand frame() reports stays with a 50 ms encoder through
a still screen of repeats and timeouts, and a still screen asks for 30
after STILL_AFTER until a new frame brings back 60.

* fix(drm): start the encoder average over when the rate goes up

Per review: the samples hold the sleep video_service paces itself with, so
after a rate increase (custom fps 1 -> 60, or QoS recovering) an average
taken at the old rate kept the demand low and the producer slow. A
shorter spf than the last now starts the average over and drops the
sample that spans the change. The demand is computed in one method that
takes its time as input, the still cap included.

* test(drm): drop the two pacing tests of the last round

Per review: they read shared.demand_fps and slept for real. The three
tests stay.

* build: pin libdrmtap 0.5.12 (rustdesk-org/libdrmtap 5936a7a88)

The panel floor of the pacing reads drmtap_crtc_refresh(), added in
libdrmtap 0.5.9. The mirror carries v0.5.12 since 2026-10-13, so the pin
moves there.

The rest of the range from 0.5.8 changes no struct in drmtap.h: 0.5.10
reads its environment through secure_getenv and loads no GL in a setuid,
setgid or file-capability process, which rustdesk does not run; 0.5.11
makes drmtap_deswizzle, also the CPU fallback of a tiled grab, refuse a
destination stride narrower than its rows instead of writing past it,
and exports drmtap_frame_owns_data, unused here; 0.5.12 changes only
docs and the version.
2026-10-14 10:44:43 +08:00
0740063863 Keyboard shortcuts (#14933)
* fix web break introduced in 38f130071 fix(linux): enable mouse side buttons in remote sessions (#14848)

* feat(shortcuts): user-configurable keyboard shortcuts for session actions

  Adds a keyboard shortcut feature (Rust matcher + Dart UI + cross-language
  parity tests) that lets users bind combinations like Ctrl+Alt+Shift+P to
  session actions. Bindings are stored in LocalConfig under
  `keyboard-shortcuts`; the matcher gates dispatch on `enabled` and
  `pass_through` flags so flipping the master switch off is a hard stop.

  Wire-up summary:
  - src/keyboard/shortcuts.rs: matcher, default bindings, parity test against
    flutter/test/fixtures/default_keyboard_shortcuts.json
  - src/keyboard.rs: shortcut intercept in process_event{,_with_session},
    feature-gated to `flutter`; runs before key swapping so users bind to
    physical keys
  - src/flutter_ffi.rs: main_reload_keyboard_shortcuts +
    main_get_default_keyboard_shortcuts; reload_from_config seeded in main_init
  - flutter/lib/common/widgets/keyboard_shortcuts/: shared config page body,
    recording dialog, shortcut display formatter, action group registry
  - flutter/lib/desktop/pages/desktop_keyboard_shortcuts_page.dart and
    flutter/lib/mobile/pages/mobile_keyboard_shortcuts_page.dart: platform
    shells around the shared body
  - flutter/lib/models/shortcut_model.dart: per-session ShortcutModel +
    registerSessionShortcutActions for actions with no toolbar TToggleMenu /
    TRadioMenu (fullscreen, switch display/tab, close tab, voice call, etc.)
  - flutter/lib/common/widgets/toolbar.dart: optional `actionId` field on
    TToggleMenu / TRadioMenu, plus per-helper auto-register pass that wires
    tagged entries' existing onChanged into the ShortcutModel
  - flutter/test/keyboard_shortcuts_test.dart + fixtures: cross-language
    parity (default bindings, supported key vocabulary)

  Design principles applied during review:

  1. Additions are fine; modifications to original logic must be deliberate.
     Tagging an existing TToggleMenu entry with `actionId:` is an addition.
     Rewriting its onChanged to satisfy a new contract is a modification —
     and was reverted for every case where the original click behavior was
     working. Four closures were touched and then reverted (mobile View
     Mode, Privacy mode multi-impl, Relative mouse mode, Reverse mouse
     wheel); their shortcuts are wired via standalone closures in
     shortcut_model.dart instead.

  2. Toolbar auto-register is reserved for entries whose onChanged is
     inherently self-flipping — typically `sessionToggleOption(name)` where
     the named option is flipped in place and the input bool is unused. The
     register pass passes `!menu.value` from registration time, which is
     harmless under self-flipping but wrong for closures that consume the
     input bool directly. Tagging a non-self-flipping entry forces a closure
     rewrite; choose non-toolbar registration in that case.

  3. When shortcuts are disabled, toolbar behavior must be bit-for-bit
     unchanged. The matcher's `enabled`-gate already guarantees no
     dispatch; the auto-register pass is left unconditional (its only effect
     is HashMap operations on a separate ShortcutModel) so mid-session
     enable works without a reconnect. The trade-off is intentional and
     documented at the top of toolbarControls.

  4. Comments stay terse. Rationale lives in one place — the doc comment of
     the helper or registration site, not duplicated at every call site.

  5. Where an existing helper needs a new optional behavior (e.g.
     `_OptionCheckBox` gaining a tooltip slot), the new branch must reduce
     to byte-identical output for existing callers (`trailing == null`
     case → original `Expanded(Text)` layout). Verified.

  6. Action IDs and labels stay consistent. Renamed `reset_cursor` →
     `reset_canvas` so the action ID matches its user-facing label
     ("Reset canvas") and capability flag.

  Out-of-scope but included:
  - AGENTS.md: documents flutter_rust_bridge no-codegen workflow and the
    Web target's hand-written TS client, since both are load-bearing for
    any new FFI work.
  - remote_toolbar.dart: i18n fix for the per-monitor tooltip ("All
    monitors" / "Monitor #N"), unrelated to shortcuts but kept here.

* langs

* fix(keyboard): shortcuts, harden config and callback lifecycle

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

* feat(keyboard): shortcuts, color of "Reset to defaults"

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

* feat(keyboard): shortcuts, release keys before shortcut callback

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

* feat(keyboard): shortcuts, debug web

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

* fix(keyboard): iPad, icon

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

* langs: add shortcut keys to az, gl, ur

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

* fix(keyboard): shortcuts, keep local modifiers and release keys on the right session

When a chord fired, the shortcut path reused release_remote_keys(), which
replays synthetic releases through process_event(). That cleared the local
MODIFIERS_STATE while the user still held the modifiers, so the next key of
a held chord no longer matched and leaked to the remote, and it sent the
releases to the globally current session instead of the one that typed the
chord.

The shortcut path now releases the remote keys itself: it sends through the
caller's session, and restores the modifiers it released on the remote as
still held locally. The press that fired is remembered until its release,
so its auto repeats and its release are consumed instead of reaching the
remote.

Also state the contract for unavailable actions: a bound chord always
belongs to RustDesk and is a no-op when the session has no handler.

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

* fix(keyboard): shortcuts, record the physical key

The recording dialog and the Web Flutter matcher named keys by
LogicalKeyboardKey, while the native matcher sees the physical key through
its USB HID usage and the Web JS matcher through KeyboardEvent.code. On
AZERTY, QWERTZ, Dvorak and similar layouts a recorded binding then did not
fire, or fired from another key.

Name keys by their physical position everywhere: physicalKeyName maps the
USB HID usage to the stored name. A new fixture pins every name to its USB
HID usage, checked on the Dart side and against rdev::usb_hid_key_from_code
on the Rust side, and a test covers AZERTY and QWERTZ events.

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

* fix(keyboard): shortcuts, keep an empty binding list on re-enable

setEnabled seeded the defaults whenever the binding list was empty, so a
user who cleared every binding got all defaults back after disable and
enable. Seed only when the config has never held a bindings list.

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

* fix(keyboard): shortcuts, track every fired key until its release

FIRED_KEY held a single key, so overlapping shortcut keys broke it: with the
chord held, P then C overwrote P, P's release reached the remote, and a P
repeat could fire the screenshot again. A repeat after the modifiers were
released was also treated as a new press and leaked to the remote.

Keep a set of fired keys instead. Once a press fires, every event of that
key belongs to RustDesk until its release: repeats are consumed without
dispatching, whatever the modifiers do, and the release removes the key.
Entries whose release never arrives are dropped when a session view is
entered or a session is closed, not by guessing from the modifiers.

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

* fix(keyboard): shortcuts, keep fired keys per session

FIRED_KEYS was process-global while dispatch is per session. Closing or
entering any session cleared the fired keys of all of them, so a key still
held in another session leaked its release to that remote, and a key fired
in one session made the same key in another session look like a repeat.

Key the fired set by session: try_dispatch resolves the session first and
only looks at that session's keys, and session_close clears only the
closing session. Entering a session view no longer clears anything: it is
a pointer enter, and a fired key may still be physically held across it.
A key whose release is missed otherwise heals itself: its next press is
consumed as a repeat and that release removes it.

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

* fix(shortcuts): preserve input state and refresh action availability

* fix(keyboard): shortcuts, keep a fired key owned across session close

Close tab is itself a shortcut, so the session that consumed the key press
is gone before the key is released. Both matchers dropped the key with the
session: the Flutter dispatcher removed its own entries in clear(), and the
Rust set was keyed by session and cleared in session_close. The release,
and the repeats while the key stayed held, then reached the next session's
remote as ordinary input.

Ownership of a fired key is physical, so it is now one set shared by every
session on each side, and closing a session no longer touches it: clear()
only drops the pending action and modifier replay, and the Rust session
state only tracks released modifiers. A key whose release is missed heals
itself on its next press. Entering view-only with the Flutter input source
still clears the Rust set, since that source stops routing key events to it.

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

* fix(keyboard): shortcuts, drop the Toggle Input Source action

The action switches the key capture backend between key down and key up.
Fired-key ownership lives in whichever matcher saw the press, the Flutter
dispatcher or the Rust set, and there is no handoff between them: after the
switch the repeats and the release of the held key reach the other backend,
which forwards them to the remote and, while the chord is held, matches the
action again and toggles the source back. Remove the shortcut instead of
building a handoff for one action. The toolbar radio menu is unchanged.

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

* perf(keyboard): shortcuts, parse the config once per stored value

The Flutter matcher read the local config three times and parsed its JSON
three times on every key press in legacy mode, on Web and in view-only.
Keep one parsed snapshot keyed by the raw stored string, so a key press
costs one config read and no parsing while the config is unchanged. The
existing readers are thin views of the same snapshot.

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

* fix(keyboard): shortcuts, run focus-changing actions on key release

Close tab and tab switching move keyboard focus to another session before
the key that fired them is released. On Linux the Flutter input source
routes map-mode sessions through the Rust matcher and legacy-mode sessions
through the Dart matcher, and neither knows the other's fired keys, so
after such an action the repeats and the release of the held key reached
the next session's remote, and the key stayed recorded as fired in the
matcher that saw the press.

Routing Linux legacy mode through the Rust FFI path instead is not a drop-in
change: the raw key handler builds its event with no unicode, so Rust's
legacy mode would fall back to US key names and lose the layout-aware
characters the Dart path sends today, and it would take modifiers from the
X11 key state instead of Flutter's.

Run these three actions when the fired key is released. The matcher that
consumed the press then also sees the repeats and the release, the remote
modifiers are still released on the press, and a session that closes drops
the action it owed. Both matchers keep the same list of actions.

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

* test(keyboard): shortcuts, pin the key-up action list on both sides

kShortcutActionsRunOnKeyUp and runs_on_release are two hand-kept lists of
the actions that run on key release. Add a shared fixture and check each
side against it, the way the key vocabulary and the default bindings are
already checked, so adding a focus-changing action on one side breaks a
test until the other side follows. The Dart test also checks that every
listed id is a configurable action.

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

* fix: prevent lost input after remote session shortcuts

Avoid synthetic Alt presses that activate the Windows system menu
and swallow the next character.

Trigger restart, switch-sides, and chat actions on key release so
focus changes cannot leave subsequent shortcut presses ignored.

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

* fix(mobile): correct Chat keyboard and input helper behavior

- Keep soft keyboard and helper hidden when Chat opens via shortcut
- Restore soft input on touch and remote focus after Chat closes
- Handle modifier releases while Chat owns focus
- Clear hidden virtual modifiers while preserving held keys and Pin behavior

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

* fix(shortcuts): prevent recording non-simultaneous key combinations

Build new captures from currently held keys instead of reusing released
letters. Preserve the last valid chord after release so it can be saved.

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

* fix(shortcuts): isolate window actions and prevent duplicate Alt presses

- Dispatch shortcut actions only to the originating session window.
- Clear local modifier state on focus loss without resending modifiers
  already released to Windows peers, preventing lost following input.
- Strengthen the regression assertion against extra Alt keydown events

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

* refact(shortcuts): empty default shortcuts for Cmd+Option+Shift+Del on macOS and iOS

Because Del may be ambiguous for new macOS users.
Cmd+Option+Shift+Backspace is "Empty Trash without confirmation" shortcut on macOS.

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

* fix: use session peer platform for Ctrl+Alt+Del events

Fix Android-to-Linux Ctrl+Alt+Del delivery by using the session's peer
platform instead of the desktop global session, which defaults to Windows

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

* fix: hide relative mouse mode shortcuts on Wayland

Separate relative mouse mode availability from screenshot support.
Filter unsupported actions and defaults while preserving saved bindings

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

* fix: align shortcut availability with platform support

- Hide unsupported mobile and web shortcut actions
- Enable mobile scaling and Android voice call shortcuts
- Use openMonitorInTheSameTab() for display switching
- Preserve cursor position when switching displays on desktop and web

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

* fix: prevent held-key shortcut recording bypass

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

* fix: preserve keyboard input after shortcut dialogs

Run native clipboard shortcuts on key release and wait for remote modifier releases before dispatching.

Release remote modifiers when mobile local UI takes focus, and document the tested iPad Bluetooth Mute shortcut limitation.

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

* fix: preserve remote key state during local shortcuts

Keep unrelated held keys pressed for session actions that retain input. Preserve key release when focus or input handling changes, and avoid synthetic Alt taps during shortcut dispatch.

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

* fix: suspend saved relative mouse shortcuts on Wayland

Filter the native active binding cache without changing stored configuration, so the binding becomes available again under X11.

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

* fix: exclude unsupported Wayland shortcuts from Flutter matching

Keep saved bindings intact while filtering the active config used by the Flutter matcher. Cover stored bindings and enable/pass-through flags in the existing shortcut tests.

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

* fix: retain forwarded key ownership across shortcut repeats

Ignore repeat presses for keys already forwarded to the peer, and release ordinary held keys before Show My Cursor enables view-only. Cover both paths with converter-level regressions.

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

* fix: release Flutter-held keys before view-only shortcuts

Track ordinary keys forwarded by the Dart matcher paths and await their releases before View Only or Show My Cursor disables input. Keep Mute key ownership and existing empty-cleanup timing unchanged.

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

* fix: filter active Flutter shortcuts by platform capabilities

Reuse the Settings action filter for saved bindings while preserving storage, default-seeding behavior and configurable Apple Ctrl+Alt+Del bindings.

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

* fix: filter native shortcut caches for mobile controllers

Suspend saved desktop-only actions on Android and iOS and hide unavailable iOS actions without changing persisted bindings. Keep native cache filtering aligned with Flutter platform capabilities.

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

* fix: share filtered shortcut bindings with the Web matcher

Pass the active platform config to JavaScript at startup and after settings changes, preserving unsupported saved bindings in local storage.

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

* fix: release Flutter-held keys before focus-changing shortcuts

Drain ordinary keys owned by the original session before run-on-key-up
actions and native clipboard callbacks. Preserve Mute holds, pending-action
cancellation and Web clipboard key-down activation.

Document the required Web matcher counterpart in the bridge:
rustdesk/rustdesk-web-js@94abe431d6

Validation: 61 native Flutter tests, 32 Chrome/HTML tests, targeted analysis
and Windows debug build.

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

* refact: remove the useless comments

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

* fix: prevent Windows menu activation from Alt-only shortcuts

Neutralize the forwarded Alt with a balanced temporary Ctrl press when a consumed local shortcut releases its only modifier in Windows Map mode. Keep physical modifier tracking and normal Alt pass-through unchanged.

Add a regression covering both Alt keys, trigger repeats and the eventual physical release. Verified with 36 native shortcut tests and real Windows menu/input checks.

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

* fix: prevent Alt menu activation in all keyboard modes

Extend Windows Alt-only shortcut neutralization to Translate and Legacy. Keep the temporary Ctrl packets in Map mode so Legacy modifier synchronization cannot release Alt before Ctrl.

Extend the existing regression across keyboard modes and decode scan, virtual and control-key events. Verify 36 native tests and 34 Windows menu replay cases.

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

* fix: initialize outgoing chat before shortcut dispatch

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

* fix: guard Windows Alt release in Flutter legacy shortcuts

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

* fix: coordinate Web shortcut dispatch and Alt modifier cleanup

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

* fix: distinguish AltGraph from ordinary Alt shortcuts

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

* fix: dispatch macOS Web shortcuts without waiting for keyup

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

* fix: support legacy peers in privacy mode shortcut

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

* fix: exclude AltGraph from Flutter shortcut matching

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

* fix: exclude GTK AltGr input from raw shortcut matching

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

* test: trim redundant keyboard shortcut coverage

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

* fix(keyboard): prevent Cmd shortcuts from opening remote Start

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

* fix(shortcuts): guard Windows menu keys after Ctrl/Cmd swapping

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

* fix(shortcuts): align recording and display routing

Reject non-Apple AltGraph captures to match runtime filtering. Honor individual-display windows for next, previous, and all-display shortcuts.

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

* fix(shortcuts): reject GTK AltGr chords in recorder

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

* fix(android): restore keyboard focus for modifier-first input

Focus the existing FlutterView when a modifier is pressed without native view focus. This restores shortcut-first input after returning to RustDesk while preserving focused inputs and normal key dispatch.

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

* fix(shortcuts): release held keys before keyboard mode changes

Drain Flutter-forwarded keys before keyboard-mode shortcut actions so releases use the original encoding. This prevents held keys from remaining down after a Web Input source 2 mode change.

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

* fix(i18n): use sentence case for shortcut labels

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

* fix(mobile): preserve touch modifiers in the input helper

Keep touch-selected modifiers visible after hiding the keyboard. Clear physical modifier releases locally in view-only mode so they do not reopen the helper.

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

* fix(web): preserve modifiers across Legacy-to-Map shortcuts

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

* fix(shortcuts): check support before registering all displays view

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:31:44 +08:00
RustDeskandClaude Opus 5.5 13e1278b4c fix: auto-login types the OS password only at the logon or lock screen (#16520)
* fix: auto-login types the OS password only at the logon or lock screen

A remote-control login on Windows and macOS now reports is_logon_screen
in platform_additions, and the client skips auto-login when the peer
reports false. Peers that don't report it (older ones, Linux) keep
today's behaviour, so this narrows the leak into the focused window
without closing it: the value is a snapshot taken at login.

Windows 7 and Server 2008 R2 report the WTS lock flag reversed, so
there only LogonUI.exe tells the lock screen apart.

Refs #16510, #15261

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

* windows: drop the LogonUI.exe check from is_logon_screen

LogonUI.exe can outlive the unlock and is matched by name only, so its
presence reported a lock screen on an unlocked desktop and let the
password through. Only the WTS lock flag decides now; on Windows 7 and
Server 2008 R2, which report it reversed, the result is inverted.

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

* windows: count only a confirmed logon or lock screen for auto-login

is_prelogin() reads a failed WTS user-name query as no user signed in,
and on Windows 7 the inverted is_locked() read a failed lock-flag query
as locked, so either failure let the password through. is_logon_screen
now uses its own queries that keep a failed read apart, and reads LOCK
and UNLOCK the other way round on Windows 7 and Server 2008 R2. The
existing functions stay as they are for their callers.

If the check task itself fails, the server reports false instead of
leaving the key out, which old clients' peers would mean.

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

* auto-login: ask the peer with a protobuf field instead of a JSON key

The client sets the new LoginRequest.auto_login only when Auto Login
would type the OS password, and the server runs the logon-screen check
only then, so nothing new runs for any other login. The answer is the
new PeerInfo.skip_auto_login: its default, false, is what an old or
Linux server means, so those peers keep typing as before, and neither
field is encoded unless set. The is_logon_screen key in
platform_additions is gone.

Windows is_locked() now reads the lock flag through
is_session_locked_flag(), which handles the reversed LOCK/UNLOCK of
Windows 7 and Server 2008 R2. Its only caller is the
allow-logon-screen-password check, which on those systems accepted the
permanent password on an unlocked desktop. Windows 8 and later read the
same as before. The C is_session_locked() is removed and its
WTS_CURRENT_SESSION guard moves into get_session_flags().

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

* auto-login: let the peer type the password, after checking its screen

The login-time check left the whole time until the password arrived
open: the screen could be unlocked during the click, the 1.2 s wait and
two network trips, a second client's password landed on the desktop the
first one had just unlocked, and turning Auto Login on mid-login skipped
the check.

Now the client still clicks and waits, then sends the password in a new
Message.os_login (reusing OSLogin) to a Windows or macOS peer from 1.5.1.
The peer checks for its logon or lock screen right then and types the
password and Return only if it finds one; otherwise it drops them. Other
peers get the keys as before, and the OS Password menu item is
unchanged. The password goes out only after login, never in the login
request. LoginRequest.auto_login and PeerInfo.skip_auto_login are
removed, so nothing new runs unless os_login arrives.

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

* server: name os_login in scope-violation logs

A rejected os_login was logged as message.other.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:06:15 +08:00
rustdesk 0b5a3d74de 1.5.1 2026-10-12 17:08:40 +08:00
Mariano Abad 109c39bccb fix(drm): drop a frame equal to the previous one, as the other capturers do (#16450)
* fix(drm): drop a frame equal to the previous one, as the other capturers do

The producer grabs on a timer, so a still screen reached the encoder as the
same frame every tick and the client decoded about 30 frames per second of
nothing new (rustdesk#16449). The x11, pipewire and quartz capturers drop a
repeat with would_block_if_equal; the drm capturer now does the same.

A regression test checks that a repeat is not encoded again and that the next
new frame is. The frame helper of the tests now differs per call, and the 180
test forgets the frame its last case repeats on purpose.

* fix(drm): a frame whose encode failed is not dropped as a repeat

The capturer marked a frame as seen when it returned it, so when its encode
failed and the screen then stayed still, every later grab compared equal to
it and it was never sent: the client kept the previous image, and a hardware
encoder never reached its failure count to switch away.

handle_one_frame now also says whether the frame was encoded, and when it was
not the loop calls TraitCapturer::forget_last_frame(), a new method whose
default does nothing. The DRM capturer then returns the next repeat instead of
dropping it.

The DRM capturer also stops keeping a copy of the last frame: it compares the
new buffer with the one it returned last before replacing it, and a turned
frame is turned into a spare buffer first.

* fix(linux): x11 and pipewire do not drop a failed frame as a repeat either

Both capturers drop a frame equal to the one they returned last, so they had
the same hole as the drm one: a frame whose encode failed was not sent again
while the screen stayed still. They now forget it when the loop says so.

* fix(linux): every video run starts by forgetting the last frame

A pipewire recorder outlives a restarted run while another display keeps the
session open, and so did the frame it returned last. A run restarted by an
encoder switch or a new subscriber then dropped the first repeat and sent
nothing until the screen changed. The capturers of the other paths are built
fresh for each run, so for them the call does nothing.

* fix(drm): keep the repeat drop in the drm capturer, with one repeat a second

The shared part of the three commits before is reverted: TraitCapturer,
the video_service loop, x11 and pipewire are as on master again. So is the
restructure of frame() from the second commit: the drop is again the one
of the first commit, would_block_if_equal on the frame returned last, as
the other capturers do.

A repeat is dropped until a second has passed since frame() last returned
a frame, so a frame whose encode failed reaches the encoder again a second
later even if the screen stays still. The tests check the drop, the repeat
a second later, and that the second starts over after it.

* fix(drm): a frame is a repeat only when its format matches too

The same bytes as BGRA and as RGBA are two pictures when red and blue
differ, and the convert path can return either, so a change of format
clears the saved frame and the new one goes out. The 180 test no longer
checks a turned repeat: nothing reported asked for it.

* fix(drm): compare a frame with cur before replacing it, without a copy

saved_raw_data copied every changed frame (8 MB at 1080p). The previous
frame is still in cur when a new one arrives, so the bytes and the
format are compared with it before the swap; a rotated output turns the
frame into a spare buffer first. The 180 test checks the turned repeat
again, since that path compares on its own now.
2026-10-12 11:29:57 +08:00
Henrik Fransson ccfa714d2e Fix Swedish translation typo (#16529)
Signed-off-by: Henrik Fransson <hmf.hmf.hmf@gmail.com>
2026-10-12 10:57:11 +08:00
fufesou 0921a7432f feat(update): verify signed update metadata (#15914)
* feat(update): verify signed update metadata

* fix(update): harden metadata publishing and app names

- share strict app-name validation across Windows and macOS
- preserve published sidecars until replacements are ready
- select macOS test features by target architecture
- update the signing dependency and URL validation coverage

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

* fix(update): prevent replacing published release assets

- block reruns after signed update metadata is published
- clarify the security rationale for app-name validation

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

* fix(update): cache TLS responses and avoid double translations

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

* refact: reduce the diffs

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

* refact(update): remove obsolete update paths

- remove unused FFI update branches
- delete legacy Windows and macOS update helpers
- simplify macOS daemon updates and redundant gating

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

* feat(update): secure same-identity macOS offline updates

- verify DMGs using signed metadata, size, and SHA-256
- enforce package and bundle identity plus macOS signature checks
- support custom release policies and propagate manual update failures

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

* fix(update): verify Windows installers with Authenticode

Reject unsigned or untrusted EXE/MSI updates
before elevation, clean up failed verified copies,
and fix the macOS manual updater logging import.

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

* fix(update): clear stale URL on response body errors

Keep body-read failures in the common cleanup path and document the
cache-only Authenticode verification policy.

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

* refactor(update): remove OS signature sanity checks

Remove Windows Authenticode and macOS codesign/Gatekeeper verification.
Keep metadata, artifact integrity, bundle identity, and transaction checks.

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

* refact(update): move update scripts into res/update

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

* fix(update): secure Windows MSI handoff against junction races

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

* fix(update): secure Windows EXE handoff against junction races

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

* fix(update): preserve legacy custom client app names

- allow names starting with digits or underscores
- preserve leading hyphens on Windows for compatibility
- reject leading hyphens on macOS to avoid pgrep option parsing

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

* refact(update): remove some simple wrappers

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

* refact(update): simple refactor

* refact(update): comments on update.scpt

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

* refact(update): comments

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

* fix(update): create download destinations exclusively

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

* fix(macos): roll back updates when final readiness checks fail

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

* fix(msi): propagate MSI installation failures

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

* chore

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

* fix(macos): reject stale IPC sockets during updates

Require open IPC sockets in addition to launchd running state before committing the privileged update transaction. Inspect sockets with lsof to avoid triggering IPC authentication.

Add native socket regression coverage to macOS CI. Remove the unused Windows helper, combine offline metadata exports, and restore unrelated formatting changed by the PR.

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

* fix(macos): allow offline updates without an installed bundle

Validate app-only updates against the running client's bundle identifier and check the destination identity when it exists. Replace source-text assertions with behavioral identity tests and run the macOS update tests in CI.

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

* ci, gate test with PUBLISH_UPDATE_METADATA == 'true'

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-10-12 10:56:50 +08:00
fufesou c8532719c0 Fix/macos mouse release (#16516)
* fix(macos): preserve mouse-up through cursor protection

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

* fix(macos): clear tracked presses released in relative mode

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

* fix(input): preserve desktop mouse releases through cursor protection

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

* fix(input): preserve button state when mouse-up is rejected

Clear tracked buttons only after coordinate validation succeeds.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-10-10 19:05:45 +08:00