| 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
5 #include "flutter/shell/platform/windows/flutter_windows_engine.h"
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"
17 #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
18 #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
19 #include "flutter/shell/platform/common/path_utils.h"
20 #include "flutter/shell/platform/embedder/embedder_struct_macros.h"
21 #include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
22 #include "flutter/shell/platform/windows/compositor_opengl.h"
23 #include "flutter/shell/platform/windows/compositor_software.h"
24 #include "flutter/shell/platform/windows/flutter_windows_view.h"
25 #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h"
26 #include "flutter/shell/platform/windows/system_utils.h"
27 #include "flutter/shell/platform/windows/task_runner.h"
28 #include "flutter/third_party/accessibility/ax/ax_node.h"
29 #include "shell/platform/windows/flutter_project_bundle.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) {
123 FlutterDesktopMessage 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
149 FlutterWindowsEngine::FlutterWindowsEngine(
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 =
300 &WindowsPlatformThreadPrioritySetter;
301
302 if (project_->ui_thread_policy() ==
303 FlutterUIThreadPolicy::RunOnPlatformThread) {
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(
691 FlutterDesktopOnPluginRegistrarDestroyed callback,
692 FlutterDesktopPluginRegistrarRef registrar) {
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(
759 const FlutterDesktopMessageResponseHandle* handle,
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,
876 KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state,
877 KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) {
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
accessibility_bridge_windows.h
binary_messenger_impl.h
flutter::AccessibilityPlugin::SetUp
static void SetUp(BinaryMessenger *binary_messenger, AccessibilityPlugin *plugin)
Definition: accessibility_plugin.cc:76
flutter::BinaryMessenger
Definition: binary_messenger.h:28
flutter::FlutterDesktopMessenger
Definition: flutter_desktop_messenger.h:23
flutter::FlutterProjectBundle
Definition: flutter_project_bundle.h:32
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:90
flutter::FlutterWindowsEngine::FlutterWindowsEngine
FlutterWindowsEngine(const FlutterProjectBundle &project, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
Definition: flutter_windows_engine.cc:149
flutter::FlutterWindowsEngine::view
FlutterWindowsView * view(FlutterViewId view_id) const
Definition: flutter_windows_engine.cc:674
flutter::FlutterWindowsEngine::OnPreEngineRestart
void OnPreEngineRestart()
Definition: flutter_windows_engine.cc:961
flutter::FlutterWindowsEngine::OnViewFocusChangeRequest
virtual void OnViewFocusChangeRequest(const FlutterViewFocusChangeRequest *request)
Definition: flutter_windows_engine.cc:1059
flutter::FlutterWindowsEngine::HandlePlatformMessage
void HandlePlatformMessage(const FlutterPlatformMessage *)
Definition: flutter_windows_engine.cc:765
flutter::FlutterWindowsEngine::OnVsync
void OnVsync(intptr_t baton)
Definition: flutter_windows_engine.cc:646
flutter::FlutterWindowsEngine::SetSwitches
void SetSwitches(const std::vector< std::string > &switches)
Definition: flutter_windows_engine.cc:240
flutter::FlutterWindowsView
Definition: flutter_windows_view.h:34
flutter::FlutterWindowsView::Focus
virtual bool Focus()
Definition: flutter_windows_view.cc:836
flutter::FlutterWindowsView::accessibility_bridge
std::weak_ptr< AccessibilityBridgeWindows > accessibility_bridge()
Definition: flutter_windows_view.h:239
flutter::KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode
std::function< SHORT(UINT, bool)> MapVirtualKeyToScanCode
Definition: keyboard_key_embedder_handler.h:43
flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler
std::function< SHORT(int)> GetKeyStateHandler
Definition: keyboard_key_embedder_handler.h:41
flutter::TaskRunner
Definition: task_runner.h:26
flutter::WindowsLifecycleManager
Definition: windows_lifecycle_manager.h:37
flutter::egl::Manager::Create
static std::unique_ptr< Manager > Create(GpuPreference gpu_preference)
Definition: manager.cc:17
flutter::egl::ProcTable::Create
static std::shared_ptr< ProcTable > Create()
Definition: proc_table.cc:12
texture_id
uint32_t texture_id
Definition: compositor_opengl.cc:20
compositor_opengl.h
compositor_software.h
FlutterDesktopBinaryReply
void(* FlutterDesktopBinaryReply)(const uint8_t *data, size_t data_size, void *user_data)
Definition: flutter_messenger.h:26
FlutterDesktopMessageResponseHandle
struct _FlutterPlatformMessageResponseHandle FlutterDesktopMessageResponseHandle
Definition: flutter_messenger.h:22
FlutterDesktopOnPluginRegistrarDestroyed
void(* FlutterDesktopOnPluginRegistrarDestroyed)(FlutterDesktopPluginRegistrarRef)
Definition: flutter_plugin_registrar.h:23
flutter_project_bundle.h
RunOnPlatformThread
@ RunOnPlatformThread
Definition: flutter_windows.h:54
kAccessibilityChannelName
static constexpr char kAccessibilityChannelName[]
Definition: flutter_windows_engine.cc:34
flutter_windows_engine.h
flutter_windows_view.h
user_data
void * user_data
Definition: flutter_windows_view_unittests.cc:53
callback
FlutterDesktopBinaryReply callback
Definition: flutter_windows_view_unittests.cc:52
keyboard_key_channel_handler.h
extended
bool extended
Definition: keyboard_key_handler_unittests.cc:118
action
int action
Definition: keyboard_key_handler_unittests.cc:116
message
Win32Message message
Definition: keyboard_unittests.cc:137
flutter::egl::GpuPreference
GpuPreference
Definition: manager.h:32
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::AppExitType
AppExitType
Definition: platform_handler.h:27
flutter::GetPreferredLanguageInfo
std::vector< LanguageInfo > GetPreferredLanguageInfo(const WindowsProcTable &windows_proc_table)
Definition: system_utils.cc:15
flutter::WindowStateEvent
WindowStateEvent
An event representing a change in window state that may update the.
Definition: windows_lifecycle_manager.h:24
flutter::FlutterViewId
int64_t FlutterViewId
Definition: flutter_view.h:13
flutter::WindowsPlatformThreadPrioritySetter
static void WindowsPlatformThreadPrioritySetter(FlutterThreadPriority priority)
Definition: flutter_windows_engine.h:60
flutter::kImplicitViewId
constexpr FlutterViewId kImplicitViewId
Definition: flutter_windows_engine.h:55
path_utils.h
standard_message_codec.h
FlutterDesktopMessage
Definition: flutter_messenger.h:31
FlutterDesktopPluginRegistrar
Definition: window_state.h:23
system_utils.h
task_runner.h