The shape shown before stays while the one in use decodes, so a switch
does not flash the default cursor. It also stayed once the shape in use
was known to be lost (rejected by the core, or not decoding): the
controller kept drawing, say, an I-beam for a pointer it never got,
until the peer changed shape. Both the native and the painted cursor
now leave the default one, as master did for the native cursor. A shape
that does not decode is marked in updateCursorData, whether it was sent
or fetched again, so both paths agree.
The client also selects a shape it rejects as it arrives. It showed the
shape before until the peer selected the rejected one again, and the
default cursor from then on; it now looks the same both times. Sciter
ignores an id it has no shape for, as it did.
The default and forbidden cursors are made with the model. They were
made the first time a build asked for one, and their decode lands
later with nothing to draw again, so that first build showed the shape
before in their place.
A fetch failing after the session was cleared marks nothing in the new
one, and a fetch that throws at once tells the listeners after the
build that asked for it, not during it.
The forbidden cursor, shown while input is off, became the cursor shown
last, so a raster made after input came back showed it until the
shape's pixels were fetched, or for good if they never came. Only a
shape stands in for a shape now; CursorModel.shown keeps the rule for
both builders.
_checkView asks for a single registration again: every native cursor
is made once per session, and one made twice leaks an HCURSOR on
Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
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
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
* Update lang.rs - Splits ambiguous pt into pt-pt and pt-br
Splits the ambiguous "Português" entry into two explicit options: Português (Portugal) [pt-pt] and Português (Brasil) [pt-br]. The pt_PT.rs translation file already existed in the repo (fully translated, key-complete) but was never wired into lang.rs. Kept the old "pt" and "br" locale codes mapped to ptbr::T for backward compatibility with existing user configs.
* Preserve pt-PT/pt-BR region in automatic locale resolution
Preserve pt-PT/pt-BR region in automatic locale resolution
* Fix pt-pt detection: check region suffix instead of redundant "pt" substring
Fix pt-pt detection: check region suffix instead of redundant "pt" substring
* Normalize legacy pt/br saved lang option to pt-br
Normalize legacy pt/br saved lang option to pt-br
* Normalize legacy pt/br lang option on Web client
* Add regression tests for pt-pt/pt-br locale resolution
* Follow CLDR inheritance for Portuguese regional locales (pt-AO, pt-MZ, etc.)
* fix(linux): a session logout should hand the peer to the login screen
Logging out closes every window in the session, the connection manager's
included, and its close handler kicks every peer with the reason a person
gets when they disconnect one by hand. That reason is the one thing the
client never retries on, so the remote session dies on a frozen frame
instead of reconnecting to the greeter that is already there.
The close carries nothing to tell the two apart: measured on KDE, the CM
receives no signal and logind still reports the session active at that
instant, and the server is killed within a few hundred ms either way, so
neither a state check nor a grace period can decide it. What is
distinguishable is the ACTION: disconnecting a peer is not the same event
as this window going away. So the window-close path now says so, and the
server ends the session without poisoning the retry; the Disconnect
button and the app's own close control keep kicking exactly as before.
Linux only, since that is where a logout closes the window.
Verified on plasma/sddm with a client attached: a logout now reconnects
to the greeter with no dialog, while closing the manager window still
shows Closed manually by the peer.
* fix(linux): close the tunnel too, and keep the web build compiling
Three seams the first pass missed. The web bridge is hand written, not
generated, so the new call needs its stub there or flutter build web
stops compiling - and that job is disabled in CI, so it would have gone
green. try_port_forward_loop is a second consumer of the same channel
and only knew Close, so a forwarded tunnel outlived the window it was
supposed to die with. And the variant had landed inside the DRM section,
whose comment says everything below it is drm-gated.
* 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: 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>
1. Enable the RustDesk client to use WebSocket for either controlling or being controlled.
2. Fix TCP sending `register_pk` frequently
Note:
1. Because hbb_common directly uses `use_ws` to read config directly, rustdesk also directly reads config
Signed-off-by: 21pages <sunboeasy@gmail.com>
1. Web, build.
2. Web and mobile, `onSubmitted` for ID text field.
3. Web, remove unused key 'toggle_option'.
Signed-off-by: fufesou <linlong1266@gmail.com>