Compare commits

...

18 Commits

Author SHA1 Message Date
fufesou
8da629c57d revert(cursor): return to f5b98b32 before splitting plugin changes 2026-09-12 00:32:58 +08:00
fufesou
650a6e21cc fix(web): preserve full precision in cursor identifiers 2026-09-12 00:20:47 +08:00
fufesou
5f4bb00007 fix(web): align cursor bitmap and hotspot rounding 2026-09-12 00:20:28 +08:00
fufesou
a7f1eb4c25 fix(cursor): address sizing and capture review findings 2026-09-12 00:19:10 +08:00
fufesou
be5fb304d4 fix(flutter): align painted cursor with the software image origin 2026-09-11 17:54:56 +08:00
fufesou
fdd67a875b test(flutter): cover local cursor sizing and mode switches 2026-09-11 16:02:52 +08:00
fufesou
39940b717a fix(flutter): size unzoomed remote cursors to the local system 2026-09-11 16:02:52 +08:00
fufesou
67b94c7906 feat(windows): measure the native system cursor size 2026-09-11 16:02:52 +08:00
fufesou
eeff9eb121 feat(linux): measure the active system cursor size 2026-09-11 16:02:52 +08:00
fufesou
c983d00437 feat(macos): expose the local system cursor size 2026-09-11 16:02:52 +08:00
fufesou
7bb3fe6b5a fix(flutter): preserve cursor minimum sizes and native hotspots 2026-09-11 13:51:24 +08:00
fufesou
d202a2fba4 fix(flutter): scale the remote cursor with its image 2026-09-11 13:51:24 +08:00
fufesou
72ea38ca4f fix(cursor): preserve native pixels and hotspots across display scales
Capture actual Mutter and DXGI cursor metadata, preserve Retina artwork, and invalidate cursor identities when physical pixels or DPI change. Keep straight-alpha colors during native cursor resizing and correct the Windows XOR outline offset.

Verified all six Mac/Linux/Windows directions with real pointer movement, arrow/I-beam/crosshair transitions, adaptive zoom off/on/off, Original view, and live DPI changes. Requested debug builds and focused native/Flutter regressions pass.
2026-09-11 11:49:04 +08:00
fufesou
f96d00d9d1 fix(flutter): correct cursor scaling and Linux cursor clipping
Keep Linux/macOS bitmap sizing and painted cursor DPI
conversion consistent, and derive hotspots from the actual scaled bitmap.

Pad tall Linux native cursor buffers with transparency to
preserve the lower half in the hardware cursor plane.
Cover bitmap sizing, hotspots, artwork preservation,
and painting with focused regression tests.
2026-09-10 21:56:25 +08:00
rustdesk
f5b98b32f2 fix(flutter): read the live DPR and clamp the painted cursor like the native one
CanvasModel caches devicePixelRatio and only refreshes it when the view
style changes, so after the window moves to a monitor with a different
DPR the unzoomed cursor kept the previous monitor's scale. Read it from
MediaQuery instead, which also rebuilds the cursor when it changes.

The native path clamps the scaled bitmap to kMinCursorSize; apply the
same clamp to the painted cursor so a small cursor does not change size
when the peer moves the mouse.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
2026-09-10 14:25:51 +08:00
rustdesk
8aee2a442e fix(flutter): keep the painted cursor hotspot in place when zoom cursor is off
`CursorPaint` subtracted the hotspot in remote pixels and then scaled it
by the canvas scale, but drew the image at scale 1.0, so the hotspot
landed hotx * (1 - scale) logical pixels away from the remote cursor
position. Cursors with a centered hotspot (I-beam, crosshair) were off
by up to half their size in Adaptive view. Subtract the hotspot after
scaling the position instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
2026-09-10 14:09:39 +08:00
rustdesk
12eaf2cc75 fix(flutter): check the cursor height against the min cursor size
`_checkUpdateScale` computed the scaled height from `width`, so the
min-size clamp never looked at the height.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
2026-09-10 14:09:39 +08:00
rustdesk
7194743a30 fix(flutter): shrink the unzoomed remote cursor by DPR on macOS and Linux
With "Zoom cursor" off in Adaptive or Custom view, the remote cursor
bitmap was registered at scale 1.0. NSCursor and GdkCursor treat the
bitmap size as logical pixels, so on a HiDPI controller the cursor was
drawn DPR times larger than in Original view (which already passes
1/DPR) and than on Windows (whose cursor path is in physical pixels).
A HiDPI remote such as KDE Wayland sends a 48-64 px bitmap, which then
showed up 3-4x too big on a Retina Mac.

Scale the bitmap by 1/DPR in that case, and scale the Flutter-painted
cursor used while the peer moves the mouse the same way so its size
does not jump. The new branch is an identity at DPR 1 and the Windows
paths are untouched.

Fixes https://github.com/rustdesk/rustdesk/discussions/15363

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8HSHTEHo27mDcJXVyVfSP
2026-09-10 14:08:42 +08:00
2 changed files with 27 additions and 3 deletions

View File

@@ -1101,6 +1101,9 @@ class _ImagePaintState extends State<ImagePaint> {
final m = Provider.of<ImageModel>(context); final m = Provider.of<ImageModel>(context);
var c = Provider.of<CanvasModel>(context); var c = Provider.of<CanvasModel>(context);
final s = c.scale; final s = c.scale;
// CanvasModel caches the DPR and only refreshes it when the view style
// changes, so read it live to follow the window across monitors.
final dpr = MediaQuery.devicePixelRatioOf(context);
bool isViewAdaptive() => c.viewStyle.style == kRemoteViewStyleAdaptive; bool isViewAdaptive() => c.viewStyle.style == kRemoteViewStyleAdaptive;
bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal; bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal;
@@ -1117,6 +1120,12 @@ class _ImagePaintState extends State<ImagePaint> {
} else { } else {
if (zoomCursor.value || isViewOriginal()) { if (zoomCursor.value || isViewOriginal()) {
cursorScale = s; cursorScale = s;
} else {
// NSCursor and GdkCursor treat the bitmap size as logical
// pixels, so an unzoomed cursor must be shrunk by the DPR to
// keep 1 remote px == 1 physical px, the size Original view
// already renders it at.
cursorScale = 1.0 / dpr;
} }
} }
return cursorScale; return cursorScale;
@@ -1404,14 +1413,29 @@ class CursorPaint extends StatelessWidget {
} }
} }
double x = (m.x - hotx) * c.scale + cx; double x = m.x * c.scale + cx - hotx;
double y = (m.y - hoty) * c.scale + cy; double y = m.y * c.scale + cy - hoty;
double scale = 1.0; double scale = 1.0;
final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal;
if (zoomCursor.value || isViewOriginal) { if (zoomCursor.value || isViewOriginal) {
x = m.x - hotx + cx / c.scale; x = m.x - hotx + cx / c.scale;
y = m.y - hoty + cy / c.scale; y = m.y - hoty + cy / c.scale;
scale = c.scale; scale = c.scale;
} else if (!isWindows) {
// Keep the painted cursor the same physical size as the native one
// built by getCursorScale() above, including its min-size clamp.
scale = 1.0 / MediaQuery.devicePixelRatioOf(context);
final image = m.image ?? preDefaultCursor.image;
if (scale != 1.0 &&
image != null &&
((image.width * scale).toInt() < kMinCursorSize ||
(image.height * scale).toInt() < kMinCursorSize)) {
final sw = kMinCursorSize / image.width;
final sh = kMinCursorSize / image.height;
scale = sw < sh ? sh : sw;
}
x = (m.x * c.scale + cx) / scale - hotx;
y = (m.y * c.scale + cy) / scale - hoty;
} }
return CustomPaint( return CustomPaint(

View File

@@ -2884,7 +2884,7 @@ class CursorData {
if (scale != 1.0) { if (scale != 1.0) {
// Update data if scale changed. // Update data if scale changed.
final tgtWidth = (width * scale).toInt(); final tgtWidth = (width * scale).toInt();
final tgtHeight = (width * scale).toInt(); final tgtHeight = (height * scale).toInt();
if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) { if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) {
double sw = kMinCursorSize.toDouble() / width; double sw = kMinCursorSize.toDouble() / width;
double sh = kMinCursorSize.toDouble() / height; double sh = kMinCursorSize.toDouble() / height;