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// FLUTTER_NOLINT
5
6#include "flutter/shell/platform/glfw/public/flutter_glfw.h"
7
8#include <GLFW/glfw3.h>
9#include <assert.h>
10
11#include <algorithm>
12#include <chrono>
13#include <cstdlib>
14#include <filesystem>
15#include <iostream>
16
17#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h"
18#include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h"
19#include "flutter/shell/platform/common/cpp/path_utils.h"
20#include "flutter/shell/platform/embedder/embedder.h"
21#include "flutter/shell/platform/glfw/glfw_event_loop.h"
22#include "flutter/shell/platform/glfw/headless_event_loop.h"
23#include "flutter/shell/platform/glfw/key_event_handler.h"
24#include "flutter/shell/platform/glfw/keyboard_hook_handler.h"
25#include "flutter/shell/platform/glfw/platform_handler.h"
26#include "flutter/shell/platform/glfw/text_input_plugin.h"
27
28// GLFW_TRUE & GLFW_FALSE are introduced since libglfw-3.3,
29// add definitions here to compile under the old versions.
30#ifndef GLFW_TRUE
31#define GLFW_TRUE 1
32#endif
33#ifndef GLFW_FALSE
34#define GLFW_FALSE 0
35#endif
36
37using UniqueGLFWwindowPtr = std::unique_ptr<GLFWwindow, void (*)(GLFWwindow*)>;
38
39static_assert(FLUTTER_ENGINE_VERSION == 1, "");
40
41const int kFlutterDesktopDontCare = GLFW_DONT_CARE;
42
43static constexpr double kDpPerInch = 160.0;
44
45// Struct for storing state within an instance of the GLFW Window.
46struct FlutterDesktopWindowControllerState {
47 // The GLFW window that is bound to this state object.
48 UniqueGLFWwindowPtr window = UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow);
49
50 // The invisible GLFW window used to upload resources in the background.
51 UniqueGLFWwindowPtr resource_window =
52 UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow);
53
54 // The state associated with the engine.
55 std::unique_ptr<FlutterDesktopEngineState> engine;
56
57 // The window handle given to API clients.
58 std::unique_ptr<FlutterDesktopWindow> window_wrapper;
59
60 // Handlers for keyboard events from GLFW.
61 std::vector<std::unique_ptr<flutter::KeyboardHookHandler>>
62 keyboard_hook_handlers;
63
64 // Whether or not the pointer has been added (or if tracking is enabled,
65 // has been added since it was last removed).
66 bool pointer_currently_added = false;
67
68 // The screen coordinates per inch on the primary monitor. Defaults to a sane
69 // value based on pixel_ratio 1.0.
70 double monitor_screen_coordinates_per_inch = kDpPerInch;
71};
72
73// Opaque reference for the GLFW window itself. This is separate from the
74// controller so that it can be provided to plugins without giving them access
75// to all of the controller-based functionality.
76struct FlutterDesktopWindow {
77 // The GLFW window that (indirectly) owns this state object.
78 GLFWwindow* window;
79
80 // Whether or not to track mouse movements to send kHover events.
81 bool hover_tracking_enabled = true;
82
83 // The ratio of pixels per screen coordinate for the window.
84 double pixels_per_screen_coordinate = 1.0;
85
86 // If non-zero, a forced pixel ratio to use instead of one computed based on
87 // screen information.
88 double pixel_ratio_override = 0.0;
89
90 // Resizing triggers a window refresh, but the resize already updates Flutter.
91 // To avoid double messages, the refresh after each resize is skipped.
92 bool skip_next_window_refresh = false;
93};
94
95// Custom deleter for FlutterEngineAOTData.
96struct AOTDataDeleter {
97 void operator()(FlutterEngineAOTData aot_data) {
98 FlutterEngineCollectAOTData(aot_data);
99 }
100};
101
102using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>;
103
104// Struct for storing state of a Flutter engine instance.
105struct FlutterDesktopEngineState {
106 // The handle to the Flutter engine instance.
107 FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine;
108
109 // The event loop for the main thread that allows for delayed task execution.
110 std::unique_ptr<flutter::EventLoop> event_loop;
111
112 // The plugin messenger handle given to API clients.
113 std::unique_ptr<FlutterDesktopMessenger> messenger;
114
115 // Message dispatch manager for messages from the Flutter engine.
116 std::unique_ptr<flutter::IncomingMessageDispatcher> message_dispatcher;
117
118 // The plugin registrar handle given to API clients.
119 std::unique_ptr<FlutterDesktopPluginRegistrar> plugin_registrar;
120
121 // The plugin registrar managing internal plugins.
122 std::unique_ptr<flutter::PluginRegistrar> internal_plugin_registrar;
123
124 // Handler for the flutter/platform channel.
125 std::unique_ptr<flutter::PlatformHandler> platform_handler;
126
127 // The controller associated with this engine instance, if any.
128 // This will always be null for a headless engine.
129 FlutterDesktopWindowControllerState* window_controller = nullptr;
130
131 // AOT data for this engine instance, if applicable.
132 UniqueAotDataPtr aot_data = nullptr;
133};
134
135// State associated with the plugin registrar.
136struct FlutterDesktopPluginRegistrar {
137 // The engine that backs this registrar.
138 FlutterDesktopEngineState* engine;
139
140 // Callback to be called on registrar destruction.
141 FlutterDesktopOnRegistrarDestroyed destruction_handler;
142};
143
144// State associated with the messenger used to communicate with the engine.
145struct FlutterDesktopMessenger {
146 // The engine that backs this messenger.
147 FlutterDesktopEngineState* engine;
148};
149
150// Retrieves state bag for the window in question from the GLFWWindow.
151static FlutterDesktopWindowControllerState* GetWindowController(
152 GLFWwindow* window) {
153 return reinterpret_cast<FlutterDesktopWindowControllerState*>(
154 glfwGetWindowUserPointer(window));
155}
156
157// Creates and returns an invisible GLFW window that shares |window|'s resource
158// context.
159static UniqueGLFWwindowPtr CreateShareWindowForWindow(GLFWwindow* window) {
160 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
161 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
162#if defined(__linux__)
163 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
164#endif
165 GLFWwindow* share_window = glfwCreateWindow(1, 1, "", NULL, window);
166 glfwDefaultWindowHints();
167 return UniqueGLFWwindowPtr(share_window, glfwDestroyWindow);
168}
169
170// Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage.
171static FlutterDesktopMessage ConvertToDesktopMessage(
172 const FlutterPlatformMessage& engine_message) {
173 FlutterDesktopMessage message = {};
174 message.struct_size = sizeof(message);
175 message.channel = engine_message.channel;
176 message.message = engine_message.message;
177 message.message_size = engine_message.message_size;
178 message.response_handle = engine_message.response_handle;
179 return message;
180}
181
182// Returns the number of screen coordinates per inch for the main monitor.
183// If the information is unavailable, returns a default value that assumes
184// that a screen coordinate is one dp.
185static double GetScreenCoordinatesPerInch() {
186 auto* primary_monitor = glfwGetPrimaryMonitor();
187 if (primary_monitor == nullptr) {
188 return kDpPerInch;
189 }
190 auto* primary_monitor_mode = glfwGetVideoMode(primary_monitor);
191 int primary_monitor_width_mm;
192 glfwGetMonitorPhysicalSize(primary_monitor, &primary_monitor_width_mm,
193 nullptr);
194 if (primary_monitor_width_mm == 0) {
195 return kDpPerInch;
196 }
197 return primary_monitor_mode->width / (primary_monitor_width_mm / 25.4);
198}
199
200// Sends a window metrics update to the Flutter engine using the given
201// framebuffer size and the current window information in |state|.
202static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller,
203 int width,
204 int height) {
205 double dpi = controller->window_wrapper->pixels_per_screen_coordinate *
206 controller->monitor_screen_coordinates_per_inch;
207
208 FlutterWindowMetricsEvent event = {};
209 event.struct_size = sizeof(event);
210 event.width = width;
211 event.height = height;
212 if (controller->window_wrapper->pixel_ratio_override == 0.0) {
213 // The Flutter pixel_ratio is defined as DPI/dp. Limit the ratio to a
214 // minimum of 1 to avoid rendering a smaller UI on standard resolution
215 // monitors.
216 event.pixel_ratio = std::max(dpi / kDpPerInch, 1.0);
217 } else {
218 event.pixel_ratio = controller->window_wrapper->pixel_ratio_override;
219 }
220 FlutterEngineSendWindowMetricsEvent(controller->engine->flutter_engine,
221 &event);
222}
223
224// Populates |task_runner| with a description that uses |engine_state|'s event
225// loop to run tasks.
226static void ConfigurePlatformTaskRunner(
227 FlutterTaskRunnerDescription* task_runner,
228 FlutterDesktopEngineState* engine_state) {
229 task_runner->struct_size = sizeof(FlutterTaskRunnerDescription);
230 task_runner->user_data = engine_state;
231 task_runner->runs_task_on_current_thread_callback = [](void* state) -> bool {
232 return reinterpret_cast<FlutterDesktopEngineState*>(state)
233 ->event_loop->RunsTasksOnCurrentThread();
234 };
235 task_runner->post_task_callback =
236 [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void {
237 reinterpret_cast<FlutterDesktopEngineState*>(state)->event_loop->PostTask(
238 task, target_time_nanos);
239 };
240}
241
242// When GLFW calls back to the window with a framebuffer size change, notify
243// FlutterEngine about the new window metrics.
244static void GLFWFramebufferSizeCallback(GLFWwindow* window,
245 int width_px,
246 int height_px) {
247 int width;
248 glfwGetWindowSize(window, &width, nullptr);
249 auto* controller = GetWindowController(window);
250 controller->window_wrapper->pixels_per_screen_coordinate =
251 width > 0 ? width_px / width : 1;
252
253 SendWindowMetrics(controller, width_px, height_px);
254 controller->window_wrapper->skip_next_window_refresh = true;
255}
256
257// Indicates that the window needs to be redrawn.
258void GLFWWindowRefreshCallback(GLFWwindow* window) {
259 auto* controller = GetWindowController(window);
260 if (controller->window_wrapper->skip_next_window_refresh) {
261 controller->window_wrapper->skip_next_window_refresh = false;
262 return;
263 }
264 // There's no engine API to request a redraw explicitly, so instead send a
265 // window metrics event with the current size to trigger it.
266 int width_px, height_px;
267 glfwGetFramebufferSize(window, &width_px, &height_px);
268 if (width_px > 0 && height_px > 0) {
269 SendWindowMetrics(controller, width_px, height_px);
270 }
271}
272
273// Sends a pointer event to the Flutter engine based on the given data.
274//
275// Any coordinate/distance values in |event_data| should be in screen
276// coordinates; they will be adjusted to pixel values before being sent.
277static void SendPointerEventWithData(GLFWwindow* window,
278 const FlutterPointerEvent& event_data) {
279 auto* controller = GetWindowController(window);
280 // If sending anything other than an add, and the pointer isn't already added,
281 // synthesize an add to satisfy Flutter's expectations about events.
282 if (!controller->pointer_currently_added &&
283 event_data.phase != FlutterPointerPhase::kAdd) {
284 FlutterPointerEvent event = {};
285 event.phase = FlutterPointerPhase::kAdd;
286 event.x = event_data.x;
287 event.y = event_data.y;
288 SendPointerEventWithData(window, event);
289 }
290 // Don't double-add (e.g., if events are delivered out of order, so an add has
291 // already been synthesized).
292 if (controller->pointer_currently_added &&
293 event_data.phase == FlutterPointerPhase::kAdd) {
294 return;
295 }
296
297 FlutterPointerEvent event = event_data;
298 // Set metadata that's always the same regardless of the event.
299 event.struct_size = sizeof(event);
300 event.timestamp =
301 std::chrono::duration_cast<std::chrono::microseconds>(
302 std::chrono::high_resolution_clock::now().time_since_epoch())
303 .count();
304 // Convert all screen coordinates to pixel coordinates.
305 double pixels_per_coordinate =
306 controller->window_wrapper->pixels_per_screen_coordinate;
307 event.x *= pixels_per_coordinate;
308 event.y *= pixels_per_coordinate;
309 event.scroll_delta_x *= pixels_per_coordinate;
310 event.scroll_delta_y *= pixels_per_coordinate;
311
312 FlutterEngineSendPointerEvent(controller->engine->flutter_engine, &event, 1);
313
314 if (event_data.phase == FlutterPointerPhase::kAdd) {
315 controller->pointer_currently_added = true;
316 } else if (event_data.phase == FlutterPointerPhase::kRemove) {
317 controller->pointer_currently_added = false;
318 }
319}
320
321// Updates |event_data| with the current location of the mouse cursor.
322static void SetEventLocationFromCursorPosition(
323 GLFWwindow* window,
324 FlutterPointerEvent* event_data) {
325 glfwGetCursorPos(window, &event_data->x, &event_data->y);
326}
327
328// Set's |event_data|'s phase to either kMove or kHover depending on the current
329// primary mouse button state.
330static void SetEventPhaseFromCursorButtonState(
331 GLFWwindow* window,
332 FlutterPointerEvent* event_data) {
333 event_data->phase =
334 glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS
335 ? FlutterPointerPhase::kMove
336 : FlutterPointerPhase::kHover;
337}
338
339// Reports the mouse entering or leaving the Flutter view.
340static void GLFWCursorEnterCallback(GLFWwindow* window, int entered) {
341 FlutterPointerEvent event = {};
342 event.phase =
343 entered ? FlutterPointerPhase::kAdd : FlutterPointerPhase::kRemove;
344 SetEventLocationFromCursorPosition(window, &event);
345 SendPointerEventWithData(window, event);
346}
347
348// Reports mouse movement to the Flutter engine.
349static void GLFWCursorPositionCallback(GLFWwindow* window, double x, double y) {
350 FlutterPointerEvent event = {};
351 event.x = x;
352 event.y = y;
353 SetEventPhaseFromCursorButtonState(window, &event);
354 SendPointerEventWithData(window, event);
355}
356
357// Reports mouse button press to the Flutter engine.
358static void GLFWMouseButtonCallback(GLFWwindow* window,
359 int key,
360 int action,
361 int mods) {
362 // Flutter currently doesn't understand other buttons, so ignore anything
363 // other than left.
364 if (key != GLFW_MOUSE_BUTTON_LEFT) {
365 return;
366 }
367
368 FlutterPointerEvent event = {};
369 event.phase = (action == GLFW_PRESS) ? FlutterPointerPhase::kDown
370 : FlutterPointerPhase::kUp;
371 SetEventLocationFromCursorPosition(window, &event);
372 SendPointerEventWithData(window, event);
373
374 // If mouse tracking isn't already enabled, turn it on for the duration of
375 // the drag to generate kMove events.
376 bool hover_enabled =
377 GetWindowController(window)->window_wrapper->hover_tracking_enabled;
378 if (!hover_enabled) {
379 glfwSetCursorPosCallback(
380 window, (action == GLFW_PRESS) ? GLFWCursorPositionCallback : nullptr);
381 }
382 // Disable enter/exit events while the mouse button is down; GLFW will send
383 // an exit event when the mouse button is released, and the pointer should
384 // stay valid until then.
385 if (hover_enabled) {
386 glfwSetCursorEnterCallback(
387 window, (action == GLFW_PRESS) ? nullptr : GLFWCursorEnterCallback);
388 }
389}
390
391// Reports scroll wheel events to the Flutter engine.
392static void GLFWScrollCallback(GLFWwindow* window,
393 double delta_x,
394 double delta_y) {
395 FlutterPointerEvent event = {};
396 SetEventLocationFromCursorPosition(window, &event);
397 SetEventPhaseFromCursorButtonState(window, &event);
398 event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll;
399 // TODO: See if this can be queried from the OS; this value is chosen
400 // arbitrarily to get something that feels reasonable.
401 const int kScrollOffsetMultiplier = 20;
402 event.scroll_delta_x = delta_x * kScrollOffsetMultiplier;
403 event.scroll_delta_y = -delta_y * kScrollOffsetMultiplier;
404 SendPointerEventWithData(window, event);
405}
406
407// Passes character input events to registered handlers.
408static void GLFWCharCallback(GLFWwindow* window, unsigned int code_point) {
409 for (const auto& handler :
410 GetWindowController(window)->keyboard_hook_handlers) {
411 handler->CharHook(window, code_point);
412 }
413}
414
415// Passes raw key events to registered handlers.
416static void GLFWKeyCallback(GLFWwindow* window,
417 int key,
418 int scancode,
419 int action,
420 int mods) {
421 for (const auto& handler :
422 GetWindowController(window)->keyboard_hook_handlers) {
423 handler->KeyboardHook(window, key, scancode, action, mods);
424 }
425}
426
427// Enables/disables the callbacks related to mouse tracking.
428static void SetHoverCallbacksEnabled(GLFWwindow* window, bool enabled) {
429 glfwSetCursorEnterCallback(window,
430 enabled ? GLFWCursorEnterCallback : nullptr);
431 glfwSetCursorPosCallback(window,
432 enabled ? GLFWCursorPositionCallback : nullptr);
433}
434
435// Flushes event queue and then assigns default window callbacks.
436static void GLFWAssignEventCallbacks(GLFWwindow* window) {
437 glfwPollEvents();
438 glfwSetKeyCallback(window, GLFWKeyCallback);
439 glfwSetCharCallback(window, GLFWCharCallback);
440 glfwSetMouseButtonCallback(window, GLFWMouseButtonCallback);
441 glfwSetScrollCallback(window, GLFWScrollCallback);
442 if (GetWindowController(window)->window_wrapper->hover_tracking_enabled) {
443 SetHoverCallbacksEnabled(window, true);
444 }
445}
446
447// Clears default window events.
448static void GLFWClearEventCallbacks(GLFWwindow* window) {
449 glfwSetKeyCallback(window, nullptr);
450 glfwSetCharCallback(window, nullptr);
451 glfwSetMouseButtonCallback(window, nullptr);
452 glfwSetScrollCallback(window, nullptr);
453 SetHoverCallbacksEnabled(window, false);
454}
455
456// The Flutter Engine calls out to this function when new platform messages are
457// available
458static void EngineOnFlutterPlatformMessage(
459 const FlutterPlatformMessage* engine_message,
460 void* user_data) {
461 if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) {
462 std::cerr << "Invalid message size received. Expected: "
463 << sizeof(FlutterPlatformMessage) << " but received "
464 << engine_message->struct_size << std::endl;
465 return;
466 }
467
468 FlutterDesktopEngineState* engine_state =
469 static_cast<FlutterDesktopEngineState*>(user_data);
470 GLFWwindow* window = engine_state->window_controller == nullptr
471 ? nullptr
472 : engine_state->window_controller->window.get();
473
474 auto message = ConvertToDesktopMessage(*engine_message);
475 engine_state->message_dispatcher->HandleMessage(
476 message,
477 [window] {
478 if (window) {
479 GLFWClearEventCallbacks(window);
480 }
481 },
482 [window] {
483 if (window) {
484 GLFWAssignEventCallbacks(window);
485 }
486 });
487}
488
489static bool EngineMakeContextCurrent(void* user_data) {
490 FlutterDesktopEngineState* engine_state =
491 static_cast<FlutterDesktopEngineState*>(user_data);
492 FlutterDesktopWindowControllerState* window_controller =
493 engine_state->window_controller;
494 if (!window_controller) {
495 return false;
496 }
497 glfwMakeContextCurrent(window_controller->window.get());
498 return true;
499}
500
501static bool EngineMakeResourceContextCurrent(void* user_data) {
502 FlutterDesktopEngineState* engine_state =
503 static_cast<FlutterDesktopEngineState*>(user_data);
504 FlutterDesktopWindowControllerState* window_controller =
505 engine_state->window_controller;
506 if (!window_controller) {
507 return false;
508 }
509 glfwMakeContextCurrent(window_controller->resource_window.get());
510 return true;
511}
512
513static bool EngineClearContext(void* user_data) {
514 FlutterDesktopEngineState* engine_state =
515 static_cast<FlutterDesktopEngineState*>(user_data);
516 FlutterDesktopWindowControllerState* window_controller =
517 engine_state->window_controller;
518 if (!window_controller) {
519 return false;
520 }
521 glfwMakeContextCurrent(nullptr);
522 return true;
523}
524
525static bool EnginePresent(void* user_data) {
526 FlutterDesktopEngineState* engine_state =
527 static_cast<FlutterDesktopEngineState*>(user_data);
528 FlutterDesktopWindowControllerState* window_controller =
529 engine_state->window_controller;
530 if (!window_controller) {
531 return false;
532 }
533 glfwSwapBuffers(window_controller->window.get());
534 return true;
535}
536
537static uint32_t EngineGetActiveFbo(void* user_data) {
538 return 0;
539}
540
541// Resolves the address of the specified OpenGL or OpenGL ES
542// core or extension function, if it is supported by the current context.
543static void* EngineProcResolver(void* user_data, const char* name) {
544 return reinterpret_cast<void*>(glfwGetProcAddress(name));
545}
546
547// Clears the GLFW window to Material Blue-Grey.
548//
549// This function is primarily to fix an issue when the Flutter Engine is
550// spinning up, wherein artifacts of existing windows are rendered onto the
551// canvas for a few moments.
552//
553// This function isn't necessary, but makes starting the window much easier on
554// the eyes.
555static void GLFWClearCanvas(GLFWwindow* window) {
556 glfwMakeContextCurrent(window);
557 // This color is Material Blue Grey.
558 glClearColor(236.0f / 255.0f, 239.0f / 255.0f, 241.0f / 255.0f, 0.0f);
559 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
560 glFlush();
561 glfwSwapBuffers(window);
562 glfwMakeContextCurrent(nullptr);
563}
564
565static void GLFWErrorCallback(int error_code, const char* description) {
566 std::cerr << "GLFW error " << error_code << ": " << description << std::endl;
567}
568
569// Attempts to load AOT data from the given path, which must be absolute and
570// non-empty. Logs and returns nullptr on failure.
571UniqueAotDataPtr LoadAotData(std::filesystem::path aot_data_path) {
572 if (aot_data_path.empty()) {
573 std::cerr
574 << "Attempted to load AOT data, but no aot_data_path was provided."
575 << std::endl;
576 return nullptr;
577 }
578 if (!std::filesystem::exists(aot_data_path)) {
579 std::cerr << "Can't load AOT data from " << aot_data_path.u8string()
580 << "; no such file." << std::endl;
581 return nullptr;
582 }
583 std::string path_string = aot_data_path.u8string();
584 FlutterEngineAOTDataSource source = {};
585 source.type = kFlutterEngineAOTDataSourceTypeElfPath;
586 source.elf_path = path_string.c_str();
587 FlutterEngineAOTData data = nullptr;
588 auto result = FlutterEngineCreateAOTData(&source, &data);
589 if (result != kSuccess) {
590 std::cerr << "Failed to load AOT data from: " << path_string << std::endl;
591 return nullptr;
592 }
593 return UniqueAotDataPtr(data);
594}
595
596// Starts an instance of the Flutter Engine.
597//
598// Configures the engine according to |engine_propreties| and using |event_loop|
599// to schedule engine tasks.
600//
601// Returns true on success, in which case |engine_state|'s 'engine' field will
602// be updated to point to the started engine.
603static bool RunFlutterEngine(
604 FlutterDesktopEngineState* engine_state,
605 const FlutterDesktopEngineProperties& engine_properties,
606 std::unique_ptr<flutter::EventLoop> event_loop) {
607 // FlutterProjectArgs is expecting a full argv, so when processing it for
608 // flags the first item is treated as the executable and ignored. Add a dummy
609 // value so that all provided arguments are used.
610 std::vector<const char*> argv = {"placeholder"};
611 if (engine_properties.switches_count > 0) {
612 argv.insert(argv.end(), &engine_properties.switches[0],
613 &engine_properties.switches[engine_properties.switches_count]);
614 }
615
616 std::filesystem::path assets_path =
617 std::filesystem::u8path(engine_properties.assets_path);
618 std::filesystem::path icu_path =
619 std::filesystem::u8path(engine_properties.icu_data_path);
620 std::filesystem::path aot_library_path =
621 std::filesystem::u8path(engine_properties.aot_library_path);
622 if (assets_path.is_relative() || icu_path.is_relative() ||
623 (!aot_library_path.empty() && aot_library_path.is_relative())) {
624 // Treat relative paths as relative to the directory of this executable.
625 std::filesystem::path executable_location =
626 flutter::GetExecutableDirectory();
627 if (executable_location.empty()) {
628 std::cerr << "Unable to find executable location to resolve paths."
629 << std::endl;
630 return false;
631 }
632 assets_path = std::filesystem::path(executable_location) / assets_path;
633 icu_path = std::filesystem::path(executable_location) / icu_path;
634 if (!aot_library_path.empty()) {
635 aot_library_path =
636 std::filesystem::path(executable_location) / aot_library_path;
637 }
638 }
639 std::string assets_path_string = assets_path.u8string();
640 std::string icu_path_string = icu_path.u8string();
641 std::string lib_path_string = aot_library_path.u8string();
642
643 // Configure a task runner using the event loop.
644 engine_state->event_loop = std::move(event_loop);
645 FlutterTaskRunnerDescription platform_task_runner = {};
646 ConfigurePlatformTaskRunner(&platform_task_runner, engine_state);
647 FlutterCustomTaskRunners task_runners = {};
648 task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
649 task_runners.platform_task_runner = &platform_task_runner;
650
651 FlutterRendererConfig config = {};
652 config.type = kOpenGL;
653 config.open_gl.struct_size = sizeof(config.open_gl);
654 config.open_gl.make_current = EngineMakeContextCurrent;
655 config.open_gl.clear_current = EngineClearContext;
656 config.open_gl.present = EnginePresent;
657 config.open_gl.fbo_callback = EngineGetActiveFbo;
658 config.open_gl.make_resource_current = EngineMakeResourceContextCurrent;
659 // Don't provide a resolver in headless mode, since headless mode should
660 // work even if GLFW initialization failed.
661 if (engine_state->window_controller != nullptr) {
662 config.open_gl.gl_proc_resolver = EngineProcResolver;
663 }
664 FlutterProjectArgs args = {};
665 args.struct_size = sizeof(FlutterProjectArgs);
666 args.assets_path = assets_path_string.c_str();
667 args.icu_data_path = icu_path_string.c_str();
668 args.command_line_argc = static_cast<int>(argv.size());
669 args.command_line_argv = &argv[0];
670 args.platform_message_callback = EngineOnFlutterPlatformMessage;
671 args.custom_task_runners = &task_runners;
672
673 if (FlutterEngineRunsAOTCompiledDartCode()) {
674 engine_state->aot_data = LoadAotData(lib_path_string);
675 if (!engine_state->aot_data) {
676 std::cerr << "Unable to start engine without AOT data." << std::endl;
677 return false;
678 }
679 args.aot_data = engine_state->aot_data.get();
680 }
681
682 FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr;
683 auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args,
684 engine_state, &engine);
685 if (result != kSuccess || engine == nullptr) {
686 std::cerr << "Failed to start Flutter engine: error " << result
687 << std::endl;
688 return false;
689 }
690 engine_state->flutter_engine = engine;
691 return true;
692}
693
694// Populates |state|'s helper object fields that are common to normal and
695// headless mode.
696//
697// Window is optional; if present it will be provided to the created
698// PlatformHandler.
699static void SetUpCommonEngineState(FlutterDesktopEngineState* state,
700 GLFWwindow* window) {
701 // Messaging.
702 state->messenger = std::make_unique<FlutterDesktopMessenger>();
703 state->messenger->engine = state;
704 state->message_dispatcher =
705 std::make_unique<flutter::IncomingMessageDispatcher>(
706 state->messenger.get());
707
708 // Plugins.
709 state->plugin_registrar = std::make_unique<FlutterDesktopPluginRegistrar>();
710 state->plugin_registrar->engine = state;
711 state->internal_plugin_registrar =
712 std::make_unique<flutter::PluginRegistrar>(state->plugin_registrar.get());
713
714 // System channel handler.
715 state->platform_handler = std::make_unique<flutter::PlatformHandler>(
716 state->internal_plugin_registrar->messenger(), window);
717}
718
719bool FlutterDesktopInit() {
720 // Before making any GLFW calls, set up a logging error handler.
721 glfwSetErrorCallback(GLFWErrorCallback);
722 return glfwInit();
723}
724
725void FlutterDesktopTerminate() {
726 glfwTerminate();
727}
728
729FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow(
730 const FlutterDesktopWindowProperties& window_properties,
731 const FlutterDesktopEngineProperties& engine_properties) {
732 auto state = std::make_unique<FlutterDesktopWindowControllerState>();
733
734 // Create the window, and set the state as its user data.
735 if (window_properties.prevent_resize) {
736 glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
737 }
738#if defined(__linux__)
739 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
740#endif
741 state->window = UniqueGLFWwindowPtr(
742 glfwCreateWindow(window_properties.width, window_properties.height,
743 window_properties.title, NULL, NULL),
744 glfwDestroyWindow);
745 glfwDefaultWindowHints();
746 GLFWwindow* window = state->window.get();
747 if (window == nullptr) {
748 return nullptr;
749 }
750 GLFWClearCanvas(window);
751 glfwSetWindowUserPointer(window, state.get());
752
753 // Create the share window before starting the engine, since it may call
754 // EngineMakeResourceContextCurrent immediately.
755 state->resource_window = CreateShareWindowForWindow(window);
756
757 state->engine = std::make_unique<FlutterDesktopEngineState>();
758 state->engine->window_controller = state.get();
759
760 // Create an event loop for the window. It is not running yet.
761 auto event_loop = std::make_unique<flutter::GLFWEventLoop>(
762 std::this_thread::get_id(), // main GLFW thread
763 [engine_state = state->engine.get()](const auto* task) {
764 if (FlutterEngineRunTask(engine_state->flutter_engine, task) !=
765 kSuccess) {
766 std::cerr << "Could not post an engine task." << std::endl;
767 }
768 });
769
770 // Start the engine.
771 if (!RunFlutterEngine(state->engine.get(), engine_properties,
772 std::move(event_loop))) {
773 return nullptr;
774 }
775 SetUpCommonEngineState(state->engine.get(), window);
776
777 state->window_wrapper = std::make_unique<FlutterDesktopWindow>();
778 state->window_wrapper->window = window;
779
780 // Set up the keyboard handlers
781 auto internal_plugin_messenger =
782 state->engine->internal_plugin_registrar->messenger();
783 state->keyboard_hook_handlers.push_back(
784 std::make_unique<flutter::KeyEventHandler>(internal_plugin_messenger));
785 state->keyboard_hook_handlers.push_back(
786 std::make_unique<flutter::TextInputPlugin>(internal_plugin_messenger));
787
788 // Trigger an initial size callback to send size information to Flutter.
789 state->monitor_screen_coordinates_per_inch = GetScreenCoordinatesPerInch();
790 int width_px, height_px;
791 glfwGetFramebufferSize(window, &width_px, &height_px);
792 GLFWFramebufferSizeCallback(window, width_px, height_px);
793
794 // Set up GLFW callbacks for the window.
795 glfwSetFramebufferSizeCallback(window, GLFWFramebufferSizeCallback);
796 glfwSetWindowRefreshCallback(window, GLFWWindowRefreshCallback);
797 GLFWAssignEventCallbacks(window);
798
799 return state.release();
800}
801
802void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) {
803 FlutterDesktopPluginRegistrarRef registrar =
804 controller->engine->plugin_registrar.get();
805 if (registrar->destruction_handler) {
806 registrar->destruction_handler(registrar);
807 }
808 FlutterEngineShutdown(controller->engine->flutter_engine);
809 delete controller;
810}
811
812void FlutterDesktopWindowSetHoverEnabled(FlutterDesktopWindowRef flutter_window,
813 bool enabled) {
814 flutter_window->hover_tracking_enabled = enabled;
815 SetHoverCallbacksEnabled(flutter_window->window, enabled);
816}
817
818void FlutterDesktopWindowSetTitle(FlutterDesktopWindowRef flutter_window,
819 const char* title) {
820 GLFWwindow* window = flutter_window->window;
821 glfwSetWindowTitle(window, title);
822}
823
824void FlutterDesktopWindowSetIcon(FlutterDesktopWindowRef flutter_window,
825 uint8_t* pixel_data,
826 int width,
827 int height) {
828 GLFWimage image = {width, height, static_cast<unsigned char*>(pixel_data)};
829 glfwSetWindowIcon(flutter_window->window, pixel_data ? 1 : 0, &image);
830}
831
832void FlutterDesktopWindowGetFrame(FlutterDesktopWindowRef flutter_window,
833 int* x,
834 int* y,
835 int* width,
836 int* height) {
837 glfwGetWindowPos(flutter_window->window, x, y);
838 glfwGetWindowSize(flutter_window->window, width, height);
839 // The above gives content area size and position; adjust for the window
840 // decoration to give actual window frame.
841 int frame_left, frame_top, frame_right, frame_bottom;
842 glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top,
843 &frame_right, &frame_bottom);
844 if (x) {
845 *x -= frame_left;
846 }
847 if (y) {
848 *y -= frame_top;
849 }
850 if (width) {
851 *width += frame_left + frame_right;
852 }
853 if (height) {
854 *height += frame_top + frame_bottom;
855 }
856}
857
858void FlutterDesktopWindowSetFrame(FlutterDesktopWindowRef flutter_window,
859 int x,
860 int y,
861 int width,
862 int height) {
863 // Get the window decoration sizes to adjust, since the GLFW setters take
864 // content position and size.
865 int frame_left, frame_top, frame_right, frame_bottom;
866 glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top,
867 &frame_right, &frame_bottom);
868 glfwSetWindowPos(flutter_window->window, x + frame_left, y + frame_top);
869 glfwSetWindowSize(flutter_window->window, width - frame_left - frame_right,
870 height - frame_top - frame_bottom);
871}
872
873double FlutterDesktopWindowGetScaleFactor(
874 FlutterDesktopWindowRef flutter_window) {
875 return flutter_window->pixels_per_screen_coordinate;
876}
877
878void FlutterDesktopWindowSetPixelRatioOverride(
879 FlutterDesktopWindowRef flutter_window,
880 double pixel_ratio) {
881 flutter_window->pixel_ratio_override = pixel_ratio;
882 // Send a metrics update using the new pixel ratio.
883 int width_px, height_px;
884 glfwGetFramebufferSize(flutter_window->window, &width_px, &height_px);
885 if (width_px > 0 && height_px > 0) {
886 auto* controller = GetWindowController(flutter_window->window);
887 SendWindowMetrics(controller, width_px, height_px);
888 }
889}
890
891void FlutterDesktopWindowSetSizeLimits(FlutterDesktopWindowRef flutter_window,
892 FlutterDesktopSize minimum_size,
893 FlutterDesktopSize maximum_size) {
894 glfwSetWindowSizeLimits(flutter_window->window, minimum_size.width,
895 minimum_size.height, maximum_size.width,
896 maximum_size.height);
897}
898
899bool FlutterDesktopRunWindowEventLoopWithTimeout(
900 FlutterDesktopWindowControllerRef controller,
901 uint32_t timeout_milliseconds) {
902 FlutterDesktopRunEngineEventLoopWithTimeout(controller->engine.get(),
903 timeout_milliseconds);
904 return !glfwWindowShouldClose(controller->window.get());
905}
906
907FlutterDesktopWindowRef FlutterDesktopGetWindow(
908 FlutterDesktopWindowControllerRef controller) {
909 // Currently, one registrar acts as the registrar for all plugins, so the
910 // name is ignored. It is part of the API to reduce churn in the future when
911 // aligning more closely with the Flutter registrar system.
912 return controller->window_wrapper.get();
913}
914
915FlutterDesktopEngineRef FlutterDesktopGetEngine(
916 FlutterDesktopWindowControllerRef controller) {
917 return controller->engine.get();
918}
919
920FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar(
921 FlutterDesktopEngineRef engine,
922 const char* plugin_name) {
923 // Currently, one registrar acts as the registrar for all plugins, so the
924 // name is ignored. It is part of the API to reduce churn in the future when
925 // aligning more closely with the Flutter registrar system.
926 return engine->plugin_registrar.get();
927}
928
929FlutterDesktopEngineRef FlutterDesktopRunEngine(
930 const FlutterDesktopEngineProperties& properties) {
931 auto engine_state = std::make_unique<FlutterDesktopEngineState>();
932
933 auto event_loop = std::make_unique<flutter::HeadlessEventLoop>(
934 std::this_thread::get_id(),
935 [state = engine_state.get()](const auto* task) {
936 if (FlutterEngineRunTask(state->flutter_engine, task) != kSuccess) {
937 std::cerr << "Could not post an engine task." << std::endl;
938 }
939 });
940
941 if (!RunFlutterEngine(engine_state.get(), properties,
942 std::move(event_loop))) {
943 return nullptr;
944 }
945 SetUpCommonEngineState(engine_state.get(), nullptr);
946
947 return engine_state.release();
948}
949
950void FlutterDesktopRunEngineEventLoopWithTimeout(
951 FlutterDesktopEngineRef engine,
952 uint32_t timeout_milliseconds) {
953 std::chrono::nanoseconds wait_duration =
954 timeout_milliseconds == 0
955 ? std::chrono::nanoseconds::max()
956 : std::chrono::milliseconds(timeout_milliseconds);
957 engine->event_loop->WaitForEvents(wait_duration);
958}
959
960bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine) {
961 auto result = FlutterEngineShutdown(engine->flutter_engine);
962 delete engine;
963 return (result == kSuccess);
964}
965
966void FlutterDesktopRegistrarEnableInputBlocking(
967 FlutterDesktopPluginRegistrarRef registrar,
968 const char* channel) {
969 registrar->engine->message_dispatcher->EnableInputBlockingForChannel(channel);
970}
971
972FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger(
973 FlutterDesktopPluginRegistrarRef registrar) {
974 return registrar->engine->messenger.get();
975}
976
977void FlutterDesktopRegistrarSetDestructionHandler(
978 FlutterDesktopPluginRegistrarRef registrar,
979 FlutterDesktopOnRegistrarDestroyed callback) {
980 registrar->destruction_handler = callback;
981}
982
983FlutterDesktopWindowRef FlutterDesktopRegistrarGetWindow(
984 FlutterDesktopPluginRegistrarRef registrar) {
985 FlutterDesktopWindowControllerState* controller =
986 registrar->engine->window_controller;
987 if (!controller) {
988 return nullptr;
989 }
990 return controller->window_wrapper.get();
991}
992
993bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger,
994 const char* channel,
995 const uint8_t* message,
996 const size_t message_size,
997 const FlutterDesktopBinaryReply reply,
998 void* user_data) {
999 FlutterPlatformMessageResponseHandle* response_handle = nullptr;
1000 if (reply != nullptr && user_data != nullptr) {
1001 FlutterEngineResult result = FlutterPlatformMessageCreateResponseHandle(
1002 messenger->engine->flutter_engine, reply, user_data, &response_handle);
1003 if (result != kSuccess) {
1004 std::cout << "Failed to create response handle\n";
1005 return false;
1006 }
1007 }
1008
1009 FlutterPlatformMessage platform_message = {
1010 sizeof(FlutterPlatformMessage),
1011 channel,
1012 message,
1013 message_size,
1014 response_handle,
1015 };
1016
1017 FlutterEngineResult message_result = FlutterEngineSendPlatformMessage(
1018 messenger->engine->flutter_engine, &platform_message);
1019
1020 if (response_handle != nullptr) {
1021 FlutterPlatformMessageReleaseResponseHandle(
1022 messenger->engine->flutter_engine, response_handle);
1023 }
1024
1025 return message_result == kSuccess;
1026}
1027
1028bool FlutterDesktopMessengerSend(FlutterDesktopMessengerRef messenger,
1029 const char* channel,
1030 const uint8_t* message,
1031 const size_t message_size) {
1032 return FlutterDesktopMessengerSendWithReply(messenger, channel, message,
1033 message_size, nullptr, nullptr);
1034}
1035
1036void FlutterDesktopMessengerSendResponse(
1037 FlutterDesktopMessengerRef messenger,
1038 const FlutterDesktopMessageResponseHandle* handle,
1039 const uint8_t* data,
1040 size_t data_length) {
1041 FlutterEngineSendPlatformMessageResponse(messenger->engine->flutter_engine,
1042 handle, data, data_length);
1043}
1044
1045void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger,
1046 const char* channel,
1047 FlutterDesktopMessageCallback callback,
1048 void* user_data) {
1049 messenger->engine->message_dispatcher->SetMessageCallback(channel, callback,
1050 user_data);
1051}
1052