mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
293 lines
9.3 KiB
C++
293 lines
9.3 KiB
C++
// 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/linux/public/flutter_linux/fl_view.h"
|
|
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
#include <GLES2/gl2.h>
|
|
#include <gdk/gdkx.h>
|
|
|
|
#include "flutter/shell/platform/embedder/embedder.h"
|
|
|
|
/**
|
|
* FlView:
|
|
*
|
|
* #FlView is a GTK widget that is capable of displaying a Flutter application.
|
|
*/
|
|
|
|
struct _FlView {
|
|
GtkWidget parent_instance;
|
|
|
|
EGLDisplay egl_display;
|
|
EGLSurface egl_surface;
|
|
EGLContext egl_context;
|
|
|
|
FlDartProject* flutter_project;
|
|
FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine;
|
|
};
|
|
|
|
enum { PROP_FLUTTER_PROJECT = 1, PROP_LAST };
|
|
|
|
G_DEFINE_TYPE(FlView, fl_view, GTK_TYPE_WIDGET)
|
|
|
|
static gboolean initialize_egl(FlView* self) {
|
|
/* Note that we don't provide the XDisplay from GTK, this would make both
|
|
* GTK and EGL share the same X connection and this would crash when used by
|
|
* a Flutter thread. So the EGL display and GTK both have separate
|
|
* connections.
|
|
*/
|
|
self->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
|
|
EGLint egl_major, egl_minor;
|
|
if (!eglInitialize(self->egl_display, &egl_major, &egl_minor)) {
|
|
g_warning("Failed to initialze EGL");
|
|
return FALSE;
|
|
}
|
|
// TODO(robert-ancell): It would probably be useful to store the EGL version
|
|
// for debugging purposes
|
|
|
|
EGLint attributes[] = {EGL_RENDERABLE_TYPE,
|
|
EGL_OPENGL_ES2_BIT,
|
|
EGL_RED_SIZE,
|
|
8,
|
|
EGL_GREEN_SIZE,
|
|
8,
|
|
EGL_BLUE_SIZE,
|
|
8,
|
|
EGL_ALPHA_SIZE,
|
|
8,
|
|
EGL_NONE};
|
|
EGLConfig egl_config;
|
|
EGLint n_config;
|
|
if (!eglChooseConfig(self->egl_display, attributes, &egl_config, 1,
|
|
&n_config)) {
|
|
g_warning("Failed to choose EGL config");
|
|
return FALSE;
|
|
}
|
|
if (n_config == 0) {
|
|
g_warning("Failed to find appropriate EGL config");
|
|
return FALSE;
|
|
}
|
|
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
|
|
g_warning("Failed to bind EGL OpenGL ES API");
|
|
return FALSE;
|
|
}
|
|
|
|
Window xid = gdk_x11_window_get_xid(gtk_widget_get_window(GTK_WIDGET(self)));
|
|
self->egl_surface =
|
|
eglCreateWindowSurface(self->egl_display, egl_config, xid, nullptr);
|
|
EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
|
|
self->egl_context = eglCreateContext(self->egl_display, egl_config,
|
|
EGL_NO_CONTEXT, context_attributes);
|
|
EGLint value;
|
|
eglQueryContext(self->egl_display, self->egl_context,
|
|
EGL_CONTEXT_CLIENT_VERSION, &value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void* fl_view_gl_proc_resolver(void* user_data, const char* name) {
|
|
return reinterpret_cast<void*>(eglGetProcAddress(name));
|
|
}
|
|
|
|
static bool fl_view_gl_make_current(void* user_data) {
|
|
FlView* self = static_cast<FlView*>(user_data);
|
|
|
|
if (!eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface,
|
|
self->egl_context))
|
|
g_warning("Failed to make EGL context current");
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool fl_view_gl_clear_current(void* user_data) {
|
|
FlView* self = static_cast<FlView*>(user_data);
|
|
|
|
if (!eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
|
EGL_NO_CONTEXT))
|
|
g_warning("Failed to make EGL context current");
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint32_t fl_view_gl_fbo_callback(void* user_data) {
|
|
/* There is only one frame buffer object - always return that */
|
|
return 0;
|
|
}
|
|
|
|
static bool fl_view_gl_present(void* user_data) {
|
|
FlView* self = static_cast<FlView*>(user_data);
|
|
|
|
if (!eglSwapBuffers(self->egl_display, self->egl_surface))
|
|
g_warning("Failed to swap EGL buffers");
|
|
|
|
return true;
|
|
}
|
|
|
|
static gboolean run_flutter_engine(FlView* self) {
|
|
FlutterRendererConfig config = {};
|
|
config.type = kOpenGL;
|
|
config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig);
|
|
config.open_gl.gl_proc_resolver = fl_view_gl_proc_resolver;
|
|
config.open_gl.make_current = fl_view_gl_make_current;
|
|
config.open_gl.clear_current = fl_view_gl_clear_current;
|
|
config.open_gl.fbo_callback = fl_view_gl_fbo_callback;
|
|
config.open_gl.present = fl_view_gl_present;
|
|
|
|
g_autofree gchar* assets_path =
|
|
fl_dart_project_get_assets_path(self->flutter_project);
|
|
g_autofree gchar* icu_data_path =
|
|
fl_dart_project_get_icu_data_path(self->flutter_project);
|
|
|
|
FlutterProjectArgs args = {};
|
|
args.struct_size = sizeof(FlutterProjectArgs);
|
|
args.assets_path = assets_path;
|
|
args.icu_data_path = icu_data_path;
|
|
|
|
FlutterEngineResult result = FlutterEngineInitialize(
|
|
FLUTTER_ENGINE_VERSION, &config, &args, self, &self->flutter_engine);
|
|
if (result != kSuccess)
|
|
return FALSE;
|
|
|
|
result = FlutterEngineRunInitialized(self->flutter_engine);
|
|
if (result != kSuccess)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void fl_view_set_property(GObject* object,
|
|
guint prop_id,
|
|
const GValue* value,
|
|
GParamSpec* pspec) {
|
|
FlView* self = FL_VIEW(object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FLUTTER_PROJECT:
|
|
g_set_object(&self->flutter_project,
|
|
static_cast<FlDartProject*>(g_value_get_object(value)));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void fl_view_get_property(GObject* object,
|
|
guint prop_id,
|
|
GValue* value,
|
|
GParamSpec* pspec) {
|
|
FlView* self = FL_VIEW(object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FLUTTER_PROJECT:
|
|
g_value_set_object(value, self->flutter_project);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void fl_view_dispose(GObject* object) {
|
|
FlView* self = FL_VIEW(object);
|
|
|
|
FlutterEngineDeinitialize(self->flutter_engine);
|
|
FlutterEngineShutdown(self->flutter_engine);
|
|
|
|
if (!eglDestroyContext(self->egl_display, self->egl_context))
|
|
g_warning("Failed to destroy EGL context");
|
|
if (!eglDestroySurface(self->egl_display, self->egl_surface))
|
|
g_warning("Failed to destroy EGL surface");
|
|
if (!eglTerminate(self->egl_display))
|
|
g_warning("Failed to terminate EGL display");
|
|
|
|
g_clear_object(&self->flutter_project);
|
|
|
|
G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
|
|
}
|
|
|
|
static void fl_view_realize(GtkWidget* widget) {
|
|
FlView* self = FL_VIEW(widget);
|
|
|
|
gtk_widget_set_realized(widget, TRUE);
|
|
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_allocation(widget, &allocation);
|
|
|
|
GdkWindowAttr window_attributes;
|
|
window_attributes.window_type = GDK_WINDOW_CHILD;
|
|
window_attributes.x = allocation.x;
|
|
window_attributes.y = allocation.y;
|
|
window_attributes.width = allocation.width;
|
|
window_attributes.height = allocation.height;
|
|
window_attributes.wclass = GDK_INPUT_OUTPUT;
|
|
window_attributes.visual = gtk_widget_get_visual(widget);
|
|
window_attributes.event_mask =
|
|
gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
|
|
|
|
gint window_attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
|
|
|
|
GdkWindow* window =
|
|
gdk_window_new(gtk_widget_get_parent_window(widget), &window_attributes,
|
|
window_attributes_mask);
|
|
gtk_widget_register_window(widget, window);
|
|
gtk_widget_set_window(widget, window);
|
|
|
|
if (initialize_egl(self))
|
|
run_flutter_engine(self);
|
|
}
|
|
|
|
static void fl_view_size_allocate(GtkWidget* widget,
|
|
GtkAllocation* allocation) {
|
|
FlView* self = FL_VIEW(widget);
|
|
|
|
gtk_widget_set_allocation(widget, allocation);
|
|
|
|
if (gtk_widget_get_realized(widget) && gtk_widget_get_has_window(widget))
|
|
gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
|
|
allocation->y, allocation->width,
|
|
allocation->height);
|
|
|
|
FlutterWindowMetricsEvent event = {};
|
|
event.struct_size = sizeof(FlutterWindowMetricsEvent);
|
|
event.width = allocation->width;
|
|
event.height = allocation->height;
|
|
event.pixel_ratio =
|
|
1; // TODO(robert-ancell): This won't work on hidpi displays
|
|
FlutterEngineSendWindowMetricsEvent(self->flutter_engine, &event);
|
|
}
|
|
|
|
static void fl_view_class_init(FlViewClass* klass) {
|
|
G_OBJECT_CLASS(klass)->set_property = fl_view_set_property;
|
|
G_OBJECT_CLASS(klass)->get_property = fl_view_get_property;
|
|
G_OBJECT_CLASS(klass)->dispose = fl_view_dispose;
|
|
GTK_WIDGET_CLASS(klass)->realize = fl_view_realize;
|
|
GTK_WIDGET_CLASS(klass)->size_allocate = fl_view_size_allocate;
|
|
|
|
g_object_class_install_property(
|
|
G_OBJECT_CLASS(klass), PROP_FLUTTER_PROJECT,
|
|
g_param_spec_object(
|
|
"flutter-project", "flutter-project", "Flutter project in use",
|
|
fl_dart_project_get_type(),
|
|
static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS)));
|
|
}
|
|
|
|
static void fl_view_init(FlView* self) {}
|
|
|
|
/**
|
|
* fl_view_new:
|
|
* @project: The project to show.
|
|
*
|
|
* Create a widget to show Flutter application.
|
|
*
|
|
* Returns: a new #FlView
|
|
*/
|
|
G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) {
|
|
return static_cast<FlView*>(
|
|
g_object_new(fl_view_get_type(), "flutter-project", project, nullptr));
|
|
}
|