mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-18 18:31:02 +03:00
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.
This commit is contained in:
@@ -3470,6 +3470,13 @@ class CursorModel with ChangeNotifier {
|
||||
img2.Image imgOrigin = img2.Image.fromBytes(
|
||||
width: w, height: h, bytes: rgba.buffer, order: img2.ChannelOrder.rgba);
|
||||
if (isWindows) {
|
||||
final pixels =
|
||||
await image.toByteData(format: ui.ImageByteFormat.rawStraightRgba);
|
||||
if (pixels == null) {
|
||||
throw StateError('Could not read straight-alpha cursor pixels');
|
||||
}
|
||||
imgOrigin = img2.Image.fromBytes(
|
||||
width: w, height: h, bytes: pixels.buffer, order: img2.ChannelOrder.rgba);
|
||||
data = imgOrigin.getBytes(order: img2.ChannelOrder.bgra);
|
||||
} else {
|
||||
ByteData? imgBytes =
|
||||
@@ -3478,6 +3485,11 @@ class CursorModel with ChangeNotifier {
|
||||
return false;
|
||||
}
|
||||
data = imgBytes.buffer.asUint8List();
|
||||
if (isLinux || isMacOS) {
|
||||
// Preserve the PNG's straight-alpha colors when resizing native cursors.
|
||||
imgOrigin = img2.decodePng(data) ??
|
||||
(throw const FormatException('Invalid native cursor PNG'));
|
||||
}
|
||||
}
|
||||
final cache = CursorData(
|
||||
peerId: peerId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -31,6 +32,11 @@ class _CursorModel implements CursorModel {
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _CursorFFI implements FFI {
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
CursorData _cursorData((int, int) size, (double, double) hotspot) {
|
||||
final image = img.Image(width: size.$1, height: size.$2, numChannels: 4);
|
||||
for (var y = 0; y < image.height; y++) {
|
||||
@@ -83,6 +89,46 @@ void _expectArtwork(img.Image native, img.Image artwork) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('received cursor keeps edge colors across scale changes',
|
||||
(tester) async {
|
||||
const side = 4;
|
||||
for (final (rgba, expected) in [
|
||||
([128, 64, 32, 128], [255, 128, 64, 128]),
|
||||
([0, 0, 0, 0], [0, 0, 0, 0]),
|
||||
([32, 64, 128, 255], [32, 64, 128, 255]),
|
||||
]) {
|
||||
final cursor = CursorModel(WeakReference<FFI>(_CursorFFI()))
|
||||
..id = 'edge-colors';
|
||||
addTearDown(cursor.disposeImages);
|
||||
addTearDown(cursor.dispose);
|
||||
await tester.runAsync(() => cursor.updateCursorData({
|
||||
'id': 'edge-colors',
|
||||
'hotx': '1',
|
||||
'hoty': '1',
|
||||
'width': '$side',
|
||||
'height': '$side',
|
||||
'colors': jsonEncode(List.generate(side * side, (_) => rgba)
|
||||
.expand((pixel) => pixel)
|
||||
.toList()),
|
||||
}));
|
||||
final data = cursor.cache!;
|
||||
for (final scale in [1.0, 0.5, 1.0]) {
|
||||
data.updateGetKey(scale);
|
||||
final image = Platform.isWindows
|
||||
? img.Image.fromBytes(
|
||||
width: data.scaledWidth,
|
||||
height: data.scaledHeight,
|
||||
bytes: data.data!.buffer,
|
||||
order: img.ChannelOrder.bgra)
|
||||
: img.decodePng(data.data!)!;
|
||||
for (final pixel in image) {
|
||||
expect([pixel.r, pixel.g, pixel.b, pixel.a], expected,
|
||||
reason: 'Edge color must survive scale $scale');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (final (source, hotspot, scale, size, linuxHotspot) in _cases) {
|
||||
testWidgets('${source.$1}x${source.$2} cursor at scale $scale',
|
||||
(tester) async {
|
||||
|
||||
Reference in New Issue
Block a user