| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "flutter/shell/platform/glfw/public/flutter_glfw.h" | |
| #include <GLFW/glfw3.h> | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <chrono> | |
| #include <cstdlib> | |
| #include <filesystem> | |
| #include <iostream> | |
| #include "flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h" | |
| #include "flutter/shell/platform/common/incoming_message_dispatcher.h" | |
| #include "flutter/shell/platform/common/path_utils.h" | |
| #include "flutter/shell/platform/embedder/embedder.h" | |
| #include "flutter/shell/platform/glfw/glfw_event_loop.h" | |
| #include "flutter/shell/platform/glfw/headless_event_loop.h" | |
| #include "flutter/shell/platform/glfw/key_event_handler.h" | |
| #include "flutter/shell/platform/glfw/keyboard_hook_handler.h" | |
| #include "flutter/shell/platform/glfw/platform_handler.h" | |
| #include "flutter/shell/platform/glfw/system_utils.h" | |
| #include "flutter/shell/platform/glfw/text_input_plugin.h" | |
| // GLFW_TRUE & GLFW_FALSE are introduced since libglfw-3.3, | |
| // add definitions here to compile under the old versions. | |
| #ifndef GLFW_TRUE | |
| #define GLFW_TRUE 1 | |
| #endif | |
| #ifndef GLFW_FALSE | |
| #define GLFW_FALSE 0 | |
| #endif | |
| using UniqueGLFWwindowPtr = std::unique_ptr<GLFWwindow, void (*)(GLFWwindow*)>; | |
| static_assert(FLUTTER_ENGINE_VERSION == 1, ""); | |
| const int kFlutterDesktopDontCare = GLFW_DONT_CARE; | |
| static constexpr double kDpPerInch = 160.0; | |
| // Struct for storing state within an instance of the GLFW Window. | |
| struct FlutterDesktopWindowControllerState { | |
| // The GLFW window that is bound to this state object. | |
| UniqueGLFWwindowPtr window = UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow); | |
| // The invisible GLFW window used to upload resources in the background. | |
| UniqueGLFWwindowPtr resource_window = | |
| UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow); | |
| // The state associated with the engine. | |
| std::unique_ptr<FlutterDesktopEngineState> engine; | |
| // The window handle given to API clients. | |
| std::unique_ptr<FlutterDesktopWindow> window_wrapper; | |
| // Handlers for keyboard events from GLFW. | |
| std::vector<std::unique_ptr<flutter::KeyboardHookHandler>> | |
| keyboard_hook_handlers; | |
| // Whether or not the pointer has been added (or if tracking is enabled, | |
| // has been added since it was last removed). | |
| bool pointer_currently_added = false; | |
| // Whether or not the pointer is down. | |
| bool pointer_currently_down = false; | |
| // The currently pressed buttons, as represented in FlutterPointerEvent. | |
| int64_t buttons = 0; | |
| // The screen coordinates per inch on the primary monitor. Defaults to a sane | |
| // value based on pixel_ratio 1.0. | |
| double monitor_screen_coordinates_per_inch = kDpPerInch; | |
| }; | |
| // Opaque reference for the GLFW window itself. This is separate from the | |
| // controller so that it can be provided to plugins without giving them access | |
| // to all of the controller-based functionality. | |
| struct FlutterDesktopWindow { | |
| // The GLFW window that (indirectly) owns this state object. | |
| GLFWwindow* window; | |
| // Whether or not to track mouse movements to send kHover events. | |
| bool hover_tracking_enabled = true; | |
| // The ratio of pixels per screen coordinate for the window. | |
| double pixels_per_screen_coordinate = 1.0; | |
| // If non-zero, a forced pixel ratio to use instead of one computed based on | |
| // screen information. | |
| double pixel_ratio_override = 0.0; | |
| // Resizing triggers a window refresh, but the resize already updates Flutter. | |
| // To avoid double messages, the refresh after each resize is skipped. | |
| bool skip_next_window_refresh = false; | |
| }; | |
| // Custom deleter for FlutterEngineAOTData. | |
| struct AOTDataDeleter { | |
| void operator()(FlutterEngineAOTData aot_data) { | |
| FlutterEngineCollectAOTData(aot_data); | |
| } | |
| }; | |
| using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>; | |
| /// Maintains one ref on the FlutterDesktopMessenger's internal reference count. | |
| using FlutterDesktopMessengerReferenceOwner = | |
| std::unique_ptr<FlutterDesktopMessenger, | |
| decltype(&FlutterDesktopMessengerRelease)>; | |
| // Struct for storing state of a Flutter engine instance. | |
| struct FlutterDesktopEngineState { | |
| // The handle to the Flutter engine instance. | |
| FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine; | |
| // The event loop for the main thread that allows for delayed task execution. | |
| std::unique_ptr<flutter::EventLoop> event_loop; | |
| // The plugin messenger handle given to API clients. | |
| FlutterDesktopMessengerReferenceOwner messenger = { | |
| nullptr, [](FlutterDesktopMessengerRef ref) {}}; | |
| // Message dispatch manager for messages from the Flutter engine. | |
| std::unique_ptr<flutter::IncomingMessageDispatcher> message_dispatcher; | |
| // The plugin registrar handle given to API clients. | |
| std::unique_ptr<FlutterDesktopPluginRegistrar> plugin_registrar; | |
| // The plugin registrar managing internal plugins. | |
| std::unique_ptr<flutter::PluginRegistrar> internal_plugin_registrar; | |
| // Handler for the flutter/platform channel. | |
| std::unique_ptr<flutter::PlatformHandler> platform_handler; | |
| // The controller associated with this engine instance, if any. | |
| // This will always be null for a headless engine. | |
| FlutterDesktopWindowControllerState* window_controller = nullptr; | |
| // AOT data for this engine instance, if applicable. | |
| UniqueAotDataPtr aot_data = nullptr; | |
| }; | |
| // State associated with the plugin registrar. | |
| struct FlutterDesktopPluginRegistrar { | |
| // The engine that backs this registrar. | |
| FlutterDesktopEngineState* engine; | |
| // Callback to be called on registrar destruction. | |
| FlutterDesktopOnPluginRegistrarDestroyed destruction_handler; | |
| }; | |
| // State associated with the messenger used to communicate with the engine. | |
| struct FlutterDesktopMessenger { | |
| FlutterDesktopMessenger() = default; | |
| /// Increments the reference count. | |
| /// | |
| /// Thread-safe. | |
| void AddRef() { ref_count_.fetch_add(1); } | |
| /// Decrements the reference count and deletes the object if the count has | |
| /// gone to zero. | |
| /// | |
| /// Thread-safe. | |
| void Release() { | |
| int32_t old_count = ref_count_.fetch_sub(1); | |
| if (old_count <= 1) { | |
| delete this; | |
| } | |
| } | |
| /// Getter for the engine field. | |
| FlutterDesktopEngineState* GetEngine() const { return engine_; } | |
| /// Setter for the engine field. | |
| /// Thread-safe. | |
| void SetEngine(FlutterDesktopEngineState* engine) { | |
| std::scoped_lock lock(mutex_); | |
| engine_ = engine; | |
| } | |
| /// Returns the mutex associated with the |FlutterDesktopMessenger|. | |
| /// | |
| /// This mutex is used to synchronize reading or writing state inside the | |
| /// |FlutterDesktopMessenger| (ie |engine_|). | |
| std::mutex& GetMutex() { return mutex_; } | |
| FlutterDesktopMessenger(const FlutterDesktopMessenger& value) = delete; | |
| FlutterDesktopMessenger& operator=(const FlutterDesktopMessenger& value) = | |
| delete; | |
| private: | |
| // The engine that backs this messenger. | |
| FlutterDesktopEngineState* engine_; | |
| std::atomic<int32_t> ref_count_ = 0; | |
| std::mutex mutex_; | |
| }; | |
| FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef( | |
| FlutterDesktopMessengerRef messenger) { | |
| messenger->AddRef(); | |
| return messenger; | |
| } | |
| void FlutterDesktopMessengerRelease(FlutterDesktopMessengerRef messenger) { | |
| messenger->Release(); | |
| } | |
| bool FlutterDesktopMessengerIsAvailable(FlutterDesktopMessengerRef messenger) { | |
| return messenger->GetEngine() != nullptr; | |
| } | |
| FlutterDesktopMessengerRef FlutterDesktopMessengerLock( | |
| FlutterDesktopMessengerRef messenger) { | |
| messenger->GetMutex().lock(); | |
| return messenger; | |
| } | |
| void FlutterDesktopMessengerUnlock(FlutterDesktopMessengerRef messenger) { | |
| messenger->GetMutex().unlock(); | |
| } | |
| // Retrieves state bag for the window in question from the GLFWWindow. | |
| static FlutterDesktopWindowControllerState* GetWindowController( | |
| GLFWwindow* window) { | |
| return reinterpret_cast<FlutterDesktopWindowControllerState*>( | |
| glfwGetWindowUserPointer(window)); | |
| } | |
| // Creates and returns an invisible GLFW window that shares |window|'s resource | |
| // context. | |
| static UniqueGLFWwindowPtr CreateShareWindowForWindow(GLFWwindow* window) { | |
| glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); | |
| glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); | |
| #if defined(__linux__) | |
| glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); | |
| #endif | |
| GLFWwindow* share_window = glfwCreateWindow(1, 1, "", NULL, window); | |
| glfwDefaultWindowHints(); | |
| return UniqueGLFWwindowPtr(share_window, glfwDestroyWindow); | |
| } | |
| // Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage. | |
| static FlutterDesktopMessage ConvertToDesktopMessage( | |
| const FlutterPlatformMessage& engine_message) { | |
| FlutterDesktopMessage message = {}; | |
| message.struct_size = sizeof(message); | |
| message.channel = engine_message.channel; | |
| message.message = engine_message.message; | |
| message.message_size = engine_message.message_size; | |
| message.response_handle = engine_message.response_handle; | |
| return message; | |
| } | |
| // Returns the number of screen coordinates per inch for the main monitor. | |
| // If the information is unavailable, returns a default value that assumes | |
| // that a screen coordinate is one dp. | |
| static double GetScreenCoordinatesPerInch() { | |
| auto* primary_monitor = glfwGetPrimaryMonitor(); | |
| if (primary_monitor == nullptr) { | |
| return kDpPerInch; | |
| } | |
| auto* primary_monitor_mode = glfwGetVideoMode(primary_monitor); | |
| int primary_monitor_width_mm; | |
| glfwGetMonitorPhysicalSize(primary_monitor, &primary_monitor_width_mm, | |
| nullptr); | |
| if (primary_monitor_width_mm == 0) { | |
| return kDpPerInch; | |
| } | |
| return primary_monitor_mode->width / (primary_monitor_width_mm / 25.4); | |
| } | |
| // Sends a window metrics update to the Flutter engine using the given | |
| // framebuffer size and the current window information in |state|. | |
| static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller, | |
| int width, | |
| int height) { | |
| double dpi = controller->window_wrapper->pixels_per_screen_coordinate * | |
| controller->monitor_screen_coordinates_per_inch; | |
| FlutterWindowMetricsEvent event = {}; | |
| event.struct_size = sizeof(event); | |
| event.width = width; | |
| event.height = height; | |
| if (controller->window_wrapper->pixel_ratio_override == 0.0) { | |
| // The Flutter pixel_ratio is defined as DPI/dp. Limit the ratio to a | |
| // minimum of 1 to avoid rendering a smaller UI on standard resolution | |
| // monitors. | |
| event.pixel_ratio = std::max(dpi / kDpPerInch, 1.0); | |
| } else { | |
| event.pixel_ratio = controller->window_wrapper->pixel_ratio_override; | |
| } | |
| FlutterEngineSendWindowMetricsEvent(controller->engine->flutter_engine, | |
| &event); | |
| } | |
| // Populates |task_runner| with a description that uses |engine_state|'s event | |
| // loop to run tasks. | |
| static void ConfigurePlatformTaskRunner( | |
| FlutterTaskRunnerDescription* task_runner, | |
| FlutterDesktopEngineState* engine_state) { | |
| task_runner->struct_size = sizeof(FlutterTaskRunnerDescription); | |
| task_runner->user_data = engine_state; | |
| task_runner->runs_task_on_current_thread_callback = [](void* state) -> bool { | |
| return reinterpret_cast<FlutterDesktopEngineState*>(state) | |
| ->event_loop->RunsTasksOnCurrentThread(); | |
| }; | |
| task_runner->post_task_callback = | |
| [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void { | |
| reinterpret_cast<FlutterDesktopEngineState*>(state)->event_loop->PostTask( | |
| task, target_time_nanos); | |
| }; | |
| } | |
| // When GLFW calls back to the window with a framebuffer size change, notify | |
| // FlutterEngine about the new window metrics. | |
| static void GLFWFramebufferSizeCallback(GLFWwindow* window, | |
| int width_px, | |
| int height_px) { | |
| int width; | |
| glfwGetWindowSize(window, &width, nullptr); | |
| auto* controller = GetWindowController(window); | |
| controller->window_wrapper->pixels_per_screen_coordinate = | |
| width > 0 ? width_px / width : 1; | |
| SendWindowMetrics(controller, width_px, height_px); | |
| controller->window_wrapper->skip_next_window_refresh = true; | |
| } | |
| // Indicates that the window needs to be redrawn. | |
| void GLFWWindowRefreshCallback(GLFWwindow* window) { | |
| auto* controller = GetWindowController(window); | |
| if (controller->window_wrapper->skip_next_window_refresh) { | |
| controller->window_wrapper->skip_next_window_refresh = false; | |
| return; | |
| } | |
| // There's no engine API to request a redraw explicitly, so instead send a | |
| // window metrics event with the current size to trigger it. | |
| int width_px, height_px; | |
| glfwGetFramebufferSize(window, &width_px, &height_px); | |
| if (width_px > 0 && height_px > 0) { | |
| SendWindowMetrics(controller, width_px, height_px); | |
| } | |
| } | |
| // Sends a pointer event to the Flutter engine based on the given data. | |
| // | |
| // Any coordinate/distance values in |event_data| should be in screen | |
| // coordinates; they will be adjusted to pixel values before being sent. | |
| static void SendPointerEventWithData(GLFWwindow* window, | |
| const FlutterPointerEvent& event_data) { | |
| auto* controller = GetWindowController(window); | |
| // If sending anything other than an add, and the pointer isn't already added, | |
| // synthesize an add to satisfy Flutter's expectations about events. | |
| if (!controller->pointer_currently_added && | |
| event_data.phase != FlutterPointerPhase::kAdd) { | |
| FlutterPointerEvent event = {}; | |
| event.phase = FlutterPointerPhase::kAdd; | |
| event.x = event_data.x; | |
| event.y = event_data.y; | |
| SendPointerEventWithData(window, event); | |
| } | |
| // Don't double-add (e.g., if events are delivered out of order, so an add has | |
| // already been synthesized). | |
| if (controller->pointer_currently_added && | |
| event_data.phase == FlutterPointerPhase::kAdd) { | |
| return; | |
| } | |
| FlutterPointerEvent event = event_data; | |
| // Set metadata that's always the same regardless of the event. | |
| event.struct_size = sizeof(event); | |
| event.timestamp = | |
| std::chrono::duration_cast<std::chrono::microseconds>( | |
| std::chrono::high_resolution_clock::now().time_since_epoch()) | |
| .count(); | |
| event.device_kind = FlutterPointerDeviceKind::kFlutterPointerDeviceKindMouse; | |
| event.buttons = | |
| (event.phase == FlutterPointerPhase::kAdd) ? 0 : controller->buttons; | |
| // Convert all screen coordinates to pixel coordinates. | |
| double pixels_per_coordinate = | |
| controller->window_wrapper->pixels_per_screen_coordinate; | |
| event.x *= pixels_per_coordinate; | |
| event.y *= pixels_per_coordinate; | |
| event.scroll_delta_x *= pixels_per_coordinate; | |
| event.scroll_delta_y *= pixels_per_coordinate; | |
| FlutterEngineSendPointerEvent(controller->engine->flutter_engine, &event, 1); | |
| if (event_data.phase == FlutterPointerPhase::kAdd) { | |
| controller->pointer_currently_added = true; | |
| } else if (event_data.phase == FlutterPointerPhase::kRemove) { | |
| controller->pointer_currently_added = false; | |
| } else if (event_data.phase == FlutterPointerPhase::kDown) { | |
| controller->pointer_currently_down = true; | |
| } else if (event_data.phase == FlutterPointerPhase::kUp) { | |
| controller->pointer_currently_down = false; | |
| } | |
| } | |
| // Updates |event_data| with the current location of the mouse cursor. | |
| static void SetEventLocationFromCursorPosition( | |
| GLFWwindow* window, | |
| FlutterPointerEvent* event_data) { | |
| glfwGetCursorPos(window, &event_data->x, &event_data->y); | |
| } | |
| // Set's |event_data|'s phase depending on the current mouse state. | |
| // If a kUp or kDown event is triggered while the current state is already | |
| // up/down, a hover/move will be called instead to avoid a crash in the Flutter | |
| // engine. | |
| static void SetEventPhaseFromCursorButtonState(GLFWwindow* window, | |
| FlutterPointerEvent* event_data, | |
| int64_t buttons) { | |
| auto* controller = GetWindowController(window); | |
| event_data->phase = | |
| (buttons == 0) | |
| ? (controller->pointer_currently_down ? FlutterPointerPhase::kUp | |
| : FlutterPointerPhase::kHover) | |
| : (controller->pointer_currently_down ? FlutterPointerPhase::kMove | |
| : FlutterPointerPhase::kDown); | |
| } | |
| // Reports the mouse entering or leaving the Flutter view. | |
| static void GLFWCursorEnterCallback(GLFWwindow* window, int entered) { | |
| FlutterPointerEvent event = {}; | |
| event.phase = | |
| entered ? FlutterPointerPhase::kAdd : FlutterPointerPhase::kRemove; | |
| SetEventLocationFromCursorPosition(window, &event); | |
| SendPointerEventWithData(window, event); | |
| } | |
| // Reports mouse movement to the Flutter engine. | |
| static void GLFWCursorPositionCallback(GLFWwindow* window, double x, double y) { | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| auto* controller = GetWindowController(window); | |
| SetEventPhaseFromCursorButtonState(window, &event, controller->buttons); | |
| SendPointerEventWithData(window, event); | |
| } | |
| // Reports mouse button press to the Flutter engine. | |
| static void GLFWMouseButtonCallback(GLFWwindow* window, | |
| int key, | |
| int action, | |
| int mods) { | |
| int64_t button; | |
| if (key == GLFW_MOUSE_BUTTON_LEFT) { | |
| button = FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary; | |
| } else if (key == GLFW_MOUSE_BUTTON_RIGHT) { | |
| button = FlutterPointerMouseButtons::kFlutterPointerButtonMouseSecondary; | |
| } else { | |
| return; | |
| } | |
| auto* controller = GetWindowController(window); | |
| controller->buttons = (action == GLFW_PRESS) ? controller->buttons | button | |
| : controller->buttons & ~button; | |
| FlutterPointerEvent event = {}; | |
| SetEventPhaseFromCursorButtonState(window, &event, controller->buttons); | |
| SetEventLocationFromCursorPosition(window, &event); | |
| SendPointerEventWithData(window, event); | |
| // If mouse tracking isn't already enabled, turn it on for the duration of | |
| // the drag to generate kMove events. | |
| bool hover_enabled = | |
| GetWindowController(window)->window_wrapper->hover_tracking_enabled; | |
| if (!hover_enabled) { | |
| glfwSetCursorPosCallback(window, (controller->buttons != 0) | |
| ? GLFWCursorPositionCallback | |
| : nullptr); | |
| } | |
| // Disable enter/exit events while the mouse button is down; GLFW will send | |
| // an exit event when the mouse button is released, and the pointer should | |
| // stay valid until then. | |
| if (hover_enabled) { | |
| glfwSetCursorEnterCallback( | |
| window, (controller->buttons != 0) ? nullptr : GLFWCursorEnterCallback); | |
| } | |
| } | |
| // Reports scroll wheel events to the Flutter engine. | |
| static void GLFWScrollCallback(GLFWwindow* window, | |
| double delta_x, | |
| double delta_y) { | |
| FlutterPointerEvent event = {}; | |
| SetEventLocationFromCursorPosition(window, &event); | |
| auto* controller = GetWindowController(window); | |
| SetEventPhaseFromCursorButtonState(window, &event, controller->buttons); | |
| event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; | |
| // TODO(chrome-bot): See if this can be queried from the OS; this value is | |
| // chosen arbitrarily to get something that feels reasonable. | |
| const int kScrollOffsetMultiplier = 20; | |
| event.scroll_delta_x = delta_x * kScrollOffsetMultiplier; | |
| event.scroll_delta_y = -delta_y * kScrollOffsetMultiplier; | |
| SendPointerEventWithData(window, event); | |
| } | |
| // Passes character input events to registered handlers. | |
| static void GLFWCharCallback(GLFWwindow* window, unsigned int code_point) { | |
| for (const auto& handler : | |
| GetWindowController(window)->keyboard_hook_handlers) { | |
| handler->CharHook(window, code_point); | |
| } | |
| } | |
| // Passes raw key events to registered handlers. | |
| static void GLFWKeyCallback(GLFWwindow* window, | |
| int key, | |
| int scancode, | |
| int action, | |
| int mods) { | |
| for (const auto& handler : | |
| GetWindowController(window)->keyboard_hook_handlers) { | |
| handler->KeyboardHook(window, key, scancode, action, mods); | |
| } | |
| } | |
| // Enables/disables the callbacks related to mouse tracking. | |
| static void SetHoverCallbacksEnabled(GLFWwindow* window, bool enabled) { | |
| glfwSetCursorEnterCallback(window, | |
| enabled ? GLFWCursorEnterCallback : nullptr); | |
| glfwSetCursorPosCallback(window, | |
| enabled ? GLFWCursorPositionCallback : nullptr); | |
| } | |
| // Flushes event queue and then assigns default window callbacks. | |
| static void GLFWAssignEventCallbacks(GLFWwindow* window) { | |
| glfwPollEvents(); | |
| glfwSetKeyCallback(window, GLFWKeyCallback); | |
| glfwSetCharCallback(window, GLFWCharCallback); | |
| glfwSetMouseButtonCallback(window, GLFWMouseButtonCallback); | |
| glfwSetScrollCallback(window, GLFWScrollCallback); | |
| if (GetWindowController(window)->window_wrapper->hover_tracking_enabled) { | |
| SetHoverCallbacksEnabled(window, true); | |
| } | |
| } | |
| // Clears default window events. | |
| static void GLFWClearEventCallbacks(GLFWwindow* window) { | |
| glfwSetKeyCallback(window, nullptr); | |
| glfwSetCharCallback(window, nullptr); | |
| glfwSetMouseButtonCallback(window, nullptr); | |
| glfwSetScrollCallback(window, nullptr); | |
| SetHoverCallbacksEnabled(window, false); | |
| } | |
| // The Flutter Engine calls out to this function when new platform messages are | |
| // available | |
| static void EngineOnFlutterPlatformMessage( | |
| const FlutterPlatformMessage* engine_message, | |
| void* user_data) { | |
| if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) { | |
| std::cerr << "Invalid message size received. Expected: " | |
| << sizeof(FlutterPlatformMessage) << " but received " | |
| << engine_message->struct_size << std::endl; | |
| return; | |
| } | |
| FlutterDesktopEngineState* engine_state = | |
| static_cast<FlutterDesktopEngineState*>(user_data); | |
| GLFWwindow* window = engine_state->window_controller == nullptr | |
| ? nullptr | |
| : engine_state->window_controller->window.get(); | |
| auto message = ConvertToDesktopMessage(*engine_message); | |
| engine_state->message_dispatcher->HandleMessage( | |
| message, | |
| [window] { | |
| if (window) { | |
| GLFWClearEventCallbacks(window); | |
| } | |
| }, | |
| [window] { | |
| if (window) { | |
| GLFWAssignEventCallbacks(window); | |
| } | |
| }); | |
| } | |
| static bool EngineMakeContextCurrent(void* user_data) { | |
| FlutterDesktopEngineState* engine_state = | |
| static_cast<FlutterDesktopEngineState*>(user_data); | |
| FlutterDesktopWindowControllerState* window_controller = | |
| engine_state->window_controller; | |
| if (!window_controller) { | |
| return false; | |
| } | |
| glfwMakeContextCurrent(window_controller->window.get()); | |
| return true; | |
| } | |
| static bool EngineMakeResourceContextCurrent(void* user_data) { | |
| FlutterDesktopEngineState* engine_state = | |
| static_cast<FlutterDesktopEngineState*>(user_data); | |
| FlutterDesktopWindowControllerState* window_controller = | |
| engine_state->window_controller; | |
| if (!window_controller) { | |
| return false; | |
| } | |
| glfwMakeContextCurrent(window_controller->resource_window.get()); | |
| return true; | |
| } | |
| static bool EngineClearContext(void* user_data) { | |
| FlutterDesktopEngineState* engine_state = | |
| static_cast<FlutterDesktopEngineState*>(user_data); | |
| FlutterDesktopWindowControllerState* window_controller = | |
| engine_state->window_controller; | |
| if (!window_controller) { | |
| return false; | |
| } | |
| glfwMakeContextCurrent(nullptr); | |
| return true; | |
| } | |
| static bool EnginePresent(void* user_data) { | |
| FlutterDesktopEngineState* engine_state = | |
| static_cast<FlutterDesktopEngineState*>(user_data); | |
| FlutterDesktopWindowControllerState* window_controller = | |
| engine_state->window_controller; | |
| if (!window_controller) { | |
| return false; | |
| } | |
| glfwSwapBuffers(window_controller->window.get()); | |
| return true; | |
| } | |
| static uint32_t EngineGetActiveFbo(void* user_data) { | |
| return 0; | |
| } | |
| // Resolves the address of the specified OpenGL or OpenGL ES | |
| // core or extension function, if it is supported by the current context. | |
| static void* EngineProcResolver(void* user_data, const char* name) { | |
| return reinterpret_cast<void*>(glfwGetProcAddress(name)); | |
| } | |
| // Clears the GLFW window to Material Blue-Grey. | |
| // | |
| // This function is primarily to fix an issue when the Flutter Engine is | |
| // spinning up, wherein artifacts of existing windows are rendered onto the | |
| // canvas for a few moments. | |
| // | |
| // This function isn't necessary, but makes starting the window much easier on | |
| // the eyes. | |
| static void GLFWClearCanvas(GLFWwindow* window) { | |
| glfwMakeContextCurrent(window); | |
| // This color is Material Blue Grey. | |
| glClearColor(236.0f / 255.0f, 239.0f / 255.0f, 241.0f / 255.0f, 0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| glFlush(); | |
| glfwSwapBuffers(window); | |
| glfwMakeContextCurrent(nullptr); | |
| } | |
| static void GLFWErrorCallback(int error_code, const char* description) { | |
| std::cerr << "GLFW error " << error_code << ": " << description << std::endl; | |
| } | |
| // Attempts to load AOT data from the given path, which must be absolute and | |
| // non-empty. Logs and returns nullptr on failure. | |
| UniqueAotDataPtr LoadAotData(const std::filesystem::path& aot_data_path) { | |
| if (aot_data_path.empty()) { | |
| std::cerr | |
| << "Attempted to load AOT data, but no aot_data_path was provided." | |
| << std::endl; | |
| return nullptr; | |
| } | |
| if (!std::filesystem::exists(aot_data_path)) { | |
| std::cerr << "Can't load AOT data from " << aot_data_path.string() | |
| << "; no such file." << std::endl; | |
| return nullptr; | |
| } | |
| std::string path_string = aot_data_path.u8string(); | |
| FlutterEngineAOTDataSource source = {}; | |
| source.type = kFlutterEngineAOTDataSourceTypeElfPath; | |
| source.elf_path = path_string.c_str(); | |
| FlutterEngineAOTData data = nullptr; | |
| auto result = FlutterEngineCreateAOTData(&source, &data); | |
| if (result != kSuccess) { | |
| std::cerr << "Failed to load AOT data from: " << path_string << std::endl; | |
| return nullptr; | |
| } | |
| return UniqueAotDataPtr(data); | |
| } | |
| // Starts an instance of the Flutter Engine. | |
| // | |
| // Configures the engine according to |engine_propreties| and using |event_loop| | |
| // to schedule engine tasks. | |
| // | |
| // Returns true on success, in which case |engine_state|'s 'engine' field will | |
| // be updated to point to the started engine. | |
| static bool RunFlutterEngine( | |
| FlutterDesktopEngineState* engine_state, | |
| const FlutterDesktopEngineProperties& engine_properties, | |
| std::unique_ptr<flutter::EventLoop> event_loop) { | |
| // FlutterProjectArgs is expecting a full argv, so when processing it for | |
| // flags the first item is treated as the executable and ignored. Add a dummy | |
| // value so that all provided arguments are used. | |
| std::vector<const char*> argv = {"placeholder"}; | |
| if (engine_properties.switches_count > 0) { | |
| argv.insert(argv.end(), &engine_properties.switches[0], | |
| &engine_properties.switches[engine_properties.switches_count]); | |
| } | |
| std::filesystem::path assets_path = | |
| std::filesystem::u8path(engine_properties.assets_path); | |
| std::filesystem::path icu_path = | |
| std::filesystem::u8path(engine_properties.icu_data_path); | |
| std::filesystem::path aot_library_path = | |
| std::filesystem::u8path(engine_properties.aot_library_path); | |
| if (assets_path.is_relative() || icu_path.is_relative() || | |
| (!aot_library_path.empty() && aot_library_path.is_relative())) { | |
| // Treat relative paths as relative to the directory of this executable. | |
| std::filesystem::path executable_location = | |
| flutter::GetExecutableDirectory(); | |
| if (executable_location.empty()) { | |
| std::cerr << "Unable to find executable location to resolve paths." | |
| << std::endl; | |
| return false; | |
| } | |
| assets_path = std::filesystem::path(executable_location) / assets_path; | |
| icu_path = std::filesystem::path(executable_location) / icu_path; | |
| if (!aot_library_path.empty()) { | |
| aot_library_path = | |
| std::filesystem::path(executable_location) / aot_library_path; | |
| } | |
| } | |
| // Configure a task runner using the event loop. | |
| engine_state->event_loop = std::move(event_loop); | |
| FlutterTaskRunnerDescription platform_task_runner = {}; | |
| ConfigurePlatformTaskRunner(&platform_task_runner, engine_state); | |
| FlutterCustomTaskRunners task_runners = {}; | |
| task_runners.struct_size = sizeof(FlutterCustomTaskRunners); | |
| task_runners.platform_task_runner = &platform_task_runner; | |
| FlutterRendererConfig config = {}; | |
| config.type = kOpenGL; | |
| config.open_gl.struct_size = sizeof(config.open_gl); | |
| config.open_gl.make_current = EngineMakeContextCurrent; | |
| config.open_gl.clear_current = EngineClearContext; | |
| config.open_gl.present = EnginePresent; | |
| config.open_gl.fbo_callback = EngineGetActiveFbo; | |
| config.open_gl.make_resource_current = EngineMakeResourceContextCurrent; | |
| // Don't provide a resolver in headless mode, since headless mode should | |
| // work even if GLFW initialization failed. | |
| if (engine_state->window_controller != nullptr) { | |
| config.open_gl.gl_proc_resolver = EngineProcResolver; | |
| } | |
| FlutterProjectArgs args = {}; | |
| args.struct_size = sizeof(FlutterProjectArgs); | |
| args.assets_path = assets_path.c_str(); | |
| args.icu_data_path = icu_path.c_str(); | |
| args.command_line_argc = static_cast<int>(argv.size()); | |
| args.command_line_argv = &argv[0]; | |
| args.platform_message_callback = EngineOnFlutterPlatformMessage; | |
| args.custom_task_runners = &task_runners; | |
| if (FlutterEngineRunsAOTCompiledDartCode()) { | |
| engine_state->aot_data = LoadAotData(aot_library_path); | |
| if (!engine_state->aot_data) { | |
| std::cerr << "Unable to start engine without AOT data." << std::endl; | |
| return false; | |
| } | |
| args.aot_data = engine_state->aot_data.get(); | |
| } | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr; | |
| auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, | |
| engine_state, &engine); | |
| if (result != kSuccess || engine == nullptr) { | |
| std::cerr << "Failed to start Flutter engine: error " << result | |
| << std::endl; | |
| return false; | |
| } | |
| engine_state->flutter_engine = engine; | |
| return true; | |
| } | |
| // Passes locale information to the Flutter engine. | |
| static void SetUpLocales(FlutterDesktopEngineState* state) { | |
| std::vector<flutter::LanguageInfo> languages = | |
| flutter::GetPreferredLanguageInfo(); | |
| std::vector<FlutterLocale> flutter_locales = | |
| flutter::ConvertToFlutterLocale(languages); | |
| // Convert the locale list to the locale pointer list that must be provided. | |
| std::vector<const FlutterLocale*> flutter_locale_list; | |
| flutter_locale_list.reserve(flutter_locales.size()); | |
| std::transform(flutter_locales.begin(), flutter_locales.end(), | |
| std::back_inserter(flutter_locale_list), | |
| [](const auto& arg) -> const auto* { return &arg; }); | |
| FlutterEngineResult result = FlutterEngineUpdateLocales( | |
| state->flutter_engine, flutter_locale_list.data(), | |
| flutter_locale_list.size()); | |
| if (result != kSuccess) { | |
| std::cerr << "Failed to set up Flutter locales." << std::endl; | |
| } | |
| } | |
| // Populates |state|'s helper object fields that are common to normal and | |
| // headless mode. | |
| // | |
| // Window is optional; if present it will be provided to the created | |
| // PlatformHandler. | |
| static void SetUpCommonEngineState(FlutterDesktopEngineState* state, | |
| GLFWwindow* window) { | |
| // Messaging. | |
| state->messenger = FlutterDesktopMessengerReferenceOwner( | |
| FlutterDesktopMessengerAddRef(new FlutterDesktopMessenger()), | |
| &FlutterDesktopMessengerRelease); | |
| state->messenger->SetEngine(state); | |
| state->message_dispatcher = | |
| std::make_unique<flutter::IncomingMessageDispatcher>( | |
| state->messenger.get()); | |
| // Plugins. | |
| state->plugin_registrar = std::make_unique<FlutterDesktopPluginRegistrar>(); | |
| state->plugin_registrar->engine = state; | |
| state->internal_plugin_registrar = | |
| std::make_unique<flutter::PluginRegistrar>(state->plugin_registrar.get()); | |
| // System channel handler. | |
| state->platform_handler = std::make_unique<flutter::PlatformHandler>( | |
| state->internal_plugin_registrar->messenger(), window); | |
| SetUpLocales(state); | |
| } | |
| bool FlutterDesktopInit() { | |
| // Before making any GLFW calls, set up a logging error handler. | |
| glfwSetErrorCallback(GLFWErrorCallback); | |
| return glfwInit(); | |
| } | |
| void FlutterDesktopTerminate() { | |
| glfwTerminate(); | |
| } | |
| FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( | |
| const FlutterDesktopWindowProperties& window_properties, | |
| const FlutterDesktopEngineProperties& engine_properties) { | |
| auto state = std::make_unique<FlutterDesktopWindowControllerState>(); | |
| // Create the window, and set the state as its user data. | |
| if (window_properties.prevent_resize) { | |
| glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); | |
| } | |
| #if defined(__linux__) | |
| glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); | |
| #endif | |
| state->window = UniqueGLFWwindowPtr( | |
| glfwCreateWindow(window_properties.width, window_properties.height, | |
| window_properties.title, NULL, NULL), | |
| glfwDestroyWindow); | |
| glfwDefaultWindowHints(); | |
| GLFWwindow* window = state->window.get(); | |
| if (window == nullptr) { | |
| return nullptr; | |
| } | |
| GLFWClearCanvas(window); | |
| glfwSetWindowUserPointer(window, state.get()); | |
| // Create the share window before starting the engine, since it may call | |
| // EngineMakeResourceContextCurrent immediately. | |
| state->resource_window = CreateShareWindowForWindow(window); | |
| state->engine = std::make_unique<FlutterDesktopEngineState>(); | |
| state->engine->window_controller = state.get(); | |
| // Create an event loop for the window. It is not running yet. | |
| auto event_loop = std::make_unique<flutter::GLFWEventLoop>( | |
| std::this_thread::get_id(), // main GLFW thread | |
| [engine_state = state->engine.get()](const auto* task) { | |
| if (FlutterEngineRunTask(engine_state->flutter_engine, task) != | |
| kSuccess) { | |
| std::cerr << "Could not post an engine task." << std::endl; | |
| } | |
| }); | |
| // Start the engine. | |
| if (!RunFlutterEngine(state->engine.get(), engine_properties, | |
| std::move(event_loop))) { | |
| return nullptr; | |
| } | |
| SetUpCommonEngineState(state->engine.get(), window); | |
| state->window_wrapper = std::make_unique<FlutterDesktopWindow>(); | |
| state->window_wrapper->window = window; | |
| // Set up the keyboard handlers | |
| auto internal_plugin_messenger = | |
| state->engine->internal_plugin_registrar->messenger(); | |
| state->keyboard_hook_handlers.push_back( | |
| std::make_unique<flutter::KeyEventHandler>(internal_plugin_messenger)); | |
| state->keyboard_hook_handlers.push_back( | |
| std::make_unique<flutter::TextInputPlugin>(internal_plugin_messenger)); | |
| // Trigger an initial size callback to send size information to Flutter. | |
| state->monitor_screen_coordinates_per_inch = GetScreenCoordinatesPerInch(); | |
| int width_px, height_px; | |
| glfwGetFramebufferSize(window, &width_px, &height_px); | |
| GLFWFramebufferSizeCallback(window, width_px, height_px); | |
| // Set up GLFW callbacks for the window. | |
| glfwSetFramebufferSizeCallback(window, GLFWFramebufferSizeCallback); | |
| glfwSetWindowRefreshCallback(window, GLFWWindowRefreshCallback); | |
| GLFWAssignEventCallbacks(window); | |
| return state.release(); | |
| } | |
| void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) { | |
| controller->engine->messenger->SetEngine(nullptr); | |
| FlutterDesktopPluginRegistrarRef registrar = | |
| controller->engine->plugin_registrar.get(); | |
| if (registrar->destruction_handler) { | |
| registrar->destruction_handler(registrar); | |
| } | |
| FlutterEngineShutdown(controller->engine->flutter_engine); | |
| delete controller; | |
| } | |
| void FlutterDesktopWindowSetHoverEnabled(FlutterDesktopWindowRef flutter_window, | |
| bool enabled) { | |
| flutter_window->hover_tracking_enabled = enabled; | |
| SetHoverCallbacksEnabled(flutter_window->window, enabled); | |
| } | |
| void FlutterDesktopWindowSetTitle(FlutterDesktopWindowRef flutter_window, | |
| const char* title) { | |
| GLFWwindow* window = flutter_window->window; | |
| glfwSetWindowTitle(window, title); | |
| } | |
| void FlutterDesktopWindowSetIcon(FlutterDesktopWindowRef flutter_window, | |
| uint8_t* pixel_data, | |
| int width, | |
| int height) { | |
| GLFWimage image = {width, height, static_cast<unsigned char*>(pixel_data)}; | |
| glfwSetWindowIcon(flutter_window->window, pixel_data ? 1 : 0, &image); | |
| } | |
| void FlutterDesktopWindowGetFrame(FlutterDesktopWindowRef flutter_window, | |
| int* x, | |
| int* y, | |
| int* width, | |
| int* height) { | |
| glfwGetWindowPos(flutter_window->window, x, y); | |
| glfwGetWindowSize(flutter_window->window, width, height); | |
| // The above gives content area size and position; adjust for the window | |
| // decoration to give actual window frame. | |
| int frame_left, frame_top, frame_right, frame_bottom; | |
| glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top, | |
| &frame_right, &frame_bottom); | |
| if (x) { | |
| *x -= frame_left; | |
| } | |
| if (y) { | |
| *y -= frame_top; | |
| } | |
| if (width) { | |
| *width += frame_left + frame_right; | |
| } | |
| if (height) { | |
| *height += frame_top + frame_bottom; | |
| } | |
| } | |
| void FlutterDesktopWindowSetFrame(FlutterDesktopWindowRef flutter_window, | |
| int x, | |
| int y, | |
| int width, | |
| int height) { | |
| // Get the window decoration sizes to adjust, since the GLFW setters take | |
| // content position and size. | |
| int frame_left, frame_top, frame_right, frame_bottom; | |
| glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top, | |
| &frame_right, &frame_bottom); | |
| glfwSetWindowPos(flutter_window->window, x + frame_left, y + frame_top); | |
| glfwSetWindowSize(flutter_window->window, width - frame_left - frame_right, | |
| height - frame_top - frame_bottom); | |
| } | |
| double FlutterDesktopWindowGetScaleFactor( | |
| FlutterDesktopWindowRef flutter_window) { | |
| return flutter_window->pixels_per_screen_coordinate; | |
| } | |
| void FlutterDesktopWindowSetPixelRatioOverride( | |
| FlutterDesktopWindowRef flutter_window, | |
| double pixel_ratio) { | |
| flutter_window->pixel_ratio_override = pixel_ratio; | |
| // Send a metrics update using the new pixel ratio. | |
| int width_px, height_px; | |
| glfwGetFramebufferSize(flutter_window->window, &width_px, &height_px); | |
| if (width_px > 0 && height_px > 0) { | |
| auto* controller = GetWindowController(flutter_window->window); | |
| SendWindowMetrics(controller, width_px, height_px); | |
| } | |
| } | |
| void FlutterDesktopWindowSetSizeLimits(FlutterDesktopWindowRef flutter_window, | |
| FlutterDesktopSize minimum_size, | |
| FlutterDesktopSize maximum_size) { | |
| glfwSetWindowSizeLimits(flutter_window->window, minimum_size.width, | |
| minimum_size.height, maximum_size.width, | |
| maximum_size.height); | |
| } | |
| bool FlutterDesktopRunWindowEventLoopWithTimeout( | |
| FlutterDesktopWindowControllerRef controller, | |
| uint32_t timeout_milliseconds) { | |
| FlutterDesktopRunEngineEventLoopWithTimeout(controller->engine.get(), | |
| timeout_milliseconds); | |
| return !glfwWindowShouldClose(controller->window.get()); | |
| } | |
| FlutterDesktopWindowRef FlutterDesktopGetWindow( | |
| FlutterDesktopWindowControllerRef controller) { | |
| // Currently, one registrar acts as the registrar for all plugins, so the | |
| // name is ignored. It is part of the API to reduce churn in the future when | |
| // aligning more closely with the Flutter registrar system. | |
| return controller->window_wrapper.get(); | |
| } | |
| FlutterDesktopEngineRef FlutterDesktopGetEngine( | |
| FlutterDesktopWindowControllerRef controller) { | |
| return controller->engine.get(); | |
| } | |
| FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( | |
| FlutterDesktopEngineRef engine, | |
| const char* plugin_name) { | |
| // Currently, one registrar acts as the registrar for all plugins, so the | |
| // name is ignored. It is part of the API to reduce churn in the future when | |
| // aligning more closely with the Flutter registrar system. | |
| return engine->plugin_registrar.get(); | |
| } | |
| FlutterDesktopEngineRef FlutterDesktopRunEngine( | |
| const FlutterDesktopEngineProperties& properties) { | |
| auto engine_state = std::make_unique<FlutterDesktopEngineState>(); | |
| auto event_loop = std::make_unique<flutter::HeadlessEventLoop>( | |
| std::this_thread::get_id(), | |
| [state = engine_state.get()](const auto* task) { | |
| if (FlutterEngineRunTask(state->flutter_engine, task) != kSuccess) { | |
| std::cerr << "Could not post an engine task." << std::endl; | |
| } | |
| }); | |
| if (!RunFlutterEngine(engine_state.get(), properties, | |
| std::move(event_loop))) { | |
| return nullptr; | |
| } | |
| SetUpCommonEngineState(engine_state.get(), nullptr); | |
| return engine_state.release(); | |
| } | |
| void FlutterDesktopRunEngineEventLoopWithTimeout( | |
| FlutterDesktopEngineRef engine, | |
| uint32_t timeout_milliseconds) { | |
| std::chrono::nanoseconds wait_duration = | |
| timeout_milliseconds == 0 | |
| ? std::chrono::nanoseconds::max() | |
| : std::chrono::milliseconds(timeout_milliseconds); | |
| engine->event_loop->WaitForEvents(wait_duration); | |
| } | |
| bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine) { | |
| auto result = FlutterEngineShutdown(engine->flutter_engine); | |
| delete engine; | |
| return (result == kSuccess); | |
| } | |
| void FlutterDesktopPluginRegistrarEnableInputBlocking( | |
| FlutterDesktopPluginRegistrarRef registrar, | |
| const char* channel) { | |
| registrar->engine->message_dispatcher->EnableInputBlockingForChannel(channel); | |
| } | |
| FlutterDesktopMessengerRef FlutterDesktopPluginRegistrarGetMessenger( | |
| FlutterDesktopPluginRegistrarRef registrar) { | |
| return registrar->engine->messenger.get(); | |
| } | |
| void FlutterDesktopPluginRegistrarSetDestructionHandler( | |
| FlutterDesktopPluginRegistrarRef registrar, | |
| FlutterDesktopOnPluginRegistrarDestroyed callback) { | |
| registrar->destruction_handler = callback; | |
| } | |
| FlutterDesktopWindowRef FlutterDesktopPluginRegistrarGetWindow( | |
| FlutterDesktopPluginRegistrarRef registrar) { | |
| FlutterDesktopWindowControllerState* controller = | |
| registrar->engine->window_controller; | |
| if (!controller) { | |
| return nullptr; | |
| } | |
| return controller->window_wrapper.get(); | |
| } | |
| bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, | |
| const char* channel, | |
| const uint8_t* message, | |
| const size_t message_size, | |
| const FlutterDesktopBinaryReply reply, | |
| void* user_data) { | |
| FlutterPlatformMessageResponseHandle* response_handle = nullptr; | |
| if (reply != nullptr && user_data != nullptr) { | |
| FlutterEngineResult result = FlutterPlatformMessageCreateResponseHandle( | |
| messenger->GetEngine()->flutter_engine, reply, user_data, | |
| &response_handle); | |
| if (result != kSuccess) { | |
| std::cout << "Failed to create response handle\n"; | |
| return false; | |
| } | |
| } | |
| FlutterPlatformMessage platform_message = { | |
| sizeof(FlutterPlatformMessage), | |
| channel, | |
| message, | |
| message_size, | |
| response_handle, | |
| }; | |
| FlutterEngineResult message_result = FlutterEngineSendPlatformMessage( | |
| messenger->GetEngine()->flutter_engine, &platform_message); | |
| if (response_handle != nullptr) { | |
| FlutterPlatformMessageReleaseResponseHandle( | |
| messenger->GetEngine()->flutter_engine, response_handle); | |
| } | |
| return message_result == kSuccess; | |
| } | |
| bool FlutterDesktopMessengerSend(FlutterDesktopMessengerRef messenger, | |
| const char* channel, | |
| const uint8_t* message, | |
| const size_t message_size) { | |
| return FlutterDesktopMessengerSendWithReply(messenger, channel, message, | |
| message_size, nullptr, nullptr); | |
| } | |
| void FlutterDesktopMessengerSendResponse( | |
| FlutterDesktopMessengerRef messenger, | |
| const FlutterDesktopMessageResponseHandle* handle, | |
| const uint8_t* data, | |
| size_t data_length) { | |
| FlutterEngineSendPlatformMessageResponse( | |
| messenger->GetEngine()->flutter_engine, handle, data, data_length); | |
| } | |
| void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger, | |
| const char* channel, | |
| FlutterDesktopMessageCallback callback, | |
| void* user_data) { | |
| messenger->GetEngine()->message_dispatcher->SetMessageCallback( | |
| channel, callback, user_data); | |
| } | |
| FlutterDesktopTextureRegistrarRef FlutterDesktopRegistrarGetTextureRegistrar( | |
| FlutterDesktopPluginRegistrarRef registrar) { | |
| std::cerr << "GLFW Texture support is not implemented yet." << std::endl; | |
| return nullptr; | |
| } | |
| int64_t FlutterDesktopTextureRegistrarRegisterExternalTexture( | |
| FlutterDesktopTextureRegistrarRef texture_registrar, | |
| const FlutterDesktopTextureInfo* texture_info) { | |
| std::cerr << "GLFW Texture support is not implemented yet." << std::endl; | |
| return -1; | |
| } | |
| void FlutterDesktopTextureRegistrarUnregisterExternalTexture( | |
| FlutterDesktopTextureRegistrarRef texture_registrar, | |
| int64_t texture_id, | |
| void (*callback)(void* user_data), | |
| void* user_data) { | |
| std::cerr << "GLFW Texture support is not implemented yet." << std::endl; | |
| } | |
| bool FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable( | |
| FlutterDesktopTextureRegistrarRef texture_registrar, | |
| int64_t texture_id) { | |
| std::cerr << "GLFW Texture support is not implemented yet." << std::endl; | |
| return false; | |
| } |