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 | |
37 | using UniqueGLFWwindowPtr = std::unique_ptr<GLFWwindow, void (*)(GLFWwindow*)>; |
38 | |
39 | static_assert(FLUTTER_ENGINE_VERSION == 1, "" ); |
40 | |
41 | const int kFlutterDesktopDontCare = GLFW_DONT_CARE; |
42 | |
43 | static constexpr double kDpPerInch = 160.0; |
44 | |
45 | // Struct for storing state within an instance of the GLFW Window. |
46 | struct 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. |
76 | struct 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. |
96 | struct AOTDataDeleter { |
97 | void operator()(FlutterEngineAOTData aot_data) { |
98 | FlutterEngineCollectAOTData(aot_data); |
99 | } |
100 | }; |
101 | |
102 | using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>; |
103 | |
104 | // Struct for storing state of a Flutter engine instance. |
105 | struct 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. |
136 | struct 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. |
145 | struct 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. |
151 | static 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. |
159 | static 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. |
171 | static 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. |
185 | static 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|. |
202 | static 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. |
226 | static 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. |
244 | static 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. |
258 | void 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. |
277 | static 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. |
322 | static 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. |
330 | static 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. |
340 | static 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. |
349 | static 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. |
358 | static 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. |
392 | static 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. |
408 | static 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. |
416 | static 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. |
428 | static 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. |
436 | static 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. |
448 | static 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 |
458 | static 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 | |
489 | static 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 | |
501 | static 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 | |
513 | static 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 | |
525 | static 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 | |
537 | static 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. |
543 | static 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. |
555 | static 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 | |
565 | static 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. |
571 | UniqueAotDataPtr 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. |
603 | static 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. |
699 | static 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 | |
719 | bool FlutterDesktopInit() { |
720 | // Before making any GLFW calls, set up a logging error handler. |
721 | glfwSetErrorCallback(GLFWErrorCallback); |
722 | return glfwInit(); |
723 | } |
724 | |
725 | void FlutterDesktopTerminate() { |
726 | glfwTerminate(); |
727 | } |
728 | |
729 | FlutterDesktopWindowControllerRef 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 | |
802 | void 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 | |
812 | void FlutterDesktopWindowSetHoverEnabled(FlutterDesktopWindowRef flutter_window, |
813 | bool enabled) { |
814 | flutter_window->hover_tracking_enabled = enabled; |
815 | SetHoverCallbacksEnabled(flutter_window->window, enabled); |
816 | } |
817 | |
818 | void FlutterDesktopWindowSetTitle(FlutterDesktopWindowRef flutter_window, |
819 | const char* title) { |
820 | GLFWwindow* window = flutter_window->window; |
821 | glfwSetWindowTitle(window, title); |
822 | } |
823 | |
824 | void 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 | |
832 | void 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 | |
858 | void 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 | |
873 | double FlutterDesktopWindowGetScaleFactor( |
874 | FlutterDesktopWindowRef flutter_window) { |
875 | return flutter_window->pixels_per_screen_coordinate; |
876 | } |
877 | |
878 | void 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 | |
891 | void 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 | |
899 | bool 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 | |
907 | FlutterDesktopWindowRef 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 | |
915 | FlutterDesktopEngineRef FlutterDesktopGetEngine( |
916 | FlutterDesktopWindowControllerRef controller) { |
917 | return controller->engine.get(); |
918 | } |
919 | |
920 | FlutterDesktopPluginRegistrarRef 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 | |
929 | FlutterDesktopEngineRef 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 | |
950 | void 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 | |
960 | bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine) { |
961 | auto result = FlutterEngineShutdown(engine->flutter_engine); |
962 | delete engine; |
963 | return (result == kSuccess); |
964 | } |
965 | |
966 | void FlutterDesktopRegistrarEnableInputBlocking( |
967 | FlutterDesktopPluginRegistrarRef registrar, |
968 | const char* channel) { |
969 | registrar->engine->message_dispatcher->EnableInputBlockingForChannel(channel); |
970 | } |
971 | |
972 | FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger( |
973 | FlutterDesktopPluginRegistrarRef registrar) { |
974 | return registrar->engine->messenger.get(); |
975 | } |
976 | |
977 | void FlutterDesktopRegistrarSetDestructionHandler( |
978 | FlutterDesktopPluginRegistrarRef registrar, |
979 | FlutterDesktopOnRegistrarDestroyed callback) { |
980 | registrar->destruction_handler = callback; |
981 | } |
982 | |
983 | FlutterDesktopWindowRef 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 | |
993 | bool 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 | |
1028 | bool 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 | |
1036 | void 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 | |
1045 | void 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 | |