blob: 0f8785d00ef8296fb65c77751ba72e4e5e6070d6 [file] [log] [blame]
// 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/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/scenic/scheduling/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.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 <lib/ui/scenic/cpp/view_identity.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <zircon/status.h>
#include <string>
#include "platform_message_channels.h"
#include "src/embedder/accessibility_bridge.h"
#include "src/embedder/embedder_state.h"
#include "src/embedder/engine/embedder.h"
#include "src/embedder/flatland_ids.h"
#include "src/embedder/flatland_view_provider.h"
#include "src/embedder/fuchsia_logger.h"
#include "src/embedder/logging.h"
#include "src/embedder/mouse_delegate.h"
#include "src/embedder/platform_message_channels.h"
#include "src/embedder/root_inspect_node.h"
#include "src/embedder/software_surface.h"
#include "src/embedder/text_delegate.h"
#include "src/embedder/touch_delegate.h"
namespace embedder {
namespace {
// TODO(akbiggs): Don't hardcode this screen size, get it from
// an event instead.
constexpr int kScreenWidth = 1280;
constexpr int kScreenHeight = 800;
/// Gets the current name of the Fuchsia process that
/// is running the embedder.
std::string GetCurrentProcessName() {
char name[ZX_MAX_NAME_LEN];
zx_status_t status = zx::process::self()->get_property(ZX_PROP_NAME, name, sizeof(name));
if (status != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to get process name for sysmem; using \"\".");
return std::string();
}
return std::string(name);
}
/// Gets the current process ID of the Fuchsia process that
/// is running the embedder.
zx_koid_t GetCurrentProcessId() {
zx_info_handle_basic_t info;
zx_status_t status =
zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
nullptr /* actual_count */, nullptr /* avail_count */);
if (status != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to get process ID for sysmem; using ZX_KOID_INVALID.");
return ZX_KOID_INVALID;
}
return info.koid;
}
/// Return kernel object id for fuchsia view ref
zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
zx_handle_t handle = view_ref.reference.get();
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
/// Handle platform message from dart application, forwarded from flutter engine
void FuchsiaHandlePlatformMessage(const FlutterPlatformMessage* message, void* user_data) {
if (!strcmp(message->channel, kAccessibilityChannel)) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
embedder->accessibility_bridge_->HandlePlatformMessage(message);
} else if (!strcmp(message->channel, kTextInputChannel)) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
embedder->text_delegate_->HandleFlutterTextInputChannelPlatformMessage(message);
} else {
FX_LOGF(INFO, embedder::kLogTag, "FuchsiaHandlePlatformMessage invoked: %s", message->channel);
}
}
/// Fuchsia implementation of the Flutter Embedder update_semantics_callback
void FuchsiaHandleSemanticsUpdate(const FlutterSemanticsUpdate* update, void* user_data) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
embedder->accessibility_bridge_->AddSemanticsUpdate(update);
}
/// Fuchsia implementation of the Flutter Engine's |vsync_callback|.
void FuchsiaVsync(void* user_data, intptr_t baton) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
// Encourage the Engine to vsync for a 60Hz display.
const uint64_t now_nanos = FlutterEngineGetCurrentTime();
const uint64_t target_vsync_time = now_nanos + 16.6 * 1e6;
FlutterEngineOnVsync(embedder->engine, baton, now_nanos, target_vsync_time);
}
/// Fuchsia implementation of the Flutter Engine's |log_message_callback|.
void FuchsiaLogMessage(const char* tag, const char* message, void* user_data) {
FX_LOG(INFO, tag, message);
}
/// Callback for the embedder's FlutterEngineSendKeyEvent function.
void FuchsiaFlutterKeyEventCallback(bool handled, void* user_data) {
FX_LOG(INFO, embedder::kLogTag, "FlutterKeyEventCallback invoked!");
}
void FuchsiaFlutterDataCallback(const uint8_t* data, size_t size, void* user_data) {
auto message_data = static_cast<const unsigned char*>(data);
FX_LOGF(INFO, embedder::kLogTag, "FuchsiaFlutterDataCallback invoked, data: %s", message_data);
}
/// Fuchsia implementation of the Flutter Engine's software rendering
/// |surface_acquire_callback|.
bool FuchsiaAcquireSoftwareSurface(void* user_data, size_t width, size_t height,
uint8_t** allocation, size_t* stride) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
// TODO(akbiggs): Anything we need to do to clean up the old
// software surface explicitly before reassigning here?
embedder->software_surface = std::make_unique<SoftwareSurface>(
embedder->sysmem_allocator, embedder->flatland_allocator,
Size{.width = static_cast<uint32_t>(width), .height = static_cast<uint32_t>(height)});
if (!embedder->software_surface->IsValid()) {
FX_LOGF(ERROR, kLogTag, "Failed to acquire software surface of size (%lu, %lu).", width,
height);
return false;
}
// If we receive an unitialized surface, we need to create a
// Flatland image and associate it with the surface.
if (embedder->software_surface->GetImageId() == 0) {
auto image_id = embedder->flatland_connection->NextContentId().value;
const auto surface_size = embedder->software_surface->GetSize();
fuchsia::math::SizeU image_size;
image_size.width = surface_size.width;
image_size.height = surface_size.height;
fuchsia::ui::composition::ImageProperties image_properties;
image_properties.set_size(image_size);
embedder->flatland_connection->flatland()->CreateImage(
{image_id}, embedder->software_surface->GetBufferCollectionImportToken(), 0,
std::move(image_properties));
embedder->software_surface->SetImageId(image_id);
embedder->software_surface->SetReleaseImageCallback(
[flatland = embedder->flatland_connection.get(), image_id]() {
flatland->flatland()->ReleaseImage({image_id});
});
}
// Enqueue fences for the next present.
embedder->flatland_connection->EnqueueAcquireFence(embedder->software_surface->GetAcquireFence());
embedder->flatland_connection->EnqueueReleaseFence(embedder->software_surface->GetReleaseFence());
// Communicate the location of the allocated memory to Flutter so
// Flutter can render into it.
*allocation = embedder->software_surface->GetAllocation();
*stride = embedder->software_surface->GetBytesPerRow();
return true;
}
/// Fuchsia implementation of the Flutter Engine's software rendering
/// |surface_present_callback|.
bool FuchsiaPresentSoftwareSurface(void* user_data, const void* allocation, size_t row_bytes,
size_t height) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
if (embedder->software_surface == nullptr) {
// We have no surface acquired to present.
return false;
}
// TODO(akbiggs): Replace with composition logic matching
// https://github.com/flutter/engine/blob/main/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc#L108.
auto image_id = embedder->software_surface->GetImageId();
embedder->flatland_connection->flatland()->SetContent({kRootTransformId}, {image_id});
embedder->flatland_connection->Present();
// Signal to the rendered surface that we've finished writing.
embedder->software_surface->SignalWritesFinished(
/* on_read_finished = */ []() {
// TODO(akbiggs): Anything we need to do after the read
// finishes?
});
return true;
}
/// Runs the Flutter app whose assets (including snapshots) are located
/// at |assets_path| in the component's package.
///
/// The Engine instance will be stored in |embedder| and |embedder| will be
/// the user data for all callbacks from the Flutter Engine.
bool RunFlutterApp(const char* assets_path, EmbedderState* embedder) {
FlutterRendererConfig renderer_config = {
.type = kSoftware,
.software =
{
.struct_size = sizeof(FlutterSoftwareRendererConfig),
.surface_present_callback = FuchsiaPresentSoftwareSurface,
.surface_acquire_callback = FuchsiaAcquireSoftwareSurface,
},
};
// Connect and set up the system font provider.
fuchsia::fonts::ProviderSyncPtr sync_font_provider;
zx_status_t status = embedder->component_context->svc()->Connect<fuchsia::fonts::Provider>(
sync_font_provider.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fonts provider service: %s",
zx_status_get_string(status));
}
FlutterProjectArgs project_args = {
.struct_size = sizeof(FlutterProjectArgs),
.assets_path = assets_path,
.platform_message_callback = FuchsiaHandlePlatformMessage,
.vsync_callback = FuchsiaVsync,
.log_message_callback = FuchsiaLogMessage,
.log_tag = "flutter_app",
.update_semantics_callback = FuchsiaHandleSemanticsUpdate,
.font_initialization_data = sync_font_provider.Unbind().TakeChannel().release()};
FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config,
&project_args, embedder, &embedder->engine);
if (result != kSuccess || embedder->engine == nullptr) {
FX_LOGF(ERROR, kLogTag, "Could not run the Flutter Engine. Error code: %d",
static_cast<int>(result));
return false;
}
return true;
}
} // namespace
} // namespace embedder
int main(int argc, const char* argv[]) {
if (argc != 2) {
FX_LOG(ERROR, embedder::kLogTag, "usage: executable <path to flutter bundle>");
return EXIT_FAILURE;
}
const char* assets_path = argv[1];
embedder::EmbedderState embedder = {
// This must be initialized before calling RunFlutterApp, as
// this hack https://github.com/flutter/engine/pull/33472
// relies
// on it.
.component_context = sys::ComponentContext::Create(),
};
// 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).
if (!embedder::RunFlutterApp(assets_path, &embedder)) {
return EXIT_FAILURE;
}
// Ensure default dispatcher is set after caling RunFlutterApp
FX_CHECK(async_get_default_dispatcher());
// Necessary to get Flutter to render frames.
FlutterWindowMetricsEvent window_metrics_event = {
.struct_size = sizeof(FlutterWindowMetricsEvent),
// TODO(akbiggs): Don't hardcode these.
.width = embedder::kScreenWidth,
.height = embedder::kScreenHeight,
.pixel_ratio = 1,
.left = 0,
.top = 0,
.physical_view_inset_top = 0,
.physical_view_inset_right = 0,
.physical_view_inset_bottom = 0,
.physical_view_inset_left = 0,
};
FlutterEngineSendWindowMetricsEvent(embedder.engine, &window_metrics_event);
// Connect to Keyboard service.
fuchsia::ui::input3::KeyboardHandle keyboard;
zx_status_t keyboard_status =
embedder.component_context->svc()->Connect<fuchsia::ui::input3::Keyboard>(
keyboard.NewRequest());
if (keyboard_status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia::ui::input3::Keyboard: %s",
zx_status_get_string(keyboard_status));
return EXIT_FAILURE;
}
// Connect to ImeService service.
fuchsia::ui::input::ImeServiceHandle ime_service;
zx_status_t ime_status =
embedder.component_context->svc()->Connect<fuchsia::ui::input::ImeService>(
ime_service.NewRequest());
if (ime_status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia::ui::input::ImeService: %s",
zx_status_get_string(ime_status));
}
// Connect to Flatland.
fuchsia::ui::composition::FlatlandHandle flatland_handle;
zx_status_t status =
embedder.component_context->svc()->Connect<fuchsia::ui::composition::Flatland>(
flatland_handle.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to Flatland: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// TODO(akbiggs): Create this on the raster thread per
// FlatlandConnection's documentation.
embedder.flatland_connection = std::make_unique<embedder::FlatlandConnection>(
// TODO(akbiggs): Pick a more appropriate debug name based
// on the Flutter app name.
"FlutterEmbedder" /* debug_name */, std::move(flatland_handle),
/* on_error_callback = */
[]() {
// TODO(akbiggs): What should we do on errors?
},
/* on_frame_presented_callback = */
[](fuchsia::scenic::scheduling::FramePresentedInfo info) {
// TODO(akbiggs): What should we do after a frame
// finishes presenting?
});
// Focuser for programatically transfering View focus.
fuchsia::ui::views::FocuserHandle focuser;
fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused;
// The protocol endpoints bound to a Flatland ViewCreationToken.
fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols;
flatland_view_protocols.set_view_focuser(focuser.NewRequest());
flatland_view_protocols.set_view_ref_focused(view_ref_focused.NewRequest());
// Mouse & Touch source
fuchsia::ui::pointer::MouseSourceHandle mouse_source;
fuchsia::ui::pointer::TouchSourceHandle touch_source;
flatland_view_protocols.set_touch_source(touch_source.NewRequest());
flatland_view_protocols.set_mouse_source(mouse_source.NewRequest());
auto view_ref_pair = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef platform_view_ref;
view_ref_pair.view_ref.Clone(&platform_view_ref);
fuchsia::ui::views::ViewRef accessibility_view_ref;
view_ref_pair.view_ref.Clone(&accessibility_view_ref);
// Response handle attached to a FlutterPlatformMessage to be called in the engine.
FlutterPlatformMessageResponseHandle* response_handle;
FlutterPlatformMessageCreateResponseHandle(embedder.engine, embedder::FuchsiaFlutterDataCallback,
&embedder, &response_handle);
// Keyboard/Text input.
embedder.text_delegate_ = std::make_unique<embedder::TextDelegate>(
std::move(platform_view_ref), std::move(ime_service), std::move(keyboard),
/* key_event_dispatch_callback: [flutter/keydata] */
[&embedder](const FlutterKeyEvent* event) {
// Send key event to the engine.
FlutterEngineResult result = FlutterEngineSendKeyEvent(
embedder.engine, event, embedder::FuchsiaFlutterKeyEventCallback, &embedder);
if (result != kSuccess) {
FX_LOGF(INFO, embedder::kLogTag, "FlutterEngineSendKeyEvent failed for the %s channel.",
embedder::kKeyDataChannel);
}
},
/*platform_dispatch_callback: [flutter/keyevent, flutter/textinput] */
[&embedder](const FlutterPlatformMessage* message) {
// Send platform message to the engine.
FlutterEngineResult result = FlutterEngineSendPlatformMessage(embedder.engine, message);
if (result != kSuccess) {
FX_LOGF(INFO, embedder::kLogTag,
"FlutterEngineSendPlatformMessage failed for the %s channel.", message->channel);
}
},
response_handle);
embedder::FlatlandViewProvider view_provider(embedder.flatland_connection.get(),
std::move(view_ref_pair),
std::move(flatland_view_protocols));
auto send_pointer_events_to_engine = [&embedder](std::vector<FlutterPointerEvent> events) {
if (events.empty())
return;
FlutterEngineSendPointerEvent(embedder.engine, &events[0], events.size());
};
auto mouse_delegate = std::make_unique<embedder::MouseDelegate>(std::move(mouse_source));
auto touch_delegate = std::make_unique<embedder::TouchDelegate>(std::move(touch_source));
mouse_delegate->WatchLoop(send_pointer_events_to_engine);
touch_delegate->WatchLoop(send_pointer_events_to_engine);
// Return to ViewProvider Initialization
fidl::BindingSet<fuchsia::ui::app::ViewProvider> view_provider_bindings;
status = embedder.component_context->outgoing()->AddPublicService<fuchsia::ui::app::ViewProvider>(
[&view_provider,
&view_provider_bindings](fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) {
view_provider_bindings.AddBinding(&view_provider, std::move(request));
});
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to add ViewProvider service: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Our run loop's dispatcher must be used to serve the component
// context in order for requests' handlers to get called while
// we're looping.
status = embedder.component_context->outgoing()->ServeFromStartupInfo();
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to serve component context: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Set up sysmem allocator for software rendering.
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
embedder.sysmem_allocator.NewRequest().TakeChannel().release());
embedder.sysmem_allocator->SetDebugClientInfo(embedder::GetCurrentProcessName(),
embedder::GetCurrentProcessId());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia.sysmem.Allocator: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Set up Flatland allocator for software rendering.
status = fdio_service_connect("/svc/fuchsia.ui.composition.Allocator",
embedder.flatland_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia.ui.composition.Allocator: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
embedder::AccessibilityBridge::SetSemanticsEnabledCallback set_semantics_enabled_callback =
[&embedder](bool enabled) { FlutterEngineUpdateSemanticsEnabled(embedder.engine, enabled); };
embedder::AccessibilityBridge::DispatchSemanticsActionCallback
dispatch_semantics_action_callback =
[&embedder](int32_t node_id, FlutterSemanticsAction action) {
FlutterEngineDispatchSemanticsAction(embedder.engine, node_id, action, nullptr, 0);
};
const std::string accessibility_inspect_name =
std::to_string(embedder::GetKoid(accessibility_view_ref));
// Connect to SemanticsManager service.
fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager;
status = embedder.component_context->svc()
->Connect<fuchsia::accessibility::semantics::SemanticsManager>(
semantics_manager.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag,
"fuchsia::accessibility::semantics::SemanticsManager connection failed: %s",
zx_status_get_string(status));
}
dart_utils::RootInspectNode::Initialize(embedder.component_context.get());
embedder.accessibility_bridge_ = std::make_unique<embedder::AccessibilityBridge>(
std::move(set_semantics_enabled_callback), std::move(dispatch_semantics_action_callback),
std::move(semantics_manager), std::move(accessibility_view_ref),
dart_utils::RootInspectNode::CreateRootChild(std::move(accessibility_inspect_name)));
view_provider.SetPixelRatioCallback(
[&embedder](float ratio) { embedder.accessibility_bridge_->SetPixelRatio(ratio); });
// TODO(benbergkamp): temporary a workaround
FlutterEngineRunMessageLoop();
FX_LOG(INFO, embedder::kLogTag, "Done looping.");
return EXIT_SUCCESS;
}