/
flutter_glfw.cc
1129 lines (992 loc) · 43.1 KB
/
flutter_glfw.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 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>;
// 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.
std::unique_ptr<FlutterDesktopMessenger> messenger;
// 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 {
// The engine that backs this messenger.
FlutterDesktopEngineState* engine;
};
// 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: 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(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.u8string()
<< "; 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;
}
}
std::string assets_path_string = assets_path.u8string();
std::string icu_path_string = icu_path.u8string();
std::string lib_path_string = aot_library_path.u8string();
// 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_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 = EngineOnFlutterPlatformMessage;
args.custom_task_runners = &task_runners;
if (FlutterEngineRunsAOTCompiledDartCode()) {
engine_state->aot_data = LoadAotData(lib_path_string);
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 = std::make_unique<FlutterDesktopMessenger>();
state->messenger->engine = 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) {
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();
}