* feat(recording): add visibility and service storage options
- support hide-recording-button in Flutter and Sciter
- allow a custom save directory for Windows service recordings
- sanitize peer IDs used in recording filenames
Tested:
- with hide-recording-button=Y and allow-auto-record-outgoing=Y,
outgoing sessions are recorded automatically while the recording button
remains hidden and cannot be stopped from the UI; verified on Flutter
desktop, Sciter, and Android
- windows-service-video-save-directory takes effect when the Windows client
runs as an installed service
- the Windows controlling side can save recordings for direct IP:port
connections
Signed-off-by: 21pages <sunboeasy@gmail.com>
* update hbb_common
Signed-off-by: 21pages <sunboeasy@gmail.com>
* fix(recording): validate configured save directories
- trim configured recording directory paths
- reject non-absolute paths and fall back to defaults
- warn when a non-empty path is invalid
Signed-off-by: 21pages <sunboeasy@gmail.com>
* fix(recording): validate configured save directories
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Fix Adjust Window sizing across DPI and fullscreen transitions
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Use visible screen frame for Adjust Window
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Tolerate floating-point errors in Adjust Window sizing
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Fix review, fix Adjust Window async metric guards
Capture the current screen before awaiting window geometry so one target-frame calculation uses consistent screen metrics.
Return early when adjusting without a context and the Flutter view list is empty, instead of calling views.first after the window or engine may have been torn
down.
Clarify the platform coordinate units used for Adjust Window scaling.
* Fix Adjust Window for maximized Linux windows
Unmaximize Linux remote windows before applying Adjust Window because native setFrame may be ignored while the window is maximized.
* Fix Adjust Window screen refresh guards
Refresh screen metrics before checking Adjust Window availability and again after exiting fullscreen so target-frame calculation uses current window geometry.
Hide Adjust Window on web because resizing relies on desktop window APIs.
* Fix review, handle missing window frame in Adjust Window
Return null when WindowController.getFrame fails so Adjust Window availability checks and resize attempts skip cleanly if the window is hidden or disposed.
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Fix disabled installation bypass
Prevent install.exe and --install from opening the install flow when disable-installation is set.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Refine disabled installation handling for portable clients
Document why --install must be filtered from both Rust and Flutter runner arguments for portable wrappers such as no-install.exe. Remove redundant UI-
layer installation checks because the install entry points are already gated upstream.
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* Fork vcpkg triplets to keep Android API version on 21
Fixes crash on API platforms 21 to 23 due to missing
symbol `__write_chk` (available since API 24).
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
* flutter/build_android_deps.sh: Refactor to remove unused
... variables and shellcheck warnings.
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
---------
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
* Feature: Restore the last viewed monitor on auto reconnect
Remembers the users last manually selected remote monitor and returns to it after an auto reconnect.
In memory, reconnect only, and bounds checked against the current display count.
It is skipped in "use all my displays" mode.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Address review on reconnect monitor restore
Avoid a crash if the session closes during a reconnect.
Don't overwrite the remembered monitor on auto restore.
Defer the switch until the view is ready so a monitor with a different size renders correctly.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Guard all-displays reconnect restore against empty display list
* Harden reconnect monitor restore against races and multi-UI sessions
Cancel a queued restore when the user manually selects a monitor, so a
newer choice is not overridden by a stale pending restore.
Compare the remembered monitor against the reconnect event's display
instead of the stale _pi.currentDisplay, which is intentionally left
unchanged when the peer has multiple sessions.
Add a frame-independent fallback so a multi-UI tab that never receives
the first-image event (its display is filtered to the owning tab) still
restores the remembered monitor.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Harden reconnect monitor restore: fallback timer, lifecycle, cursor
Follow-up hardening on the auto-reconnect monitor restore:
- Cancel the fallback timer synchronously once this tab owns the restore,
so it can no longer fire while onEvent2UIRgba is awaiting canvas setup
and switch displays before the canvas is ready (the offset the deferred
restore exists to avoid). The multi-UI no-frame fallback stays intact.
- Apply the restore in a finally so a throwing canvas init still runs it
instead of stranding a queued restore with the timer already cancelled.
- Cancel the fallback timer on a manual monitor switch, so a newer user
selection supersedes a queued restore instead of racing it.
- Restore with updateCursorPos: false, matching other programmatic
display switches so an auto-restore does not reposition the cursor.
---------
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Feature: add monitor-switch buttons to remote toolbars
Add buttons to cycle through the remote displays from the toolbars:
- A main-toolbar button and a minimized-handle button, both using a shared SVG icon with the current monitor number overlaid.
- Two opt-in settings under Settings/Other. The minimized-toolbar option is nested under the main-toolbar option.
- The minimized button only appears once the toolbar is collapsed.
- Cycling does not move the remote cursor, matching the existing in-toolbar monitor buttons.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* fix: respect individual-window display mode when cycling
In "Show displays as individual windows" mode, route the cycle button through openMonitorInNewTabOrWindow like the monitor selector, so each display keeps its own window instead of repurposing the current one.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
---------
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Feature: add monitor-switch buttons to remote toolbars
Add a one-click "switch to next monitor" control to both desktop toolbars:
- Main toolbar: always shown when the remote has more than one monitor,
styled to match the existing blue icon buttons (white screen, black number).
- Minimized (draggable show/hide) toolbar: off by default, toggled via a new
"Show monitor switch on minimized toolbar" checkbox in the Display menu and
persisted as a local option.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Update remote_toolbar.dart
* refact: unify monitor-switch button icons, share tooltip
Addressing the review feedback on the monitor-switch toolbar buttons:
- Add assets/display_switcher.svg and use it for both the main and minimized buttons. This replaces the hand-drawn glyph (Containers + magic numbers) on the main toolbar. The icon scales with DPI/theme and the two toolbars stay visually consistent.
- Flip the minimized button's label to white for contrast, since the new icon has a solid screen.
- Move the tooltip string into a shared _MonitorCycle.tooltip getter so both buttons use one source of truth.
- Use const Offstage() for consistency with the surrounding returns.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
* Improve monitor-switch settings and toolbar behavior
- Nest the minimized-toolbar option under the main one in settings only show when the main option is enabled.
- Only show the minimized switch button on the collapsed toolbar handle, so it no longer duplicates the main switch while the toolbar is expanded.
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
---------
Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
* fix(arm64-linux): fix CJK font rendering on flutter-elinux
The flutter-elinux engine used for ARM64 Linux builds is compiled without
--enable-fontconfig, so Flutter's text shaper cannot discover system fonts.
This causes CJK characters to render as tofu boxes even when fonts such as
Noto Sans CJK are installed. See flutter/flutter#139293.
Fix by loading a CJK font at startup via FontLoader (bypassing fontconfig)
and propagating it through two paths so all text widgets are covered:
1. MyTheme.applyFontFallback() — updates textTheme on both light and dark
ThemeData so Material components receive the fallback through the theme.
2. _mergeCjkFallback() in GetMaterialApp builders — wraps child widgets in
DefaultTextStyle.merge so bare Text() widgets and those with inherit:true
also render CJK characters correctly.
Font discovery queries fc-list for zh, ja, and ko separately, preferring
fonts present in all three sets (true pan-CJK fonts such as NotoSansCJK or
SourceHanSans) over Chinese-only fonts that may lack Japanese kana or Korean
hangul glyphs. Falls back to a hardcoded search-path list covering
Debian/Ubuntu, Fedora/RHEL, Arch Linux, and WenQuanYi font layouts.
This is an app-level workaround. The engine-level fix is tracked at
flutter/flutter#180235 (open as of 2026-06).
Fixes#10666
Signed-off-by: Bia503 <yinwenche189@gmail.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Signed-off-by: Bia503 <yinwenche189@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
RemotePage.dispose() only reaches sessionClose at the tail of gFFI.close(),
behind several awaits (canvas save, image update, the enable_soft_keyboard
platform call). If the app is backgrounded while the page is disposing,
dispose can be suspended before that runs, so the session is never torn down.
The next reconnect re-attaches to the leaked session (mobile reuses a constant
sessionId) and is stuck on "Connecting..." forever while the orphaned io_loop
keeps streaming.
Dispatch sessionClose at the start of dispose so teardown happens synchronously
on route pop, before backgrounding can interrupt it. The sessionClose in
gFFI.close() becomes a no-op once the session is already removed.
Fixes#15060
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>