diff --git a/packages/pasteboard/.dart_tool/package_config.json b/packages/pasteboard/.dart_tool/package_config.json index d9a139bf..ee5e8360 100644 --- a/packages/pasteboard/.dart_tool/package_config.json +++ b/packages/pasteboard/.dart_tool/package_config.json @@ -3,133 +3,133 @@ "packages": [ { "name": "async", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.7.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/async-2.8.2", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "boolean_selector", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/boolean_selector-2.1.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "characters", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/characters-1.2.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "charcode", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/charcode-1.3.1", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "clock", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/clock-1.1.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "collection", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/collection-1.15.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "fake_async", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/fake_async-1.2.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "flutter", - "rootUri": "file:///Users/YeungKC/flutter/packages/flutter", + "rootUri": "file:///C:/Users/yangbin/fvm/versions/master/packages/flutter", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "flutter_test", - "rootUri": "file:///Users/YeungKC/flutter/packages/flutter_test", + "rootUri": "file:///C:/Users/yangbin/fvm/versions/master/packages/flutter_test", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "matcher", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/matcher-0.12.11", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "meta", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.4.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/meta-1.7.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "path", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/path-1.8.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "sky_engine", - "rootUri": "file:///Users/YeungKC/flutter/bin/cache/pkg/sky_engine", + "rootUri": "file:///C:/Users/yangbin/fvm/versions/master/bin/cache/pkg/sky_engine", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "source_span", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/source_span-1.8.1", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "stack_trace", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stack_trace-1.10.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "stream_channel", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stream_channel-2.1.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "string_scanner", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/string_scanner-1.1.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "term_glyph", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/term_glyph-1.2.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "test_api", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/test_api-0.4.3", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "typed_data", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/typed_data-1.3.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "vector_math", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/vector_math-2.1.1", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.14" }, { "name": "very_good_analysis", - "rootUri": "file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2", + "rootUri": "file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2", "packageUri": "lib/", "languageVersion": "2.12" }, @@ -140,7 +140,7 @@ "languageVersion": "2.12" } ], - "generated": "2021-07-05T05:12:47.903598Z", + "generated": "2021-10-25T09:43:45.845068Z", "generator": "pub", - "generatorVersion": "2.14.0-269.0.dev" + "generatorVersion": "2.15.0-248.0.dev" } diff --git a/packages/pasteboard/.dart_tool/package_config_subset b/packages/pasteboard/.dart_tool/package_config_subset index 0d59a139..fcc8453c 100644 --- a/packages/pasteboard/.dart_tool/package_config_subset +++ b/packages/pasteboard/.dart_tool/package_config_subset @@ -1,93 +1,93 @@ -pasteboard -2.12 -file:///Users/YeungKC/FlutterProjects/flutter-plugins/packages/pasteboard/ -file:///Users/YeungKC/FlutterProjects/flutter-plugins/packages/pasteboard/lib/ async 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.7.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.7.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/async-2.8.2/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/async-2.8.2/lib/ boolean_selector 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/ characters 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/characters-1.2.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/characters-1.2.0/lib/ charcode 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/charcode-1.3.1/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ clock 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/clock-1.1.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/clock-1.1.0/lib/ collection 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/collection-1.15.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/collection-1.15.0/lib/ fake_async 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/fake_async-1.2.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib/ matcher 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/matcher-0.12.11/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/matcher-0.12.11/lib/ meta 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.4.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.4.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/meta-1.7.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ path 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/path-1.8.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/path-1.8.0/lib/ source_span 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/source_span-1.8.1/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/source_span-1.8.1/lib/ stack_trace 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stack_trace-1.10.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/ stream_channel 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stream_channel-2.1.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/ string_scanner 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/string_scanner-1.1.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ term_glyph 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/term_glyph-1.2.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ test_api 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/test_api-0.4.3/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/test_api-0.4.3/lib/ typed_data 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/typed_data-1.3.0/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/ vector_math -2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib/ +2.14 +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/vector_math-2.1.1/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/vector_math-2.1.1/lib/ very_good_analysis 2.12 -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2/ -file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2/lib/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2/ +file:///C:/Users/yangbin/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2/lib/ sky_engine 2.12 -file:///Users/YeungKC/flutter/bin/cache/pkg/sky_engine/ -file:///Users/YeungKC/flutter/bin/cache/pkg/sky_engine/lib/ +file:///C:/Users/yangbin/fvm/versions/master/bin/cache/pkg/sky_engine/ +file:///C:/Users/yangbin/fvm/versions/master/bin/cache/pkg/sky_engine/lib/ flutter 2.12 -file:///Users/YeungKC/flutter/packages/flutter/ -file:///Users/YeungKC/flutter/packages/flutter/lib/ +file:///C:/Users/yangbin/fvm/versions/master/packages/flutter/ +file:///C:/Users/yangbin/fvm/versions/master/packages/flutter/lib/ flutter_test 2.12 -file:///Users/YeungKC/flutter/packages/flutter_test/ -file:///Users/YeungKC/flutter/packages/flutter_test/lib/ +file:///C:/Users/yangbin/fvm/versions/master/packages/flutter_test/ +file:///C:/Users/yangbin/fvm/versions/master/packages/flutter_test/lib/ +pasteboard +2.12 +file:///C:/Users/yangbin/workspace/mixin/flutter-plugins/packages/pasteboard/ +file:///C:/Users/yangbin/workspace/mixin/flutter-plugins/packages/pasteboard/lib/ 2 diff --git a/packages/pasteboard/.dart_tool/version b/packages/pasteboard/.dart_tool/version index 39204ca9..40f09f9d 100644 --- a/packages/pasteboard/.dart_tool/version +++ b/packages/pasteboard/.dart_tool/version @@ -1 +1 @@ -2.3.0-17.0.pre.630 \ No newline at end of file +2.6.0-12.0.pre.458 \ No newline at end of file diff --git a/packages/pasteboard/.gitignore b/packages/pasteboard/.gitignore new file mode 100644 index 00000000..beebdaab --- /dev/null +++ b/packages/pasteboard/.gitignore @@ -0,0 +1,32 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ + +.vscode + diff --git a/packages/pasteboard/.idea/.gitignore b/packages/pasteboard/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/packages/pasteboard/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/packages/pasteboard/.idea/libraries/Dart_SDK.xml b/packages/pasteboard/.idea/libraries/Dart_SDK.xml deleted file mode 100644 index c8a37540..00000000 --- a/packages/pasteboard/.idea/libraries/Dart_SDK.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/pasteboard/.idea/libraries/Flutter_Plugins.xml b/packages/pasteboard/.idea/libraries/Flutter_Plugins.xml deleted file mode 100644 index 53449dae..00000000 --- a/packages/pasteboard/.idea/libraries/Flutter_Plugins.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/packages/pasteboard/.idea/modules.xml b/packages/pasteboard/.idea/modules.xml deleted file mode 100644 index 82ec8255..00000000 --- a/packages/pasteboard/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/pasteboard/.idea/runConfigurations/example_lib_main_dart.xml b/packages/pasteboard/.idea/runConfigurations/example_lib_main_dart.xml deleted file mode 100644 index 5fd9159d..00000000 --- a/packages/pasteboard/.idea/runConfigurations/example_lib_main_dart.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/pasteboard/.idea/vcs.xml b/packages/pasteboard/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/packages/pasteboard/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/packages/pasteboard/.packages b/packages/pasteboard/.packages deleted file mode 100644 index 20a23093..00000000 --- a/packages/pasteboard/.packages +++ /dev/null @@ -1,29 +0,0 @@ -# This file is deprecated. Tools should instead consume -# `.dart_tools/package_config.json`. -# -# For more info see: https://dart.dev/go/dot-packages-deprecation -# -# Generated by pub on 2021-07-05 13:12:47.885095. -async:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.7.0/lib/ -boolean_selector:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/ -characters:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib/ -charcode:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ -clock:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib/ -collection:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib/ -fake_async:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib/ -flutter:file:///Users/YeungKC/flutter/packages/flutter/lib/ -flutter_test:file:///Users/YeungKC/flutter/packages/flutter_test/lib/ -matcher:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib/ -meta:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.4.0/lib/ -path:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/ -sky_engine:file:///Users/YeungKC/flutter/bin/cache/pkg/sky_engine/lib/ -source_span:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib/ -stack_trace:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/ -stream_channel:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/ -string_scanner:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ -term_glyph:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ -test_api:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/ -typed_data:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/ -vector_math:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib/ -very_good_analysis:file:///Users/YeungKC/flutter/.pub-cache/hosted/pub.dartlang.org/very_good_analysis-2.1.2/lib/ -pasteboard:lib/ diff --git a/packages/pasteboard/example/lib/main.dart b/packages/pasteboard/example/lib/main.dart index f078b645..9d12c8ba 100644 --- a/packages/pasteboard/example/lib/main.dart +++ b/packages/pasteboard/example/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:pasteboard/pasteboard.dart'; @@ -16,6 +17,8 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { + String _console = ""; + Uint8List? bytes; String? fileUrl; @@ -43,26 +46,29 @@ class _MyAppState extends State { ), MaterialButton( onPressed: () async { - final url = await Pasteboard.absoluteUrlString; - if (url?.startsWith('file') ?? false) { - var tryParse = Uri.tryParse(url!); - return setState(() { - fileUrl = tryParse!.toFilePath(); - this.bytes = null; - }); - } - final bytes = await Pasteboard.image; setState(() { fileUrl = null; this.bytes = bytes; + _console = "bytes: ${bytes?.length}"; }); }, child: const Text('paste image'), ), - Text('bytes: $bytes', maxLines: 1), - Text('fileUrl: $fileUrl', maxLines: 1), + TextButton( + onPressed: () async { + final files = await Pasteboard.files(); + setState(() { + _console = 'files: \n ${files.isEmpty ? 'empty' : ''}'; + for (final file in files) { + _console += '$file ${File(file).existsSync()}\n'; + } + }); + }, + child: const Text("Get files"), + ), + Text(' $_console'), if (bytes != null) Image.memory(bytes!), if (fileUrl != null) Image.file(File(fileUrl!)) ], diff --git a/packages/pasteboard/example/pubspec.lock b/packages/pasteboard/example/pubspec.lock index be49b4f9..2b9dfc15 100644 --- a/packages/pasteboard/example/pubspec.lock +++ b/packages/pasteboard/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -21,7 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -80,14 +80,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.7.0" pasteboard: dependency: "direct main" description: @@ -148,7 +148,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" + version: "0.4.3" typed_data: dependency: transitive description: @@ -162,7 +162,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" flutter: ">=1.20.0" diff --git a/packages/pasteboard/example/windows/.gitignore b/packages/pasteboard/example/windows/.gitignore new file mode 100644 index 00000000..5d57396c --- /dev/null +++ b/packages/pasteboard/example/windows/.gitignore @@ -0,0 +1,19 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +cmake-build-* \ No newline at end of file diff --git a/packages/pasteboard/example/windows/CMakeLists.txt b/packages/pasteboard/example/windows/CMakeLists.txt new file mode 100644 index 00000000..4d4932fc --- /dev/null +++ b/packages/pasteboard/example/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.14) +project(pasteboard_example LANGUAGES CXX) + +set(BINARY_NAME "pasteboard_example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) +# target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/pasteboard/example/windows/flutter/CMakeLists.txt b/packages/pasteboard/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..b2e4bd8d --- /dev/null +++ b/packages/pasteboard/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.cc b/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..7c2f20e1 --- /dev/null +++ b/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + PasteboardPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PasteboardPlugin")); +} diff --git a/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.h b/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/packages/pasteboard/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/pasteboard/example/windows/flutter/generated_plugins.cmake b/packages/pasteboard/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b0870296 --- /dev/null +++ b/packages/pasteboard/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + pasteboard +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/pasteboard/example/windows/runner/CMakeLists.txt b/packages/pasteboard/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..de2d8916 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/pasteboard/example/windows/runner/Runner.rc b/packages/pasteboard/example/windows/runner/Runner.rc new file mode 100644 index 00000000..b284ecc8 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "Demonstrates how to use the pasteboard plugin." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "pasteboard_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "pasteboard_example.exe" "\0" + VALUE "ProductName", "pasteboard_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/pasteboard/example/windows/runner/flutter_window.cpp b/packages/pasteboard/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..b43b9095 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/pasteboard/example/windows/runner/flutter_window.h b/packages/pasteboard/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/packages/pasteboard/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/pasteboard/example/windows/runner/main.cpp b/packages/pasteboard/example/windows/runner/main.cpp new file mode 100644 index 00000000..26a18bbf --- /dev/null +++ b/packages/pasteboard/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"pasteboard_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/pasteboard/example/windows/runner/resource.h b/packages/pasteboard/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/packages/pasteboard/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/pasteboard/example/windows/runner/resources/app_icon.ico b/packages/pasteboard/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/packages/pasteboard/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/pasteboard/example/windows/runner/runner.exe.manifest b/packages/pasteboard/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..c977c4a4 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/pasteboard/example/windows/runner/utils.cpp b/packages/pasteboard/example/windows/runner/utils.cpp new file mode 100644 index 00000000..d19bdbbc --- /dev/null +++ b/packages/pasteboard/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/pasteboard/example/windows/runner/utils.h b/packages/pasteboard/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/pasteboard/example/windows/runner/win32_window.cpp b/packages/pasteboard/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..c10f08dc --- /dev/null +++ b/packages/pasteboard/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/pasteboard/example/windows/runner/win32_window.h b/packages/pasteboard/example/windows/runner/win32_window.h new file mode 100644 index 00000000..17ba4311 --- /dev/null +++ b/packages/pasteboard/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/pasteboard/lib/pasteboard.dart b/packages/pasteboard/lib/pasteboard.dart index 25c0e150..58b2bede 100644 --- a/packages/pasteboard/lib/pasteboard.dart +++ b/packages/pasteboard/lib/pasteboard.dart @@ -1,13 +1,29 @@ import 'dart:async'; +import 'dart:io'; import 'dart:typed_data'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; class Pasteboard { static const MethodChannel _channel = MethodChannel('pasteboard'); - static Future get image => - _channel.invokeMethod('image'); + static Future get image async { + final image = await _channel.invokeMethod('image'); + + if (image == null) { + return null; + } + if (Platform.isMacOS) { + return image as Uint8List; + } else if (Platform.isWindows) { + final file = File(image as String); + final bytes = await file.readAsBytes(); + await file.delete(); + return bytes; + } + return null; + } static Future get absoluteUrlString => _channel.invokeMethod('absoluteUrlString'); @@ -20,4 +36,9 @@ class Pasteboard { static Future writeUrl(String url) async => await _channel.invokeMethod('writeUrl', [url]); + + static Future> files() async { + final files = await _channel.invokeMethod('files'); + return files?.cast() ?? const []; + } } diff --git a/packages/pasteboard/pasteboard.iml b/packages/pasteboard/pasteboard.iml deleted file mode 100644 index 7ac84a92..00000000 --- a/packages/pasteboard/pasteboard.iml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/pasteboard/pubspec.lock b/packages/pasteboard/pubspec.lock deleted file mode 100644 index 7114b5ca..00000000 --- a/packages/pasteboard/pubspec.lock +++ /dev/null @@ -1,154 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.7.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - very_good_analysis: - dependency: "direct dev" - description: - name: very_good_analysis - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" -sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" diff --git a/packages/pasteboard/pubspec.yaml b/packages/pasteboard/pubspec.yaml index bf2f78e8..5b6f4621 100644 --- a/packages/pasteboard/pubspec.yaml +++ b/packages/pasteboard/pubspec.yaml @@ -29,6 +29,8 @@ flutter: platforms: macos: pluginClass: PasteboardPlugin + windows: + pluginClass: PasteboardPlugin # To add assets to your plugin package, add an assets section, like this: # assets: diff --git a/packages/pasteboard/test/pasteboard_test.dart b/packages/pasteboard/test/pasteboard_test.dart new file mode 100644 index 00000000..4f1a93f5 --- /dev/null +++ b/packages/pasteboard/test/pasteboard_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pasteboard/pasteboard.dart'; + +void main() { + const MethodChannel channel = MethodChannel('pasteboard'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await Pasteboard.platformVersion, '42'); + }); +} diff --git a/packages/pasteboard/windows/.gitignore b/packages/pasteboard/windows/.gitignore new file mode 100644 index 00000000..b3eb2be1 --- /dev/null +++ b/packages/pasteboard/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/pasteboard/windows/CMakeLists.txt b/packages/pasteboard/windows/CMakeLists.txt new file mode 100644 index 00000000..6829ed50 --- /dev/null +++ b/packages/pasteboard/windows/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.14) +set(PROJECT_NAME "pasteboard") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "pasteboard_plugin") + +add_library(${PLUGIN_NAME} SHARED + "pasteboard_plugin.cpp" + ) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + + + +# List of absolute paths to libraries that should be bundled with the plugin +set(pasteboard_bundled_libraries + "" + PARENT_SCOPE + ) diff --git a/packages/pasteboard/windows/include/pasteboard/pasteboard_plugin.h b/packages/pasteboard/windows/include/pasteboard/pasteboard_plugin.h new file mode 100644 index 00000000..67b75c02 --- /dev/null +++ b/packages/pasteboard/windows/include/pasteboard/pasteboard_plugin.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_ +#define FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void PasteboardPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_ diff --git a/packages/pasteboard/windows/pasteboard_plugin.cpp b/packages/pasteboard/windows/pasteboard_plugin.cpp new file mode 100644 index 00000000..626f7755 --- /dev/null +++ b/packages/pasteboard/windows/pasteboard_plugin.cpp @@ -0,0 +1,367 @@ +#include "include/pasteboard/pasteboard_plugin.h" + +// This must be included before many other Windows headers. +#include + +#include + +#include +#include +#include + +#include +#include +#include "strconv.h" + +namespace { + +PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp) { + BITMAP bmp; + PBITMAPINFO pbmi; + WORD cClrBits; + + // Retrieve the bitmap color format, width, and height. + assert(GetObject(hBmp, sizeof(BITMAP), (LPSTR) &bmp)); + + // Convert the color format to a count of bits. + cClrBits = (WORD) (bmp.bmPlanes * bmp.bmBitsPixel); + if (cClrBits == 1) + cClrBits = 1; + else if (cClrBits <= 4) + cClrBits = 4; + else if (cClrBits <= 8) + cClrBits = 8; + else if (cClrBits <= 16) + cClrBits = 16; + else if (cClrBits <= 24) + cClrBits = 24; + else cClrBits = 32; + + // Allocate memory for the BITMAPINFO structure. (This structure + // contains a BITMAPINFOHEADER structure and an array of RGBQUAD + // data structures.) + + if (cClrBits < 24) + pbmi = (PBITMAPINFO) LocalAlloc(LPTR, + sizeof(BITMAPINFOHEADER) + + sizeof(RGBQUAD) * int(1 << cClrBits)); + + // There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel + + else + pbmi = (PBITMAPINFO) LocalAlloc(LPTR, + sizeof(BITMAPINFOHEADER)); + + // Initialize the fields in the BITMAPINFO structure. + + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = bmp.bmWidth; + pbmi->bmiHeader.biHeight = bmp.bmHeight; + pbmi->bmiHeader.biPlanes = bmp.bmPlanes; + pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel; + if (cClrBits < 24) + pbmi->bmiHeader.biClrUsed = (1 << cClrBits); + + // If the bitmap is not compressed, set the BI_RGB flag. + pbmi->bmiHeader.biCompression = BI_RGB; + + // Compute the number of bytes in the array of color + // indices and store the result in biSizeImage. + // The width must be DWORD aligned unless the bitmap is RLE + // compressed. + pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8 + * pbmi->bmiHeader.biHeight; + // Set biClrImportant to 0, indicating that all of the + // device colors are important. + pbmi->bmiHeader.biClrImportant = 0; + return pbmi; +} + +void CreateBMPFile(LPCTSTR pszFile, HBITMAP hBMP) { + HANDLE hf; // file handle + BITMAPFILEHEADER hdr; // bitmap file-header + PBITMAPINFOHEADER pbih; // bitmap info-header + LPBYTE lpBits; // memory pointer + DWORD dwTotal; // total count of bytes + DWORD cb; // incremental count of bytes + BYTE *hp; // byte pointer + DWORD dwTmp; + PBITMAPINFO pbi; + HDC hDC; + + hDC = CreateCompatibleDC(GetWindowDC(GetDesktopWindow())); + SelectObject(hDC, hBMP); + + pbi = CreateBitmapInfoStruct(hBMP); + + pbih = (PBITMAPINFOHEADER) pbi; + lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage); + + assert(lpBits); + + // Retrieve the color table (RGBQUAD array) and the bits + // (array of palette indices) from the DIB. + assert(GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi, + DIB_RGB_COLORS)); + + // Create the .BMP file. + hf = CreateFile(pszFile, + GENERIC_READ | GENERIC_WRITE, + (DWORD) 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + (HANDLE) NULL); + assert(hf != INVALID_HANDLE_VALUE); + + hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" + // Compute the size of the entire file. + hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD) + pbih->biSizeImage); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + + // Compute the offset to the array of color indices. + hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD); + + // Copy the BITMAPFILEHEADER into the .BMP file. + assert(WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), + (LPDWORD) &dwTmp, NULL)); + + // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. + assert(WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + + pbih->biClrUsed * sizeof(RGBQUAD), + (LPDWORD) &dwTmp, (NULL))); + + // Copy the array of color indices into the .BMP file. + dwTotal = cb = pbih->biSizeImage; + hp = lpBits; + assert(WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL)); + + // Close the .BMP file. + assert(CloseHandle(hf)); + + // Free memory. + GlobalFree((HGLOBAL) lpBits); +} + +void CreateBitmapHeaderWithColorDepth(LONG width, + LONG height, + WORD color_depth, + BITMAPINFOHEADER *hdr) { + // These values are shared with gfx::PlatformDevice. + hdr->biSize = sizeof(BITMAPINFOHEADER); + hdr->biWidth = width; + hdr->biHeight = -height; // Minus means top-down bitmap. + hdr->biPlanes = 1; + hdr->biBitCount = color_depth; + hdr->biCompression = BI_RGB; // No compression. + hdr->biSizeImage = 0; + hdr->biXPelsPerMeter = 1; + hdr->biYPelsPerMeter = 1; + hdr->biClrUsed = 0; + hdr->biClrImportant = 0; +} + +HBITMAP CreateHBitmapXRGB8888(int width, int height, HANDLE shared_section, void **data) { + if (width == 0 || height == 0) { + width = 1; + height = 1; + } + BITMAPINFOHEADER hdr = {0}; + CreateBitmapHeaderWithColorDepth(width, height, 32, &hdr); + HBITMAP hbitmap = CreateDIBSection(nullptr, reinterpret_cast(&hdr), 0, data, shared_section, 0); + return hbitmap; +} + +// A scoper to manage acquiring and automatically releasing the clipboard. +class ScopedClipboard { + public: + ScopedClipboard() : opened_(false) {} + + ~ScopedClipboard() { + if (opened_) + Release(); + } + + bool Acquire(HWND owner) { + const int kMaxAttemptsToOpenClipboard = 5; + + if (opened_) { + return false; + } + + for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) { + if (::OpenClipboard(owner)) { + opened_ = true; + return true; + } + + // If we didn't manage to open the clipboard, sleep a bit and be hopeful. + ::Sleep(5); + } + + // We failed to acquire the clipboard. + return false; + } + + void Release() { + if (opened_) { + ::CloseClipboard(); + opened_ = false; + } + } + + private: + bool opened_; +}; + +class PasteboardPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); + + PasteboardPlugin(); + + virtual ~PasteboardPlugin(); + + private: + // Called when a method is called on this plugin's channel from Dart. + void HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result); +}; + +// static +void PasteboardPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows *registrar) { + auto channel = + std::make_unique>( + registrar->messenger(), "pasteboard", + &flutter::StandardMethodCodec::GetInstance()); + + auto plugin = std::make_unique(); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto &call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +PasteboardPlugin::PasteboardPlugin() {} + +PasteboardPlugin::~PasteboardPlugin() {} + +void PasteboardPlugin::HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result) { + if (method_call.method_name() == "image") { + if (!IsClipboardFormatAvailable(CF_DIB)) { + result->Success(); + return; + } + ScopedClipboard clipboard; + + if (!clipboard.Acquire(nullptr)) { + result->Success(); + return; + } + // We use a DIB rather than a DDB here since ::GetObject() with the + // HBITMAP returned from ::GetClipboardData(CF_BITMAP) always reports a color + // depth of 32bpp. + auto *bitmap = static_cast(::GetClipboardData(CF_DIB)); + if (!bitmap) { + result->Success(); + return; + } + + int color_table_length = 0; + + // For more information on BITMAPINFOHEADER and biBitCount definition, + // see https://docs.microsoft.com/en-us/windows/win32/wmdm/-bitmapinfoheader + switch (bitmap->bmiHeader.biBitCount) { + case 1: + case 4: + case 8: + color_table_length = bitmap->bmiHeader.biClrUsed + ? int(bitmap->bmiHeader.biClrUsed) + : int(1 << bitmap->bmiHeader.biBitCount); + break; + case 16: + case 32: + if (bitmap->bmiHeader.biCompression == BI_BITFIELDS) + color_table_length = 3; + break; + case 24:break; + default:result->Success(); + return; + } + + const void *bitmap_bits = reinterpret_cast(bitmap) + + bitmap->bmiHeader.biSize + + color_table_length * sizeof(RGBQUAD); + + void *dst_bits; + auto dst_hbitmap = CreateHBitmapXRGB8888(bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, + nullptr, &dst_bits); + + auto hdc = CreateCompatibleDC(nullptr); + auto old_hbitmap = static_cast(SelectObject(hdc, dst_hbitmap)); + ::SetDIBitsToDevice(hdc, 0, 0, bitmap->bmiHeader.biWidth, + bitmap->bmiHeader.biHeight, 0, 0, 0, + bitmap->bmiHeader.biHeight, bitmap_bits, bitmap, + DIB_RGB_COLORS); + SelectObject(hdc, old_hbitmap); + DeleteDC(hdc); + + TCHAR path[MAX_PATH]; + GetTempPath(MAX_PATH, path); + TCHAR name[MAX_PATH]; + GetTempFileName(path, L"pasteboard", false, name); + CreateBMPFile(name, dst_hbitmap); + + DeleteObject(dst_hbitmap); + + result->Success(flutter::EncodableValue(wide_to_utf8(name))); + } else if (method_call.method_name() == "files") { + if (!IsClipboardFormatAvailable(CF_HDROP)) { + result->Success(); + return; + } + if (!OpenClipboard(nullptr)) { + result->Error("0", "open clipboard failed"); + return; + } + auto handle = GetClipboardData(CF_HDROP); + flutter::EncodableList file_list; + if (handle) { + auto data = reinterpret_cast(GlobalLock(handle)); + if (data) { + auto files = DragQueryFile(data, 0xFFFFFFFF, nullptr, 0); + for (unsigned int i = 0; i < files; ++i) { + TCHAR filename[MAX_PATH]; + DragQueryFile(data, i, filename, sizeof(TCHAR) * MAX_PATH); + std::wstring wide_filename(filename); + file_list.emplace_back(wide_to_utf8(wide_filename)); + } + } + } + CloseClipboard(); + result->Success(flutter::EncodableValue(file_list)); + } else { + result->NotImplemented(); + } +} + +} // namespace + +void PasteboardPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + PasteboardPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/pasteboard/windows/strconv.h b/packages/pasteboard/windows/strconv.h new file mode 100644 index 00000000..eddd0121 --- /dev/null +++ b/packages/pasteboard/windows/strconv.h @@ -0,0 +1,565 @@ +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019-2021 JavaCommons +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +----------------------------------------------------------------------------- + */ +/* strconv.h v1.8.10 */ +/* Last Modified: 2021/08/30 21:53 */ +#ifndef STRCONV_H +#define STRCONV_H + +#include +#include +#include +#include +#include + +#if __cplusplus >= 201103L && !defined(STRCONV_CPP98) +static inline std::wstring cp_to_wide(const std::string &s, UINT codepage) +{ + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::wstring result(out_length, L'\0'); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0], out_length); + return result; +} +static inline std::string wide_to_cp(const std::wstring &s, UINT codepage) +{ + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::string result(out_length, '\0'); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0], out_length, 0, 0); + return result; +} +#else /* __cplusplus < 201103L */ +static inline std::wstring cp_to_wide(const std::string &s, UINT codepage) +{ + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::vector buffer(out_length); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &buffer[0], out_length); + std::wstring result(buffer.begin(), buffer.end()); + return result; +} +static inline std::string wide_to_cp(const std::wstring &s, UINT codepage) +{ + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::vector buffer(out_length); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &buffer[0], out_length, 0, 0); + std::string result(buffer.begin(), buffer.end()); + return result; +} +#endif + +static inline std::string cp_to_utf8(const std::string &s, UINT codepage) +{ + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, codepage); + return wide_to_cp(wide, CP_UTF8); +} +static inline std::string utf8_to_cp(const std::string &s, UINT codepage) +{ + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, CP_UTF8); + return wide_to_cp(wide, codepage); +} + +static inline std::wstring ansi_to_wide(const std::string &s) +{ + return cp_to_wide(s, CP_ACP); +} +static inline std::string wide_to_ansi(const std::wstring &s) +{ + return wide_to_cp(s, CP_ACP); +} + +static inline std::wstring sjis_to_wide(const std::string &s) +{ + return cp_to_wide(s, 932); +} +static inline std::string wide_to_sjis(const std::wstring &s) +{ + return wide_to_cp(s, 932); +} + +static inline std::wstring utf8_to_wide(const std::string &s) +{ + return cp_to_wide(s, CP_UTF8); +} +static inline std::string wide_to_utf8(const std::wstring &s) +{ + return wide_to_cp(s, CP_UTF8); +} + +static inline std::string ansi_to_utf8(const std::string &s) +{ + return cp_to_utf8(s, CP_ACP); +} +static inline std::string utf8_to_ansi(const std::string &s) +{ + return utf8_to_cp(s, CP_ACP); +} + +static inline std::string sjis_to_utf8(const std::string &s) +{ + return cp_to_utf8(s, 932); +} +static inline std::string utf8_to_sjis(const std::string &s) +{ + return utf8_to_cp(s, 932); +} + +#ifdef __cpp_char8_t +static inline std::u8string utf8_to_char8(const std::string &s) +{ + return std::u8string(s.begin(), s.end()); +} +static inline std::string char8_to_utf8(const std::u8string &s) +{ + return std::string(s.begin(), s.end()); +} + +static inline std::wstring char8_to_wide(const std::u8string &s) +{ + return cp_to_wide(char8_to_utf8(s), CP_UTF8); +} +static inline std::u8string wide_to_char8(const std::wstring &s) +{ + return utf8_to_char8(wide_to_cp(s, CP_UTF8)); +} + +static inline std::u8string cp_to_char8(const std::string &s, UINT codepage) +{ + return utf8_to_char8(cp_to_utf8(s, codepage)); +} +static inline std::string char8_to_cp(const std::u8string &s, UINT codepage) +{ + return utf8_to_cp(char8_to_utf8(s), codepage); +} + +static inline std::u8string ansi_to_char8(const std::string &s) +{ + return cp_to_char8(s, CP_ACP); +} +static inline std::string char8_to_ansi(const std::u8string &s) +{ + return char8_to_cp(s, CP_ACP); +} + +static inline std::u8string sjis_to_char8(const std::string &s) +{ + return cp_to_char8(s, 932); +} +static inline std::string char8_to_sjis(const std::u8string &s) +{ + return char8_to_cp(s, 932); +} +#endif + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + +static inline std::wstring vformat(const wchar_t *format, va_list args) +{ + int len = _vsnwprintf(0, 0, format, args); + if (len < 0) + return L""; + std::vector buffer(len + 1); + len = _vsnwprintf(&buffer[0], len, format, args); + if (len < 0) + return L""; + buffer[len] = L'\0'; + return &buffer[0]; +} +static inline std::string vformat(const char *format, va_list args) +{ + int len = _vsnprintf(0, 0, format, args); + if (len < 0) + return ""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, format, args); + if (len < 0) + return ""; + buffer[len] = '\0'; + return &buffer[0]; +} +#ifdef __cpp_char8_t +static inline std::u8string vformat(const char8_t *format, va_list args) +{ + int len = _vsnprintf(0, 0, (const char *)format, args); + if (len < 0) + return u8""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, (const char *)format, args); + if (len < 0) + return u8""; + buffer[len] = '\0'; + return (char8_t *)&buffer[0]; +} +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +static inline std::wstring format(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return s; +} +static inline std::string format(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return s; +} +#ifdef __cpp_char8_t +static inline std::u8string format(const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return s; +} +#endif + +static inline void format(std::ostream &ostrm, const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_utf8(s) << std::flush; +} +static inline void format(std::ostream &ostrm, const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << s << std::flush; +} +#ifdef __cpp_char8_t +static inline void format(std::ostream &ostrm, const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_utf8(s) << std::flush; +} +#endif + +static inline std::string formatA(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return wide_to_ansi(s); +} +static inline std::string formatA(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return utf8_to_ansi(s); +} +#ifdef __cpp_char8_t +static inline std::string formatA(const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return char8_to_ansi(s); +} +#endif + +static inline void formatA(std::ostream &ostrm, const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_ansi(s) << std::flush; +} +static inline void formatA(std::ostream &ostrm, const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << utf8_to_ansi(s) << std::flush; +} +#ifdef __cpp_char8_t +static inline void formatA(std::ostream &ostrm, const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_ansi(s) << std::flush; +} +#endif + +static inline void dbgmsg(const wchar_t *title, const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + MessageBoxW(0, s.c_str(), title, MB_OK); +} +static inline void dbgmsg(const char *title, const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + MessageBoxW(0, utf8_to_wide(s).c_str(), utf8_to_wide(title).c_str(), MB_OK); +} +#ifdef __cpp_char8_t +static inline void dbgmsg(const char8_t *title, const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + MessageBoxW(0, char8_to_wide(s).c_str(), char8_to_wide(title).c_str(), MB_OK); +} +#endif + +static inline HANDLE handle_for_ostream(std::ostream &ostrm) +{ + if (&ostrm == &std::cout) + { + return GetStdHandle(STD_OUTPUT_HANDLE); + } + else if (&ostrm == &std::cerr) + { + return GetStdHandle(STD_ERROR_HANDLE); + } + return INVALID_HANDLE_VALUE; +} +static inline void dbgout(std::ostream &ostrm, const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + std::wstring ws = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) + { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) + { + std::string s = wide_to_cp(ws, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else + { + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } +} +static inline void dbgout(std::ostream &ostrm, const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) + { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) + { + s = utf8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else + { + std::wstring ws = utf8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } +} +#ifdef __cpp_char8_t +static inline void dbgout(std::ostream &ostrm, const char8_t *format, ...) +{ + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) + { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) + { + std::string str = char8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, (const char *)str.c_str(), (DWORD)str.size(), &dwNumberOfCharsWrite, NULL); + } + else + { + std::wstring ws = char8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } +} +#endif + +class unicode_ostream +{ + private: + std::ostream *m_ostrm; + UINT m_target_cp; + bool is_ascii(const std::string &s) + { + for (std::size_t i = 0; i < s.size(); i++) + { + unsigned char c = (unsigned char)s[i]; + if (c > 0x7f) + return false; + } + return true; + } + + public: + unicode_ostream(std::ostream &ostrm, UINT target_cp = CP_ACP) : m_ostrm(&ostrm), m_target_cp(target_cp) {} + std::ostream &stream() { return *m_ostrm; } + void stream(std::ostream &ostrm) { m_ostrm = &ostrm; } + UINT target_cp() { return m_target_cp; } + void target_cp(UINT cp) { m_target_cp = cp; } + template + unicode_ostream &operator<<(const T &x) + { + std::ostringstream oss; + oss << x; + std::string output = oss.str(); + if (is_ascii(output)) + { + (*m_ostrm) << x; + } + else + { + (*m_ostrm) << utf8_to_cp(output, m_target_cp); + } + return *this; + } + unicode_ostream &operator<<(const std::wstring &x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream &operator<<(const wchar_t *x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream &operator<<(const std::string &x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream &operator<<(const char *x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } +#ifdef __cpp_char8_t + unicode_ostream &operator<<(const std::u8string &x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream &operator<<(const char8_t *x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } +#endif + unicode_ostream &operator<<(std::ostream &(*pf)(std::ostream &)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } + unicode_ostream &operator<<(std::basic_ios &(*pf)(std::basic_ios &)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } +}; + +#define U8(X) ((const char *)u8##X) +#define WIDE(X) (L##X) + +#endif /* STRCONV_H */ \ No newline at end of file