diff --git a/.github/workflows/check_and_lint.yml b/.github/workflows/check_and_lint.yml index fe11e4b..eee89ad 100644 --- a/.github/workflows/check_and_lint.yml +++ b/.github/workflows/check_and_lint.yml @@ -10,18 +10,21 @@ jobs: Flutter: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./core/dart steps: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - channel: 'master' - - run: flutter pub get - - run: flutter format --output=none --set-exit-if-changed . - - run: flutter analyze - - run: flutter test + channel: "master" + - name: Install Melos + run: flutter pub global activate melos + - name: Melos Boostrap + run: melos bootstrap + - name: Flutter Format + run: melos exec -c 1 flutter format . --output=none --set-exit-if-changed + - name: Flutter Analyze + run: melos exec -c 1 flutter analyze + - name: Futter Test + run: melos exec -c 1 flutter test Rustfmt: runs-on: ubuntu-latest @@ -51,6 +54,9 @@ jobs: - name: Install GTK if: (matrix.os == 'ubuntu-latest') run: sudo apt-get update && sudo apt-get install libgtk-3-dev + - name: Update rust + if: (matrix.os == 'ubuntu-latest') + run: rustup update - uses: actions/checkout@v2 - name: Install clippy run: rustup component add clippy diff --git a/.gitignore b/.gitignore index 816ffe8..beeee5a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ Cargo.lock pubspec.lock .flutter-plugins .flutter-plugins-dependencies + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..224af64 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f6ae9d..ba785bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,17 @@ { + // "rust-analyzer.cargo.target": "aarch64-apple-ios", + "rust-analyzer.cargo.target": "aarch64-linux-android", "rust-analyzer.runnableEnv": { "RUST_TEST_THREADS": 1, }, - "rust-analyzer.cargo.allFeatures": false, + // "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.checkOnSave.allTargets": false, + // "rust-analyzer.cargo.unsetTest": [ + // "nativeshell_core", + // ], "rust-analyzer.cargo.features": [ - "mock" - ] -} + // "mock" + ], + "dart.runPubGetOnPubspecChanges": "never" +} \ No newline at end of file diff --git a/0001-Fix-panic-in-finalizer.patch b/0001-Fix-panic-in-finalizer.patch new file mode 100644 index 0000000..810552b --- /dev/null +++ b/0001-Fix-panic-in-finalizer.patch @@ -0,0 +1,35 @@ +From 1505b454ce65d94b5fff83d6be437fd446a1bbfe Mon Sep 17 00:00:00 2001 +From: Matej Knopp +Date: Sun, 17 Jul 2022 23:01:00 +0100 +Subject: [PATCH 1/2] Fix panic in finalizer + +--- + core/rust/src/finalizable_handle.rs | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/core/rust/src/finalizable_handle.rs b/core/rust/src/finalizable_handle.rs +index 3e796c8..007b471 100644 +--- a/core/rust/src/finalizable_handle.rs ++++ b/core/rust/src/finalizable_handle.rs +@@ -244,12 +244,12 @@ pub(crate) mod finalizable_handle_native { + state.objects.remove(&handle) + }; + if let Some(mut object_state) = object_state { +- let mut finalizer = object_state +- .finalizer +- .take() +- .expect("Finalizer executed more than once"); +- let finalizer = finalizer.take().unwrap(); +- finalizer(); ++ let finalizer = object_state.finalizer.take(); ++ // Finalizer may have been removed in FinalizableHandle::drop ++ if let Some(mut finalizer) = finalizer { ++ let finalizer = finalizer.take().unwrap(); ++ finalizer(); ++ } + } + } + +-- +2.32.1 (Apple Git-133) + diff --git a/0002-Make-clippy-happy.patch b/0002-Make-clippy-happy.patch new file mode 100644 index 0000000..7346084 --- /dev/null +++ b/0002-Make-clippy-happy.patch @@ -0,0 +1,25 @@ +From 1ee31acf95ae38e4b1db99a819ee991970b24d77 Mon Sep 17 00:00:00 2001 +From: Matej Knopp +Date: Sun, 17 Jul 2022 23:05:34 +0100 +Subject: [PATCH 2/2] Make clippy happy + +--- + core/rust/src/platform/darwin/run_loop.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/core/rust/src/platform/darwin/run_loop.rs b/core/rust/src/platform/darwin/run_loop.rs +index 93f9c51..a9dcd1d 100644 +--- a/core/rust/src/platform/darwin/run_loop.rs ++++ b/core/rust/src/platform/darwin/run_loop.rs +@@ -330,7 +330,7 @@ impl PlatformRunLoop { + ]; + + // To stop event loop immediately, we need to post event. +- let () = msg_send![app, postEvent: dummy_event atStart: YES]; ++ let _: () = msg_send![app, postEvent: dummy_event atStart: YES]; + } + } + +-- +2.32.1 (Apple Git-133) + diff --git a/Cargo.toml b/Cargo.toml index 99d2135..581f8cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [workspace] members = [ - "core/rust", "core/rust_derive", + "core/rust", + "engine_context/rust", + "jni_context", ] + +[patch.crates-io] +nativeshell_jni_context = { path = "jni_context" } diff --git a/core/rust/src/message_channel/codec.rs b/core/rust/src/message_channel/codec.rs index a8eb77f..e8d045a 100644 --- a/core/rust/src/message_channel/codec.rs +++ b/core/rust/src/message_channel/codec.rs @@ -154,7 +154,7 @@ impl<'a> Reader<'a> { } else { let v = &self.buf[self.pos..self.pos + len]; self.pos += len; - String::from_utf8_lossy(v).to_owned().to_string() + String::from_utf8_lossy(v).into() } } fn align_to(&mut self, align: usize) { diff --git a/engine_context/dart/CHANGELOG.md b/engine_context/dart/CHANGELOG.md new file mode 100644 index 0000000..6073234 --- /dev/null +++ b/engine_context/dart/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial release. diff --git a/engine_context/dart/LICENSE b/engine_context/dart/LICENSE new file mode 100644 index 0000000..53a2746 --- /dev/null +++ b/engine_context/dart/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2022 Matej Knopp and the contributors + +MIT LICENSE + +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. diff --git a/engine_context/dart/README.md b/engine_context/dart/README.md new file mode 100644 index 0000000..5082940 --- /dev/null +++ b/engine_context/dart/README.md @@ -0,0 +1,28 @@ +# flutter_engine_context + +Flutter plugin that provides access to Flutter engine components (like view or texture registrar) from native code. + +## Example + +Dart code: +```dart + final handle = await FlutterEngineContext.instance.getEngineHandle(); + // pass the handle native code (i.e. through FFI). + nativeMethod(handle); +``` + +Rust code: +```rust + let context = FlutterEngineContext::new(); + let flutter_view = context.get_flutter_view(handle); + let texture_registry = contet.get_texture_registry(handle); +``` + +Rust code for Android: +```rust + let context = FlutterEngineContext::new(&jni_env, class_loader); + let flutter_view = context.get_flutter_view(handle); + let texture_registry = contet.get_texture_registry(handle); +``` + +On Android the `FlutterEngineContext` needs to be initialized with JNI environment and class loader used to load Flutter plugin (or application code). diff --git a/engine_context/dart/analysis_options.yaml b/engine_context/dart/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/engine_context/dart/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/engine_context/dart/android/.gitignore b/engine_context/dart/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/engine_context/dart/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/engine_context/dart/android/build.gradle b/engine_context/dart/android/build.gradle new file mode 100644 index 0000000..105e986 --- /dev/null +++ b/engine_context/dart/android/build.gradle @@ -0,0 +1,35 @@ +group 'dev.nativeshell.flutter_engine_context' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + } +} diff --git a/engine_context/dart/android/settings.gradle b/engine_context/dart/android/settings.gradle new file mode 100644 index 0000000..aa38a26 --- /dev/null +++ b/engine_context/dart/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_engine_context' diff --git a/engine_context/dart/android/src/main/AndroidManifest.xml b/engine_context/dart/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3c2e7bc --- /dev/null +++ b/engine_context/dart/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/engine_context/dart/android/src/main/java/dev/nativeshell/flutter_engine_context/FlutterEngineContextPlugin.java b/engine_context/dart/android/src/main/java/dev/nativeshell/flutter_engine_context/FlutterEngineContextPlugin.java new file mode 100644 index 0000000..a5f0e07 --- /dev/null +++ b/engine_context/dart/android/src/main/java/dev/nativeshell/flutter_engine_context/FlutterEngineContextPlugin.java @@ -0,0 +1,132 @@ +package dev.nativeshell.flutter_engine_context; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.android.FlutterView; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.view.TextureRegistry; + +/** FlutterEngineContextPlugin */ +// used from JNI +@SuppressWarnings("UnusedDeclaration") +public class FlutterEngineContextPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; + private int handle; + FlutterPluginBinding flutterPluginBinding; + ActivityPluginBinding activityPluginBinding; + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + handle = registry.registerPlugin(this); + this.flutterPluginBinding = flutterPluginBinding; + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "dev.nativeshell.flutter_engine_context"); + channel.setMethodCallHandler(this); + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + activityPluginBinding = binding; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + if (call.method.equals("getEngineHandle")) { + result.success(handle); + } else { + result.notImplemented(); + } + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + registry.unregisterPlugin(handle); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + } + + @Override + public void onDetachedFromActivity() { + } + + static public Activity getActivity(int handle) { + final FlutterEngineContextPlugin plugin = registry.getPlugin(handle); + if (plugin != null && plugin.activityPluginBinding != null) { + return plugin.activityPluginBinding.getActivity(); + } else { + return null; + } + } + + static public FlutterView getFlutterView(int handle) { + final Activity activity = getActivity(handle); + if (activity != null) { + return activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID); + } else { + return null; + } + } + + static public BinaryMessenger getBinaryMessenger(int handle) { + final FlutterEngineContextPlugin plugin = registry.getPlugin(handle); + if (plugin != null && plugin.flutterPluginBinding != null) { + return plugin.flutterPluginBinding.getBinaryMessenger(); + } else { + return null; + } + } + + static public TextureRegistry getTextureRegistry(int handle) { + final FlutterEngineContextPlugin plugin = registry.getPlugin(handle); + if (plugin != null && plugin.flutterPluginBinding != null) { + return plugin.flutterPluginBinding.getTextureRegistry(); + } else { + return null; + } + } + + static class Registry { + int registerPlugin(FlutterEngineContextPlugin plugin) { + final int res = nextHandle; + ++nextHandle; + plugins.put(res, plugin); + return res; + } + + FlutterEngineContextPlugin getPlugin(int handle) { + return plugins.get(handle); + } + + void unregisterPlugin(int handle) { + plugins.remove(handle); + } + + private final Map plugins = new HashMap<>(); + private int nextHandle = 1; + } + + private static final Registry registry = new Registry(); +} diff --git a/engine_context/dart/ios/.gitignore b/engine_context/dart/ios/.gitignore new file mode 100644 index 0000000..0c88507 --- /dev/null +++ b/engine_context/dart/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/engine_context/dart/ios/Assets/.gitkeep b/engine_context/dart/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/engine_context/dart/ios/Classes/EngineContextPlugin.h b/engine_context/dart/ios/Classes/EngineContextPlugin.h new file mode 100644 index 0000000..0b1bd06 --- /dev/null +++ b/engine_context/dart/ios/Classes/EngineContextPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterEngineContextPlugin : NSObject +@end diff --git a/engine_context/dart/ios/Classes/EngineContextPlugin.m b/engine_context/dart/ios/Classes/EngineContextPlugin.m new file mode 100644 index 0000000..6a3c683 --- /dev/null +++ b/engine_context/dart/ios/Classes/EngineContextPlugin.m @@ -0,0 +1,78 @@ +#import "EngineContextPlugin.h" + +// Flutter API doesn't provide an official way to get a view from registrar. +// This will likely break in future when multiple views per engine are +// supported. But that will be a major breaking change anyway. +@interface _FlutterPluginRegistrar : NSObject +@property(readwrite, nonatomic) FlutterEngine *flutterEngine; +@end + +@interface _FlutterEngineContext : NSObject { +@public + __weak FlutterEngine *engine; +} + +@end + +@implementation _FlutterEngineContext + +@end + +@interface FlutterEngineContextPlugin () { + int64_t engineHandle; +} +@end + +@implementation FlutterEngineContextPlugin + +static NSMutableDictionary *registry; +static int64_t nextHandle = 1; + ++ (void)initialize { + registry = [NSMutableDictionary new]; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterEngineContextPlugin *instance = + [[FlutterEngineContextPlugin alloc] init]; + instance->engineHandle = nextHandle; + ++nextHandle; + + _FlutterEngineContext *context = [_FlutterEngineContext new]; + context->engine = ((_FlutterPluginRegistrar *)registrar).flutterEngine; + // There is no unregister callback on macOS, which means we'll leak + // an _FlutterEngineContext instance for every engine. Fortunately the + // instance is tiny and only uses weak pointers to reference engine artifacts. + [registry setObject:context forKey:@(instance->engineHandle)]; + + FlutterMethodChannel *channel = [FlutterMethodChannel + methodChannelWithName:@"dev.nativeshell.flutter_engine_context" + binaryMessenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + if ([@"getEngineHandle" isEqualToString:call.method]) { + result(@(engineHandle)); + } else { + result(FlutterMethodNotImplemented); + } +} + ++ (UIView *)getFlutterView:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->engine.viewController.view; +} + ++ (id)getTextureRegistry:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->engine; +} + ++ (id)getBinaryMessenger:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->engine.binaryMessenger; +} + +@end diff --git a/engine_context/dart/ios/flutter_engine_context.podspec b/engine_context/dart/ios/flutter_engine_context.podspec new file mode 100644 index 0000000..f5e1451 --- /dev/null +++ b/engine_context/dart/ios/flutter_engine_context.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint engine_context.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_engine_context' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } +end diff --git a/engine_context/dart/lib/flutter_engine_context.dart b/engine_context/dart/lib/flutter_engine_context.dart new file mode 100644 index 0000000..aa1dbe2 --- /dev/null +++ b/engine_context/dart/lib/flutter_engine_context.dart @@ -0,0 +1,31 @@ +import 'package:flutter/services.dart'; + +class FlutterEngineContext { + /// Shared instance for [FlutterEngineContext]. + static final instance = FlutterEngineContext(); + + final _methodChannel = + const MethodChannel('dev.nativeshell.flutter_engine_context'); + + int? _engineHandle; + + /// Returns handle for current engine. This handle can be then passed to + /// FFI to obtain engine components (i.e. FlutterView or TextureRegistry). + /// + /// Dart: + /// ```dart + /// final handle = await FlutterEngineContext.instance.getEngineHandle(); + /// // pass the handle native code (i.e. through FFI). + /// ``` + /// + /// Native code: + /// ```rust + /// let context = FlutterEngineContext::new(); + /// let flutter_view = context.get_flutter_view(handle); + /// let texture_registry = contet.get_texture_registry(handle); + /// ``` + Future getEngineHandle() async { + _engineHandle ??= await _methodChannel.invokeMethod('getEngineHandle'); + return _engineHandle!; + } +} diff --git a/engine_context/dart/linux/CMakeLists.txt b/engine_context/dart/linux/CMakeLists.txt new file mode 100644 index 0000000..dcc70fb --- /dev/null +++ b/engine_context/dart/linux/CMakeLists.txt @@ -0,0 +1,47 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "flutter_engine_context") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "flutter_engine_context_plugin") + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +# +# Any new source files that you add to the plugin should be added here. +add_library(${PLUGIN_NAME} SHARED + "flutter_engine_context_plugin.cc" +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flutter_engine_context_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/engine_context/dart/linux/flutter_engine_context_plugin.cc b/engine_context/dart/linux/flutter_engine_context_plugin.cc new file mode 100644 index 0000000..de52326 --- /dev/null +++ b/engine_context/dart/linux/flutter_engine_context_plugin.cc @@ -0,0 +1,123 @@ +#include "include/flutter_engine_context/flutter_engine_context_plugin.h" + +#include +#include +#include + +#include +#include + +#define FLUTTER_ENGINE_CONTEXT_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_engine_context_plugin_get_type(), \ + FlutterEngineContextPlugin)) + +namespace { +struct EngineContext { + FlView *view; + FlBinaryMessenger *binary_messenger; + FlTextureRegistrar *texture_registrar; +}; +std::map contexts; +int64_t next_handle = 1; +} // namespace + +extern "C" { +FlView *FlutterEngineContextGetFlutterView(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return (context->second.view); + } else { + return 0; + } +} + +FlBinaryMessenger * +FlutterEngineContextGetBinaryMessenger(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return (context->second.binary_messenger); + } else { + return 0; + } +} + +FlTextureRegistrar * +FlutterEngineContextGetTextureRegistrar(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return (context->second.texture_registrar); + } else { + return 0; + } +} +} + +struct _FlutterEngineContextPlugin { + GObject parent_instance; + int64_t handle; +}; + +G_DEFINE_TYPE(FlutterEngineContextPlugin, flutter_engine_context_plugin, + g_object_get_type()) + +// Called when a method call is received from Flutter. +static void flutter_engine_context_plugin_handle_method_call( + FlutterEngineContextPlugin *self, FlMethodCall *method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar *method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getEngineHandle") == 0) { + g_autoptr(FlValue) result = fl_value_new_int(self->handle); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +static void flutter_engine_context_plugin_dispose(GObject *object) { + FlutterEngineContextPlugin *plugin = FLUTTER_ENGINE_CONTEXT_PLUGIN(object); + contexts.erase(plugin->handle); + G_OBJECT_CLASS(flutter_engine_context_plugin_parent_class)->dispose(object); +} + +static void flutter_engine_context_plugin_class_init( + FlutterEngineContextPluginClass *klass) { + G_OBJECT_CLASS(klass)->dispose = flutter_engine_context_plugin_dispose; +} + +static void +flutter_engine_context_plugin_init(FlutterEngineContextPlugin *self) {} + +static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call, + gpointer user_data) { + FlutterEngineContextPlugin *plugin = FLUTTER_ENGINE_CONTEXT_PLUGIN(user_data); + flutter_engine_context_plugin_handle_method_call(plugin, method_call); +} + +void flutter_engine_context_plugin_register_with_registrar( + FlPluginRegistrar *registrar) { + FlutterEngineContextPlugin *plugin = FLUTTER_ENGINE_CONTEXT_PLUGIN( + g_object_new(flutter_engine_context_plugin_get_type(), nullptr)); + + plugin->handle = next_handle; + ++next_handle; + + EngineContext context; + context.view = fl_plugin_registrar_get_view(registrar); + context.binary_messenger = fl_plugin_registrar_get_messenger(registrar); + context.texture_registrar = + fl_plugin_registrar_get_texture_registrar(registrar); + contexts[plugin->handle] = context; + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + fl_plugin_registrar_get_messenger(registrar), + "dev.nativeshell.flutter_engine_context", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + channel, method_call_cb, g_object_ref(plugin), g_object_unref); + + g_object_unref(plugin); +} diff --git a/engine_context/dart/linux/include/flutter_engine_context/flutter_engine_context_plugin.h b/engine_context/dart/linux/include/flutter_engine_context/flutter_engine_context_plugin.h new file mode 100644 index 0000000..7050e2c --- /dev/null +++ b/engine_context/dart/linux/include/flutter_engine_context/flutter_engine_context_plugin.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ +#define FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _FlutterEngineContextPlugin FlutterEngineContextPlugin; +typedef struct { + GObjectClass parent_class; +} FlutterEngineContextPluginClass; + +FLUTTER_PLUGIN_EXPORT GType flutter_engine_context_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT FlView * +FlutterEngineContextGetFlutterView(int64_t engine_handle); + +FLUTTER_PLUGIN_EXPORT FlBinaryMessenger * +FlutterEngineContextGetBinaryMessenger(int64_t engine_handle); + +FLUTTER_PLUGIN_EXPORT FlTextureRegistrar * +FlutterEngineContextGetTextureRegistrar(int64_t engine_handle); + +FLUTTER_PLUGIN_EXPORT void +flutter_engine_context_plugin_register_with_registrar( + FlPluginRegistrar *registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ diff --git a/engine_context/dart/macos/Classes/EngineContextPlugin.h b/engine_context/dart/macos/Classes/EngineContextPlugin.h new file mode 100644 index 0000000..67fbd6a --- /dev/null +++ b/engine_context/dart/macos/Classes/EngineContextPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterEngineContextPlugin : NSObject +@end diff --git a/engine_context/dart/macos/Classes/EngineContextPlugin.m b/engine_context/dart/macos/Classes/EngineContextPlugin.m new file mode 100644 index 0000000..264d85b --- /dev/null +++ b/engine_context/dart/macos/Classes/EngineContextPlugin.m @@ -0,0 +1,83 @@ +#import "EngineContextPlugin.h" + +@interface _FlutterEngineContext : NSObject { +@public + __weak NSView *flutterView; +@public + __weak id binaryMessenger; +@public + __weak id textureRegistry; +} +@end + +@implementation _FlutterEngineContext + +@end + +@interface FlutterEngineContextPlugin () { + int64_t engineHandle; +} +@end + +@implementation FlutterEngineContextPlugin + +static NSMutableDictionary *registry; +static int64_t nextHandle = 1; + ++ (void)initialize { + registry = [NSMutableDictionary new]; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterEngineContextPlugin *instance = + [[FlutterEngineContextPlugin alloc] init]; + instance->engineHandle = nextHandle; + ++nextHandle; + + // View is available only after registerWithRegistrar: completes. And we don't + // want to keep strong reference to the registrar in instance because it + // references engine and unfortunately instance itself will leak given current + // Flutter plugin architecture on macOS; + dispatch_async(dispatch_get_main_queue(), ^{ + _FlutterEngineContext *context = [_FlutterEngineContext new]; + context->flutterView = registrar.view; + context->binaryMessenger = registrar.messenger; + context->textureRegistry = registrar.textures; + // There is no unregister callback on macOS, which means we'll leak + // an _FlutterEngineContext instance for every engine. Fortunately the + // instance is tiny and only uses weak pointers to reference engine + // artifacts. + [registry setObject:context forKey:@(instance->engineHandle)]; + }); + + FlutterMethodChannel *channel = [FlutterMethodChannel + methodChannelWithName:@"dev.nativeshell.flutter_engine_context" + binaryMessenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + if ([@"getEngineHandle" isEqualToString:call.method]) { + result(@(engineHandle)); + } else { + result(FlutterMethodNotImplemented); + } +} + ++ (NSView *)getFlutterView:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->flutterView; +} + ++ (id)getTextureRegistry:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->textureRegistry; +} + ++ (id)getBinaryMessenger:(int64_t)engineHandle { + _FlutterEngineContext *context = [registry objectForKey:@(engineHandle)]; + return context->binaryMessenger; +} + +@end diff --git a/engine_context/dart/macos/flutter_engine_context.podspec b/engine_context/dart/macos/flutter_engine_context.podspec new file mode 100644 index 0000000..44a9a61 --- /dev/null +++ b/engine_context/dart/macos/flutter_engine_context.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint engine_context.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_engine_context' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } +end diff --git a/engine_context/dart/pubspec.yaml b/engine_context/dart/pubspec.yaml new file mode 100644 index 0000000..e90a4ec --- /dev/null +++ b/engine_context/dart/pubspec.yaml @@ -0,0 +1,78 @@ +name: flutter_engine_context +description: Easy access to FlutterView, FlutterBinaryMessenger and FlutterTextureRegistry for FFI. +version: 0.1.0 +homepage: https://github.com/nativeshell/nativeshell_ng + +environment: + sdk: ">=2.17.0 <3.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: dev.nativeshell.flutter_engine_context + pluginClass: FlutterEngineContextPlugin + ios: + pluginClass: FlutterEngineContextPlugin + linux: + pluginClass: FlutterEngineContextPlugin + macos: + pluginClass: FlutterEngineContextPlugin + windows: + pluginClass: FlutterEngineContextPluginCApi + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/engine_context/dart/test/flutter_engine_context_test.dart b/engine_context/dart/test/flutter_engine_context_test.dart new file mode 100644 index 0000000..2b6ec16 --- /dev/null +++ b/engine_context/dart/test/flutter_engine_context_test.dart @@ -0,0 +1,5 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('empty test', () {}); +} diff --git a/engine_context/dart/windows/.gitignore b/engine_context/dart/windows/.gitignore new file mode 100644 index 0000000..b3eb2be --- /dev/null +++ b/engine_context/dart/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/engine_context/dart/windows/CMakeLists.txt b/engine_context/dart/windows/CMakeLists.txt new file mode 100644 index 0000000..cc077cf --- /dev/null +++ b/engine_context/dart/windows/CMakeLists.txt @@ -0,0 +1,53 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "flutter_engine_context") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "flutter_engine_context_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "flutter_engine_context_plugin.cpp" + "flutter_engine_context_plugin.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/flutter_engine_context/flutter_engine_context_plugin_c_api.h" + "flutter_engine_context_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +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. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flutter_engine_context_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/engine_context/dart/windows/flutter_engine_context_plugin.cpp b/engine_context/dart/windows/flutter_engine_context_plugin.cpp new file mode 100644 index 0000000..78b30d7 --- /dev/null +++ b/engine_context/dart/windows/flutter_engine_context_plugin.cpp @@ -0,0 +1,99 @@ +#include "flutter_engine_context_plugin.h" + +// This must be included before many other Windows headers. +#include + +#include +#include +#include + +#include + +namespace { +struct EngineContext { + HWND hwnd; + FlutterDesktopTextureRegistrarRef texture_registrar; + FlutterDesktopMessengerRef binary_messenger; +}; +std::map contexts; +int64_t next_handle = 1; +} // namespace + +namespace engine_context { + +size_t GetFlutterView(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return reinterpret_cast(context->second.hwnd); + } else { + return 0; + } +} + +FlutterDesktopTextureRegistrarRef GetTextureRegistrar(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return context->second.texture_registrar; + } else { + return nullptr; + } +} + +FlutterDesktopMessengerRef GetBinaryMessenger(int64_t engine_handle) { + auto context = contexts.find(engine_handle); + if (context != contexts.end()) { + return context->second.binary_messenger; + } else { + return nullptr; + } +} + +// static +void FlutterEngineContextPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows *registrar, + FlutterDesktopPluginRegistrarRef raw_registrar) { + + int64_t handle = next_handle; + ++next_handle; + + EngineContext context; + context.hwnd = registrar->GetView()->GetNativeWindow(); + context.texture_registrar = + FlutterDesktopRegistrarGetTextureRegistrar(raw_registrar); + context.binary_messenger = + FlutterDesktopPluginRegistrarGetMessenger(raw_registrar); + contexts[handle] = context; + + auto channel = + std::make_unique>( + registrar->messenger(), "dev.nativeshell.flutter_engine_context", + &flutter::StandardMethodCodec::GetInstance()); + + auto plugin = std::make_unique(handle); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto &call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +FlutterEngineContextPlugin::FlutterEngineContextPlugin(int64_t engine_handle) + : engine_handle_(engine_handle) {} + +FlutterEngineContextPlugin::~FlutterEngineContextPlugin() { + contexts.erase(engine_handle_); +} + +void FlutterEngineContextPlugin::HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result) { + if (method_call.method_name().compare("getEngineHandle") == 0) { + result->Success(flutter::EncodableValue(engine_handle_)); + } else { + result->NotImplemented(); + } +} + +} // namespace engine_context diff --git a/engine_context/dart/windows/flutter_engine_context_plugin.h b/engine_context/dart/windows/flutter_engine_context_plugin.h new file mode 100644 index 0000000..52a201b --- /dev/null +++ b/engine_context/dart/windows/flutter_engine_context_plugin.h @@ -0,0 +1,41 @@ +#ifndef FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ +#define FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ + +#include +#include + +#include + +namespace engine_context { + +size_t GetFlutterView(int64_t engine_handle); +FlutterDesktopTextureRegistrarRef GetTextureRegistrar(int64_t engine_handle); +FlutterDesktopMessengerRef GetBinaryMessenger(int64_t engine_handle); + +class FlutterEngineContextPlugin : public flutter::Plugin { +public: + static void + RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar, + FlutterDesktopPluginRegistrarRef raw_registrar); + + FlutterEngineContextPlugin(int64_t engine_handle); + + virtual ~FlutterEngineContextPlugin(); + + // Disallow copy and assign. + FlutterEngineContextPlugin(const FlutterEngineContextPlugin &) = delete; + FlutterEngineContextPlugin & + operator=(const FlutterEngineContextPlugin &) = delete; + +private: + int64_t engine_handle_; + + // Called when a method is called on this plugin's channel from Dart. + void HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result); +}; + +} // namespace engine_context + +#endif // FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_H_ diff --git a/engine_context/dart/windows/flutter_engine_context_plugin_c_api.cpp b/engine_context/dart/windows/flutter_engine_context_plugin_c_api.cpp new file mode 100644 index 0000000..c47504e --- /dev/null +++ b/engine_context/dart/windows/flutter_engine_context_plugin_c_api.cpp @@ -0,0 +1,27 @@ +#include "include/flutter_engine_context/flutter_engine_context_plugin_c_api.h" + +#include + +#include "flutter_engine_context_plugin.h" + +void FlutterEngineContextPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + engine_context::FlutterEngineContextPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar), + registrar); +} + +size_t FlutterEngineContextGetFlutterView(int64_t engine_handle) { + return engine_context::GetFlutterView(engine_handle); +} + +FlutterDesktopTextureRegistrarRef +FlutterEngineContextGetTextureRegistrar(int64_t engine_handle) { + return engine_context::GetTextureRegistrar(engine_handle); +} + +FlutterDesktopMessengerRef +FlutterEngineContextGetBinaryMessenger(int64_t engine_handle) { + return engine_context::GetBinaryMessenger(engine_handle); +} \ No newline at end of file diff --git a/engine_context/dart/windows/include/flutter_engine_context/flutter_engine_context_plugin_c_api.h b/engine_context/dart/windows/include/flutter_engine_context/flutter_engine_context_plugin_c_api.h new file mode 100644 index 0000000..da0b661 --- /dev/null +++ b/engine_context/dart/windows/include/flutter_engine_context/flutter_engine_context_plugin_c_api.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_C_API_H_ + +#include +#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 FlutterEngineContextPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +FLUTTER_PLUGIN_EXPORT size_t +FlutterEngineContextGetFlutterView(int64_t engine_handle); + +FLUTTER_PLUGIN_EXPORT FlutterDesktopTextureRegistrarRef +FlutterEngineContextGetTextureRegistrar(int64_t engine_handle); + +FLUTTER_PLUGIN_EXPORT FlutterDesktopMessengerRef +FlutterEngineContextGetBinaryMessenger(int64_t engine_handle); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_ENGINE_CONTEXT_PLUGIN_C_API_H_ diff --git a/engine_context/rust/Cargo.toml b/engine_context/rust/Cargo.toml new file mode 100644 index 0000000..3c7ddfc --- /dev/null +++ b/engine_context/rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "flutter_engine_context" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Easy access to FlutterView, FlutterBinaryMessenger and FlutterTextureRegistry for FFI." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.19" +android_logger = "0.11" +log = "0.4" +nativeshell_jni_context = "0.1.0" + +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +objc = "0.2.7" +cocoa = "0.24" diff --git a/engine_context/rust/src/android.rs b/engine_context/rust/src/android.rs new file mode 100644 index 0000000..8f03906 --- /dev/null +++ b/engine_context/rust/src/android.rs @@ -0,0 +1,164 @@ +use std::fmt::Display; + +use jni::{objects::JObject, sys::jint}; +use nativeshell_jni_context::AndroidJniContext; + +use crate::FlutterEngineContextResult; + +pub(crate) struct PlatformContext { + java_vm: &'static jni::JavaVM, + class_loader: jni::objects::GlobalRef, +} + +#[derive(Debug)] +pub enum Error { + InvalidHandle, + MissingClassLoader, + JNIError(jni::errors::Error), + AndroidJniContextError(nativeshell_jni_context::Error), +} + +pub(crate) type FlutterView = jni::objects::GlobalRef; +pub(crate) type FlutterTextureRegistry = jni::objects::GlobalRef; +pub(crate) type FlutterBinaryMessenger = jni::objects::GlobalRef; +pub(crate) type Activity = jni::objects::GlobalRef; + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::JNIError(e) => e.fmt(f), + Error::MissingClassLoader => write!(f, "missing class loader"), + Error::InvalidHandle => write!(f, "invalid engine handle"), + Error::AndroidJniContextError(e) => e.fmt(f), + } + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(err: jni::errors::Error) -> Self { + Error::JNIError(err) + } +} + +impl From for Error { + fn from(err: nativeshell_jni_context::Error) -> Self { + Error::AndroidJniContextError(err) + } +} + +impl PlatformContext { + pub fn new() -> FlutterEngineContextResult { + let context = AndroidJniContext::get()?; + let class_loader = context + .class_loader() + .cloned() + .ok_or(Error::MissingClassLoader)?; + Ok(Self { + java_vm: context.vm(), + class_loader, + }) + } + + fn get_plugin_class<'a>( + &'a self, + env: &jni::JNIEnv<'a>, + ) -> FlutterEngineContextResult> { + let plugin_class = env + .call_method( + self.class_loader.as_obj(), + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + &[env + .new_string( + "dev/nativeshell/flutter_engine_context/FlutterEngineContextPlugin", + )? + .into()], + )? + .l()?; + Ok(plugin_class.into()) + } + + pub fn get_activity(&self, handle: i64) -> FlutterEngineContextResult { + let id: jint = handle.try_into().map_err(|_| Error::InvalidHandle)?; + let env = self.java_vm.get_env()?; + let class = self.get_plugin_class(&env)?; + let activity = env + .call_static_method( + class, + "getActivity", + "(I)Landroid/app/Activity;", + &[id.into()], + )? + .l()?; + if env.is_same_object(activity, JObject::null())? { + Err(Error::InvalidHandle) + } else { + Ok(env.new_global_ref(activity)?) + } + } + + pub fn get_flutter_view(&self, handle: i64) -> FlutterEngineContextResult { + let id: jint = handle.try_into().map_err(|_| Error::InvalidHandle)?; + let env = self.java_vm.get_env()?; + let class = self.get_plugin_class(&env)?; + let view = env + .call_static_method( + class, + "getFlutterView", + "(I)Lio/flutter/embedding/android/FlutterView;", + &[id.into()], + )? + .l()?; + if env.is_same_object(view, JObject::null())? { + Err(Error::InvalidHandle) + } else { + Ok(env.new_global_ref(view)?) + } + } + + pub fn get_binary_messenger( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let id: jint = handle.try_into().map_err(|_| Error::InvalidHandle)?; + let env = self.java_vm.get_env()?; + let class = self.get_plugin_class(&env)?; + let messenger = env + .call_static_method( + class, + "getBinaryMessenger", + "(I)Lio/flutter/plugin/common/BinaryMessenger;", + &[id.into()], + )? + .l()?; + if env.is_same_object(messenger, JObject::null())? { + Err(Error::InvalidHandle) + } else { + Ok(env.new_global_ref(messenger)?) + } + } + + pub fn get_texture_registry( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let id: jint = handle.try_into().map_err(|_| Error::InvalidHandle)?; + let env = self.java_vm.get_env()?; + let class = self.get_plugin_class(&env)?; + let registry = env + .call_static_method( + class, + "getTextureRegistry", + "(I)Lio/flutter/view/TextureRegistry;", + &[id.into()], + )? + .l()?; + if env.is_same_object(registry, JObject::null())? { + Err(Error::InvalidHandle) + } else { + Ok(env.new_global_ref(registry)?) + } + } +} diff --git a/engine_context/rust/src/darwin.rs b/engine_context/rust/src/darwin.rs new file mode 100644 index 0000000..9c75e49 --- /dev/null +++ b/engine_context/rust/src/darwin.rs @@ -0,0 +1,78 @@ +use std::fmt::Display; + +use cocoa::base::{id, nil}; +use objc::{class, msg_send, sel, sel_impl}; + +use crate::FlutterEngineContextResult; + +pub(crate) struct PlatformContext {} + +#[derive(Debug)] +pub enum Error { + InvalidHandle, +} + +pub(crate) type FlutterView = id; +pub(crate) type FlutterTextureRegistry = id; +pub(crate) type FlutterBinaryMessenger = id; + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidHandle => write!(f, "invalid engine handle"), + } + } +} + +impl std::error::Error for Error {} + +impl PlatformContext { + pub fn new() -> FlutterEngineContextResult { + Ok(Self {}) + } + + pub fn get_flutter_view(&self, handle: i64) -> FlutterEngineContextResult { + unsafe { + let view: id = msg_send![class!(FlutterEngineContextPlugin), getFlutterView: handle]; + if view == nil { + Err(Error::InvalidHandle) + } else { + Ok(view) + } + } + } + + pub fn get_texture_registry( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + unsafe { + let registry: id = msg_send![ + class!(FlutterEngineContextPlugin), + getTextureRegistry: handle + ]; + if registry == nil { + Err(Error::InvalidHandle) + } else { + Ok(registry) + } + } + } + + pub fn get_binary_messenger( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + unsafe { + let messenger: id = msg_send![ + class!(FlutterEngineContextPlugin), + getBinaryMessenger: handle + ]; + if messenger == nil { + Err(Error::InvalidHandle) + } else { + Ok(messenger) + } + } + } +} diff --git a/engine_context/rust/src/lib.rs b/engine_context/rust/src/lib.rs new file mode 100644 index 0000000..6f94858 --- /dev/null +++ b/engine_context/rust/src/lib.rs @@ -0,0 +1,79 @@ +#![allow(clippy::new_without_default)] + +use std::{cell::Cell, marker::PhantomData, sync::MutexGuard}; + +#[cfg(target_os = "android")] +#[path = "android.rs"] +pub mod platform; + +#[cfg(target_os = "windows")] +#[path = "windows.rs"] +pub mod platform; + +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +pub mod platform; + +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[path = "darwin.rs"] +pub mod platform; + +pub type FlutterEngineContextError = platform::Error; +pub type FlutterEngineContextResult = Result; + +pub type FlutterView = platform::FlutterView; +pub type FlutterTextureRegistry = platform::FlutterTextureRegistry; +pub type FlutterBinaryMessenger = platform::FlutterBinaryMessenger; +#[cfg(target_os = "android")] +pub type Activity = platform::Activity; + +type PhantomUnsync = PhantomData>; +type PhantomUnsend = PhantomData>; + +pub struct FlutterEngineContext { + platform_context: platform::PlatformContext, + _unsync: PhantomUnsync, + _unsend: PhantomUnsend, +} + +impl FlutterEngineContext { + /// Creates new FlutterEngineContext instance. + /// Must be called on platform thread. + pub fn new() -> FlutterEngineContextResult { + Ok(Self { + platform_context: platform::PlatformContext::new()?, + _unsync: PhantomData, + _unsend: PhantomData, + }) + } + + /// Returns flutter view for given engine handle. + pub fn get_flutter_view( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + self.platform_context.get_flutter_view(handle) + } + + /// Returns texture registry for given engine handle. + pub fn get_texture_registry( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + self.platform_context.get_texture_registry(handle) + } + + /// Returns binary messenger for given engine handle. + pub fn get_binary_messenger( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + self.platform_context.get_binary_messenger(handle) + } + + /// Returns android activity for given handle. + #[cfg(target_os = "android")] + pub fn get_activity(&self, handle: i64) -> FlutterEngineContextResult { + self.platform_context.get_activity(handle) + } +} diff --git a/engine_context/rust/src/linux.rs b/engine_context/rust/src/linux.rs new file mode 100644 index 0000000..2f0b2d4 --- /dev/null +++ b/engine_context/rust/src/linux.rs @@ -0,0 +1,94 @@ +use std::{ + ffi::{c_void, CString}, + fmt::Display, + mem::transmute, + os::raw::{c_char, c_int}, +}; + +use crate::FlutterEngineContextResult; + +pub struct PlatformContext {} + +#[derive(Debug)] +pub enum Error { + InvalidHandle, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidHandle => write!(f, "invalid engine handle"), + } + } +} + +impl std::error::Error for Error {} + +const RTLD_LAZY: c_int = 1; + +extern "C" { + fn dlopen(filename: *const c_char, flags: c_int) -> *mut c_void; + fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void; +} + +pub(crate) type FlutterView = FlView; +pub(crate) type FlutterTextureRegistry = FlTextureRegistrar; +pub(crate) type FlutterBinaryMessenger = FlBinaryMessenger; + +type FlView = *mut c_void; +type FlTextureRegistrar = *mut c_void; +type FlBinaryMessenger = *mut c_void; +type GetFlutterViewProc = unsafe extern "C" fn(i64) -> FlView; +type GetFlutterTextureRegistrarProc = unsafe extern "C" fn(i64) -> FlTextureRegistrar; +type GetFlutterBinaryMessengerProc = unsafe extern "C" fn(i64) -> FlBinaryMessenger; + +impl PlatformContext { + pub fn new() -> FlutterEngineContextResult { + Ok(Self {}) + } + + fn get_proc(name: &str) -> *mut c_void { + let dl = unsafe { dlopen(std::ptr::null_mut(), RTLD_LAZY) }; + let name = CString::new(name).unwrap(); + unsafe { dlsym(dl, name.as_ptr()) } + } + + pub fn get_flutter_view(&self, handle: i64) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetFlutterView"); + let proc: GetFlutterViewProc = unsafe { transmute(proc) }; + let view = unsafe { proc(handle) }; + if view.is_null() { + Err(Error::InvalidHandle) + } else { + Ok(view) + } + } + + pub fn get_binary_messenger( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetBinaryMessenger"); + let proc: GetFlutterBinaryMessengerProc = unsafe { transmute(proc) }; + let messenger = unsafe { proc(handle) }; + if messenger.is_null() { + Err(Error::InvalidHandle) + } else { + Ok(messenger) + } + } + + pub fn get_texture_registry( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetTextureRegistrar"); + let proc: GetFlutterTextureRegistrarProc = unsafe { transmute(proc) }; + let registry = unsafe { proc(handle) }; + if registry.is_null() { + Err(Error::InvalidHandle) + } else { + Ok(registry) + } + } +} diff --git a/engine_context/rust/src/windows.rs b/engine_context/rust/src/windows.rs new file mode 100644 index 0000000..4e0e21c --- /dev/null +++ b/engine_context/rust/src/windows.rs @@ -0,0 +1,102 @@ +use std::{ + ffi::{c_void, CString}, + fmt::Display, + mem::transmute, +}; + +use crate::FlutterEngineContextResult; + +#[derive(Debug)] +pub enum Error { + InvalidHandle, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidHandle => write!(f, "invalid engine handle"), + } + } +} + +impl std::error::Error for Error {} + +pub struct PlatformContext {} + +#[allow(clippy::upper_case_acronyms)] +type LPCSTR = *const i8; +#[allow(clippy::upper_case_acronyms)] +type HINSTANCE = isize; +#[allow(clippy::upper_case_acronyms)] +type HMODULE = isize; +#[allow(clippy::upper_case_acronyms)] +type HWND = isize; + +#[link(name = "kernel32")] +extern "system" { + pub fn GetModuleHandleA(lpmodulename: LPCSTR) -> HINSTANCE; + pub fn GetProcAddress(hModule: HMODULE, lpProcName: LPCSTR) -> *mut c_void; +} + +pub(crate) type FlutterView = HWND; +pub(crate) type FlutterTextureRegistry = FlutterDesktopTextureRegistrarRef; +pub(crate) type FlutterBinaryMessenger = FlutterDesktopMessengerRef; + +type FlutterDesktopTextureRegistrarRef = *mut c_void; +type FlutterDesktopMessengerRef = *mut c_void; + +type GetFlutterViewProc = unsafe extern "C" fn(i64) -> isize; +type GetTextureRegistrarProc = unsafe extern "C" fn(i64) -> FlutterDesktopTextureRegistrarRef; +type GetMessengerProc = unsafe extern "C" fn(i64) -> FlutterDesktopMessengerRef; + +impl PlatformContext { + pub fn new() -> FlutterEngineContextResult { + Ok(Self {}) + } + + fn get_proc(name: &str) -> *mut c_void { + let module_name = CString::new("flutter_engine_context_plugin.dll").unwrap(); + let module = unsafe { GetModuleHandleA(module_name.as_ptr()) }; + let proc_name = CString::new(name).unwrap(); + unsafe { GetProcAddress(module, proc_name.as_ptr()) } + } + + pub fn get_flutter_view(&self, handle: i64) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetFlutterView"); + let proc: GetFlutterViewProc = unsafe { transmute(proc) }; + let view = unsafe { proc(handle) }; + if view == 0 { + Err(Error::InvalidHandle) + } else { + Ok(view) + } + } + + pub fn get_texture_registry( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetTextureRegistrar"); + let proc: GetTextureRegistrarProc = unsafe { transmute(proc) }; + let registry = unsafe { proc(handle) }; + if registry.is_null() { + Err(Error::InvalidHandle) + } else { + Ok(registry) + } + } + + pub fn get_binary_messenger( + &self, + handle: i64, + ) -> FlutterEngineContextResult { + let proc = Self::get_proc("FlutterEngineContextGetBinaryMessenger"); + let proc: GetMessengerProc = unsafe { transmute(proc) }; + let messenger = unsafe { proc(handle) }; + if messenger.is_null() { + Err(Error::InvalidHandle) + } else { + Ok(messenger) + } + } +} diff --git a/jni_context/Cargo.toml b/jni_context/Cargo.toml new file mode 100644 index 0000000..27c97bb --- /dev/null +++ b/jni_context/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nativeshell_jni_context" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Access to JavaVM for Flutter JNI libraries." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.19" +once_cell = "1.16.0" diff --git a/jni_context/src/android/mini_run_loop.rs b/jni_context/src/android/mini_run_loop.rs new file mode 100644 index 0000000..1c814dc --- /dev/null +++ b/jni_context/src/android/mini_run_loop.rs @@ -0,0 +1,130 @@ +use std::{ + ffi::c_int, + mem::ManuallyDrop, + rc::{Rc, Weak}, + sync::{Arc, Mutex}, +}; + +use super::sys::{ + libc::{close, pipe, read}, + ndk_sys::{ + ALooper_acquire, ALooper_addFd, ALooper_forThread, ALooper_release, ALooper_removeFd, + ALOOPER_EVENT_INPUT, + }, +}; + +use {super::sys::libc::write, super::sys::ndk_sys::ALooper}; + +/// Minimal run-loop implementation. +pub(crate) struct MiniRunLoop { + looper: *mut ALooper, + pipes: [c_int; 2], + state: Rc, + state_ptr: *const State, +} + +struct State { + callbacks: Arc>, +} + +type SenderCallback = Box; + +pub(crate) struct RunLoopCallbacks { + fd: c_int, + callbacks: Vec, +} + +impl RunLoopCallbacks { + pub fn schedule(&mut self, callback: SenderCallback) { + self.callbacks.push(callback); + let buf = [0u8; 8]; + unsafe { + write(self.fd, buf.as_ptr() as *const _, buf.len()); + } + } +} + +impl MiniRunLoop { + pub fn is_main_thread() -> bool { + let looper = unsafe { ALooper_forThread() }; + !looper.is_null() + } + + pub fn new() -> Self { + let looper = unsafe { + let looper = ALooper_forThread(); + ALooper_acquire(looper); + looper + }; + let mut pipes: [c_int; 2] = [0, 2]; + unsafe { pipe(pipes.as_mut_ptr()) }; + let state = Rc::new(State { + callbacks: Arc::new(Mutex::new(RunLoopCallbacks { + fd: pipes[1], + callbacks: Vec::new(), + })), + }); + let state_ptr = Weak::into_raw(Rc::downgrade(&state)); + unsafe { + ALooper_addFd( + looper, + pipes[0], + 0, + ALOOPER_EVENT_INPUT as c_int, + Some(Self::looper_cb), + state_ptr as *mut _, + ); + } + + Self { + looper, + pipes, + state, + state_ptr, + } + } + + unsafe extern "C" fn looper_cb( + fd: ::std::os::raw::c_int, + _events: ::std::os::raw::c_int, + data: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int { + let mut buf = [0u8; 8]; + read(fd, buf.as_mut_ptr() as *mut _, buf.len()); + + let state = data as *const State; + let state = ManuallyDrop::new(Weak::from_raw(state)); + if let Some(state) = state.upgrade() { + state.process_callbacks(); + } + 1 + } + + pub fn callbacks(&self) -> Arc> { + self.state.callbacks.clone() + } +} + +impl State { + fn process_callbacks(&self) { + let callbacks: Vec = { + let mut callbacks = self.callbacks.lock().unwrap(); + callbacks.callbacks.drain(0..).collect() + }; + for c in callbacks { + c() + } + } +} + +impl Drop for MiniRunLoop { + fn drop(&mut self) { + unsafe { + ALooper_removeFd(self.looper, self.pipes[0]); + ALooper_release(self.looper); + Weak::from_raw(self.state_ptr); + close(self.pipes[0]); + close(self.pipes[1]); + } + } +} diff --git a/jni_context/src/android/mod.rs b/jni_context/src/android/mod.rs new file mode 100644 index 0000000..638a650 --- /dev/null +++ b/jni_context/src/android/mod.rs @@ -0,0 +1,141 @@ +mod mini_run_loop; +mod sys; + +use std::{ + ffi::c_void, + fmt::Display, + mem::ManuallyDrop, + sync::{Arc, Mutex}, +}; + +use jni::{objects::GlobalRef, JavaVM}; +use once_cell::sync::OnceCell; + +use self::mini_run_loop::{MiniRunLoop, RunLoopCallbacks}; + +#[derive(Debug, Clone)] +pub enum Error { + NotInitialized, + NotInitializedOnMainThread, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::NotInitialized => write!( + f, + "JNI_OnLoad was not called. Make sure to load the library using 'System.loadLibrary'." + ), + Error::NotInitializedOnMainThread => write!( + f, + "JNI_OnLoad was not called on the main thread. Make sure to load the library using 'System.loadLibrary' on main thread." + ), + } + } +} + +impl std::error::Error for Error {} + +pub struct AndroidJniContext { + vm: JavaVM, + class_loader: Option, + callbacks: Arc>, + main_thread_id: std::thread::ThreadId, +} + +impl std::fmt::Debug for AndroidJniContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AndroidJniContext").finish() + } +} + +static CONTEXT: OnceCell> = OnceCell::new(); + +impl AndroidJniContext { + /// Returns JNI context for current dylib. Will fail with error if current + /// library was not loaded using `System.loadLibrary` or was not loaded on + /// main thread. + pub fn get() -> Result<&'static AndroidJniContext, Error> { + match CONTEXT.get() { + Some(Ok(context)) => Ok(context), + Some(Err(e)) => Err(e.clone()), + None => Err(Error::NotInitialized), + } + } + + /// Returns reference to current process JavaVM. + pub fn vm(&self) -> &JavaVM { + &self.vm + } + + /// Returns class loader that was used to load application code. + /// This will only work when used in Flutter application. + pub fn class_loader(&self) -> Option<&GlobalRef> { + self.class_loader.as_ref() + } + + /// Will schedule the following closure to be executed on the main thread. + /// Main thread is the thread on which System.loadLibrary was called. The + /// thread must have active Looper. + /// + /// Conceptually this may seem out of scope for this crate, but it is + /// necessary given that there might not be other opportunity to interact + /// with main looper outside of JNI_OnLoad. + pub fn schedule_on_main_thread(&self, f: F) + where + F: FnOnce() + 'static + Send, + { + let mut callbacks = self.callbacks.lock().unwrap(); + callbacks.schedule(Box::new(f)); + } + + /// Returns true if current thread is the main thread, false otherwise. + pub fn is_main_thread(&self) -> bool { + std::thread::current().id() == self.main_thread_id + } +} + +fn get_class_loader(vm: &JavaVM) -> Option { + let env = vm.attach_current_thread().unwrap(); + let class = env.find_class("io/flutter/embedding/android/FlutterView"); + if let Ok(class) = class { + let loader = env.call_method(class, "getClassLoader", "()Ljava/lang/ClassLoader;", &[]); + if let Ok(loader) = loader { + return Some(env.new_global_ref(loader.l().unwrap()).unwrap()); + } + } + None +} + +#[no_mangle] +#[allow(non_snake_case)] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn JNI_OnLoad( + vm: *mut jni::sys::JavaVM, + _reserved: *mut c_void, +) -> jni::sys::jint { + // There are obscure reasons why JNI_OnLoad might be called more than once. + if CONTEXT.get().is_some() { + return jni::sys::JNI_VERSION_1_6; + } + + if !MiniRunLoop::is_main_thread() { + CONTEXT.set(Err(Error::NotInitializedOnMainThread)).unwrap(); + return jni::sys::JNI_VERSION_1_6; + } + + let mini_runloop = ManuallyDrop::new(MiniRunLoop::new()); + + let vm = unsafe { JavaVM::from_raw(vm) }.unwrap(); + let class_loader = get_class_loader(&vm); + + CONTEXT + .set(Ok(AndroidJniContext { + vm, + class_loader, + callbacks: mini_runloop.callbacks(), + main_thread_id: std::thread::current().id(), + })) + .unwrap(); + jni::sys::JNI_VERSION_1_6 +} diff --git a/jni_context/src/android/sys.rs b/jni_context/src/android/sys.rs new file mode 100644 index 0000000..1bcc033 --- /dev/null +++ b/jni_context/src/android/sys.rs @@ -0,0 +1,50 @@ +#[allow(non_camel_case_types)] +pub mod ndk_sys { + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct ALooper { + _unused: [u8; 0], + } + + pub type ALooper_callbackFunc = ::std::option::Option< + unsafe extern "C" fn( + fd: ::std::os::raw::c_int, + events: ::std::os::raw::c_int, + data: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >; + + pub const ALOOPER_EVENT_INPUT: ::std::os::raw::c_uint = 1; + + #[link(name = "android")] + extern "C" { + pub fn ALooper_forThread() -> *mut ALooper; + pub fn ALooper_acquire(looper: *mut ALooper); + pub fn ALooper_release(looper: *mut ALooper); + pub fn ALooper_addFd( + looper: *mut ALooper, + fd: ::std::os::raw::c_int, + ident: ::std::os::raw::c_int, + events: ::std::os::raw::c_int, + callback: ALooper_callbackFunc, + data: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; + pub fn ALooper_removeFd( + looper: *mut ALooper, + fd: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; + } +} + +// We only use handful of methods, no need to pull entire libc as dependency +#[allow(non_camel_case_types)] +pub mod libc { + use std::os::raw::{c_int, c_void}; + + extern "C" { + pub fn read(fd: c_int, buf: *mut c_void, count: usize) -> isize; + pub fn pipe(fds: *mut c_int) -> c_int; + pub fn close(fd: c_int) -> c_int; + pub fn write(fd: c_int, buf: *const c_void, count: usize) -> isize; + } +} diff --git a/jni_context/src/lib.rs b/jni_context/src/lib.rs new file mode 100644 index 0000000..85c9d26 --- /dev/null +++ b/jni_context/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(target_os = "android")] +mod android; + +#[cfg(target_os = "android")] +pub use android::*; diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 0000000..ada64b5 --- /dev/null +++ b/melos.yaml @@ -0,0 +1,5 @@ +name: nativeshell_ng + +packages: + - engine_context/dart + - core/dart