Add a Linux Shell that uses GTK for rendering. (flutter/engine#16977)

This commit is contained in:
Robert Ancell 2020-03-25 09:26:02 +13:00 committed by GitHub
parent 2f3a5f36fd
commit 71d631160f
9 changed files with 574 additions and 1 deletions

View File

@ -1145,6 +1145,11 @@ FILE: ../../../flutter/shell/platform/glfw/platform_handler.h
FILE: ../../../flutter/shell/platform/glfw/public/flutter_glfw.h
FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.cc
FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.h
FILE: ../../../flutter/shell/platform/linux/fl_dart_project.cc
FILE: ../../../flutter/shell/platform/linux/fl_view.cc
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h
FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.cc
FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.h
FILE: ../../../flutter/shell/platform/windows/client_wrapper/dart_project_unittests.cc

View File

@ -5,15 +5,23 @@
assert(is_linux)
import("//flutter/shell/platform/glfw/config.gni")
import("//flutter/shell/platform/linux/config.gni")
group("linux") {
deps = []
if (build_glfw_shell) {
deps = [
deps += [
":flutter_linux_glfw",
"//flutter/shell/platform/glfw:publish_headers_glfw",
"//flutter/shell/platform/glfw/client_wrapper:publish_wrapper_glfw",
]
}
if (build_linux_shell) {
deps += [
":flutter_linux_gtk",
":publish_headers_linux",
]
}
}
# Temporary workaround for the issue describe in
@ -37,3 +45,49 @@ if (build_glfw_shell) {
public_configs = [ "//flutter:config" ]
}
}
if (build_linux_shell) {
_public_headers = [
"public/flutter_linux/fl_dart_project.h",
"public/flutter_linux/fl_view.h",
"public/flutter_linux/flutter_linux.h",
]
config("relative_flutter_linux_headers") {
include_dirs = [ "public" ]
}
source_set("flutter_linux") {
sources = [
"fl_dart_project.cc",
"fl_view.cc",
]
configs += [
"//flutter/shell/platform/linux/config:gtk",
"//flutter/shell/platform/linux/config:egl",
]
# Set flag to stop headers being directly included (library users should not do this)
defines = [ "FLUTTER_LINUX_COMPILATION" ]
deps = [
"//flutter/shell/platform/embedder:embedder_with_symbol_prefix",
]
}
shared_library("flutter_linux_gtk") {
deps = [
":flutter_linux",
]
public_configs = [ "//flutter:config" ]
}
copy("publish_headers_linux") {
sources = _public_headers
outputs = [
"$root_out_dir/flutter_linux/{{source_file_part}}",
]
}
}

View File

@ -0,0 +1,11 @@
# 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.
declare_args() {
# Whether to build the Linux (GTK) shell for the host platform, if available.
#
# The Linux shell is not currently built by default as the CI system doesn't
# (yet) have GTK as a dependency. When that is ready the flag will be removed.
build_linux_shell = false
}

View File

@ -3,7 +3,17 @@
# found in the LICENSE file.
import("//build/config/linux/pkg_config.gni")
import("//flutter/shell/platform/linux/config.gni")
pkg_config("x11") {
packages = [ "x11" ]
}
if (build_linux_shell) {
pkg_config("gtk") {
packages = [ "gtk+-3.0" ]
}
pkg_config("egl") {
packages = [ "egl" ]
}
}

View File

@ -0,0 +1,140 @@
// 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_dart_project.h"
#include <gmodule.h>
/**
* FlDartProject:
*
* #FlDartProject represents a Dart project. It is used provide information
* about the application when creating a #FlView.
*/
struct _FlDartProject {
GObject parent_instance;
gchar* assets_path;
gchar* icu_data_path;
};
enum { PROP_ASSETS_PATH = 1, PROP_ICU_DATA_PATH, PROP_LAST };
G_DEFINE_TYPE(FlDartProject, fl_dart_project, G_TYPE_OBJECT)
static void fl_dart_project_set_property(GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec) {
FlDartProject* self = FL_DART_PROJECT(object);
switch (prop_id) {
case PROP_ASSETS_PATH:
g_free(self->assets_path);
self->assets_path = g_strdup(g_value_get_string(value));
break;
case PROP_ICU_DATA_PATH:
g_free(self->icu_data_path);
self->icu_data_path = g_strdup(g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void fl_dart_project_get_property(GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec) {
FlDartProject* self = FL_DART_PROJECT(object);
switch (prop_id) {
case PROP_ASSETS_PATH:
g_value_set_string(value, self->assets_path);
break;
case PROP_ICU_DATA_PATH:
g_value_set_string(value, self->icu_data_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void fl_dart_project_dispose(GObject* object) {
FlDartProject* self = FL_DART_PROJECT(object);
g_clear_pointer(&self->assets_path, g_free);
g_clear_pointer(&self->icu_data_path, g_free);
G_OBJECT_CLASS(fl_dart_project_parent_class)->dispose(object);
}
static void fl_dart_project_class_init(FlDartProjectClass* klass) {
G_OBJECT_CLASS(klass)->set_property = fl_dart_project_set_property;
G_OBJECT_CLASS(klass)->get_property = fl_dart_project_get_property;
G_OBJECT_CLASS(klass)->dispose = fl_dart_project_dispose;
g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_ASSETS_PATH,
g_param_spec_string(
"assets-path", "assets-path", "Path to Flutter assets", nullptr,
static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_ICU_DATA_PATH,
g_param_spec_string(
"icu-data-path", "icu-data-path", "Path to ICU data", nullptr,
static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
}
static void fl_dart_project_init(FlDartProject* self) {}
/**
* fl_dart_project_new:
* @assets_path: a file path, e.g. "build/assets"
* @icu_data_path: a file path, e.g. "build/icudtl.dat"
*
* Create a Flutter project.
*
* Returns: a new #FlDartProject
*/
G_MODULE_EXPORT FlDartProject* fl_dart_project_new(const gchar* assets_path,
const gchar* icu_data_path) {
return static_cast<FlDartProject*>(
g_object_new(fl_dart_project_get_type(), "assets-path", assets_path,
"icu-data-path", icu_data_path, nullptr));
}
/**
* fl_dart_project_get_assets_path:
* @view: a #FlDartProject
*
* Get the path to the directory containing the assets used in the Flutter
* application.
*
* Returns: a file path, e.g. "build/assets"
*/
G_MODULE_EXPORT const gchar* fl_dart_project_get_assets_path(
FlDartProject* self) {
g_return_val_if_fail(FL_IS_DART_PROJECT(self), nullptr);
return self->assets_path;
}
/**
* fl_dart_project_get_icu_data_path:
* @view: a #FlDartProject
*
* Get the path to the ICU data file in the Flutter application.
*
* Returns: a file path, e.g. "build/icudtl.dat"
*/
G_MODULE_EXPORT const gchar* fl_dart_project_get_icu_data_path(
FlDartProject* self) {
g_return_val_if_fail(FL_IS_DART_PROJECT(self), nullptr);
return self->icu_data_path;
}

View File

@ -0,0 +1,287 @@
// 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;
FlutterProjectArgs args = {};
args.struct_size = sizeof(FlutterProjectArgs);
args.assets_path = fl_dart_project_get_assets_path(self->flutter_project);
args.icu_data_path = fl_dart_project_get_icu_data_path(self->flutter_project);
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));
}

View File

@ -0,0 +1,27 @@
// 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.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_
#include <glib-object.h>
#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION)
#error "Only <flutter_linux/flutter_linux.h> can be included directly."
#endif
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlDartProject, fl_dart_project, FL, DART_PROJECT, GObject)
FlDartProject* fl_dart_project_new(const gchar* assets_path,
const gchar* icu_data_path);
const gchar* fl_dart_project_get_assets_path(FlDartProject* project);
const gchar* fl_dart_project_get_icu_data_path(FlDartProject* project);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_

View File

@ -0,0 +1,24 @@
// 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.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_
#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION)
#error "Only <flutter_linux/flutter_linux.h> can be included directly."
#endif
#include <gtk/gtk.h>
#include "fl_dart_project.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlView, fl_view, FL, VIEW, GtkWidget)
FlView* fl_view_new(FlDartProject* project);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_

View File

@ -0,0 +1,15 @@
// 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.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_
#define __FLUTTER_LINUX_INSIDE__
#include <flutter_linux/fl_dart_project.h>
#include <flutter_linux/fl_view.h>
#undef __FLUTTER_LINUX_INSIDE__
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_