diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdd394..d885df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,3 +39,23 @@ ## 0.6.2 - Update `dart_rfb` to version 0.7.0 (refactored read loop) + +## 0.7.0 + +- Use `dart_rfb` 0.9.0 with ZRLE support and widget-side logging +- Fix `Unsupported operation: Infinity or NaN toInt` error in gesture detector +- Add defensive checks for Infinity/NaN values in coordinate calculations +- Fix mouse pointer coordinate mapping to correctly handle different aspect ratios +- Fix coordinate calculation to account for BoxFit.contain scaling and centering offsets +- Fix widget size tracking to update correctly when window is resized +- Use ValueListenableBuilder to react to size changes in real-time +- Improve SizeTrackingWidget to use LayoutBuilder for accurate size updates +- Thanks @Spokplacenta ! + +## 0.8.0 + +- Add [RemoteFrameBufferWidget.syncLocalClipboardToRemote] (default `true`) to disable pushing the local clipboard to the remote VNC server +- Add [RemoteFrameBufferWidget.onFirstFrame] callback after the first decoded framebuffer is shown +- Fix perpetual loading spinner when ZRLE leaves the internal buffer at zero but a decoded [Image] exists +- Harden [RemoteFrameBufferWidget._decodeAndUpdateImage] with dimension and buffer length checks +- Migrate keyboard handling from deprecated `RawKeyboard` to `HardwareKeyboard` diff --git a/example/.gitignore b/example/.gitignore index 24476c5..6c31954 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index b302054..2f15bb9 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -182,7 +182,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -235,6 +235,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -344,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -423,7 +424,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -470,7 +471,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b84f54e..57ceab4 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef64..b3c1761 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index ddcf30f..fbc267b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,72 +5,83 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.13.0" binary: dependency: transitive description: name: binary - url: "https://pub.dartlang.org" + sha256: "9e095f2f1c94f06501352c8621f12d055da1c945dffae933ed0ae7fa5eeba046" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "5bbf32bc9e518d41ec49718e2931cd4527292c9b0c6d2dffcf7fe6b9a8a8cf72" + url: "https://pub.dev" source: hosted version: "2.1.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" dart_des: dependency: transitive description: name: dart_des - url: "https://pub.dartlang.org" + sha256: "0a66afb8883368c824497fd2a1fd67bdb1a785965a3956728382c03d40747c33" + url: "https://pub.dev" source: hosted version: "1.0.2" dart_rfb: dependency: transitive description: - name: dart_rfb - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.0" + path: "." + ref: main + resolved-ref: "4321ffb3199ecb47c429b725be6a1c2472b15a1d" + url: "https://github.com/Spokplacenta/dart-rfb.git" + source: git + version: "0.9.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -80,7 +91,8 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_rfb: @@ -89,7 +101,7 @@ packages: path: ".." relative: true source: path - version: "0.6.1" + version: "0.8.0" flutter_test: dependency: "direct dev" description: flutter @@ -99,133 +111,175 @@ packages: dependency: transitive description: name: fpdart - url: "https://pub.dartlang.org" + sha256: "19db038cf3bb49bfe28b2a0b363ce84cf2e6a0f471a93ae09271cfef03dae935" + url: "https://pub.dev" source: hosted version: "0.4.0" freezed_annotation: dependency: transitive description: name: freezed_annotation - url: "https://pub.dartlang.org" + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.4" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "3.0.2" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" logging: dependency: "direct main" description: name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - matan: - dependency: transitive - description: - name: matan - url: "https://pub.dartlang.org" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.3.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.13.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.17.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: e3320978e3715725e62f04358fd249c1efe5999297b2c6acd626a817593281b0 + url: "https://pub.dev" source: hosted version: "1.9.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "862015c5db1f3f3c4ea3b94dc2490363a84262994b88902315ed74be1155612f" + url: "https://pub.dev" source: hosted version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.7.10" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "15.0.2" sdks: - dart: ">=2.18.6 <3.0.0" - flutter: ">=1.17.0" + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/child_size_notifier_widget.dart b/lib/src/child_size_notifier_widget.dart index ea93a07..43e760a 100644 --- a/lib/src/child_size_notifier_widget.dart +++ b/lib/src/child_size_notifier_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart'; /// Widget that exposes its child's size via a [ValueNotifier]. /// +/// The size is updated whenever the widget is resized. /// Inspired by: https://stackoverflow.com/a/58004112/373138 class SizeTrackingWidget extends StatefulWidget { final Widget _child; @@ -15,19 +16,40 @@ class SizeTrackingWidget extends StatefulWidget { _sizeValueNotifier = sizeValueNotifier; @override - State createState() => _SizeTackingState(); + State createState() => _SizeTrackingState(); } -class _SizeTackingState extends State { +class _SizeTrackingState extends State { + Size? _lastSize; + + void _updateSize() { + final RenderBox? renderBox = context.findRenderObject() as RenderBox?; + if (renderBox != null) { + final Size currentSize = renderBox.size; + if (_lastSize != currentSize) { + _lastSize = currentSize; + widget._sizeValueNotifier.value = currentSize; + } + } + } + @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((final _) { - widget._sizeValueNotifier.value = - (context.findRenderObject() as RenderBox?)!.size; - }); + WidgetsBinding.instance.addPostFrameCallback((final _) => _updateSize()); + } + + @override + void didUpdateWidget(final SizeTrackingWidget oldWidget) { + super.didUpdateWidget(oldWidget); + WidgetsBinding.instance.addPostFrameCallback((final _) => _updateSize()); } @override - Widget build(final BuildContext context) => widget._child; + Widget build(final BuildContext context) => LayoutBuilder( + builder: (final BuildContext context, final BoxConstraints constraints) { + WidgetsBinding.instance.addPostFrameCallback((final _) => _updateSize()); + return widget._child; + }, + ); } diff --git a/lib/src/extensions/coordinate_conversion_extensions.dart b/lib/src/extensions/coordinate_conversion_extensions.dart new file mode 100644 index 0000000..50557ea --- /dev/null +++ b/lib/src/extensions/coordinate_conversion_extensions.dart @@ -0,0 +1,92 @@ +import 'dart:ui'; + +import 'package:flutter/widgets.dart' hide Image; + +/// Extension to convert local widget coordinates to remote framebuffer coordinates. +/// +/// With BoxFit.contain, the image is scaled to fit while preserving aspect ratio. +/// We need to calculate the actual scaling and offsets (centering) to map +/// widget coordinates to image coordinates correctly. +extension CoordinateConversion on double { + /// Convert local X coordinate to remote framebuffer X coordinate. + int toRemoteX({ + required final Size widgetSize, + required final int imageWidth, + required final int imageHeight, + }) { + // Check if sizes are valid + if (widgetSize.width <= 0 || + widgetSize.height <= 0 || + imageWidth <= 0 || + imageHeight <= 0) { + return 0; + } + // Additional defensive checks for Infinity/NaN + if (!isFinite || + !widgetSize.width.isFinite || + !imageWidth.isFinite || + widgetSize.width == 0 || + imageWidth == 0) { + return 0; + } + // Calculate scaling factors for both dimensions + final double scaleX = widgetSize.width / imageWidth; + final double scaleY = widgetSize.height / imageHeight; + // With BoxFit.contain, the actual scale is the minimum to preserve aspect ratio + final double actualScale = scaleX < scaleY ? scaleX : scaleY; + // Calculate the displayed image size + final double displayedWidth = imageWidth * actualScale; + // Calculate offset (centering) - area where the image doesn't fill the widget horizontally + final double offsetX = (widgetSize.width - displayedWidth) / 2; + // Adjust local coordinates by subtracting the offset + final double adjustedX = this - offsetX; + // Convert to image coordinates using the actual scale + final double result = adjustedX / actualScale; + if (!result.isFinite) { + return 0; + } + // Clamp to valid image bounds + return result.clamp(0, imageWidth - 1).toInt(); + } + + /// Convert local Y coordinate to remote framebuffer Y coordinate. + int toRemoteY({ + required final Size widgetSize, + required final int imageWidth, + required final int imageHeight, + }) { + // Check if sizes are valid + if (widgetSize.width <= 0 || + widgetSize.height <= 0 || + imageWidth <= 0 || + imageHeight <= 0) { + return 0; + } + // Additional defensive checks for Infinity/NaN + if (!isFinite || + !widgetSize.height.isFinite || + !imageHeight.isFinite || + widgetSize.height == 0 || + imageHeight == 0) { + return 0; + } + // Calculate scaling factors for both dimensions + final double scaleX = widgetSize.width / imageWidth; + final double scaleY = widgetSize.height / imageHeight; + // With BoxFit.contain, the actual scale is the minimum to preserve aspect ratio + final double actualScale = scaleX < scaleY ? scaleX : scaleY; + // Calculate the displayed image size + final double displayedHeight = imageHeight * actualScale; + // Calculate offset (centering) - area where the image doesn't fill the widget vertically + final double offsetY = (widgetSize.height - displayedHeight) / 2; + // Adjust local coordinates by subtracting the offset + final double adjustedY = this - offsetY; + // Convert to image coordinates using the actual scale + final double result = adjustedY / actualScale; + if (!result.isFinite) { + return 0; + } + // Clamp to valid image bounds + return result.clamp(0, imageHeight - 1).toInt(); + } +} diff --git a/lib/src/remote_frame_buffer_client_isolate.dart b/lib/src/remote_frame_buffer_client_isolate.dart index d08bb52..d3e8753 100644 --- a/lib/src/remote_frame_buffer_client_isolate.dart +++ b/lib/src/remote_frame_buffer_client_isolate.dart @@ -82,11 +82,18 @@ Future startRemoteFrameBufferClient( frameBufferUpdateRequest: (final _) => client.requestUpdate(), ); }); - await client.connect( - hostname: initMessage.hostName, - password: initMessage.password.toNullable(), - port: initMessage.port, - ); + try { + await client.connect( + hostname: initMessage.hostName, + password: initMessage.password.toNullable(), + port: initMessage.port, + ); + } on Exception catch (e, st) { + // Même format que [Isolate.spawn] `onError` : évite une erreur « non gérée » + // dans l’isolate pour un refus TCP / handshake attendu côté UI. + initMessage.sendPort.send([e, st]); + return; + } client ..handleIncomingMessages() ..requestUpdate(); diff --git a/lib/src/remote_frame_buffer_gesture_detector.dart b/lib/src/remote_frame_buffer_gesture_detector.dart index c924ab7..9c73d43 100644 --- a/lib/src/remote_frame_buffer_gesture_detector.dart +++ b/lib/src/remote_frame_buffer_gesture_detector.dart @@ -2,6 +2,7 @@ import 'dart:isolate'; import 'dart:ui'; import 'package:flutter/widgets.dart' hide Image; +import 'package:flutter_rfb/src/extensions/coordinate_conversion_extensions.dart'; import 'package:flutter_rfb/src/remote_frame_buffer_isolate_messages.dart'; import 'package:fpdart/fpdart.dart'; @@ -20,9 +21,20 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { _remoteFrameBufferWidgetSize = remoteFrameBufferWidgetSize, _sendPort = sendPort; + /// Check if the widget and image sizes are valid for coordinate calculations. + bool get _hasValidSize => + _remoteFrameBufferWidgetSize.width > 0 && + _remoteFrameBufferWidgetSize.height > 0 && + _image.width > 0 && + _image.height > 0; + @override GestureTapDownCallback? get onSecondaryTapDown => - (final TapDownDetails details) => _sendPort.match( + (final TapDownDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( () {}, (final SendPort sendPort) => sendPort.send( RemoteFrameBufferIsolateSendMessage.pointerEvent( @@ -34,21 +46,27 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { button6Down: false, button7Down: false, button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), + x: details.localPosition.dx.toRemoteX( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), + y: details.localPosition.dy.toRemoteY( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), ), ), ); + }; @override - GestureTapUpCallback? get onSecondaryTapUp => - (final TapUpDetails details) => _sendPort.match( + GestureTapUpCallback? get onSecondaryTapUp => (final TapUpDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( () {}, (final SendPort sendPort) => sendPort.send( RemoteFrameBufferIsolateSendMessage.pointerEvent( @@ -60,21 +78,27 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { button6Down: false, button7Down: false, button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), + x: details.localPosition.dx.toRemoteX( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), + y: details.localPosition.dy.toRemoteY( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), ), ), ); + }; @override - GestureTapDownCallback? get onTapDown => - (final TapDownDetails details) => _sendPort.match( + GestureTapDownCallback? get onTapDown => (final TapDownDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( () {}, (final SendPort sendPort) => sendPort.send( RemoteFrameBufferIsolateSendMessage.pointerEvent( @@ -86,21 +110,27 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { button6Down: false, button7Down: false, button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), + x: details.localPosition.dx.toRemoteX( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), + y: details.localPosition.dy.toRemoteY( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), ), ), ); + }; @override - GestureTapUpCallback? get onTapUp => - (final TapUpDetails details) => _sendPort.match( + GestureTapUpCallback? get onTapUp => (final TapUpDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( () {}, (final SendPort sendPort) => sendPort.send( RemoteFrameBufferIsolateSendMessage.pointerEvent( @@ -112,15 +142,18 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { button6Down: false, button7Down: false, button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), + x: details.localPosition.dx.toRemoteX( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), + y: details.localPosition.dy.toRemoteY( + widgetSize: _remoteFrameBufferWidgetSize, + imageWidth: _image.width, + imageHeight: _image.height, + ), ), ), ); + }; } diff --git a/lib/src/remote_frame_buffer_widget.dart b/lib/src/remote_frame_buffer_widget.dart index be3507e..fbb356a 100644 --- a/lib/src/remote_frame_buffer_widget.dart +++ b/lib/src/remote_frame_buffer_widget.dart @@ -6,6 +6,7 @@ import 'dart:ui'; import 'package:dart_rfb/dart_rfb.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide Image; +import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_rfb/src/child_size_notifier_widget.dart'; import 'package:flutter_rfb/src/extensions/logical_keyboard_key_extensions.dart'; @@ -24,23 +25,36 @@ class RemoteFrameBufferWidget extends StatefulWidget { final Option _connectingWidget; final String _hostName; final Option _onError; + final Option _onFirstFrame; final Option _password; final int _port; + final bool _syncLocalClipboardToRemote; /// Immediately tries to establish a connection to a remote server at /// [hostName]:[port], optionally using [password]. + /// + /// Set [syncLocalClipboardToRemote] to `false` to avoid pushing the local + /// clipboard to the remote server (useful when the host app copies logs or + /// console text that must not be sent to the VNC session). + /// + /// [onFirstFrame] is called once after the first decoded framebuffer image + /// is displayed. RemoteFrameBufferWidget({ super.key, final Widget? connectingWidget, required final String hostName, final void Function(Object error)? onError, + final VoidCallback? onFirstFrame, final String? password, final int port = 5900, + final bool syncLocalClipboardToRemote = true, }) : _connectingWidget = optionOf(connectingWidget), _hostName = hostName, _onError = optionOf(onError), + _onFirstFrame = optionOf(onFirstFrame), _password = optionOf(password), - _port = port; + _port = port, + _syncLocalClipboardToRemote = syncLocalClipboardToRemote; @override State createState() => @@ -49,7 +63,7 @@ class RemoteFrameBufferWidget extends StatefulWidget { @visibleForTesting class RemoteFrameBufferWidgetState extends State { - late Timer _clipBoardMonitorTimer; + Timer? _clipBoardMonitorTimer; Option _frameBuffer = none(); Option _image = none(); Option _isolate = none(); @@ -58,26 +72,14 @@ class RemoteFrameBufferWidgetState extends State { Option> _streamSubscription = none(); @override - Widget build(final BuildContext context) => _frameBuffer - .flatMap( - (final ByteData frameBuffer) => frameBuffer.buffer - .asUint8List( - frameBuffer.offsetInBytes, - frameBuffer.lengthInBytes, - ) - .where((final int byte) => byte != 0) - .isNotEmpty - ? _image - : none(), - ) - .match( + Widget build(final BuildContext context) => _image.match( _buildConnecting, (final Image image) => _buildImage(image: image), ); @override void dispose() { - _clipBoardMonitorTimer.cancel(); + _clipBoardMonitorTimer?.cancel(); _streamSubscription.match( () {}, (final StreamSubscription subscription) => @@ -91,15 +93,17 @@ class RemoteFrameBufferWidgetState extends State { () {}, (final Isolate isolate) => isolate.kill(), ); - RawKeyboard.instance.removeListener(_rawKeyEventListener); + HardwareKeyboard.instance.removeHandler(_keyEventHandler); super.dispose(); } @override void initState() { super.initState(); - _monitorClipBoard(); - RawKeyboard.instance.addListener(_rawKeyEventListener); + if (widget._syncLocalClipboardToRemote) { + _monitorClipBoard(); + } + HardwareKeyboard.instance.addHandler(_keyEventHandler); unawaited(_initAsync()); } @@ -112,11 +116,18 @@ class RemoteFrameBufferWidgetState extends State { SizeTrackingWidget _buildImage({required final Image image}) => SizeTrackingWidget( sizeValueNotifier: _sizeValueNotifier, - child: RemoteFrameBufferGestureDetector( - image: image, - remoteFrameBufferWidgetSize: _sizeValueNotifier.value, - sendPort: _isolateSendPort, - child: RawImage(image: image), + child: ValueListenableBuilder( + valueListenable: _sizeValueNotifier, + builder: (final BuildContext context, final Size size, final Widget? child) => + RemoteFrameBufferGestureDetector( + image: image, + remoteFrameBufferWidgetSize: size, + sendPort: _isolateSendPort, + child: RawImage( + image: image, + fit: BoxFit.contain, + ), + ), ), ); @@ -124,33 +135,63 @@ class RemoteFrameBufferWidgetState extends State { required final ByteData frameBuffer, required final RemoteFrameBufferIsolateReceiveMessageFrameBufferUpdate message, - }) => - decodeImageFromPixels( - frameBuffer.buffer.asUint8List(), - message.frameBufferWidth, - message.frameBufferHeight, - PixelFormat.bgra8888, - (final Image result) { - if (mounted) { - setState( - () { - _image.match( - () {}, - (final Image image) => image.dispose(), - ); - _image = some(result); - }, - ); - _isolateSendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - const RemoteFrameBufferIsolateSendMessage - .frameBufferUpdateRequest(), - ), - ); - } - }, + }) { + final int w = message.frameBufferWidth; + final int h = message.frameBufferHeight; + if (w <= 0 || h <= 0) { + _logger.warning('Framebuffer update: invalid dimensions (${w}x$h).'); + return; + } + final int expectedBytes = w * h * 4; + if (frameBuffer.lengthInBytes < expectedBytes) { + _logger.warning( + 'Framebuffer update: buffer too short (${frameBuffer.lengthInBytes} < $expectedBytes).', ); + return; + } + final Uint8List pixels = frameBuffer.buffer.asUint8List( + frameBuffer.offsetInBytes, + expectedBytes, + ); + decodeImageFromPixels( + pixels, + w, + h, + PixelFormat.bgra8888, + (final Image result) { + if (!mounted) { + result.dispose(); + return; + } + final bool isFirstFrame = _image.isNone(); + setState(() { + _image.match( + () {}, + (final Image image) => image.dispose(), + ); + _image = some(result); + }); + if (isFirstFrame) { + widget._onFirstFrame.match( + () {}, + (final VoidCallback callback) { + SchedulerBinding.instance.addPostFrameCallback((final _) { + if (mounted) { + callback(); + } + }); + }, + ); + } + _isolateSendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + const RemoteFrameBufferIsolateSendMessage.frameBufferUpdateRequest(), + ), + ); + }, + ); + } Task _handleFrameBufferUpdateMessage({ required final RemoteFrameBufferIsolateReceiveMessageFrameBufferUpdate @@ -206,9 +247,9 @@ class RemoteFrameBufferWidgetState extends State { ), ).run()) .match( - (final Object error) => - // ignore: avoid_print - print('Error updating frame buffer: $error'), + (final Object error) => debugPrint( + 'RemoteFrameBufferWidget: updateFrameBuffer $error', + ), (final _) {}, ); }, @@ -221,11 +262,14 @@ class RemoteFrameBufferWidgetState extends State { rectangle: rectangle, ).run()) .match( - (final Object error) => - // ignore: avoid_print - print('Error updating frame buffer: $error'), + (final Object error) => debugPrint( + 'RemoteFrameBufferWidget: updateFrameBuffer $error', + ), (final _) {}, ), + zrle: () async => _logger.warning( + 'ZRLE rectangle received — decoding should happen upstream in dart_rfb.', + ), unsupported: (final ByteData bytes) async {}, ); } @@ -244,7 +288,6 @@ class RemoteFrameBufferWidgetState extends State { _streamSubscription = some( receivePort.listen( (final Object? message) { - // Error, first is error, second is stacktrace or null if (message is List) { widget._onError.match( () {}, @@ -257,12 +300,14 @@ class RemoteFrameBufferWidgetState extends State { final RemoteFrameBufferIsolateReceiveMessageClipBoardUpdate update, ) => - Clipboard.setData(ClipboardData(text: update.text)), + unawaited( + Clipboard.setData(ClipboardData(text: update.text)), + ), frameBufferUpdate: ( final RemoteFrameBufferIsolateReceiveMessageFrameBufferUpdate update, ) { - _handleFrameBufferUpdateMessage(update: update).run(); + unawaited(_handleFrameBufferUpdateMessage(update: update).run()); }, ); } @@ -316,16 +361,18 @@ class RemoteFrameBufferWidgetState extends State { ); } - void _rawKeyEventListener(final RawKeyEvent rawKeyEvent) => - _isolateSendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.keyEvent( - down: rawKeyEvent.isKeyPressed(rawKeyEvent.logicalKey), - key: rawKeyEvent.logicalKey.asXWindowSystemKey(), - ), + bool _keyEventHandler(final KeyEvent event) { + _isolateSendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.keyEvent( + down: event is KeyDownEvent, + key: event.logicalKey.asXWindowSystemKey(), ), - ); + ), + ); + return false; + } /// Updates [frameBuffer] with the given [rectangle]s. @visibleForTesting diff --git a/pubspec.lock b/pubspec.lock index 850d65e..e18e1e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,189 +5,217 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" source: hosted - version: "50.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "7.7.1" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: b003c3098049a51720352d219b0bb5f219b60fbfb68e7a4748139a06a5676515 + url: "https://pub.dev" source: hosted version: "2.3.1" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.13.0" binary: dependency: transitive description: name: binary - url: "https://pub.dartlang.org" + sha256: "9e095f2f1c94f06501352c8621f12d055da1c945dffae933ed0ae7fa5eeba046" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "5bbf32bc9e518d41ec49718e2931cd4527292c9b0c6d2dffcf7fe6b9a8a8cf72" + url: "https://pub.dev" source: hosted version: "2.1.0" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "8.0.0" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + sha256: "59e08b0079bb75f7e27392498e26339387c1089c6bd58525a14eb8508637277b" + url: "https://pub.dev" source: hosted version: "8.4.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659 + url: "https://pub.dev" source: hosted version: "2.0.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + sha256: "02ce3596b459c666530f045ad6f96209474e8fee6e4855940a3cee65fb872ec5" + url: "https://pub.dev" source: hosted version: "4.3.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.1" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted version: "3.1.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" dart_des: dependency: transitive description: name: dart_des - url: "https://pub.dartlang.org" + sha256: "0a66afb8883368c824497fd2a1fd67bdb1a785965a3956728382c03d40747c33" + url: "https://pub.dev" source: hosted version: "1.0.2" dart_rfb: dependency: "direct main" description: - name: dart_rfb - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.0" + path: "." + ref: main + resolved-ref: "4321ffb3199ecb47c429b725be6a1c2472b15a1d" + url: "https://github.com/Spokplacenta/dart-rfb.git" + source: git + version: "0.9.0" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "3.1.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + url: "https://pub.dev" source: hosted version: "1.0.1" flutter: @@ -199,9 +227,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "5.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -211,280 +240,343 @@ packages: dependency: "direct main" description: name: fpdart - url: "https://pub.dartlang.org" + sha256: "19db038cf3bb49bfe28b2a0b363ce84cf2e6a0f471a93ae09271cfef03dae935" + url: "https://pub.dev" source: hosted version: "0.4.0" freezed: dependency: "direct dev" description: name: freezed - url: "https://pub.dartlang.org" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - url: "https://pub.dartlang.org" + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.4" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" source: hosted version: "3.2.0" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" source: hosted version: "2.1.1" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" source: hosted version: "2.2.0" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted version: "4.0.2" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852" + url: "https://pub.dev" source: hosted version: "1.0.3" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted version: "0.6.5" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "3.0.2" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "5.1.1" logging: dependency: "direct main" description: name: logging - url: "https://pub.dartlang.org" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" source: hosted - version: "1.1.0" - matan: - dependency: transitive - description: - name: matan - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" + version: "1.3.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.13.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.17.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: "52e38f7e1143ef39daf532117d6b8f8f617bf4bcd6044ed8c29040d20d269630" + url: "https://pub.dev" source: hosted version: "1.0.3" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted version: "2.1.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted version: "1.5.1" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + url: "https://pub.dev" source: hosted version: "1.2.1" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" source: hosted version: "1.4.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" source: hosted version: "1.0.3" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "2.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: e3320978e3715725e62f04358fd249c1efe5999297b2c6acd626a817593281b0 + url: "https://pub.dev" source: hosted version: "1.9.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" stream_transform: dependency: "direct main" description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "862015c5db1f3f3c4ea3b94dc2490363a84262994b88902315ed74be1155612f" + url: "https://pub.dev" source: hosted version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.7.10" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad + url: "https://pub.dev" source: hosted version: "1.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.2.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: "3a969ddcc204a3e34e863d204b29c0752716f78b6f9cc8235083208d268a4ccd" + url: "https://pub.dev" source: hosted version: "2.2.0" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.6 <3.0.0" - flutter: ">=1.17.0" + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 0638006..9b79a6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,32 +2,31 @@ description: A VNC / Remote Framebuffer / RFC 6143 client purely written in Dart homepage: https://github.com/Goddchen/flutter-vnc name: flutter_rfb repository: https://github.com/Goddchen/flutter-vnc -version: 0.6.2 +version: 0.8.0 +publish_to: 'none' environment: - sdk: ">=2.18.6 <3.0.0" - flutter: ">=1.17.0" + sdk: ^3.5.0 + flutter: ">=3.16.0" dependencies: - dart_rfb: ^0.7.0 - # dart_rfb: - # git: - # path: dart-rfb/ - # ref: main - # url: https://github.com/Goddchen/dart-rfb - # path: ../dart-rfb + dart_rfb: + git: + url: https://github.com/Spokplacenta/dart-rfb.git + ref: main flutter: sdk: flutter + # Même contrainte que dart_rfb (dépendance git ci-dessus) pour une seule résolution. fpdart: ^0.4.0 - freezed_annotation: ^2.2.0 - logging: ^1.1.0 + freezed_annotation: ^2.4.4 + logging: ^1.2.0 stream_transform: ^2.1.0 dev_dependencies: - build_runner: ^2.3.3 - flutter_lints: ^2.0.0 + build_runner: ^2.4.13 + flutter_lints: ^5.0.0 flutter_test: sdk: flutter - freezed: ^2.3.2 + freezed: ^2.5.7 flutter: