diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ef7478a6914a0..1e9e7d1604090 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1132,6 +1132,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/flutter_view_controller.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 54e1bbbd7c709..d0221b1e392ca 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -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}}", + ] + } +} diff --git a/shell/platform/linux/config.gni b/shell/platform/linux/config.gni new file mode 100644 index 0000000000000..012f3d5438b44 --- /dev/null +++ b/shell/platform/linux/config.gni @@ -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 +} diff --git a/shell/platform/linux/config/BUILD.gn b/shell/platform/linux/config/BUILD.gn index 48cf022355cff..19f8001db2fb5 100644 --- a/shell/platform/linux/config/BUILD.gn +++ b/shell/platform/linux/config/BUILD.gn @@ -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" ] + } +} diff --git a/shell/platform/linux/fl_dart_project.cc b/shell/platform/linux/fl_dart_project.cc new file mode 100644 index 0000000000000..cbabc8cb9648f --- /dev/null +++ b/shell/platform/linux/fl_dart_project.cc @@ -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 + +/** + * 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(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(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( + 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; +} diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc new file mode 100644 index 0000000000000..218f1aed410c5 --- /dev/null +++ b/shell/platform/linux/fl_view.cc @@ -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 +#include +#include +#include + +#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(eglGetProcAddress(name)); +} + +static bool fl_view_gl_make_current(void* user_data) { + FlView* self = static_cast(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(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(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(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(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( + g_object_new(fl_view_get_type(), "flutter-project", project, nullptr)); +} diff --git a/shell/platform/linux/public/flutter_linux/fl_dart_project.h b/shell/platform/linux/public/flutter_linux/fl_dart_project.h new file mode 100644 index 0000000000000..d2a62b56b72f9 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_dart_project.h @@ -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 + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only 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_ diff --git a/shell/platform/linux/public/flutter_linux/fl_view.h b/shell/platform/linux/public/flutter_linux/fl_view.h new file mode 100644 index 0000000000000..a47733e251747 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_view.h @@ -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 can be included directly." +#endif + +#include + +#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_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h new file mode 100644 index 0000000000000..b63b05c24a073 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -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 +#include + +#undef __FLUTTER_LINUX_INSIDE__ + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_