fix(flutter): size unzoomed remote cursors to the local system

This commit is contained in:
fufesou
2026-09-11 16:02:52 +08:00
parent 67b94c7906
commit 39940b717a
3 changed files with 108 additions and 10 deletions

View File

@@ -19,6 +19,7 @@ import '../../models/input_model.dart';
import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import '../../utils/image.dart';
import '../../utils/cursor_size.dart';
import '../widgets/remote_toolbar.dart';
import '../widgets/kb_layout_type_chooser.dart';
import '../widgets/tabbar_widget.dart';
@@ -1088,6 +1089,21 @@ class ImagePaint extends StatefulWidget {
class _ImagePaintState extends State<ImagePaint> {
bool _lastRemoteCursorMoved = false;
final _localCursorSize = LocalCursorSize();
@override
void initState() {
super.initState();
_localCursorSize.addListener(_cursorSizeChanged);
}
void _cursorSizeChanged() => setState(() {});
@override
void dispose() {
_localCursorSize.dispose();
super.dispose();
}
String get id => widget.id;
RxBool get zoomCursor => widget.zoomCursor;
@@ -1109,6 +1125,11 @@ class _ImagePaintState extends State<ImagePaint> {
bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal;
mouseRegion({child}) => Obx(() {
final useLocalSize = !isWeb &&
(isLinux || isMacOS || isWindows) &&
!zoomCursor.value &&
isViewAdaptive();
if (useLocalSize) _localCursorSize.ensureLoaded(dpr);
double getCursorScale() {
var c = Provider.of<CanvasModel>(context);
var cursorScale = 1.0;
@@ -1149,11 +1170,16 @@ class _ImagePaintState extends State<ImagePaint> {
_firstEnterImage.value = true;
}
return _buildCustomCursor(
context, getCursorScale());
context, getCursorScale(),
useLocalSize: useLocalSize);
}
}())
: _buildDisabledCursor(context, getCursorScale())
: _buildDisabledCursor(context, getCursorScale(),
useLocalSize: useLocalSize)
: MouseCursor.defer,
onEnter: (_) {
if (useLocalSize) _localCursorSize.refresh();
},
onHover: (evt) {},
child: child);
});
@@ -1266,15 +1292,21 @@ class _ImagePaintState extends State<ImagePaint> {
);
}
MouseCursor _buildCustomCursor(BuildContext context, double scale) {
MouseCursor _buildCustomCursor(BuildContext context, double scale,
{bool useLocalSize = false}) {
final cursor = Provider.of<CursorModel>(context);
final cache = cursor.cache ?? preDefaultCursor.cache;
if (useLocalSize && _localCursorSize.value == null) return MouseCursor.defer;
cache?.localSize = useLocalSize ? _localCursorSize.value : null;
return buildCursorOfCache(cursor, scale, cache);
}
MouseCursor _buildDisabledCursor(BuildContext context, double scale) {
MouseCursor _buildDisabledCursor(BuildContext context, double scale,
{bool useLocalSize = false}) {
final cursor = Provider.of<CursorModel>(context);
final cache = preForbiddenCursor.cache;
if (useLocalSize && _localCursorSize.value == null) return MouseCursor.defer;
cache?.localSize = useLocalSize ? _localCursorSize.value : null;
return buildCursorOfCache(cursor, scale, cache);
}

View File

@@ -39,6 +39,7 @@ import 'package:vector_math/vector_math.dart' show Vector2;
import '../common.dart';
import '../utils/image.dart' as img;
import '../utils/cursor_size.dart';
import '../common/widgets/dialog.dart';
import 'input_model.dart';
import 'platform_model.dart';
@@ -2863,6 +2864,8 @@ class CursorData {
double hoty;
final int width;
final int height;
double? localSize;
late final int _visibleSize = cursorVisibleSize(image);
CursorData({
required this.peerId,
@@ -2886,14 +2889,18 @@ class CursorData {
int _scaledDimension(int dimension, double scale) {
const minBitmapSize = 1;
final pixels = dimension * scale;
return _usesLogicalCursorPixels
return _usesLogicalCursorPixels || localSize != null
? max(minBitmapSize, pixels.round())
: pixels.toInt();
}
double _checkUpdateScale(double scale) {
double oldScale = this.scale;
if (scale != 1.0) {
if (localSize != null) {
scale = _visibleSize == 0
? 1.0
: max(localSize!, kMinCursorSize) / _visibleSize;
} else if (scale != 1.0) {
// Update data if scale changed.
final tgtWidth = (width * scale).toInt();
final tgtHeight = (height * scale).toInt();
@@ -2904,13 +2911,17 @@ class CursorData {
}
}
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
const bytesPerPixel = 4;
final byteLength = _scaledDimension(width, scale) *
_scaledDimension(height, scale) * bytesPerPixel;
if (_doubleToInt(oldScale) != _doubleToInt(scale) ||
(isWindows && data != null && data!.length != byteLength)) {
if (isWindows) {
data = img2
.copyResize(
image,
width: (width * scale).toInt(),
height: (height * scale).toInt(),
width: _scaledDimension(width, scale),
height: _scaledDimension(height, scale),
interpolation: img2.Interpolation.average,
)
.getBytes(order: img2.ChannelOrder.bgra);
@@ -2936,7 +2947,7 @@ class CursorData {
String updateGetKey(double scale) {
scale = _checkUpdateScale(scale);
return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}';
return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}${localSize == null ? '' : '_local'}';
}
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as img;
int cursorVisibleSize(img.Image image) {
var left = image.width;
var top = image.height;
var right = -1;
var bottom = -1;
for (final pixel in image) {
if (pixel.a == 0) continue;
if (pixel.x < left) left = pixel.x;
if (pixel.x > right) right = pixel.x;
if (pixel.y < top) top = pixel.y;
if (pixel.y > bottom) bottom = pixel.y;
}
if (right < left) return 0;
final width = right - left + 1;
final height = bottom - top + 1;
return width > height ? width : height;
}
class LocalCursorSize extends ValueNotifier<double?> {
LocalCursorSize() : super(null);
static const _channel = MethodChannel('org.rustdesk.rustdesk/cursor');
double? _devicePixelRatio;
int _generation = 0;
void ensureLoaded(double devicePixelRatio) {
if (_devicePixelRatio == devicePixelRatio) return;
_devicePixelRatio = devicePixelRatio;
refresh();
}
Future<void> refresh() async {
final generation = ++_generation;
try {
final size = await _channel.invokeMethod<double>('getSystemCursorSize');
if (size == null || !size.isFinite || size <= 0) {
throw const FormatException('Invalid local system cursor size');
}
if (generation == _generation) value = size;
} catch (error, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: error, stack: stack, library: 'local cursor size'));
}
}
@override
void dispose() {
++_generation;
super.dispose();
}
}