Flutter Windows Embedder
flutter_windows_engine.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <dwmapi.h>
8 
9 #include <filesystem>
10 #include <shared_mutex>
11 #include <sstream>
12 
13 #include "flutter/fml/logging.h"
14 #include "flutter/fml/paths.h"
15 #include "flutter/fml/platform/win/wstring_conversion.h"
16 #include "flutter/fml/synchronization/waitable_event.h"
20 #include "flutter/shell/platform/embedder/embedder_struct_macros.h"
28 #include "flutter/third_party/accessibility/ax/ax_node.h"
30 
31 // winbase.h defines GetCurrentTime as a macro.
32 #undef GetCurrentTime
33 
34 static constexpr char kAccessibilityChannelName[] = "flutter/accessibility";
35 
36 namespace flutter {
37 
38 namespace {
39 
40 // Lifted from vsync_waiter_fallback.cc
41 static std::chrono::nanoseconds SnapToNextTick(
42  std::chrono::nanoseconds value,
43  std::chrono::nanoseconds tick_phase,
44  std::chrono::nanoseconds tick_interval) {
45  std::chrono::nanoseconds offset = (tick_phase - value) % tick_interval;
46  if (offset != std::chrono::nanoseconds::zero())
47  offset = offset + tick_interval;
48  return value + offset;
49 }
50 
51 // Creates and returns a FlutterRendererConfig that renders to the view (if any)
52 // of a FlutterWindowsEngine, using OpenGL (via ANGLE).
53 // The user_data received by the render callbacks refers to the
54 // FlutterWindowsEngine.
55 FlutterRendererConfig GetOpenGLRendererConfig() {
56  FlutterRendererConfig config = {};
57  config.type = kOpenGL;
58  config.open_gl.struct_size = sizeof(config.open_gl);
59  config.open_gl.make_current = [](void* user_data) -> bool {
60  auto host = static_cast<FlutterWindowsEngine*>(user_data);
61  if (!host->egl_manager()) {
62  return false;
63  }
64  return host->egl_manager()->render_context()->MakeCurrent();
65  };
66  config.open_gl.clear_current = [](void* user_data) -> bool {
67  auto host = static_cast<FlutterWindowsEngine*>(user_data);
68  if (!host->egl_manager()) {
69  return false;
70  }
71  return host->egl_manager()->render_context()->ClearCurrent();
72  };
73  config.open_gl.present = [](void* user_data) -> bool { FML_UNREACHABLE(); };
74  config.open_gl.fbo_reset_after_present = true;
75  config.open_gl.fbo_with_frame_info_callback =
76  [](void* user_data, const FlutterFrameInfo* info) -> uint32_t {
77  FML_UNREACHABLE();
78  };
79  config.open_gl.gl_proc_resolver = [](void* user_data,
80  const char* what) -> void* {
81  return reinterpret_cast<void*>(eglGetProcAddress(what));
82  };
83  config.open_gl.make_resource_current = [](void* user_data) -> bool {
84  auto host = static_cast<FlutterWindowsEngine*>(user_data);
85  if (!host->egl_manager()) {
86  return false;
87  }
88  return host->egl_manager()->resource_context()->MakeCurrent();
89  };
90  config.open_gl.gl_external_texture_frame_callback =
91  [](void* user_data, int64_t texture_id, size_t width, size_t height,
92  FlutterOpenGLTexture* texture) -> bool {
93  auto host = static_cast<FlutterWindowsEngine*>(user_data);
94  if (!host->texture_registrar()) {
95  return false;
96  }
97  return host->texture_registrar()->PopulateTexture(texture_id, width, height,
98  texture);
99  };
100  return config;
101 }
102 
103 // Creates and returns a FlutterRendererConfig that renders to the view (if any)
104 // of a FlutterWindowsEngine, using software rasterization.
105 // The user_data received by the render callbacks refers to the
106 // FlutterWindowsEngine.
107 FlutterRendererConfig GetSoftwareRendererConfig() {
108  FlutterRendererConfig config = {};
109  config.type = kSoftware;
110  config.software.struct_size = sizeof(config.software);
111  config.software.surface_present_callback =
112  [](void* user_data, const void* allocation, size_t row_bytes,
113  size_t height) {
114  FML_UNREACHABLE();
115  return false;
116  };
117  return config;
118 }
119 
120 // Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage.
121 static FlutterDesktopMessage ConvertToDesktopMessage(
122  const FlutterPlatformMessage& engine_message) {
124  message.struct_size = sizeof(message);
125  message.channel = engine_message.channel;
126  message.message = engine_message.message;
127  message.message_size = engine_message.message_size;
128  message.response_handle = engine_message.response_handle;
129  return message;
130 }
131 
132 // Converts a LanguageInfo struct to a FlutterLocale struct. |info| must outlive
133 // the returned value, since the returned FlutterLocale has pointers into it.
134 FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) {
135  FlutterLocale locale = {};
136  locale.struct_size = sizeof(FlutterLocale);
137  locale.language_code = info.language.c_str();
138  if (!info.region.empty()) {
139  locale.country_code = info.region.c_str();
140  }
141  if (!info.script.empty()) {
142  locale.script_code = info.script.c_str();
143  }
144  return locale;
145 }
146 
147 } // namespace
148 
150  const FlutterProjectBundle& project,
151  std::shared_ptr<WindowsProcTable> windows_proc_table)
152  : project_(std::make_unique<FlutterProjectBundle>(project)),
153  windows_proc_table_(std::move(windows_proc_table)),
154  aot_data_(nullptr, nullptr),
155  lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
156  if (windows_proc_table_ == nullptr) {
157  windows_proc_table_ = std::make_shared<WindowsProcTable>();
158  }
159 
160  gl_ = egl::ProcTable::Create();
161 
162  embedder_api_.struct_size = sizeof(FlutterEngineProcTable);
163  FlutterEngineGetProcAddresses(&embedder_api_);
164 
165  task_runner_ =
166  std::make_unique<TaskRunner>(
167  embedder_api_.GetCurrentTime, [this](const auto* task) {
168  if (!engine_) {
169  FML_LOG(ERROR)
170  << "Cannot post an engine task when engine is not running.";
171  return;
172  }
173  if (embedder_api_.RunTask(engine_, task) != kSuccess) {
174  FML_LOG(ERROR) << "Failed to post an engine task.";
175  }
176  });
177 
178  // Set up the legacy structs backing the API handles.
179  messenger_ =
180  fml::RefPtr<FlutterDesktopMessenger>(new FlutterDesktopMessenger());
181  messenger_->SetEngine(this);
182  plugin_registrar_ = std::make_unique<FlutterDesktopPluginRegistrar>();
183  plugin_registrar_->engine = this;
184 
185  messenger_wrapper_ =
186  std::make_unique<BinaryMessengerImpl>(messenger_->ToRef());
187  message_dispatcher_ =
188  std::make_unique<IncomingMessageDispatcher>(messenger_->ToRef());
189 
190  texture_registrar_ =
191  std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_);
192 
193  // Check for impeller support.
194  auto& switches = project_->GetSwitches();
195  enable_impeller_ = std::find(switches.begin(), switches.end(),
196  "--enable-impeller=true") != switches.end();
197 
198  egl_manager_ = egl::Manager::Create(
199  static_cast<egl::GpuPreference>(project_->gpu_preference()));
200  window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();
201  window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(
202  [](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,
203  LRESULT* result) {
204  BASE_DCHECK(user_data);
205  FlutterWindowsEngine* that =
206  static_cast<FlutterWindowsEngine*>(user_data);
207  BASE_DCHECK(that->lifecycle_manager_);
208  return that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar,
209  result);
210  },
211  static_cast<void*>(this));
212 
213  // Set up internal channels.
214  // TODO: Replace this with an embedder.h API. See
215  // https://github.com/flutter/flutter/issues/71099
216  internal_plugin_registrar_ =
217  std::make_unique<PluginRegistrar>(plugin_registrar_.get());
218 
219  accessibility_plugin_ = std::make_unique<AccessibilityPlugin>(this);
220  AccessibilityPlugin::SetUp(messenger_wrapper_.get(),
221  accessibility_plugin_.get());
222 
223  cursor_handler_ =
224  std::make_unique<CursorHandler>(messenger_wrapper_.get(), this);
225  platform_handler_ =
226  std::make_unique<PlatformHandler>(messenger_wrapper_.get(), this);
227  settings_plugin_ = std::make_unique<SettingsPlugin>(messenger_wrapper_.get(),
228  task_runner_.get());
229 }
230 
231 FlutterWindowsEngine::~FlutterWindowsEngine() {
232  messenger_->SetEngine(nullptr);
233  Stop();
234 }
235 
236 FlutterWindowsEngine* FlutterWindowsEngine::GetEngineForId(int64_t engine_id) {
237  return reinterpret_cast<FlutterWindowsEngine*>(engine_id);
238 }
239 
240 void FlutterWindowsEngine::SetSwitches(
241  const std::vector<std::string>& switches) {
242  project_->SetSwitches(switches);
243 }
244 
245 bool FlutterWindowsEngine::Run() {
246  return Run("");
247 }
248 
249 bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
250  if (!project_->HasValidPaths()) {
251  FML_LOG(ERROR) << "Missing or unresolvable paths to assets.";
252  return false;
253  }
254  std::string assets_path_string = project_->assets_path().u8string();
255  std::string icu_path_string = project_->icu_path().u8string();
256  if (embedder_api_.RunsAOTCompiledDartCode()) {
257  aot_data_ = project_->LoadAotData(embedder_api_);
258  if (!aot_data_) {
259  FML_LOG(ERROR) << "Unable to start engine without AOT data.";
260  return false;
261  }
262  }
263 
264  // FlutterProjectArgs is expecting a full argv, so when processing it for
265  // flags the first item is treated as the executable and ignored. Add a dummy
266  // value so that all provided arguments are used.
267  std::string executable_name = GetExecutableName();
268  std::vector<const char*> argv = {executable_name.c_str()};
269  std::vector<std::string> switches = project_->GetSwitches();
270  std::transform(
271  switches.begin(), switches.end(), std::back_inserter(argv),
272  [](const std::string& arg) -> const char* { return arg.c_str(); });
273 
274  const std::vector<std::string>& entrypoint_args =
275  project_->dart_entrypoint_arguments();
276  std::vector<const char*> entrypoint_argv;
277  std::transform(
278  entrypoint_args.begin(), entrypoint_args.end(),
279  std::back_inserter(entrypoint_argv),
280  [](const std::string& arg) -> const char* { return arg.c_str(); });
281 
282  // Configure task runners.
283  FlutterTaskRunnerDescription platform_task_runner = {};
284  platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription);
285  platform_task_runner.user_data = task_runner_.get();
286  platform_task_runner.runs_task_on_current_thread_callback =
287  [](void* user_data) -> bool {
288  return static_cast<TaskRunner*>(user_data)->RunsTasksOnCurrentThread();
289  };
290  platform_task_runner.post_task_callback = [](FlutterTask task,
291  uint64_t target_time_nanos,
292  void* user_data) -> void {
293  static_cast<TaskRunner*>(user_data)->PostFlutterTask(task,
294  target_time_nanos);
295  };
296  FlutterCustomTaskRunners custom_task_runners = {};
297  custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
298  custom_task_runners.platform_task_runner = &platform_task_runner;
299  custom_task_runners.thread_priority_setter =
301 
302  if (project_->ui_thread_policy() ==
304  FML_LOG(WARNING)
305  << "Running with merged platform and UI thread. Experimental.";
306  custom_task_runners.ui_task_runner = &platform_task_runner;
307  }
308 
309  FlutterProjectArgs args = {};
310  args.struct_size = sizeof(FlutterProjectArgs);
311  args.shutdown_dart_vm_when_done = true;
312  args.assets_path = assets_path_string.c_str();
313  args.icu_data_path = icu_path_string.c_str();
314  args.command_line_argc = static_cast<int>(argv.size());
315  args.command_line_argv = argv.empty() ? nullptr : argv.data();
316  args.engine_id = reinterpret_cast<int64_t>(this);
317 
318  // Fail if conflicting non-default entrypoints are specified in the method
319  // argument and the project.
320  //
321  // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
322  // The entrypoint method parameter should eventually be removed from this
323  // method and only the entrypoint specified in project_ should be used.
324  if (!project_->dart_entrypoint().empty() && !entrypoint.empty() &&
325  project_->dart_entrypoint() != entrypoint) {
326  FML_LOG(ERROR) << "Conflicting entrypoints were specified in "
327  "FlutterDesktopEngineProperties.dart_entrypoint and "
328  "FlutterDesktopEngineRun(engine, entry_point). ";
329  return false;
330  }
331  if (!entrypoint.empty()) {
332  args.custom_dart_entrypoint = entrypoint.data();
333  } else if (!project_->dart_entrypoint().empty()) {
334  args.custom_dart_entrypoint = project_->dart_entrypoint().c_str();
335  }
336  args.dart_entrypoint_argc = static_cast<int>(entrypoint_argv.size());
337  args.dart_entrypoint_argv =
338  entrypoint_argv.empty() ? nullptr : entrypoint_argv.data();
339  args.platform_message_callback =
340  [](const FlutterPlatformMessage* engine_message,
341  void* user_data) -> void {
342  auto host = static_cast<FlutterWindowsEngine*>(user_data);
343  return host->HandlePlatformMessage(engine_message);
344  };
345  args.vsync_callback = [](void* user_data, intptr_t baton) -> void {
346  auto host = static_cast<FlutterWindowsEngine*>(user_data);
347  host->OnVsync(baton);
348  };
349  args.on_pre_engine_restart_callback = [](void* user_data) {
350  auto host = static_cast<FlutterWindowsEngine*>(user_data);
351  host->OnPreEngineRestart();
352  };
353  args.update_semantics_callback2 = [](const FlutterSemanticsUpdate2* update,
354  void* user_data) {
355  auto host = static_cast<FlutterWindowsEngine*>(user_data);
356 
357  auto view = host->view(update->view_id);
358  if (!view) {
359  return;
360  }
361 
362  auto accessibility_bridge = view->accessibility_bridge().lock();
363  if (!accessibility_bridge) {
364  return;
365  }
366 
367  for (size_t i = 0; i < update->node_count; i++) {
368  const FlutterSemanticsNode2* node = update->nodes[i];
369  accessibility_bridge->AddFlutterSemanticsNodeUpdate(*node);
370  }
371 
372  for (size_t i = 0; i < update->custom_action_count; i++) {
373  const FlutterSemanticsCustomAction2* action = update->custom_actions[i];
374  accessibility_bridge->AddFlutterSemanticsCustomActionUpdate(*action);
375  }
376 
377  accessibility_bridge->CommitUpdates();
378  };
379  args.root_isolate_create_callback = [](void* user_data) {
380  auto host = static_cast<FlutterWindowsEngine*>(user_data);
381  if (host->root_isolate_create_callback_) {
382  host->root_isolate_create_callback_();
383  }
384  };
385  args.channel_update_callback = [](const FlutterChannelUpdate* update,
386  void* user_data) {
387  auto host = static_cast<FlutterWindowsEngine*>(user_data);
388  if (SAFE_ACCESS(update, channel, nullptr) != nullptr) {
389  std::string channel_name(update->channel);
390  host->OnChannelUpdate(std::move(channel_name),
391  SAFE_ACCESS(update, listening, false));
392  }
393  };
394  args.view_focus_change_request_callback =
395  [](const FlutterViewFocusChangeRequest* request, void* user_data) {
396  auto host = static_cast<FlutterWindowsEngine*>(user_data);
397  host->OnViewFocusChangeRequest(request);
398  };
399 
400  args.custom_task_runners = &custom_task_runners;
401 
402  if (!platform_view_plugin_) {
403  platform_view_plugin_ = std::make_unique<PlatformViewPlugin>(
404  messenger_wrapper_.get(), task_runner_.get());
405  }
406  if (egl_manager_) {
407  auto resolver = [](const char* name) -> void* {
408  return reinterpret_cast<void*>(::eglGetProcAddress(name));
409  };
410 
411  // TODO(schectman) Pass the platform view manager to the compositor
412  // constructors: https://github.com/flutter/flutter/issues/143375
413  compositor_ =
414  std::make_unique<CompositorOpenGL>(this, resolver, enable_impeller_);
415  } else {
416  compositor_ = std::make_unique<CompositorSoftware>();
417  }
418 
419  FlutterCompositor compositor = {};
420  compositor.struct_size = sizeof(FlutterCompositor);
421  compositor.user_data = this;
422  compositor.create_backing_store_callback =
423  [](const FlutterBackingStoreConfig* config,
424  FlutterBackingStore* backing_store_out, void* user_data) -> bool {
425  auto host = static_cast<FlutterWindowsEngine*>(user_data);
426 
427  return host->compositor_->CreateBackingStore(*config, backing_store_out);
428  };
429 
430  compositor.collect_backing_store_callback =
431  [](const FlutterBackingStore* backing_store, void* user_data) -> bool {
432  auto host = static_cast<FlutterWindowsEngine*>(user_data);
433 
434  return host->compositor_->CollectBackingStore(backing_store);
435  };
436 
437  compositor.present_view_callback =
438  [](const FlutterPresentViewInfo* info) -> bool {
439  auto host = static_cast<FlutterWindowsEngine*>(info->user_data);
440 
441  return host->Present(info);
442  };
443  args.compositor = &compositor;
444 
445  if (aot_data_) {
446  args.aot_data = aot_data_.get();
447  }
448 
449  // The platform thread creates OpenGL contexts. These
450  // must be released to be used by the engine's threads.
451  FML_DCHECK(!egl_manager_ || !egl_manager_->HasContextCurrent());
452 
453  FlutterRendererConfig renderer_config;
454 
455  if (enable_impeller_) {
456  // Impeller does not support a Software backend. Avoid falling back and
457  // confusing the engine on which renderer is selected.
458  if (!egl_manager_) {
459  FML_LOG(ERROR) << "Could not create surface manager. Impeller backend "
460  "does not support software rendering.";
461  return false;
462  }
463  renderer_config = GetOpenGLRendererConfig();
464  } else {
465  renderer_config =
466  egl_manager_ ? GetOpenGLRendererConfig() : GetSoftwareRendererConfig();
467  }
468 
469  auto result = embedder_api_.Run(FLUTTER_ENGINE_VERSION, &renderer_config,
470  &args, this, &engine_);
471  if (result != kSuccess || engine_ == nullptr) {
472  FML_LOG(ERROR) << "Failed to start Flutter engine: error " << result;
473  return false;
474  }
475 
476  // Configure device frame rate displayed via devtools.
477  FlutterEngineDisplay display = {};
478  display.struct_size = sizeof(FlutterEngineDisplay);
479  display.display_id = 0;
480  display.single_display = true;
481  display.refresh_rate =
482  1.0 / (static_cast<double>(FrameInterval().count()) / 1000000000.0);
483 
484  std::vector<FlutterEngineDisplay> displays = {display};
485  embedder_api_.NotifyDisplayUpdate(engine_,
486  kFlutterEngineDisplaysUpdateTypeStartup,
487  displays.data(), displays.size());
488 
489  SendSystemLocales();
490 
491  settings_plugin_->StartWatching();
492  settings_plugin_->SendSettings();
493 
494  InitializeKeyboard();
495 
496  return true;
497 }
498 
499 bool FlutterWindowsEngine::Stop() {
500  if (engine_) {
501  for (const auto& [callback, registrar] :
502  plugin_registrar_destruction_callbacks_) {
503  callback(registrar);
504  }
505  FlutterEngineResult result = embedder_api_.Shutdown(engine_);
506  engine_ = nullptr;
507  return (result == kSuccess);
508  }
509  return false;
510 }
511 
512 std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
513  std::unique_ptr<WindowBindingHandler> window) {
514  auto view_id = next_view_id_;
515  auto view = std::make_unique<FlutterWindowsView>(
516  view_id, this, std::move(window), windows_proc_table_);
517 
518  view->CreateRenderSurface();
519  view->UpdateSemanticsEnabled(semantics_enabled_);
520 
521  next_view_id_++;
522 
523  {
524  // Add the view to the embedder. This must happen before the engine
525  // is notified the view exists and starts presenting to it.
526  std::unique_lock write_lock(views_mutex_);
527  FML_DCHECK(views_.find(view_id) == views_.end());
528  views_[view_id] = view.get();
529  }
530 
531  if (!view->IsImplicitView()) {
532  FML_DCHECK(running());
533 
534  struct Captures {
535  fml::AutoResetWaitableEvent latch;
536  bool added;
537  };
538  Captures captures = {};
539 
540  FlutterWindowMetricsEvent metrics = view->CreateWindowMetricsEvent();
541 
542  FlutterAddViewInfo info = {};
543  info.struct_size = sizeof(FlutterAddViewInfo);
544  info.view_id = view_id;
545  info.view_metrics = &metrics;
546  info.user_data = &captures;
547  info.add_view_callback = [](const FlutterAddViewResult* result) {
548  Captures* captures = reinterpret_cast<Captures*>(result->user_data);
549  captures->added = result->added;
550  captures->latch.Signal();
551  };
552 
553  FlutterEngineResult result = embedder_api_.AddView(engine_, &info);
554  if (result != kSuccess) {
555  FML_LOG(ERROR)
556  << "Starting the add view operation failed. FlutterEngineAddView "
557  "returned an unexpected result: "
558  << result << ". This indicates a bug in the Windows embedder.";
559  FML_DCHECK(false);
560  return nullptr;
561  }
562 
563  // Block the platform thread until the engine has added the view.
564  // TODO(loicsharma): This blocks the platform thread eagerly and can
565  // cause unnecessary delay in input processing. Instead, this should block
566  // lazily only when the app does an operation which needs the view.
567  // https://github.com/flutter/flutter/issues/146248
568  captures.latch.Wait();
569 
570  if (!captures.added) {
571  // Adding the view failed. Update the embedder's state to match the
572  // engine's state. This is unexpected and indicates a bug in the Windows
573  // embedder.
574  FML_LOG(ERROR) << "FlutterEngineAddView failed to add view";
575  std::unique_lock write_lock(views_mutex_);
576  views_.erase(view_id);
577  return nullptr;
578  }
579  }
580 
581  return std::move(view);
582 }
583 
584 void FlutterWindowsEngine::RemoveView(FlutterViewId view_id) {
585  FML_DCHECK(running());
586 
587  // Notify the engine to stop rendering to the view if it isn't the implicit
588  // view. The engine and framework assume the implicit view always exists and
589  // can continue presenting.
590  if (view_id != kImplicitViewId) {
591  struct Captures {
592  fml::AutoResetWaitableEvent latch;
593  bool removed;
594  };
595  Captures captures = {};
596 
597  FlutterRemoveViewInfo info = {};
598  info.struct_size = sizeof(FlutterRemoveViewInfo);
599  info.view_id = view_id;
600  info.user_data = &captures;
601  info.remove_view_callback = [](const FlutterRemoveViewResult* result) {
602  // This is invoked on an engine thread. If
603  // |FlutterRemoveViewResult.removed| is `true`, the engine guarantees the
604  // view won't be presented.
605  Captures* captures = reinterpret_cast<Captures*>(result->user_data);
606  captures->removed = result->removed;
607  captures->latch.Signal();
608  };
609 
610  FlutterEngineResult result = embedder_api_.RemoveView(engine_, &info);
611  if (result != kSuccess) {
612  FML_LOG(ERROR) << "Starting the remove view operation failed. "
613  "FlutterEngineRemoveView "
614  "returned an unexpected result: "
615  << result
616  << ". This indicates a bug in the Windows embedder.";
617  FML_DCHECK(false);
618  return;
619  }
620 
621  // Block the platform thread until the engine has removed the view.
622  // TODO(loicsharma): This blocks the platform thread eagerly and can
623  // cause unnecessary delay in input processing. Instead, this should block
624  // lazily only when an operation needs the view.
625  // https://github.com/flutter/flutter/issues/146248
626  captures.latch.Wait();
627 
628  if (!captures.removed) {
629  // Removing the view failed. This is unexpected and indicates a bug in the
630  // Windows embedder.
631  FML_LOG(ERROR) << "FlutterEngineRemoveView failed to remove view";
632  return;
633  }
634  }
635 
636  {
637  // The engine no longer presents to the view. Remove the view from the
638  // embedder.
639  std::unique_lock write_lock(views_mutex_);
640 
641  FML_DCHECK(views_.find(view_id) != views_.end());
642  views_.erase(view_id);
643  }
644 }
645 
646 void FlutterWindowsEngine::OnVsync(intptr_t baton) {
647  std::chrono::nanoseconds current_time =
648  std::chrono::nanoseconds(embedder_api_.GetCurrentTime());
649  std::chrono::nanoseconds frame_interval = FrameInterval();
650  auto next = SnapToNextTick(current_time, start_time_, frame_interval);
651  embedder_api_.OnVsync(engine_, baton, next.count(),
652  (next + frame_interval).count());
653 }
654 
655 std::chrono::nanoseconds FlutterWindowsEngine::FrameInterval() {
656  if (frame_interval_override_.has_value()) {
657  return frame_interval_override_.value();
658  }
659  uint64_t interval = 16600000;
660 
661  DWM_TIMING_INFO timing_info = {};
662  timing_info.cbSize = sizeof(timing_info);
663  HRESULT result = DwmGetCompositionTimingInfo(NULL, &timing_info);
664  if (result == S_OK && timing_info.rateRefresh.uiDenominator > 0 &&
665  timing_info.rateRefresh.uiNumerator > 0) {
666  interval = static_cast<double>(timing_info.rateRefresh.uiDenominator *
667  1000000000.0) /
668  static_cast<double>(timing_info.rateRefresh.uiNumerator);
669  }
670 
671  return std::chrono::nanoseconds(interval);
672 }
673 
674 FlutterWindowsView* FlutterWindowsEngine::view(FlutterViewId view_id) const {
675  std::shared_lock read_lock(views_mutex_);
676 
677  auto iterator = views_.find(view_id);
678  if (iterator == views_.end()) {
679  return nullptr;
680  }
681 
682  return iterator->second;
683 }
684 
685 // Returns the currently configured Plugin Registrar.
686 FlutterDesktopPluginRegistrarRef FlutterWindowsEngine::GetRegistrar() {
687  return plugin_registrar_.get();
688 }
689 
690 void FlutterWindowsEngine::AddPluginRegistrarDestructionCallback(
693  plugin_registrar_destruction_callbacks_[callback] = registrar;
694 }
695 
696 void FlutterWindowsEngine::SendWindowMetricsEvent(
697  const FlutterWindowMetricsEvent& event) {
698  if (engine_) {
699  embedder_api_.SendWindowMetricsEvent(engine_, &event);
700  }
701 }
702 
703 void FlutterWindowsEngine::SendPointerEvent(const FlutterPointerEvent& event) {
704  if (engine_) {
705  embedder_api_.SendPointerEvent(engine_, &event, 1);
706  }
707 }
708 
709 void FlutterWindowsEngine::SendKeyEvent(const FlutterKeyEvent& event,
710  FlutterKeyEventCallback callback,
711  void* user_data) {
712  if (engine_) {
713  embedder_api_.SendKeyEvent(engine_, &event, callback, user_data);
714  }
715 }
716 
717 void FlutterWindowsEngine::SendViewFocusEvent(
718  const FlutterViewFocusEvent& event) {
719  if (engine_) {
720  embedder_api_.SendViewFocusEvent(engine_, &event);
721  }
722 }
723 
724 bool FlutterWindowsEngine::SendPlatformMessage(
725  const char* channel,
726  const uint8_t* message,
727  const size_t message_size,
728  const FlutterDesktopBinaryReply reply,
729  void* user_data) {
730  FlutterPlatformMessageResponseHandle* response_handle = nullptr;
731  if (reply != nullptr && user_data != nullptr) {
732  FlutterEngineResult result =
733  embedder_api_.PlatformMessageCreateResponseHandle(
734  engine_, reply, user_data, &response_handle);
735  if (result != kSuccess) {
736  FML_LOG(ERROR) << "Failed to create response handle";
737  return false;
738  }
739  }
740 
741  FlutterPlatformMessage platform_message = {
742  sizeof(FlutterPlatformMessage),
743  channel,
744  message,
745  message_size,
746  response_handle,
747  };
748 
749  FlutterEngineResult message_result =
750  embedder_api_.SendPlatformMessage(engine_, &platform_message);
751  if (response_handle != nullptr) {
752  embedder_api_.PlatformMessageReleaseResponseHandle(engine_,
753  response_handle);
754  }
755  return message_result == kSuccess;
756 }
757 
758 void FlutterWindowsEngine::SendPlatformMessageResponse(
760  const uint8_t* data,
761  size_t data_length) {
762  embedder_api_.SendPlatformMessageResponse(engine_, handle, data, data_length);
763 }
764 
765 void FlutterWindowsEngine::HandlePlatformMessage(
766  const FlutterPlatformMessage* engine_message) {
767  if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) {
768  FML_LOG(ERROR) << "Invalid message size received. Expected: "
769  << sizeof(FlutterPlatformMessage) << " but received "
770  << engine_message->struct_size;
771  return;
772  }
773 
774  auto message = ConvertToDesktopMessage(*engine_message);
775 
776  message_dispatcher_->HandleMessage(message, [this] {}, [this] {});
777 }
778 
779 void FlutterWindowsEngine::ReloadSystemFonts() {
780  embedder_api_.ReloadSystemFonts(engine_);
781 }
782 
783 void FlutterWindowsEngine::ScheduleFrame() {
784  embedder_api_.ScheduleFrame(engine_);
785 }
786 
787 void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {
788  next_frame_callback_ = std::move(callback);
789 
790  embedder_api_.SetNextFrameCallback(
791  engine_,
792  [](void* user_data) {
793  // Embedder callback runs on raster thread. Switch back to platform
794  // thread.
795  FlutterWindowsEngine* self =
796  static_cast<FlutterWindowsEngine*>(user_data);
797 
798  self->task_runner_->PostTask(std::move(self->next_frame_callback_));
799  },
800  this);
801 }
802 
803 HCURSOR FlutterWindowsEngine::GetCursorByName(
804  const std::string& cursor_name) const {
805  static auto* cursors = new std::map<std::string, const wchar_t*>{
806  {"allScroll", IDC_SIZEALL},
807  {"basic", IDC_ARROW},
808  {"click", IDC_HAND},
809  {"forbidden", IDC_NO},
810  {"help", IDC_HELP},
811  {"move", IDC_SIZEALL},
812  {"none", nullptr},
813  {"noDrop", IDC_NO},
814  {"precise", IDC_CROSS},
815  {"progress", IDC_APPSTARTING},
816  {"text", IDC_IBEAM},
817  {"resizeColumn", IDC_SIZEWE},
818  {"resizeDown", IDC_SIZENS},
819  {"resizeDownLeft", IDC_SIZENESW},
820  {"resizeDownRight", IDC_SIZENWSE},
821  {"resizeLeft", IDC_SIZEWE},
822  {"resizeLeftRight", IDC_SIZEWE},
823  {"resizeRight", IDC_SIZEWE},
824  {"resizeRow", IDC_SIZENS},
825  {"resizeUp", IDC_SIZENS},
826  {"resizeUpDown", IDC_SIZENS},
827  {"resizeUpLeft", IDC_SIZENWSE},
828  {"resizeUpRight", IDC_SIZENESW},
829  {"resizeUpLeftDownRight", IDC_SIZENWSE},
830  {"resizeUpRightDownLeft", IDC_SIZENESW},
831  {"wait", IDC_WAIT},
832  };
833  const wchar_t* idc_name = IDC_ARROW;
834  auto it = cursors->find(cursor_name);
835  if (it != cursors->end()) {
836  idc_name = it->second;
837  }
838  return windows_proc_table_->LoadCursor(nullptr, idc_name);
839 }
840 
841 void FlutterWindowsEngine::SendSystemLocales() {
842  std::vector<LanguageInfo> languages =
843  GetPreferredLanguageInfo(*windows_proc_table_);
844  std::vector<FlutterLocale> flutter_locales;
845  flutter_locales.reserve(languages.size());
846  for (const auto& info : languages) {
847  flutter_locales.push_back(CovertToFlutterLocale(info));
848  }
849  // Convert the locale list to the locale pointer list that must be provided.
850  std::vector<const FlutterLocale*> flutter_locale_list;
851  flutter_locale_list.reserve(flutter_locales.size());
852  std::transform(flutter_locales.begin(), flutter_locales.end(),
853  std::back_inserter(flutter_locale_list),
854  [](const auto& arg) -> const auto* { return &arg; });
855  embedder_api_.UpdateLocales(engine_, flutter_locale_list.data(),
856  flutter_locale_list.size());
857 }
858 
859 void FlutterWindowsEngine::InitializeKeyboard() {
860  auto internal_plugin_messenger = internal_plugin_registrar_->messenger();
861  KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState;
862  KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan =
863  [](UINT virtual_key, bool extended) {
864  return MapVirtualKey(virtual_key,
865  extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC);
866  };
867  keyboard_key_handler_ = std::move(CreateKeyboardKeyHandler(
868  internal_plugin_messenger, get_key_state, map_vk_to_scan));
869  text_input_plugin_ =
870  std::move(CreateTextInputPlugin(internal_plugin_messenger));
871 }
872 
873 std::unique_ptr<KeyboardHandlerBase>
874 FlutterWindowsEngine::CreateKeyboardKeyHandler(
875  BinaryMessenger* messenger,
878  auto keyboard_key_handler = std::make_unique<KeyboardKeyHandler>(messenger);
879  keyboard_key_handler->AddDelegate(
880  std::make_unique<KeyboardKeyEmbedderHandler>(
881  [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
882  void* user_data) {
883  return SendKeyEvent(event, callback, user_data);
884  },
885  get_key_state, map_vk_to_scan));
886  keyboard_key_handler->AddDelegate(
887  std::make_unique<KeyboardKeyChannelHandler>(messenger));
888  keyboard_key_handler->InitKeyboardChannel();
889  return keyboard_key_handler;
890 }
891 
892 std::unique_ptr<TextInputPlugin> FlutterWindowsEngine::CreateTextInputPlugin(
893  BinaryMessenger* messenger) {
894  return std::make_unique<TextInputPlugin>(messenger, this);
895 }
896 
897 bool FlutterWindowsEngine::RegisterExternalTexture(int64_t texture_id) {
898  return (embedder_api_.RegisterExternalTexture(engine_, texture_id) ==
899  kSuccess);
900 }
901 
902 bool FlutterWindowsEngine::UnregisterExternalTexture(int64_t texture_id) {
903  return (embedder_api_.UnregisterExternalTexture(engine_, texture_id) ==
904  kSuccess);
905 }
906 
907 bool FlutterWindowsEngine::MarkExternalTextureFrameAvailable(
908  int64_t texture_id) {
909  return (embedder_api_.MarkExternalTextureFrameAvailable(
910  engine_, texture_id) == kSuccess);
911 }
912 
913 bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {
914  struct Captures {
915  fml::closure callback;
916  };
917  auto captures = new Captures();
918  captures->callback = std::move(callback);
919  if (embedder_api_.PostRenderThreadTask(
920  engine_,
921  [](void* opaque) {
922  auto captures = reinterpret_cast<Captures*>(opaque);
923  captures->callback();
924  delete captures;
925  },
926  captures) == kSuccess) {
927  return true;
928  }
929  delete captures;
930  return false;
931 }
932 
933 bool FlutterWindowsEngine::DispatchSemanticsAction(
934  FlutterViewId view_id,
935  uint64_t target,
936  FlutterSemanticsAction action,
937  fml::MallocMapping data) {
938  FlutterSendSemanticsActionInfo info{
939  .struct_size = sizeof(FlutterSendSemanticsActionInfo),
940  .view_id = view_id,
941  .node_id = target,
942  .action = action,
943  .data = data.GetMapping(),
944  .data_length = data.GetSize(),
945  };
946  return (embedder_api_.SendSemanticsAction(engine_, &info));
947 }
948 
949 void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
950  if (engine_ && semantics_enabled_ != enabled) {
951  std::shared_lock read_lock(views_mutex_);
952 
953  semantics_enabled_ = enabled;
954  embedder_api_.UpdateSemanticsEnabled(engine_, enabled);
955  for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
956  iterator->second->UpdateSemanticsEnabled(enabled);
957  }
958  }
959 }
960 
961 void FlutterWindowsEngine::OnPreEngineRestart() {
962  // Reset the keyboard's state on hot restart.
963  InitializeKeyboard();
964 }
965 
966 std::string FlutterWindowsEngine::GetExecutableName() const {
967  std::pair<bool, std::string> result = fml::paths::GetExecutablePath();
968  if (result.first) {
969  const std::string& executable_path = result.second;
970  size_t last_separator = executable_path.find_last_of("/\\");
971  if (last_separator == std::string::npos ||
972  last_separator == executable_path.size() - 1) {
973  return executable_path;
974  }
975  return executable_path.substr(last_separator + 1);
976  }
977  return "Flutter";
978 }
979 
980 void FlutterWindowsEngine::UpdateAccessibilityFeatures() {
981  UpdateHighContrastMode();
982 }
983 
984 void FlutterWindowsEngine::UpdateHighContrastMode() {
985  high_contrast_enabled_ = windows_proc_table_->GetHighContrastEnabled();
986 
987  SendAccessibilityFeatures();
988  settings_plugin_->UpdateHighContrastMode(high_contrast_enabled_);
989 }
990 
991 void FlutterWindowsEngine::SendAccessibilityFeatures() {
992  int flags = 0;
993 
994  if (high_contrast_enabled_) {
995  flags |=
996  FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast;
997  }
998 
999  embedder_api_.UpdateAccessibilityFeatures(
1000  engine_, static_cast<FlutterAccessibilityFeature>(flags));
1001 }
1002 
1003 void FlutterWindowsEngine::RequestApplicationQuit(HWND hwnd,
1004  WPARAM wparam,
1005  LPARAM lparam,
1006  AppExitType exit_type) {
1007  platform_handler_->RequestAppExit(hwnd, wparam, lparam, exit_type, 0);
1008 }
1009 
1010 void FlutterWindowsEngine::OnQuit(std::optional<HWND> hwnd,
1011  std::optional<WPARAM> wparam,
1012  std::optional<LPARAM> lparam,
1013  UINT exit_code) {
1014  lifecycle_manager_->Quit(hwnd, wparam, lparam, exit_code);
1015 }
1016 
1017 void FlutterWindowsEngine::OnDwmCompositionChanged() {
1018  std::shared_lock read_lock(views_mutex_);
1019 
1020  for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
1021  iterator->second->OnDwmCompositionChanged();
1022  }
1023 }
1024 
1025 void FlutterWindowsEngine::OnWindowStateEvent(HWND hwnd,
1026  WindowStateEvent event) {
1027  lifecycle_manager_->OnWindowStateEvent(hwnd, event);
1028 }
1029 
1030 std::optional<LRESULT> FlutterWindowsEngine::ProcessExternalWindowMessage(
1031  HWND hwnd,
1032  UINT message,
1033  WPARAM wparam,
1034  LPARAM lparam) {
1035  if (lifecycle_manager_) {
1036  return lifecycle_manager_->ExternalWindowMessage(hwnd, message, wparam,
1037  lparam);
1038  }
1039  return std::nullopt;
1040 }
1041 
1042 void FlutterWindowsEngine::UpdateFlutterCursor(
1043  const std::string& cursor_name) const {
1044  SetFlutterCursor(GetCursorByName(cursor_name));
1045 }
1046 
1047 void FlutterWindowsEngine::SetFlutterCursor(HCURSOR cursor) const {
1048  windows_proc_table_->SetCursor(cursor);
1049 }
1050 
1051 void FlutterWindowsEngine::OnChannelUpdate(std::string name, bool listening) {
1052  if (name == "flutter/platform" && listening) {
1053  lifecycle_manager_->BeginProcessingExit();
1054  } else if (name == "flutter/lifecycle" && listening) {
1055  lifecycle_manager_->BeginProcessingLifecycle();
1056  }
1057 }
1058 
1059 void FlutterWindowsEngine::OnViewFocusChangeRequest(
1060  const FlutterViewFocusChangeRequest* request) {
1061  std::shared_lock read_lock(views_mutex_);
1062 
1063  auto iterator = views_.find(request->view_id);
1064  if (iterator == views_.end()) {
1065  return;
1066  }
1067 
1068  FlutterWindowsView* view = iterator->second;
1069  view->Focus();
1070 }
1071 
1072 bool FlutterWindowsEngine::Present(const FlutterPresentViewInfo* info) {
1073  // This runs on the raster thread. Lock the views map for the entirety of the
1074  // present operation to block the platform thread from destroying the
1075  // view during the present.
1076  std::shared_lock read_lock(views_mutex_);
1077 
1078  auto iterator = views_.find(info->view_id);
1079  if (iterator == views_.end()) {
1080  return false;
1081  }
1082 
1083  FlutterWindowsView* view = iterator->second;
1084 
1085  return compositor_->Present(view, info->layers, info->layers_count);
1086 }
1087 
1088 } // namespace flutter
static void SetUp(BinaryMessenger *binary_messenger, AccessibilityPlugin *plugin)
FlutterWindowsEngine(const FlutterProjectBundle &project, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
FlutterWindowsView * view(FlutterViewId view_id) const
virtual void OnViewFocusChangeRequest(const FlutterViewFocusChangeRequest *request)
void HandlePlatformMessage(const FlutterPlatformMessage *)
void SetSwitches(const std::vector< std::string > &switches)
std::weak_ptr< AccessibilityBridgeWindows > accessibility_bridge()
std::function< SHORT(UINT, bool)> MapVirtualKeyToScanCode
static std::unique_ptr< Manager > Create(GpuPreference gpu_preference)
Definition: manager.cc:17
static std::shared_ptr< ProcTable > Create()
Definition: proc_table.cc:12
uint32_t texture_id
void(* FlutterDesktopBinaryReply)(const uint8_t *data, size_t data_size, void *user_data)
struct _FlutterPlatformMessageResponseHandle FlutterDesktopMessageResponseHandle
void(* FlutterDesktopOnPluginRegistrarDestroyed)(FlutterDesktopPluginRegistrarRef)
@ RunOnPlatformThread
static constexpr char kAccessibilityChannelName[]
FlutterDesktopBinaryReply callback
Win32Message message
std::vector< LanguageInfo > GetPreferredLanguageInfo(const WindowsProcTable &windows_proc_table)
Definition: system_utils.cc:15
WindowStateEvent
An event representing a change in window state that may update the.
int64_t FlutterViewId
Definition: flutter_view.h:13
static void WindowsPlatformThreadPrioritySetter(FlutterThreadPriority priority)
constexpr FlutterViewId kImplicitViewId