blob: 879a36201ac176bb68e2778bcab7752b2556f95c [file] [log] [blame]
// 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));
}