mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 08:21:02 +03:00
@@ -1118,12 +1118,17 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
? cursor.cache ?? preDefaultCursor.cache
|
? cursor.cache ?? preDefaultCursor.cache
|
||||||
: preForbiddenCursor.cache;
|
: preForbiddenCursor.cache;
|
||||||
final peerDpr = cache?.pixelRatio ?? 0;
|
final peerDpr = cache?.pixelRatio ?? 0;
|
||||||
if (!isWeb && isViewScaled() && !zoomCursor.value && peerDpr > 0) {
|
if (!isWeb && isViewScaled() && zoomCursor.isFalse && peerDpr > 0) {
|
||||||
|
// Adaptive/Custom scales the video, but Zoom cursor is off: preserve the
|
||||||
|
// cursor's logical size instead of multiplying it by the canvas scale.
|
||||||
|
// Divide by the source bitmap density to obtain logical cursor pixels.
|
||||||
|
// Windows expects a physical-pixel scale at this call boundary, hence dpr;
|
||||||
|
// buildCursorOfCache() converts it back to logical scale for the plugin.
|
||||||
return (isWindows ? dpr : 1.0) / peerDpr;
|
return (isWindows ? dpr : 1.0) / peerDpr;
|
||||||
}
|
}
|
||||||
// Density metadata is optional. Keep the legacy path for hosts that
|
// Density metadata is optional. Keep the legacy path for hosts that
|
||||||
// omit it so capture-backend upgrades are not a client prerequisite.
|
// omit it so capture-backend upgrades are not a client prerequisite.
|
||||||
final imageScale = isViewScaled() && zoomCursor.value
|
final imageScale = isViewScaled() && zoomCursor.isTrue
|
||||||
? _cursorImageScale(widget.ffi, cursor, useLocalPointer: true)
|
? _cursorImageScale(widget.ffi, cursor, useLocalPointer: true)
|
||||||
: s;
|
: s;
|
||||||
var cursorScale = 1.0;
|
var cursorScale = 1.0;
|
||||||
@@ -1218,12 +1223,19 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedules a refresh after layout to keep the painted cursor aligned with video.
|
||||||
|
///
|
||||||
|
/// Viewport, view scale or DPR changes can clamp or detach scroll positions
|
||||||
|
/// without a ScrollNotification, so read the resulting metrics after layout.
|
||||||
|
/// Save this build's fractions: a delayed refresh may update the model without
|
||||||
|
/// notifying, leaving the cursor painted from older values. CanvasModel compares
|
||||||
|
/// the refreshed fractions with this snapshot and notifies only if they differ,
|
||||||
|
/// repairing the cursor position without creating a rebuild loop.
|
||||||
void _syncScrollAfterLayout(CanvasModel canvas) {
|
void _syncScrollAfterLayout(CanvasModel canvas) {
|
||||||
// Custom scrollbars also paint from scroll fractions; preserve Original's path.
|
// Custom scrollbars also paint from scroll fractions; preserve Original's path.
|
||||||
if (canvas.scrollStyle != ScrollStyle.scrolledge &&
|
if (canvas.scrollStyle != ScrollStyle.scrolledge &&
|
||||||
canvas.viewStyle.style != kRemoteViewStyleCustom) return;
|
canvas.viewStyle.style != kRemoteViewStyleCustom) return;
|
||||||
final renderedScroll = (canvas.scrollX, canvas.scrollY);
|
final renderedScroll = (canvas.scrollX, canvas.scrollY);
|
||||||
// Relayout can clamp or detach scroll positions without a scroll event.
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
canvas.updateScrollAfterLayout(renderedScroll);
|
canvas.updateScrollAfterLayout(renderedScroll);
|
||||||
@@ -1458,8 +1470,11 @@ class CursorPaint extends StatelessWidget {
|
|||||||
: (sx < sy ? sx : sy);
|
: (sx < sy ? sx : sy);
|
||||||
if (scale < minimumScale) scale = minimumScale;
|
if (scale < minimumScale) scale = minimumScale;
|
||||||
}
|
}
|
||||||
// Keep the hotspot at the remote position even when the minimum enlarges
|
// Drawing origin = (remote cursor position * canvas scale + video origin)
|
||||||
// the cursor; fractional coordinates must survive painting below.
|
// / cursor image scale - source hotspot.
|
||||||
|
// ImagePainter scales both the drawing origin and the artwork, so this
|
||||||
|
// keeps the hotspot at the displayed remote position even if the cursor
|
||||||
|
// scale differs from the canvas scale, e.g. because of the minimum size.
|
||||||
final x = (m.x * c.scale + cx) / scale - hotx;
|
final x = (m.x * c.scale + cx) / scale - hotx;
|
||||||
final y = (m.y * c.scale + cy) / scale - hoty;
|
final y = (m.y * c.scale + cy) / scale - hoty;
|
||||||
|
|
||||||
@@ -1474,6 +1489,20 @@ class CursorPaint extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the renderer-adjusted video origin in local logical pixels,
|
||||||
|
/// keeping the painted cursor aligned with the video.
|
||||||
|
///
|
||||||
|
/// Software rendering truncates the origin in image coordinates before
|
||||||
|
/// scaling it back. Linux texture rendering truncates it directly in logical
|
||||||
|
/// pixels. Match that rounding without rounding the cursor or its hotspot.
|
||||||
|
///
|
||||||
|
/// Returns null for overflowing scrollbar/edge-scroll layouts, whose origin
|
||||||
|
/// the caller computes separately, and for other texture renderers, which
|
||||||
|
/// use canvas.x/y unchanged.
|
||||||
|
///
|
||||||
|
/// Example: canvas.x = 10.75 and video scale = 0.5 produce a software-rendered
|
||||||
|
/// origin of (10.75 / 0.5).toInt() * 0.5 = 10.5. Using that same origin for
|
||||||
|
/// the cursor avoids a 0.25-logical-pixel alignment error.
|
||||||
Offset? _softwareImageOffset(CanvasModel canvas) {
|
Offset? _softwareImageOffset(CanvasModel canvas) {
|
||||||
if (canvas.imageOverflow.isTrue &&
|
if (canvas.imageOverflow.isTrue &&
|
||||||
canvas.scrollStyle != ScrollStyle.scrollauto) {
|
canvas.scrollStyle != ScrollStyle.scrollauto) {
|
||||||
@@ -1497,8 +1526,43 @@ class CursorPaint extends StatelessWidget {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match physical video pixels, independently of the cursor's density metadata:
|
/// Returns the base cursor artwork scale needed to match the remote video.
|
||||||
// a single DRM output keeps physical desktop coordinates even at high DPI.
|
///
|
||||||
|
/// The result is Flutter logical pixels per source cursor bitmap pixel.
|
||||||
|
/// Callers handle minimum-size clamping, native-platform DPR conversion,
|
||||||
|
/// rasterization, and hotspot positioning separately. This function does not
|
||||||
|
/// draw the cursor or transform its remote position.
|
||||||
|
///
|
||||||
|
/// Callers decide whether the cursor should follow the video:
|
||||||
|
/// - [CursorPaint] uses this scale regardless of the Zoom cursor setting.
|
||||||
|
/// - The MouseRegion cursor uses it in Adaptive/Custom view only when Zoom
|
||||||
|
/// cursor is enabled. With Zoom cursor disabled, a separate sizing policy
|
||||||
|
/// keeps the native cursor independent of video zoom.
|
||||||
|
/// Selecting Adaptive/Custom view alone does not imply cursor zoom.
|
||||||
|
///
|
||||||
|
/// For Linux hosts, desktop coordinates and captured bitmap pixels can use
|
||||||
|
/// different scales. The video renderer normally converts bitmap pixels to
|
||||||
|
/// view coordinates using `canvas.scale / display.scale`; cursor artwork
|
||||||
|
/// must use the same conversion. The non-texture scrollbar/edge-scroll
|
||||||
|
/// renderer is an exception: it paints directly at `canvas.scale`.
|
||||||
|
/// Non-Linux hosts also use `canvas.scale` directly.
|
||||||
|
///
|
||||||
|
/// With multiple viewed Linux displays, [useLocalPointer] selects which
|
||||||
|
/// position determines the relevant display. When true, prefer the remote
|
||||||
|
/// coordinates mapped from local mouse input, so native cursor sizing updates
|
||||||
|
/// on display crossings without waiting for a host-position echo. When false,
|
||||||
|
/// use [cursor]'s host-reported position, as required for the painted overlay.
|
||||||
|
/// Before the first mapped local position, both paths use [cursor].
|
||||||
|
/// If no display can be selected, fall back to `canvas.scale`.
|
||||||
|
///
|
||||||
|
/// Do not substitute cursor-bitmap density (`CursorData.pixelRatio`) for
|
||||||
|
/// `display.scale`: this helper follows video geometry, not unzoomed cursor
|
||||||
|
/// DPI normalization. A high-DPI capture path can still use physical desktop
|
||||||
|
/// coordinates, so cursor density alone does not determine the video scale.
|
||||||
|
///
|
||||||
|
/// For example, with Linux `display.scale == 2` and `canvas.scale == 0.5`,
|
||||||
|
/// the usual rendering path returns 0.25: a 64-pixel cursor is drawn 16 logical
|
||||||
|
/// pixels wide, before any minimum-size adjustment.
|
||||||
double _cursorImageScale(FFI ffi, CursorModel cursor,
|
double _cursorImageScale(FFI ffi, CursorModel cursor,
|
||||||
{bool useLocalPointer = false}) {
|
{bool useLocalPointer = false}) {
|
||||||
final canvas = ffi.canvasModel;
|
final canvas = ffi.canvasModel;
|
||||||
@@ -1514,12 +1578,15 @@ double _cursorImageScale(FFI ffi, CursorModel cursor,
|
|||||||
if (displays.length == 1) return canvas.scale / displays.first.scale;
|
if (displays.length == 1) return canvas.scale / displays.first.scale;
|
||||||
final rect = peer.rect;
|
final rect = peer.rect;
|
||||||
if (rect != null) {
|
if (rect != null) {
|
||||||
// Local input drives native sizing; host positions still drive the overlay.
|
// Mapped local input is already in host desktop coordinates. CursorModel's
|
||||||
// Before the first local movement, retain the existing position source.
|
// position is relative to rect, so restore its origin for display lookup.
|
||||||
|
// Before the first local movement, both paths use the host position.
|
||||||
final position =
|
final position =
|
||||||
(useLocalPointer ? ffi.inputModel.remotePointerPosition.value : null) ??
|
(useLocalPointer ? ffi.inputModel.remotePointerPosition.value : null) ??
|
||||||
Offset(cursor.x + rect.left, cursor.y + rect.top);
|
Offset(cursor.x + rect.left, cursor.y + rect.top);
|
||||||
for (final display in displays) {
|
for (final display in displays) {
|
||||||
|
// Display origins are desktop coordinates; dimensions are physical pixels.
|
||||||
|
// Convert the dimensions so these bounds use the same units as position.
|
||||||
if (Rect.fromLTWH(display.x, display.y, display.width / display.scale,
|
if (Rect.fromLTWH(display.x, display.y, display.width / display.scale,
|
||||||
display.height / display.scale).contains(position)) {
|
display.height / display.scale).contains(position)) {
|
||||||
return canvas.scale / display.scale;
|
return canvas.scale / display.scale;
|
||||||
|
|||||||
@@ -449,7 +449,10 @@ class InputModel {
|
|||||||
final isPhysicalMouse = false.obs;
|
final isPhysicalMouse = false.obs;
|
||||||
int _lastButtons = 0;
|
int _lastButtons = 0;
|
||||||
Offset lastMousePos = Offset.zero;
|
Offset lastMousePos = Offset.zero;
|
||||||
// Host position echoes may be absent; keep locally mapped input separately.
|
// Latest local pointer position mapped to absolute remote desktop coordinates.
|
||||||
|
// Used to select the native cursor's display scale in Linux All Displays mode,
|
||||||
|
// without waiting for a host position update. Keep it separate from the
|
||||||
|
// host-reported position used to paint the remote cursor.
|
||||||
final remotePointerPosition = Rxn<Offset>();
|
final remotePointerPosition = Rxn<Offset>();
|
||||||
int _lastWheelTsUs = 0;
|
int _lastWheelTsUs = 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user