* fix(linux): enable mouse side buttons in remote sessions
Flutter's Linux embedder never delivers X11 button 8/9 (back/forward)
events to Dart, so mouse side buttons were silently dropped in remote
sessions.
Intercept these buttons at the GDK level via button-press/release-event
handlers on all windows (main + sub-windows) and forward them through
a dedicated platform channel to the active InputModel session.
Also add a defensive XSetPointerMapping call during enigo init to
extend the X11 core pointer button map to 9 buttons on servers where
it is smaller (e.g. minimal X server configurations).
* fix: address review feedback for side button support
- Use XOpenDisplay/XCloseDisplay instead of reading Display* from
xdo_t's private struct layout at offset 0 (fragile ABI assumption)
- Track side button down ownership per button via a Map instead of a
single slot, preventing cross-button mismatch on overlapping presses
* fix: gate side buttons on view-only and fix teardown
- Skip side button events in view-only sessions (consistent with
other mouse entry points)
- Release held side buttons on session close to avoid stuck buttons
on the remote
- Drop unpaired 'up' events instead of falling back to the active
model, which could send to the wrong session
* docs: add clarifying comments from review feedback
- Note global scope of XSetPointerMapping and that it runs once
via lazy_static singleton
- Clarify sub-window callback is safe on X11-only builds
- Document per-isolate design of initSideButtonChannel
* fix: replace broken XSetPointerMapping with diagnostic check
XSetPointerMapping requires the length to match XGetPointerMapping's
return value - it cannot extend the button count. The previous code
would trigger a BadValue X error on servers with fewer than 9 buttons.
Replace with a diagnostic-only check that logs whether the core
pointer has enough buttons for side button simulation. RustDesk's
uinput "Mouse passthrough" device already provides the needed buttons
in practice.
Also add .catchError to fire-and-forget side button releases during
session teardown to prevent unhandled async errors.
* fix: ensure side button releases bypass permission checks
If permissions change between button down and up (e.g. keyboardPerm
revoked, view-only toggled), sendMouse's early return would suppress
the release, leaving a stuck button on the remote.
Add _sendMouseUnchecked that bypasses permission checks, used for:
- Side button 'up' events (matching a recorded 'down')
- Forced releases during session teardown
Gate all permission checks (isViewOnly, keyboardPerm, isViewCamera)
at the 'down' entry point before recording in _sideButtonDownModels.
* fix: add NULL guards and avoid blocking platform channel handler
- Add NULL checks for FL_VIEW cast and channel creation in
on_subwindow_created (review feedback from fufesou)
- Use fire-and-forget (unawaited) for _sendMouseUnchecked calls
inside the platform channel handler to avoid blocking platform
messages when sessionSendMouse is slow (review feedback from Copilot)
* fix: remove circular import and skip X11 check on Wayland
- Move initSideButtonChannel() call from initEnv() in main.dart to
the InputModel constructor, removing the circular import between
main.dart and input_model.dart
- Skip check_x11_button_map() when DISPLAY is not set to avoid
noisy warnings on pure Wayland environments
* flutter: improve address book pull error handling
Summary:
- Show error messages when fetching the address book list fails.
- After the initial fetch, switching back to the AB tab no longer re-fetches it, even if an error occurred or the error banner was dismissed.
Tested:
- Self-hosted server:
- normal
- 403 responses
- legacy address book mode
- Public server
- Verified that switching tabs no longer re-fetches AB after the initial fetch, regardless of whether an error occurred or the error banner was cleared.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* use resp.statusCode in address book json decoding
Signed-off-by: 21pages <sunboeasy@gmail.com>
* flutter: clear address book list errors on reset
Signed-off-by: 21pages <sunboeasy@gmail.com>
* flutter: clear address book pull errors consistently
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* docs: fix typos in documentation and code comments
- Fix 'seperated' -> 'separated' in remote_input.dart
- Fix 'seperators' -> 'separators' in fuse/cs.rs
- Update outdated 'OSX' -> 'macOS' in virtual display README
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>
* impl(cm): implement change_theme and change_language callbacks
These callbacks were previously empty TODO stubs.
Now they properly invoke the Sciter UI handlers to notify
the UI when theme or language changes occur.
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>
---------
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>
* add option to hide stop-service when service is running
Signed-off-by: 21pages <sunboeasy@gmail.com>
* update hbb_common to upstream
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* avatar
* refactor avatar display: unify rendering and resolve at use time
- Extract buildAvatarWidget() in common.dart to share avatar rendering
logic across desktop settings, desktop CM and mobile CM
- Add resolve_avatar_url() in Rust, exposed via FFI (SyncReturn),
to resolve relative avatar paths (e.g. "/avatar/xxx") to absolute URLs
- Store avatar as-is in local config, only resolve when displaying
(settings page) or sending (LoginRequest)
- Resolve avatar in LoginRequest before sending to remote peer
- Add error handling for network image load failures
- Guard against empty client.name[0] crash
- Show avatar in mobile settings page account tile
Signed-off-by: 21pages <sunboeasy@gmail.com>
* web: implement mainResolveAvatarUrl via js getByName
Signed-off-by: 21pages <sunboeasy@gmail.com>
* increase ipc Data enum size limit to 120 bytes
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
* feat(terminal): add reconnection buffer support for persistent sessions
Fix two related issues:
1. Reconnecting to persistent sessions shows blank screen - server now
automatically sends historical buffer on reconnection via SessionState
machine with pending_buffer, eliminating the need for client-initiated
buffer requests.
2. Terminal output before view ready causes NaN errors - buffer output
chunks on client side until terminal view has valid dimensions, then
flush in order on first valid resize.
Rust side:
- Introduce SessionState enum (Closed/Active) replacing bool is_opened
- Auto-attach pending buffer on reconnection in handle_open()
- Always drain output channel in read_outputs() to prevent overflow
- Increase channel buffer from 100 to 500
- Optimize get_recent() to collect whole chunks (avoids ANSI truncation)
- Extract create_terminal_data_response() helper (DRY)
- Add reconnected flag to TerminalOpened protobuf message
Flutter side:
- Buffer output chunks until terminal view has valid dimensions
- Flush buffered output on first valid resize via _markViewReady()
- Clear terminal on reconnection to avoid duplicate output from buffer replay
- Fix max_bytes type (u32) to match protobuf definition
- Pass reconnected field through FlutterHandler event
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(terminal): add two-phase SIGWINCH for TUI app redraw and session remap on reconnection
Fix TUI apps (top, htop) not redrawing after reconnection. A single
resize-then-restore is too fast for ncurses to detect a size change,
so split across two read_outputs() polling cycles (~30ms apart) to
force a full redraw.
Also fix reconnection failure when client terminal_id doesn't match
any surviving server-side session ID by remapping the lowest surviving
session to the requested ID.
Rust side:
- Add two-phase SIGWINCH state machine (SigwinchPhase: TempResize →
Restore → Idle) with retry logic (max 3 attempts per phase)
- Add do_sigwinch_resize() for cross-platform PTY resize (direct PTY
and Windows helper mode)
- Add session remap logic for non-contiguous terminal_id reconnection
- Extract try_send_output() helper with rate-limited drop logging (DRY)
- Add 3-byte limit to UTF-8 continuation byte skipping in get_recent()
to prevent runaway on non-UTF-8 binary data
- Remove reconnected flag from flutter.rs (unused on client side)
Flutter side:
- Add reconnection screen clear and deferred flush logic
- Filter self from persistent_sessions restore list
- Add comments for web-related changes
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* - UI display: display_name first
- Fallback: name
- Technical identity: still name
### What changed
- Added account display helpers and display_name state in user model:
- flutter/lib/models/user_model.dart:16
- Account/logout label now uses display_name (@name) when both exist:
- flutter/lib/mobile/pages/settings_page.dart:689
- flutter/lib/desktop/pages/desktop_setting_page.dart:2016
- flutter/lib/desktop/pages/desktop_setting_page.dart:2135
- Desktop Account info now shows both when applicable:
- Display Name: ...
- Username: ...
- flutter/lib/desktop/pages/desktop_setting_page.dart:2039
- Previously done group-list behavior remains:
- group user list displays display_name with name fallback
- flutter/lib/common/widgets/my_group.dart:187
- Persistence path for display_name remains enabled (including group cache/submodule field):
- libs/hbb_common/src/config.rs:2347
- src/client.rs:2630
- LoginRequest.my_name now resolves as:
1. OPTION_DISPLAY_NAME (manual override)
2. user_info.display_name
3. user_info.name
4. OS username fallback
* 1. GUID key (...Uninstall\{GUID}) is MSI-native metadata generated by Windows Installer.
2. Non-GUID key (...Uninstall\RustDesk) is explicitly written by RustDesk’s MSI compatibility component in res/msi/Package/Components/Regs.wxs:44, populated by preprocess.py --arp from .github/workflows/
flutter-build.yml:262.
So they were not using the same EstimatedSize logic:
- MSI GUID key: MSI-calculated size (KB).
- RustDesk key: custom script value from res/msi/preprocess.py:339 (previously bytes, now fixed to KB).
That mismatch is exactly why you saw different sizes.
* improve display name handling
- Append (@username) when multiple users share the same display name
- Trim whitespace from display_name before comparison and display
- Add missing translate() for Logout button on desktop
Signed-off-by: 21pages <sunboeasy@gmail.com>
* group peer filter match both user's display name and user's name
Signed-off-by: 21pages <sunboeasy@gmail.com>
* case-insensitive search in group peer filter
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
* fix(terminal): ensure tab close is resilient to session cleanup failures
- Wrap _closeTerminalSessionIfNeeded in isolated try/catch so that
tabController.closeBy always executes even if FFI calls throw
- Add clarifying comment in handleWindowCloseButton for single-tab
audit dialog flow
* fix(terminal): fix session reconnect ID mismatch and tab close race condition
Remap surviving persistent sessions to client-requested terminal IDs on
reconnect, preventing new shell creation when IDs are non-contiguous.
Snapshot peerTabCount before async operations in _closeTab to avoid race
with concurrent _closeAllTabs clearing the tab controller.
Remove debug log statements.
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
- Fix new tab not auto-focusing: add FocusNode to TerminalView and
request focus when tab is selected via tab state listener
- Fix NaN error when data arrives before terminal view layout: buffer
output data until terminal view has valid dimensions, flush on first
valid resize callback
Signed-off-by: fufesou <linlong1266@gmail.com>
Terminal tab keys use the format "peerId_terminalId". The previous code
used split('_')[0] or startsWith('$peerId_') to extract the peerId,
which breaks when the peerId itself contains underscores.
This can happen in two scenarios:
- Hostname-based ID: when OPTION_ALLOW_HOSTNAME_AS_ID is enabled, the
peerId is derived from the system hostname, which commonly contains
underscores (e.g. "my_dev_machine").
- Custom ID: the validation regex ^[a-zA-Z][\w-]{5,15}$ allows
underscores since \w matches [a-zA-Z0-9_], so IDs like "my_dev_01"
are valid.
Fix all three parsing sites in terminal_tab_page.dart to use
lastIndexOf('_'), which is safe because terminalId is always a plain
integer with no underscores.
* fix issue: #13911 'Double Click' bug on iPad with Magic Mouse
* remote_input.dart comments - gestures.dart organization and clean states of all interrupted gestures
When controlled peer is reconnecting after signout/switch user, auto retry for 30s (matches server's peer offline threshold) instead of immediately showing "Remote desktop is offline" error.
Ref: https://github.com/rustdesk/rustdesk/discussions/14048
Signed-off-by: 21pages <sunboeasy@gmail.com>
* feat: Add relative mouse mode
- Add "Relative Mouse Mode" toggle in desktop toolbar and bind to InputModel
- Implement relative mouse movement path: Flutter pointer deltas -> `type: move_relative` -> new `MOUSE_TYPE_MOVE_RELATIVE` in Rust
- In server input service, simulate relative movement via Enigo and keep latest cursor position in sync
- Track pointer-lock center in Flutter (local widget + screen coordinates) and re-center OS cursor after each relative move
- Update pointer-lock center on window move/resize/restore/maximize and when remote display geometry changes
- Hide local cursor when relative mouse mode is active (both Flutter cursor and OS cursor), restore on leave/disable
- On Windows, clip OS cursor to the window rect while in relative mode and release clip when leaving/turning off
- Implement platform helpers: `get_cursor_pos`, `set_cursor_pos`, `show_cursor`, `clip_cursor` (no-op clip/hide on Linux for now)
- Add keyboard shortcut Ctrl+Alt+Shift+M to toggle relative mode (enabled by default, works on all platforms)
- Remove `enable-relative-mouse-shortcut` config option - shortcut is now always available when keyboard permission is granted
- Handle window blur/focus/minimize events to properly release/restore cursor constraints
- Add MOUSE_TYPE_MASK constant and unit tests for mouse event constants
Note: Relative mouse mode state is NOT persisted to config (session-only).
Note: On Linux, show_cursor and clip_cursor are no-ops; cursor hiding is handled by Flutter side.
Signed-off-by: fufesou <linlong1266@gmail.com>
* feat(mouse): relative mouse mode, exit hint
Signed-off-by: fufesou <linlong1266@gmail.com>
* refact(relative mouse): shortcut
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
Each desktop isolate now independently tracks wakelock state.
WakelockPlus.disable() is only called when all tabs within the
same isolate are closed/minimized.
WakelockPlus ensures screen stays awake as long as any isolate
has wakelock enabled.
Signed-off-by: 21pages <sunboeasy@gmail.com>