1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/shell/platform/glfw/public/flutter_glfw.h"
6
7 #include <GLFW/glfw3.h>
8 #include <assert.h>
9
10 #include <algorithm>
11 #include <chrono>
12 #include <cstdlib>
13 #include <iostream>
14
15 #include "flutter/shell/platform/embedder/embedder.h"
16 #include "flutter/shell/platform/glfw/glfw_event_loop.h"
17 #include "flutter/shell/platform/glfw/keyboard_hook_handler.h"
18
19 #ifndef WINDOWS_PLATFORM
20 #define WINDOWS_PLATFORM
21 #endif
22
23 // GLFW_TRUE & GLFW_FALSE are introduced since libglfw-3.3,
24 // add definitions here to compile under the old versions.
25 #ifndef GLFW_TRUE
26 #define GLFW_TRUE 1
27 #endif
28 #ifndef GLFW_FALSE
29 #define GLFW_FALSE 0
30 #endif
31
32 using UniqueGLFWwindowPtr = std::unique_ptr<GLFWwindow, void (*)(GLFWwindow*)>;
33
34 static constexpr double kDpPerInch = 160.0;
35
36 // Struct for storing state within an instance of the GLFW Window.
37 struct FlutterDesktopWindowControllerState {
38 // The GLFW window that is bound to this state object.
39 UniqueGLFWwindowPtr window = UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow);
40
41 // The invisible GLFW window used to upload resources in the background.
42 UniqueGLFWwindowPtr resource_window =
43 UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow);
44
45 // The handle to the Flutter engine instance.
46 FLUTTER_API_SYMBOL(FlutterEngine) engine;
47
48 // The window handle given to API clients.
49 std::unique_ptr<FlutterDesktopWindow> window_wrapper;
50
51 // Handlers for keyboard events from GLFW.
52 std::vector<std::unique_ptr<flutter::KeyboardHookHandler>>
53 keyboard_hook_handlers;
54
55 // The event loop for the main thread that allows for delayed task execution.
56 std::unique_ptr<flutter::GLFWEventLoop> event_loop;
57
58 // Whether or not the pointer has been added (or if tracking is enabled,
59 // has been added since it was last removed).
60 bool pointer_currently_added = false;
61
62 // The screen coordinates per inch on the primary monitor. Defaults to a sane
63 // value based on pixel_ratio 1.0.
64 double monitor_screen_coordinates_per_inch = kDpPerInch;
65 };
66
67 // Opaque reference for the GLFW window itself. This is separate from the
68 // controller so that it can be provided to plugins without giving them access
69 // to all of the controller-based functionality.
70 struct FlutterDesktopWindow {
71 // The GLFW window that (indirectly) owns this state object.
72 GLFWwindow* window;
73
74 // Whether or not to track mouse movements to send kHover events.
75 bool hover_tracking_enabled = true;
76
77 // The ratio of pixels per screen coordinate for the window.
78 double pixels_per_screen_coordinate = 1.0;
79
80 // Resizing triggers a window refresh, but the resize already updates Flutter.
81 // To avoid double messages, the refresh after each resize is skipped.
82 bool skip_next_window_refresh = false;
83 };
84
85 // Struct for storing state of a Flutter engine instance.
86 struct FlutterDesktopEngineState {
87 // The handle to the Flutter engine instance.
88 FLUTTER_API_SYMBOL(FlutterEngine) engine;
89 };
90
91 // Retrieves state bag for the window in question from the GLFWWindow.
GetSavedWindowState(GLFWwindow * window)92 static FlutterDesktopWindowControllerState* GetSavedWindowState(
93 GLFWwindow* window) {
94 return reinterpret_cast<FlutterDesktopWindowControllerState*>(
95 glfwGetWindowUserPointer(window));
96 }
97
98 // Creates and returns an invisible GLFW window that shares |window|'s resource
99 // context.
CreateShareWindowForWindow(GLFWwindow * window)100 static UniqueGLFWwindowPtr CreateShareWindowForWindow(GLFWwindow* window) {
101 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
102 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
103 GLFWwindow* share_window = glfwCreateWindow(1, 1, "", NULL, window);
104 glfwDefaultWindowHints();
105 return UniqueGLFWwindowPtr(share_window, glfwDestroyWindow);
106 }
107
108 // Returns the number of screen coordinates per inch for the main monitor.
109 // If the information is unavailable, returns a default value that assumes
110 // that a screen coordinate is one dp.
GetScreenCoordinatesPerInch()111 static double GetScreenCoordinatesPerInch() {
112 auto* primary_monitor = glfwGetPrimaryMonitor();
113 auto* primary_monitor_mode = glfwGetVideoMode(primary_monitor);
114 int primary_monitor_width_mm;
115 glfwGetMonitorPhysicalSize(primary_monitor, &primary_monitor_width_mm,
116 nullptr);
117 if (primary_monitor_width_mm == 0) {
118 return kDpPerInch;
119 }
120 return primary_monitor_mode->width / (primary_monitor_width_mm / 25.4);
121 }
122
123 // Sends a window metrics update to the Flutter engine using the given
124 // framebuffer size and the current window information in |state|.
SendWindowMetrics(FlutterDesktopWindowControllerState * state,int width,int height)125 static void SendWindowMetrics(FlutterDesktopWindowControllerState* state,
126 int width,
127 int height) {
128 double dpi = state->window_wrapper->pixels_per_screen_coordinate *
129 state->monitor_screen_coordinates_per_inch;
130
131 FlutterWindowMetricsEvent event = {};
132 event.struct_size = sizeof(event);
133 event.width = width;
134 event.height = height;
135 // The Flutter pixel_ratio is defined as DPI/dp. Limit the ratio to a minimum
136 // of 1 to avoid rendering a smaller UI on standard resolution monitors.
137 event.pixel_ratio = std::max(dpi / kDpPerInch, 1.0);
138 FlutterEngineSendWindowMetricsEvent(state->engine, &event);
139 }
140
141 // When GLFW calls back to the window with a framebuffer size change, notify
142 // FlutterEngine about the new window metrics.
GLFWFramebufferSizeCallback(GLFWwindow * window,int width_px,int height_px)143 static void GLFWFramebufferSizeCallback(GLFWwindow* window,
144 int width_px,
145 int height_px) {
146 int width;
147 glfwGetWindowSize(window, &width, nullptr);
148 auto* state = GetSavedWindowState(window);
149 state->window_wrapper->pixels_per_screen_coordinate =
150 width > 0 ? width_px / width : 1;
151
152 SendWindowMetrics(state, width_px, height_px);
153 state->window_wrapper->skip_next_window_refresh = true;
154 }
155
156 // Indicates that the window needs to be redrawn.
GLFWWindowRefreshCallback(GLFWwindow * window)157 void GLFWWindowRefreshCallback(GLFWwindow* window) {
158 auto* state = GetSavedWindowState(window);
159 if (state->window_wrapper->skip_next_window_refresh) {
160 state->window_wrapper->skip_next_window_refresh = false;
161 return;
162 }
163 // There's no engine API to request a redraw explicitly, so instead send a
164 // window metrics event with the current size to trigger it.
165 int width_px, height_px;
166 glfwGetFramebufferSize(window, &width_px, &height_px);
167 if (width_px > 0 && height_px > 0) {
168 SendWindowMetrics(state, width_px, height_px);
169 }
170 }
171
172 // Sends a pointer event to the Flutter engine based on the given data.
173 //
174 // Any coordinate/distance values in |event_data| should be in screen
175 // coordinates; they will be adjusted to pixel values before being sent.
SendPointerEventWithData(GLFWwindow * window,const FlutterPointerEvent & event_data)176 static void SendPointerEventWithData(GLFWwindow* window,
177 const FlutterPointerEvent& event_data) {
178 auto* state = GetSavedWindowState(window);
179 // If sending anything other than an add, and the pointer isn't already added,
180 // synthesize an add to satisfy Flutter's expectations about events.
181 if (!state->pointer_currently_added &&
182 event_data.phase != FlutterPointerPhase::kAdd) {
183 FlutterPointerEvent event = {};
184 event.phase = FlutterPointerPhase::kAdd;
185 event.x = event_data.x;
186 event.y = event_data.y;
187 SendPointerEventWithData(window, event);
188 }
189 // Don't double-add (e.g., if events are delivered out of order, so an add has
190 // already been synthesized).
191 if (state->pointer_currently_added &&
192 event_data.phase == FlutterPointerPhase::kAdd) {
193 return;
194 }
195
196 FlutterPointerEvent event = event_data;
197 // Set metadata that's always the same regardless of the event.
198 event.struct_size = sizeof(event);
199 event.timestamp =
200 std::chrono::duration_cast<std::chrono::microseconds>(
201 std::chrono::high_resolution_clock::now().time_since_epoch())
202 .count();
203 // Convert all screen coordinates to pixel coordinates.
204 double pixels_per_coordinate =
205 state->window_wrapper->pixels_per_screen_coordinate;
206 event.x *= pixels_per_coordinate;
207 event.y *= pixels_per_coordinate;
208 event.scroll_delta_x *= pixels_per_coordinate;
209 event.scroll_delta_y *= pixels_per_coordinate;
210
211 FlutterEngineSendPointerEvent(state->engine, &event, 1);
212
213 if (event_data.phase == FlutterPointerPhase::kAdd) {
214 state->pointer_currently_added = true;
215 } else if (event_data.phase == FlutterPointerPhase::kRemove) {
216 state->pointer_currently_added = false;
217 }
218 }
219
220 // Updates |event_data| with the current location of the mouse cursor.
SetEventLocationFromCursorPosition(GLFWwindow * window,FlutterPointerEvent * event_data)221 static void SetEventLocationFromCursorPosition(
222 GLFWwindow* window,
223 FlutterPointerEvent* event_data) {
224 glfwGetCursorPos(window, &event_data->x, &event_data->y);
225 }
226
227 // Set's |event_data|'s phase to either kMove or kHover depending on the current
228 // primary mouse button state.
SetEventPhaseFromCursorButtonState(GLFWwindow * window,FlutterPointerEvent * event_data)229 static void SetEventPhaseFromCursorButtonState(
230 GLFWwindow* window,
231 FlutterPointerEvent* event_data) {
232 event_data->phase =
233 glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS
234 ? FlutterPointerPhase::kMove
235 : FlutterPointerPhase::kHover;
236 }
237
238 // Reports the mouse entering or leaving the Flutter view.
GLFWCursorEnterCallback(GLFWwindow * window,int entered)239 static void GLFWCursorEnterCallback(GLFWwindow* window, int entered) {
240 FlutterPointerEvent event = {};
241 event.phase =
242 entered ? FlutterPointerPhase::kAdd : FlutterPointerPhase::kRemove;
243 SetEventLocationFromCursorPosition(window, &event);
244 SendPointerEventWithData(window, event);
245 }
246
247 // Reports mouse movement to the Flutter engine.
GLFWCursorPositionCallback(GLFWwindow * window,double x,double y)248 static void GLFWCursorPositionCallback(GLFWwindow* window, double x, double y) {
249 FlutterPointerEvent event = {};
250 event.x = x;
251 event.y = y;
252 SetEventPhaseFromCursorButtonState(window, &event);
253 SendPointerEventWithData(window, event);
254 }
255
256 // Reports mouse button press to the Flutter engine.
GLFWMouseButtonCallback(GLFWwindow * window,int key,int action,int mods)257 static void GLFWMouseButtonCallback(GLFWwindow* window,
258 int key,
259 int action,
260 int mods) {
261 // Flutter currently doesn't understand other buttons, so ignore anything
262 // other than left.
263 if (key != GLFW_MOUSE_BUTTON_LEFT) {
264 return;
265 }
266 FlutterPointerEvent event = {};
267 event.phase = (action == GLFW_PRESS) ? FlutterPointerPhase::kDown
268 : FlutterPointerPhase::kUp;
269 SetEventLocationFromCursorPosition(window, &event);
270 SendPointerEventWithData(window, event);
271
272 // If mouse tracking isn't already enabled, turn it on for the duration of
273 // the drag to generate kMove events.
274 bool hover_enabled =
275 GetSavedWindowState(window)->window_wrapper->hover_tracking_enabled;
276 if (!hover_enabled) {
277 glfwSetCursorPosCallback(
278 window, (action == GLFW_PRESS) ? GLFWCursorPositionCallback : nullptr);
279 }
280 // Disable enter/exit events while the mouse button is down; GLFW will send
281 // an exit event when the mouse button is released, and the pointer should
282 // stay valid until then.
283 if (hover_enabled) {
284 glfwSetCursorEnterCallback(
285 window, (action == GLFW_PRESS) ? nullptr : GLFWCursorEnterCallback);
286 }
287 }
288
289 // Reports scroll wheel events to the Flutter engine.
GLFWScrollCallback(GLFWwindow * window,double delta_x,double delta_y)290 static void GLFWScrollCallback(GLFWwindow* window,
291 double delta_x,
292 double delta_y) {
293 FlutterPointerEvent event = {};
294 SetEventLocationFromCursorPosition(window, &event);
295 SetEventPhaseFromCursorButtonState(window, &event);
296 event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll;
297 // TODO: See if this can be queried from the OS; this value is chosen
298 // arbitrarily to get something that feels reasonable.
299 const int kScrollOffsetMultiplier = 20;
300 event.scroll_delta_x = delta_x * kScrollOffsetMultiplier;
301 event.scroll_delta_y = -delta_y * kScrollOffsetMultiplier;
302 SendPointerEventWithData(window, event);
303 }
304
305 // Passes character input events to registered handlers.
GLFWCharCallback(GLFWwindow * window,unsigned int code_point)306 static void GLFWCharCallback(GLFWwindow* window, unsigned int code_point) {
307 for (const auto& handler :
308 GetSavedWindowState(window)->keyboard_hook_handlers) {
309 handler->CharHook(window, code_point);
310 }
311 }
312
313 // Passes raw key events to registered handlers.
GLFWKeyCallback(GLFWwindow * window,int key,int scancode,int action,int mods)314 static void GLFWKeyCallback(GLFWwindow* window,
315 int key,
316 int scancode,
317 int action,
318 int mods) {
319 for (const auto& handler :
320 GetSavedWindowState(window)->keyboard_hook_handlers) {
321 handler->KeyboardHook(window, key, scancode, action, mods);
322 }
323 }
324
325 // Enables/disables the callbacks related to mouse tracking.
SetHoverCallbacksEnabled(GLFWwindow * window,bool enabled)326 static void SetHoverCallbacksEnabled(GLFWwindow* window, bool enabled) {
327 glfwSetCursorEnterCallback(window,
328 enabled ? GLFWCursorEnterCallback : nullptr);
329 glfwSetCursorPosCallback(window,
330 enabled ? GLFWCursorPositionCallback : nullptr);
331 }
332
333 // Flushes event queue and then assigns default window callbacks.
GLFWAssignEventCallbacks(GLFWwindow * window)334 static void GLFWAssignEventCallbacks(GLFWwindow* window) {
335 glfwPollEvents();
336 glfwSetKeyCallback(window, GLFWKeyCallback);
337 glfwSetCharCallback(window, GLFWCharCallback);
338 glfwSetMouseButtonCallback(window, GLFWMouseButtonCallback);
339 glfwSetScrollCallback(window, GLFWScrollCallback);
340 if (GetSavedWindowState(window)->window_wrapper->hover_tracking_enabled) {
341 SetHoverCallbacksEnabled(window, true);
342 }
343 }
344
GLFWMakeContextCurrent(void * user_data)345 static bool GLFWMakeContextCurrent(void* user_data) {
346 GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data);
347 glfwMakeContextCurrent(window);
348 return true;
349 }
350
GLFWMakeResourceContextCurrent(void * user_data)351 static bool GLFWMakeResourceContextCurrent(void* user_data) {
352 GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data);
353 glfwMakeContextCurrent(GetSavedWindowState(window)->resource_window.get());
354 return true;
355 }
356
GLFWClearContext(void * user_data)357 static bool GLFWClearContext(void* user_data) {
358 glfwMakeContextCurrent(nullptr);
359 return true;
360 }
361
GLFWPresent(void * user_data)362 static bool GLFWPresent(void* user_data) {
363 GLFWwindow* window = reinterpret_cast<GLFWwindow*>(user_data);
364 glfwSwapBuffers(window);
365 return true;
366 }
367
GLFWGetActiveFbo(void * user_data)368 static uint32_t GLFWGetActiveFbo(void* user_data) {
369 return 0;
370 }
371
372 // Clears the GLFW window to Material Blue-Grey.
373 //
374 // This function is primarily to fix an issue when the Flutter Engine is
375 // spinning up, wherein artifacts of existing windows are rendered onto the
376 // canvas for a few moments.
377 //
378 // This function isn't necessary, but makes starting the window much easier on
379 // the eyes.
GLFWClearCanvas(GLFWwindow * window)380 static void GLFWClearCanvas(GLFWwindow* window) {
381 glfwMakeContextCurrent(window);
382 // This color is Material Blue Grey.
383 glClearColor(236.0f / 255.0f, 239.0f / 255.0f, 241.0f / 255.0f, 0.0f);
384 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
385 glFlush();
386 glfwSwapBuffers(window);
387 glfwMakeContextCurrent(nullptr);
388 }
389
390 // Resolves the address of the specified OpenGL or OpenGL ES
391 // core or extension function, if it is supported by the current context.
GLFWProcResolver(void * user_data,const char * name)392 static void* GLFWProcResolver(void* user_data, const char* name) {
393 return reinterpret_cast<void*>(glfwGetProcAddress(name));
394 }
395
GLFWErrorCallback(int error_code,const char * description)396 static void GLFWErrorCallback(int error_code, const char* description) {
397 std::cerr << "GLFW error " << error_code << ": " << description << std::endl;
398 }
399
400 // Spins up an instance of the Flutter Engine.
401 //
402 // This function launches the Flutter Engine in a background thread, supplying
403 // the necessary callbacks for rendering within a GLFWwindow (if one is
404 // provided).
405 //
406 // Returns a caller-owned pointer to the engine.
407 static FLUTTER_API_SYMBOL(FlutterEngine)
RunFlutterEngine(GLFWwindow * window,const SurfacePresentCallback & sendSurface,const FlutterCustomTaskRunners * custom_task_runners)408 RunFlutterEngine(GLFWwindow* window,
409 const SurfacePresentCallback& sendSurface,
410 const FlutterCustomTaskRunners* custom_task_runners) {
411 // FlutterProjectArgs is expecting a full argv, so when processing it for
412 // flags the first item is treated as the executable and ignored. Add a dummy
413 // value so that all provided arguments are used.
414 std::vector<const char*> argv = {"placeholder"};
415
416 FlutterRendererConfig config = {};
417 if (window == nullptr) {
418 config.type = kOpenGL;
419 config.open_gl.struct_size = sizeof(config.open_gl);
420 config.open_gl.make_current = [](void* data) -> bool { return false; };
421 config.open_gl.clear_current = [](void* data) -> bool { return false; };
422 config.open_gl.present = [](void* data) -> bool { return false; };
423 config.open_gl.fbo_callback = [](void* data) -> uint32_t { return 0; };
424 } else {
425 // Provide the necessary callbacks for rendering within a GLFWwindow.
426 config.type = kOpenGL;
427 config.open_gl.struct_size = sizeof(config.open_gl);
428 config.open_gl.make_current = GLFWMakeContextCurrent;
429 config.open_gl.clear_current = GLFWClearContext;
430 config.open_gl.present = GLFWPresent;
431 config.open_gl.send_current_surface = sendSurface;
432 config.open_gl.fbo_callback = GLFWGetActiveFbo;
433 config.open_gl.make_resource_current = GLFWMakeResourceContextCurrent;
434 config.open_gl.gl_proc_resolver = GLFWProcResolver;
435 }
436 FlutterProjectArgs args = {};
437 args.struct_size = sizeof(FlutterProjectArgs);
438 args.custom_task_runners = custom_task_runners;
439 FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr;
440 auto result =
441 FlutterEngineRun(&config, &args, window, &engine);
442 if (result != kSuccess || engine == nullptr) {
443 std::cerr << "Failed to start Flutter engine: error " << result
444 << std::endl;
445 return nullptr;
446 }
447 return engine;
448 }
449
FlutterDesktopInit()450 bool FlutterDesktopInit() {
451 // Before making any GLFW calls, set up a logging error handler.
452 glfwSetErrorCallback(GLFWErrorCallback);
453 return glfwInit();
454 }
455
FlutterDesktopTerminate()456 void FlutterDesktopTerminate() {
457 glfwTerminate();
458 }
459
FlutterDesktopCreateWindow(int & initial_width,int & initial_height,const char * title,const SurfacePresentCallback & sendSurface)460 FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow(
461 int &initial_width,
462 int &initial_height,
463 const char* title,
464 const SurfacePresentCallback& sendSurface) {
465 auto state = std::make_unique<FlutterDesktopWindowControllerState>();
466
467 #ifndef USE_GLFW_WINDOW
468 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
469 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
470 #endif
471
472 // Create the window, and set the state as its user data.
473 state->window = UniqueGLFWwindowPtr(
474 glfwCreateWindow(initial_width, initial_height, title, NULL, NULL),
475 glfwDestroyWindow);
476 GLFWwindow* window = state->window.get();
477 if (window == nullptr) {
478 return nullptr;
479 }
480 GLFWClearCanvas(window);
481 glfwSetWindowUserPointer(window, state.get());
482
483 // Create the share window before starting the engine, since it may call
484 // GLFWMakeResourceContextCurrent immediately.
485 state->resource_window = CreateShareWindowForWindow(window);
486
487 // Create an event loop for the window. It is not running yet.
488 state->event_loop = std::make_unique<flutter::GLFWEventLoop>(
489 std::this_thread::get_id(), // main GLFW thread
490 [state = state.get()](const auto* task) {
491 if (FlutterEngineRunTask(state->engine, task) != kSuccess) {
492 std::cerr << "Could not post an engine task." << std::endl;
493 }
494 });
495
496 // Configure task runner interop.
497 FlutterTaskRunnerDescription platform_task_runner = {};
498 platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription);
499 platform_task_runner.user_data = state.get();
500 platform_task_runner.runs_task_on_current_thread_callback =
501 [](void* state) -> bool {
502 return reinterpret_cast<FlutterDesktopWindowControllerState*>(state)
503 ->event_loop->RunsTasksOnCurrentThread();
504 };
505 platform_task_runner.post_task_callback =
506 [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void {
507 reinterpret_cast<FlutterDesktopWindowControllerState*>(state)
508 ->event_loop->PostTask(task, target_time_nanos);
509 };
510
511 FlutterCustomTaskRunners custom_task_runners = {};
512 custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
513 custom_task_runners.platform_task_runner = &platform_task_runner;
514 custom_task_runners.render_task_runner = &platform_task_runner;
515
516 // Start the engine.
517 state->engine =
518 RunFlutterEngine(window, sendSurface, &custom_task_runners);
519 if (state->engine == nullptr) {
520 return nullptr;
521 }
522
523 state->window_wrapper = std::make_unique<FlutterDesktopWindow>();
524 state->window_wrapper->window = window;
525
526 // Trigger an initial size callback to send size information to Flutter.
527 state->monitor_screen_coordinates_per_inch = GetScreenCoordinatesPerInch();
528 int width_px, height_px;
529 #ifdef USE_GLFW_WINDOW
530 glfwGetFramebufferSize(window, &width_px, &height_px);
531 initial_width = width_px;
532 initial_height = height_px;
533 #else
534 glfwGetWindowSize(window, &width_px, &height_px);
535 #endif
536 GLFWFramebufferSizeCallback(window, width_px, height_px);
537
538 // Set up GLFW callbacks for the window.
539 glfwSetFramebufferSizeCallback(window, GLFWFramebufferSizeCallback);
540 glfwSetWindowRefreshCallback(window, GLFWWindowRefreshCallback);
541 GLFWAssignEventCallbacks(window);
542
543 return state.release();
544 }
545
FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller)546 void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) {
547 FlutterEngineShutdown(controller->engine);
548 delete controller;
549 }
550
FlutterDesktopSetIdleCallback(FlutterDesktopWindowControllerRef controller,const IdleCallback & idleCallback)551 void FlutterDesktopSetIdleCallback(FlutterDesktopWindowControllerRef controller,
552 const IdleCallback& idleCallback) {
553 FlutterEngineSetIdleNotificationCallback(controller->engine, idleCallback);
554 }
555
FlutterDesktopAddKeyboardHookHandler(FlutterDesktopWindowControllerRef controller,std::unique_ptr<flutter::KeyboardHookHandler> keyboardHookHandler)556 void FlutterDesktopAddKeyboardHookHandler(FlutterDesktopWindowControllerRef controller,
557 std::unique_ptr<flutter::KeyboardHookHandler> keyboardHookHandler) {
558 controller->keyboard_hook_handlers.push_back(std::move(keyboardHookHandler));
559 }
560
FlutterDesktopSetClipboardData(FlutterDesktopWindowControllerRef controller,const char * data)561 FLUTTER_EXPORT void FlutterDesktopSetClipboardData(
562 FlutterDesktopWindowControllerRef controller, const char* data) {
563 GLFWwindow* window = FlutterDesktopGetWindow(controller)->window;
564 glfwSetClipboardString(window, data);
565 }
566
FlutterDesktopGetClipboardData(FlutterDesktopWindowControllerRef controller)567 FLUTTER_EXPORT const char* FlutterDesktopGetClipboardData(
568 FlutterDesktopWindowControllerRef controller) {
569 GLFWwindow* window = FlutterDesktopGetWindow(controller)->window;
570 auto result = glfwGetClipboardString(window);
571 return result == NULL ? "" : result;
572 }
573
FlutterDesktopGetFramebufferSize(FlutterDesktopWindowRef flutter_window,int * width,int * height)574 void FlutterDesktopGetFramebufferSize(FlutterDesktopWindowRef flutter_window,
575 int* width,
576 int* height) {
577 glfwGetFramebufferSize(flutter_window->window, width, height);
578 }
579
FlutterDesktopWindowSetHoverEnabled(FlutterDesktopWindowRef flutter_window,bool enabled)580 void FlutterDesktopWindowSetHoverEnabled(FlutterDesktopWindowRef flutter_window,
581 bool enabled) {
582 flutter_window->hover_tracking_enabled = enabled;
583 SetHoverCallbacksEnabled(flutter_window->window, enabled);
584 }
585
FlutterDesktopWindowSetTitle(FlutterDesktopWindowRef flutter_window,const char * title)586 void FlutterDesktopWindowSetTitle(FlutterDesktopWindowRef flutter_window,
587 const char* title) {
588 GLFWwindow* window = flutter_window->window;
589 glfwSetWindowTitle(window, title);
590 }
591
FlutterDesktopWindowSetIcon(FlutterDesktopWindowRef flutter_window,uint8_t * pixel_data,int width,int height)592 void FlutterDesktopWindowSetIcon(FlutterDesktopWindowRef flutter_window,
593 uint8_t* pixel_data,
594 int width,
595 int height) {
596 GLFWimage image = {width, height, static_cast<unsigned char*>(pixel_data)};
597 glfwSetWindowIcon(flutter_window->window, pixel_data ? 1 : 0, &image);
598 }
599
FlutterDesktopWindowGetFrame(FlutterDesktopWindowRef flutter_window,int * x,int * y,int * width,int * height)600 void FlutterDesktopWindowGetFrame(FlutterDesktopWindowRef flutter_window,
601 int* x,
602 int* y,
603 int* width,
604 int* height) {
605 glfwGetWindowPos(flutter_window->window, x, y);
606 glfwGetWindowSize(flutter_window->window, width, height);
607 // The above gives content area size and position; adjust for the window
608 // decoration to give actual window frame.
609 int frame_left, frame_top, frame_right, frame_bottom;
610 glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top,
611 &frame_right, &frame_bottom);
612 if (x) {
613 *x -= frame_left;
614 }
615 if (y) {
616 *y -= frame_top;
617 }
618 if (width) {
619 *width += frame_left + frame_right;
620 }
621 if (height) {
622 *height += frame_top + frame_bottom;
623 }
624 }
625
FlutterDesktopWindowSetFrame(FlutterDesktopWindowRef flutter_window,int x,int y,int width,int height)626 void FlutterDesktopWindowSetFrame(FlutterDesktopWindowRef flutter_window,
627 int x,
628 int y,
629 int width,
630 int height) {
631 // Get the window decoration sizes to adjust, since the GLFW setters take
632 // content position and size.
633 int frame_left, frame_top, frame_right, frame_bottom;
634 glfwGetWindowFrameSize(flutter_window->window, &frame_left, &frame_top,
635 &frame_right, &frame_bottom);
636 glfwSetWindowPos(flutter_window->window, x + frame_left, y + frame_top);
637 glfwSetWindowSize(flutter_window->window, width - frame_left - frame_right,
638 height - frame_top - frame_bottom);
639 }
640
FlutterDesktopGetWindowSize(FlutterDesktopWindowRef flutter_window,int * width,int * height)641 void FlutterDesktopGetWindowSize(FlutterDesktopWindowRef flutter_window,
642 int* width,
643 int* height) {
644 glfwGetWindowSize(flutter_window->window, width, height);
645 }
646
FlutterDesktopSetWindowSize(FlutterDesktopWindowRef flutter_window,int & width,int & height)647 void FlutterDesktopSetWindowSize(FlutterDesktopWindowRef flutter_window,
648 int& width,
649 int& height) {
650 glfwSetWindowSize(flutter_window->window, width, height);
651 #ifdef USE_GLFW_WINDOW
652 int width_px, height_px;
653 glfwGetFramebufferSize(flutter_window->window, &width_px, &height_px);
654 width = width_px;
655 height = height_px;
656 #endif
657 GLFWFramebufferSizeCallback(flutter_window->window, width, height);
658
659 // Set up GLFW callbacks for the window.
660 glfwSetFramebufferSizeCallback(flutter_window->window, GLFWFramebufferSizeCallback);
661 glfwSetWindowRefreshCallback(flutter_window->window, GLFWWindowRefreshCallback);
662 GLFWAssignEventCallbacks(flutter_window->window);
663 }
664
FlutterDesktopWindowGetScaleFactor(FlutterDesktopWindowRef flutter_window)665 double FlutterDesktopWindowGetScaleFactor(
666 FlutterDesktopWindowRef flutter_window) {
667 return flutter_window->pixels_per_screen_coordinate;
668 }
669
FlutterDesktopRunWindowLoop(FlutterDesktopWindowControllerRef controller)670 void FlutterDesktopRunWindowLoop(FlutterDesktopWindowControllerRef controller) {
671 GLFWwindow* window = controller->window.get();
672 while (!glfwWindowShouldClose(window)) {
673 auto wait_duration = std::chrono::milliseconds::max();
674 controller->event_loop->WaitForEvents(wait_duration);
675 }
676 FlutterDesktopDestroyWindow(controller);
677 }
678
FlutterDesktopWaitForEvents(FlutterDesktopWindowControllerRef controller)679 void FlutterDesktopWaitForEvents(FlutterDesktopWindowControllerRef controller) {
680 auto wait_duration = std::chrono::milliseconds::max();
681 controller->event_loop->WaitForEvents(wait_duration);
682 }
683
FlutterDesktopWindowShouldClose(FlutterDesktopWindowControllerRef controller)684 bool FlutterDesktopWindowShouldClose(FlutterDesktopWindowControllerRef controller) {
685 GLFWwindow* window = controller->window.get();
686 return glfwWindowShouldClose(window);
687 }
688
FlutterDesktopGetWindow(FlutterDesktopWindowControllerRef controller)689 FlutterDesktopWindowRef FlutterDesktopGetWindow(
690 FlutterDesktopWindowControllerRef controller) {
691 // Currently, one registrar acts as the registrar for all plugins, so the
692 // name is ignored. It is part of the API to reduce churn in the future when
693 // aligning more closely with the Flutter registrar system.
694 return controller->window_wrapper.get();
695 }
696
FlutterDesktopRunEngine()697 FlutterDesktopEngineRef FlutterDesktopRunEngine() {
698 auto engine =
699 RunFlutterEngine(nullptr, nullptr, nullptr /* custom task runners */);
700 if (engine == nullptr) {
701 return nullptr;
702 }
703 auto engine_state = new FlutterDesktopEngineState();
704 engine_state->engine = engine;
705 return engine_state;
706 }
707
FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref)708 bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) {
709 std::cout << "Shutting down flutter engine process." << std::endl;
710 auto result = FlutterEngineShutdown(engine_ref->engine);
711 delete engine_ref;
712 return (result == kSuccess);
713 }
714