From f1b037fe5999681158c1e7971a9bca8ff3ad0aed Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Thu, 27 Nov 2025 11:17:29 +0100 Subject: [PATCH 01/11] Adapt Flutter widget for ZRLE --- CHANGELOG.md | 4 + example/pubspec.lock | 168 +++++++++----- lib/src/remote_frame_buffer_widget.dart | 3 + pubspec.lock | 290 ++++++++++++++++-------- pubspec.yaml | 16 +- 5 files changed, 313 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdd394..3a05236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,3 +39,7 @@ ## 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 diff --git a/example/pubspec.lock b/example/pubspec.lock index ddcf30f..0ba786b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,72 +5,81 @@ 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: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.0" 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: "../../dart-rfb" + relative: true + source: path + 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 +89,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 +99,7 @@ packages: path: ".." relative: true source: path - version: "0.6.1" + version: "0.7.0" flutter_test: dependency: "direct dev" description: flutter @@ -99,133 +109,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" + sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946 + 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" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.11.1" 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: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.7.7" 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.8.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/remote_frame_buffer_widget.dart b/lib/src/remote_frame_buffer_widget.dart index be3507e..37c694a 100644 --- a/lib/src/remote_frame_buffer_widget.dart +++ b/lib/src/remote_frame_buffer_widget.dart @@ -226,6 +226,9 @@ class RemoteFrameBufferWidgetState extends State { print('Error updating frame buffer: $error'), (final _) {}, ), + zrle: () async => _logger.warning( + 'ZRLE rectangle received but not decoded upstream', + ), unsupported: (final ByteData bytes) async {}, ); } diff --git a/pubspec.lock b/pubspec.lock index 850d65e..2a3ef5e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,189 +5,215 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" source: hosted - version: "50.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.13.0" 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: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" source: hosted version: "2.3.1" 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: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + url: "https://pub.dev" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab" + url: "https://pub.dev" source: hosted version: "2.1.0" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" source: hosted version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" source: hosted version: "7.2.7" 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: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.0" 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: "../dart-rfb" + relative: true + source: path + version: "0.9.0" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" source: hosted version: "2.2.4" 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,7 +225,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_test: @@ -211,280 +238,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: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.2" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - url: "https://pub.dartlang.org" + sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2 + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" 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 + dependency: "direct main" 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: "4.7.0" + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + 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" + sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946 + 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" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.11.1" 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: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted version: "2.1.3" 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: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.5.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: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.7.7" 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: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted version: "1.0.2" 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: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" sdks: - dart: ">=2.18.6 <3.0.0" - flutter: ">=1.17.0" + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 0638006..a3565e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,24 +2,20 @@ 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.7.0 environment: sdk: ">=2.18.6 <3.0.0" flutter: ">=1.17.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: + path: ../dart-rfb flutter: sdk: flutter fpdart: ^0.4.0 - freezed_annotation: ^2.2.0 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 logging: ^1.1.0 stream_transform: ^2.1.0 @@ -28,6 +24,6 @@ dev_dependencies: flutter_lints: ^2.0.0 flutter_test: sdk: flutter - freezed: ^2.3.2 + freezed: ^2.5.2 flutter: From dfff479eef63f3f5e0cb9635310a9f4bcecb429b Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Fri, 28 Nov 2025 20:57:36 +0100 Subject: [PATCH 02/11] fix: prevent Infinity/NaN errors in gesture detector - Add defensive checks for Infinity/NaN values in coordinate calculations - Add _hasValidSize check to prevent errors when widget or image size is invalid - Extract coordinate conversion to helper methods with proper validation --- CHANGELOG.md | 5 + .../remote_frame_buffer_gesture_detector.dart | 222 +++++++++++------- pubspec.yaml | 2 +- 3 files changed, 137 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a05236..0fa3ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,3 +43,8 @@ ## 0.7.0 - Use `dart_rfb` 0.9.0 with ZRLE support and widget-side logging + +## 0.7.1 + +- Fix `Unsupported operation: Infinity or NaN toInt` error in gesture detector +- Add defensive checks for Infinity/NaN values in coordinate calculations diff --git a/lib/src/remote_frame_buffer_gesture_detector.dart b/lib/src/remote_frame_buffer_gesture_detector.dart index c924ab7..7adaaf8 100644 --- a/lib/src/remote_frame_buffer_gesture_detector.dart +++ b/lib/src/remote_frame_buffer_gesture_detector.dart @@ -20,107 +20,147 @@ 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; + + /// Convert local position to remote framebuffer coordinates. + int _toRemoteX(final double localX) { + if (!_hasValidSize) { + return 0; + } + // Additional defensive checks for Infinity/NaN + if (!localX.isFinite || + !_remoteFrameBufferWidgetSize.width.isFinite || + !_image.width.isFinite || + _remoteFrameBufferWidgetSize.width == 0 || + _image.width == 0) { + return 0; + } + final double result = localX / _remoteFrameBufferWidgetSize.width * _image.width; + if (!result.isFinite) { + return 0; + } + return result.toInt(); + } + + int _toRemoteY(final double localY) { + if (!_hasValidSize) { + return 0; + } + // Additional defensive checks for Infinity/NaN + if (!localY.isFinite || + !_remoteFrameBufferWidgetSize.height.isFinite || + !_image.height.isFinite || + _remoteFrameBufferWidgetSize.height == 0 || + _image.height == 0) { + return 0; + } + final double result = localY / _remoteFrameBufferWidgetSize.height * _image.height; + if (!result.isFinite) { + return 0; + } + return result.toInt(); + } + @override GestureTapDownCallback? get onSecondaryTapDown => - (final TapDownDetails details) => _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: true, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), - ), + (final TapDownDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: true, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + x: _toRemoteX(details.localPosition.dx), + y: _toRemoteY(details.localPosition.dy), ), - ); + ), + ); + }; @override - GestureTapUpCallback? get onSecondaryTapUp => - (final TapUpDetails details) => _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), - ), + GestureTapUpCallback? get onSecondaryTapUp => (final TapUpDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + x: _toRemoteX(details.localPosition.dx), + y: _toRemoteY(details.localPosition.dy), ), - ); + ), + ); + }; @override - GestureTapDownCallback? get onTapDown => - (final TapDownDetails details) => _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: true, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), - ), + GestureTapDownCallback? get onTapDown => (final TapDownDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: true, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + x: _toRemoteX(details.localPosition.dx), + y: _toRemoteY(details.localPosition.dy), ), - ); + ), + ); + }; @override - GestureTapUpCallback? get onTapUp => - (final TapUpDetails details) => _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: (details.localPosition.dx / - _remoteFrameBufferWidgetSize.width * - _image.width) - .toInt(), - y: (details.localPosition.dy / - _remoteFrameBufferWidgetSize.height * - _image.height) - .toInt(), - ), + GestureTapUpCallback? get onTapUp => (final TapUpDetails details) { + if (!_hasValidSize) { + return; + } + _sendPort.match( + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + x: _toRemoteX(details.localPosition.dx), + y: _toRemoteY(details.localPosition.dy), ), - ); + ), + ); + }; } + diff --git a/pubspec.yaml b/pubspec.yaml index a3565e8..2fd4738 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ 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.7.0 +version: 0.7.1 environment: sdk: ">=2.18.6 <3.0.0" From e89bb7267801eec8c5d3a4625b02d9fc3b9722d1 Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Fri, 28 Nov 2025 20:58:12 +0100 Subject: [PATCH 03/11] fix: improve mouse coordinate mapping and widget size tracking - Fix coordinate calculation to account for BoxFit.contain scaling and centering offsets - Handle different aspect ratios correctly by calculating actual scale and 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 --- CHANGELOG.md | 8 ++++ lib/src/child_size_notifier_widget.dart | 36 +++++++++++++++--- .../remote_frame_buffer_gesture_detector.dart | 38 +++++++++++++++++-- lib/src/remote_frame_buffer_widget.dart | 17 ++++++--- pubspec.yaml | 2 +- 5 files changed, 86 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa3ed6..91cd234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,3 +48,11 @@ - Fix `Unsupported operation: Infinity or NaN toInt` error in gesture detector - Add defensive checks for Infinity/NaN values in coordinate calculations + +## 0.7.2 + +- 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 diff --git a/lib/src/child_size_notifier_widget.dart b/lib/src/child_size_notifier_widget.dart index ea93a07..1e44b15 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; @@ -19,15 +20,40 @@ class SizeTrackingWidget extends StatefulWidget { } class _SizeTackingState 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 - Widget build(final BuildContext context) => widget._child; + void didUpdateWidget(final SizeTrackingWidget oldWidget) { + super.didUpdateWidget(oldWidget); + WidgetsBinding.instance.addPostFrameCallback((final _) => _updateSize()); + } + + @override + Widget build(final BuildContext context) { + // Use LayoutBuilder to detect size changes + return LayoutBuilder( + builder: (final BuildContext context, final BoxConstraints constraints) { + // Schedule size update after layout + WidgetsBinding.instance.addPostFrameCallback((final _) => _updateSize()); + return widget._child; + }, + ); + } } diff --git a/lib/src/remote_frame_buffer_gesture_detector.dart b/lib/src/remote_frame_buffer_gesture_detector.dart index 7adaaf8..c708624 100644 --- a/lib/src/remote_frame_buffer_gesture_detector.dart +++ b/lib/src/remote_frame_buffer_gesture_detector.dart @@ -28,6 +28,10 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { _image.height > 0; /// Convert local position 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. int _toRemoteX(final double localX) { if (!_hasValidSize) { return 0; @@ -40,11 +44,24 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { _image.width == 0) { return 0; } - final double result = localX / _remoteFrameBufferWidgetSize.width * _image.width; + // Calculate scaling factors for both dimensions + final double scaleX = _remoteFrameBufferWidgetSize.width / _image.width; + final double scaleY = _remoteFrameBufferWidgetSize.height / _image.height; + // 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 = _image.width * actualScale; + // Calculate offset (centering) - area where the image doesn't fill the widget horizontally + final double offsetX = (_remoteFrameBufferWidgetSize.width - displayedWidth) / 2; + // Adjust local coordinates by subtracting the offset + final double adjustedX = localX - offsetX; + // Convert to image coordinates using the actual scale + final double result = adjustedX / actualScale; if (!result.isFinite) { return 0; } - return result.toInt(); + // Clamp to valid image bounds + return result.clamp(0, _image.width - 1).toInt(); } int _toRemoteY(final double localY) { @@ -59,11 +76,24 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { _image.height == 0) { return 0; } - final double result = localY / _remoteFrameBufferWidgetSize.height * _image.height; + // Calculate scaling factors for both dimensions + final double scaleX = _remoteFrameBufferWidgetSize.width / _image.width; + final double scaleY = _remoteFrameBufferWidgetSize.height / _image.height; + // 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 = _image.height * actualScale; + // Calculate offset (centering) - area where the image doesn't fill the widget vertically + final double offsetY = (_remoteFrameBufferWidgetSize.height - displayedHeight) / 2; + // Adjust local coordinates by subtracting the offset + final double adjustedY = localY - offsetY; + // Convert to image coordinates using the actual scale + final double result = adjustedY / actualScale; if (!result.isFinite) { return 0; } - return result.toInt(); + // Clamp to valid image bounds + return result.clamp(0, _image.height - 1).toInt(); } @override diff --git a/lib/src/remote_frame_buffer_widget.dart b/lib/src/remote_frame_buffer_widget.dart index 37c694a..11d8b76 100644 --- a/lib/src/remote_frame_buffer_widget.dart +++ b/lib/src/remote_frame_buffer_widget.dart @@ -112,11 +112,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, // Preserve aspect ratio, coordinates are calculated correctly + ), + ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 2fd4738..03989b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ 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.7.1 +version: 0.7.2 environment: sdk: ">=2.18.6 <3.0.0" From 544787cca7b293b46632ac0ff7eeaaa15f8cab7a Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Mon, 1 Dec 2025 09:26:53 +0100 Subject: [PATCH 04/11] chore: update project configuration and improve AppDelegate - Update .gitignore to include new build-related directories - Change AppDelegate annotation from @NSApplicationMain to @main - Increment object version and last upgrade check in project.pbxproj - Update MACOSX_DEPLOYMENT_TARGET to 10.15 in project settings - Add GPU validation mode in Runner.xcscheme --- example/.gitignore | 2 ++ example/macos/Runner.xcodeproj/project.pbxproj | 11 ++++++----- .../xcshareddata/xcschemes/Runner.xcscheme | 3 ++- example/macos/Runner/AppDelegate.swift | 6 +++++- 4 files changed, 15 insertions(+), 7 deletions(-) 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 + } } From 2212ddbec00f6cb8dc41d8b38474bc672414a84d Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Wed, 7 Jan 2026 18:22:30 +0100 Subject: [PATCH 05/11] refactor: convert coordinate conversion methods to extension functions - Extract _toRemoteX and _toRemoteY methods to CoordinateConversion extension on double - Improve code readability by using extension methods instead of private methods - Make coordinate conversion logic reusable across the codebase --- .../coordinate_conversion_extensions.dart | 92 ++++++++ .../remote_frame_buffer_gesture_detector.dart | 223 ++++++++---------- 2 files changed, 185 insertions(+), 130 deletions(-) create mode 100644 lib/src/extensions/coordinate_conversion_extensions.dart 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_gesture_detector.dart b/lib/src/remote_frame_buffer_gesture_detector.dart index c708624..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'; @@ -27,75 +28,6 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { _image.width > 0 && _image.height > 0; - /// Convert local position 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. - int _toRemoteX(final double localX) { - if (!_hasValidSize) { - return 0; - } - // Additional defensive checks for Infinity/NaN - if (!localX.isFinite || - !_remoteFrameBufferWidgetSize.width.isFinite || - !_image.width.isFinite || - _remoteFrameBufferWidgetSize.width == 0 || - _image.width == 0) { - return 0; - } - // Calculate scaling factors for both dimensions - final double scaleX = _remoteFrameBufferWidgetSize.width / _image.width; - final double scaleY = _remoteFrameBufferWidgetSize.height / _image.height; - // 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 = _image.width * actualScale; - // Calculate offset (centering) - area where the image doesn't fill the widget horizontally - final double offsetX = (_remoteFrameBufferWidgetSize.width - displayedWidth) / 2; - // Adjust local coordinates by subtracting the offset - final double adjustedX = localX - 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, _image.width - 1).toInt(); - } - - int _toRemoteY(final double localY) { - if (!_hasValidSize) { - return 0; - } - // Additional defensive checks for Infinity/NaN - if (!localY.isFinite || - !_remoteFrameBufferWidgetSize.height.isFinite || - !_image.height.isFinite || - _remoteFrameBufferWidgetSize.height == 0 || - _image.height == 0) { - return 0; - } - // Calculate scaling factors for both dimensions - final double scaleX = _remoteFrameBufferWidgetSize.width / _image.width; - final double scaleY = _remoteFrameBufferWidgetSize.height / _image.height; - // 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 = _image.height * actualScale; - // Calculate offset (centering) - area where the image doesn't fill the widget vertically - final double offsetY = (_remoteFrameBufferWidgetSize.height - displayedHeight) / 2; - // Adjust local coordinates by subtracting the offset - final double adjustedY = localY - 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, _image.height - 1).toInt(); - } - @override GestureTapDownCallback? get onSecondaryTapDown => (final TapDownDetails details) { @@ -103,22 +35,30 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { return; } _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: true, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: _toRemoteX(details.localPosition.dx), - y: _toRemoteY(details.localPosition.dy), + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: true, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + 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 @@ -127,22 +67,30 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { return; } _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: _toRemoteX(details.localPosition.dx), - y: _toRemoteY(details.localPosition.dy), + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + 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 @@ -151,22 +99,30 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { return; } _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: true, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: _toRemoteX(details.localPosition.dx), - y: _toRemoteY(details.localPosition.dy), + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: true, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + 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 @@ -175,22 +131,29 @@ class RemoteFrameBufferGestureDetector extends GestureDetector { return; } _sendPort.match( - () {}, - (final SendPort sendPort) => sendPort.send( - RemoteFrameBufferIsolateSendMessage.pointerEvent( - button1Down: false, - button2Down: false, - button3Down: false, - button4Down: false, - button5Down: false, - button6Down: false, - button7Down: false, - button8Down: false, - x: _toRemoteX(details.localPosition.dx), - y: _toRemoteY(details.localPosition.dy), + () {}, + (final SendPort sendPort) => sendPort.send( + RemoteFrameBufferIsolateSendMessage.pointerEvent( + button1Down: false, + button2Down: false, + button3Down: false, + button4Down: false, + button5Down: false, + button6Down: false, + button7Down: false, + button8Down: false, + 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, + ), + ), ), - ), - ); + ); }; } - From 3cc4352eb2624a418ebc5113c7f6100d6c09e6a1 Mon Sep 17 00:00:00 2001 From: Spokplacenta <71837821+Spokplacenta@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:24:41 +0100 Subject: [PATCH 06/11] Update pubspec.yaml Co-authored-by: Goddchen --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 03989b9..b3d8af0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ environment: flutter: ">=1.17.0" dependencies: - dart_rfb: + dart_rfb: ^0.9.0 path: ../dart-rfb flutter: sdk: flutter From ec148bfab9bd58c169c7c1dab90e6c54af110175 Mon Sep 17 00:00:00 2001 From: Spokplacenta <71837821+Spokplacenta@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:25:04 +0100 Subject: [PATCH 07/11] Update pubspec.yaml Co-authored-by: Goddchen --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b3d8af0..852a9b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ 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.7.2 +version: 0.7.0 environment: sdk: ">=2.18.6 <3.0.0" From 1ac64c24835c14e64f6d0f06d5b4ab827c2676ee Mon Sep 17 00:00:00 2001 From: Spokplacenta <71837821+Spokplacenta@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:26:08 +0100 Subject: [PATCH 08/11] Update pubspec.yaml Co-authored-by: Goddchen --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 852a9b0..b9bf1ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,11 @@ environment: dependencies: dart_rfb: ^0.9.0 - path: ../dart-rfb + # git: + # path: dart-rfb/ + # ref: main + # url: https://github.com/Goddchen/dart-rfb + # path: ../dart-rfb flutter: sdk: flutter fpdart: ^0.4.0 From 8636b02c5d83cee41f65da1b0876ee15f88b538c Mon Sep 17 00:00:00 2001 From: Spokplacenta <71837821+Spokplacenta@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:27:41 +0100 Subject: [PATCH 09/11] Update lib/src/remote_frame_buffer_widget.dart Co-authored-by: Goddchen --- lib/src/remote_frame_buffer_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/remote_frame_buffer_widget.dart b/lib/src/remote_frame_buffer_widget.dart index 11d8b76..14d4a32 100644 --- a/lib/src/remote_frame_buffer_widget.dart +++ b/lib/src/remote_frame_buffer_widget.dart @@ -234,7 +234,7 @@ class RemoteFrameBufferWidgetState extends State { (final _) {}, ), zrle: () async => _logger.warning( - 'ZRLE rectangle received but not decoded upstream', + 'ZRLE rectangle received but not decoded upstream, dart_rfb should have decoded this and provided it here as a raw rectangle.', ), unsupported: (final ByteData bytes) async {}, ); From 015f868eea86e3d76024942e986c8e749c307acc Mon Sep 17 00:00:00 2001 From: Anthony Marchand Date: Wed, 7 Jan 2026 18:52:50 +0100 Subject: [PATCH 10/11] chore: remove unnecessary json_annotation dependency json_annotation is already provided as a transitive dependency by freezed_annotation, so it doesn't need to be declared explicitly. --- example/pubspec.lock | 2 +- pubspec.lock | 2 +- pubspec.yaml | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 0ba786b..382702c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -99,7 +99,7 @@ packages: path: ".." relative: true source: path - version: "0.7.0" + version: "0.7.2" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.lock b/pubspec.lock index 2a3ef5e..3d0da3f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -315,7 +315,7 @@ packages: source: hosted version: "0.6.5" json_annotation: - dependency: "direct main" + dependency: transitive description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" diff --git a/pubspec.yaml b/pubspec.yaml index b9bf1ef..f259103 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,6 @@ dependencies: sdk: flutter fpdart: ^0.4.0 freezed_annotation: ^2.4.1 - json_annotation: ^4.8.1 logging: ^1.1.0 stream_transform: ^2.1.0 From c3c56ba60e083871597f18748b3602eb902c3aff Mon Sep 17 00:00:00 2001 From: Spokplacenta <71837821+Spokplacenta@users.noreply.github.com> Date: Thu, 8 Jan 2026 00:04:42 +0100 Subject: [PATCH 11/11] Update CHANGELOG.md Co-authored-by: Goddchen --- CHANGELOG.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91cd234..f50372d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,16 +43,11 @@ ## 0.7.0 - Use `dart_rfb` 0.9.0 with ZRLE support and widget-side logging - -## 0.7.1 - - Fix `Unsupported operation: Infinity or NaN toInt` error in gesture detector - Add defensive checks for Infinity/NaN values in coordinate calculations - -## 0.7.2 - - 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 !