| // 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 <filesystem> |
| #include <iostream> |
| #include <sstream> |
| |
| #include "flutter/fml/platform/win/wstring_conversion.h" |
| #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" |
| #include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" |
| #include "flutter/shell/platform/common/json_message_codec.h" |
| #include "flutter/shell/platform/common/path_utils.h" |
| #include "flutter/shell/platform/windows/flutter_windows_view.h" |
| #include "flutter/shell/platform/windows/system_utils.h" |
| #include "flutter/shell/platform/windows/task_runner.h" |
| #include "third_party/rapidjson/include/rapidjson/document.h" |
| |
| #if defined(WINUWP) |
| #include "flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.h" |
| #else |
| #include <dwmapi.h> |
| #include "flutter/shell/platform/windows/accessibility_bridge_delegate_win32.h" |
| #endif // defined(WINUWP) |
| |
| // winbase.h defines GetCurrentTime as a macro. |
| #undef GetCurrentTime |
| |
| 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->view()) { |
| return false; |
| } |
| return host->view()->MakeCurrent(); |
| }; |
| config.open_gl.clear_current = [](void* user_data) -> bool { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (!host->view()) { |
| return false; |
| } |
| return host->view()->ClearContext(); |
| }; |
| config.open_gl.present = [](void* user_data) -> bool { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (!host->view()) { |
| return false; |
| } |
| return host->view()->SwapBuffers(); |
| }; |
| config.open_gl.fbo_reset_after_present = true; |
| config.open_gl.fbo_with_frame_info_callback = |
| [](void* user_data, const FlutterFrameInfo* info) -> uint32_t { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (host->view()) { |
| return host->view()->GetFrameBufferId(info->size.width, |
| info->size.height); |
| } else { |
| return kWindowFrameBufferID; |
| } |
| }; |
| 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->view()) { |
| return false; |
| } |
| return host->view()->MakeResourceCurrent(); |
| }; |
| 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) { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (!host->view()) { |
| return false; |
| } |
| return host->view()->PresentSoftwareBitmap(allocation, row_bytes, height); |
| }; |
| 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) |
| : project_(std::make_unique<FlutterProjectBundle>(project)), |
| aot_data_(nullptr, nullptr) { |
| embedder_api_.struct_size = sizeof(FlutterEngineProcTable); |
| FlutterEngineGetProcAddresses(&embedder_api_); |
| |
| task_runner_ = TaskRunner::Create( |
| embedder_api_.GetCurrentTime, [this](const auto* task) { |
| if (!engine_) { |
| std::cerr << "Cannot post an engine task when engine is not running." |
| << std::endl; |
| return; |
| } |
| if (embedder_api_.RunTask(engine_, task) != kSuccess) { |
| std::cerr << "Failed to post an engine task." << std::endl; |
| } |
| }); |
| |
| // Set up the legacy structs backing the API handles. |
| messenger_ = std::make_unique<FlutterDesktopMessenger>(); |
| messenger_->engine = this; |
| plugin_registrar_ = std::make_unique<FlutterDesktopPluginRegistrar>(); |
| plugin_registrar_->engine = this; |
| |
| messenger_wrapper_ = std::make_unique<BinaryMessengerImpl>(messenger_.get()); |
| message_dispatcher_ = |
| std::make_unique<IncomingMessageDispatcher>(messenger_.get()); |
| |
| FlutterWindowsTextureRegistrar::ResolveGlFunctions(gl_procs_); |
| texture_registrar_ = |
| std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_procs_); |
| surface_manager_ = AngleSurfaceManager::Create(); |
| #ifndef WINUWP |
| window_proc_delegate_manager_ = |
| std::make_unique<WindowProcDelegateManagerWin32>(); |
| #endif |
| |
| // Set up internal channels. |
| // TODO: Replace this with an embedder.h API. See |
| // https://github.com/flutter/flutter/issues/71099 |
| settings_channel_ = |
| std::make_unique<BasicMessageChannel<rapidjson::Document>>( |
| messenger_wrapper_.get(), "flutter/settings", |
| &JsonMessageCodec::GetInstance()); |
| } |
| |
| FlutterWindowsEngine::~FlutterWindowsEngine() { |
| Stop(); |
| } |
| |
| void FlutterWindowsEngine::SetSwitches( |
| const std::vector<std::string>& switches) { |
| project_->SetSwitches(switches); |
| } |
| |
| bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { |
| if (!project_->HasValidPaths()) { |
| std::cerr << "Missing or unresolvable paths to assets." << std::endl; |
| 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_) { |
| std::cerr << "Unable to start engine without AOT data." << std::endl; |
| 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::vector<std::string> switches = project_->GetSwitches(); |
| std::vector<const char*> argv = {"placeholder"}; |
| 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.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.size() > 0 ? argv.data() : nullptr; |
| args.dart_entrypoint_argc = static_cast<int>(entrypoint_argv.size()); |
| args.dart_entrypoint_argv = |
| entrypoint_argv.size() > 0 ? entrypoint_argv.data() : nullptr; |
| 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->view()->OnPreEngineRestart(); |
| }; |
| args.update_semantics_node_callback = [](const FlutterSemanticsNode* node, |
| void* user_data) { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (!node || node->id == kFlutterSemanticsNodeIdBatchEnd) { |
| host->accessibility_bridge_->CommitUpdates(); |
| return; |
| } |
| host->accessibility_bridge_->AddFlutterSemanticsNodeUpdate(node); |
| }; |
| args.update_semantics_custom_action_callback = |
| [](const FlutterSemanticsCustomAction* action, void* user_data) { |
| auto host = static_cast<FlutterWindowsEngine*>(user_data); |
| if (!action || action->id == kFlutterSemanticsNodeIdBatchEnd) { |
| host->accessibility_bridge_->CommitUpdates(); |
| return; |
| } |
| host->accessibility_bridge_->AddFlutterSemanticsCustomActionUpdate( |
| action); |
| }; |
| |
| args.custom_task_runners = &custom_task_runners; |
| |
| if (aot_data_) { |
| args.aot_data = aot_data_.get(); |
| } |
| if (entrypoint) { |
| args.custom_dart_entrypoint = entrypoint; |
| } |
| |
| FlutterRendererConfig renderer_config = surface_manager_ |
| ? GetOpenGLRendererConfig() |
| : GetSoftwareRendererConfig(); |
| |
| auto result = embedder_api_.Run(FLUTTER_ENGINE_VERSION, &renderer_config, |
| &args, this, &engine_); |
| if (result != kSuccess || engine_ == nullptr) { |
| std::cerr << "Failed to start Flutter engine: error " << result |
| << std::endl; |
| 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()); |
| |
| SendSystemSettings(); |
| |
| 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; |
| } |
| |
| void FlutterWindowsEngine::SetView(FlutterWindowsView* view) { |
| view_ = view; |
| } |
| |
| 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; |
| |
| #ifndef WINUWP |
| 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); |
| } |
| #endif |
| |
| return std::chrono::nanoseconds(interval); |
| } |
| |
| // 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) { |
| std::cout << "Failed to create response handle\n"; |
| 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)) { |
| std::cerr << "Invalid message size received. Expected: " |
| << sizeof(FlutterPlatformMessage) << " but received " |
| << engine_message->struct_size << std::endl; |
| return; |
| } |
| |
| auto message = ConvertToDesktopMessage(*engine_message); |
| |
| message_dispatcher_->HandleMessage( |
| message, [this] {}, [this] {}); |
| } |
| |
| void FlutterWindowsEngine::ReloadSystemFonts() { |
| embedder_api_.ReloadSystemFonts(engine_); |
| } |
| |
| void FlutterWindowsEngine::ReloadPlatformBrightness() { |
| if (engine_) { |
| SendSystemSettings(); |
| } |
| } |
| |
| void FlutterWindowsEngine::SendSystemSettings() { |
| std::vector<LanguageInfo> languages = GetPreferredLanguageInfo(); |
| 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()); |
| |
| rapidjson::Document settings(rapidjson::kObjectType); |
| auto& allocator = settings.GetAllocator(); |
| settings.AddMember("alwaysUse24HourFormat", |
| Prefer24HourTime(GetUserTimeFormat()), allocator); |
| settings.AddMember("textScaleFactor", 1.0, allocator); |
| settings.AddMember("platformBrightness", |
| fml::WideStringToUtf8(GetPreferredBrightness()), |
| allocator); |
| settings_channel_->Send(settings); |
| } |
| |
| 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::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 defined(WINUWP) |
| using AccessibilityBridgeDelegateWindows = AccessibilityBridgeDelegateWinUWP; |
| #else |
| using AccessibilityBridgeDelegateWindows = AccessibilityBridgeDelegateWin32; |
| #endif // defined(WINUWP) |
| |
| if (engine_ && semantics_enabled_ != enabled) { |
| semantics_enabled_ = enabled; |
| embedder_api_.UpdateSemanticsEnabled(engine_, enabled); |
| |
| if (!semantics_enabled_ && accessibility_bridge_) { |
| accessibility_bridge_.reset(); |
| } else if (semantics_enabled_ && !accessibility_bridge_) { |
| accessibility_bridge_ = std::make_shared<AccessibilityBridge>( |
| std::make_unique<AccessibilityBridgeDelegateWindows>(this)); |
| } |
| } |
| } |
| |
| gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId( |
| AccessibilityNodeId id) { |
| if (!accessibility_bridge_) { |
| return nullptr; |
| } |
| std::shared_ptr<FlutterPlatformNodeDelegate> node_delegate = |
| accessibility_bridge_->GetFlutterPlatformNodeDelegateFromID(id).lock(); |
| if (!node_delegate) { |
| return nullptr; |
| } |
| return node_delegate->GetNativeViewAccessible(); |
| } |
| |
| } // namespace flutter |