| // 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 <assert.h> |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <cstdlib> |
| #include <filesystem> |
| #include <iostream> |
| |
| #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" |
| #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" |
| #include "flutter/shell/platform/common/cpp/path_utils.h" |
| #include "flutter/shell/platform/embedder/embedder.h" |
| #include "flutter/shell/platform/glfw/glfw_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/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 handle to the Flutter engine instance. |
| FLUTTER_API_SYMBOL(FlutterEngine) engine; |
| |
| // The window handle given to API clients. |
| std::unique_ptr<FlutterDesktopWindow> window_wrapper; |
| |
| // The plugin registrar handle given to API clients. |
| std::unique_ptr<FlutterDesktopPluginRegistrar> plugin_registrar; |
| |
| // Message dispatch manager for messages from the Flutter engine. |
| std::unique_ptr<flutter::IncomingMessageDispatcher> message_dispatcher; |
| |
| // The plugin registrar managing internal plugins. |
| std::unique_ptr<flutter::PluginRegistrar> internal_plugin_registrar; |
| |
| // Handlers for keyboard events from GLFW. |
| std::vector<std::unique_ptr<flutter::KeyboardHookHandler>> |
| keyboard_hook_handlers; |
| |
| // Handler for the flutter/platform channel. |
| std::unique_ptr<flutter::PlatformHandler> platform_handler; |
| |
| // The event loop for the main thread that allows for delayed task execution. |
| std::unique_ptr<flutter::GLFWEventLoop> event_loop; |
| |
| // 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; |
| |
| // 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; |
| }; |
| |
| // Struct for storing state of a Flutter engine instance. |
| struct FlutterDesktopEngineState { |
| // The handle to the Flutter engine instance. |
| FLUTTER_API_SYMBOL(FlutterEngine) engine; |
| }; |
| |
| // State associated with the plugin registrar. |
| struct FlutterDesktopPluginRegistrar { |
| // The plugin messenger handle given to API clients. |
| std::unique_ptr<FlutterDesktopMessenger> messenger; |
| |
| // The handle for the window associated with this registrar. |
| FlutterDesktopWindow* window; |
| }; |
| |
| // State associated with the messenger used to communicate with the engine. |
| struct FlutterDesktopMessenger { |
| // The Flutter engine this messenger sends outgoing messages to. |
| FLUTTER_API_SYMBOL(FlutterEngine) engine; |
| |
| // The message dispatcher for handling incoming messages. |
| flutter::IncomingMessageDispatcher* dispatcher; |
| }; |
| |
| // Retrieves state bag for the window in question from the GLFWWindow. |
| static FlutterDesktopWindowControllerState* GetSavedWindowState( |
| 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(); |
| 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* state, |
| int width, |
| int height) { |
| double dpi = state->window_wrapper->pixels_per_screen_coordinate * |
| state->monitor_screen_coordinates_per_inch; |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = width; |
| event.height = height; |
| if (state->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 = state->window_wrapper->pixel_ratio_override; |
| } |
| FlutterEngineSendWindowMetricsEvent(state->engine, &event); |
| } |
| |
| // 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* state = GetSavedWindowState(window); |
| state->window_wrapper->pixels_per_screen_coordinate = |
| width > 0 ? width_px / width : 1; |
| |
| SendWindowMetrics(state, width_px, height_px); |
| state->window_wrapper->skip_next_window_refresh = true; |
| } |
| |
| // Indicates that the window needs to be redrawn. |
| void GLFWWindowRefreshCallback(GLFWwindow* window) { |
| auto* state = GetSavedWindowState(window); |
| if (state->window_wrapper->skip_next_window_refresh) { |
| state->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(state, 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* state = GetSavedWindowState(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 (!state->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 (state->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(); |
| // Convert all screen coordinates to pixel coordinates. |
| double pixels_per_coordinate = |
| state->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(state->engine, &event, 1); |
| |
| if (event_data.phase == FlutterPointerPhase::kAdd) { |
| state->pointer_currently_added = true; |
| } else if (event_data.phase == FlutterPointerPhase::kRemove) { |
| state->pointer_currently_added = 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 to either kMove or kHover depending on the current |
| // primary mouse button state. |
| static void SetEventPhaseFromCursorButtonState( |
| GLFWwindow* window, |
| FlutterPointerEvent* event_data) { |
| event_data->phase = |
| glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS |
| ? FlutterPointerPhase::kMove |
| : FlutterPointerPhase::kHover; |
| } |
| |
| // 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; |
| SetEventPhaseFromCursorButtonState(window, &event); |
| SendPointerEventWithData(window, event); |
| } |
| |
| // Reports mouse button press to the Flutter engine. |
| static void GLFWMouseButtonCallback(GLFWwindow* window, |
| int key, |
| int action, |
| int mods) { |
| // Flutter currently doesn't understand other buttons, so ignore anything |
| // other than left. |
| if (key != GLFW_MOUSE_BUTTON_LEFT) { |
| return; |
| } |
| |
| FlutterPointerEvent event = {}; |
| event.phase = (action == GLFW_PRESS) ? FlutterPointerPhase::kDown |
| : FlutterPointerPhase::kUp; |
| 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 = |
| GetSavedWindowState(window)->window_wrapper->hover_tracking_enabled; |
| if (!hover_enabled) { |
| glfwSetCursorPosCallback( |
| window, (action == GLFW_PRESS) ? 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, (action == GLFW_PRESS) ? 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); |
| SetEventPhaseFromCursorButtonState(window, &event); |
| event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; |
| // TODO: 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 : |
| GetSavedWindowState(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 : |
| GetSavedWindowState(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 (GetSavedWindowState(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 GLFWOnFlutterPlatformMessage( |
| 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; |
| } |
| |
| GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data); |
| auto state = GetSavedWindowState(window); |
| |
| auto message = ConvertToDesktopMessage(*engine_message); |
| state->message_dispatcher->HandleMessage( |
| message, [window] { GLFWClearEventCallbacks(window); }, |
| [window] { GLFWAssignEventCallbacks(window); }); |
| } |
| |
| static bool GLFWMakeContextCurrent(void* user_data) { |
| GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data); |
| glfwMakeContextCurrent(window); |
| return true; |
| } |
| |
| static bool GLFWMakeResourceContextCurrent(void* user_data) { |
| GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data); |
| glfwMakeContextCurrent(GetSavedWindowState(window)->resource_window.get()); |
| return true; |
| } |
| |
| static bool GLFWClearContext(void* user_data) { |
| glfwMakeContextCurrent(nullptr); |
| return true; |
| } |
| |
| static bool GLFWPresent(void* user_data) { |
| GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data); |
| glfwSwapBuffers(window); |
| return true; |
| } |
| |
| static uint32_t GLFWGetActiveFbo(void* user_data) { |
| return 0; |
| } |
| |
| // 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); |
| } |
| |
| // Resolves the address of the specified OpenGL or OpenGL ES |
| // core or extension function, if it is supported by the current context. |
| static void* GLFWProcResolver(void* user_data, const char* name) { |
| return reinterpret_cast<void*>(glfwGetProcAddress(name)); |
| } |
| |
| static void GLFWErrorCallback(int error_code, const char* description) { |
| std::cerr << "GLFW error " << error_code << ": " << description << std::endl; |
| } |
| |
| // Spins up an instance of the Flutter Engine. |
| // |
| // This function launches the Flutter Engine in a background thread, supplying |
| // the necessary callbacks for rendering within a GLFWwindow (if one is |
| // provided). |
| // |
| // Returns a caller-owned pointer to the engine. |
| static FLUTTER_API_SYMBOL(FlutterEngine) |
| RunFlutterEngine(GLFWwindow* window, |
| const FlutterDesktopEngineProperties& engine_properties, |
| const FlutterCustomTaskRunners* custom_task_runners) { |
| // 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); |
| if (assets_path.is_relative() || icu_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 nullptr; |
| } |
| assets_path = std::filesystem::path(executable_location) / assets_path; |
| icu_path = std::filesystem::path(executable_location) / icu_path; |
| } |
| std::string assets_path_string = assets_path.u8string(); |
| std::string icu_path_string = icu_path.u8string(); |
| |
| FlutterRendererConfig config = {}; |
| if (window == nullptr) { |
| config.type = kOpenGL; |
| config.open_gl.struct_size = sizeof(config.open_gl); |
| config.open_gl.make_current = [](void* data) -> bool { return false; }; |
| config.open_gl.clear_current = [](void* data) -> bool { return false; }; |
| config.open_gl.present = [](void* data) -> bool { return false; }; |
| config.open_gl.fbo_callback = [](void* data) -> uint32_t { return 0; }; |
| } else { |
| // Provide the necessary callbacks for rendering within a GLFWwindow. |
| config.type = kOpenGL; |
| config.open_gl.struct_size = sizeof(config.open_gl); |
| config.open_gl.make_current = GLFWMakeContextCurrent; |
| config.open_gl.clear_current = GLFWClearContext; |
| config.open_gl.present = GLFWPresent; |
| config.open_gl.fbo_callback = GLFWGetActiveFbo; |
| config.open_gl.make_resource_current = GLFWMakeResourceContextCurrent; |
| config.open_gl.gl_proc_resolver = GLFWProcResolver; |
| } |
| FlutterProjectArgs args = {}; |
| args.struct_size = sizeof(FlutterProjectArgs); |
| args.assets_path = assets_path_string.c_str(); |
| args.icu_data_path = icu_path_string.c_str(); |
| args.command_line_argc = static_cast<int>(argv.size()); |
| args.command_line_argv = &argv[0]; |
| args.platform_message_callback = GLFWOnFlutterPlatformMessage; |
| args.custom_task_runners = custom_task_runners; |
| FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr; |
| auto result = |
| FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, window, &engine); |
| if (result != kSuccess || engine == nullptr) { |
| std::cerr << "Failed to start Flutter engine: error " << result |
| << std::endl; |
| return nullptr; |
| } |
| return engine; |
| } |
| |
| 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 |
| // GLFWMakeResourceContextCurrent immediately. |
| state->resource_window = CreateShareWindowForWindow(window); |
| |
| // Create an event loop for the window. It is not running yet. |
| state->event_loop = std::make_unique<flutter::GLFWEventLoop>( |
| std::this_thread::get_id(), // main GLFW thread |
| [state = state.get()](const auto* task) { |
| if (FlutterEngineRunTask(state->engine, task) != kSuccess) { |
| std::cerr << "Could not post an engine task." << std::endl; |
| } |
| }); |
| |
| // Configure task runner interop. |
| FlutterTaskRunnerDescription platform_task_runner = {}; |
| platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); |
| platform_task_runner.user_data = state.get(); |
| platform_task_runner.runs_task_on_current_thread_callback = |
| [](void* state) -> bool { |
| return reinterpret_cast<FlutterDesktopWindowControllerState*>(state) |
| ->event_loop->RunsTasksOnCurrentThread(); |
| }; |
| platform_task_runner.post_task_callback = |
| [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void { |
| reinterpret_cast<FlutterDesktopWindowControllerState*>(state) |
| ->event_loop->PostTask(task, target_time_nanos); |
| }; |
| |
| FlutterCustomTaskRunners custom_task_runners = {}; |
| custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); |
| custom_task_runners.platform_task_runner = &platform_task_runner; |
| |
| // Start the engine. |
| state->engine = |
| RunFlutterEngine(window, engine_properties, &custom_task_runners); |
| if (state->engine == nullptr) { |
| return nullptr; |
| } |
| |
| // TODO: Restructure the internals to follow the structure of the C++ API, so |
| // that this isn't a tangle of references. |
| auto messenger = std::make_unique<FlutterDesktopMessenger>(); |
| state->message_dispatcher = |
| std::make_unique<flutter::IncomingMessageDispatcher>(messenger.get()); |
| messenger->engine = state->engine; |
| messenger->dispatcher = state->message_dispatcher.get(); |
| |
| state->window_wrapper = std::make_unique<FlutterDesktopWindow>(); |
| state->window_wrapper->window = window; |
| |
| state->plugin_registrar = std::make_unique<FlutterDesktopPluginRegistrar>(); |
| state->plugin_registrar->messenger = std::move(messenger); |
| state->plugin_registrar->window = state->window_wrapper.get(); |
| |
| state->internal_plugin_registrar = |
| std::make_unique<flutter::PluginRegistrar>(state->plugin_registrar.get()); |
| |
| // Set up the keyboard handlers. |
| auto internal_plugin_messenger = |
| state->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)); |
| state->platform_handler = std::make_unique<flutter::PlatformHandler>( |
| internal_plugin_messenger, state->window.get()); |
| |
| // 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) { |
| FlutterEngineShutdown(controller->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* state = GetSavedWindowState(flutter_window->window); |
| SendWindowMetrics(state, 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) { |
| std::chrono::nanoseconds wait_duration = |
| timeout_milliseconds == 0 |
| ? std::chrono::nanoseconds::max() |
| : std::chrono::milliseconds(timeout_milliseconds); |
| controller->event_loop->WaitForEvents(wait_duration); |
| |
| 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(); |
| } |
| |
| FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( |
| FlutterDesktopWindowControllerRef controller, |
| 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 controller->plugin_registrar.get(); |
| } |
| |
| FlutterDesktopEngineRef FlutterDesktopRunEngine( |
| const FlutterDesktopEngineProperties& properties) { |
| auto engine = |
| RunFlutterEngine(nullptr, properties, nullptr /* custom task runners */); |
| if (engine == nullptr) { |
| return nullptr; |
| } |
| auto engine_state = new FlutterDesktopEngineState(); |
| engine_state->engine = engine; |
| return engine_state; |
| } |
| |
| bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) { |
| std::cout << "Shutting down flutter engine process." << std::endl; |
| auto result = FlutterEngineShutdown(engine_ref->engine); |
| delete engine_ref; |
| return (result == kSuccess); |
| } |
| |
| void FlutterDesktopRegistrarEnableInputBlocking( |
| FlutterDesktopPluginRegistrarRef registrar, |
| const char* channel) { |
| registrar->messenger->dispatcher->EnableInputBlockingForChannel(channel); |
| } |
| |
| FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger( |
| FlutterDesktopPluginRegistrarRef registrar) { |
| return registrar->messenger.get(); |
| } |
| |
| FlutterDesktopWindowRef FlutterDesktopRegistrarGetWindow( |
| FlutterDesktopPluginRegistrarRef registrar) { |
| return registrar->window; |
| } |
| |
| 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->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->engine, &platform_message); |
| |
| if (response_handle != nullptr) { |
| FlutterPlatformMessageReleaseResponseHandle(messenger->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->engine, handle, data, |
| data_length); |
| } |
| |
| void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger, |
| const char* channel, |
| FlutterDesktopMessageCallback callback, |
| void* user_data) { |
| messenger->dispatcher->SetMessageCallback(channel, callback, user_data); |
| } |