diff --git a/.appveyor.yml b/.appveyor.yml index c6debbb57..52efa1940 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,7 +17,10 @@ image: Visual Studio 2017 platform: x64 install: - - ps: build\ci\install_flutter.ps1 $env:APPVEYOR_BUILD_FOLDER\.. + # None of the packaged channels are new enough for the current state + # of FDE, so for now clone master instead. + #- ps: build\ci\install_flutter.ps1 $env:APPVEYOR_BUILD_FOLDER\.. + - git clone -b master https://github.com/flutter/flutter.git %APPVEYOR_BUILD_FOLDER%\..\flutter build_script: - - msbuild "example\windows\Example Embedder.sln" + - msbuild "example\windows_fde\Example Embedder.sln" diff --git a/.travis.yml b/.travis.yml index ab49723a9..283338a30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ matrix: before_script: - export PATH=$PATH:$TRAVIS_BUILD_DIR/../flutter/bin:$TRAVIS_BUILD_DIR/bin script: - - make -C example/linux + - make -C example/linux_fde - os: linux dist: xenial @@ -39,11 +39,11 @@ matrix: before_script: - export PATH=$PATH:$TRAVIS_BUILD_DIR/bin script: - - make -C example/linux USE_GN=1 + - make -C example/linux_fde USE_GN=1 - os: osx language: objective-c - xcode_project: example/macos/ExampleEmbedder.xcodeproj + xcode_project: example/macos_fde/ExampleEmbedder.xcodeproj xcode_scheme: ExampleEmbedder install: - build/ci/install_flutter $TRAVIS_BUILD_DIR/.. diff --git a/Debugging.md b/Debugging.md index 7e27b3b1b..46fc5cb93 100644 --- a/Debugging.md +++ b/Debugging.md @@ -41,7 +41,7 @@ There are two options: The `flutter attach` command can connect to a desktop Flutter application and provide the same interactive command line that `flutter run` would provide. In the directory of the Flutter portion of your application (e.g., -`/example/flutter_app`) run: +`/example/` for this project's example application) run: ``` $ flutter attach --device-id=flutter-tester --debug-port=49494 @@ -52,7 +52,7 @@ necessary to bypass checks for an attached iOS or Android device. ### VS Code -Open the Flutter portion of your application (e.g., `/example/flutter_app`). +Open the Flutter portion of your application (e.g., `/example/`). Add a [launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) like the following, substituting your Observatory port: diff --git a/Flutter-Requirements.md b/Flutter-Requirements.md index cdfcf43b8..8074f8ac0 100644 --- a/Flutter-Requirements.md +++ b/Flutter-Requirements.md @@ -62,3 +62,13 @@ on all platforms. Symptoms of missing fonts can include text failing to display, console logging about failure to load fonts, or in some cases crashes. + +## Plugins + +If your project uses any plugins with platform components, they won't +work, as the native side will be missing. Depending on how the Dart side of the +plugin is written, they may fail gracefully, or may throw errors. + +You may need to make the calls to those plugins conditional based on the host +platform. Alternately, if you have the expertise, you could implement the native +side of the plugin in your desktop project(s). diff --git a/Quick-Start.md b/Quick-Start.md new file mode 100644 index 000000000..bf23f2df8 --- /dev/null +++ b/Quick-Start.md @@ -0,0 +1,48 @@ +# Quick Start + +A common question for people discovering this project is: How do I easily add +desktop support to my existing Flutter application? + +The answer is that at this point, you don't. The project is still in early +stages, and a lot of things are still in flux; if you don't already have +experience doing desktop development on the platform(s) you want to add, +this project is probably not ready for you to use it yet. The focus is currently +on improving core functionality, not on ease of use. Neither the API surface nor +the project structure are stable, and no attempt will be made to provide +supported migration paths as things change. + +However, if you want to try out an existing Flutter application running on the +desktop even with those caveats, and don't have experience with desktop +development, here are two approaches that might work for you. + +With either approach, be sure to follow the [main README](README.md) and +[library README](library/README.md) instructions on setting up prerequisites +and adjusting your Flutter application. + +## Replace the 'example' Flutter Code + +Since `example/` is already configured to run on all the platforms this project +supports, you can swap in your project's Dart code, `pubspec.yaml`, resources, +etc., then follow the [normal directions](example/README.md) for building the +example application on your platform. + +This will be the easiest approach to keep working as the project changes, but +requires that you essentially wrap your whole application in a +flutter-desktop-embedding checkout. + +## Copy the '\*\_fde' Directories + +Starting from the example projects means you don't have to create projects from +scratch, and since they are self-contained they can be added to an existing +project without needing to move it. However, because the projects build +the flutter-desktop-embedding libraries from source, they contain relative paths +to the flutter-desktop-embedding projects and tools they depend on. You will +need to update those paths in order for the projects to work. On Linux, the +variables you will need to change are documented in the Makefile. On macOS and +Windows, you will need some familiarity with Xcode and Visual Studio +respectively to make the changes. + +With this approach, you should expect breakage when you update the +flutter-desktop-embedding reposity; when that happens you will need to look at +what has changed in the example projects and update your copies accordingly, or +start over with fresh copies and adjust the paths again. diff --git a/README.md b/README.md index 36fd1f8ec..b03340051 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ optional plugins to access other native platform functionality. ## How to Use This Code +_If you have an existing Flutter app and just want to get it running, see +the [quick start](Quick-Start.md) page before continuing._ + ### Setting Up The tooling and build infrastructure for this project requires that you have @@ -18,7 +21,7 @@ a Flutter tree in the same parent directory as the clone of this project: ``` - ├─ flutter (from http://github.com/flutter/flutter) + ├─ flutter (from https://github.com/flutter/flutter) └─ flutter-desktop-embedding (from https://github.com/google/flutter-desktop-embedding) ``` diff --git a/build/ci/install_flutter b/build/ci/install_flutter index 9c569fa22..5116aa53b 100755 --- a/build/ci/install_flutter +++ b/build/ci/install_flutter @@ -16,8 +16,8 @@ set -e -readonly CHANNEL="stable" -readonly VERSION="1.0.0" +readonly CHANNEL="dev" +readonly VERSION="1.1.8" if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then readonly FLUTTER_OS="linux" diff --git a/example/flutter_app/.gitignore b/example/.gitignore similarity index 100% rename from example/flutter_app/.gitignore rename to example/.gitignore diff --git a/example/flutter_app/.metadata b/example/.metadata similarity index 100% rename from example/flutter_app/.metadata rename to example/.metadata diff --git a/example/README.md b/example/README.md index 8f1152c63..dc732378a 100644 --- a/example/README.md +++ b/example/README.md @@ -3,9 +3,18 @@ This application shows an example of how to use the embedding library on each platform including build dependencies, resource bundling, and using plugins. +In this example, the platform-specific code lives in `_fde`. For +instance, the macOS project is in macos\_fde. This follows the pattern of +the `android/` and `ios/` directories in a typical Flutter application (with +`_fde` suffixes to avoid confusion or collisions if desktop support is added +to Flutter itself). There's no requirement to use the same names in your +project, or even to put them in the Flutter application directory. + The example application is intended to be a starting point, rather than an authoritative example. For instance, you might use a different build system, -package resources differently, etc. +package resources differently, etc. If you are are adding Flutter to an +existing desktop application, you might instead put the Flutter application code +inside your existing project structure. It also serves as a simple test environment for the plugins that are part of this project, and built-in event handling, so is a collection of unrelated @@ -13,26 +22,33 @@ functionality rather than a usable application. ## Building and Running the Example -Since the example is meant to show how the library would actually be used, it -deliberately uses platform-specific build systems that are separate from the -rest of the project's build system. +There is currently no tool that abstracts the platform-specific builds the +way `flutter build` or `flutter run` does for iOS and Android, so you will need +to follow the platform-specific build instructions for your platform below. -The examples do build the library from source, so you will need to ensure you +The examples build the library from source, so you will need to ensure you have all the dependencies for [building the library on your platform](../library/README.md) before continuing. ### Linux -Run `make` under `linux/`. The example binary and its resources will be -in `out/`, and can be run from there: +Run `make -C example/linux_fde/`. The example binary and its resources will be +in `example/build/linux_fde`, and can be run from there: + +``` +$ ./example/build/linux_fde/debug/flutter_embedder_example +``` + +To build a version with Dart asserts disabled (and thus no DEBUG banner), +run `make BUILD=release` instead, then launch it with: ``` -$ ./out/flutter_embedder_example +$ ./example/build/linux_fde/release/flutter_embedder_example ``` ### macOS -Open the ExampleEmbedder Xcode project under `macos/`, and build and run the +Open the ExampleEmbedder Xcode project under `macos_fde/`, and build and run the example application target. #### Note @@ -50,11 +66,11 @@ to a XIB in your own project: ### Windows -Open the `Example Embedder` Visual Studio solution file under `windows\` and +Open the `Example Embedder` Visual Studio solution file under `windows_fde\` and build the GLFW Example project. The resulting binary will be in `bin\x64\$(Configuration)\GLFW Example\`. It -currently uses relative paths so must be run from the `windows\` directory: +currently uses relative paths so must be run from the `windows_fde\` directory: ``` > ".\bin\x64\$(Configuration)\GLFW Example\GLFW Example.exe" diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 000000000..5e2133eb6 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/example/flutter_app/README.md b/example/flutter_app/README.md deleted file mode 100644 index 725d0d44a..000000000 --- a/example/flutter_app/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Example Flutter app - -A simple example Flutter app to be run by the embedder examples. - -To run it, build and run the embedder example for your platform. diff --git a/example/flutter_app/analysis_options.yaml b/example/flutter_app/analysis_options.yaml deleted file mode 100644 index f04c6cf0f..000000000 --- a/example/flutter_app/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: ../../analysis_options.yaml diff --git a/example/flutter_app/fonts/Roboto/LICENSE.txt b/example/fonts/Roboto/LICENSE.txt similarity index 100% rename from example/flutter_app/fonts/Roboto/LICENSE.txt rename to example/fonts/Roboto/LICENSE.txt diff --git a/example/flutter_app/fonts/Roboto/Roboto-Black.ttf b/example/fonts/Roboto/Roboto-Black.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Black.ttf rename to example/fonts/Roboto/Roboto-Black.ttf diff --git a/example/flutter_app/fonts/Roboto/Roboto-Bold.ttf b/example/fonts/Roboto/Roboto-Bold.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Bold.ttf rename to example/fonts/Roboto/Roboto-Bold.ttf diff --git a/example/flutter_app/fonts/Roboto/Roboto-Light.ttf b/example/fonts/Roboto/Roboto-Light.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Light.ttf rename to example/fonts/Roboto/Roboto-Light.ttf diff --git a/example/flutter_app/fonts/Roboto/Roboto-Medium.ttf b/example/fonts/Roboto/Roboto-Medium.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Medium.ttf rename to example/fonts/Roboto/Roboto-Medium.ttf diff --git a/example/flutter_app/fonts/Roboto/Roboto-Regular.ttf b/example/fonts/Roboto/Roboto-Regular.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Regular.ttf rename to example/fonts/Roboto/Roboto-Regular.ttf diff --git a/example/flutter_app/fonts/Roboto/Roboto-Thin.ttf b/example/fonts/Roboto/Roboto-Thin.ttf similarity index 100% rename from example/flutter_app/fonts/Roboto/Roboto-Thin.ttf rename to example/fonts/Roboto/Roboto-Thin.ttf diff --git a/example/flutter_app/lib/keyboard_test_page.dart b/example/lib/keyboard_test_page.dart similarity index 100% rename from example/flutter_app/lib/keyboard_test_page.dart rename to example/lib/keyboard_test_page.dart diff --git a/example/flutter_app/lib/main.dart b/example/lib/main.dart similarity index 100% rename from example/flutter_app/lib/main.dart rename to example/lib/main.dart diff --git a/example/linux/.gitignore b/example/linux/.gitignore deleted file mode 100644 index 89f9ac04a..000000000 --- a/example/linux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/example/linux/Makefile b/example/linux_fde/Makefile similarity index 82% rename from example/linux/Makefile rename to example/linux_fde/Makefile index e09be8d80..c9073a6e7 100644 --- a/example/linux/Makefile +++ b/example/linux_fde/Makefile @@ -12,12 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Example-specific variables. +# To modify this Makefile for a different application, these are the values +# that are mostly likely to need to be changed. + +# The location of the flutter-desktop-embedding repository. +FDE_ROOT=$(CURDIR)/../.. +# The C++ code for the embedder application. +SOURCES=flutter_embedder_example.cc +# Plugins to include (from the flutter-desktop-embedding plugins/ directory). +PLUGIN_NAMES=color_panel file_chooser menubar + + +# Default build type. For a release build, set BUILD=release. +# Currently this only sets NDEBUG, which is used to control the flags passed +# to the Flutter engine in the example shell, and not the complation settings +# (e.g., optimization level) of the C++ code. +BUILD:=debug + # Dependency locations -PROJECT_ROOT=$(CURDIR)/../.. -FLUTTER_EMBEDDER_LIB_DIR=$(PROJECT_ROOT)/library/linux -FLUTTER_APP_DIR=$(PROJECT_ROOT)/example/flutter_app -PLUGINS_DIR=$(PROJECT_ROOT)/plugins -TOOLS_DIR=$(PROJECT_ROOT)/tools +FLUTTER_APP_DIR=$(CURDIR)/.. +FLUTTER_APP_BUILD_DIR=$(FLUTTER_APP_DIR)/build +FLUTTER_EMBEDDER_LIB_DIR=$(FDE_ROOT)/library/linux +PLUGINS_DIR=$(FDE_ROOT)/plugins +TOOLS_DIR=$(FDE_ROOT)/tools FLUTTER_DIR=$(shell $(TOOLS_DIR)/flutter_location) # Libraries @@ -27,7 +45,6 @@ FLUTTER_EMBEDDER_LIB=$(FLUTTER_EMBEDDER_LIB_DIR)/lib$(FLUTTER_EMBEDDER_LIB_NAME) FLUTTER_ENGINE_LIB_NAME=flutter_engine FLUTTER_ENGINE_LIB=$(FLUTTER_EMBEDDER_LIB_DIR)/lib$(FLUTTER_ENGINE_LIB_NAME).so -PLUGIN_NAMES=color_panel file_chooser menubar PLUGIN_LIB_NAME_PREFIX=flutter_embedder_ PLUGIN_LIBS=$(foreach plugin,$(PLUGIN_NAMES)\ ,$(PLUGINS_DIR)/$(plugin)/linux/lib$(PLUGIN_LIB_NAME_PREFIX)$(plugin).so) @@ -37,7 +54,7 @@ ALL_LIBS=$(FLUTTER_EMBEDDER_LIB) $(FLUTTER_ENGINE_LIB) $(PLUGIN_LIBS) # Headers PLUGIN_DIRS=$(patsubst %,$(PLUGINS_DIR)/%/linux,$(PLUGIN_NAMES)) LIBRARY_DIRS=$(FLUTTER_EMBEDDER_LIB_DIR) $(PLUGIN_DIRS) -INCLUDE_DIRS=$(patsubst %,%/include,$(LIBRARY_DIRS)) $(PROJECT_ROOT)/library/include +INCLUDE_DIRS=$(patsubst %,%/include,$(LIBRARY_DIRS)) $(FDE_ROOT)/library/include # Tools BUILD_ASSETS_BIN=$(TOOLS_DIR)/build_flutter_assets @@ -47,10 +64,10 @@ FLUTTER_BIN=$(FLUTTER_DIR)/bin/flutter ICU_DATA_NAME=icudtl.dat ICU_DATA_SOURCE=$(FLUTTER_DIR)/bin/cache/artifacts/engine/linux-x64/$(ICU_DATA_NAME) FLUTTER_ASSETS_NAME=flutter_assets -FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_DIR)/build/$(FLUTTER_ASSETS_NAME) +FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_BUILD_DIR)/$(FLUTTER_ASSETS_NAME) # Output bundle structure and targets -OUT_DIR=$(CURDIR)/out +OUT_DIR=$(FLUTTER_APP_BUILD_DIR)/linux_fde/$(BUILD) OUT_DATA_DIR=$(OUT_DIR)/data OUT_LIB_DIR=$(OUT_DIR)/lib @@ -60,9 +77,7 @@ ALL_LIBS_OUT=$(foreach lib,$(ALL_LIBS),$(OUT_LIB_DIR)/$(notdir $(lib))) # Overrides for the optional GN build. ifdef USE_GN -GN_OUT_DIR=$(PROJECT_ROOT)/out -# Use GN's out dir even though this isn't a GN build, to group build output. -OUT_DIR=$(GN_OUT_DIR)/example +GN_OUT_DIR=$(FDE_ROOT)/out # The GN build places all libraries at the top level of the output directory. FLUTTER_EMBEDDER_LIB=$(GN_OUT_DIR)/lib$(FLUTTER_EMBEDDER_LIB_NAME).so @@ -83,7 +98,9 @@ endif # Build settings CXX=g++ -std=c++14 -CXXFLAGS=-Wall -Werror $(shell pkg-config --cflags jsoncpp glfw3) +CXXFLAGS.release=-DNDEBUG +CXXFLAGS=-Wall -Werror $(shell pkg-config --cflags jsoncpp glfw3) \ + $(CXXFLAGS.$(BUILD)) CPPFLAGS=$(patsubst %,-I%,$(INCLUDE_DIRS)) ifdef USE_GN CPPFLAGS+=-DUSE_FLATTENED_INCLUDES @@ -95,8 +112,6 @@ LDFLAGS=-L$(OUT_LIB_DIR) \ $(patsubst %,-l$(PLUGIN_LIB_NAME_PREFIX)%,$(PLUGIN_NAMES)) \ -Wl,-rpath=\$$ORIGIN/lib -SOURCES=flutter_embedder_example.cc - # Targets .PHONY: all diff --git a/example/linux/flutter_embedder_example.cc b/example/linux_fde/flutter_embedder_example.cc similarity index 74% rename from example/linux/flutter_embedder_example.cc rename to example/linux_fde/flutter_embedder_example.cc index d231cb498..2dc82fc99 100644 --- a/example/linux/flutter_embedder_example.cc +++ b/example/linux_fde/flutter_embedder_example.cc @@ -24,9 +24,9 @@ #include #ifdef USE_FLATTENED_INCLUDES -#include +#include #else -#include +#include #endif namespace { @@ -53,10 +53,6 @@ std::string GetExecutableDirectory() { } // namespace int main(int argc, char **argv) { - if (!flutter_desktop_embedding::FlutterInit()) { - std::cerr << "Couldn't init GLFW" << std::endl; - } - // Resources are located relative to the executable. std::string base_directory = GetExecutableDirectory(); if (base_directory.empty()) { @@ -71,26 +67,27 @@ int main(int argc, char **argv) { #ifdef NDEBUG arguments.push_back("--disable-dart-asserts"); #endif + + flutter_desktop_embedding::FlutterWindowController flutter_controller( + icu_data_path); + // Start the engine. - auto window = flutter_desktop_embedding::CreateFlutterWindow( - 640, 480, assets_path, icu_data_path, arguments); - if (window == nullptr) { - flutter_desktop_embedding::FlutterTerminate(); + if (!flutter_controller.CreateWindow(640, 480, assets_path, arguments)) { return EXIT_FAILURE; } // Register any native plugins. plugins_menubar::MenubarPlugin::RegisterWithRegistrar( - flutter_desktop_embedding::GetRegistrarForPlugin( - window, "plugins_menubar::MenubarPlugin")); + flutter_controller.GetRegistrarForPlugin( + "plugins_menubar::MenubarPlugin")); plugins_color_panel::ColorPanelPlugin::RegisterWithRegistrar( - flutter_desktop_embedding::GetRegistrarForPlugin( - window, "plugins_color_panel::ColorPanelPlugin")); + flutter_controller.GetRegistrarForPlugin( + "plugins_color_panel::ColorPanelPlugin")); plugins_file_chooser::FileChooserPlugin::RegisterWithRegistrar( - flutter_desktop_embedding::GetRegistrarForPlugin( - window, "plugins_file_chooser::FileChooserPlugin")); + flutter_controller.GetRegistrarForPlugin( + "plugins_file_chooser::FileChooserPlugin")); - flutter_desktop_embedding::FlutterWindowLoop(window); - glfwTerminate(); + // Run until the window is closed. + flutter_controller.RunEventLoop(); return EXIT_SUCCESS; } diff --git a/example/macos/.gitignore b/example/macos_fde/.gitignore similarity index 100% rename from example/macos/.gitignore rename to example/macos_fde/.gitignore diff --git a/example/macos/AppDelegate.swift b/example/macos_fde/AppDelegate.swift similarity index 100% rename from example/macos/AppDelegate.swift rename to example/macos_fde/AppDelegate.swift diff --git a/example/macos/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos_fde/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/macos/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/macos_fde/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/macos/Base.lproj/MainMenu.xib b/example/macos_fde/Base.lproj/MainMenu.xib similarity index 100% rename from example/macos/Base.lproj/MainMenu.xib rename to example/macos_fde/Base.lproj/MainMenu.xib diff --git a/example/macos/Example Embedder-Bridging-Header.h b/example/macos_fde/Example Embedder-Bridging-Header.h similarity index 100% rename from example/macos/Example Embedder-Bridging-Header.h rename to example/macos_fde/Example Embedder-Bridging-Header.h diff --git a/example/macos/ExampleEmbedder.xcodeproj/project.pbxproj b/example/macos_fde/ExampleEmbedder.xcodeproj/project.pbxproj similarity index 99% rename from example/macos/ExampleEmbedder.xcodeproj/project.pbxproj rename to example/macos_fde/ExampleEmbedder.xcodeproj/project.pbxproj index 125827f21..e9eadc9ee 100644 --- a/example/macos/ExampleEmbedder.xcodeproj/project.pbxproj +++ b/example/macos_fde/ExampleEmbedder.xcodeproj/project.pbxproj @@ -131,7 +131,7 @@ 33CC10FE2044A7620003C045 /* FlutterEmbedderMac.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FlutterEmbedderMac.xcodeproj; path = ../../library/macos/FlutterEmbedderMac.xcodeproj; sourceTree = ""; }; 33CC11122044BFA00003C045 /* ExampleWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleWindow.swift; sourceTree = ""; }; 33CC11162044C3600003C045 /* Example Embedder-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example Embedder-Bridging-Header.h"; sourceTree = ""; }; - 33CC112C20461AD40003C045 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = ../../example/flutter_app/build/flutter_assets; sourceTree = ""; }; + 33CC112C20461AD40003C045 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = ../build/flutter_assets; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -355,7 +355,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "DEPOT_ROOT=\"$PROJECT_DIR\"/../..\n\"$DEPOT_ROOT\"/tools/build_flutter_assets \"$DEPOT_ROOT\"/example/flutter_app"; + shellScript = "FDE_ROOT=\"$PROJECT_DIR\"/../..\n\"$FDE_ROOT\"/tools/build_flutter_assets \"$PROJECT_DIR\"/.."; }; /* End PBXShellScriptBuildPhase section */ diff --git a/example/macos/ExampleEmbedder.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/macos_fde/ExampleEmbedder.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/macos/ExampleEmbedder.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to example/macos_fde/ExampleEmbedder.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/example/macos/ExampleEmbedder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos_fde/ExampleEmbedder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/macos/ExampleEmbedder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/macos_fde/ExampleEmbedder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/macos/ExampleEmbedder.xcodeproj/xcshareddata/xcschemes/ExampleEmbedder.xcscheme b/example/macos_fde/ExampleEmbedder.xcodeproj/xcshareddata/xcschemes/ExampleEmbedder.xcscheme similarity index 100% rename from example/macos/ExampleEmbedder.xcodeproj/xcshareddata/xcschemes/ExampleEmbedder.xcscheme rename to example/macos_fde/ExampleEmbedder.xcodeproj/xcshareddata/xcschemes/ExampleEmbedder.xcscheme diff --git a/example/macos/ExampleWindow.swift b/example/macos_fde/ExampleWindow.swift similarity index 100% rename from example/macos/ExampleWindow.swift rename to example/macos_fde/ExampleWindow.swift diff --git a/example/macos/Info.plist b/example/macos_fde/Info.plist similarity index 100% rename from example/macos/Info.plist rename to example/macos_fde/Info.plist diff --git a/example/flutter_app/pubspec.yaml b/example/pubspec.yaml similarity index 70% rename from example/flutter_app/pubspec.yaml rename to example/pubspec.yaml index 50e2c816f..1111bea12 100644 --- a/example/flutter_app/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,5 +1,5 @@ name: example_flutter -description: A new Flutter project. +description: An example project for flutter-desktop-embedding. dependencies: flutter: @@ -8,12 +8,15 @@ dependencies: cupertino_icons: ^0.1.0 # Desktop embedder plugins. + # Note: In an actual project, these paths would include the + # path from your application to the flutter_desktop_embedding + # checkout. color_panel: - path: ../../plugins/color_panel + path: ../plugins/color_panel file_chooser: - path: ../../plugins/file_chooser + path: ../plugins/file_chooser menubar: - path: ../../plugins/menubar + path: ../plugins/menubar dev_dependencies: flutter_test: diff --git a/example/flutter_app/test/widget_test.dart b/example/test/widget_test.dart similarity index 100% rename from example/flutter_app/test/widget_test.dart rename to example/test/widget_test.dart diff --git a/example/windows/.gitignore b/example/windows_fde/.gitignore similarity index 100% rename from example/windows/.gitignore rename to example/windows_fde/.gitignore diff --git a/example/windows/Example Embedder.sln b/example/windows_fde/Example Embedder.sln similarity index 100% rename from example/windows/Example Embedder.sln rename to example/windows_fde/Example Embedder.sln diff --git a/example/windows/GLFW Example.vcxproj b/example/windows_fde/GLFW Example.vcxproj similarity index 100% rename from example/windows/GLFW Example.vcxproj rename to example/windows_fde/GLFW Example.vcxproj diff --git a/example/windows/GLFW Example.vcxproj.filters b/example/windows_fde/GLFW Example.vcxproj.filters similarity index 100% rename from example/windows/GLFW Example.vcxproj.filters rename to example/windows_fde/GLFW Example.vcxproj.filters diff --git a/example/windows/flutter_embedder_example.cpp b/example/windows_fde/flutter_embedder_example.cpp similarity index 67% rename from example/windows/flutter_embedder_example.cpp rename to example/windows_fde/flutter_embedder_example.cpp index 88789f0e6..34b8530eb 100644 --- a/example/windows/flutter_embedder_example.cpp +++ b/example/windows_fde/flutter_embedder_example.cpp @@ -13,30 +13,31 @@ // limitations under the License. #include +#include #include -#include "flutter_desktop_embedding/glfw/embedder.h" +#include "flutter_desktop_embedding/glfw/flutter_window_controller.h" int main(int argc, char **argv) { - if (!flutter_desktop_embedding::FlutterInit()) { - std::cout << "Couldn't init GLFW" << std::endl; - } + // TODO: Make paths relative to the executable so it can be run from anywhere. + std::string assets_path = "..\\build\\flutter_assets"; + std::string icu_data_path = + "..\\..\\library\\windows\\dependencies\\engine\\icudtl.dat"; + // Arguments for the Flutter Engine. std::vector arguments; #ifndef _DEBUG arguments.push_back("--disable-dart-asserts"); #endif + flutter_desktop_embedding::FlutterWindowController flutter_controller( + icu_data_path); + // Start the engine. - // TODO: Make paths relative to the executable so it can be run from anywhere. - auto window = flutter_desktop_embedding::CreateFlutterWindow( - 640, 480, "..\\..\\example\\flutter_app\\build\\flutter_assets", - "..\\..\\library\\windows\\dependencies\\engine\\icudtl.dat", arguments); - if (window == nullptr) { - flutter_desktop_embedding::FlutterTerminate(); + if (!flutter_controller.CreateWindow(640, 480, assets_path, arguments)) { return EXIT_FAILURE; } - flutter_desktop_embedding::FlutterWindowLoop(window); - flutter_desktop_embedding::FlutterTerminate(); + // Run until the window is closed. + flutter_controller.RunEventLoop(); return EXIT_SUCCESS; } diff --git a/example/windows/scripts/build_example_app.bat b/example/windows_fde/scripts/build_example_app.bat similarity index 88% rename from example/windows/scripts/build_example_app.bat rename to example/windows_fde/scripts/build_example_app.bat index 7a72c1930..470fd0b05 100644 --- a/example/windows/scripts/build_example_app.bat +++ b/example/windows_fde/scripts/build_example_app.bat @@ -12,4 +12,4 @@ :: See the License for the specific language governing permissions and :: limitations under the License. @echo off -%~dp0..\..\..\tools\build_flutter_assets %~dp0..\..\..\example\flutter_app +%~dp0..\..\..\tools\build_flutter_assets %~dp0..\.. diff --git a/library/BUILD.gn b/library/BUILD.gn index 12aedfb09..e4d786eec 100644 --- a/library/BUILD.gn +++ b/library/BUILD.gn @@ -21,9 +21,11 @@ published_shared_library("flutter_embedder") { if (is_linux || is_win) { public = [ "include/flutter_desktop_embedding/glfw/embedder.h", + "include/flutter_desktop_embedding/glfw/flutter_window_controller.h", ] sources = [ "common/glfw/embedder.cc", + "common/glfw/flutter_window_controller.cc", "common/glfw/key_event_handler.cc", "common/glfw/key_event_handler.h", "common/glfw/keyboard_hook_handler.h", diff --git a/library/GN.md b/library/GN.md index d5ca4c2d5..11cf785d0 100644 --- a/library/GN.md +++ b/library/GN.md @@ -79,8 +79,6 @@ $ ninja -C out To use the GN build for the depedencies of the example application, when running `make` for the example add `USE_GN=1` to the end of the command. -The resulting binary will be in `out/example/` rather than `example/linux/out/`. - #### Windows Building the example with GN is not currently supported. Follow the [Visual diff --git a/library/README.md b/library/README.md index 79329f705..2cd62a2a9 100644 --- a/library/README.md +++ b/library/README.md @@ -17,6 +17,12 @@ for now there is no equivalent to `flutter create`. There are currently no binary releases of the libraries. While a more Flutter-like model of using an SDK containing pre-compiled binaries is likely to be supported in the future, for now you must build the library from source. +(**Note:** You may be tempted to pre-build a generic binary that can run any +Flutter app. If you do, keep in mind that the primary reason there are no +binary releases is that you *must* use the same version of Flutter to build +`flutter_assets` as you use to build the library. If you later upgrade Flutter, +or if you distribute the binary version to other people building their +applications with different versions of Flutter, it will break.) Once you build the library for your platform, link it into your build using whatever build system you are using, and add the relevant headers (see @@ -49,7 +55,8 @@ $ sudo apt-get install libglfw3-dev libepoxy-dev libjsoncpp-dev libgtk-3-dev \ #### Using the Library Run `make` under `linux/`, then link `libflutter_embedder.so` into your -binary. See [embedder.h](include/flutter_desktop_embedding/glfw/embedder.h) +binary. See +[flutter_window_controller.h](include/flutter_desktop_embedding/glfw/flutter_window_controller.h) for details on calling into the library. You will also need to link `libflutter_engine.so` into your binary. @@ -90,8 +97,8 @@ You must have a copy of Visual Studio installed. Build the GLFW Library project under `windows/` in Visual Studio into a static or dynamic library, then link `flutter_embedder.lib` into your binary and make -sure `embedder.h` is in your include paths. Also ensure that the -`flutter_engine.dll`, and if using a dynamic library +sure `flutter_window_controller.h` is in your include paths. Also ensure that +the `flutter_engine.dll`, and if using a dynamic library `flutter_embedder.dll`, are in valid DLL include paths. The output files are located in `bin\x64\$(Configuration)\GLFW Library\`. diff --git a/library/common/glfw/embedder.cc b/library/common/glfw/embedder.cc index b5d926940..7671e4805 100644 --- a/library/common/glfw/embedder.cc +++ b/library/common/glfw/embedder.cc @@ -20,6 +20,13 @@ #include #include +#ifdef __linux__ +// Epoxy must be included before any graphics-related code. +#include +#endif + +#include + #include #include "library/common/glfw/key_event_handler.h" @@ -48,7 +55,13 @@ static constexpr double kDpPerInch = 160.0; // Struct for storing state within an instance of the GLFW Window. struct FlutterEmbedderState { + // The GLFW window that owns this state object. + GLFWwindow *window; + + // The handle to the Flutter engine instance. FlutterEngine engine; + + // The helper class managing plugin registration and messaging. std::unique_ptr plugin_handler; // Handlers for keyboard events from GLFW. @@ -98,12 +111,15 @@ static void GLFWFramebufferSizeCallback(GLFWwindow *window, int width_px, double dpi = state->window_pixels_per_screen_coordinate * state->monitor_screen_coordinates_per_inch; + // Limit the ratio to 1 to avoid rendering a smaller UI in standard resolution + // monitors. + double pixel_ratio = std::max(dpi / kDpPerInch, 1.0); FlutterWindowMetricsEvent event = {}; event.struct_size = sizeof(event); event.width = width_px; event.height = height_px; - event.pixel_ratio = dpi / kDpPerInch; + event.pixel_ratio = pixel_ratio; FlutterEngineSendWindowMetricsEvent(state->engine, &event); } @@ -239,6 +255,10 @@ static void *GLFWProcResolver(void *user_data, const char *name) { return reinterpret_cast(glfwGetProcAddress(name)); } +static void GLFWErrorCallback(int error_code, const char *description) { + std::cerr << "GLFW error " << error_code << ": " << description << std::endl; +} + // Spins up an instance of the Flutter Engine. // // This function launches the Flutter Engine in a background thread, supplying @@ -276,6 +296,8 @@ static FlutterEngine RunFlutterEngine( auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, window, &engine); if (result != kSuccess || engine == nullptr) { + std::cerr << "Failed to start Flutter engine: error " << result + << std::endl; return nullptr; } return engine; @@ -283,43 +305,50 @@ static FlutterEngine RunFlutterEngine( namespace flutter_desktop_embedding { -// Initialize glfw -bool FlutterInit() { return glfwInit(); } +bool FlutterInit() { + // Before making any GLFW calls, set up a logging error handler. + glfwSetErrorCallback(GLFWErrorCallback); + return glfwInit(); +} -// Tear down glfw void FlutterTerminate() { glfwTerminate(); } -PluginRegistrar *GetRegistrarForPlugin(GLFWwindow *flutter_window, +PluginRegistrar *GetRegistrarForPlugin(FlutterWindowRef flutter_window, const std::string &plugin_name) { - auto *state = GetSavedEmbedderState(flutter_window); // Currently, PluginHandler 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 state->plugin_handler.get(); + return flutter_window->plugin_handler.get(); } -GLFWwindow *CreateFlutterWindow(size_t initial_width, size_t initial_height, - const std::string &assets_path, - const std::string &icu_data_path, - const std::vector &arguments) { +FlutterWindowRef CreateFlutterWindow( + size_t initial_width, size_t initial_height, const std::string &assets_path, + const std::string &icu_data_path, + const std::vector &arguments) { #ifdef __linux__ gtk_init(0, nullptr); #endif + // Create the window. auto window = glfwCreateWindow(initial_width, initial_height, kDefaultWindowTitle, NULL, NULL); if (window == nullptr) { return nullptr; } GLFWClearCanvas(window); + + // Start the engine. auto engine = RunFlutterEngine(window, assets_path, icu_data_path, arguments); if (engine == nullptr) { glfwDestroyWindow(window); return nullptr; } + // Create an embedder state object attached to the window. FlutterEmbedderState *state = new FlutterEmbedderState(); - state->plugin_handler = std::make_unique(engine); + state->window = window; + glfwSetWindowUserPointer(window, state); state->engine = engine; + state->plugin_handler = std::make_unique(engine); // Set up the keyboard handlers. state->keyboard_hook_handlers.push_back( @@ -327,24 +356,26 @@ GLFWwindow *CreateFlutterWindow(size_t initial_width, size_t initial_height, state->keyboard_hook_handlers.push_back( std::make_unique(state->plugin_handler.get())); - glfwSetWindowUserPointer(window, state); - + // 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); - glfwSetFramebufferSizeCallback(window, GLFWFramebufferSizeCallback); GLFWFramebufferSizeCallback(window, width_px, height_px); + // Set up GLFW callbacks for the window. + glfwSetFramebufferSizeCallback(window, GLFWFramebufferSizeCallback); GLFWAssignEventCallbacks(window); - return window; + + return state; } -void FlutterWindowLoop(GLFWwindow *flutter_window) { +void FlutterWindowLoop(FlutterWindowRef flutter_window) { + GLFWwindow *window = flutter_window->window; #ifdef __linux__ // Necessary for GTK thread safety. XInitThreads(); #endif - while (!glfwWindowShouldClose(flutter_window)) { + while (!glfwWindowShouldClose(window)) { #ifdef __linux__ glfwPollEvents(); if (gtk_events_pending()) { @@ -356,10 +387,9 @@ void FlutterWindowLoop(GLFWwindow *flutter_window) { // TODO(awdavies): This will be deprecated soon. __FlutterEngineFlushPendingTasksNow(); } - auto state = GetSavedEmbedderState(flutter_window); - FlutterEngineShutdown(state->engine); - delete state; - glfwDestroyWindow(flutter_window); + FlutterEngineShutdown(flutter_window->engine); + delete flutter_window; + glfwDestroyWindow(window); } } // namespace flutter_desktop_embedding diff --git a/library/common/glfw/flutter_window_controller.cc b/library/common/glfw/flutter_window_controller.cc new file mode 100644 index 000000000..5e686130e --- /dev/null +++ b/library/common/glfw/flutter_window_controller.cc @@ -0,0 +1,68 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "library/include/flutter_desktop_embedding/glfw/flutter_window_controller.h" + +#include + +namespace flutter_desktop_embedding { + +FlutterWindowController::FlutterWindowController(std::string &icu_data_path) + : icu_data_path_(icu_data_path) { + init_succeeded_ = FlutterInit(); +} + +FlutterWindowController::~FlutterWindowController() { + if (init_succeeded_) { + FlutterTerminate(); + } +} + +bool FlutterWindowController::CreateWindow( + size_t width, size_t height, const std::string &assets_path, + const std::vector &arguments) { + if (!init_succeeded_) { + std::cerr << "Could not create window; FlutterInit failed." << std::endl; + return false; + } + + if (window_) { + std::cerr << "Only one Flutter window can exist at a time." << std::endl; + return false; + } + + window_ = CreateFlutterWindow(width, height, assets_path, icu_data_path_, + arguments); + if (!window_) { + std::cerr << "Failed to create window." << std::endl; + return false; + } + return true; +} + +PluginRegistrar *FlutterWindowController::GetRegistrarForPlugin( + const std::string &plugin_name) { + if (!window_) { + return nullptr; + } + return flutter_desktop_embedding::GetRegistrarForPlugin(window_, plugin_name); +} + +void FlutterWindowController::RunEventLoop() { + if (window_) { + FlutterWindowLoop(window_); + } +} + +} // namespace flutter_desktop_embedding diff --git a/library/include/flutter_desktop_embedding/glfw/embedder.h b/library/include/flutter_desktop_embedding/glfw/embedder.h index 4d485aaff..2ce67e94d 100644 --- a/library/include/flutter_desktop_embedding/glfw/embedder.h +++ b/library/include/flutter_desktop_embedding/glfw/embedder.h @@ -19,13 +19,6 @@ #include #include -#ifdef __linux__ -// Epoxy must be included before any graphics-related code. -#include -#endif - -#include - #ifdef USE_FLATTENED_INCLUDES #include "fde_export.h" #include "plugin_registrar.h" @@ -34,19 +27,23 @@ #include "../plugin_registrar.h" #endif +// Opaque reference to a Flutter window. +typedef struct FlutterEmbedderState *FlutterWindowRef; + namespace flutter_desktop_embedding { -// Calls glfwInit() +// Sets up the embedder's graphic context. Must be called before any other +// methods. // -// glfwInit() must be called in the same library as glfwCreateWindow() +// Note: Internally, this library uses GLFW, which does not support multiple +// copies within the same process. Internally this calls glfwInit, which will +// fail if you have called glfwInit elsewhere in the process. FDE_EXPORT bool FlutterInit(); -// Calls glfwTerminate() -// -// glfwTerminate() must be called in the same library as glfwCreateWindow() +// Tears down embedder state. Must be called before the process terminates. FDE_EXPORT void FlutterTerminate(); -// Creates a GLFW Window running a Flutter Application. +// Creates a Window running a Flutter Application. // // FlutterInit() must be called prior to this function. // @@ -58,9 +55,11 @@ FDE_EXPORT void FlutterTerminate(); // https://github.com/flutter/engine/blob/master/shell/common/switches.h for // for details. Not all arguments will apply to embedding mode. // -// Returns a null pointer in the event of an error. The caller owns the pointer -// when it is non-null. -FDE_EXPORT GLFWwindow *CreateFlutterWindow( +// Returns a null pointer in the event of an error. Otherwise, the pointer is +// valid until FlutterWindowLoop has been called and returned. Note that calling +// CreateFlutterWindow without later calling FlutterWindowLoop on that pointer +// is a memory leak. +FDE_EXPORT FlutterWindowRef CreateFlutterWindow( size_t initial_width, size_t initial_height, const std::string &assets_path, const std::string &icu_data_path, const std::vector &arguments); @@ -71,16 +70,13 @@ FDE_EXPORT GLFWwindow *CreateFlutterWindow( // The name must be unique across the application, so the recommended approach // is to use the fully namespace-qualified name of the plugin class. FDE_EXPORT PluginRegistrar *GetRegistrarForPlugin( - GLFWwindow *flutter_window, const std::string &plugin_name); + FlutterWindowRef flutter_window, const std::string &plugin_name); -// Loops on flutter window events until termination. -// -// Must be used instead of glfwWindowShouldClose as it cleans up engine state -// after termination. +// Loops on Flutter window events until the window is closed. // -// After this function the user must eventually call FlutterTerminate() if doing -// cleanup. -FDE_EXPORT void FlutterWindowLoop(GLFWwindow *flutter_window); +// Once this function returns, FlutterWindowRef is no longer valid, and must +// not be used again. +FDE_EXPORT void FlutterWindowLoop(FlutterWindowRef flutter_window); } // namespace flutter_desktop_embedding diff --git a/library/include/flutter_desktop_embedding/glfw/flutter_window_controller.h b/library/include/flutter_desktop_embedding/glfw/flutter_window_controller.h new file mode 100644 index 000000000..a1f0819c7 --- /dev/null +++ b/library/include/flutter_desktop_embedding/glfw/flutter_window_controller.h @@ -0,0 +1,89 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBRARY_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_GLFW_FLUTTER_WINDOW_CONTROLLER_H_ +#define LIBRARY_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_GLFW_FLUTTER_WINDOW_CONTROLLER_H_ + +#include +#include + +#include "embedder.h" + +#ifdef USE_FLATTENED_INCLUDES +#include "fde_export.h" +#include "plugin_registrar.h" +#else +#include "../fde_export.h" +#include "../plugin_registrar.h" +#endif + +namespace flutter_desktop_embedding { + +// A controller for a window displaying Flutter content. +// +// This is the primary wrapper class for the desktop embedding C API. +// If you use this class, you should not call any of the setup or teardown +// methods in embedder.h directly, as this class will do that internally. +// +// Note: This is an early implementation (using GLFW internally) which +// requires control of the application's event loop, and is thus useful +// primarily for building a simple one-window shell hosting a Flutter +// application. The final implementation and API will be very different. +class FDE_EXPORT FlutterWindowController { + public: + // There must be only one instance of this class in an application at any + // given time, as Flutter does not support multiple engines in one process, + // or multiple views in one engine. + explicit FlutterWindowController(std::string &icu_data_path); + + ~FlutterWindowController(); + + // Creates and displays a window for displaying Flutter content. + // + // The |assets_path| is the path to the flutter_assets folder for the Flutter + // application to be run. |icu_data_path| is the path to the icudtl.dat file + // for the version of Flutter you are using. + // + // The |arguments| are passed to the Flutter engine. See: + // https://github.com/flutter/engine/blob/master/shell/common/switches.h for + // for details. Not all arguments will apply to embedding mode. + // + // Only one Flutter window can exist at a time; see constructor comment. + bool CreateWindow(size_t width, size_t height, const std::string &assets_path, + const std::vector &arguments); + + // Returns the PluginRegistrar to register a plugin with the given name. + // + // The name must be unique across the application, so the recommended approach + // is to use the fully namespace-qualified name of the plugin class. + PluginRegistrar *GetRegistrarForPlugin(const std::string &plugin_name); + + // Loops on Flutter window events until the window closes. + void RunEventLoop(); + + private: + // The path to the ICU data file. Set at creation time since it is the same + // for any window created. + std::string icu_data_path_; + + // Whether or not FlutterInit succeeded at creation time. + bool init_succeeded_ = false; + + // The curent Flutter window, if any. + FlutterWindowRef window_ = nullptr; +}; + +} // namespace flutter_desktop_embedding + +#endif // LIBRARY_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_GLFW_FLUTTER_WINDOW_CONTROLLER_H_ diff --git a/library/macos/FLEViewController.m b/library/macos/FLEViewController.mm similarity index 91% rename from library/macos/FLEViewController.m rename to library/macos/FLEViewController.mm index 6b8489f03..254e82294 100644 --- a/library/macos/FLEViewController.m +++ b/library/macos/FLEViewController.mm @@ -33,6 +33,12 @@ static const int kDefaultWindowFramebuffer = 0; +// Android KeyEvent constants from https://developer.android.com/reference/android/view/KeyEvent +static const int kAndroidMetaStateShift = 1 << 0; +static const int kAndroidMetaStateAlt = 1 << 1; +static const int kAndroidMetaStateCtrl = 1 << 12; +static const int kAndroidMetaStateMeta = 1 << 16; + #pragma mark - Private interface declaration. /** @@ -291,10 +297,15 @@ - (BOOL)launchEngineInternalWithAssetsPath:(NSURL *)assets flutterArguments.command_line_argv = argv; flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; - BOOL result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &flutterArguments, - (__bridge void *)(self), &_engine) == kSuccess; + // TODO: Replace auto with FlutterEngineResult after next required Flutter update. + auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &flutterArguments, + (__bridge void *)(self), &_engine); free(argv); - return result; + if (result != kSuccess) { + NSLog(@"Failed to start Flutter engine: error %d", result); + return NO; + } + return YES; } + (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless { @@ -340,7 +351,8 @@ - (void)handlePlatformMessage:(const FlutterPlatformMessage *)message { FLEBinaryReply binaryResponseHandler = ^(NSData *response) { if (responseHandle) { - FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle, response.bytes, + FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle, + static_cast(response.bytes), response.length); responseHandle = NULL; } else { @@ -366,7 +378,7 @@ - (void)dispatchMouseEvent:(NSEvent *)event phase:(FlutterPointerPhase)phase { .phase = phase, .x = locationInBackingCoordinates.x, .y = -locationInBackingCoordinates.y, // convertPointToBacking makes this negative. - .timestamp = event.timestamp * NSEC_PER_MSEC, + .timestamp = static_cast(event.timestamp * NSEC_PER_MSEC), }; FlutterEngineSendPointerEvent(_engine, &flutterEvent, 1); } @@ -376,6 +388,11 @@ - (void)dispatchKeyEvent:(NSEvent *)event ofType:(NSString *)type { @"keymap" : @"android", @"type" : type, @"keyCode" : @(event.keyCode), + @"metaState" : + @(((event.modifierFlags & NSEventModifierFlagShift) ? kAndroidMetaStateShift : 0) | + ((event.modifierFlags & NSEventModifierFlagOption) ? kAndroidMetaStateAlt : 0) | + ((event.modifierFlags & NSEventModifierFlagControl) ? kAndroidMetaStateCtrl : 0) | + ((event.modifierFlags & NSEventModifierFlagCommand) ? kAndroidMetaStateMeta : 0)) }]; } @@ -388,8 +405,8 @@ - (void)viewDidReshape:(NSOpenGLView *)view { CGRect scaledBounds = [view convertRectToBacking:view.bounds]; const FlutterWindowMetricsEvent event = { .struct_size = sizeof(event), - .width = scaledBounds.size.width, - .height = scaledBounds.size.height, + .width = static_cast(scaledBounds.size.width), + .height = static_cast(scaledBounds.size.height), .pixel_ratio = scaledBounds.size.width / view.bounds.size.width, }; FlutterEngineSendWindowMetricsEvent(_engine, &event); @@ -401,11 +418,12 @@ - (void)sendOnChannel:(nonnull NSString *)channel message:(nullable NSData *)mes FlutterPlatformMessage platformMessage = { .struct_size = sizeof(FlutterPlatformMessage), .channel = [channel UTF8String], - .message = message.bytes, + .message = static_cast(message.bytes), .message_size = message.length, }; - FlutterResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage); + // TODO: Replace auto with FlutterEngineResult after next required Flutter update. + auto result = FlutterEngineSendPlatformMessage(_engine, &platformMessage); if (result != kSuccess) { NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel, result); } diff --git a/library/macos/FlutterEmbedderMac.xcodeproj/project.pbxproj b/library/macos/FlutterEmbedderMac.xcodeproj/project.pbxproj index 2d953f6b7..bbc1a68f4 100644 --- a/library/macos/FlutterEmbedderMac.xcodeproj/project.pbxproj +++ b/library/macos/FlutterEmbedderMac.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ 1E24923C1FCF50BE00DD3BBB /* FLEReshapeListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E2492321FCF50BE00DD3BBB /* FLEReshapeListener.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1E24923E1FCF50BE00DD3BBB /* FLETextInputPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E2492341FCF50BE00DD3BBB /* FLETextInputPlugin.m */; }; 1E24923F1FCF50BE00DD3BBB /* FLEViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E2492351FCF50BE00DD3BBB /* FLEViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1E2492401FCF50BE00DD3BBB /* FLEViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E2492361FCF50BE00DD3BBB /* FLEViewController.m */; }; + 1E2492401FCF50BE00DD3BBB /* FLEViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1E2492361FCF50BE00DD3BBB /* FLEViewController.mm */; }; 1E2492411FCF50BE00DD3BBB /* FlutterEmbedderMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E2492371FCF50BE00DD3BBB /* FlutterEmbedderMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1EEF8E071FD1F0C300DD563C /* FLEView.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EEF8E051FD1F0C300DD563C /* FLEView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1EEF8E081FD1F0C300DD563C /* FLEView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EEF8E061FD1F0C300DD563C /* FLEView.m */; }; @@ -87,7 +87,7 @@ 1E2492331FCF50BE00DD3BBB /* FLETextInputPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLETextInputPlugin.h; sourceTree = ""; }; 1E2492341FCF50BE00DD3BBB /* FLETextInputPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLETextInputPlugin.m; sourceTree = ""; }; 1E2492351FCF50BE00DD3BBB /* FLEViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEViewController.h; sourceTree = ""; }; - 1E2492361FCF50BE00DD3BBB /* FLEViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEViewController.m; sourceTree = ""; }; + 1E2492361FCF50BE00DD3BBB /* FLEViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEViewController.mm; sourceTree = ""; }; 1E2492371FCF50BE00DD3BBB /* FlutterEmbedderMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlutterEmbedderMac.h; sourceTree = ""; }; 1E2492381FCF50BE00DD3BBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 1EEF8E051FD1F0C300DD563C /* FLEView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEView.h; sourceTree = ""; }; @@ -148,7 +148,7 @@ 1E2492341FCF50BE00DD3BBB /* FLETextInputPlugin.m */, 1EEF8E061FD1F0C300DD563C /* FLEView.m */, 6442F82C20EA6C5F00A393AE /* FLEViewController+Internal.h */, - 1E2492361FCF50BE00DD3BBB /* FLEViewController.m */, + 1E2492361FCF50BE00DD3BBB /* FLEViewController.mm */, 1E2492381FCF50BE00DD3BBB /* Info.plist */, 33B1650E201A5F7400732DC9 /* Dependencies */, 1E2492251FCF504200DD3BBB /* Products */, @@ -316,7 +316,7 @@ buildActionMask = 2147483647; files = ( 33D7B59A20A4F54400296EFC /* FLEMethodChannel.m in Sources */, - 1E2492401FCF50BE00DD3BBB /* FLEViewController.m in Sources */, + 1E2492401FCF50BE00DD3BBB /* FLEViewController.mm in Sources */, 3389A68D215949CB00A27898 /* FLEMethodError.m in Sources */, 33C0FA2721B84AA4008F8959 /* FLEMethodCall.m in Sources */, 33C0FA2021B84810008F8959 /* FLEJSONMessageCodec.m in Sources */, diff --git a/library/windows/GLFW Library.vcxproj b/library/windows/GLFW Library.vcxproj index d7dfa8a2f..c14747a6e 100644 --- a/library/windows/GLFW Library.vcxproj +++ b/library/windows/GLFW Library.vcxproj @@ -148,6 +148,7 @@ + diff --git a/library/windows/GLFW Library.vcxproj.filters b/library/windows/GLFW Library.vcxproj.filters index bf62d4890..ad4017e40 100644 --- a/library/windows/GLFW Library.vcxproj.filters +++ b/library/windows/GLFW Library.vcxproj.filters @@ -1,48 +1,51 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/plugins/README.md b/plugins/README.md index 9235d136c..eda11bb0a 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -57,7 +57,7 @@ plugin you want to use. For instance: See the example application under each platform's directory in the `example` directory to see an example of including optional plugins on that platform. -The Flutter application under `example/flutter_app` shows examples of using +The Flutter application under `example/` shows examples of using optional plugins on the Dart side. ## Writing your own plugins diff --git a/plugins/color_panel/macos/FLEColorPanelPlugin.mm b/plugins/color_panel/macos/FLEColorPanelPlugin.mm index 7fd97e3f4..68df89039 100644 --- a/plugins/color_panel/macos/FLEColorPanelPlugin.mm +++ b/plugins/color_panel/macos/FLEColorPanelPlugin.mm @@ -24,15 +24,15 @@ @implementation FLEColorPanelPlugin { } + (void)registerWithRegistrar:(id)registrar { - FLEMethodChannel* channel = [FLEMethodChannel - methodChannelWithName:@(plugins_color_panel::kChannelName) - binaryMessenger:registrar.messenger - codec:[FLEJSONMethodCodec sharedInstance]]; - FLEColorPanelPlugin* instance = [[FLEColorPanelPlugin alloc] initWithChannel:channel]; + FLEMethodChannel *channel = + [FLEMethodChannel methodChannelWithName:@(plugins_color_panel::kChannelName) + binaryMessenger:registrar.messenger + codec:[FLEJSONMethodCodec sharedInstance]]; + FLEColorPanelPlugin *instance = [[FLEColorPanelPlugin alloc] initWithChannel:channel]; [registrar addMethodCallDelegate:instance channel:channel]; } -- (instancetype)initWithChannel:(FLEMethodChannel*)channel { +- (instancetype)initWithChannel:(FLEMethodChannel *)channel { self = [super init]; if (self) { _channel = channel; @@ -45,26 +45,29 @@ - (instancetype)initWithChannel:(FLEMethodChannel*)channel { * panel channel. */ - (void)handleMethodCall:(FLEMethodCall *)call result:(FLEMethodResult)result { - BOOL handled = YES; + id methodResult = nil; if ([call.methodName isEqualToString:@(plugins_color_panel::kShowColorPanelMethod)]) { if ([call.arguments isKindOfClass:[NSDictionary class]]) { BOOL showAlpha = [[call.arguments valueForKey:@(plugins_color_panel::kColorPanelShowAlpha)] boolValue]; [self showColorPanelWithAlpha:showAlpha]; } else { - NSLog(@"Malformed call for %@. Expected an NSDictionary but got %@", - @(plugins_color_panel::kShowColorPanelMethod), - NSStringFromClass([call.arguments class])); - handled = NO; + NSString *errorString = + [NSString stringWithFormat:@"Malformed call for %@. Expected an NSDictionary but got %@", + @(plugins_color_panel::kShowColorPanelMethod), + NSStringFromClass([call.arguments class])]; + methodResult = [[FLEMethodError alloc] initWithCode:@"Bad arguments" + message:errorString + details:nil]; } } else if ([call.methodName isEqualToString:@(plugins_color_panel::kHideColorPanelMethod)]) { [self hideColorPanel]; } else { - handled = NO; + methodResult = FLEMethodNotImplemented; } - // Send an immediate empty success message for handled messages, since the actual color data - // will be provided in follow-up messages. - result(handled ? nil : FLEMethodNotImplemented); + // If no errors are generated, send an immediate empty success message for handled messages, since + // the actual color data will be provided in follow-up messages. + result(methodResult); } /** @@ -114,7 +117,7 @@ - (void)selectedColorDidChange { NSColor *color = [NSColorPanel sharedColorPanel].color; NSDictionary *colorDictionary = [self dictionaryWithColor:color]; [_channel invokeMethod:@(plugins_color_panel::kColorSelectedCallbackMethod) - arguments:colorDictionary]; + arguments:colorDictionary]; } /** @@ -138,8 +141,7 @@ - (NSDictionary *)dictionaryWithColor:(NSColor *)color { - (void)windowWillClose:(NSNotification *)notification { [self removeColorPanelConnections]; - [_channel invokeMethod:@(plugins_color_panel::kClosedCallbackMethod) - arguments:nil]; + [_channel invokeMethod:@(plugins_color_panel::kClosedCallbackMethod) arguments:nil]; } @end diff --git a/tools/dart_tools/bin/update_flutter_engine.dart b/tools/dart_tools/bin/update_flutter_engine.dart index 3dd08f056..bdb2189da 100644 --- a/tools/dart_tools/bin/update_flutter_engine.dart +++ b/tools/dart_tools/bin/update_flutter_engine.dart @@ -23,6 +23,7 @@ import 'package:path/path.dart' as path; import 'package:archive/archive.dart'; import '../lib/flutter_utils.dart'; +import '../lib/git_utils.dart'; /// The filename stored next to a downloaded engine library to indicate its /// version. @@ -84,6 +85,9 @@ Future main(List arguments) async { 'is present.\n' 'Defaults to a "flutter" directory next to this repository.', defaultsTo: getDefaultFlutterRoot()) + ..addFlag('skip_min_version_check', + help: 'If set, skips the initial check that the Flutter tree whose ' + 'engine version is being fetched is new enough for the framework.') ..addOption( 'hash', // Note: engine_override takes precedence over this flag so that @@ -112,6 +116,22 @@ Future main(List arguments) async { final String flutterRoot = parsedArguments['flutter_root']; final outputRoot = path.canonicalize(path.absolute(parsedArguments.rest[0])); + // TODO: Consider making a setup script that should be run after any update, + // which checks/fetches dependencies, and moving this check there. For now, + // do it here since it's a hook that's run on every build. + if (!parsedArguments['skip_min_version_check']) { + bool containsRequiredCommit = await gitHeadContainsCommit( + flutterRoot, lastKnownRequiredFlutterCommit); + if (!containsRequiredCommit) { + print('Flutter engine update aborted: Your Flutter tree is too ' + 'old for use with this project. Please update to a newer version of ' + 'Flutter, then try again.\n\n' + 'Note that this may require switching to Flutter master. See:\n' + 'https://github.com/flutter/flutter/wiki/Flutter-build-release-channels'); + exit(1); + } + } + final engineOverrideBuildType = await getEngineOverrideBuildType(); if (engineOverrideBuildType == null) { final String targetHash = diff --git a/tools/dart_tools/lib/flutter_utils.dart b/tools/dart_tools/lib/flutter_utils.dart index 3941980b5..509db89b8 100644 --- a/tools/dart_tools/lib/flutter_utils.dart +++ b/tools/dart_tools/lib/flutter_utils.dart @@ -19,6 +19,14 @@ import 'dart:io'; import 'package:path/path.dart' as path; +/// The last Flutter hash that's known to be required; a branch that doesn't +/// contain this commit will either fail to build, or fail to run. +/// +/// This should be updated whenever a new dependency is introduced (e.g., a +/// required embedder API addition or implementation fix). +const String lastKnownRequiredFlutterCommit = + '390ded9340e529b8475fefd1afdbe59c5b8d4081'; + /// Returns the path to the root of this repository. /// /// Relies on the known location of dart_tools/bin within the repo, and the fact diff --git a/tools/dart_tools/lib/git_utils.dart b/tools/dart_tools/lib/git_utils.dart new file mode 100644 index 000000000..553632a38 --- /dev/null +++ b/tools/dart_tools/lib/git_utils.dart @@ -0,0 +1,33 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the 'License'); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Wrappers for git commands used by the tools. + +import 'run_command.dart'; + +/// Returns true if the current +Future gitHeadContainsCommit( + String repositoryRoot, String commitHash) async { + final exitCode = await runCommand( + 'git', + [ + 'merge-base', + '--is-ancestor', + commitHash, + 'HEAD', + ], + workingDirectory: repositoryRoot, + allowFail: true); + return exitCode == 0; +}