├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── README.md ├── assets └── image.png └── src ├── flutter_application.cc ├── flutter_application.h ├── macros.h ├── main.cc ├── utils.cc ├── utils.h ├── wayland_display.cc └── wayland_display.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | Standard: Cpp11 4 | SortIncludes: true 5 | ... 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | *.lock 9 | profile 10 | 11 | DerivedData/ 12 | build/ 13 | 14 | *.pbxuser 15 | *.mode1v3 16 | *.mode2v3 17 | *.perspectivev3 18 | 19 | !default.pbxuser 20 | !default.mode1v3 21 | !default.mode2v3 22 | !default.perspectivev3 23 | 24 | xcuserdata 25 | *.xcscmblueprint 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | *.sublime-workspace 35 | cmake-*/ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Flutter Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | cmake_minimum_required(VERSION 3.5.2) 6 | 7 | project(flutter_wayland) 8 | 9 | set(FLUTTER_ENGINE_SHA b9523318caa1a99ffde8adaf331212eb879cabc9) 10 | 11 | set(FLUTTER_EMBEDDER_ARTIFACTS_ZIP ${CMAKE_BINARY_DIR}/flutter_embedder_${FLUTTER_ENGINE_SHA}.zip) 12 | set(FLUTTER_ARTIFACTS_ZIP ${CMAKE_BINARY_DIR}/flutter_artifact_${FLUTTER_ENGINE_SHA}.zip) 13 | set(FLUTTER_BUCKET_BASE "https://storage.googleapis.com/flutter_infra/flutter") 14 | 15 | # Download and setup the Flutter Engine. 16 | if(NOT EXISTS ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP}) 17 | file(DOWNLOAD 18 | ${FLUTTER_BUCKET_BASE}/${FLUTTER_ENGINE_SHA}/linux-x64/linux-x64-embedder 19 | ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} 20 | SHOW_PROGRESS 21 | ) 22 | execute_process( 23 | COMMAND ${CMAKE_COMMAND} -E tar xzf ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} 24 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 25 | ) 26 | endif() 27 | 28 | if(NOT EXISTS ${FLUTTER_ARTIFACTS_ZIP}) 29 | file(DOWNLOAD 30 | ${FLUTTER_BUCKET_BASE}/${FLUTTER_ENGINE_SHA}/linux-x64/artifacts.zip 31 | ${FLUTTER_ARTIFACTS_ZIP} 32 | SHOW_PROGRESS 33 | ) 34 | execute_process( 35 | COMMAND ${CMAKE_COMMAND} -E tar xzf ${FLUTTER_ARTIFACTS_ZIP} 36 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 37 | ) 38 | endif() 39 | 40 | find_package(PkgConfig) 41 | pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) 42 | pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) 43 | pkg_check_modules(EGL REQUIRED egl) 44 | 45 | # Executable 46 | file(GLOB_RECURSE FLUTTER_WAYLAND_SRC 47 | "src/*.cc" 48 | "src/*.h" 49 | ) 50 | 51 | link_directories(${CMAKE_BINARY_DIR}) 52 | 53 | add_executable(flutter_wayland ${FLUTTER_WAYLAND_SRC}) 54 | 55 | target_link_libraries(flutter_wayland 56 | ${WAYLAND_CLIENT_LIBRARIES} 57 | ${WAYLAND_EGL_LIBRARIES} 58 | ${EGL_LIBRARIES} 59 | flutter_engine 60 | ) 61 | 62 | target_include_directories(flutter_wayland 63 | PRIVATE 64 | ${WAYLAND_CLIENT_INCLUDE_DIRS} 65 | ${WAYLAND_EGL_INCLUDE_DIRS} 66 | ${EGL_INCLUDE_DIRS} 67 | ${CMAKE_BINARY_DIR} 68 | ) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flutter Wayland 2 | ============ 3 | 4 | A Flutter Embedder that talks to Wayland. 5 | 6 | ![Running in Weston](assets/image.png) 7 | 8 | Build Setup Instructions 9 | ------------------------ 10 | 11 | * Install the following packages (on Debian Stretch): `weston`, `libwayland-dev`, `cmake` and `ninja`. 12 | * From the source root `mkdir build` and move into the directory. 13 | * `cmake -G Ninja ../`. This should check you development environment for required packages, download the Flutter engine artifacts and unpack the same in the build directory. 14 | * `ninja` to build the embedder. 15 | * Run the embedder using `./flutter_wayland`. Make sure `weston` is running. See the instructions on running Flutter applications below. 16 | 17 | Running Flutter Applications 18 | ---------------------------- 19 | 20 | ``` 21 | Flutter Wayland Embedder 22 | ======================== 23 | 24 | Usage: `flutter_wayland ` 25 | 26 | This utility runs an instance of a Flutter application and renders using 27 | Wayland core protocols. 28 | 29 | The Flutter tools can be obtained at https://flutter.io/ 30 | 31 | asset_bundle_path: The Flutter application code needs to be snapshotted using 32 | the Flutter tools and the assets packaged in the appropriate 33 | location. This can be done for any Flutter application by 34 | running `flutter build bundle` while in the directory of a 35 | valid Flutter project. This should package all the code and 36 | assets in the "build/flutter_assets" directory. Specify this 37 | directory as the first argument to this utility. 38 | 39 | flutter_flags: Typically empty. These extra flags are passed directly to the 40 | Flutter engine. To see all supported flags, run 41 | `flutter_tester --help` using the test binary included in the 42 | Flutter tools. 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/flutter_wayland/e48b11082ba73b424be0549995009e62020becb6/assets/image.png -------------------------------------------------------------------------------- /src/flutter_application.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "flutter_application.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "utils.h" 15 | 16 | namespace flutter { 17 | 18 | static_assert(FLUTTER_ENGINE_VERSION == 1, ""); 19 | 20 | static const char* kICUDataFileName = "icudtl.dat"; 21 | 22 | static std::string GetICUDataPath() { 23 | auto exe_dir = GetExecutableDirectory(); 24 | if (exe_dir == "") { 25 | return ""; 26 | } 27 | std::stringstream stream; 28 | stream << exe_dir << kICUDataFileName; 29 | 30 | auto icu_path = stream.str(); 31 | 32 | if (!FileExistsAtPath(icu_path.c_str())) { 33 | FLWAY_ERROR << "Could not find " << icu_path << std::endl; 34 | return ""; 35 | } 36 | 37 | return icu_path; 38 | } 39 | 40 | FlutterApplication::FlutterApplication( 41 | std::string bundle_path, 42 | const std::vector& command_line_args, 43 | RenderDelegate& render_delegate) 44 | : render_delegate_(render_delegate) { 45 | if (!FlutterAssetBundleIsValid(bundle_path)) { 46 | FLWAY_ERROR << "Flutter asset bundle was not valid." << std::endl; 47 | return; 48 | } 49 | 50 | FlutterRendererConfig config = {}; 51 | config.type = kOpenGL; 52 | config.open_gl.struct_size = sizeof(config.open_gl); 53 | config.open_gl.make_current = [](void* userdata) -> bool { 54 | return reinterpret_cast(userdata) 55 | ->render_delegate_.OnApplicationContextMakeCurrent(); 56 | }; 57 | config.open_gl.clear_current = [](void* userdata) -> bool { 58 | return reinterpret_cast(userdata) 59 | ->render_delegate_.OnApplicationContextClearCurrent(); 60 | }; 61 | config.open_gl.present = [](void* userdata) -> bool { 62 | return reinterpret_cast(userdata) 63 | ->render_delegate_.OnApplicationPresent(); 64 | }; 65 | config.open_gl.fbo_callback = [](void* userdata) -> uint32_t { 66 | return reinterpret_cast(userdata) 67 | ->render_delegate_.OnApplicationGetOnscreenFBO(); 68 | }; 69 | config.open_gl.gl_proc_resolver = [](void* userdata, 70 | const char* name) -> void* { 71 | auto address = eglGetProcAddress(name); 72 | if (address != nullptr) { 73 | return reinterpret_cast(address); 74 | } 75 | FLWAY_ERROR << "Tried unsuccessfully to resolve: " << name << std::endl; 76 | return nullptr; 77 | }; 78 | 79 | auto icu_data_path = GetICUDataPath(); 80 | 81 | if (icu_data_path == "") { 82 | FLWAY_ERROR << "Could not find ICU data. It should be placed next to the " 83 | "executable but it wasn't there." 84 | << std::endl; 85 | return; 86 | } 87 | 88 | std::vector command_line_args_c; 89 | 90 | for (const auto& arg : command_line_args) { 91 | command_line_args_c.push_back(arg.c_str()); 92 | } 93 | 94 | FlutterProjectArgs args = { 95 | .struct_size = sizeof(FlutterProjectArgs), 96 | .assets_path = bundle_path.c_str(), 97 | .main_path = "", 98 | .packages_path = "", 99 | .icu_data_path = icu_data_path.c_str(), 100 | .command_line_argc = static_cast(command_line_args_c.size()), 101 | .command_line_argv = command_line_args_c.data(), 102 | }; 103 | 104 | FlutterEngine engine = nullptr; 105 | auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, 106 | this /* userdata */, &engine_); 107 | 108 | if (result != kSuccess) { 109 | FLWAY_ERROR << "Could not run the Flutter engine" << std::endl; 110 | return; 111 | } 112 | 113 | valid_ = true; 114 | } 115 | 116 | FlutterApplication::~FlutterApplication() { 117 | if (engine_ == nullptr) { 118 | return; 119 | } 120 | 121 | auto result = FlutterEngineShutdown(engine_); 122 | 123 | if (result != kSuccess) { 124 | FLWAY_ERROR << "Could not shutdown the Flutter engine." << std::endl; 125 | } 126 | } 127 | 128 | bool FlutterApplication::IsValid() const { 129 | return valid_; 130 | } 131 | 132 | bool FlutterApplication::SetWindowSize(size_t width, size_t height) { 133 | FlutterWindowMetricsEvent event = {}; 134 | event.struct_size = sizeof(event); 135 | event.width = width; 136 | event.height = height; 137 | event.pixel_ratio = 1.0; 138 | return FlutterEngineSendWindowMetricsEvent(engine_, &event) == kSuccess; 139 | } 140 | 141 | void FlutterApplication::ProcessEvents() { 142 | __FlutterEngineFlushPendingTasksNow(); 143 | } 144 | 145 | bool FlutterApplication::SendPointerEvent(int button, int x, int y) { 146 | if (!valid_) { 147 | FLWAY_ERROR << "Pointer events on an invalid application." << std::endl; 148 | return false; 149 | } 150 | 151 | // Simple hover event. Nothing to do. 152 | if (last_button_ == 0 && button == 0) { 153 | return true; 154 | } 155 | 156 | FlutterPointerPhase phase = kCancel; 157 | 158 | if (last_button_ == 0 && button != 0) { 159 | phase = kDown; 160 | } else if (last_button_ == button) { 161 | phase = kMove; 162 | } else { 163 | phase = kUp; 164 | } 165 | 166 | last_button_ = button; 167 | return SendFlutterPointerEvent(phase, x, y); 168 | } 169 | 170 | bool FlutterApplication::SendFlutterPointerEvent(FlutterPointerPhase phase, 171 | double x, 172 | double y) { 173 | FlutterPointerEvent event = {}; 174 | event.struct_size = sizeof(event); 175 | event.phase = phase; 176 | event.x = x; 177 | event.y = y; 178 | event.timestamp = 179 | std::chrono::duration_cast( 180 | std::chrono::high_resolution_clock::now().time_since_epoch()) 181 | .count(); 182 | return FlutterEngineSendPointerEvent(engine_, &event, 1) == kSuccess; 183 | } 184 | 185 | } // namespace flutter 186 | -------------------------------------------------------------------------------- /src/flutter_application.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "macros.h" 13 | 14 | namespace flutter { 15 | 16 | class FlutterApplication { 17 | public: 18 | class RenderDelegate { 19 | public: 20 | virtual bool OnApplicationContextMakeCurrent() = 0; 21 | 22 | virtual bool OnApplicationContextClearCurrent() = 0; 23 | 24 | virtual bool OnApplicationPresent() = 0; 25 | 26 | virtual uint32_t OnApplicationGetOnscreenFBO() = 0; 27 | }; 28 | 29 | FlutterApplication(std::string bundle_path, 30 | const std::vector& args, 31 | RenderDelegate& render_delegate); 32 | 33 | ~FlutterApplication(); 34 | 35 | bool IsValid() const; 36 | 37 | void ProcessEvents(); 38 | 39 | bool SetWindowSize(size_t width, size_t height); 40 | 41 | bool SendPointerEvent(int button, int x, int y); 42 | 43 | private: 44 | bool valid_; 45 | RenderDelegate& render_delegate_; 46 | FlutterEngine engine_ = nullptr; 47 | int last_button_ = 0; 48 | 49 | bool SendFlutterPointerEvent(FlutterPointerPhase phase, double x, double y); 50 | 51 | FLWAY_DISALLOW_COPY_AND_ASSIGN(FlutterApplication); 52 | }; 53 | 54 | } // namespace flutter 55 | -------------------------------------------------------------------------------- /src/macros.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #define FLWAY_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete; 10 | 11 | #define FLWAY_DISALLOW_ASSIGN(TypeName) \ 12 | void operator=(const TypeName&) = delete; 13 | 14 | #define FLWAY_DISALLOW_COPY_AND_ASSIGN(TypeName) \ 15 | FLWAY_DISALLOW_COPY(TypeName) \ 16 | FLWAY_DISALLOW_ASSIGN(TypeName) 17 | 18 | #define __FLWAY_LINE_PREFIX << __FILE__ << ":" << __LINE__ << ": " 19 | #define FLWAY_LOG std::cout << "LOG: " __FLWAY_LINE_PREFIX 20 | #define FLWAY_ERROR std::cerr << "ERROR: " __FLWAY_LINE_PREFIX 21 | #define FLWAY_WIP \ 22 | std::cerr << "Work In Progress. Aborting." << std::endl; \ 23 | abort(); 24 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "flutter_application.h" 11 | #include "utils.h" 12 | #include "wayland_display.h" 13 | 14 | namespace flutter { 15 | 16 | static void PrintUsage() { 17 | std::cerr << "Flutter Wayland Embedder" << std::endl << std::endl; 18 | std::cerr << "========================" << std::endl; 19 | std::cerr << "Usage: `" << GetExecutableName() 20 | << " `" << std::endl 21 | << std::endl; 22 | std::cerr << R"~( 23 | This utility runs an instance of a Flutter application and renders using 24 | Wayland core protocols. 25 | 26 | The Flutter tools can be obtained at https://flutter.io/ 27 | 28 | asset_bundle_path: The Flutter application code needs to be snapshotted using 29 | the Flutter tools and the assets packaged in the appropriate 30 | location. This can be done for any Flutter application by 31 | running `flutter build bundle` while in the directory of a 32 | valid Flutter project. This should package all the code and 33 | assets in the "build/flutter_assets" directory. Specify this 34 | directory as the first argument to this utility. 35 | 36 | flutter_flags: Typically empty. These extra flags are passed directly to the 37 | Flutter engine. To see all supported flags, run 38 | `flutter_tester --help` using the test binary included in the 39 | Flutter tools. 40 | )~" << std::endl; 41 | } 42 | 43 | static bool Main(std::vector args) { 44 | if (args.size() == 0) { 45 | std::cerr << " " << std::endl; 46 | PrintUsage(); 47 | return false; 48 | } 49 | 50 | const auto asset_bundle_path = args[0]; 51 | 52 | if (!FlutterAssetBundleIsValid(asset_bundle_path)) { 53 | std::cerr << " " << std::endl; 54 | PrintUsage(); 55 | return false; 56 | } 57 | 58 | const size_t kWidth = 800; 59 | const size_t kHeight = 600; 60 | 61 | for (const auto& arg : args) { 62 | FLWAY_ERROR << "Arg: " << arg << std::endl; 63 | } 64 | 65 | WaylandDisplay display(kWidth, kHeight); 66 | 67 | if (!display.IsValid()) { 68 | FLWAY_ERROR << "Wayland display was not valid." << std::endl; 69 | return false; 70 | } 71 | 72 | FlutterApplication application(asset_bundle_path, args, display); 73 | if (!application.IsValid()) { 74 | FLWAY_ERROR << "Flutter application was not valid." << std::endl; 75 | return false; 76 | } 77 | 78 | if (!application.SetWindowSize(kWidth, kHeight)) { 79 | FLWAY_ERROR << "Could not update Flutter application size." << std::endl; 80 | return false; 81 | } 82 | 83 | display.Run(); 84 | 85 | return true; 86 | } 87 | 88 | } // namespace flutter 89 | 90 | int main(int argc, char* argv[]) { 91 | std::vector args; 92 | for (int i = 1; i < argc; ++i) { 93 | args.push_back(argv[i]); 94 | } 95 | return flutter::Main(std::move(args)) ? EXIT_SUCCESS : EXIT_FAILURE; 96 | } 97 | -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "utils.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace flutter { 12 | 13 | static std::string GetExecutablePath() { 14 | char executable_path[1024] = {0}; 15 | std::stringstream stream; 16 | stream << "/proc/" << getpid() << "/exe"; 17 | auto path = stream.str(); 18 | auto executable_path_size = 19 | ::readlink(path.c_str(), executable_path, sizeof(executable_path)); 20 | if (executable_path_size <= 0) { 21 | return ""; 22 | } 23 | return std::string{executable_path, 24 | static_cast(executable_path_size)}; 25 | } 26 | 27 | std::string GetExecutableName() { 28 | auto path_string = GetExecutablePath(); 29 | auto found = path_string.find_last_of('/'); 30 | if (found == std::string::npos) { 31 | return ""; 32 | } 33 | return path_string.substr(found + 1); 34 | } 35 | 36 | std::string GetExecutableDirectory() { 37 | auto path_string = GetExecutablePath(); 38 | auto found = path_string.find_last_of('/'); 39 | if (found == std::string::npos) { 40 | return ""; 41 | } 42 | return path_string.substr(0, found + 1); 43 | } 44 | 45 | bool FileExistsAtPath(const std::string& path) { 46 | return ::access(path.c_str(), R_OK) == 0; 47 | } 48 | 49 | bool FlutterAssetBundleIsValid(const std::string& bundle_path) { 50 | if (!FileExistsAtPath(bundle_path)) { 51 | FLWAY_ERROR << "Bundle directory does not exist." << std::endl; 52 | return false; 53 | } 54 | 55 | if (!FileExistsAtPath(bundle_path + std::string{"/kernel_blob.bin"})) { 56 | FLWAY_ERROR << "Kernel blob does not exist." << std::endl; 57 | return false; 58 | } 59 | 60 | return true; 61 | } 62 | 63 | } // namespace flutter 64 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include "macros.h" 8 | 9 | namespace flutter { 10 | 11 | std::string GetExecutableName(); 12 | 13 | std::string GetExecutableDirectory(); 14 | 15 | bool FileExistsAtPath(const std::string& path); 16 | 17 | bool FlutterAssetBundleIsValid(const std::string& bundle_path); 18 | 19 | } // namespace flutter 20 | -------------------------------------------------------------------------------- /src/wayland_display.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef WL_EGL_PLATFORM 6 | #define WL_EGL_PLATFORM 1 7 | #endif 8 | 9 | #include "wayland_display.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace flutter { 17 | 18 | #define DISPLAY reinterpret_cast(data) 19 | 20 | const wl_registry_listener WaylandDisplay::kRegistryListener = { 21 | .global = [](void* data, 22 | struct wl_registry* wl_registry, 23 | uint32_t name, 24 | const char* interface, 25 | uint32_t version) -> void { 26 | DISPLAY->AnnounceRegistryInterface(wl_registry, name, interface, version); 27 | }, 28 | 29 | .global_remove = 30 | [](void* data, struct wl_registry* wl_registry, uint32_t name) -> void { 31 | DISPLAY->UnannounceRegistryInterface(wl_registry, name); 32 | }, 33 | }; 34 | 35 | const wl_shell_surface_listener WaylandDisplay::kShellSurfaceListener = { 36 | .ping = [](void* data, 37 | struct wl_shell_surface* wl_shell_surface, 38 | uint32_t serial) -> void { 39 | wl_shell_surface_pong(DISPLAY->shell_surface_, serial); 40 | }, 41 | 42 | .configure = [](void* data, 43 | struct wl_shell_surface* wl_shell_surface, 44 | uint32_t edges, 45 | int32_t width, 46 | int32_t height) -> void { 47 | FLWAY_ERROR << "Unhandled resize." << std::endl; 48 | }, 49 | 50 | .popup_done = [](void* data, 51 | struct wl_shell_surface* wl_shell_surface) -> void { 52 | // Nothing to do. 53 | }, 54 | }; 55 | 56 | WaylandDisplay::WaylandDisplay(size_t width, size_t height) 57 | : screen_width_(width), screen_height_(height) { 58 | if (screen_width_ == 0 || screen_height_ == 0) { 59 | FLWAY_ERROR << "Invalid screen dimensions." << std::endl; 60 | return; 61 | } 62 | 63 | display_ = wl_display_connect(nullptr); 64 | 65 | if (!display_) { 66 | FLWAY_ERROR << "Could not connect to the wayland display." << std::endl; 67 | return; 68 | } 69 | 70 | registry_ = wl_display_get_registry(display_); 71 | if (!registry_) { 72 | FLWAY_ERROR << "Could not get the wayland registry." << std::endl; 73 | return; 74 | } 75 | 76 | wl_registry_add_listener(registry_, &kRegistryListener, this); 77 | 78 | wl_display_roundtrip(display_); 79 | 80 | if (!SetupEGL()) { 81 | FLWAY_ERROR << "Could not setup EGL." << std::endl; 82 | return; 83 | } 84 | 85 | valid_ = true; 86 | } 87 | 88 | WaylandDisplay::~WaylandDisplay() { 89 | if (shell_surface_) { 90 | wl_shell_surface_destroy(shell_surface_); 91 | shell_surface_ = nullptr; 92 | } 93 | 94 | if (shell_) { 95 | wl_shell_destroy(shell_); 96 | shell_ = nullptr; 97 | } 98 | 99 | if (egl_surface_) { 100 | eglDestroySurface(egl_display_, egl_surface_); 101 | egl_surface_ = nullptr; 102 | } 103 | 104 | if (egl_display_) { 105 | eglTerminate(egl_display_); 106 | egl_display_ = nullptr; 107 | } 108 | 109 | if (window_) { 110 | wl_egl_window_destroy(window_); 111 | window_ = nullptr; 112 | } 113 | 114 | if (surface_) { 115 | wl_surface_destroy(surface_); 116 | surface_ = nullptr; 117 | } 118 | 119 | if (compositor_) { 120 | wl_compositor_destroy(compositor_); 121 | compositor_ = nullptr; 122 | } 123 | 124 | if (registry_) { 125 | wl_registry_destroy(registry_); 126 | registry_ = nullptr; 127 | } 128 | 129 | if (display_) { 130 | wl_display_flush(display_); 131 | wl_display_disconnect(display_); 132 | display_ = nullptr; 133 | } 134 | } 135 | 136 | bool WaylandDisplay::IsValid() const { 137 | return valid_; 138 | } 139 | 140 | bool WaylandDisplay::Run() { 141 | if (!valid_) { 142 | FLWAY_ERROR << "Could not run an invalid display." << std::endl; 143 | return false; 144 | } 145 | 146 | while (valid_) { 147 | wl_display_dispatch(display_); 148 | } 149 | 150 | return true; 151 | } 152 | 153 | static void LogLastEGLError() { 154 | struct EGLNameErrorPair { 155 | const char* name; 156 | EGLint code; 157 | }; 158 | 159 | #define _EGL_ERROR_DESC(a) \ 160 | { #a, a } 161 | 162 | const EGLNameErrorPair pairs[] = { 163 | _EGL_ERROR_DESC(EGL_SUCCESS), 164 | _EGL_ERROR_DESC(EGL_NOT_INITIALIZED), 165 | _EGL_ERROR_DESC(EGL_BAD_ACCESS), 166 | _EGL_ERROR_DESC(EGL_BAD_ALLOC), 167 | _EGL_ERROR_DESC(EGL_BAD_ATTRIBUTE), 168 | _EGL_ERROR_DESC(EGL_BAD_CONTEXT), 169 | _EGL_ERROR_DESC(EGL_BAD_CONFIG), 170 | _EGL_ERROR_DESC(EGL_BAD_CURRENT_SURFACE), 171 | _EGL_ERROR_DESC(EGL_BAD_DISPLAY), 172 | _EGL_ERROR_DESC(EGL_BAD_SURFACE), 173 | _EGL_ERROR_DESC(EGL_BAD_MATCH), 174 | _EGL_ERROR_DESC(EGL_BAD_PARAMETER), 175 | _EGL_ERROR_DESC(EGL_BAD_NATIVE_PIXMAP), 176 | _EGL_ERROR_DESC(EGL_BAD_NATIVE_WINDOW), 177 | _EGL_ERROR_DESC(EGL_CONTEXT_LOST), 178 | }; 179 | 180 | #undef _EGL_ERROR_DESC 181 | 182 | const auto count = sizeof(pairs) / sizeof(EGLNameErrorPair); 183 | 184 | EGLint last_error = eglGetError(); 185 | 186 | for (size_t i = 0; i < count; i++) { 187 | if (last_error == pairs[i].code) { 188 | FLWAY_ERROR << "EGL Error: " << pairs[i].name << " (" << pairs[i].code 189 | << ")" << std::endl; 190 | return; 191 | } 192 | } 193 | 194 | FLWAY_ERROR << "Unknown EGL Error" << std::endl; 195 | } 196 | 197 | bool WaylandDisplay::SetupEGL() { 198 | if (!compositor_ || !shell_) { 199 | FLWAY_ERROR << "EGL setup needs missing compositor and shell connection." 200 | << std::endl; 201 | return false; 202 | } 203 | 204 | surface_ = wl_compositor_create_surface(compositor_); 205 | 206 | if (!surface_) { 207 | FLWAY_ERROR << "Could not create compositor surface." << std::endl; 208 | return false; 209 | } 210 | 211 | shell_surface_ = wl_shell_get_shell_surface(shell_, surface_); 212 | 213 | if (!shell_surface_) { 214 | FLWAY_ERROR << "Could not shell surface." << std::endl; 215 | return false; 216 | } 217 | 218 | wl_shell_surface_add_listener(shell_surface_, &kShellSurfaceListener, this); 219 | 220 | wl_shell_surface_set_title(shell_surface_, "Flutter"); 221 | 222 | wl_shell_surface_set_toplevel(shell_surface_); 223 | 224 | window_ = wl_egl_window_create(surface_, screen_width_, screen_height_); 225 | 226 | if (!window_) { 227 | FLWAY_ERROR << "Could not create EGL window." << std::endl; 228 | return false; 229 | } 230 | 231 | if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) { 232 | LogLastEGLError(); 233 | FLWAY_ERROR << "Could not bind the ES API." << std::endl; 234 | return false; 235 | } 236 | 237 | egl_display_ = eglGetDisplay(display_); 238 | if (egl_display_ == EGL_NO_DISPLAY) { 239 | LogLastEGLError(); 240 | FLWAY_ERROR << "Could not access EGL display." << std::endl; 241 | return false; 242 | } 243 | 244 | if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) { 245 | LogLastEGLError(); 246 | FLWAY_ERROR << "Could not initialize EGL display." << std::endl; 247 | return false; 248 | } 249 | 250 | EGLConfig egl_config = nullptr; 251 | 252 | // Choose an EGL config to use for the surface and context. 253 | { 254 | EGLint attribs[] = { 255 | // clang-format off 256 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 257 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 258 | EGL_RED_SIZE, 8, 259 | EGL_GREEN_SIZE, 8, 260 | EGL_BLUE_SIZE, 8, 261 | EGL_ALPHA_SIZE, 8, 262 | EGL_DEPTH_SIZE, 0, 263 | EGL_STENCIL_SIZE, 0, 264 | EGL_NONE, // termination sentinel 265 | // clang-format on 266 | }; 267 | 268 | EGLint config_count = 0; 269 | 270 | if (eglChooseConfig(egl_display_, attribs, &egl_config, 1, &config_count) != 271 | EGL_TRUE) { 272 | LogLastEGLError(); 273 | FLWAY_ERROR << "Error when attempting to choose an EGL surface config." 274 | << std::endl; 275 | return false; 276 | } 277 | 278 | if (config_count == 0 || egl_config == nullptr) { 279 | LogLastEGLError(); 280 | FLWAY_ERROR << "No matching configs." << std::endl; 281 | return false; 282 | } 283 | } 284 | 285 | // Create an EGL window surface with the matched config. 286 | { 287 | const EGLint attribs[] = {EGL_NONE}; 288 | 289 | egl_surface_ = 290 | eglCreateWindowSurface(egl_display_, egl_config, window_, attribs); 291 | 292 | if (surface_ == EGL_NO_SURFACE) { 293 | LogLastEGLError(); 294 | FLWAY_ERROR << "EGL surface was null during surface selection." 295 | << std::endl; 296 | return false; 297 | } 298 | } 299 | 300 | // Create an EGL context with the match config. 301 | { 302 | const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; 303 | 304 | egl_context_ = eglCreateContext(egl_display_, egl_config, 305 | nullptr /* share group */, attribs); 306 | 307 | if (egl_context_ == EGL_NO_CONTEXT) { 308 | LogLastEGLError(); 309 | FLWAY_ERROR << "Could not create an onscreen context." << std::endl; 310 | return false; 311 | } 312 | } 313 | 314 | return true; 315 | } 316 | 317 | void WaylandDisplay::AnnounceRegistryInterface(struct wl_registry* wl_registry, 318 | uint32_t name, 319 | const char* interface_name, 320 | uint32_t version) { 321 | if (strcmp(interface_name, "wl_compositor") == 0) { 322 | compositor_ = static_cast( 323 | wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); 324 | return; 325 | } 326 | 327 | if (strcmp(interface_name, "wl_shell") == 0) { 328 | shell_ = static_cast( 329 | wl_registry_bind(wl_registry, name, &wl_shell_interface, 1)); 330 | return; 331 | } 332 | } 333 | 334 | void WaylandDisplay::UnannounceRegistryInterface( 335 | struct wl_registry* wl_registry, 336 | uint32_t name) {} 337 | 338 | // |flutter::FlutterApplication::RenderDelegate| 339 | bool WaylandDisplay::OnApplicationContextMakeCurrent() { 340 | if (!valid_) { 341 | FLWAY_ERROR << "Invalid display." << std::endl; 342 | return false; 343 | } 344 | 345 | if (eglMakeCurrent(egl_display_, egl_surface_, egl_surface_, egl_context_) != 346 | EGL_TRUE) { 347 | LogLastEGLError(); 348 | FLWAY_ERROR << "Could not make the onscreen context current" << std::endl; 349 | return false; 350 | } 351 | 352 | return true; 353 | } 354 | 355 | // |flutter::FlutterApplication::RenderDelegate| 356 | bool WaylandDisplay::OnApplicationContextClearCurrent() { 357 | if (!valid_) { 358 | FLWAY_ERROR << "Invalid display." << std::endl; 359 | return false; 360 | } 361 | 362 | if (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, 363 | EGL_NO_CONTEXT) != EGL_TRUE) { 364 | LogLastEGLError(); 365 | FLWAY_ERROR << "Could not clear the context." << std::endl; 366 | return false; 367 | } 368 | 369 | return true; 370 | } 371 | 372 | // |flutter::FlutterApplication::RenderDelegate| 373 | bool WaylandDisplay::OnApplicationPresent() { 374 | if (!valid_) { 375 | FLWAY_ERROR << "Invalid display." << std::endl; 376 | return false; 377 | } 378 | 379 | if (eglSwapBuffers(egl_display_, egl_surface_) != EGL_TRUE) { 380 | LogLastEGLError(); 381 | FLWAY_ERROR << "Could not swap the EGL buffer." << std::endl; 382 | return false; 383 | } 384 | 385 | return true; 386 | } 387 | 388 | // |flutter::FlutterApplication::RenderDelegate| 389 | uint32_t WaylandDisplay::OnApplicationGetOnscreenFBO() { 390 | if (!valid_) { 391 | FLWAY_ERROR << "Invalid display." << std::endl; 392 | return 999; 393 | } 394 | 395 | return 0; // FBO0 396 | } 397 | 398 | } // namespace flutter 399 | -------------------------------------------------------------------------------- /src/wayland_display.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "flutter_application.h" 15 | #include "macros.h" 16 | 17 | namespace flutter { 18 | 19 | class WaylandDisplay : public FlutterApplication::RenderDelegate { 20 | public: 21 | WaylandDisplay(size_t width, size_t height); 22 | 23 | ~WaylandDisplay(); 24 | 25 | bool IsValid() const; 26 | 27 | bool Run(); 28 | 29 | private: 30 | static const wl_registry_listener kRegistryListener; 31 | static const wl_shell_surface_listener kShellSurfaceListener; 32 | bool valid_ = false; 33 | const int screen_width_; 34 | const int screen_height_; 35 | wl_display* display_ = nullptr; 36 | wl_registry* registry_ = nullptr; 37 | wl_compositor* compositor_ = nullptr; 38 | wl_shell* shell_ = nullptr; 39 | wl_shell_surface* shell_surface_ = nullptr; 40 | wl_surface* surface_ = nullptr; 41 | wl_egl_window* window_ = nullptr; 42 | EGLDisplay egl_display_ = EGL_NO_DISPLAY; 43 | EGLSurface egl_surface_ = nullptr; 44 | EGLContext egl_context_ = EGL_NO_CONTEXT; 45 | 46 | bool SetupEGL(); 47 | 48 | void AnnounceRegistryInterface(struct wl_registry* wl_registry, 49 | uint32_t name, 50 | const char* interface, 51 | uint32_t version); 52 | 53 | void UnannounceRegistryInterface(struct wl_registry* wl_registry, 54 | uint32_t name); 55 | 56 | bool StopRunning(); 57 | 58 | // |flutter::FlutterApplication::RenderDelegate| 59 | bool OnApplicationContextMakeCurrent() override; 60 | 61 | // |flutter::FlutterApplication::RenderDelegate| 62 | bool OnApplicationContextClearCurrent() override; 63 | 64 | // |flutter::FlutterApplication::RenderDelegate| 65 | bool OnApplicationPresent() override; 66 | 67 | // |flutter::FlutterApplication::RenderDelegate| 68 | uint32_t OnApplicationGetOnscreenFBO() override; 69 | 70 | FLWAY_DISALLOW_COPY_AND_ASSIGN(WaylandDisplay); 71 | }; 72 | 73 | } // namespace flutter 74 | --------------------------------------------------------------------------------