mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-10-10 14:01:56 +00:00
master
11521
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
4411d10f45 |
fix(linux): report the process user as the active user under Flatpak (#16519)
File transfer to a Flatpak peer always failed with "No active console user logged on", because PeerInfo.username came from loginctl, which the sandbox cannot reach: Flathub builds lack the org.freedesktop.Flatpak talk-name, so `flatpak-spawn --host` fails. A Flatpak instance only runs inside the logged-in user's own session, so that user is the active one. Fixes #16506 Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
67090d3aa1 |
fix: a refused privacy-mode request no longer turns off another connection's (#16507)
* fix: a refused privacy-mode request no longer turns off another connection's `Connection::turn_on_privacy`'s error arm tore down whatever privacy mode was active, whoever owned it. With connection A in privacy mode, connection B's request is correctly refused with OCCUPIED, and the error arm then called `turn_off_privacy_to_msg(INVALID_PRIVACY_MODE_CONN_ID, ..)`. That id bypasses the ownership check in `check_off_conn_id`, so A's privacy mode went away: the screen was un-blanked and local input un-hooked. The call passes `state: None`, so no `set_privacy_mode_state` goes out either and A was never told, leaving its client showing privacy mode as on. `check_privacy_mode_changed` doesn't cover it, as it only sends PrvOnByOther while a privacy mode is still active. Pass `self.inner.id`, as the sibling error path above already does. `check_off_conn_id` then cleans up after this connection when it owns privacy mode, and refuses when another connection does. The bypass itself stays: `ipc::Data::Close`, `reset_all` and `TurnOnGuard` need an unconditional teardown. Its contract is now noted on `check_off_conn_id`, since that is what the buggy call site got wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Steven Storie <sstangle73@gmail.com> * fix: disconnect other remote sessions when privacy mode starts Signed-off-by: fufesou <linlong1266@gmail.com> * fix: exclude privacy disconnect hook from iOS builds Signed-off-by: fufesou <linlong1266@gmail.com> * fix: validate privacy mode before disconnecting other sessions Signed-off-by: fufesou <linlong1266@gmail.com> * chore Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: Steven Storie <sstangle73@gmail.com> Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: fufesou <linlong1266@gmail.com> |
||
|
|
001b0e9617 |
Fix/preserve deleted printer (#16500)
* fix(windows): preserve manually deleted printers Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): distinguish printer detection failures Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): skip printer detection when disabled Signed-off-by: fufesou <linlong1266@gmail.com> * fix(flutter): load printer status asynchronously Signed-off-by: fufesou <linlong1266@gmail.com> * refactor(flutter): remove printer status mounted checks Signed-off-by: fufesou <linlong1266@gmail.com> * fix(msi): preserve deleted printers in direct upgrades Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): preserve deleted printers in silent EXE installs Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): skip printer installation when detection fails Signed-off-by: fufesou <linlong1266@gmail.com> * fix(installer): default printer off when detection fails Signed-off-by: fufesou <linlong1266@gmail.com> * fix(printer): limit checked enumeration to presence queries Signed-off-by: fufesou <linlong1266@gmail.com> * fix(installer): allow cancellation during printer detection Inline MSI update command construction without changing its arguments. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(flutter): time out installer printer detection Signed-off-by: fufesou <linlong1266@gmail.com> * chore Signed-off-by: fufesou <linlong1266@gmail.com> * test(windows): cover multilingual install app names Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
0bc2473ffe | fix(drm): size a plane-rotated scanout as the mode transposed (#16444) | ||
|
|
3f4bfe535e |
Fix/wayland input keyboard layout (#16462)
* fix(linux): respect Wayland keyboard layouts for uinput Resolve printable keys from GNOME input sources or the Wayland keymap and KDE active layout group. Refresh layout metadata on input at most once per second, and preserve the keycode and owned modifiers until release. Preserve the original Legacy character on client key release and handle Caps Lock shortcuts without introducing an unwanted Shift modifier. Validated with GNOME/KDE full-service input and disconnect checks, plus Windows/Linux native keyboard capture and release checks. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): harden Wayland keyboard layout detection Keep the last usable map on transient discovery failures and preserve the legacy character path when no map is available, with explicit warnings. Retain each injected key representation through release and handle IBus default layout and variant metadata without compiling a layout named default. Use native GNOME and Plasma sources, including KWin sessions without the optional layout service. Read the Xwayland keymap and effective group from the same keyboard on other desktops. Load XCB/XKB libraries dynamically and accept the unix/:N local display spelling. Validation: 24/24 temporary Linux production-module and IPC checks passed, including an actual read-only Xwayland query. The two latest routing/display regressions failed before their fixes. Rustfmt and git diff --check passed. No Cargo/Flutter commands or full remote application tests were rerun. Xwayland state freshness and synchronous desktop I/O remain known limits. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): bound input waits for layout refresh Run throttled Wayland layout discovery in a single background worker and give the triggering input a 25 ms wait budget. Later input uses the existing map without waiting or queuing more queries. Preserve source error handling and skip known Legacy clipboard-only text. Document Xwayland freshness and per-character modifier ownership limits. Desktop calls remain uncancelled; the budget bounds input waiting, subject to OS scheduling. Validation: Linux module compilation with cached dependencies, 23 existing temporary module/IPC checks, and stalled-XCB timing probes passed. Rechecked cache/error/release cases after the final lock-scope change. No permanent regression tests or Cargo/Flutter commands. Signed-off-by: fufesou <linlong1266@gmail.com> * docs(linux): record layout query latency measurements Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): reply to failed uinput key state queries Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): prefer non-keypad character mappings Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): preserve text after unmappable uinput characters Continue processing a text sequence when one character cannot be resolved in the host layout. Return the first mapping error after the supported characters are injected, so the existing throttled log still reports failure. Keep modifier-query, injection and release errors fail-fast. This fixes German a^bc losing bc without adding clipboard or fixed-US fallback. The unsupported caret remains an explicit mapping error. Validation: three temporary Linux checks (nine inputs) passed using the production resolver and sequence methods, real framed IPC and native XKB decoding. The suffix check failed before the fix. Formatting and diff checks passed; no permanent tests, Cargo/Flutter commands or full app build added. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): preserve Latin shortcuts on non-Latin layouts Detect Ctrl/Alt/Meta independently of CapsLock. If the active XKB map lacks an ASCII shortcut letter, reuse the existing physical letter mapping while preserving explicit Shift and the press-time key for release. Existing layout mappings and unsupported ordinary text retain their behavior. Honor disable-uinput-layout-fallback and log this compatibility path with the existing throttle. Validation: 12 temporary Linux production-module/IPC checks passed, including 43 non-Latin shortcut/layout/lock/Shift cases. The new checks failed before the fix. Formatting and git diff --check passed. No real mobile/compositor session or Cargo/Flutter build was run. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): preserve missing punctuation shortcuts Allow missing ASCII graphic shortcuts to reuse the existing physical key table when Ctrl, Alt or Meta is active. This restores Ctrl+[ and Ctrl+] with a valid Russian layout instead of discarding their character events. Retain the table's Shift requirement for punctuation and use existing modifier ownership and press-time release tracking. Uppercase letters do not synthesize Shift. Active-layout mappings, ordinary text errors and disable-uinput-layout-fallback retain their behavior. Validation: 14 temporary Linux production-module/IPC checks passed after recompiling the final source with rustc and cached dependencies. Includes 19 punctuation/lock/Shift combinations and 43 existing Latin-shortcut cases. The two new checks failed before the fix. Formatting and git diff --check passed. No Cargo/Flutter build or real mobile/native Wayland session was run. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): respect held Shift when resolving shortcuts Derive Shift-held candidates from the same XKB keymap and query the caller's Shift state for character shortcuts. On US layouts, Ctrl+Shift+< now uses the comma key instead of the extra ISO key that produces >. Keep caller-held Shift out of synthesized modifier ownership. Preserve ordinary input and letter-shortcut identity, and do not replace an ordinary shortcut with a keypad alias to satisfy the held-Shift lookup. Validation: 16 temporary Linux production-module/IPC checks passed with native XKB event replay and cached dependencies via rustc. Includes 30 US text/shortcut cases and 40 UK/DE/FR character/lock cases. The new Ctrl+Shift+< check failed before the fix. Changed sections pass formatting and git diff --check. No Cargo/Flutter build or real mobile/native Wayland session was run; device-state and packet-modifier handling use fixtures. Signed-off-by: fufesou <linlong1266@gmail.com> * docs(android): document paired-punctuation input limitation Signed-off-by: fufesou <linlong1266@gmail.com> * docs(linux): explain Xwayland layout synchronization limits Document the focus-scoped modifier updates observed on GNOME and KWin. Core and physical X11 keyboards can retain an old layout group while native Wayland windows have focus; reconnecting or waiting cannot request the missing update. Validation: 16 existing module/IPC probes and the live GNOME mapping probe passed. Live KDE queries reproduced native French versus stale Xwayland US. Formatting and diff checks passed. This is a comment-only change; strict Xwayland-only behavior is preserved. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): query native Sway and Hyprland keyboard layouts Read the uinput keyboard's active layout through compositor IPC and validate its group against the native Wayland keymap. Match the IPC server to the Wayland session, and accept numbered Hyprland device names while rejecting ambiguous keyboard selections. Validation: 21 temporary production-module and framed-IPC checks passed. Live Sway US/FR group and a/q mapping checks passed with Xwayland disabled; the GNOME French layout probe also passed. Formatting checks passed. A live Hyprland session and a full application build were not tested. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): defer unverified native compositor layout queries Remove the Sway/Hyprland adapter that paired a uinput device's layout group with a seat keymap based only on matching layout names. Different device options can make a synthesized AltGr key toggle CapsLock instead. Restore the existing best-effort Xwayland source for other desktops and document the device-keymap information missing from the examined native queries. GNOME/KDE discovery and injection/cache behavior are unchanged. Validation: 16 temporary production-module/IPC checks passed after the removal, plus read-only GNOME French and Xwayland mapping checks. Rust formatting and diff checks passed. No full application build or mobile end-to-end test was run for this follow-up. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): reject unreliable GNOME per-window layout sources GNOME restores per-window layouts without persisting MRU. Reject MRU selection when per-window input sources are enabled with multiple sources, and invalidate the cached map and source identity for this typed error. Transient discovery failures still retain the last valid mapping. Reuse the existing logged/disableable compatibility policy. This contains stale-map acceptance; it does not add effective per-window layout discovery. Single-source and global-source GNOME configurations retain their path. Validation: the old code failed the temporary stale-cache regression. All 3 focused production-module checks pass after the fix, covering cache invalidation/recovery, compatibility modes, single/default source mapping, UK punctuation, and last-good retention after an IBus connection failure. Compiled with rustc and cached Linux dependencies; formatting/diff checks passed. No permanent tests, Cargo/Flutter build or mobile session added. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): reject inactive IBus layout sources Read the current GlobalEngine description instead of looking up the persisted MRU engine by name. GNOME can suppress IBus for password entry without updating MRU, leaving the old engine's layout valid but inactive. Report an engine-ID mismatch as UnreliableSource so the existing cache invalidation path drops that mapping. Keep transient query-error handling and layout/variant normalization. Do not infer a US compositor layout from GNOME's xkb:us::eng echo engine. Validation: the old code failed the inactive-engine cache and default-layout checks. All 3 temporary production-module/D-Bus checks pass with this fix, covering invalidation/recovery, compatibility policies, metadata sentinels, query failures and ordinary French XKB selection. Compiled with rustc and cached Linux dependencies; formatting/diff checks pass. No permanent tests, Cargo/Flutter build or live password/mobile session added. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): prewarm uinput layout discovery at startup Start the existing layout worker when the uinput keyboard client connects, before the server accepts remote input. Reuse its throttle and single-worker logic; keep the existing 25 ms input wait and background completion policy. Prewarming reduces cold-start fallback without introducing input queues or promising readiness while a desktop query remains outstanding. Verified with three temporary Linux production-module checks using cached dependencies: a 125 ms query resolves UK @ before first input, a 200 ms outstanding query stays nonblocking and publishes its map, and a failed startup query retries successfully. All three fail their startup assertions before the change. No permanent tests or Cargo/Flutter builds were added. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): preserve first input wait during layout prewarming Keep the startup query's completion receiver until the first relevant input takes it. That input can wait for the already-running query within the existing 25 ms deadline, without holding the receiver handoff lock. Later input remains nonblocking and the existing single-worker guard is unchanged. Prefer a due refresh to an old completed startup notification. Verified with four temporary Linux production-module checks compiled with rustc -C opt-level=3 and cached dependencies: joining a pending UK query, one bounded wait followed by nonblocking input, due refresh after prewarm, and recovery after query failure. Both lost-wait checks fail before the fix. Formatting and git diff --check pass. No permanent tests or full build added. An unoptimized delayed-query probe still exceeded 25 ms and used fallback; this restores the wait opportunity, not an initial-readiness guarantee. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(uinput): remove unnecessary change Signed-off-by: fufesou <linlong1266@gmail.com> * refactor(linux): simplify uinput character releases Release the mapping stored for the same character. Linux character input already pairs down/up events, so opposite-case matching is unnecessary. Validation: git diff --check passed. Static comparisons confirmed that Map-mode dispatch, raw-key injection and keycode offsets match the PR base. No Cargo/Flutter commands or live-session tests were run. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): normalize Unicode CapsLock shortcuts Normalize single-scalar Unicode lowercase for CapsLock shortcuts without adding Shift. For lowercase expansions, resolve the original symbol with the actual CapsLock state instead of truncating it. Document the existing first-seat discovery limitation. Validation: two temporary Rust context checks against lookup fixtures failed before the fix and passed afterward. No Cargo/Flutter build or end-to-end input test was run. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): derive CapsLock shortcut keys from XKB Keep the actual CapsLock state when resolving shortcut characters. Use the existing Caps-off and Caps+Shift mappings to omit generated Shift only when XKB confirms the same physical key and remaining modifiers, preserving the Turkish I/ı and İ/i key identities. Validation: three temporary production-module checks passed on 192.168.5.32 with native libxkbcommon; two failed before this fix. Covered Turkish, German, Cyrillic and ASCII shortcuts, explicit Shift, ordinary text and UK punctuation. No full application build, IPC or end-to-end input test was run. Signed-off-by: fufesou <linlong1266@gmail.com> * refactor(linux): remove CapsLock shortcut special cases Use the existing lock-state synchronization and ordinary XKB mappings for mobile soft-keyboard input. Remove CapsLock-specific shortcut remapping and compatibility-path lowercasing while retaining held-Shift punctuation handling. Validation: rebuilt the Linux Rust library and reused the unchanged Flutter UI. Actual Android Gboard input matched UK punctuation on GNOME and KDE; French/German switching on KDE passed after the refresh interval. The user confirmed real iPad input on both hosts, including smart quotes. Formatting and whitespace checks passed. The optional Ctrl/Shift toolbar automation was inconclusive and is not counted as passing coverage. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): press Level3 before Shift for uinput characters Preserve mapping candidate preference while storing generated modifiers in Level3-before-Shift order. This avoids activating Compose with lv3:ralt_switch_multikey; releases retain their existing reverse order. Verified on Linux with temporary production-mapper/libxkbcommon tests: Lithuanian ! fails before this change and passes afterward; all 24 UK punctuation/lock-state cases pass with released modifiers and unchanged lock state. Formatting and diff checks pass. No full application build or mobile end-to-end test was run for this change. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): preserve legacy uinput IPC without a keymap Send sequences and character clicks through their original IPC messages when the layout cache is unavailable, avoiding unnecessary synchronous key-state queries. Keep throttled fallback warnings and leave layout discovery, retry policy, and key-down/up pairing unchanged. Verified with three temporary production-method checks on Linux using a recording transport and injected query errors. The unavailable-map routing check fails before the fix and passes afterward; raw-click routing and available-map query-error handling remain unchanged. Changed-code formatting and diff checks pass. No full application build or end-to-end input test was run. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): skip layout queries for legacy uinput fallback When no keymap is available, single-character input must use the legacy KeyDown/KeyUp path without querying lock or modifier state first. Retain the legacy key in the existing press record so discovery completing before a repeat or release cannot change the selected representation. Validated with four temporary production-method checks on Linux using a controlled cache/resolver and recording transport: missing-map fallback with query failures, press/release across cache transitions, unchanged raw-key dispatch, and Ctrl+C without extra Shift or queries. The initial fallback check failed before this change. Valid-map errors still surface. Changed-code formatting and git diff --check pass. No full Cargo/Flutter build or live service/mobile test was run for this change. Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
9b9e5f113a |
fix(wayland): place a portal stream at the only output of its size (#16451)
* fix(wayland): place a portal stream at the only output of its size xdg-desktop-portal-hyprland reports every stream at (0, 0). With no output at the origin, sort_streams dropped the stream; with an output of another size there, the stream took its origin and skipped try_fix_logical_size, so the client sent physical pixels from the wrong origin and the pointer landed on another output. With more than one output, a stream whose position is not the origin of an output of its size (a rotated output counts in either orientation) now takes the origin of the only output of that size; with several outputs of that size, or none, the position is kept. A position found by try_fill_positions names an output of its size, or no output has its size, so later calls keep it while the outputs and the measured size do not change. Refs #15731 * fix(wayland): reconcile portal positions with measured sizes only The first commit corrected any position that named no output of the stream size, trusting the size. That size is the portal size when get_res() fails, and it can be the mode of another output, so a correct portal position could move onto the wrong output. A stream now records whether its position came from the portal and whether its resolution was measured, and a portal position is overridden only by a measured size. Streams without a portal position go through try_fill_positions as before; one it leaves unresolved, such as a rotated output whose transposed size no mode matches, now takes the origin of the only output of its size. Refs #15731 * fix(wayland): only a measured size moves a stream position The reconciliation after try_fill_positions let a position through to corrected_origin unless the portal had sent it. A position that try_fill_positions restored from its cache is not from the portal, so when get_res() failed and the size was the portal size, a cached (1920, 0) with a 1440x900 portal size moved to the 1440x900 output at (0, 0), and with both outputs shared sort_streams dropped one of the streams. Only a measured size moves a position now, whatever its source, so position_from_portal goes away. |
||
|
|
1d4abd0258 |
fix: resolve HTTP API 407 errors through HTTPS proxies (#16496)
Upgrade reqwest to 0.12.28 to include the fix for missing Proxy-Authorization headers when forwarding HTTP requests through HTTPS proxies. Signed-off-by: 21pages <sunboeasy@gmail.com> |
||
|
|
6da7977a94 |
fix(video): defer refresh teardown until hw encoder warms up (#16459)
* fix(video): defer refresh teardown until hw encoder warms up
On macOS, hevc_videotoolbox (the only encoder there reporting
!latency_free()) can return no packet for its first encode call. The
OPTION_REFRESH check in run() tore the encoder down with
bail!("SWITCH") inside that window, so each new encoder was destroyed
before producing a frame and rebuilt about once a second. With H265
selected on macOS 27 / Apple silicon, the picture froze or the session
dropped every 1-3 minutes.
On macOS, once the encoder has been fed a frame, defer the refresh
until it emits its first packet. Before that there is no warm-up to
lose, so a refresh can still restart a stalled capturer. Other
platforms and every latency-free encoder behave as before. An encoder
that never emits is still switched away from by the unchanged
max_fail_times path. The only new state is one flag.
Verified on macOS 27.0 / 27.0.1, M5 Pro, 2560x1440 H265. Soaked for two
weeks on a 1.4.9-based build carrying this deferral:
- before: ~40 "no valid frame" and ~27 refresh teardowns per 60s
- after: 37 sessions, every "no valid frame" at times:1, so
max_fail_times was never approached; H265 never lost
Fixes #16457
Signed-off-by: David Riding <48262524+dmriding@users.noreply.github.com>
* Update video_service.rs
---------
Signed-off-by: David Riding <48262524+dmriding@users.noreply.github.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
|
||
|
|
25d2c6db55 |
log key-up and text injection failures (#16455)
* fix(macos): log key-up and text injection failures Report failed key releases through the rdev input path and missing sources, mappings, or events in the Enigo keyboard path. Throttle repeated failures without logging text contents or changing input behavior. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(macos): refine keyboard failure diagnostics Remove key identities from virtual-input and Enigo failure logs while preserving operation and failure details. Also report key-down event creation failures in key_click with the existing throttle, without changing event posting or error propagation. Signed-off-by: fufesou <linlong1266@gmail.com> * refact(logs): update rdev, add diagnostic logs Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
f0bd880fe7 |
Update nl.rs (#16485)
lang: Dutch language file updated - various consistency fixes - ... -> … - empty values translated |
||
|
|
a916af8f00 | Translate voice call audio capture prompt to pt-PT (#16482) | ||
|
|
9f9585ce15 |
Remove ConfigUpdate handling
Clients no longer apply a rendezvous-server list pushed by the rendezvous server. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PkpVy2ufxgEjKd3SjXPnJ8 |
||
|
|
e8a865924b | Updated Polish translation (#16456) | ||
|
|
c9c0b5d0ef |
Fix translation for voice call audio capture prompt (#16433)
* Fix translation for voice call audio capture prompt * fix: translation, audio capture prompt, wrong "Share screen" Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: fufesou <linlong1266@gmail.com> |
||
|
|
5406950b02 |
fix(macos): open Screen Recording settings from Configure (#16452)
CGRequestScreenCaptureAccess shows its prompt only once per app. After the user denies it, or after an update changes the code signature, pressing Configure on the Screen Recording banner did nothing. Open the Screen & System Audio Recording pane as well, so the button always leads somewhere the user can grant access. Co-authored-by: Eric Mason <17150+ericmason@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
685fa2e4a1 |
fix(drm): the startup cache warm does not wake sleeping displays (#16437)
* fix(drm): the startup cache warm does not wake sleeping displays
Every `_drm` connection ran the display wake before the service answered with
its list, so the cache warm that each --server start runs (the service restarts
the --server every hour) woke displays that had idled off, with no client
connected (rustdesk#16356).
The --server now opens the handshake with DrmHello{wake}, and the service wakes
and settles only when asked. The warm asks without a wake; the availability
probes, the login refresh and the capture stream keep it, since a client is
waiting for them. Without a wake the service still clears the wake latch of a
connector it sees lit, as the warm did.
A warm that finds nothing lit leaves the cache without a verdict, where its own
wake used to make it Available. So a login that the DRM path can serve now
probes on a missing verdict before anything reads the cache (the greeter check,
the PipeWire prompt, the display list). That probe wakes the displays as the
warm did, but only when a client logs in.
* fix(drm): throttle the log of a peer that does not open with DrmHello
AGENTS.md: a log call that can fire at a rate a peer controls must not use
debug or higher unthrottled. Same 5 s interval as the rejected-IPC logs in
ipc/auth.rs.
* fix(drm): the startup warm keeps a list a login published while it queried
Per review: the warm samples the state generation before its query and
publishes only if no other probe published since, the check the two
background refreshes and the login refresh already use.
* fix(drm): log a failed login settlement task instead of dropping its error
Per review: the JoinError of the blocking task was discarded with `let _ =`.
|
||
|
|
e5bc204fe4 | fix(wayland): avoid clock waits in PipeWire capture (#16430) | ||
|
|
fada664df7 |
fix(clipboard): unix, keep Windows directories searchable (#16406)
A directory pasted from Windows with FILE_ATTRIBUTE_READONLY or FILE_ATTRIBUTE_HIDDEN (Git for Windows hides `.git`) was given mode 0444 or 0400 in the FUSE list, because the read-only and hidden branches came before the directory branch. Without `x` the directory cannot be entered once a mode-preserving copy lands it on disk. Windows does not restrict access to a directory for either attribute, so give every directory 0755, as a plain one already gets. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1.5.0 |
||
|
|
9d3ae7acd2 |
fix(windows): restore Flutter view visibility after FancyZones placement (#16397)
* fix(windows): restore Flutter view visibility after FancyZones placement Signed-off-by: 21pages <sunboeasy@gmail.com> * fix(windows): avoid stale resize targets during Flutter refresh * fix(windows): update multi-window dependency for FancyZones --------- Signed-off-by: 21pages <sunboeasy@gmail.com> |
||
|
|
a7f2260203 | fix(clipboard): unix, don't wrap FILETIMEs past 2554 (#16385) | ||
|
|
40a4aa34c8 |
Don't retry the _pa ipc at a Linux login screen (#16390)
At a greeter the cm is not started, so the `_pa` socket never exists and the audio service fails to connect and logs an error once per second for as long as a client is connected. Wait in `pa_impl::run` while `is_prelogin()` is true; audio starts once a user logs in. Fixes #16012 |
||
|
|
0c18a8cbc9 |
fix(flutter): restore multi-window rendering after reconnect (#16383)
Use the window's current display when lastUserDisplay is unset so additional monitor windows restore their capture subscriptions. Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
4812a9815b |
AGENTS.md: limit corner-case growth and test bloat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b6b11fd9b1 |
fix(drm): apply the uinput range on one thread instead of holding a lock across the await (#16362)
* drm: apply the uinput range on one thread instead of holding a lock across the await #16122 serialized the three paths that apply a uinput range in a DRM session (the layout poll, `update_uinput_resolution` and `check_init`) with a tokio Mutex held over the awaited `update_mouse_resolution`, which AGENTS.md rules out ("Do not hold locks across `.await`"; greptile on #16122). The serialization itself has to stay: the uinput service keeps the range of whichever request reached it last and each apply sends it over its own connection, so two overlapping applies can leave the device on one range while the other one is recorded. The check, the apply and what it records now run as one job on a dedicated thread (`run_uinput_apply`), one job at a time in the order asked, with the runtime of that thread for the IPC and its 3 s timeout. The async paths await the answer and the display service loop blocks on it; nothing holds a lock while it waits. The poll publishes its drift flag inside its job, as it did under the lock. A thread that failed to start or died is started again for a later job, so one failure does not stop every later apply (the poll used to build a runtime per attempt and retry). The drm-off build keeps the code it had. Tests (in `input_map_tests`, which CI runs): uinput_applies_run_one_at_a_time_in_the_order_asked, a_uinput_apply_job_can_bound_its_ipc_with_a_timeout, a_uinput_apply_thread_that_died_is_started_again. Mutations: every job on its own thread, the apply runtime without timers, the answer never sent, a dead thread not started again; each turns a test red. The call sites run IPC and are covered by reading. * drm: log a layout poll whose uinput apply job got no answer The layout poll dropped the result of its job with let _ =, so a poll whose job was dropped (the apply thread failed to start or went away) left no trace, while update_uinput_resolution and check_init log it (greptile and CodeRabbit on #16362). It logs now. The drift flag keeps what the last poll that ran published; a later poll starts the thread again and publishes it. * drm: throttle the uinput apply thread failure logs The layout poll hands the apply thread a job every 1.5s, so a thread that cannot start logged two errors per poll for as long as a session lasted. Keep one line per site every 10 minutes, with the count of the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d1722c5d6d |
fix(linux): use BUS_VIRTUAL for the virtual mouse device (#16382)
The uinput virtual mouse RustDesk creates on Linux reported bustype BUS_USB. libinput's evdev_tag_external_mouse() tags any BUS_USB or BUS_BLUETOOTH pointer device as an external mouse, so desktop environments' "disable touchpad when an external mouse is present" setting suspends the user's real touchpad whenever RustDesk creates this device, even though nothing is physically plugged in. Changed the constant to BUS_VIRTUAL (0x06, verified against the current upstream kernel's include/uapi/linux/input.h), which correctly identifies this as the software-only device it is. Fixes #16318. Signed-off-by: Mohsin Manzoor Bhat <mohsinmanzoor1913@gmail.com> |
||
|
|
0d49ead0c3 |
fix(drm): turn a captured frame only when the plane did not rotate it (#16345)
* fix(drm): turn a captured frame only when the plane did not rotate it A compositor rotates an output either in hardware, setting the primary plane's `rotation` property, or in software, drawing the scanout already turned. `wl_output` cannot tell the two apart, and `frame_transform` guessed: 90/270 turned, 180 left alone, which is right for i915 + mutter (rotate-180 in hardware, scanout upright) and wrong wherever the compositor drew the scanout turned, where 180 comes out upside down. Measured before changing it. virtio-gpu (no rotation property, mutter 46): the scanout dump at 180 is the desktop upside down, at 90 and 270 the logical desktop turned inside the native mode framebuffer. i915 + mutter (GNOME 50) at 180: the plane reports rotate-180 and the dump is upright, identical to the unrotated one. amdgpu + KWin 6 at 180: the plane stays at rotate-0 and the dump is upside down. The producer reads `drmtap_plane_rotation()` (libdrmtap 0.5.8, optional symbol) right after each grab and stamps it on the frame, in the dma-buf descriptor and in the CPU frame header, both `serde(default)` so an older producer still parses. A plane that rotated scans out the logical desktop whatever the output transform says: the compositor programs it from the CRTC transform, which also folds in the connector's panel orientation that `wl_output` never carries, so the consumer leaves such a frame alone. A plane at rotate-0 scanned out what the compositor drew, and the frame is turned by the whole output transform. `None` (a library before 0.5.8, or nothing it could read) keeps the previous rule, so nothing changes until the library says otherwise. The session is still sized from the output transform: a plane that rotated 90 in hardware hands over the portrait scanout those dims already name. * build: pin libdrmtap 0.5.8 (rustdesk-org/libdrmtap 95d4d7454) The rotation fix reads drmtap_plane_rotation(), added in libdrmtap 0.5.8. The mirror carries that commit since 2026-09-25, so the pin moves there. * drm: pin the decoding of a frame from a producer older than the plane rotation The root service and the --server are upgraded separately, so a frame header or a dma-buf descriptor without `plane_rotation` must still decode, as None, which keeps the previous rule. The tests built both messages with the field set to None; this one decodes payloads that omit it, and one that carries it (greptile on #16345). Serde already reads a missing Option as None, so what the test pins is the wire name and the None on absence, for both messages. Mutation: renaming the field on the wire, in either message, turns it red. * drm: take the plane rotation right after the grab, not after the copies `drmtap_plane_rotation()` reads the rotation property of the plane the last grab came from at the moment it is called. The cpu path asked only after `DrmReader::grab()` had copied the frame into its buffer and the producer had copied it again into the message, so on a large frame the compositor had time to change the plane and the frame went out with the rotation of the next one; `frame_transform` turns that frame by it with nothing to absorb the skew (zhou, review of 26-sep). `DrmReader` now reads the rotation right after a successful `drmtap_grab_mapped` or `drmtap_grab_desc`, before any copy or release, and `plane_rotation()` returns that snapshot, so both paths stamp a frame with the rotation its own grab saw. Test: a_frame_keeps_the_rotation_its_plane_had_when_it_was_grabbed, in `plane_rotation_tests` (which CI already runs), drives the reader through a fake library whose frame release turns the plane: the rotation read at the grab survives on the cpu grab and on the dma-buf export. Mutations: no snapshot on the cpu grab, none on the export, and the cpu snapshot taken after the release, each turn it red. |
||
|
|
be9b96cf65 | Translate voice call instruction to Spanish (#16358) | ||
|
|
89fcf56c4e |
fix(clipboard): unix, don't let the next file's preload fail a request (#16364)
After serving a request for file N, `serve_file_contents` preloads the next non-directory file with `load_handle()?`. When that file can no longer be opened (deleted or renamed after the copy, or unreadable), its error replaced N's reply, so every size and range request for N failed although N itself is intact. Keep the preload best-effort. Its failure is logged at trace, because it can fire on every request for N, and N+1's own request still reports the error. Signed-off-by: chlee <sourcehatchery@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8a76628007 |
fix(clipboard): unix, use the real Windows FILETIME epoch offset (#16366)
`LDAP_EPOCH_DELTA`, the offset from the FILETIME epoch (1601-01-01) to the Unix epoch in 100 ns units, was 116444772610000000. The real offset is 116444736000000000 (11644473600 s), so the old value is 3661 s (1h01m01s) too large. Unix peers encode and decode with the same constant, so the error cancels between them, but Windows sends and reads real FILETIMEs: files copied from Windows got a modified time 1h01m01s too early on Linux and macOS, and files copied from Linux or macOS were sent one 1h01m01s too late. Signed-off-by: chlee <sourcehatchery@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b5d8b979d8 |
i18n(tw): use the Screen Share page label in the voice call hint (#16355)
The hint referenced the "Screen share" page as 「螢幕分享」, but the actual Traditional Chinese label of that page is 「僅分享螢幕畫面」 (the value of the "Screen Share" key). Match it, as zh-CN does with "仅共享屏幕". Signed-off-by: Mosquito1123 <zhangwentong1123@icloud.com> |
||
|
|
44683d108b |
fix(macos): clear Finder progress after clipboard transfers (#16346)
* fix(macos): clear Finder progress after clipboard transfers Signed-off-by: fufesou <linlong1266@gmail.com> * test(macos): cover single-response clipboard completion Verify progress completion, handle cleanup, and finalized file contents. Move function-only Finder imports into progress helpers. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(macos): unpublish clipboard progress before renaming Complete and unpublish NSProgress before the final file rename to avoid stale Finder and Dock progress indicators. Signed-off-by: fufesou <linlong1266@gmail.com> * chore: add ref Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
50258e21e9 |
fix(drm): measure the cursor hotspot on drivers without HOTSPOT_X/Y, confirm before publishing (#16122)
* drm: carry the cursor plane position on the frame header
CursorSnapshot gains the plane position x/y. It rides DmabufDesc.cursor_pos
and Data::DrmFrame.cursor_pos as a serde-default Option, so a producer that
predates the field reads as None on the consumer, and a consumer that predates
it ignores the key. The producer reads the cursor once per delivered frame,
after the grab, so a WouldBlock retry does no cursor work; the position is a
few ms newer than the frame, which the consumer tolerates because it only
counts consecutive identical positions. The consumer destructures the field
and does not use it yet.
* input: remember the last absolute peer move and forget it on any other move
The DRM cursor calibration subtracts a cursor plane origin from the point this
process injected, so the point has to be the post-remap uinput one and nothing
else: LATEST_PEER_INPUT_CURSOR stores an X-server coordinate on the relative
arm. The new sample carries an Instant, a sequence number (two measurements
against one sample are not independent) and the layout generation it was
mapped against. A relative delta and a move this process makes itself forget
it. display_service exposes the existing drift flag to readers.
* drm: resolve the cursor calibration context once, with the capturer
CalContext { rect, physical_size, built_gen } is computed in
IpcDrmCapturer::new from the same wayland snapshot the transform comes from,
and only when the snapshot is complete, the output is an identity match, the
unfolded transform is 0 (180 included) and the geometry is valid. It is
stored in Shared before the transform's Release store, so a receive thread
that has seen the transform sees the context. Nothing reads it yet.
* drm: measure the cursor hotspot and confirm it before publishing
On a driver without HOTSPOT_X/Y the wire carries infer_hotspot's guess, which
the bitmap cannot get right for a shape whose click point the theme put where
the alpha does not mark it. This process injects the tip and the plane origin
rides every frame: once both are still, tip - origin is the hotspot.
Only a guessed shape is measured. The first measurement is a candidate and
changes nothing visible. A second measurement confirms it only from another
stable plane position and another peer sample, and then the retained
candidate is the one value that reaches the cache, the served hotspot, the id
remix and the delivery. Near what is served, nothing changes, even when the
value is also near the wire, so a correction a few px from the wire does not
flap; near the wire otherwise, the wire is served and the cached correction is
dropped. A stable position is measured at most once, a layout generation
change stops measurement once the display service's poll has seen it, and the
cache is read only behind the context gate, so a correction measured upright
never reaches a rotated output.
Each of these gates has a test that goes red with the check removed or
altered (file restored byte for byte afterwards):
publish the first measurement -> retained candidate, same sample, still plane, local nudge
same sample confirms -> same sample never confirms, local nudge
confirm with the new value -> retained candidate, near wire, in band
drop the near-wire arm -> near wire serves the wire
drop the in-band arm -> in band changes nothing visible
drop the re-store after clear-on-cap -> in band stays cached
measure a kernel-measured hotspot -> never calibrated at the origin
read the cache without a context -> not served without a context
drop the once-per-position flag -> still plane measured once
drop the transform gate -> context only for an upright output
swap phys and extent -> the arithmetic, the scaled context
drop the clear-on-cap -> cache bounded
drop the partial-snapshot gate -> context only for an identity match
drop the generation check -> closed intervals
compare the wire before the served -> in band with both keeps the served correction
* drm: a cached hotspot correction is served only under the generation it was confirmed in
The calibration cache mapped a wire cursor id to its confirmed hotspot
and nothing else. A pair of stops taken inside the layout poll window,
after a monitor moved and before the poll saw it, could confirm a value
measured against the old layout; the generation gate then stopped that
stream, but the capturer rebuilt after the promotion seeded its served
hotspot from the cache and replayed the value under the new layout, until
another pair of its own replaced it (zhou, review of 25-sep).
Each entry now carries the layout generation it was confirmed under, and
a stream reads the cache under its own `built_gen` only: a value from
another generation is not served, the wire hotspot is, until a pair on
this generation confirms one. Checked at the read rather than by clearing
on promotion, and an entry is replaced or dropped only by its own
generation or a newer one, the bound included (at the cap the cache
clears what the writer's generation or an older one confirmed, and
caches nothing if only newer entries are left), so a receive thread
still running on the old layout can neither serve its value to the
rebuilt stream nor undo the value that stream confirmed.
Tests: a_correction_confirmed_under_an_older_generation_is_not_served (two
stops under one generation, the stream rebuilt under the next, its own
pair, then the old stream writing late),
the_bound_never_drops_an_entry_from_a_newer_generation. Mutations:
reading without the generation filter, replacing or dropping a newer
entry from an older generation, and at the cap either clearing
everything or caching when only newer entries are left, each turn one
of them red.
* drm: calibrate only against samples injected under the layout's adopted uinput range
`usable_sample` took the remap flag being clear as "the input mapping is
ready". It is not: the display service bumps the layout generation and
promotes the baseline before the uinput range update completes, and a
failed or pending update leaves the device mapping absolute coordinates
with the range of the previous layout. Every sample then carries the new
generation while its point lands scaled by the old range; two stops share
that error and agreement confirms it (zhou, review of 25-sep: a desktop
narrowed from 4000 to 3800 px puts a hotx of 4 at 54, inside the bitmap).
The display service now keeps the layout generation whose uinput range
the device runs, and a count, raised when an apply starts and again when
the device acknowledges it. Both move when `update_mouse_resolution`
returns Ok, on the DRM session-init path (also run by the hotplug
worker) and in the layout poll; the session-init path labels the range
with the generation read before it computed the rect, and the raw DRM
union it falls back to when the compositor cannot be asked with no
generation, so no stream measures on it until the poll applies or finds
a compositor range. A range that already fits the layout moves the
generation only, so the samples injected under it stay valid. From the
moment an apply starts, on any path, no generation is ready, the count
is raised and the recorded rect is forgotten, so an apply that fails or
times out (the device may still take the range late) leaves nothing
ready, and only an acknowledged apply records a generation again. The
poll and `update_uinput_resolution` run on different threads; they
check, apply and record under one lock. `check_init`, which a display
falling back to PipeWire runs inside a DRM session, records its apply
the same way (cfg(feature = "drm") lines only; the drm-off build is
unchanged). It has no fits check and applies on every run, as before, so
each run moves the count. A path that read its generation before the
poll bumped it records under the older one; no stream measures then
until a later layout check records the current one. A sample is stamped
under the ENIGO guard of its move, which an adoption also needs for its
refresh, so a move stamped with the count of an adoption was injected
after that adoption's refresh. The count is raised before the generation
is published and read after it. A stream measures only while that
generation is its own; each peer sample carries the count at injection
and is a reference only under the current one; a candidate measured
under one count starts over instead of confirming under the next. The
remap flag is untouched.
Tests: a_stale_input_mapping_publishes_nothing_until_fresh_samples_follow_the_adoption
(generations equal, no drift, the device on the previous range: two
stops publish and cache nothing; after the adoption a sample from before
it does not count, and fresh ones confirm),
a_sample_is_a_reference_only_under_the_adopted_input_mapping,
a_candidate_does_not_confirm_across_an_input_mapping_change,
an_acknowledged_range_is_an_adoption_and_a_fitting_one_is_not,
the_raw_drm_union_labels_a_range_with_no_generation,
an_apply_leaves_no_range_ready_until_it_is_acknowledged; the drm CI step runs
input_map_tests. Mutations: dropping the adopted-generation gate,
dropping the count gate, confirming across counts, an adoption that does
not count, a fitting range that counts, the union labelled with the
generation, and an apply that keeps the recorded rect or leaves a
generation ready, each turn one of them red. The call sites (the lock,
the calls before and after each apply on the three paths, the range-fits
branches) run IPC and are covered by reading.
* drm: forget the peer sample under the mouse lock on a move of this process
The temporary-move thread (the display probe `fill_displays` runs in the
PipeWire init, `check_init`) forgot the last absolute peer sample before
it took ENIGO. A peer move holding ENIGO at that moment moved the pointer
and recorded its sample after the forget, and the temporary move then
landed on top: the pointer somewhere else, the sample still the peer
point, with the current generation, count and a fresh sequence, so two
such overlaps could confirm a wrong hotspot (zhou, review of
|
||
|
|
47a2a3c83d |
i18n(tw): fill missing Traditional Chinese translations (#16353)
Fill the three empty entries in src/lang/tw.rs, mirroring the existing zh-CN (cn.rs) translations and reusing the terminology already used elsewhere in the file: - terminal-clipboard-write-tip - Allow terminal apps to copy to clipboard - To start a voice call, enable "Audio capture" on the "Screen share" page. Signed-off-by: Mosquito1123 <zhangwentong1123@icloud.com> |
||
|
|
17feabbcab |
fix(windows): allow existing session to foreground (#16349)
Signed-off-by: TayfurYldz <238304586+TayfurYldz@users.noreply.github.com> Co-authored-by: TayfurYldz <238304586+TayfurYldz@users.noreply.github.com> |
||
|
|
08affc69c4 |
Fix clipboard range overflow (#16329)
* fix(clipboard): unix file server, bound range reads The Range request fields nPositionLow and cbRequested are UINT32s carried in i32 fields, but were sign-extended to u64. A negative cbRequested became a near-u64::MAX length, `offset + length` wrapped past the clamp, and the server tried to allocate a u64::MAX buffer, aborting the process. Sign-extending nPositionLow also rejected offsets whose low word is >= 2 GiB, breaking reads past 2 GiB in large files. - decode nPositionLow and cbRequested as u32 - clamp with `length > file.size - offset` (offset <= size is checked above) - allocate the read buffer fallibly and return an error instead of aborting Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(enigo): win, scale absolute mouse moves without overflow mouse_move_to multiplied the peer-supplied coordinate by 65535 in i32, which overflows for large values, and divided by the virtual screen size, which panics if the metrics come back as 0. Scale in i64, clamp to i32, and skip the move when the virtual screen size is unavailable. Results for inputs that did not overflow are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(agents): avoid unthrottled high-frequency debug logs Debug output is written to the log file, so per-packet / per-event log sites must use trace or hbb_common's throttled_log!. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): unix file server, reject range reads over the frame limit A response larger than MAX_FRAME_LENGTH (1 GiB) cannot be encoded for sending, yet the server allocated and read it first, then failed the send. Reject such requests before allocating, so a peer can no longer make the server allocate more than 1 GiB per request. Requests that work today are unaffected. Drop the extra allocation-failure log; the caller already logs the error. Tests cover the wire decoding through read_file_contents (negative cbRequested, nPositionLow past 2 GiB) and the frame-limit rejection. Tests that use the global CLIP_FILES now take a shared lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): keep file transfers on the file list they started from A file-contents request names a file only by its index, and the side that owns the files resolved it against whatever it held at that moment. On Windows 10+ Explorer's clipboard also offers FileContents, which the owner re-read from the live clipboard for every chunk, so copying another file mid-transfer spliced the new file into the one being pasted (#16332). A second paste, or another connection's paste, replaced the list the same way, and on macOS/Linux sync_files replaced CLIP_FILES on every host copy. Each request now names its list in the existing clip_data_id field: an FNV-1a hash of the file-descriptor PDU, which both sides already hold. Owners keep the last 4 lists they sent and serve a request from the list it names; an unknown id fails instead of reading another file. Peers that send no id read the list last sent to their connection, and a Windows owner stops reading the live clipboard once it has changed since the list was built. Tests cover a host copy and a second paste during a transfer, a list id sent to another connection, and the hash's reference vectors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): unix file server, cap a single range read at 16 MiB A peer could still make the server allocate and read up to the 1 GiB frame limit per request. Cap the data a single range read returns at 16 MiB. The read is clamped to the rest of the file first, and a larger one is refused rather than shortened: a short response reads as end of file to IStream callers on Windows and would silently truncate the copy. macOS requests 4 MiB per range (BLOCK_SIZE) and FUSE reads are smaller. A Windows client forwards the application's IStream::Read size, so only an application reading more than 16 MiB in one call is affected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(enigo): win, warn (throttled) when a mouse move is skipped A missing virtual screen size is a fault that should show up in a user's log, but mouse_move_to runs per input event. Log it at warn at most once a minute via throttled_log! instead of at trace. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): review fixes for the file lists kept per transfer unix owner: - A retired list closes its files and 8 MiB read buffers; they reopen, from offset 0, on the next read. - The new list is built aside and swapped in only on success, so a failed rebuild leaves the current list in place. - A rebuild with an unchanged id keeps its peers on the new list instead of retiring a duplicate, so re-copying a directory no longer pushes out a list that a transfer is still reading. Windows: - One served-list slot per list, recording every connection it was sent to, so pastes of the same list do not use up slots. - A request for an earlier list is served before OleGetClipboard, so a busy clipboard cannot fail it. - An IStream read over 16 MiB is split into requests the unix file server accepts. Reads up to 16 MiB take the unchanged path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): give each copied file list its own id The list id hashed only the descriptors, so two copies with identical names, sizes and times (the same file in two folders) shared an id and a transfer could read the other copy's files. Each owner now writes a random nonce into the first descriptor's clsid, inside the reserved bytes every peer skips; no owner sets FD_CLSID. The paste side hashes what it received, so it needs no change, and older peers ignore the bytes. Re-sending the same paths with the same descriptors keeps the nonce, and the id. Windows owner: - Serve a request from its list before the ownership check when the list is no longer current or the host clipboard now holds files from a peer, so another controller copying onto the host no longer fails in-flight transfers. Only lists sent to that connection are served. - Evict the list sent least recently instead of in insertion order. unix owner: take everything from the crate in one use block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard,enigo): bound served-list reads, clamp absolute moves wf_cliprdr: the served-list branch runs before the ownership check, yet it allocated the peer's cbRequested (up to 4 GiB) before looking the list up. Accept only FILECONTENTS_SIZE or a range of at most 16 MiB there, which is all that clients sending an id ever request, and fail anything else before allocating. enigo: clamp the scaled absolute coordinate to 0..=65535, the range MOUSEEVENTF_ABSOLUTE takes, instead of to the i32 range. Coordinates on the virtual desktop give the same result as before; off-desktop ones now land on its edge. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): keep a file list while it is being read, throttle read errors A unix paste side fetches every new file list as soon as the host copies, so each copy retires the previous list, and four copies during one long transfer evicted the list it was still reading. A kept list that serves a read now becomes the most recent (unix: back of the retired queue, Windows: last_served), so the next eviction takes a list nobody is reading. A failed file-contents read logged an error per request, and peers can now trigger new ones (an unknown list id, an over-limit range). Log it through throttled_log! instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): unix, keep retired lists in the order they were sent A peer that sends no list id is served the list last sent to its connection, found by searching the retired queue from the back. That only holds while the queue stays in retirement order, which is the order lists were sent. The previous commit moved a list to the back on every read, so another connection reading an older list could make a peer's next chunk come from that list: a transfer of B continued with A's bytes. Keep the queue in retirement order and track recency in a separate last_used stamp, set on retirement and on each read. A full queue evicts the list with the lowest stamp, so a list still being read is kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): unix, keep files being read open when their list is retired Retiring a list closed every file handle, so the next read of a file part-way through reopened it by path. If that path had been replaced meanwhile (an editor saving over it), the rest of the transfer came from the replacement: AAAABBBB arrived as AAAADDDD. Close only files that are not being read, such as the preloaded next file; a file part-way through keeps its handle and offset, and so its identity. Tests read two files of one list alternately, retire the list, replace both paths by rename, and check each file continues with its original bytes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): cap Windows ranges, expire idle kept lists, check file identity Windows owner: refuse every FILECONTENTS_RANGE over 16 MiB before allocating. Only the served-list branch was capped; requests without an id still allocated whatever the peer asked for, up to 4 GiB. Both owners: a kept (retired / non-current) file list that nobody has read for 5 minutes is dropped, with its open files; its transfer is over or its peer gone. The current list never expires, as before. On Windows, reads of the current list through the existing path now also refresh its kept copy, so a long transfer does not lose its list once a newer one is sent. Windows owner: old-list reads compare the file's size and last-write time with the descriptor that was sent, on the handle they read from, and fail on a mismatch instead of reading whatever file now has that path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(clipboard): unix, check list expiry without backdating an Instant an_idle_retired_list_expires backdated a timestamp with Instant::now().checked_sub(TTL + 1s), which is None while the monotonic clock is under about five minutes, as on a freshly booted CI machine; the list then never expired and the test failed. expire_retired now takes the current time, and the test passes a later one instead. Callers pass Instant::now(), so behavior is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): drop read-time refresh, idle expiry and rebuild reuse; tighten checks Remove the read-time recency and the 5-minute idle expiry on both owners, and the unix reuse of an unchanged rebuild (has_same_files). They covered corner cases, added state, and each brought regressions of its own. Kept lists are again evicted oldest first (unix) or least recently sent (Windows), 4 at most; a transfer that outlives 4 newer lists fails cleanly. Windows: serve kept lists after the existing ownership check again, so a request carrying a list id no longer bypasses it while the host clipboard holds files from another session. Refuse request types other than size or range before allocating. unix: a retired list's file must still match the size and modified time it was listed with, checked on the handle the read uses; a file replaced at its path before its read now fails instead of being served. Tests: drop the four tests of the removed behavior, add a_retired_list_refuses_a_file_replaced_before_its_read. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(agents): handle corner cases raised in review with small fixes or docs A refactor added to cover a corner case rarely converges: new counters, timestamps and expiry rules interact with state existing code relies on, and the next review round finds the problems they introduced. Fix a corner case when the fix is a few lines with no new state; otherwise document it as a known limit, and ask before anything larger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
e9ddbd8f46 |
cursor: bound the decoded cursor caches on the Flutter client (#16304)
* cursor: name a shape by what it looks like, not by its handle A cursor id was the platform's handle for the shape: HCURSOR on Windows, the XFixes serial on X11. Apps mint a new handle for a shape they have shown before (Chrome, Electron, Qt custom cursors), and an X11 animated cursor gets one per frame, so one arrow arrived under thousands of ids over a session. The controlled side cached and sent it again under each, and the controller decoded, rendered and kept it again under each, and grew by gigabytes over weeks of connection. The controlled side now names a shape by an xxh3 hash of its size, hotspot and pixels, taken once when a handle is first seen, before the pixels are compressed. The per-connection send already sends a shape once and afterwards only its id, so a shape now crosses once however many handles it has. The id is opaque in the protocol, so controllers of any version accept it. The DRM backend already named shapes by content with FNV-1a; it uses the same hash now. A peer before 1.5.0 still names shapes by handle, and the fix has to work against every deployed peer. For those peers the controller hashes each CursorData's compressed colors with its geometry: one peer compresses the same pixels to the same bytes, so a shape seen before is recognised without being decompressed. The UI gets that shape once, under one id, and every later handle for it is passed on as a cursor_id of that id. The map from the peer's ids is kept for the connection, unbounded, since the peer sends a shape once and may select any id it named again; an entry is two integers. From 1.5.0 the controller passes the peer's ids through unchanged, on the old path. The web core in flutter/web does the same in its own tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: hold a content id to what the web client can read The web client decodes a u64 into a JS number and throws on a value past 2^53, which drops the whole message. A full 64-bit xxh3 id is almost always past it, so a web client connected to a peer on this build would never have been given a cursor shape. DRM's ids were already full width, so web clients lost the cursor on DRM peers too; the controlled side now renames those as well. The id keeps the hash's low 53 bits. Two of ten thousand distinct shapes then collide with a chance of about one in two hundred million. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: hand the UI a shape's pixels as they are, not as text A shape's pixels went to the Flutter UI as a JSON array of integers inside the cursor_data event: the core serialised a 1 MiB shape into up to 4 MiB of text on the receive loop, and the UI parsed that into a million-element list and copied it into bytes, on the UI thread. The event, text and all, was then kept for the session so that a tab moved to another window could replay it, and a reconnect, on which the peer sends every shape again, appended another copy of each. The core now sends a shape as an EventToUI::Cursor variant carrying Vec<u8>, which flutter_rust_bridge hands to Dart as a Uint8List, on the same per-session stream as the events so that it keeps its order with the cursor_id events around it. The web core calls onCursorData with the Uint8Array, as it calls onRgba for a frame. The id stays text, since the peer's ids can exceed Dart's int. The replay keeps each shape's pixels once, by id, and encodes them as base64 only when a tab moves. A shape arriving is also recorded as the last one selected, since the replay goes in first-seen order; before, a tab moved right after a new shape came back on the previous one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: let a controller ask the peer for a shape again A peer sends each shape once per connection and afterwards only its id, so a controller must keep every shape it was sent for as long as it is connected. The controlled side now answers Misc::request_cursor_data with the CursorData of that id, from the shapes it has sent, and only to a connection the cursor service would send it to. A controller from 1.5.0 on can then bound what it keeps and ask again for a shape it let go. None asks yet: the peer has to have the answer before a controller can rely on it, and peers are updated last. The shapes are kept by content id, and every handle for a shape shares its one message, where each handle used to keep its own copy. They are dropped with the service's own cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep a shape as its ui.Image and nothing else Each shape the UI had been sent stayed in four copies for the session: the pixels for a tab move, an image package copy to resize from (on a macOS or Linux controller of a Windows or Linux peer, a second one decoded back from a PNG), the bytes handed to the native cursor, and the ui.Image painted when the peer moves the pointer. A shape now keeps only its ui.Image. The resize source and the native bytes exist while a native cursor is being registered and are dropped once it is; a shape shown again at a raster it was registered at needs neither. For a raster it lacks, the pixels are read back from the ui.Image, which returns them exactly as they came. A tab move reads every shape back the same way, and the moved window decodes them as it would have on arrival. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: pin that a released shape keeps neither its pixels nor its bytes The shape in use once registered, the shapes not in use, and the pixels read back for a moved tab are all dropped; the tests now say so, so that a later change keeping one of them fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep one native cursor per shape, and each tab its own A shape got a native cursor for every raster it was shown at, and all of them stayed until the session ended, so every zoom or DPR change added a native cursor for each shape in use. A shape now keeps the one at its current raster; the one it replaces is deleted the next time a cursor already registered is built, by when its successor has been activated. A raster returned to before that takes its cursor back instead of registering another under the same name. The native cursors of a window's engine are shared by its tabs, and the predefined cursors had one name in all of them, so a tab closing deleted the default and forbidden cursors the other tabs still showed. Names are now scoped to the tab's cursor model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep the shapes compressed in the core, and only the one in use in the UI The UI kept every shape as a ui.Image, one raw copy each, for as long as the session lasted, because the peer sends a shape once and a shape might have to be drawn again. The core receives the shapes compressed, at a few hundredths of that, and now keeps them so: Session::cursor_shapes holds each shape that decoded, as the peer sent it, under the id the UI knows it by. The colors are copied, since the message's may be a slice of a larger received buffer. The UI keeps a ui.Image for the shape in use alone. A shape shown again at a raster its native cursor has needs no pixels at all; for a raster it lacks, for painting it, or for the window a tab moved to, the UI asks the core through session_get_cursor_shape, which decompresses and checks the shape again with decode_cursor_data, the same size and decompression limits as on arrival. A shape the core cannot give is not asked for again until the peer sends it. A tab move now carries only the id in use, and nothing is read back from the GPU. The web core keeps the shapes the same way and gives them back through getCursorShape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: a decode that finishes after the session clears keeps nothing A shape asked of the core while a tab closed was decoded after the cursor model had been cleared and stored in it, where nothing disposed of its ui.Image again; a shape whose cache failed to build left its ui.Image undisposed as well. A clear now starts a new generation, and a fetch or decode from an earlier one disposes what it made. A shape the core gave but that did not decode is not asked for again on every frame it is painted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep a native cursor for the shape in use alone Every shape the peer showed kept a native cursor for the session, each a raster the size of the shape as displayed. A shape switched away from now gives its native cursor up the way a replaced raster does: deleted the next time a cursor already registered is built, taken back if the shape returns first. A shape shown again after that is rebuilt from the compressed copy the core keeps. The predefined cursors are not the peer's shapes and stay registered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep the native cursors of the 16 shapes used last Keeping a native cursor for the shape in use alone rebuilt one on nearly every switch, since the pointer moves among a few shapes: the arrow, text, hand, four resize arrows, wait. The native cursors of the peer's shapes are now kept in order of use, 16 of them, and the one used longest ago gives its cursor up the way a replaced raster does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: forget the native cursors a session clear deletes The clear deleted every registered native cursor but kept their names, so a cursor model used again, as the mobile client's is, took each name for registered and showed a cursor that no longer existed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: hold a native cursor to 512 pixels a side A native cursor could be registered up to 1024 pixels a side, four MiB of pixels each, which with sixteen kept made 64 MiB at worst. The largest cursor a system shows is 256 pixels (Windows' largest pointer size), so a raster twice that is shown no larger: the cursor is drawn smaller rather than rejected, and sixteen kept come to 16 MiB at most. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: a shape switched away from while it decodes is not marked lost A shape asked of the core was taken for undecodable when it was no longer kept once its decode finished, but a shape the peer switched away from in the meantime is let go on purpose, and marking it made every later request for it be skipped: its native cursor stayed deferred and its painted cursor missing for the rest of the session. The decode now reports whether it decoded, and only a failure marks the shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * flutter: leave two untouched signatures as they were dart format had rewrapped registerEventHandler and sessionSetCommon next to the cursor additions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: read a shape's pixels from where its view starts The pixels used to be copied out of the JSON event into a fresh list, so the list's buffer began at its first pixel. Now they come straight from the core, native or web, as whatever Uint8List it hands over, and img2's fromBytes takes the buffer, not the list, so a view into a larger buffer would have been read from the buffer's start. Neither core gives a view today; this stops depending on that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: a DRM cache reset keeps the shape it was making room for The shape being sent was filed in CURSOR_SHAPES before the DRM ceiling cleared it with everything else, so a controller asking for the shape on screen got nothing until the next new shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: delete a replaced native cursor at the next build that shows one A replaced native cursor was deleted only by a build of a cursor already registered, so a run of new shapes, each shown once, left every replaced cursor registered: 16 on the books, all of them in the system. Now every build that shows a cursor deletes the ones replaced before it, right before it registers or takes one back; what replaced them was activated in an earlier frame, so they are off screen. A build that has to ask for pixels shows nothing new and deletes nothing, so a raster returned to right after being replaced still takes its cursor back; one returned to later registers again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: a shape that does not decode leaves no handle and no copy CursorDedupe named a handle as it hashed the shape, before the shape was checked, so a peer sending shapes that fail the checks under new handles grew the map with nothing the UI could ever select, and such a shape took over the handle of one that had decoded. The handle is now named once its shape decoded, or was shown before. The flutter build also copied a shape's compressed colors out of the message before the checks, so a message with a small size and huge colors was copied whole before being rejected. The copy is made once the shape decoded; until then the message's own bytes are held. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: remember the shape the core lacks only while it is in use Every id the core could not give was kept in a set for the session, so a peer sending cursor_id after cursor_id of shapes it never sent grew it by a string each, at a few bytes of traffic per entry. The mark exists so that painting does not ask for the shape in use on every frame, and the shape in use is one, so one mark is kept: the peer selecting another shape forgets it, and selecting a lost shape again asks once more. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: forget the shape the core lacks once the peer selects another The mark was replaced only by another shape the core lacked, so a lost shape selected again after a known one was still not asked for. Selecting any other shape now clears it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: leave the DRM cursor's own id as it was The wire id is cursor_content_id whatever the platform, set in run_cursor over the shape's pixels, so the id the DRM reader derives for itself only tells one hardware cursor from the next and keys the service's cache. Which hash it uses does not reach a controller; the change to xxh3 and the dependency it brought to scrap go. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: say the 1.5.0 gate means the release Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: say why the peer answers request_cursor_data with what it holds No controller sends it yet, and a review asked for it to go because a cache reset on the peer could leave a request unanswered. A shape the peer's cache dropped is rebuilt, and indexed again, the next time it is shown, before any connection names it by id, so the shape a controller has just been told to show is always there to give; that is the contract a future bounded controller relies on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: drop the tests nothing can fail a_handle_is_named_once_its_shape_is_shown dates from when `name` wrote the handle map. `name` is pure now, so its last assertion cannot fail, and the others repeat two neighbours. every_handle_the_peer_named_stays_for_the_connection fails only for a bound added on purpose, which the struct's doc rules out. The Dart arrival test is covered by the first test of its file. The kept-shape test now asserts what `cursor_shape` is for, a copy rather than a view of the received buffer, in place of the unknown fields it drops. The content-id test moves out from between two `use` lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: let the kept-shape tests run without the flutter feature CI's cargo test builds the default features, and the two functions under test are pure, so they compile for every test build and the module loses its feature gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep native cursors for every frame of an animated cursor An animated cursor is a shape per frame, 18 for the Windows busy cursor and 23 for KDE's wait cursor. With 16 native cursors a cycle longer than that missed on every frame: fetched from the core, decoded, registered again and the replaced cursor deleted, about thirty times a second for as long as the peer was busy. 64 holds two such animations and the everyday set besides. The tests settle in 10 ms instead of 100 ms, so the ones that fill the limit stay quick. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: release only the shapes that can hold pixels Selecting a cursor swept every shape of the session to release pixels, though only two can hold any: the shape selected before, and a shape that finished decoding after the peer moved on. Release those, so a cursor_id costs the same however many shapes the peer has shown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: show the native cursor limit holds two animations and the everyday set 64 is a working set, not the longest animation: KDE's wait and progress cursors are 23 frames each, and a session that meets both plus a dozen static shapes must keep them all, or every turn rebuilds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: let the shape in use keep its pixels for a new raster Dropping them once the native cursor was registered meant every scale change, zooming with the cursor zoomed or crossing to a display of another scale, showed the system arrow while the core gave the shape back and it decoded again. The shape in use now keeps them and makes the new raster at once; a shape switched away from still keeps none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: never fall back to the default cursor on a switch A switch to a shape without its image, or without a native cursor at its raster, showed the default arrow until the core gave the shape back and it decoded: the painted cursor on every switch back, the native one after an eviction or while a new shape decoded. - The shape shown before stays, image, hotspot and native cursor, until the one in use is ready. - Where the cursor is painted, on mobile or with the remote cursor shown, the images of the 64 shapes used last are kept, like the native cursors, so an animation does not decode a frame per turn. Elsewhere only the shape in use keeps one. - A shape that finishes decoding after the peer moved on is kept with them instead of dropped, so a fast animation fills them at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: name the dedupe's content-id set for what it holds `shown` read as the shape on screen. It is the shapes decoded and given to the UI, keyed by content id, so a shape arriving under a new handle is looked up once instead of scanning the handle map: `decoded`, `has_decoded`, and `record` for the call that fills both maps. The web mirror is renamed the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: keep native cursors for the session, as before Evicting native cursors and deleting a shape's replaced raster made Windows leak more than master. The engine's deleteCustomCursor/windows frees the HCURSOR with DeleteObject, which does not take a cursor, so a delete frees nothing and each cursor made again for a raster, or for a shape shown once more, leaks another handle. Keeping every raster of every shape until the session is cleared creates each one once, as master does. Fixing the engine instead is not cheap: x64 runs our fork on Flutter 3.24, where the fix is one more patch to rebuild and carry across every upgrade; arm64 runs the stock engine of a newer Flutter with the same code, where it means porting the fork and publishing an arm64 engine too, or waiting for an upstream fix to reach stable. Content ids keep the count to the shapes the peer really shows. The painted images, the compressed shapes in the core, and a switch showing the cursor shown before are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: record the Windows measurement behind keeping native cursors A measurement on Windows confirmed what the engine source implied: of 500 cursors made by CreateIconIndirect, DeleteObject freed none and left 500 USER objects alive, while DestroyCursor and DestroyIcon freed all 500. The comment now says so, that native cursors have no LRU for that reason only, and that one can come back once both engines destroy cursors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: look a native cursor up by the raster asked for The key ended in the raster made last. A shape switched away from keeps no pixels, so its raster stays where it was, and going back to a raster it was shown at before looked up the wrong key: A at 1.0, A at 0.5, then B, then A at 1.0 again missed the 32x32 cursor that was still there, fetched A from the core, and showed B meanwhile. With native cursors kept for every raster, the key now ends in the raster asked for; with pixels, that is the raster made, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: let a shape keep its pixels until a native cursor holds them A shape switched away from, or decoded after the peer moved on, let its pixels go whether or not it had a native cursor, and a native cursor is made only for the shape in use. A shape whose restore kept finishing after the next switch, as an animation's frames can, was fetched and decoded again every time it came back and never got a native cursor. A shape without one now keeps its pixels until one is made, at most kRecentShapes of them, the one waiting longest let go first. Mobile only paints the cursor, so it lets them go as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * proto: keep cursor ids as strings in JS A Windows peer before 1.5.0 names a cursor by its handle, sign-extended above 2^53 when its top bit is set. The web client decodes u64 fields into JS numbers, and ts-proto throws on a value a number cannot hold, so each such cursor_data or cursor_id was dropped whole and the web cursor stayed on the shape before. [jstype = JS_STRING] makes ts-proto generate these two fields as strings; the wire format, the Rust code generated and every other client are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: wait for a native cursor at the raster asked for, not any raster A shape let its pixels go once it had a native cursor at some raster. After a zoom, or on a display of another scale, an animation's frames had native cursors at the old raster only; each was fetched again at the new one, and a restore that finished after the next switch let its pixels go at once, so the frames never got a native cursor at the new raster and the cursor stayed on the one shown before. A lookup that finds no native cursor at the raster asked for now counts the shape as waiting for one, so a late restore keeps its pixels until it has one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cursor: count a shape held again when its asked raster has a native cursor A lookup that missed took the shape out of _nativeIds, but one that hit did not put it back. A shape asked at a new raster, then at one it has a native cursor for, still counted as waiting: a restore finishing after the switch away kept its pixels in _awaitingNative instead of letting them go. The set now says whether the raster asked last has a native cursor, either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * Revert "cursor: count a shape held again when its asked raster has a native cursor" This reverts commit |
||
|
|
bb924d2939 |
fix: avoid unnecessary clipboard reads and stale initial sync (#16293)
* refact: simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * refact(clipboard): don't keep the clipboard cache Signed-off-by: fufesou <linlong1266@gmail.com> * fix: prevent initial clipboard sync from overwriting newer updates Signed-off-by: fufesou <linlong1266@gmail.com> * fix: invalidate initial clipboard snapshots on remote and file updates Signed-off-by: fufesou <linlong1266@gmail.com> * fix: handle clipboard changes during initial sync Track clipboard changes even when they produce no sync message. Refresh invalidated snapshots to preserve sync after Wayland startup. Prevent repeated PeerInfo responses from restarting initial sync. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: skip file clipboard reads when file sync is not required Check is_file_required() before reading clipboard file URLs to avoid unnecessary clipboard access when no session needs file synchronization. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: refresh clipboard requirements before listener startup Update Flutter's text and file clipboard requirements before starting the shared clipboard loop so startup notifications use the current settings. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: exclude disconnected sessions from clipboard requirements Only count connected Flutter sessions when checking clipboard requirements. Recompute them on disconnect so a remaining session with sync disabled does not keep unnecessary clipboard reads enabled. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: filter disconnected sessions from clipboard requirements Exclude disconnected Flutter sessions from text and file clipboard requirement calculations, even when their tabs remain open. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: block file clipboard traffic before login Signed-off-by: fufesou <linlong1266@gmail.com> * fix: skip Wayland host startup clipboard broadcasts Signed-off-by: fufesou <linlong1266@gmail.com> * fix: respect initial clipboard sync setting on Wayland Stop broadcasting startup selections through the normal clipboard sync path. Keep generation invalidation for snapshots captured before the listener becomes active. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: wait for clipboard listener readiness before initial sync Wait for Linux clipboard backends to finish subscribing before the initial read. Update clipboard-master to use its readiness callback. Report completion for empty or failed reads so initial sync does not remain pending. Document snapshot invalidation across listener restarts. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: wait for Linux clipboard readiness asynchronously Use watch notifications to keep the connection loop responsive during clipboard startup. Cancel readiness waits when the session channel closes. Advance generation on readiness and capture it with each initial read so listener restarts invalidate older snapshots. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve Wayland startup file clipboard sync Distinguish startup selections from normal clipboard changes. Process startup files through the existing client clipboard worker, while keeping text initial sync and host startup suppression unchanged. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: bound clipboard readiness waits on Linux Add a five-second timeout to the initial-sync readiness wait and log failures. Update clipboard-master to support timeout and cancellation while waiting for the initial Wayland selection. Signed-off-by: fufesou <linlong1266@gmail.com> * refact(clipboard): update clipboard-master Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
134af545ae | Rename Portuguese module from ptbr to pt_BR (#16342) | ||
|
|
417e3bdd2c | Rename ptbr.rs to pt_BR.rs (#16343) | ||
|
|
483cc10266 |
fix(appimage): build AppRun with bounded argument splitting (#16330)
* fix(appimage): build AppRun with bounded argument splitting AppRun v2.0.0, which appimage-builder downloads, copies each argument into a 1 KiB stack buffer in apprun_shell_split_arguments, so any argument of 1024 bytes or more aborts the AppImage with "buffer overflow detected" before RustDesk starts (e.g. --connect with a 10000-char id). Build AppRun from the v2.0.0 commit with a patch that sizes the buffer from the input and stops the quote/escape scanning at the terminating NUL, in ubuntu:16.04 like upstream's release, and put it where appimage-builder looks before downloading. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(appimage): verify the patched AppRun is the one shipped Reset the split buffer by its first byte rather than zeroing all of it per argument, check the AppRun source tarball's sha256, pin the build image by digest, and fail the job if appimage-builder deployed anything other than the binary built here. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
efec124c52 |
Fix, online state, validate the data length (#16339)
* fix(client): reject truncated online status responses Validate the bitmap length before indexing server-provided states. Return an error for short responses so they cannot panic or update cached online status. Signed-off-by: fufesou <linlong1266@gmail.com> * docs(client): illustrate online status bitmap sizing Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> |
||
|
|
341193290e |
fix(mobile): honor reverse mouse wheel setting (#16337)
* fix(mobile): honor reverse mouse wheel setting Signed-off-by: zhenghe <zhenghe2009@gmail.com> * refactor(mobile): place wheel helper under platform Signed-off-by: zhenghe <zhenghe2009@gmail.com> --------- Signed-off-by: zhenghe <zhenghe2009@gmail.com> |
||
|
|
f299fb9906 |
fix(clipboard): unix, check file transfer permission on incoming file… (#16333)
* fix(clipboard): unix, check file transfer permission on incoming file clipboard The Cliprdr arm served incoming file clipboard messages on Linux/macOS without checking the file transfer permission, which the controlled side can turn off mid-session. can_sub_file_clipboard_service() only gates the outgoing service, so a peer could keep pushing files in and keep requesting the host's copied file list and contents. Check file_transfer_enabled() on every message. It is the same permission the client and the Windows cm use for file copy-paste. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(clipboard): honor one-way file transfer on incoming file requests One-way file transfer was only enforced on the outgoing side: the host never announces its clipboard files. A controller could still send FormatDataRequest / FileContentsRequest directly, and the host answered them, from the live clipboard on Windows and from serv_files on Linux/macOS. Drop those two requests when one-way file transfer is on, before any platform handling. The messages that carry the peer's own files to this side are untouched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style: drop redundant doc comment Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
58ff78a823 |
Hdr tonemap (#16033)
* scrap: tone-map HDR desktops to SDR in the Windows capturer With HDR enabled, Windows composes the desktop as linear scRGB in R16G16B16A16_FLOAT and places SDR white at the user's "SDR content brightness" (DISPLAYCONFIG_SDR_WHITE_LEVEL / 1000, e.g. 2.5 for 200 nits) rather than at 1.0. The legacy IDXGIOutput1::DuplicateOutput we used always converts that surface to BGRA8, and it does so by clipping, so everything above ~40% linear brightness lands on white. That is the washed-out / overexposed / high-contrast picture reported for HDR hosts in #5368, #9951, #12707 and discussion #7652. Ask for the desktop with IDXGIOutput5::DuplicateOutput1 and the format list [R16G16B16A16_FLOAT, B8G8R8A8_UNORM]. An SDR desktop still yields BGRA8 and takes the unchanged path. A float frame is converted on the GPU by a small pixel shader: divide by the SDR white level, clamp, apply the sRGB transfer. SDR content round-trips exactly, HDR highlights clip at white, the same result the local user sees. The white level is read per output through DisplayConfigGetDeviceInfo and refreshed once a second, so dragging the Windows slider tracks remotely. The converted BGRA8 texture feeds both the staging-copy (CPU) path and the vram (hwcodec texture) path; rotation is unchanged. Toggling HDR mid-session invalidates the duplication as before, and the recreated capturer re-detects the format. The shaders are compiled at runtime from a dynamically loaded d3dcompiler_47.dll so no new import is added to the binary. If the DLL, the compile or any D3D object creation fails, a process-wide flag makes later capturers fall back to the legacy DuplicateOutput, i.e. today's behaviour. This deliberately stops at SDR on the controlled side, with no option and no protocol change, which is how OBS, Sunshine (client without HDR) and macOS screen capture handle it. Real HDR pass-through would need 10-bit capture, Main10/AV1-10 encoders in the hwcodec fork, colour metadata on the wire and, decisively, an HDR output path on the controller: Flutter's desktop external textures are BGRA8888/RGBA8888 only on every platform, so nothing would be visible. When that changes it should follow the Sunshine/Moonlight pattern: an hdr capability bit advertised by the controller behind an explicit user toggle, negotiated like i444. Type-checked against x86_64-pc-windows-msvc (with and without the vram feature); not yet run on an HDR machine, which needs Windows 10 1703+ with HDR on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F (cherry picked from commit ca3c2f8ac7d1549a40855411b0539a69c82d7223) * scrap: stay on DXGI when the HDR conversion fails, load the compiler once Review follow-up: - A tone-map failure surfaced as a capture error, and the capture loop answers any DXGI error by switching the capturer to GDI for the rest of its life. The capturer now drops the tone-map, re-creates the duplication with the legacy DuplicateOutput (DXGI's clipped BGRA8, the pre-HDR behaviour) and returns WouldBlock, so the session stays on DXGI and simply fetches the next frame. - Only failures that cannot succeed anywhere in the process (no d3dcompiler_47.dll, shaders that do not compile) set the global UNAVAILABLE flag; D3D object creation failures stay with the capturer that hit them, so another adapter or a recreated capturer tries again. - D3DCompile is resolved once through a OnceLock instead of a LoadLibrary per capturer that was never freed. - The module doc now states what this pass is: normalization of an HDR desktop to SDR, with everything above SDR white clipping, not a tone map, and why a roll-off is deliberately not applied. The earlier claim that clipping matches what the local user sees was wrong for HDR content on an HDR display. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: only apply the SDR white level to outputs DXGI reports as HDR Review follow-up: - An FP16 desktop is not necessarily HDR. Since Windows 11 22H2 an Advanced Color (WCG) SDR display is composed in FP16 scRGB too, and there 1.0 is the display's reference white, not 80 nits, so the SDRWhiteLevel scaling does not apply (Microsoft: "Reference white applies only to HDR displays"). Query IDXGIOutput6::GetDesc1 and treat the output as HDR only for DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, the documented Win32 check; a non-HDR FP16 frame now gets just the scRGB -> sRGB transfer. Without IDXGIOutput6 (before Windows 10 1803) FP16 can only mean HDR. - The SDR white level is now Option: a failed query or a reported 0 is "unknown" on both the initial and the refresh path, an HDR output with an unknown level logs a warning and keeps the 80-nit assumption until the periodic re-query succeeds. DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL needs Windows 10 1709, not 1703 as an earlier message said. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: re-read the Advanced Color state while capturing, gate FP16 on IDXGIOutput6 Review follow-up: - The HDR/WCG decision was taken once when the conversion was created. HDR can be switched on or off, and a WCG desktop can turn into an HDR one, without the duplication being invalidated, so the choice between "divide by the SDR white level" and "no scaling" could go stale. The once-a-second refresh now re-reads it: it keeps an IDXGIFactory1, and when IsCurrent reports FALSE it creates a fresh factory and re-finds the output by GDI name, since a stale factory's outputs keep stale descriptions (this is the procedure the GetDesc1 docs require). On a change it switches modes, re-reads the white level and updates the shader constant; an unreadable level keeps the last known one. - Float frames are only requested when IDXGIOutput6 exists, as Microsoft's duplication sample does. That interface is also what tells HDR from WCG, so there is no longer a state where FP16 is requested without being able to interpret it. IDXGIOutput6 dates from Windows 10 1703, not 1803 as the previous commit said; 1803 added IDXGIFactory6. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: replace the DXGI factory and output together when re-reading HDR state Review follow-up: when the fresh factory did not find the output by name, the refresh kept the new, current factory next to the old output, and because IsCurrent then reported TRUE it never enumerated again, so the stale output could stay forever. enumerate_output6 now returns both or neither, and the refresh only replaces the pair together; a null factory forces another enumeration on the next refresh while the old output is still read in the meantime. The constructor gets the same guarantee for free, since a failed lookup leaves the factory null there too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: keep enumerate_output6's both-or-neither promise when IDXGIOutput6 is missing Review nit: a matched output whose IDXGIOutput6 query fails returned the fresh factory next to a null output, which let the constructor pair a current factory with the capturer's fallback output. Return neither in that case so the next refresh enumerates again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: log DuplicateOutput1 failures before fallback Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: avoid null duplication after HDR fallback failure Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: load the D3D compiler securely from System32 Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: release the frame through the state machine when abandoning the tone-map master's FrameState machine is left at Acquired by the raw ReleaseFrame(), so the next release_frame() hits DXGI_ERROR_INVALID_CALL on the fresh duplication and the capture loop falls back to GDI -- the outcome the fallback exists to avoid. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Signed-off-by: 21pages <sunboeasy@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: 21pages <sunboeasy@gmail.com> |
||
|
|
a8ae0d6d66 |
Review and complete pt_PT.rs translation (European Portuguese) (#16311)
* Update pt_PT.rs * Update pt_PT.rs * Update src/lang/pt_PT.rs fiz misspelling of "Apenas" Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Fix config_screen: replace trailing slash with period Per CodeRabbit's suggestion — the slash was rendered directly in the permission prompt text, not as a line continuation, so it read as a stray character instead of proper punctuation. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fix terminal-clipboard-write-tip: translate full 3-sentence source text Per Greptile's review — the previous translation for this key only covered a short summary (matching an equally incomplete ptbr.rs entry), not the full English source defined in en.rs, which explains the permission's scope (persists across all connections until disabled in Settings) and that manual copy/paste is unaffected. Translated from the actual en.rs source text as required by AGENTS.md's localization guidelines. --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
2e333f64a4 |
Update sc.rs (#16314)
* Update sc.rs * Update sc.rs |
||
|
|
e424b57fca |
Kx v1 (#16326)
* key exchange version 1 on the rendezvous and peer handshakes Bump hbb_common for key exchange version 1, one stream key per direction, and negotiate it on both handshakes. Rendezvous: `secure_tcp` reads the version the server advertises in `KeyExchange.version`, answers with the highest both speak, splits the key when that is at least 1, and arms the check of the server's echo on its first encrypted message. Peer: the controlled side advertises `KX_VERSION_LATEST` inside the signed `IdPk`, the controller answers in `PublicKey.kx_version` and both split the key when the pick is at least 1. A pick above what was offered is refused as a bug or tampering. `decode_id_pk_dtls` returns the advertised version along with the fingerprint. An absent field on either side is version 0, so any old peer or server keeps today's stream byte for byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * bump hbb_common: keep the advertisement check armed until an echo arrives Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * bump hbb_common: drop box_pk_of until something calls it Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * key exchange: say 0 on WebRTC, where no stream key applies A WebRTC stream is encrypted by DTLS and `set_key_split` collapses to `set_key` on it, which sets nothing but `peer_verified`. Both sides still put 1 on the wire, the controlled peer in its signed identity and the controller in its pick, and agreed on a version neither applied. That held only because both fell into the same branch: the day one side splits keys on WebRTC and the other does not, they would agree on 1, derive different keys, and have no version mismatch to point at. The controlled peer now advertises 0 on WebRTC and refuses a pick above what it advertised rather than above the newest it speaks anywhere; the controller picks 0 there. What is on the wire is what runs. Also bumps hbb_common for the echo contract: the server tags every message after the exchange, not the first alone, so dropping one frame at a known position does not rid an attacker of the downgrade check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * key exchange: verify the server's signed version before trusting it bump hbb_common d9f519e: KeyExchange.signed_version A server that signs its version sets bit 255 of the ephemeral key inside the signed keys[0]. A client that sees that bit, or the field itself, verifies sign("rdkx-ver" || key || version LE) under the server's signing key before picking a version and refuses the exchange otherwise, so a version lowered in the clear is caught at the handshake rather than one frame pair later by the echo. A legacy server sends canonical keys and no field and takes the unchanged path; the echo still checks its version. Tests: version 1 keys match the server both ways; an advertisement lowered in transit is refused at the first echo; legacy, signed-1 and signed-3 servers each exchange application data; nine tamperings of the signed version are refused before the client replies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: comments say what the fields and functions are bump hbb_common e3452ac: the same on the shared side. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: drop the advertisement echo bump hbb_common 5194159: RendezvousMessage.kx_advertised and the check on decrypted frames are gone. signed_version settles the version inside the exchange, so the client arms nothing after it; the stub in the tests stops tagging its frames and the test of a lowered advertisement goes, the signed-version cases covering that refusal before any reply. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: verify KxParams, the server's signed structure bump hbb_common e74a188: KeyExchange.signed_params and KxParams. The client parses the signed bytes as KxParams and compares its fields to the key it verified and the version it was shown, rather than matching a fixed byte string, so a field added to KxParams later parses past an older client. The tamper cases now include a payload signed as the old byte string. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: require the KxParams signing prefix before parsing bump hbb_common 26582e1: KX_PARAMS_DOMAIN. Without it a signed IdPk, which the server signs with the same key, parsed as a KxParams whose pk was the id and whose version was 0, so an id registered with the bytes of a marked ephemeral key could stand in for the params at version 0. Two tamper cases pin this: params signed without the prefix, and a signed IdPk in their place. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: one call sets the negotiated key bump hbb_common cbc8772: Stream::set_negotiated_key. The rendezvous client, the controller and the controlled side each branched on the picked version to choose between split and single keys. They now hand the transcript to Stream, which makes that choice once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * Update hbb_common: refuse unknown key exchange versions, pin wire vectors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * client: prove the peer handshake picks its version and encrypts under it The rendezvous exchange had tests against a stub server; the peer handshake had none, so a controller that fell back to version 0 would still connect, still report secured, and pass every test. These run Client::secure_connection against a stub controlled peer and check the pick it sees and the application data both ways: new peers pick 1, a peer without versions gets 0, and a pick lowered in transit leaves the two sides unable to read each other. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * Update hbb_common: pin the subkeys for an advertisement above the pick Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * Update hbb_common to main, with #614 merged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns * key exchange: name the picked version, say why the marker bit is safe Review follow-ups with no change in behaviour: the rendezvous pick is called picked, as in the peer handshake and KxTranscript; the marker comment says X25519 ignores the bit and the bytes stay as signed; the controlled side's refusal names the version it offered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
b30f781fd6 | fix: restrict VRAM stall detection to AMD SDK (#16319) | ||
|
|
68a35e65f2 |
fix(unix): ignore SIGPIPE in native Flutter entry (#16322)
* fix(linux): ignore SIGPIPE in Flutter runner Initialize SIGPIPE before entering the Rust core so writes to closed IPC peers return EPIPE instead of terminating the server. Add a native-entry regression test for the failure reported in #16297. * test(linux): remove SIGPIPE runner test * fix(flutter): initialize SIGPIPE in the Rust native entry Share SIGPIPE initialization between the Linux and macOS native runners before core startup. Remove the Linux C++ initialization. * fix: share SIGPIPE initialization with Sciter startup Initialize SIGPIPE before global_init in core_main, shared by Flutter and Sciter on Linux and macOS. * fix(flutter): keep SIGPIPE setup in the native FFI entry Sciter already receives SIGPIPE initialization from the Rust runtime. Scope the missing startup initialization to native Flutter runners on Linux and macOS. |