blob: 69621369096c5464979751862f0e235e9a68e5bd [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include <dwmapi.h>
#include <filesystem>
#include <sstream>
#include "flutter/fml/logging.h"
#include "flutter/fml/paths.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
#include "flutter/shell/platform/common/path_utils.h"
#include "flutter/shell/platform/embedder/embedder_struct_macros.h"
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
#include "flutter/shell/platform/windows/compositor_opengl.h"
#include "flutter/shell/platform/windows/compositor_software.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h"
#include "flutter/shell/platform/windows/system_utils.h"
#include "flutter/shell/platform/windows/task_runner.h"
#include "flutter/third_party/accessibility/ax/ax_node.h"
// winbase.h defines GetCurrentTime as a macro.
#undef GetCurrentTime
static constexpr char kAccessibilityChannelName[] = "flutter/accessibility";
namespace flutter {
namespace {
// Lifted from vsync_waiter_fallback.cc
static std::chrono::nanoseconds SnapToNextTick(
std::chrono::nanoseconds value,
std::chrono::nanoseconds tick_phase,
std::chrono::nanoseconds tick_interval) {
std::chrono::nanoseconds offset = (tick_phase - value) % tick_interval;
if (offset != std::chrono::nanoseconds::zero())
offset = offset + tick_interval;
return value + offset;
}
// Creates and returns a FlutterRendererConfig that renders to the view (if any)
// of a FlutterWindowsEngine, using OpenGL (via ANGLE).
// The user_data received by the render callbacks refers to the
// FlutterWindowsEngine.
FlutterRendererConfig GetOpenGLRendererConfig() {
FlutterRendererConfig config = {};
config.type = kOpenGL;
config.open_gl.struct_size = sizeof(config.open_gl);
config.open_gl.make_current = [](void* user_data) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!host->egl_manager()) {
return false;
}
return host->egl_manager()->render_context()->MakeCurrent();
};
config.open_gl.clear_current = [](void* user_data) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!host->egl_manager()) {
return false;
}
return host->egl_manager()->render_context()->ClearCurrent();
};
config.open_gl.present = [](void* user_data) -> bool { FML_UNREACHABLE(); };
config.open_gl.fbo_reset_after_present = true;
config.open_gl.fbo_with_frame_info_callback =
[](void* user_data, const FlutterFrameInfo* info) -> uint32_t {
FML_UNREACHABLE();
};
config.open_gl.gl_proc_resolver = [](void* user_data,
const char* what) -> void* {
return reinterpret_cast<void*>(eglGetProcAddress(what));
};
config.open_gl.make_resource_current = [](void* user_data) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!host->egl_manager()) {
return false;
}
return host->egl_manager()->resource_context()->MakeCurrent();
};
config.open_gl.gl_external_texture_frame_callback =
[](void* user_data, int64_t texture_id, size_t width, size_t height,
FlutterOpenGLTexture* texture) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!host->texture_registrar()) {
return false;
}
return host->texture_registrar()->PopulateTexture(texture_id, width, height,
texture);
};
return config;
}
// Creates and returns a FlutterRendererConfig that renders to the view (if any)
// of a FlutterWindowsEngine, using software rasterization.
// The user_data received by the render callbacks refers to the
// FlutterWindowsEngine.
FlutterRendererConfig GetSoftwareRendererConfig() {
FlutterRendererConfig config = {};
config.type = kSoftware;
config.software.struct_size = sizeof(config.software);
config.software.surface_present_callback =
[](void* user_data, const void* allocation, size_t row_bytes,
size_t height) {
FML_UNREACHABLE();
return false;
};
return config;
}
// Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage.
static FlutterDesktopMessage ConvertToDesktopMessage(
const FlutterPlatformMessage& engine_message) {
FlutterDesktopMessage message = {};
message.struct_size = sizeof(message);
message.channel = engine_message.channel;
message.message = engine_message.message;
message.message_size = engine_message.message_size;
message.response_handle = engine_message.response_handle;
return message;
}
// Converts a LanguageInfo struct to a FlutterLocale struct. |info| must outlive
// the returned value, since the returned FlutterLocale has pointers into it.
FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) {
FlutterLocale locale = {};
locale.struct_size = sizeof(FlutterLocale);
locale.language_code = info.language.c_str();
if (!info.region.empty()) {
locale.country_code = info.region.c_str();
}
if (!info.script.empty()) {
locale.script_code = info.script.c_str();
}
return locale;
}
} // namespace
FlutterWindowsEngine::FlutterWindowsEngine(
const FlutterProjectBundle& project,
std::shared_ptr<WindowsProcTable> windows_proc_table)
: project_(std::make_unique<FlutterProjectBundle>(project)),
windows_proc_table_(std::move(windows_proc_table)),
aot_data_(nullptr, nullptr),
views_mutex_(fml::SharedMutex::Create()),
lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
if (windows_proc_table_ == nullptr) {
windows_proc_table_ = std::make_shared<WindowsProcTable>();
}
gl_ = egl::ProcTable::Create();
embedder_api_.struct_size = sizeof(FlutterEngineProcTable);
FlutterEngineGetProcAddresses(&embedder_api_);
task_runner_ =
std::make_unique<TaskRunner>(
embedder_api_.GetCurrentTime, [this](const auto* task) {
if (!engine_) {
FML_LOG(ERROR)
<< "Cannot post an engine task when engine is not running.";
return;
}
if (embedder_api_.RunTask(engine_, task) != kSuccess) {
FML_LOG(ERROR) << "Failed to post an engine task.";
}
});
// Set up the legacy structs backing the API handles.
messenger_ =
fml::RefPtr<FlutterDesktopMessenger>(new FlutterDesktopMessenger());
messenger_->SetEngine(this);
plugin_registrar_ = std::make_unique<FlutterDesktopPluginRegistrar>();
plugin_registrar_->engine = this;
messenger_wrapper_ =
std::make_unique<BinaryMessengerImpl>(messenger_->ToRef());
message_dispatcher_ =
std::make_unique<IncomingMessageDispatcher>(messenger_->ToRef());
texture_registrar_ =
std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_);
// Check for impeller support.
auto& switches = project_->GetSwitches();
enable_impeller_ = std::find(switches.begin(), switches.end(),
"--enable-impeller=true") != switches.end();
egl_manager_ = egl::Manager::Create(enable_impeller_);
window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();
window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(
[](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,
LRESULT* result) {
BASE_DCHECK(user_data);
FlutterWindowsEngine* that =
static_cast<FlutterWindowsEngine*>(user_data);
BASE_DCHECK(that->lifecycle_manager_);
return that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar,
result);
},
static_cast<void*>(this));
// Set up internal channels.
// TODO: Replace this with an embedder.h API. See
// https://github.com/flutter/flutter/issues/71099
internal_plugin_registrar_ =
std::make_unique<PluginRegistrar>(plugin_registrar_.get());
accessibility_plugin_ = std::make_unique<AccessibilityPlugin>(this);
AccessibilityPlugin::SetUp(messenger_wrapper_.get(),
accessibility_plugin_.get());
cursor_handler_ =
std::make_unique<CursorHandler>(messenger_wrapper_.get(), this);
platform_handler_ =
std::make_unique<PlatformHandler>(messenger_wrapper_.get(), this);
settings_plugin_ = std::make_unique<SettingsPlugin>(messenger_wrapper_.get(),
task_runner_.get());
}
FlutterWindowsEngine::~FlutterWindowsEngine() {
messenger_->SetEngine(nullptr);
Stop();
}
void FlutterWindowsEngine::SetSwitches(
const std::vector<std::string>& switches) {
project_->SetSwitches(switches);
}
bool FlutterWindowsEngine::Run() {
return Run("");
}
bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
if (!project_->HasValidPaths()) {
FML_LOG(ERROR) << "Missing or unresolvable paths to assets.";
return false;
}
std::string assets_path_string = project_->assets_path().u8string();
std::string icu_path_string = project_->icu_path().u8string();
if (embedder_api_.RunsAOTCompiledDartCode()) {
aot_data_ = project_->LoadAotData(embedder_api_);
if (!aot_data_) {
FML_LOG(ERROR) << "Unable to start engine without AOT data.";
return false;
}
}
// FlutterProjectArgs is expecting a full argv, so when processing it for
// flags the first item is treated as the executable and ignored. Add a dummy
// value so that all provided arguments are used.
std::string executable_name = GetExecutableName();
std::vector<const char*> argv = {executable_name.c_str()};
std::vector<std::string> switches = project_->GetSwitches();
std::transform(
switches.begin(), switches.end(), std::back_inserter(argv),
[](const std::string& arg) -> const char* { return arg.c_str(); });
const std::vector<std::string>& entrypoint_args =
project_->dart_entrypoint_arguments();
std::vector<const char*> entrypoint_argv;
std::transform(
entrypoint_args.begin(), entrypoint_args.end(),
std::back_inserter(entrypoint_argv),
[](const std::string& arg) -> const char* { return arg.c_str(); });
// Configure task runners.
FlutterTaskRunnerDescription platform_task_runner = {};
platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription);
platform_task_runner.user_data = task_runner_.get();
platform_task_runner.runs_task_on_current_thread_callback =
[](void* user_data) -> bool {
return static_cast<TaskRunner*>(user_data)->RunsTasksOnCurrentThread();
};
platform_task_runner.post_task_callback = [](FlutterTask task,
uint64_t target_time_nanos,
void* user_data) -> void {
static_cast<TaskRunner*>(user_data)->PostFlutterTask(task,
target_time_nanos);
};
FlutterCustomTaskRunners custom_task_runners = {};
custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
custom_task_runners.platform_task_runner = &platform_task_runner;
custom_task_runners.thread_priority_setter =
&WindowsPlatformThreadPrioritySetter;
FlutterProjectArgs args = {};
args.struct_size = sizeof(FlutterProjectArgs);
args.shutdown_dart_vm_when_done = true;
args.assets_path = assets_path_string.c_str();
args.icu_data_path = icu_path_string.c_str();
args.command_line_argc = static_cast<int>(argv.size());
args.command_line_argv = argv.empty() ? nullptr : argv.data();
// Fail if conflicting non-default entrypoints are specified in the method
// argument and the project.
//
// TODO(cbracken): https://github.com/flutter/flutter/issues/109285
// The entrypoint method parameter should eventually be removed from this
// method and only the entrypoint specified in project_ should be used.
if (!project_->dart_entrypoint().empty() && !entrypoint.empty() &&
project_->dart_entrypoint() != entrypoint) {
FML_LOG(ERROR) << "Conflicting entrypoints were specified in "
"FlutterDesktopEngineProperties.dart_entrypoint and "
"FlutterDesktopEngineRun(engine, entry_point). ";
return false;
}
if (!entrypoint.empty()) {
args.custom_dart_entrypoint = entrypoint.data();
} else if (!project_->dart_entrypoint().empty()) {
args.custom_dart_entrypoint = project_->dart_entrypoint().c_str();
}
args.dart_entrypoint_argc = static_cast<int>(entrypoint_argv.size());
args.dart_entrypoint_argv =
entrypoint_argv.empty() ? nullptr : entrypoint_argv.data();
args.platform_message_callback =
[](const FlutterPlatformMessage* engine_message,
void* user_data) -> void {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
return host->HandlePlatformMessage(engine_message);
};
args.vsync_callback = [](void* user_data, intptr_t baton) -> void {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
host->OnVsync(baton);
};
args.on_pre_engine_restart_callback = [](void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
host->OnPreEngineRestart();
};
args.update_semantics_callback2 = [](const FlutterSemanticsUpdate2* update,
void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
// TODO(loicsharma): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
auto view = host->view(kImplicitViewId);
if (!view) {
return;
}
auto accessibility_bridge = view->accessibility_bridge().lock();
if (!accessibility_bridge) {
return;
}
for (size_t i = 0; i < update->node_count; i++) {
const FlutterSemanticsNode2* node = update->nodes[i];
accessibility_bridge->AddFlutterSemanticsNodeUpdate(*node);
}
for (size_t i = 0; i < update->custom_action_count; i++) {
const FlutterSemanticsCustomAction2* action = update->custom_actions[i];
accessibility_bridge->AddFlutterSemanticsCustomActionUpdate(*action);
}
accessibility_bridge->CommitUpdates();
};
args.root_isolate_create_callback = [](void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (host->root_isolate_create_callback_) {
host->root_isolate_create_callback_();
}
};
args.channel_update_callback = [](const FlutterChannelUpdate* update,
void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (SAFE_ACCESS(update, channel, nullptr) != nullptr) {
std::string channel_name(update->channel);
host->OnChannelUpdate(std::move(channel_name),
SAFE_ACCESS(update, listening, false));
}
};
args.custom_task_runners = &custom_task_runners;
if (!platform_view_plugin_) {
platform_view_plugin_ = std::make_unique<PlatformViewPlugin>(
messenger_wrapper_.get(), task_runner_.get());
}
if (egl_manager_) {
auto resolver = [](const char* name) -> void* {
return reinterpret_cast<void*>(::eglGetProcAddress(name));
};
// TODO(schectman) Pass the platform view manager to the compositor
// constructors: https://github.com/flutter/flutter/issues/143375
compositor_ = std::make_unique<CompositorOpenGL>(this, resolver);
} else {
compositor_ = std::make_unique<CompositorSoftware>();
}
FlutterCompositor compositor = {};
compositor.struct_size = sizeof(FlutterCompositor);
compositor.user_data = this;
compositor.create_backing_store_callback =
[](const FlutterBackingStoreConfig* config,
FlutterBackingStore* backing_store_out, void* user_data) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
return host->compositor_->CreateBackingStore(*config, backing_store_out);
};
compositor.collect_backing_store_callback =
[](const FlutterBackingStore* backing_store, void* user_data) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
return host->compositor_->CollectBackingStore(backing_store);
};
compositor.present_view_callback =
[](const FlutterPresentViewInfo* info) -> bool {
auto host = static_cast<FlutterWindowsEngine*>(info->user_data);
return host->Present(info);
};
args.compositor = &compositor;
if (aot_data_) {
args.aot_data = aot_data_.get();
}
// The platform thread creates OpenGL contexts. These
// must be released to be used by the engine's threads.
FML_DCHECK(!egl_manager_ || !egl_manager_->HasContextCurrent());
FlutterRendererConfig renderer_config;
if (enable_impeller_) {
// Impeller does not support a Software backend. Avoid falling back and
// confusing the engine on which renderer is selected.
if (!egl_manager_) {
FML_LOG(ERROR) << "Could not create surface manager. Impeller backend "
"does not support software rendering.";
return false;
}
renderer_config = GetOpenGLRendererConfig();
} else {
renderer_config =
egl_manager_ ? GetOpenGLRendererConfig() : GetSoftwareRendererConfig();
}
auto result = embedder_api_.Run(FLUTTER_ENGINE_VERSION, &renderer_config,
&args, this, &engine_);
if (result != kSuccess || engine_ == nullptr) {
FML_LOG(ERROR) << "Failed to start Flutter engine: error " << result;
return false;
}
// Configure device frame rate displayed via devtools.
FlutterEngineDisplay display = {};
display.struct_size = sizeof(FlutterEngineDisplay);
display.display_id = 0;
display.single_display = true;
display.refresh_rate =
1.0 / (static_cast<double>(FrameInterval().count()) / 1000000000.0);
std::vector<FlutterEngineDisplay> displays = {display};
embedder_api_.NotifyDisplayUpdate(engine_,
kFlutterEngineDisplaysUpdateTypeStartup,
displays.data(), displays.size());
SendSystemLocales();
SetLifecycleState(flutter::AppLifecycleState::kResumed);
settings_plugin_->StartWatching();
settings_plugin_->SendSettings();
InitializeKeyboard();
return true;
}
bool FlutterWindowsEngine::Stop() {
if (engine_) {
for (const auto& [callback, registrar] :
plugin_registrar_destruction_callbacks_) {
callback(registrar);
}
FlutterEngineResult result = embedder_api_.Shutdown(engine_);
engine_ = nullptr;
return (result == kSuccess);
}
return false;
}
std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
std::unique_ptr<WindowBindingHandler> window) {
auto view_id = next_view_id_;
auto view = std::make_unique<FlutterWindowsView>(
view_id, this, std::move(window), windows_proc_table_);
view->CreateRenderSurface();
next_view_id_++;
{
// Add the view to the embedder. This must happen before the engine
// is notified the view exists and starts presenting to it.
fml::UniqueLock write_lock{*views_mutex_};
FML_DCHECK(views_.find(view_id) == views_.end());
views_[view_id] = view.get();
}
if (!view->IsImplicitView()) {
FML_DCHECK(running());
struct Captures {
fml::AutoResetWaitableEvent latch;
bool added;
};
Captures captures = {};
FlutterWindowMetricsEvent metrics = view->CreateWindowMetricsEvent();
FlutterAddViewInfo info = {};
info.struct_size = sizeof(FlutterAddViewInfo);
info.view_id = view_id;
info.view_metrics = &metrics;
info.user_data = &captures;
info.add_view_callback = [](const FlutterAddViewResult* result) {
Captures* captures = reinterpret_cast<Captures*>(result->user_data);
captures->added = result->added;
captures->latch.Signal();
};
FlutterEngineResult result = embedder_api_.AddView(engine_, &info);
if (result != kSuccess) {
FML_LOG(ERROR)
<< "Starting the add view operation failed. FlutterEngineAddView "
"returned an unexpected result: "
<< result << ". This indicates a bug in the Windows embedder.";
FML_DCHECK(false);
return nullptr;
}
// Block the platform thread until the engine has added the view.
// TODO(loicsharma): This blocks the platform thread eagerly and can
// cause unnecessary delay in input processing. Instead, this should block
// lazily only when the app does an operation which needs the view.
// https://github.com/flutter/flutter/issues/146248
captures.latch.Wait();
if (!captures.added) {
// Adding the view failed. Update the embedder's state to match the
// engine's state. This is unexpected and indicates a bug in the Windows
// embedder.
FML_LOG(ERROR) << "FlutterEngineAddView failed to add view";
fml::UniqueLock write_lock{*views_mutex_};
views_.erase(view_id);
return nullptr;
}
}
return std::move(view);
}
void FlutterWindowsEngine::RemoveView(FlutterViewId view_id) {
FML_DCHECK(running());
// Notify the engine to stop rendering to the view if it isn't the implicit
// view. The engine and framework assume the implicit view always exists and
// can continue presenting.
if (view_id != kImplicitViewId) {
struct Captures {
fml::AutoResetWaitableEvent latch;
bool removed;
};
Captures captures = {};
FlutterRemoveViewInfo info = {};
info.struct_size = sizeof(FlutterRemoveViewInfo);
info.view_id = view_id;
info.user_data = &captures;
info.remove_view_callback = [](const FlutterRemoveViewResult* result) {
// This is invoked on an engine thread. If
// |FlutterRemoveViewResult.removed| is `true`, the engine guarantees the
// view won't be presented.
Captures* captures = reinterpret_cast<Captures*>(result->user_data);
captures->removed = result->removed;
captures->latch.Signal();
};
FlutterEngineResult result = embedder_api_.RemoveView(engine_, &info);
if (result != kSuccess) {
FML_LOG(ERROR) << "Starting the remove view operation failed. "
"FlutterEngineRemoveView "
"returned an unexpected result: "
<< result
<< ". This indicates a bug in the Windows embedder.";
FML_DCHECK(false);
return;
}
// Block the platform thread until the engine has removed the view.
// TODO(loicsharma): This blocks the platform thread eagerly and can
// cause unnecessary delay in input processing. Instead, this should block
// lazily only when an operation needs the view.
// https://github.com/flutter/flutter/issues/146248
captures.latch.Wait();
if (!captures.removed) {
// Removing the view failed. This is unexpected and indicates a bug in the
// Windows embedder.
FML_LOG(ERROR) << "FlutterEngineRemoveView failed to remove view";
return;
}
}
{
// The engine no longer presents to the view. Remove the view from the
// embedder.
fml::UniqueLock write_lock{*views_mutex_};
FML_DCHECK(views_.find(view_id) != views_.end());
views_.erase(view_id);
}
}
void FlutterWindowsEngine::OnVsync(intptr_t baton) {
std::chrono::nanoseconds current_time =
std::chrono::nanoseconds(embedder_api_.GetCurrentTime());
std::chrono::nanoseconds frame_interval = FrameInterval();
auto next = SnapToNextTick(current_time, start_time_, frame_interval);
embedder_api_.OnVsync(engine_, baton, next.count(),
(next + frame_interval).count());
}
std::chrono::nanoseconds FlutterWindowsEngine::FrameInterval() {
if (frame_interval_override_.has_value()) {
return frame_interval_override_.value();
}
uint64_t interval = 16600000;
DWM_TIMING_INFO timing_info = {};
timing_info.cbSize = sizeof(timing_info);
HRESULT result = DwmGetCompositionTimingInfo(NULL, &timing_info);
if (result == S_OK && timing_info.rateRefresh.uiDenominator > 0 &&
timing_info.rateRefresh.uiNumerator > 0) {
interval = static_cast<double>(timing_info.rateRefresh.uiDenominator *
1000000000.0) /
static_cast<double>(timing_info.rateRefresh.uiNumerator);
}
return std::chrono::nanoseconds(interval);
}
FlutterWindowsView* FlutterWindowsEngine::view(FlutterViewId view_id) const {
fml::SharedLock read_lock{*views_mutex_};
auto iterator = views_.find(view_id);
if (iterator == views_.end()) {
return nullptr;
}
return iterator->second;
}
// Returns the currently configured Plugin Registrar.
FlutterDesktopPluginRegistrarRef FlutterWindowsEngine::GetRegistrar() {
return plugin_registrar_.get();
}
void FlutterWindowsEngine::AddPluginRegistrarDestructionCallback(
FlutterDesktopOnPluginRegistrarDestroyed callback,
FlutterDesktopPluginRegistrarRef registrar) {
plugin_registrar_destruction_callbacks_[callback] = registrar;
}
void FlutterWindowsEngine::SendWindowMetricsEvent(
const FlutterWindowMetricsEvent& event) {
if (engine_) {
embedder_api_.SendWindowMetricsEvent(engine_, &event);
}
}
void FlutterWindowsEngine::SendPointerEvent(const FlutterPointerEvent& event) {
if (engine_) {
embedder_api_.SendPointerEvent(engine_, &event, 1);
}
}
void FlutterWindowsEngine::SendKeyEvent(const FlutterKeyEvent& event,
FlutterKeyEventCallback callback,
void* user_data) {
if (engine_) {
embedder_api_.SendKeyEvent(engine_, &event, callback, user_data);
}
}
bool FlutterWindowsEngine::SendPlatformMessage(
const char* channel,
const uint8_t* message,
const size_t message_size,
const FlutterDesktopBinaryReply reply,
void* user_data) {
FlutterPlatformMessageResponseHandle* response_handle = nullptr;
if (reply != nullptr && user_data != nullptr) {
FlutterEngineResult result =
embedder_api_.PlatformMessageCreateResponseHandle(
engine_, reply, user_data, &response_handle);
if (result != kSuccess) {
FML_LOG(ERROR) << "Failed to create response handle";
return false;
}
}
FlutterPlatformMessage platform_message = {
sizeof(FlutterPlatformMessage),
channel,
message,
message_size,
response_handle,
};
FlutterEngineResult message_result =
embedder_api_.SendPlatformMessage(engine_, &platform_message);
if (response_handle != nullptr) {
embedder_api_.PlatformMessageReleaseResponseHandle(engine_,
response_handle);
}
return message_result == kSuccess;
}
void FlutterWindowsEngine::SendPlatformMessageResponse(
const FlutterDesktopMessageResponseHandle* handle,
const uint8_t* data,
size_t data_length) {
embedder_api_.SendPlatformMessageResponse(engine_, handle, data, data_length);
}
void FlutterWindowsEngine::HandlePlatformMessage(
const FlutterPlatformMessage* engine_message) {
if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) {
FML_LOG(ERROR) << "Invalid message size received. Expected: "
<< sizeof(FlutterPlatformMessage) << " but received "
<< engine_message->struct_size;
return;
}
auto message = ConvertToDesktopMessage(*engine_message);
message_dispatcher_->HandleMessage(message, [this] {}, [this] {});
}
void FlutterWindowsEngine::ReloadSystemFonts() {
embedder_api_.ReloadSystemFonts(engine_);
}
void FlutterWindowsEngine::ScheduleFrame() {
embedder_api_.ScheduleFrame(engine_);
}
void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {
next_frame_callback_ = std::move(callback);
embedder_api_.SetNextFrameCallback(
engine_,
[](void* user_data) {
// Embedder callback runs on raster thread. Switch back to platform
// thread.
FlutterWindowsEngine* self =
static_cast<FlutterWindowsEngine*>(user_data);
self->task_runner_->PostTask(std::move(self->next_frame_callback_));
},
this);
}
void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) {
if (lifecycle_manager_) {
lifecycle_manager_->SetLifecycleState(state);
}
}
void FlutterWindowsEngine::SendSystemLocales() {
std::vector<LanguageInfo> languages =
GetPreferredLanguageInfo(*windows_proc_table_);
std::vector<FlutterLocale> flutter_locales;
flutter_locales.reserve(languages.size());
for (const auto& info : languages) {
flutter_locales.push_back(CovertToFlutterLocale(info));
}
// Convert the locale list to the locale pointer list that must be provided.
std::vector<const FlutterLocale*> flutter_locale_list;
flutter_locale_list.reserve(flutter_locales.size());
std::transform(flutter_locales.begin(), flutter_locales.end(),
std::back_inserter(flutter_locale_list),
[](const auto& arg) -> const auto* { return &arg; });
embedder_api_.UpdateLocales(engine_, flutter_locale_list.data(),
flutter_locale_list.size());
}
void FlutterWindowsEngine::InitializeKeyboard() {
auto internal_plugin_messenger = internal_plugin_registrar_->messenger();
KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState;
KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan =
[](UINT virtual_key, bool extended) {
return MapVirtualKey(virtual_key,
extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC);
};
keyboard_key_handler_ = std::move(CreateKeyboardKeyHandler(
internal_plugin_messenger, get_key_state, map_vk_to_scan));
text_input_plugin_ =
std::move(CreateTextInputPlugin(internal_plugin_messenger));
}
std::unique_ptr<KeyboardHandlerBase>
FlutterWindowsEngine::CreateKeyboardKeyHandler(
BinaryMessenger* messenger,
KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state,
KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) {
auto keyboard_key_handler = std::make_unique<KeyboardKeyHandler>(messenger);
keyboard_key_handler->AddDelegate(
std::make_unique<KeyboardKeyEmbedderHandler>(
[this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* user_data) {
return SendKeyEvent(event, callback, user_data);
},
get_key_state, map_vk_to_scan));
keyboard_key_handler->AddDelegate(
std::make_unique<KeyboardKeyChannelHandler>(messenger));
keyboard_key_handler->InitKeyboardChannel();
return keyboard_key_handler;
}
std::unique_ptr<TextInputPlugin> FlutterWindowsEngine::CreateTextInputPlugin(
BinaryMessenger* messenger) {
return std::make_unique<TextInputPlugin>(messenger, this);
}
bool FlutterWindowsEngine::RegisterExternalTexture(int64_t texture_id) {
return (embedder_api_.RegisterExternalTexture(engine_, texture_id) ==
kSuccess);
}
bool FlutterWindowsEngine::UnregisterExternalTexture(int64_t texture_id) {
return (embedder_api_.UnregisterExternalTexture(engine_, texture_id) ==
kSuccess);
}
bool FlutterWindowsEngine::MarkExternalTextureFrameAvailable(
int64_t texture_id) {
return (embedder_api_.MarkExternalTextureFrameAvailable(
engine_, texture_id) == kSuccess);
}
bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {
struct Captures {
fml::closure callback;
};
auto captures = new Captures();
captures->callback = std::move(callback);
if (embedder_api_.PostRenderThreadTask(
engine_,
[](void* opaque) {
auto captures = reinterpret_cast<Captures*>(opaque);
captures->callback();
delete captures;
},
captures) == kSuccess) {
return true;
}
delete captures;
return false;
}
bool FlutterWindowsEngine::DispatchSemanticsAction(
uint64_t target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
return (embedder_api_.DispatchSemanticsAction(engine_, target, action,
data.GetMapping(),
data.GetSize()) == kSuccess);
}
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
if (engine_ && semantics_enabled_ != enabled) {
fml::SharedLock read_lock{*views_mutex_};
semantics_enabled_ = enabled;
embedder_api_.UpdateSemanticsEnabled(engine_, enabled);
for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
iterator->second->UpdateSemanticsEnabled(enabled);
}
}
}
void FlutterWindowsEngine::OnPreEngineRestart() {
// Reset the keyboard's state on hot restart.
InitializeKeyboard();
}
std::string FlutterWindowsEngine::GetExecutableName() const {
std::pair<bool, std::string> result = fml::paths::GetExecutablePath();
if (result.first) {
const std::string& executable_path = result.second;
size_t last_separator = executable_path.find_last_of("/\\");
if (last_separator == std::string::npos ||
last_separator == executable_path.size() - 1) {
return executable_path;
}
return executable_path.substr(last_separator + 1);
}
return "Flutter";
}
void FlutterWindowsEngine::UpdateAccessibilityFeatures() {
UpdateHighContrastMode();
}
void FlutterWindowsEngine::UpdateHighContrastMode() {
high_contrast_enabled_ = windows_proc_table_->GetHighContrastEnabled();
SendAccessibilityFeatures();
settings_plugin_->UpdateHighContrastMode(high_contrast_enabled_);
}
void FlutterWindowsEngine::SendAccessibilityFeatures() {
int flags = 0;
if (high_contrast_enabled_) {
flags |=
FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast;
}
embedder_api_.UpdateAccessibilityFeatures(
engine_, static_cast<FlutterAccessibilityFeature>(flags));
}
void FlutterWindowsEngine::RequestApplicationQuit(HWND hwnd,
WPARAM wparam,
LPARAM lparam,
AppExitType exit_type) {
platform_handler_->RequestAppExit(hwnd, wparam, lparam, exit_type, 0);
}
void FlutterWindowsEngine::OnQuit(std::optional<HWND> hwnd,
std::optional<WPARAM> wparam,
std::optional<LPARAM> lparam,
UINT exit_code) {
lifecycle_manager_->Quit(hwnd, wparam, lparam, exit_code);
}
void FlutterWindowsEngine::OnDwmCompositionChanged() {
fml::SharedLock read_lock{*views_mutex_};
for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
iterator->second->OnDwmCompositionChanged();
}
}
void FlutterWindowsEngine::OnWindowStateEvent(HWND hwnd,
WindowStateEvent event) {
lifecycle_manager_->OnWindowStateEvent(hwnd, event);
}
std::optional<LRESULT> FlutterWindowsEngine::ProcessExternalWindowMessage(
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
if (lifecycle_manager_) {
return lifecycle_manager_->ExternalWindowMessage(hwnd, message, wparam,
lparam);
}
return std::nullopt;
}
void FlutterWindowsEngine::OnChannelUpdate(std::string name, bool listening) {
if (name == "flutter/platform" && listening) {
lifecycle_manager_->BeginProcessingExit();
} else if (name == "flutter/lifecycle" && listening) {
lifecycle_manager_->BeginProcessingLifecycle();
}
}
bool FlutterWindowsEngine::Present(const FlutterPresentViewInfo* info) {
// This runs on the raster thread. Lock the views map for the entirety of the
// present operation to block the platform thread from destroying the
// view during the present.
fml::SharedLock read_lock{*views_mutex_};
auto iterator = views_.find(info->view_id);
if (iterator == views_.end()) {
return false;
}
FlutterWindowsView* view = iterator->second;
return compositor_->Present(view, info->layers, info->layers_count);
}
} // namespace flutter