| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // Entrypoint for running a Flutter app on Fuchsia. |
| // |
| // Usage: ./main <path_to_flutter_asset_bundle> |
| |
| #include <fuchsia/ui/app/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/syslog/global.h> |
| #include <string> |
| #include <zircon/status.h> |
| |
| #include "src/embedder/engine/embedder.h" |
| |
| namespace { |
| |
| constexpr char kLogTag[] = "flutter_embedder"; |
| |
| // TODO(akbiggs): This is just being used to verify that we can |
| // create and bind things on the component context. We should |
| // replace this with an actual ViewProvider impl. |
| class DummyViewProvider : public fuchsia::ui::app::ViewProvider { |
| public: |
| DummyViewProvider() {} |
| ~DummyViewProvider() override {} |
| DummyViewProvider(const DummyViewProvider &) = delete; |
| DummyViewProvider &operator=(const DummyViewProvider &) = delete; |
| |
| void CreateView( |
| zx::eventpair token, |
| fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services, |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) |
| override { |
| FX_LOG(INFO, kLogTag, "ViewProvider::CreateView"); |
| } |
| |
| void CreateViewWithViewRef(zx::eventpair view_token, |
| fuchsia::ui::views::ViewRefControl control_ref, |
| fuchsia::ui::views::ViewRef view_ref) override { |
| FX_LOG(INFO, kLogTag, "ViewProvider::CreateViewWithViewRef"); |
| } |
| |
| void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override { |
| FX_LOG(INFO, kLogTag, "ViewProvider::CreateView2"); |
| } |
| }; |
| |
| // State for the embedder. The engine callbacks |
| // get this data as an argument so we can access |
| // this data from our implementation of the callbacks. |
| struct EmbedderData { |
| // WARNING: |component_context| MUST BE THE FIRST FIELD OF THIS DATA. |
| // We do a terrible hack in https://github.com/flutter/engine/pull/33472 that |
| // requires reading this field to work around fxb/75282 to set the inspect |
| // node inside the embedder on the Fuchsia platform. |
| // TODO(fxb/75282): Properly fix this and remove the terrible hack. |
| std::unique_ptr<sys::ComponentContext> component_context; |
| }; |
| |
| bool FuchsiaPresentSoftwareSurface(void *user_data, const void *allocation, |
| size_t row_bytes, size_t height) { |
| // TODO(akbiggs): Present the surface to the screen. |
| FX_LOGF(ERROR, kLogTag, |
| "surface_present_callback (row_bytes=%lu, height=%lu)", row_bytes, |
| height); |
| return true; |
| } |
| |
| void FuchsiaLogMessage(const char *tag, const char *message, void *user_data) { |
| // TODO(akbiggs): This does not report the file and line number of the Dart |
| // app that the log came from. |
| FX_LOG(INFO, tag, message); |
| } |
| |
| bool RunFlutterApp(const char *assets_path, EmbedderData *embedder_data) { |
| FlutterRendererConfig renderer_config = { |
| .type = kSoftware, |
| .software = |
| { |
| .struct_size = sizeof(FlutterSoftwareRendererConfig), |
| .surface_present_callback = FuchsiaPresentSoftwareSurface, |
| // TODO(akbiggs): Add callback for acquiring the software surface. |
| }, |
| }; |
| FlutterProjectArgs project_args = { |
| .struct_size = sizeof(FlutterProjectArgs), |
| .assets_path = assets_path, |
| .log_message_callback = FuchsiaLogMessage, |
| .log_tag = "flutter_app", |
| }; |
| |
| // TODO(akbiggs): Store this FlutterEngine instance somewhere instead of |
| // throwing it away. |
| FlutterEngine engine; |
| FlutterEngineResult result = |
| FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, |
| embedder_data, &engine); |
| |
| if (result != kSuccess || engine == nullptr) { |
| FX_LOGF(ERROR, kLogTag, "Could not run the Flutter Engine. Error code: %d", |
| static_cast<int>(result)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| int main(int argc, const char *argv[]) { |
| if (argc != 2) { |
| FX_LOG(ERROR, kLogTag, "usage: executable <path to flutter bundle>"); |
| return EXIT_FAILURE; |
| } |
| const char *assets_path = argv[1]; |
| |
| EmbedderData embedder_data = { |
| .component_context = sys::ComponentContext::Create(), |
| }; |
| |
| // TODO(akbiggs): Remove debug logs once we're sure this is stable. |
| FX_LOG(INFO, kLogTag, "Running Flutter app."); |
| |
| // Note: Serving the component context must happen after RunFlutterApp |
| // because the embedder platform has a hack that adds inspect data into the |
| // Dart VM using the component context |
| // (https://github.com/flutter/engine/pull/33472). |
| RunFlutterApp(assets_path, &embedder_data); |
| |
| FX_LOG(INFO, kLogTag, "Binding ViewProvider."); |
| DummyViewProvider view_provider; |
| fidl::BindingSet<fuchsia::ui::app::ViewProvider> view_provider_bindings; |
| zx_status_t status = |
| embedder_data.component_context->outgoing() |
| ->AddPublicService<fuchsia::ui::app::ViewProvider>( |
| [&view_provider, &view_provider_bindings]( |
| fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> |
| request) { |
| FX_LOG(INFO, kLogTag, "Adding ViewProvider binding."); |
| |
| view_provider_bindings.AddBinding(&view_provider, |
| std::move(request)); |
| }); |
| if (status != ZX_OK) { |
| FX_LOGF(ERROR, kLogTag, "Failed to add ViewProvider service: %s", |
| zx_status_get_string(status)); |
| } |
| |
| // Our run loop's dispatcher must be used as the dispatcher for serving |
| // the component context in order for requests handlers to get called while |
| // we're looping. |
| FX_LOG(INFO, kLogTag, "Serving component context."); |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| status = embedder_data.component_context->outgoing()->ServeFromStartupInfo( |
| loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_LOGF(ERROR, kLogTag, "Failed to serve component context: %s", |
| zx_status_get_string(status)); |
| } |
| |
| // Loop until we're done. |
| FX_LOG(INFO, kLogTag, "Looping."); |
| loop.Run(); |
| |
| FX_LOG(INFO, kLogTag, "Done looping."); |
| return EXIT_SUCCESS; |
| } |