diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5bbac4f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [jwinarske] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index e23dba2..8965f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ Icon? *.sublime-workspace cmake-*/ + +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fffc166..a89dd35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,63 +6,111 @@ cmake_minimum_required(VERSION 3.5.2) project(flutter_wayland) -set(FLUTTER_ENGINE_SHA b9523318caa1a99ffde8adaf331212eb879cabc9) - -set(FLUTTER_EMBEDDER_ARTIFACTS_ZIP ${CMAKE_BINARY_DIR}/flutter_embedder_${FLUTTER_ENGINE_SHA}.zip) -set(FLUTTER_ARTIFACTS_ZIP ${CMAKE_BINARY_DIR}/flutter_artifact_${FLUTTER_ENGINE_SHA}.zip) -set(FLUTTER_BUCKET_BASE "https://storage.googleapis.com/flutter_infra/flutter") - -# Download and setup the Flutter Engine. -if(NOT EXISTS ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP}) - file(DOWNLOAD - ${FLUTTER_BUCKET_BASE}/${FLUTTER_ENGINE_SHA}/linux-x64/linux-x64-embedder - ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} - SHOW_PROGRESS - ) - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xzf ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) -endif() +if(NOT FLUTTER_ENGINE_LIBRARY AND NOT CMAKE_CROSSCOMPILING) + + if(NOT FLUTTER_ENGINE_SHA) + + if(NOT CHANNEL) + set(CHANNEL "dev" CACHE STRING "Choose the channel, options are: master, dev, beta, stable" FORCE) + message(STATUS "Flutter Channel not set, defaulting to dev") + endif() + + message(STATUS "Flutter Channel ........ ${CHANNEL}") + + include(FetchContent) + + FetchContent_Declare(engine-version + URL https://raw.githubusercontent.com/flutter/flutter/${CHANNEL}/bin/internal/engine.version + DOWNLOAD_NAME engine.version + DOWNLOAD_NO_EXTRACT TRUE + DOWNLOAD_DIR ${CMAKE_BINARY_DIR} + ) + + FetchContent_GetProperties(engine-version) + if(NOT engine-version_POPULATED) + FetchContent_Populate(engine-version) + file(READ ${CMAKE_BINARY_DIR}/engine.version FLUTTER_ENGINE_SHA) + string(REPLACE "\n" "" FLUTTER_ENGINE_SHA ${FLUTTER_ENGINE_SHA}) + else() + MESSAGE(FATAL "Unable to determine engine-version, please override FLUTTER_ENGINE_SHA") + endif() + + endif() + + message(STATUS "Engine SHA1 ............ ${FLUTTER_ENGINE_SHA}") + + # Download and setup the Flutter Engine. -if(NOT EXISTS ${FLUTTER_ARTIFACTS_ZIP}) - file(DOWNLOAD - ${FLUTTER_BUCKET_BASE}/${FLUTTER_ENGINE_SHA}/linux-x64/artifacts.zip - ${FLUTTER_ARTIFACTS_ZIP} - SHOW_PROGRESS - ) - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xzf ${FLUTTER_ARTIFACTS_ZIP} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) + set(FLUTTER_EMBEDDER_ARTIFACTS_ZIP ${CMAKE_BINARY_DIR}/flutter_embedder_${FLUTTER_ENGINE_SHA}.zip) + set(FLUTTER_BUCKET_BASE "https://storage.googleapis.com/flutter_infra/flutter") + + if(NOT EXISTS ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP}) + file(DOWNLOAD + ${FLUTTER_BUCKET_BASE}/${FLUTTER_ENGINE_SHA}/linux-x64/linux-x64-embedder + ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} + SHOW_PROGRESS + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${FLUTTER_EMBEDDER_ARTIFACTS_ZIP} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + endif() + + set(FLUTTER_ENGINE_LIBRARY ${CMAKE_BINARY_DIR}/libflutter_engine.so) +else() + message(STATUS "Engine ................. ${FLUTTER_ENGINE_LIBRARY}") endif() -find_package(PkgConfig) -pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) -pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) -pkg_check_modules(EGL REQUIRED egl) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS FALSE) + +include(FindPkgConfig) +pkg_check_modules(WAYLANDPP REQUIRED + wayland-client-extra++>=0.2.7 + wayland-client++>=0.2.7 + wayland-client-unstable++>=0.2.7 + wayland-cursor++>=0.2.7 + wayland-egl++>=0.2.7 + wayland-client++>=0.2.7 + ) + +pkg_check_modules(EGL REQUIRED egl) +pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) +pkg_check_modules(RAPIDJSON REQUIRED "RapidJSON>=1.1.0") + -# Executable -file(GLOB_RECURSE FLUTTER_WAYLAND_SRC - "src/*.cc" - "src/*.h" +set(FLUTTER_WAYLAND_SRC + flutter/standard_codec.cc + src/wayland_display.cc + src/keyboard.cc + src/platform_channel.cc + src/utils.cc + src/main.cc ) link_directories(${CMAKE_BINARY_DIR}) add_executable(flutter_wayland ${FLUTTER_WAYLAND_SRC}) -target_link_libraries(flutter_wayland - ${WAYLAND_CLIENT_LIBRARIES} - ${WAYLAND_EGL_LIBRARIES} - ${EGL_LIBRARIES} - flutter_engine +target_link_libraries(flutter_wayland dl + ${FLUTTER_ENGINE_LIBRARY} + ${XKBCOMMON_LINK_LIBRARIES} + ${RAPIDJSON_LINK_LIBRARIES} + ${WAYLANDPP_LINK_LIBRARIES} + ${EGL_LDFLAGS} ) - +link_directories() target_include_directories(flutter_wayland PRIVATE - ${WAYLAND_CLIENT_INCLUDE_DIRS} - ${WAYLAND_EGL_INCLUDE_DIRS} - ${EGL_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + ${WAYLANDPP_INCLUDE_DIRS} + ${EGL_INCLUDE_DIRS} + ${XKBCOMMON_INCLUDE_DIRS} + ${RAPIDJSON_INCLUDE_DIRS} ) + +target_compile_options(flutter_wayland PUBLIC ${EGL_CFLAGS}) + +install(TARGETS flutter_wayland RUNTIME DESTINATION bin) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c822bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, Joel Winarske +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 61f761d..961030c 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,97 @@ -Flutter Wayland -============ +# Flutter Wayland -A Flutter Embedder that talks to Wayland. +A Flutter Embedder that talks to Wayland -![Running in Weston](assets/image.png) +** ARCHIVED REPO ** -Build Setup Instructions ------------------------- +I recommend use of https://github.com/toyota-connected/ivi-homescreen/ which is re-write of this proof of concept app. -* Install the following packages (on Debian Stretch): `weston`, `libwayland-dev`, `cmake` and `ninja`. -* From the source root `mkdir build` and move into the directory. -* `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. -* `ninja` to build the embedder. -* Run the embedder using `./flutter_wayland`. Make sure `weston` is running. See the instructions on running Flutter applications below. +![Running in Weston](assets/fedora_34.png) -Running Flutter Applications ----------------------------- +#### Build Setup Instructions + +* Ubuntu 18/20 deps: `sudo apt-get install cmake lib-wayland++ libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libxkbcommon-dev rapidjson-dev clang` +* Fedora 33/34 deps: `sudo dnf install wayland-devel libxkbcommon-devel rapidjson-devel pugixml-devel waylandpp-devel clang` + +``` +mkdir build && cd build +CXX=/usr/bin/clang++ CC=/usr/bin/clang cmake .. +make -j VERBOSE=1 +``` + +#### Enable Wayland on Ubuntu 16/18 + +* log out +* select gear icon +* select `Ubuntu on Wayland` +* login as usual + +## Running Flutter Application ``` Flutter Wayland Embedder + ======================== +Usage: `flutter_wayland ` -Usage: `flutter_wayland ` This utility runs an instance of a Flutter application and renders using Wayland core protocols. The Flutter tools can be obtained at https://flutter.io/ -asset_bundle_path: The Flutter application code needs to be snapshotted using - the Flutter tools and the assets packaged in the appropriate - location. This can be done for any Flutter application by - running `flutter build bundle` while in the directory of a - valid Flutter project. This should package all the code and - assets in the "build/flutter_assets" directory. Specify this - directory as the first argument to this utility. +app_path: This either points to asset bundle path, or + an Ahead Of Time (AOT) shared library (.so). + +asset_path: The Flutter application code needs to be snapshotted using + the Flutter tools and the assets packaged in the appropriate + location. This can be done for any Flutter application by + running `flutter build bundle` while in the directory of a + valid Flutter project. This should package all the code and + assets in the "build/flutter_assets" directory. Specify this + directory as the first argument to this utility. + +flutter_flags: Typically empty. These extra flags are passed directly to the + Flutter engine. To see all supported flags, run + `flutter_tester --help` using the test binary included in the + Flutter tools. +``` + +#### Building Gallery Application +``` +cd ~/development +export PATH=`pwd`/flutter/bin:$PATH +flutter doctor -v +git clone https://github.com/flutter/gallery.git +cd gallery +flutter channel dev +flutter upgrade +flutter build bundle +``` +#### Running Gallery Application +``` +cd flutter_wayland/build +cp ~/development/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat . +./flutter_wayland /home/joel/development/gallery/build/flutter_assets +``` +Note: the flutter engine and gallery channel must match, or you will see something like this: - flutter_flags: Typically empty. These extra flags are passed directly to the - Flutter engine. To see all supported flags, run - `flutter_tester --help` using the test binary included in the - Flutter tools. +``` +[ERROR:flutter/shell/common/shell.cc(103)] Dart Error: Can't load Kernel binary: Invalid SDK hash. +[ERROR:flutter/runtime/dart_isolate.cc(171)] Could not prepare isolate. +[ERROR:flutter/runtime/runtime_controller.cc(415)] Could not create root isolate. +[ERROR:flutter/shell/common/shell.cc(588)] Could not launch engine with configuration. +``` +A run without errors might look like this: + +``` +$ ./flutter_wayland /home/joel/development/gallery/build/flutter_assets +LOG: /mnt/raid10/flutter_wayland/src/main.cc:65: Arg: /home/joel/development/gallery/build/flutter_assets +Pointer Present +Keyboard Present +assets_path: /home/joel/development/gallery/build/flutter_assets +load_aot: 0 +flutter: Observatory listening on http://127.0.0.1:36061/E9T-fNgSjiU=/ +LOG: /mnt/raid10/flutter_wayland/src/platform_channel.cc:108: PlatformChannel: SystemChrome.setApplicationSwitcherDescription ``` diff --git a/assets/fedora_34.png b/assets/fedora_34.png new file mode 100644 index 0000000..5c2f41e Binary files /dev/null and b/assets/fedora_34.png differ diff --git a/assets/ubuntu_wayland_18.0.4.png b/assets/ubuntu_wayland_18.0.4.png new file mode 100644 index 0000000..f777414 Binary files /dev/null and b/assets/ubuntu_wayland_18.0.4.png differ diff --git a/flutter/byte_stream_wrappers.h b/flutter/byte_stream_wrappers.h new file mode 100644 index 0000000..96ab0b0 --- /dev/null +++ b/flutter/byte_stream_wrappers.h @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_ + +// Utility classes for interacting with a buffer of bytes as a stream, for use +// in message channel codecs. + +#include +#include +#include +#include + +namespace flutter { + +// Wraps an array of bytes with utility methods for treating it as a readable +// stream. +class ByteBufferStreamReader { + public: + // Createa a reader reading from |bytes|, which must have a length of |size|. + // |bytes| must remain valid for the lifetime of this object. + explicit ByteBufferStreamReader(const uint8_t* bytes, size_t size) + : bytes_(bytes), size_(size) {} + + // Reads and returns the next byte from the stream. + uint8_t ReadByte() { + if (location_ >= size_) { + std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl; + return 0; + } + return bytes_[location_++]; + } + + // Reads the next |length| bytes from the stream into |buffer|. The caller + // is responsible for ensuring that |buffer| is large enough. + void ReadBytes(uint8_t* buffer, size_t length) { + if (location_ + length > size_) { + std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl; + return; + } + std::memcpy(buffer, &bytes_[location_], length); + location_ += length; + } + + // Advances the read cursor to the next multiple of |alignment| relative to + // the start of the wrapped byte buffer, unless it is already aligned. + void ReadAlignment(uint8_t alignment) { + uint8_t mod = location_ % alignment; + if (mod) { + location_ += alignment - mod; + } + } + + private: + // The buffer to read from. + const uint8_t* bytes_; + // The total size of the buffer. + size_t size_; + // The current read location. + size_t location_ = 0; +}; + +// Wraps an array of bytes with utility methods for treating it as a writable +// stream. +class ByteBufferStreamWriter { + public: + // Createa a writter that writes into |buffer|. + // |buffer| must remain valid for the lifetime of this object. + explicit ByteBufferStreamWriter(std::vector* buffer) + : bytes_(buffer) { + assert(buffer); + } + + // Writes |byte| to the wrapped buffer. + void WriteByte(uint8_t byte) { bytes_->push_back(byte); } + + // Writes the next |length| bytes from |bytes| into the wrapped buffer. + // The caller is responsible for ensuring that |buffer| is large enough. + void WriteBytes(const uint8_t* bytes, size_t length) { + assert(length > 0); + bytes_->insert(bytes_->end(), bytes, bytes + length); + } + + // Writes 0s until the next multiple of |alignment| relative to + // the start of the wrapped byte buffer, unless the write positition is + // already aligned. + void WriteAlignment(uint8_t alignment) { + uint8_t mod = bytes_->size() % alignment; + if (mod) { + for (int i = 0; i < alignment - mod; ++i) { + WriteByte(0); + } + } + } + + private: + // The buffer to write to. + std::vector* bytes_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_ diff --git a/flutter/encodable_value.h b/flutter/encodable_value.h new file mode 100644 index 0000000..e098c13 --- /dev/null +++ b/flutter/encodable_value.h @@ -0,0 +1,570 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_ + +#include + +#include +#include +#include +#include +#include + +namespace flutter { + +static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double"); + +class EncodableValue; +// Convenience type aliases for list and map EncodableValue types. +using EncodableList = std::vector; +using EncodableMap = std::map; + +// An object that can contain any value or collection type supported by +// Flutter's standard method codec. +// +// For details, see: +// https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html +// +// As an example, the following Dart structure: +// { +// 'flag': true, +// 'name': 'Thing', +// 'values': [1, 2.0, 4], +// } +// would correspond to: +// EncodableValue(EncodableMap{ +// {EncodableValue("flag"), EncodableValue(true)}, +// {EncodableValue("name"), EncodableValue("Thing")}, +// {EncodableValue("values"), EncodableValue(EncodableList{ +// EncodableValue(1), +// EncodableValue(2.0), +// EncodableValue(4), +// })}, +// }) +class EncodableValue { + public: + // Possible types for an EncodableValue to reperesent. + enum class Type { + kNull, // A null value. + kBool, // A boolean value. + kInt, // A 32-bit integer. + kLong, // A 64-bit integer. + kDouble, // A 64-bit floating point number. + kString, // A string. + kByteList, // A list of bytes. + kIntList, // A list of 32-bit integers. + kLongList, // A list of 64-bit integers. + kDoubleList, // A list of 64-bit floating point numbers. + kList, // A list of EncodableValues. + kMap, // A mapping from EncodableValues to EncodableValues. + }; + + // Creates an instance representing a null value. + EncodableValue() {} + + // Creates an instance representing a bool value. + explicit EncodableValue(bool value) : bool_(value), type_(Type::kBool) {} + + // Creates an instance representing a 32-bit integer value. + explicit EncodableValue(int32_t value) : int_(value), type_(Type::kInt) {} + + // Creates an instance representing a 64-bit integer value. + explicit EncodableValue(int64_t value) : long_(value), type_(Type::kLong) {} + + // Creates an instance representing a 64-bit floating point value. + explicit EncodableValue(double value) + : double_(value), type_(Type::kDouble) {} + + // Creates an instance representing a string value. + explicit EncodableValue(const char* value) + : string_(new std::string(value)), type_(Type::kString) {} + + // Creates an instance representing a string value. + explicit EncodableValue(const std::string& value) + : string_(new std::string(value)), type_(Type::kString) {} + + // Creates an instance representing a list of bytes. + explicit EncodableValue(std::vector list) + : byte_list_(new std::vector(std::move(list))), + type_(Type::kByteList) {} + + // Creates an instance representing a list of 32-bit integers. + explicit EncodableValue(std::vector list) + : int_list_(new std::vector(std::move(list))), + type_(Type::kIntList) {} + + // Creates an instance representing a list of 64-bit integers. + explicit EncodableValue(std::vector list) + : long_list_(new std::vector(std::move(list))), + type_(Type::kLongList) {} + + // Creates an instance representing a list of 64-bit floating point values. + explicit EncodableValue(std::vector list) + : double_list_(new std::vector(std::move(list))), + type_(Type::kDoubleList) {} + + // Creates an instance representing a list of EncodableValues. + explicit EncodableValue(EncodableList list) + : list_(new EncodableList(std::move(list))), type_(Type::kList) {} + + // Creates an instance representing a map from EncodableValues to + // EncodableValues. + explicit EncodableValue(EncodableMap map) + : map_(new EncodableMap(std::move(map))), type_(Type::kMap) {} + + // Convience constructor for creating default value of the given type. + // + // Collections types will be empty, numeric types will be 0, strings will be + // empty, and booleans will be false. For non-collection types, prefer using + // the value-based constructor with an explicit value for clarity. + explicit EncodableValue(Type type) : type_(type) { + switch (type_) { + case Type::kNull: + break; + case Type::kBool: + bool_ = false; + break; + case Type::kInt: + int_ = 0; + break; + case Type::kLong: + long_ = 0; + break; + case Type::kDouble: + double_ = 0.0; + break; + case Type::kString: + string_ = new std::string(); + break; + case Type::kByteList: + byte_list_ = new std::vector(); + break; + case Type::kIntList: + int_list_ = new std::vector(); + break; + case Type::kLongList: + long_list_ = new std::vector(); + break; + case Type::kDoubleList: + double_list_ = new std::vector(); + break; + case Type::kList: + list_ = new std::vector(); + break; + case Type::kMap: + map_ = new std::map(); + break; + } + } + + ~EncodableValue() { DestroyValue(); } + + EncodableValue(const EncodableValue& other) { + DestroyValue(); + + type_ = other.type_; + switch (type_) { + case Type::kNull: + break; + case Type::kBool: + bool_ = other.bool_; + break; + case Type::kInt: + int_ = other.int_; + break; + case Type::kLong: + long_ = other.long_; + break; + case Type::kDouble: + double_ = other.double_; + break; + case Type::kString: + string_ = new std::string(*other.string_); + break; + case Type::kByteList: + byte_list_ = new std::vector(*other.byte_list_); + break; + case Type::kIntList: + int_list_ = new std::vector(*other.int_list_); + break; + case Type::kLongList: + long_list_ = new std::vector(*other.long_list_); + break; + case Type::kDoubleList: + double_list_ = new std::vector(*other.double_list_); + break; + case Type::kList: + list_ = new std::vector(*other.list_); + break; + case Type::kMap: + map_ = new std::map(*other.map_); + break; + } + } + + EncodableValue(EncodableValue&& other) noexcept { *this = std::move(other); } + + EncodableValue& operator=(const EncodableValue& other) { + if (&other == this) { + return *this; + } + using std::swap; + EncodableValue temp(other); + swap(*this, temp); + return *this; + } + + EncodableValue& operator=(EncodableValue&& other) noexcept { + if (&other == this) { + return *this; + } + DestroyValue(); + + type_ = other.type_; + switch (type_) { + case Type::kNull: + break; + case Type::kBool: + bool_ = other.bool_; + break; + case Type::kInt: + int_ = other.int_; + break; + case Type::kLong: + long_ = other.long_; + break; + case Type::kDouble: + double_ = other.double_; + break; + case Type::kString: + string_ = other.string_; + break; + case Type::kByteList: + byte_list_ = other.byte_list_; + break; + case Type::kIntList: + int_list_ = other.int_list_; + break; + case Type::kLongList: + long_list_ = other.long_list_; + break; + case Type::kDoubleList: + double_list_ = other.double_list_; + break; + case Type::kList: + list_ = other.list_; + break; + case Type::kMap: + map_ = other.map_; + break; + } + // Ensure that destruction doesn't run on the source of the move. + other.type_ = Type::kNull; + return *this; + } + + // Allow assigning any value type that can be used for a constructor. + template + EncodableValue& operator=(const T& value) { + *this = EncodableValue(value); + return *this; + } + + // This operator exists only to provide a stable ordering for use as a + // std::map key. It does not attempt to provide useful ordering semantics. + // Notably: + // - Numeric values are not guaranteed any ordering across numeric types. + // E.g., 1 as a Long may sort after 100 as an Int. + // - Collection types use pointer equality, rather than value. This means that + // multiple collections with the same values will end up as separate keys + // in a map (consistent with default Dart Map behavior). + bool operator<(const EncodableValue& other) const { + if (type_ != other.type_) { + return type_ < other.type_; + } + switch (type_) { + case Type::kNull: + return false; + case Type::kBool: + return bool_ < other.bool_; + case Type::kInt: + return int_ < other.int_; + case Type::kLong: + return long_ < other.long_; + case Type::kDouble: + return double_ < other.double_; + case Type::kString: + return *string_ < *other.string_; + case Type::kByteList: + case Type::kIntList: + case Type::kLongList: + case Type::kDoubleList: + case Type::kList: + case Type::kMap: + return this < &other; + } + assert(false); + return false; + } + + // Returns the bool value this object represents. + // + // It is a programming error to call this unless IsBool() is true. + bool BoolValue() const { + assert(IsBool()); + return bool_; + } + + // Returns the 32-bit integer value this object represents. + // + // It is a programming error to call this unless IsInt() is true. + int32_t IntValue() const { + assert(IsInt()); + return int_; + } + + // Returns the 64-bit integer value this object represents. + // + // It is a programming error to call this unless IsLong() or IsInt() is true. + // + // Note that calling this function on an Int value is the only case where + // a *Value() function can be called without the corresponding Is*() being + // true. This is to simplify handling objects received from Flutter where the + // values may be larger than 32-bit, since they have the same type on the Dart + // side, but will be either 32-bit or 64-bit here depending on the value. + int64_t LongValue() const { + assert(IsLong() || IsInt()); + if (IsLong()) { + return long_; + } + return int_; + } + + // Returns the double value this object represents. + // + // It is a programming error to call this unless IsDouble() is true. + double DoubleValue() const { + assert(IsDouble()); + return double_; + } + + // Returns the string value this object represents. + // + // It is a programming error to call this unless IsString() is true. + const std::string& StringValue() const { + assert(IsString()); + return *string_; + } + + // Returns the byte list this object represents. + // + // It is a programming error to call this unless IsByteList() is true. + const std::vector& ByteListValue() const { + assert(IsByteList()); + return *byte_list_; + } + + // Returns the byte list this object represents. + // + // It is a programming error to call this unless IsByteList() is true. + std::vector& ByteListValue() { + assert(IsByteList()); + return *byte_list_; + } + + // Returns the 32-bit integer list this object represents. + // + // It is a programming error to call this unless IsIntList() is true. + const std::vector& IntListValue() const { + assert(IsIntList()); + return *int_list_; + } + + // Returns the 32-bit integer list this object represents. + // + // It is a programming error to call this unless IsIntList() is true. + std::vector& IntListValue() { + assert(IsIntList()); + return *int_list_; + } + + // Returns the 64-bit integer list this object represents. + // + // It is a programming error to call this unless IsLongList() is true. + const std::vector& LongListValue() const { + assert(IsLongList()); + return *long_list_; + } + + // Returns the 64-bit integer list this object represents. + // + // It is a programming error to call this unless IsLongList() is true. + std::vector& LongListValue() { + assert(IsLongList()); + return *long_list_; + } + + // Returns the double list this object represents. + // + // It is a programming error to call this unless IsDoubleList() is true. + const std::vector& DoubleListValue() const { + assert(IsDoubleList()); + return *double_list_; + } + + // Returns the double list this object represents. + // + // It is a programming error to call this unless IsDoubleList() is true. + std::vector& DoubleListValue() { + assert(IsDoubleList()); + return *double_list_; + } + + // Returns the list of EncodableValues this object represents. + // + // It is a programming error to call this unless IsList() is true. + const EncodableList& ListValue() const { + assert(IsList()); + return *list_; + } + + // Returns the list of EncodableValues this object represents. + // + // It is a programming error to call this unless IsList() is true. + EncodableList& ListValue() { + assert(IsList()); + return *list_; + } + + // Returns the map of EncodableValue : EncodableValue pairs this object + // represent. + // + // It is a programming error to call this unless IsMap() is true. + const EncodableMap& MapValue() const { + assert(IsMap()); + return *map_; + } + + // Returns the map of EncodableValue : EncodableValue pairs this object + // represent. + // + // It is a programming error to call this unless IsMap() is true. + EncodableMap& MapValue() { + assert(IsMap()); + return *map_; + } + + // Returns true if this represents a null value. + bool IsNull() const { return type_ == Type::kNull; } + + // Returns true if this represents a bool value. + bool IsBool() const { return type_ == Type::kBool; } + + // Returns true if this represents a 32-bit integer value. + bool IsInt() const { return type_ == Type::kInt; } + + // Returns true if this represents a 64-bit integer value. + bool IsLong() const { return type_ == Type::kLong; } + + // Returns true if this represents a double value. + bool IsDouble() const { return type_ == Type::kDouble; } + + // Returns true if this represents a string value. + bool IsString() const { return type_ == Type::kString; } + + // Returns true if this represents a list of bytes. + bool IsByteList() const { return type_ == Type::kByteList; } + + // Returns true if this represents a list of 32-bit integers. + bool IsIntList() const { return type_ == Type::kIntList; } + + // Returns true if this represents a list of 64-bit integers. + bool IsLongList() const { return type_ == Type::kLongList; } + + // Returns true if this represents a list of doubles. + bool IsDoubleList() const { return type_ == Type::kDoubleList; } + + // Returns true if this represents a list of EncodableValues. + bool IsList() const { return type_ == Type::kList; } + + // Returns true if this represents a map of EncodableValue : EncodableValue + // pairs. + bool IsMap() const { return type_ == Type::kMap; } + + // Returns the type this value represents. + // + // This is primarily intended for use with switch(); for individual checks, + // prefer an Is*() call. + Type type() const { return type_; } + + private: + // Performs any cleanup necessary for the active union value. This must be + // called before assigning a new value, and on object destruction. + // + // After calling this, type_ will alway be kNull. + void DestroyValue() { + switch (type_) { + case Type::kNull: + case Type::kBool: + case Type::kInt: + case Type::kLong: + case Type::kDouble: + break; + case Type::kString: + delete string_; + break; + case Type::kByteList: + delete byte_list_; + break; + case Type::kIntList: + delete int_list_; + break; + case Type::kLongList: + delete long_list_; + break; + case Type::kDoubleList: + delete double_list_; + break; + case Type::kList: + delete list_; + break; + case Type::kMap: + delete map_; + break; + } + + type_ = Type::kNull; + } + + // The anonymous union that stores the represented value. Accessing any of + // these entries other than the one that corresponds to the current value of + // |type_| has undefined behavior. + // + // Pointers are used for the non-POD types to avoid making the overall size + // of the union unnecessarily large. + // + // TODO: Replace this with std::variant once c++17 is available. + union { + bool bool_; + int32_t int_; + int64_t long_; + double double_; + std::string* string_; + std::vector* byte_list_; + std::vector* int_list_; + std::vector* long_list_; + std::vector* double_list_; + std::vector* list_; + std::map* map_; + }; + + // The currently active union entry. + Type type_ = Type::kNull; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_ diff --git a/flutter/message_codec.h b/flutter/message_codec.h new file mode 100644 index 0000000..386d0d0 --- /dev/null +++ b/flutter/message_codec.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_MESSAGE_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_MESSAGE_CODEC_H_ + +#include +#include +#include + +namespace flutter { + +// Translates between a binary message and higher-level method call and +// response/error objects. +template +class MessageCodec { + public: + MessageCodec() = default; + + virtual ~MessageCodec() = default; + + // Prevent copying. + MessageCodec(MessageCodec const&) = delete; + MessageCodec& operator=(MessageCodec const&) = delete; + + // Returns the message encoded in |binary_message|, or nullptr if it cannot be + // decoded by this codec. + std::unique_ptr DecodeMessage(const uint8_t* binary_message, + const size_t message_size) const { + return std::move(DecodeMessageInternal(binary_message, message_size)); + } + + // Returns the message encoded in |binary_message|, or nullptr if it cannot be + // decoded by this codec. + std::unique_ptr DecodeMessage( + const std::vector& binary_message) const { + size_t size = binary_message.size(); + const uint8_t* data = size > 0 ? &binary_message[0] : nullptr; + return std::move(DecodeMessageInternal(data, size)); + } + + // Returns a binary encoding of the given |message|, or nullptr if the + // message cannot be serialized by this codec. + std::unique_ptr> EncodeMessage(const T& message) const { + return std::move(EncodeMessageInternal(message)); + } + + protected: + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr DecodeMessageInternal( + const uint8_t* binary_message, + const size_t message_size) const = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> EncodeMessageInternal( + const T& message) const = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_MESSAGE_CODEC_H_ diff --git a/flutter/method_call.h b/flutter/method_call.h new file mode 100644 index 0000000..80274e7 --- /dev/null +++ b/flutter/method_call.h @@ -0,0 +1,41 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_TYPED_METHOD_CALL_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_TYPED_METHOD_CALL_H_ + +#include +#include + +namespace flutter { + +// An object encapsulating a method call from Flutter whose arguments are of +// type T. +template +class MethodCall { + public: + // Creates a MethodCall with the given name and arguments. + MethodCall(const std::string& method_name, std::unique_ptr arguments) + : method_name_(method_name), arguments_(std::move(arguments)) {} + + virtual ~MethodCall() = default; + + // Prevent copying. + MethodCall(MethodCall const&) = delete; + MethodCall& operator=(MethodCall const&) = delete; + + // The name of the method being called. + const std::string& method_name() const { return method_name_; } + + // The arguments to the method call, or NULL if there are none. + const T* arguments() const { return arguments_.get(); } + + private: + std::string method_name_; + std::unique_ptr arguments_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_TYPED_METHOD_CALL_H_ diff --git a/flutter/method_codec.h b/flutter/method_codec.h new file mode 100644 index 0000000..6f9e09a --- /dev/null +++ b/flutter/method_codec.h @@ -0,0 +1,93 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_METHOD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_METHOD_CODEC_H_ + +#include +#include +#include + +#include "method_call.h" + +namespace flutter { + +// Translates between a binary message and higher-level method call and +// response/error objects. +template +class MethodCodec { + public: + MethodCodec() = default; + + virtual ~MethodCodec() = default; + + // Prevent copying. + MethodCodec(MethodCodec const&) = delete; + MethodCodec& operator=(MethodCodec const&) = delete; + + // Returns the MethodCall encoded in |message|, or nullptr if it cannot be + // decoded. + std::unique_ptr> DecodeMethodCall( + const uint8_t* message, + const size_t message_size) const { + return std::move(DecodeMethodCallInternal(message, message_size)); + } + + // Returns the MethodCall encoded in |message|, or nullptr if it cannot be + // decoded. + std::unique_ptr> DecodeMethodCall( + const std::vector& message) const { + size_t size = message.size(); + const uint8_t* data = size > 0 ? &message[0] : nullptr; + return std::move(DecodeMethodCallInternal(data, size)); + } + + // Returns a binary encoding of the given |method_call|, or nullptr if the + // method call cannot be serialized by this codec. + std::unique_ptr> EncodeMethodCall( + const MethodCall& method_call) const { + return std::move(EncodeMethodCallInternal(method_call)); + } + + // Returns a binary encoding of |result|. |result| must be a type supported + // by the codec. + std::unique_ptr> EncodeSuccessEnvelope( + const T* result = nullptr) const { + return std::move(EncodeSuccessEnvelopeInternal(result)); + } + + // Returns a binary encoding of |error|. The |error_details| must be a type + // supported by the codec. + std::unique_ptr> EncodeErrorEnvelope( + const std::string& error_code, + const std::string& error_message = "", + const T* error_details = nullptr) const { + return std::move( + EncodeErrorEnvelopeInternal(error_code, error_message, error_details)); + } + + protected: + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> DecodeMethodCallInternal( + const uint8_t* message, + const size_t message_size) const = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> EncodeMethodCallInternal( + const MethodCall& method_call) const = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> EncodeSuccessEnvelopeInternal( + const T* result) const = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> EncodeErrorEnvelopeInternal( + const std::string& error_code, + const std::string& error_message, + const T* error_details) const = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_METHOD_CODEC_H_ diff --git a/flutter/standard_codec.cc b/flutter/standard_codec.cc new file mode 100644 index 0000000..9fe4b5b --- /dev/null +++ b/flutter/standard_codec.cc @@ -0,0 +1,384 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains what would normally be standard_codec_serializer.cc, +// standard_message_codec.cc, and standard_method_codec.cc. They are grouped +// together to simplify use of the client wrapper, since the common case is +// that any client that needs one of these files needs all three. + +#include + +#include +#include +#include +#include +#include + +#include "flutter/standard_message_codec.h" +#include "flutter/standard_method_codec.h" +#include "standard_codec_serializer.h" + +namespace flutter { + +// ===== standard_codec_serializer.h ===== + +namespace { + +// The order/values here must match the constants in message_codecs.dart. +enum class EncodedType { + kNull = 0, + kTrue, + kFalse, + kInt32, + kInt64, + kLargeInt, // No longer used. If encountered, treat as kString. + kFloat64, + kString, + kUInt8List, + kInt32List, + kInt64List, + kFloat64List, + kList, + kMap, +}; + +// Returns the encoded type that should be written when serializing |value|. +EncodedType EncodedTypeForValue(const EncodableValue& value) { + switch (value.type()) { + case EncodableValue::Type::kNull: + return EncodedType::kNull; + case EncodableValue::Type::kBool: + return value.BoolValue() ? EncodedType::kTrue : EncodedType::kFalse; + case EncodableValue::Type::kInt: + return EncodedType::kInt32; + case EncodableValue::Type::kLong: + return EncodedType::kInt64; + case EncodableValue::Type::kDouble: + return EncodedType::kFloat64; + case EncodableValue::Type::kString: + return EncodedType::kString; + case EncodableValue::Type::kByteList: + return EncodedType::kUInt8List; + case EncodableValue::Type::kIntList: + return EncodedType::kInt32List; + case EncodableValue::Type::kLongList: + return EncodedType::kInt64List; + case EncodableValue::Type::kDoubleList: + return EncodedType::kFloat64List; + case EncodableValue::Type::kList: + return EncodedType::kList; + case EncodableValue::Type::kMap: + return EncodedType::kMap; + } + assert(false); + return EncodedType::kNull; +} + +} // namespace + +StandardCodecSerializer::StandardCodecSerializer() = default; + +StandardCodecSerializer::~StandardCodecSerializer() = default; + +EncodableValue StandardCodecSerializer::ReadValue( + ByteBufferStreamReader* stream) const { + EncodedType type = static_cast(stream->ReadByte()); + ; + switch (type) { + case EncodedType::kNull: + return EncodableValue(); + case EncodedType::kTrue: + return EncodableValue(true); + case EncodedType::kFalse: + return EncodableValue(false); + case EncodedType::kInt32: { + int32_t int_value = 0; + stream->ReadBytes(reinterpret_cast(&int_value), 4); + return EncodableValue(int_value); + } + case EncodedType::kInt64: { + int64_t long_value = 0; + stream->ReadBytes(reinterpret_cast(&long_value), 8); + return EncodableValue(long_value); + } + case EncodedType::kFloat64: { + double double_value = 0; + stream->ReadAlignment(8); + stream->ReadBytes(reinterpret_cast(&double_value), 8); + return EncodableValue(double_value); + } + case EncodedType::kLargeInt: + case EncodedType::kString: { + size_t size = ReadSize(stream); + std::string string_value; + string_value.resize(size); + stream->ReadBytes(reinterpret_cast(&string_value[0]), size); + return EncodableValue(string_value); + } + case EncodedType::kUInt8List: + return ReadVector(stream); + case EncodedType::kInt32List: + return ReadVector(stream); + case EncodedType::kInt64List: + return ReadVector(stream); + case EncodedType::kFloat64List: + return ReadVector(stream); + case EncodedType::kList: { + size_t length = ReadSize(stream); + EncodableList list_value; + list_value.reserve(length); + for (size_t i = 0; i < length; ++i) { + list_value.push_back(ReadValue(stream)); + } + return EncodableValue(list_value); + } + case EncodedType::kMap: { + size_t length = ReadSize(stream); + EncodableMap map_value; + for (size_t i = 0; i < length; ++i) { + EncodableValue key = ReadValue(stream); + EncodableValue value = ReadValue(stream); + map_value.emplace(std::move(key), std::move(value)); + } + return EncodableValue(map_value); + } + } + std::cerr << "Unknown type in StandardCodecSerializer::ReadValue: " + << static_cast(type) << std::endl; + return EncodableValue(); +} + +void StandardCodecSerializer::WriteValue(const EncodableValue& value, + ByteBufferStreamWriter* stream) const { + stream->WriteByte(static_cast(EncodedTypeForValue(value))); + switch (value.type()) { + case EncodableValue::Type::kNull: + case EncodableValue::Type::kBool: + // Null and bool are encoded directly in the type. + break; + case EncodableValue::Type::kInt: { + int32_t int_value = value.IntValue(); + stream->WriteBytes(reinterpret_cast(&int_value), 4); + break; + } + case EncodableValue::Type::kLong: { + int64_t long_value = value.LongValue(); + stream->WriteBytes(reinterpret_cast(&long_value), 8); + break; + } + case EncodableValue::Type::kDouble: { + stream->WriteAlignment(8); + double double_value = value.DoubleValue(); + stream->WriteBytes(reinterpret_cast(&double_value), 8); + break; + } + case EncodableValue::Type::kString: { + const auto& string_value = value.StringValue(); + size_t size = string_value.size(); + WriteSize(size, stream); + if (size > 0) { + stream->WriteBytes( + reinterpret_cast(string_value.data()), size); + } + break; + } + case EncodableValue::Type::kByteList: + WriteVector(value.ByteListValue(), stream); + break; + case EncodableValue::Type::kIntList: + WriteVector(value.IntListValue(), stream); + break; + case EncodableValue::Type::kLongList: + WriteVector(value.LongListValue(), stream); + break; + case EncodableValue::Type::kDoubleList: + WriteVector(value.DoubleListValue(), stream); + break; + case EncodableValue::Type::kList: + WriteSize(value.ListValue().size(), stream); + for (const auto& item : value.ListValue()) { + WriteValue(item, stream); + } + break; + case EncodableValue::Type::kMap: + WriteSize(value.MapValue().size(), stream); + for (const auto& pair : value.MapValue()) { + WriteValue(pair.first, stream); + WriteValue(pair.second, stream); + } + break; + } +} + +size_t StandardCodecSerializer::ReadSize(ByteBufferStreamReader* stream) const { + uint8_t byte = stream->ReadByte(); + if (byte < 254) { + return byte; + } else if (byte == 254) { + uint16_t value; + stream->ReadBytes(reinterpret_cast(&value), 2); + return value; + } else { + uint32_t value; + stream->ReadBytes(reinterpret_cast(&value), 4); + return value; + } +} + +void StandardCodecSerializer::WriteSize(size_t size, + ByteBufferStreamWriter* stream) const { + if (size < 254) { + stream->WriteByte(static_cast(size)); + } else if (size <= 0xffff) { + stream->WriteByte(254); + uint16_t value = static_cast(size); + stream->WriteBytes(reinterpret_cast(&value), 2); + } else { + stream->WriteByte(255); + uint32_t value = static_cast(size); + stream->WriteBytes(reinterpret_cast(&value), 4); + } +} + +template +EncodableValue StandardCodecSerializer::ReadVector( + ByteBufferStreamReader* stream) const { + size_t count = ReadSize(stream); + std::vector vector; + vector.resize(count); + uint8_t type_size = static_cast(sizeof(T)); + if (type_size > 1) { + stream->ReadAlignment(type_size); + } + stream->ReadBytes(reinterpret_cast(vector.data()), + count * type_size); + return EncodableValue(vector); +} + +template +void StandardCodecSerializer::WriteVector( + const std::vector vector, + ByteBufferStreamWriter* stream) const { + size_t count = vector.size(); + WriteSize(count, stream); + if (count == 0) { + return; + } + uint8_t type_size = static_cast(sizeof(T)); + if (type_size > 1) { + stream->WriteAlignment(type_size); + } + stream->WriteBytes(reinterpret_cast(vector.data()), + count * type_size); +} + +// ===== standard_message_codec.h ===== + +// static +const StandardMessageCodec& StandardMessageCodec::GetInstance() { + static StandardMessageCodec sInstance; + return sInstance; +} + +StandardMessageCodec::StandardMessageCodec() = default; + +StandardMessageCodec::~StandardMessageCodec() = default; + +std::unique_ptr StandardMessageCodec::DecodeMessageInternal( + const uint8_t* binary_message, + const size_t message_size) const { + StandardCodecSerializer serializer; + ByteBufferStreamReader stream(binary_message, message_size); + return std::make_unique(serializer.ReadValue(&stream)); +} + +std::unique_ptr> +StandardMessageCodec::EncodeMessageInternal( + const EncodableValue& message) const { + StandardCodecSerializer serializer; + auto encoded = std::make_unique>(); + ByteBufferStreamWriter stream(encoded.get()); + serializer.WriteValue(message, &stream); + return encoded; +} + +// ===== standard_method_codec.h ===== + +// static +const StandardMethodCodec& StandardMethodCodec::GetInstance() { + static StandardMethodCodec sInstance; + return sInstance; +} + +std::unique_ptr> +StandardMethodCodec::DecodeMethodCallInternal(const uint8_t* message, + const size_t message_size) const { + StandardCodecSerializer serializer; + ByteBufferStreamReader stream(message, message_size); + EncodableValue method_name = serializer.ReadValue(&stream); + if (!method_name.IsString()) { + std::cerr << "Invalid method call; method name is not a string." + << std::endl; + return nullptr; + } + auto arguments = + std::make_unique(serializer.ReadValue(&stream)); + return std::make_unique>(method_name.StringValue(), + std::move(arguments)); +} + +std::unique_ptr> +StandardMethodCodec::EncodeMethodCallInternal( + const MethodCall& method_call) const { + StandardCodecSerializer serializer; + auto encoded = std::make_unique>(); + ByteBufferStreamWriter stream(encoded.get()); + serializer.WriteValue(EncodableValue(method_call.method_name()), &stream); + if (method_call.arguments()) { + serializer.WriteValue(*method_call.arguments(), &stream); + } else { + serializer.WriteValue(EncodableValue(), &stream); + } + return encoded; +} + +std::unique_ptr> +StandardMethodCodec::EncodeSuccessEnvelopeInternal( + const EncodableValue* result) const { + StandardCodecSerializer serializer; + auto encoded = std::make_unique>(); + ByteBufferStreamWriter stream(encoded.get()); + stream.WriteByte(0); + if (result) { + serializer.WriteValue(*result, &stream); + } else { + serializer.WriteValue(EncodableValue(), &stream); + } + return encoded; +} + +std::unique_ptr> +StandardMethodCodec::EncodeErrorEnvelopeInternal( + const std::string& error_code, + const std::string& error_message, + const EncodableValue* error_details) const { + StandardCodecSerializer serializer; + auto encoded = std::make_unique>(); + ByteBufferStreamWriter stream(encoded.get()); + stream.WriteByte(1); + serializer.WriteValue(EncodableValue(error_code), &stream); + if (error_message.empty()) { + serializer.WriteValue(EncodableValue(), &stream); + } else { + serializer.WriteValue(EncodableValue(error_message), &stream); + } + if (error_details) { + serializer.WriteValue(*error_details, &stream); + } else { + serializer.WriteValue(EncodableValue(), &stream); + } + return encoded; +} + +} // namespace flutter diff --git a/flutter/standard_codec_serializer.h b/flutter/standard_codec_serializer.h new file mode 100644 index 0000000..695895f --- /dev/null +++ b/flutter/standard_codec_serializer.h @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_ENCODABLE_VALUE_SERIALIZER_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_ENCODABLE_VALUE_SERIALIZER_H_ + +#include "byte_stream_wrappers.h" +#include "flutter/encodable_value.h" + +namespace flutter { + +// Encapsulates the logic for encoding/decoding EncodableValues to/from the +// standard codec binary representation. +class StandardCodecSerializer { + public: + StandardCodecSerializer(); + ~StandardCodecSerializer(); + + // Prevent copying. + StandardCodecSerializer(StandardCodecSerializer const&) = delete; + StandardCodecSerializer& operator=(StandardCodecSerializer const&) = delete; + + // Reads and returns the next value from |stream|. + EncodableValue ReadValue(ByteBufferStreamReader* stream) const; + + // Writes the encoding of |value| to |stream|. + void WriteValue(const EncodableValue& value, + ByteBufferStreamWriter* stream) const; + + protected: + // Reads the variable-length size from the current position in |stream|. + size_t ReadSize(ByteBufferStreamReader* stream) const; + + // Writes the variable-length size encoding to |stream|. + void WriteSize(size_t size, ByteBufferStreamWriter* stream) const; + + // Reads a fixed-type list whose values are of type T from the current + // position in |stream|, and returns it as the corresponding EncodableValue. + // |T| must correspond to one of the support list value types of + // EncodableValue. + template + EncodableValue ReadVector(ByteBufferStreamReader* stream) const; + + // Writes |vector| to |stream| as a fixed-type list. |T| must correspond to + // one of the support list value types of EncodableValue. + template + void WriteVector(const std::vector vector, + ByteBufferStreamWriter* stream) const; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_ENCODABLE_VALUE_SERIALIZER_H_ diff --git a/flutter/standard_message_codec.h b/flutter/standard_message_codec.h new file mode 100644 index 0000000..75644c7 --- /dev/null +++ b/flutter/standard_message_codec.h @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_MESSAGE_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_MESSAGE_CODEC_H_ + +#include "encodable_value.h" +#include "message_codec.h" + +namespace flutter { + +// A binary message encoding/decoding mechanism for communications to/from the +// Flutter engine via message channels. +class StandardMessageCodec : public MessageCodec { + public: + // Returns the shared instance of the codec. + static const StandardMessageCodec& GetInstance(); + + ~StandardMessageCodec(); + + // Prevent copying. + StandardMessageCodec(StandardMessageCodec const&) = delete; + StandardMessageCodec& operator=(StandardMessageCodec const&) = delete; + + protected: + // Instances should be obtained via GetInstance. + StandardMessageCodec(); + + // |flutter::MessageCodec| + std::unique_ptr DecodeMessageInternal( + const uint8_t* binary_message, + const size_t message_size) const override; + + // |flutter::MessageCodec| + std::unique_ptr> EncodeMessageInternal( + const EncodableValue& message) const override; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_MESSAGE_CODEC_H_ diff --git a/flutter/standard_method_codec.h b/flutter/standard_method_codec.h new file mode 100644 index 0000000..3b47411 --- /dev/null +++ b/flutter/standard_method_codec.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_METHOD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_METHOD_CODEC_H_ + +#include "encodable_value.h" +#include "method_call.h" +#include "method_codec.h" + +namespace flutter { + +// An implementation of MethodCodec that uses a binary serialization. +class StandardMethodCodec : public MethodCodec { + public: + // Returns the shared instance of the codec. + static const StandardMethodCodec& GetInstance(); + + ~StandardMethodCodec() = default; + + // Prevent copying. + StandardMethodCodec(StandardMethodCodec const&) = delete; + StandardMethodCodec& operator=(StandardMethodCodec const&) = delete; + + protected: + // Instances should be obtained via GetInstance. + StandardMethodCodec() = default; + + // |flutter::MethodCodec| + std::unique_ptr> DecodeMethodCallInternal( + const uint8_t* message, + const size_t message_size) const override; + + // |flutter::MethodCodec| + std::unique_ptr> EncodeMethodCallInternal( + const MethodCall& method_call) const override; + + // |flutter::MethodCodec| + std::unique_ptr> EncodeSuccessEnvelopeInternal( + const EncodableValue* result) const override; + + // |flutter::MethodCodec| + std::unique_ptr> EncodeErrorEnvelopeInternal( + const std::string& error_code, + const std::string& error_message, + const EncodableValue* error_details) const override; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_METHOD_CODEC_H_ diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..4ab292f --- /dev/null +++ b/src/constants.h @@ -0,0 +1,21 @@ +// Copyright 2020 Joel Winarske. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +namespace flutter { + +// Filenames +constexpr char kAotFileName[] = "libaot.so"; +constexpr char kICUDataFileName[] = "icudtl.dat"; +constexpr char kKernelBlobFileName[] = "kernel_blob.bin"; + +// Symbols +constexpr char kDartVmSnapshotInstructions[] = "_kDartVmSnapshotInstructions"; +constexpr char kDartIsolateSnapshotInstructions[] = + "_kDartIsolateSnapshotInstructions"; +constexpr char kDartVmSnapshotData[] = "_kDartVmSnapshotData"; +constexpr char kDartIsolateSnapshotData[] = "_kDartIsolateSnapshotData"; + +} // namespace flutter diff --git a/src/flutter_application.cc b/src/flutter_application.cc deleted file mode 100644 index 51f68ec..0000000 --- a/src/flutter_application.cc +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter_application.h" - -#include -#include - -#include -#include -#include - -#include "utils.h" - -namespace flutter { - -static_assert(FLUTTER_ENGINE_VERSION == 1, ""); - -static const char* kICUDataFileName = "icudtl.dat"; - -static std::string GetICUDataPath() { - auto exe_dir = GetExecutableDirectory(); - if (exe_dir == "") { - return ""; - } - std::stringstream stream; - stream << exe_dir << kICUDataFileName; - - auto icu_path = stream.str(); - - if (!FileExistsAtPath(icu_path.c_str())) { - FLWAY_ERROR << "Could not find " << icu_path << std::endl; - return ""; - } - - return icu_path; -} - -FlutterApplication::FlutterApplication( - std::string bundle_path, - const std::vector& command_line_args, - RenderDelegate& render_delegate) - : render_delegate_(render_delegate) { - if (!FlutterAssetBundleIsValid(bundle_path)) { - FLWAY_ERROR << "Flutter asset bundle was not valid." << std::endl; - return; - } - - FlutterRendererConfig config = {}; - config.type = kOpenGL; - config.open_gl.struct_size = sizeof(config.open_gl); - config.open_gl.make_current = [](void* userdata) -> bool { - return reinterpret_cast(userdata) - ->render_delegate_.OnApplicationContextMakeCurrent(); - }; - config.open_gl.clear_current = [](void* userdata) -> bool { - return reinterpret_cast(userdata) - ->render_delegate_.OnApplicationContextClearCurrent(); - }; - config.open_gl.present = [](void* userdata) -> bool { - return reinterpret_cast(userdata) - ->render_delegate_.OnApplicationPresent(); - }; - config.open_gl.fbo_callback = [](void* userdata) -> uint32_t { - return reinterpret_cast(userdata) - ->render_delegate_.OnApplicationGetOnscreenFBO(); - }; - config.open_gl.gl_proc_resolver = [](void* userdata, - const char* name) -> void* { - auto address = eglGetProcAddress(name); - if (address != nullptr) { - return reinterpret_cast(address); - } - FLWAY_ERROR << "Tried unsuccessfully to resolve: " << name << std::endl; - return nullptr; - }; - - auto icu_data_path = GetICUDataPath(); - - if (icu_data_path == "") { - FLWAY_ERROR << "Could not find ICU data. It should be placed next to the " - "executable but it wasn't there." - << std::endl; - return; - } - - std::vector command_line_args_c; - - for (const auto& arg : command_line_args) { - command_line_args_c.push_back(arg.c_str()); - } - - FlutterProjectArgs args = { - .struct_size = sizeof(FlutterProjectArgs), - .assets_path = bundle_path.c_str(), - .main_path = "", - .packages_path = "", - .icu_data_path = icu_data_path.c_str(), - .command_line_argc = static_cast(command_line_args_c.size()), - .command_line_argv = command_line_args_c.data(), - }; - - FlutterEngine engine = nullptr; - auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, - this /* userdata */, &engine_); - - if (result != kSuccess) { - FLWAY_ERROR << "Could not run the Flutter engine" << std::endl; - return; - } - - valid_ = true; -} - -FlutterApplication::~FlutterApplication() { - if (engine_ == nullptr) { - return; - } - - auto result = FlutterEngineShutdown(engine_); - - if (result != kSuccess) { - FLWAY_ERROR << "Could not shutdown the Flutter engine." << std::endl; - } -} - -bool FlutterApplication::IsValid() const { - return valid_; -} - -bool FlutterApplication::SetWindowSize(size_t width, size_t height) { - FlutterWindowMetricsEvent event = {}; - event.struct_size = sizeof(event); - event.width = width; - event.height = height; - event.pixel_ratio = 1.0; - return FlutterEngineSendWindowMetricsEvent(engine_, &event) == kSuccess; -} - -void FlutterApplication::ProcessEvents() { - __FlutterEngineFlushPendingTasksNow(); -} - -bool FlutterApplication::SendPointerEvent(int button, int x, int y) { - if (!valid_) { - FLWAY_ERROR << "Pointer events on an invalid application." << std::endl; - return false; - } - - // Simple hover event. Nothing to do. - if (last_button_ == 0 && button == 0) { - return true; - } - - FlutterPointerPhase phase = kCancel; - - if (last_button_ == 0 && button != 0) { - phase = kDown; - } else if (last_button_ == button) { - phase = kMove; - } else { - phase = kUp; - } - - last_button_ = button; - return SendFlutterPointerEvent(phase, x, y); -} - -bool FlutterApplication::SendFlutterPointerEvent(FlutterPointerPhase phase, - double x, - double y) { - FlutterPointerEvent event = {}; - event.struct_size = sizeof(event); - event.phase = phase; - event.x = x; - event.y = y; - event.timestamp = - std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); - return FlutterEngineSendPointerEvent(engine_, &event, 1) == kSuccess; -} - -} // namespace flutter diff --git a/src/flutter_application.h b/src/flutter_application.h deleted file mode 100644 index df648f2..0000000 --- a/src/flutter_application.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#pragma once - -#include - -#include -#include - -#include "macros.h" - -namespace flutter { - -class FlutterApplication { - public: - class RenderDelegate { - public: - virtual bool OnApplicationContextMakeCurrent() = 0; - - virtual bool OnApplicationContextClearCurrent() = 0; - - virtual bool OnApplicationPresent() = 0; - - virtual uint32_t OnApplicationGetOnscreenFBO() = 0; - }; - - FlutterApplication(std::string bundle_path, - const std::vector& args, - RenderDelegate& render_delegate); - - ~FlutterApplication(); - - bool IsValid() const; - - void ProcessEvents(); - - bool SetWindowSize(size_t width, size_t height); - - bool SendPointerEvent(int button, int x, int y); - - private: - bool valid_; - RenderDelegate& render_delegate_; - FlutterEngine engine_ = nullptr; - int last_button_ = 0; - - bool SendFlutterPointerEvent(FlutterPointerPhase phase, double x, double y); - - FLWAY_DISALLOW_COPY_AND_ASSIGN(FlutterApplication); -}; - -} // namespace flutter diff --git a/src/keyboard.cc b/src/keyboard.cc new file mode 100644 index 0000000..d78478c --- /dev/null +++ b/src/keyboard.cc @@ -0,0 +1,119 @@ +#include "keyboard.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "macros.h" + +namespace flutter { + +Keyboard::Keyboard(FlutterEngine engine, wayland::keyboard_t& keyb) + : engine_(engine), keyb_(keyb) { + static wayland::keyboard_keymap_format keyb_format = + wayland::keyboard_keymap_format::no_keymap; + static struct xkb_context* xkb_context = + xkb_context_new(XKB_CONTEXT_NO_FLAGS); + static struct xkb_keymap* keymap = NULL; + static struct xkb_state* xkb_state = NULL; + + keyb_.on_keymap() = [&](wayland::keyboard_keymap_format format, int fd, + uint32_t size) { + keyb_format = format; + if (format == wayland::keyboard_keymap_format::xkb_v1) { + char* keymap_string = reinterpret_cast( + mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)); + xkb_keymap_unref(keymap); + keymap = xkb_keymap_new_from_string(xkb_context, keymap_string, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_string, size); + close(fd); + xkb_state_unref(xkb_state); + xkb_state = xkb_state_new(keymap); + } + }; + + keyb_.on_key() = [&](uint32_t, uint32_t, uint32_t key, + wayland::keyboard_key_state state) { + if (keyb_format == wayland::keyboard_keymap_format::xkb_v1) { + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); + uint32_t utf32 = xkb_keysym_to_utf32(keysym); + if (utf32) { + static constexpr char kChannelName[] = "flutter/keyevent"; + + static constexpr char kKeyCodeKey[] = "keyCode"; + static constexpr char kKeyMapKey[] = "keymap"; + static constexpr char kLinuxKeyMap[] = "linux"; + static constexpr char kScanCodeKey[] = "scanCode"; + static constexpr char kModifiersKey[] = "modifiers"; + static constexpr char kTypeKey[] = "type"; + static constexpr char kToolkitKey[] = "toolkit"; + static constexpr char kGLFWKey[] = "glfw"; + static constexpr char kUnicodeScalarValues[] = "unicodeScalarValues"; + static constexpr char kKeyUp[] = "keyup"; + static constexpr char kKeyDown[] = "keydown"; + + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + document.AddMember(kKeyCodeKey, key, allocator); + document.AddMember(kKeyMapKey, kLinuxKeyMap, allocator); + document.AddMember(kScanCodeKey, key + 8, allocator); + document.AddMember(kModifiersKey, 0, allocator); + document.AddMember(kToolkitKey, kGLFWKey, allocator); + document.AddMember(kUnicodeScalarValues, utf32, allocator); + + switch (state) { + case wayland::keyboard_key_state::pressed: + document.AddMember(kTypeKey, kKeyDown, allocator); + break; + case wayland::keyboard_key_state::released: + document.AddMember(kTypeKey, kKeyUp, allocator); + break; + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + std::cout << buffer.GetString() << std::endl; + + FlutterPlatformMessage message = {}; + message.struct_size = sizeof(FlutterPlatformMessage); + message.channel = kChannelName; + message.message = reinterpret_cast(buffer.GetString()); + message.message_size = buffer.GetSize(); + message.response_handle = nullptr; + + auto result = FlutterEngineSendPlatformMessage(engine_, &message); + if (result != kSuccess) + FLWAY_LOG << "FlutterEngineSendPlatformMessage Result: " << result + << std::endl; + } else { + char name[64]; + xkb_keysym_get_name(keysym, name, 64); + FLWAY_LOG << "the key " << name << " was " + << ((state == wayland::keyboard_key_state::pressed) + ? "pressed" + : "released") + << std::endl; + } + } + }; + + keyb_.on_modifiers() = [&](uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, + 0, 0, group); + }; +} + +} // namespace flutter \ No newline at end of file diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..e543174 --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +namespace flutter { + +class Keyboard { + public: + Keyboard(FlutterEngine engine, wayland::keyboard_t& keyb); + + private: + FlutterEngine engine_; + wayland::keyboard_t keyb_; +}; + +} // namespace flutter \ No newline at end of file diff --git a/src/main.cc b/src/main.cc index f8ac472..3027e78 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,3 +1,4 @@ +// Copyright 2020 Joel Winarske. All rights reserved. // Copyright 2018 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,7 +8,6 @@ #include #include -#include "flutter_application.h" #include "utils.h" #include "wayland_display.h" @@ -17,7 +17,7 @@ static void PrintUsage() { std::cerr << "Flutter Wayland Embedder" << std::endl << std::endl; std::cerr << "========================" << std::endl; std::cerr << "Usage: `" << GetExecutableName() - << " `" << std::endl + << " `" << std::endl << std::endl; std::cerr << R"~( This utility runs an instance of a Flutter application and renders using @@ -25,18 +25,21 @@ Wayland core protocols. The Flutter tools can be obtained at https://flutter.io/ -asset_bundle_path: The Flutter application code needs to be snapshotted using - the Flutter tools and the assets packaged in the appropriate - location. This can be done for any Flutter application by - running `flutter build bundle` while in the directory of a - valid Flutter project. This should package all the code and - assets in the "build/flutter_assets" directory. Specify this - directory as the first argument to this utility. - - flutter_flags: Typically empty. These extra flags are passed directly to the - Flutter engine. To see all supported flags, run - `flutter_tester --help` using the test binary included in the - Flutter tools. +app_path: This either points to asset bundle path, or + an Ahead Of Time (AOT) shared library (.so). + +asset_path: The Flutter application code needs to be snapshotted using + the Flutter tools and the assets packaged in the appropriate + location. This can be done for any Flutter application by + running `flutter build bundle` while in the directory of a + valid Flutter project. This should package all the code and + assets in the "build/flutter_assets" directory. Specify this + directory as the first argument to this utility. + +flutter_flags: Typically empty. These extra flags are passed directly to the + Flutter engine. To see all supported flags, run + `flutter_tester --help` using the test binary included in the + Flutter tools. )~" << std::endl; } @@ -47,35 +50,35 @@ static bool Main(std::vector args) { return false; } - const auto asset_bundle_path = args[0]; + const auto assets_path = args[0]; - if (!FlutterAssetBundleIsValid(asset_bundle_path)) { - std::cerr << " " << std::endl; + if (!FlutterAssetsPathIsValid(assets_path)) { + std::cerr << " " << std::endl; PrintUsage(); return false; } - const size_t kWidth = 800; - const size_t kHeight = 600; + const size_t kWidth = 1280; + const size_t kHeight = 1024; for (const auto& arg : args) { - FLWAY_ERROR << "Arg: " << arg << std::endl; + FLWAY_LOG << "Arg: " << arg << std::endl; } - WaylandDisplay display(kWidth, kHeight); + WaylandDisplay display(kWidth, kHeight, args); if (!display.IsValid()) { FLWAY_ERROR << "Wayland display was not valid." << std::endl; return false; } - FlutterApplication application(asset_bundle_path, args, display); - if (!application.IsValid()) { + display.InitializeApplication(assets_path, args); + if (!display.IsValid()) { FLWAY_ERROR << "Flutter application was not valid." << std::endl; return false; } - if (!application.SetWindowSize(kWidth, kHeight)) { + if (!display.SetWindowSize(kWidth, kHeight)) { FLWAY_ERROR << "Could not update Flutter application size." << std::endl; return false; } diff --git a/src/platform_channel.cc b/src/platform_channel.cc new file mode 100644 index 0000000..682a19d --- /dev/null +++ b/src/platform_channel.cc @@ -0,0 +1,437 @@ +#include "platform_channel.h" + +#include +#include +#include +#include +#include + +#include + +#include "macros.h" + +namespace flutter { + +PlatformChannel::PlatformChannel() { + static constexpr char kAccessibilityChannel[] = "flutter/accessibility"; + static constexpr char kFlutterPlatformChannel[] = "flutter/platform"; + static constexpr char kTextInputChannel[] = "flutter/textinput"; + static constexpr char kKeyEventChannel[] = "flutter/keyevent"; + static constexpr char kFlutterPlatformViewsChannel[] = + "flutter/platform_views"; + + static constexpr char kPluginFlutterIoConnectivity[] = + "plugins.flutter.io/connectivity"; + static constexpr char kPluginFlutterIoConnectivityStatus[] = + "plugins.flutter.io/connectivity_status"; + static constexpr char kPluginFlutterIoUrlLauncher[] = + "plugins.flutter.io/url_launcher"; + static constexpr char kPluginFlutterIoVideoPlayer[] = + "flutter.io/videoPlayer"; + static constexpr char kPluginFlutterIoVideoPlayerEvents[] = + "flutter.io/videoPlayer/videoEventsnull"; + + platform_message_handlers_[kAccessibilityChannel] = + std::bind(&PlatformChannel::OnAccessibilityChannelPlatformMessage, this, + std::placeholders::_1); + platform_message_handlers_[kFlutterPlatformChannel] = + std::bind(&PlatformChannel::OnFlutterPlatformChannelPlatformMessage, this, + std::placeholders::_1); + platform_message_handlers_[kTextInputChannel] = + std::bind(&PlatformChannel::OnFlutterTextInputChannelPlatformMessage, + this, std::placeholders::_1); + platform_message_handlers_[kFlutterPlatformViewsChannel] = + std::bind(&PlatformChannel::OnFlutterPlatformViewsChannelPlatformMessage, + this, std::placeholders::_1); + platform_message_handlers_[kPluginFlutterIoConnectivity] = + std::bind(&PlatformChannel::OnFlutterPluginConnectivity, this, + std::placeholders::_1); + platform_message_handlers_[kPluginFlutterIoConnectivityStatus] = + std::bind(&PlatformChannel::OnFlutterPluginConnectivityStatus, this, + std::placeholders::_1); + platform_message_handlers_[kPluginFlutterIoUrlLauncher] = + std::bind(&PlatformChannel::OnFlutterPluginIoUrlLauncher, this, + std::placeholders::_1); + platform_message_handlers_[kPluginFlutterIoVideoPlayer] = + std::bind(&PlatformChannel::OnFlutterPluginIoVideoPlayer, this, + std::placeholders::_1); + platform_message_handlers_[kPluginFlutterIoVideoPlayerEvents] = + std::bind(&PlatformChannel::OnFlutterPluginIoVideoPlayerEvents, this, + std::placeholders::_1); +} + +void PlatformChannel::SetEngine(FlutterEngine engine) { + engine_ = engine; +} + +void PlatformChannel::PlatformMessageCallback( + const FlutterPlatformMessage* message) { + // Find the handler for the channel; if there isn't one, report the failure. + if (platform_message_handlers_.find(message->channel) == + platform_message_handlers_.end()) { + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + nullptr, 0); + return; + } + + auto& message_handler = platform_message_handlers_[message->channel]; + + message_handler(message); +} + +void PlatformChannel::OnAccessibilityChannelPlatformMessage( + const FlutterPlatformMessage* message) { + std::string msg; + msg.assign(reinterpret_cast(message->message), + message->message_size); + FLWAY_LOG << "AccessibilityChannel: " << msg << std::endl; +} + +void PlatformChannel::OnFlutterPlatformChannelPlatformMessage( + const FlutterPlatformMessage* message) { + rapidjson::Document document; + document.Parse(reinterpret_cast(message->message), + message->message_size); + if (document.HasParseError() || !document.IsObject()) { + return; + } + + auto root = document.GetObject(); + auto method = root.FindMember("method"); + if (method == root.MemberEnd() || !method->value.IsString()) { + return; + } + + std::string msg; + msg.assign(reinterpret_cast(message->message), + message->message_size); + FLWAY_LOG << "PlatformChannel: " << method->value.GetString() << std::endl; + +#if 0 + static constexpr char kSetApplicationSwitcherDescription[] = "SystemChrome.setApplicationSwitcherDescription"; + static constexpr char kSetSystemUiOverlayStyle[] = "SystemChrome.setSystemUIOverlayStyle"; + static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop"; + static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData"; + static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData"; + static constexpr char kSystemSoundPlay[] = "SystemSound.play"; + static constexpr char kTextPlainFormat[] = "text/plain"; + static constexpr char kTextKey[] = "text"; +#endif + + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + nullptr, 0); +} + +void PlatformChannel::OnFlutterTextInputChannelPlatformMessage( + const FlutterPlatformMessage* message) { + rapidjson::Document document; + document.Parse(reinterpret_cast(message->message), + message->message_size); + if (document.HasParseError() || !document.IsObject()) { + return; + } + auto root = document.GetObject(); + auto method = root.FindMember("method"); + if (method == root.MemberEnd() || !method->value.IsString()) { + return; + } + + std::string msg; + msg.assign(reinterpret_cast(message->message), + message->message_size); + FLWAY_LOG << "TextInput: " << method->value.GetString() << std::endl; + +#if 0 + if (method->value == "TextInput.show") { + if (ime_) { + text_sync_service_->ShowKeyboard(); + } + } else if (method->value == "TextInput.hide") { + if (ime_) { + text_sync_service_->HideKeyboard(); + } + } else + + if (method->value == "TextInput.setClient") { + current_text_input_client_ = 0; + DeactivateIme(); + auto args = root.FindMember("args"); + if (args == root.MemberEnd() || !args->value.IsArray() || + args->value.Size() != 2) + return; + const auto& configuration = args->value[1]; + if (!configuration.IsObject()) { + return; + } + // TODO(abarth): Read the keyboard type from the configuration. + current_text_input_client_ = args->value[0].GetInt(); + + auto initial_text_input_state = fuchsia::ui::input::TextInputState{}; + initial_text_input_state.text = ""; + last_text_state_ = std::make_unique( + initial_text_input_state); + ActivateIme(); + } else if (method->value == "TextInput.setEditingState") { + if (ime_) { + auto args_it = root.FindMember("args"); + if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { + return; + } + const auto& args = args_it->value; + fuchsia::ui::input::TextInputState state; + state.text = ""; + // TODO(abarth): Deserialize state. + auto text = args.FindMember("text"); + if (text != args.MemberEnd() && text->value.IsString()) + state.text = text->value.GetString(); + auto selection_base = args.FindMember("selectionBase"); + if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) + state.selection.base = selection_base->value.GetInt(); + auto selection_extent = args.FindMember("selectionExtent"); + if (selection_extent != args.MemberEnd() && + selection_extent->value.IsInt()) + state.selection.extent = selection_extent->value.GetInt(); + auto selection_affinity = args.FindMember("selectionAffinity"); + if (selection_affinity != args.MemberEnd() && + selection_affinity->value.IsString() && + selection_affinity->value == "TextAffinity.upstream") + state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM; + else + state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM; + // We ignore selectionIsDirectional because that concept doesn't exist on + // Fuchsia. + auto composing_base = args.FindMember("composingBase"); + if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) + state.composing.start = composing_base->value.GetInt(); + auto composing_extent = args.FindMember("composingExtent"); + if (composing_extent != args.MemberEnd() && + composing_extent->value.IsInt()) + state.composing.end = composing_extent->value.GetInt(); + ime_->SetState(std::move(state)); + } + } else if (method->value == "TextInput.clearClient") { + current_text_input_client_ = 0; + last_text_state_ = nullptr; + DeactivateIme(); + } else { + FLWAY_ERROR << "Unknown " << message->channel << " method " + << method->value.GetString(); + } +#endif +} + +void PlatformChannel::OnFlutterPlatformViewsChannelPlatformMessage( + const FlutterPlatformMessage* message) { + rapidjson::Document document; + document.Parse(reinterpret_cast(message->message), + message->message_size); + if (document.HasParseError() || !document.IsObject()) { + FLWAY_ERROR << "Could not parse document"; + return; + } + auto root = document.GetObject(); + auto method = root.FindMember("method"); + if (method == root.MemberEnd() || !method->value.IsString()) { + return; + } + + std::string msg; + msg.assign(reinterpret_cast(message->message), + message->message_size); + FLWAY_LOG << "PlatformViews: " << method->value.GetString() << std::endl; + + if (method->value == "View.enableWireframe") { + auto args_it = root.FindMember("args"); + if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { + FLWAY_ERROR << "No arguments found."; + return; + } + const auto& args = args_it->value; + + auto enable = args.FindMember("enable"); + if (!enable->value.IsBool()) { + FLWAY_ERROR << "Argument 'enable' is not a bool"; + return; + } + + FLWAY_LOG << "wireframe_enabled_callback goes here" << std::endl; + } else { + FLWAY_ERROR << "Unknown " << message->channel << " method " + << method->value.GetString(); + } +} + +void PlatformChannel::OnFlutterPluginIoUrlLauncher( + const FlutterPlatformMessage* message) { + std::unique_ptr> result; + auto codec = &flutter::StandardMethodCodec::GetInstance(); + auto method_call = + codec->DecodeMethodCall(message->message, message->message_size); + + if (method_call->method_name().compare("launch") == 0) { + std::string url; + if (method_call->arguments() && method_call->arguments()->IsMap()) { + const EncodableMap& arguments = method_call->arguments()->MapValue(); + auto url_it = arguments.find(EncodableValue("url")); + if (url_it != arguments.end()) { + url = url_it->second.StringValue(); + } + } + if (url.empty()) { + auto result = + codec->EncodeErrorEnvelope("argument_error", "No URL provided"); + goto done; + } + + pid_t pid = fork(); + if (pid == 0) { + execl("/usr/bin/xdg-open", "xdg-open", url.c_str(), nullptr); + exit(1); + } + int status = 0; + waitpid(pid, &status, 0); + if (status != 0) { + std::ostringstream error_message; + error_message << "Failed to open " << url << ": error " << status; + result = codec->EncodeErrorEnvelope("open_error", error_message.str()); + goto done; + } + auto val = EncodableValue(true); + result = codec->EncodeSuccessEnvelope(&val); + + } else if (method_call->method_name().compare("canLaunch") == 0) { + std::string url; + if (method_call->arguments() && method_call->arguments()->IsMap()) { + const EncodableMap& arguments = method_call->arguments()->MapValue(); + auto url_it = arguments.find(EncodableValue("url")); + if (url_it != arguments.end()) { + url = url_it->second.StringValue(); + } + } + if (url.empty()) { + result = codec->EncodeErrorEnvelope("argument_error", "No URL provided"); + goto done; + } + flutter::EncodableValue response( + (url.rfind("https:", 0) == 0) || (url.rfind("http:", 0) == 0) || + (url.rfind("ftp:", 0) == 0) || (url.rfind("file:", 0) == 0)); + result = codec->EncodeSuccessEnvelope(&response); + } +done: + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); +} + +void PlatformChannel::OnFlutterPluginIoVideoPlayerEvents( + const FlutterPlatformMessage* message) { + std::unique_ptr> result; + auto codec = &flutter::StandardMethodCodec::GetInstance(); + auto method_call = + codec->DecodeMethodCall(message->message, message->message_size); + FLWAY_LOG << "VideoPlayerEvents: " << method_call->method_name() << std::endl; + + if (method_call->method_name().compare("listen") == 0) { + } else if (method_call->method_name().compare("cancel") == 0) { + } + + flutter::EncodableValue val(true); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); +} + +void PlatformChannel::OnFlutterPluginIoVideoPlayer( + const FlutterPlatformMessage* message) { + std::unique_ptr> result; + auto codec = &flutter::StandardMethodCodec::GetInstance(); + auto method_call = + codec->DecodeMethodCall(message->message, message->message_size); + FLWAY_LOG << "VideoPlayer: " << method_call->method_name() << std::endl; + + if (method_call->method_name().compare("init") == 0) { + FLWAY_LOG << "Initialize Video Player here..." << std::endl; + flutter::EncodableValue val(true); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); + return; + } else if (method_call->method_name().compare("create") == 0) { + EncodableMap args = method_call->arguments()->MapValue(); + std::stringstream ss; + ss << "\n"; + for (auto it = args.cbegin(); it != args.cend(); ++it) { + ss << "\t" << it->first.StringValue() << " : [" + << (it->second.IsNull() ? "" : it->second.StringValue()) << "]\n"; + } + FLWAY_LOG << ss.str() << std::endl; + EncodableValue val(args); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); + return; + } else if (method_call->method_name().compare("dispose") == 0) { + FLWAY_LOG << "Terminate Video Player here..." << std::endl; + flutter::EncodableValue val(true); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); + return; + } + + flutter::EncodableValue val(false); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); +} + +void PlatformChannel::OnFlutterPluginConnectivityStatus( + const FlutterPlatformMessage* message) { + std::unique_ptr> result; + auto codec = &flutter::StandardMethodCodec::GetInstance(); + auto method_call = + codec->DecodeMethodCall(message->message, message->message_size); + FLWAY_LOG << "ConnectivityStatus: " << method_call->method_name() + << std::endl; + + flutter::EncodableValue val(true); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); +} + +void PlatformChannel::OnFlutterPluginConnectivity( + const FlutterPlatformMessage* message) { + std::unique_ptr> result; + auto codec = &flutter::StandardMethodCodec::GetInstance(); + auto method_call = + codec->DecodeMethodCall(message->message, message->message_size); + + if (method_call->method_name().compare("check") == 0) { + flutter::EncodableValue val("wifi"); + result = codec->EncodeSuccessEnvelope(&val); + FlutterEngineSendPlatformMessageResponse(engine_, message->response_handle, + result->data(), result->size()); + return; +#if 0 + wifi + mobile + none +#endif + } else if (method_call->method_name().compare("wifiName") == 0) { + } else if (method_call->method_name().compare("wifiBSSID") == 0) { + } else if (method_call->method_name().compare("wifiIPAddress") == 0) { + } else if (method_call->method_name().compare( + "requestLocationServiceAuthorization") == 0) { + } else if (method_call->method_name().compare( + "getLocationServiceAuthorization") == 0) { +#if 0 + notDetermined + restricted + denied + authorizedAlways + authorizedWhenInUse +#endif + } +} + +} // namespace flutter \ No newline at end of file diff --git a/src/platform_channel.h b/src/platform_channel.h new file mode 100644 index 0000000..1be0904 --- /dev/null +++ b/src/platform_channel.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include +#include + +namespace flutter { + +class PlatformChannel { + public: + PlatformChannel(); + void PlatformMessageCallback(const FlutterPlatformMessage* message); + void SetEngine(FlutterEngine engine); + + private: + FlutterEngine engine_; + std::map> + platform_message_handlers_; + + void OnAccessibilityChannelPlatformMessage(const FlutterPlatformMessage*); + void OnFlutterPlatformChannelPlatformMessage(const FlutterPlatformMessage*); + void OnFlutterTextInputChannelPlatformMessage(const FlutterPlatformMessage*); + void OnFlutterPlatformViewsChannelPlatformMessage( + const FlutterPlatformMessage*); + + void OnFlutterPluginIoUrlLauncher(const FlutterPlatformMessage*); + void OnFlutterPluginConnectivity(const FlutterPlatformMessage*); + void OnFlutterPluginConnectivityStatus(const FlutterPlatformMessage*); + void OnFlutterPluginIoVideoPlayer(const FlutterPlatformMessage*); + void OnFlutterPluginIoVideoPlayerEvents(const FlutterPlatformMessage*); +}; + +} // namespace flutter \ No newline at end of file diff --git a/src/utils.cc b/src/utils.cc index 6ef60ab..ec61718 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,3 +1,4 @@ +// Copyright 2020 Joel Winarske. All rights reserved. // Copyright 2018 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -42,21 +43,41 @@ std::string GetExecutableDirectory() { return path_string.substr(0, found + 1); } +std::string GetAotFilepath(const std::string& path) { + return path + std::string{"/"} + std::string{kAotFileName}; +} + bool FileExistsAtPath(const std::string& path) { return ::access(path.c_str(), R_OK) == 0; } -bool FlutterAssetBundleIsValid(const std::string& bundle_path) { - if (!FileExistsAtPath(bundle_path)) { - FLWAY_ERROR << "Bundle directory does not exist." << std::endl; +bool FlutterAotPresent(const std::string& path) { + if (!FileExistsAtPath(path)) { + FLWAY_ERROR << "Asset directory does not exist." << std::endl; + return false; + } + + if (!FileExistsAtPath(path + std::string{"/"} + std::string{kAotFileName})) { return false; } - if (!FileExistsAtPath(bundle_path + std::string{"/kernel_blob.bin"})) { - FLWAY_ERROR << "Kernel blob does not exist." << std::endl; + return true; +} + +bool FlutterAssetsPathIsValid(const std::string& path) { + if (!FileExistsAtPath(path)) { + FLWAY_ERROR << "Asset directory does not exist." << std::endl; return false; } + if (!FileExistsAtPath(path + std::string{"/"} + std::string{kAotFileName})) { + if (!FileExistsAtPath(path + std::string{"/"} + + std::string{kKernelBlobFileName})) { + FLWAY_ERROR << "Kernel blob does not exist." << std::endl; + return false; + } + } + return true; } diff --git a/src/utils.h b/src/utils.h index 0cd4a00..d3f173d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,9 +1,11 @@ +// Copyright 2020 Joel Winarske. All rights reserved. // Copyright 2018 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #pragma once +#include "constants.h" #include "macros.h" namespace flutter { @@ -12,8 +14,12 @@ std::string GetExecutableName(); std::string GetExecutableDirectory(); +std::string GetAotFilepath(const std::string& path); + bool FileExistsAtPath(const std::string& path); -bool FlutterAssetBundleIsValid(const std::string& bundle_path); +bool FlutterAotPresent(const std::string& path); + +bool FlutterAssetsPathIsValid(const std::string& path); } // namespace flutter diff --git a/src/wayland_display.cc b/src/wayland_display.cc index a14c90c..f6fa768 100644 --- a/src/wayland_display.cc +++ b/src/wayland_display.cc @@ -1,398 +1,599 @@ +// Copyright 2020 Joel Winarske. All rights reserved. // Copyright 2018 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef WL_EGL_PLATFORM -#define WL_EGL_PLATFORM 1 -#endif - #include "wayland_display.h" -#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "utils.h" namespace flutter { -#define DISPLAY reinterpret_cast(data) - -const wl_registry_listener WaylandDisplay::kRegistryListener = { - .global = [](void* data, - struct wl_registry* wl_registry, - uint32_t name, - const char* interface, - uint32_t version) -> void { - DISPLAY->AnnounceRegistryInterface(wl_registry, name, interface, version); - }, - - .global_remove = - [](void* data, struct wl_registry* wl_registry, uint32_t name) -> void { - DISPLAY->UnannounceRegistryInterface(wl_registry, name); - }, -}; - -const wl_shell_surface_listener WaylandDisplay::kShellSurfaceListener = { - .ping = [](void* data, - struct wl_shell_surface* wl_shell_surface, - uint32_t serial) -> void { - wl_shell_surface_pong(DISPLAY->shell_surface_, serial); - }, - - .configure = [](void* data, - struct wl_shell_surface* wl_shell_surface, - uint32_t edges, - int32_t width, - int32_t height) -> void { - FLWAY_ERROR << "Unhandled resize." << std::endl; - }, - - .popup_done = [](void* data, - struct wl_shell_surface* wl_shell_surface) -> void { - // Nothing to do. - }, -}; - -WaylandDisplay::WaylandDisplay(size_t width, size_t height) - : screen_width_(width), screen_height_(height) { - if (screen_width_ == 0 || screen_height_ == 0) { - FLWAY_ERROR << "Invalid screen dimensions." << std::endl; - return; +static std::string GetICUDataPath() { + auto exe_dir = GetExecutableDirectory(); + if (exe_dir == "") { + return ""; } + std::stringstream stream; + stream << exe_dir << kICUDataFileName; - display_ = wl_display_connect(nullptr); + auto icu_path = stream.str(); - if (!display_) { - FLWAY_ERROR << "Could not connect to the wayland display." << std::endl; - return; + if (!FileExistsAtPath(icu_path.c_str())) { + FLWAY_ERROR << "Could not find " << icu_path << std::endl; + return ""; } - registry_ = wl_display_get_registry(display_); - if (!registry_) { - FLWAY_ERROR << "Could not get the wayland registry." << std::endl; - return; - } + return icu_path; +} - wl_registry_add_listener(registry_, &kRegistryListener, this); +void WaylandDisplay::InitializeApplication( + std::string assets_path, + const std::vector& command_line_args) { + FlutterRendererConfig config = {}; + config.type = kOpenGL; + config.open_gl.struct_size = sizeof(config.open_gl); + config.open_gl.make_current = [](void* context) -> bool { + return reinterpret_cast(context)->GLMakeCurrent(); + }; + config.open_gl.clear_current = [](void* context) -> bool { + return reinterpret_cast(context)->GLClearCurrent(); + }; + config.open_gl.present = [](void* context) -> bool { + return reinterpret_cast(context)->GLPresent(); + }; + config.open_gl.fbo_callback = [](void* context) -> uint32_t { + return reinterpret_cast(context)->GLFboCallback(); + }; + config.open_gl.make_resource_current = [](void* context) -> bool { + return reinterpret_cast(context)->GLMakeResourceCurrent(); + }; + config.open_gl.gl_proc_resolver = [](void* context, + const char* name) -> void* { + auto address = eglGetProcAddress(name); + if (address != nullptr) { + return reinterpret_cast(address); + } + FLWAY_ERROR << "Tried unsuccessfully to resolve: " << name << std::endl; + return nullptr; + }; +#if 0 + config.open_gl.gl_external_texture_frame_callback = + [](void* context, int64_t texture_id, size_t width, size_t height, + FlutterOpenGLTexture* texture) -> bool { + return reinterpret_cast(context) + ->GLExternalTextureFrameCallback(texture_id, width, height, texture); + }; +#endif - wl_display_roundtrip(display_); + auto icu_data_path = GetICUDataPath(); - if (!SetupEGL()) { - FLWAY_ERROR << "Could not setup EGL." << std::endl; + if (icu_data_path == "") { + FLWAY_ERROR << "Could not find ICU data. It should be placed next to the " + "executable but it wasn't there." + << std::endl; return; } - valid_ = true; -} + std::vector command_line_args_c; -WaylandDisplay::~WaylandDisplay() { - if (shell_surface_) { - wl_shell_surface_destroy(shell_surface_); - shell_surface_ = nullptr; + for (const auto& arg : command_line_args) { + command_line_args_c.push_back(arg.c_str()); } - if (shell_) { - wl_shell_destroy(shell_); - shell_ = nullptr; - } - - if (egl_surface_) { - eglDestroySurface(egl_display_, egl_surface_); - egl_surface_ = nullptr; + FlutterProjectArgs args = {}; + args.struct_size = sizeof(FlutterProjectArgs); + args.assets_path = assets_path.c_str(); + load_aot = FlutterAotPresent(assets_path); + std::cout << "assets_path: " << assets_path << std::endl; + std::cout << "load_aot: " << load_aot << std::endl; + if (load_aot && !InitializeAot(assets_path, args)) { + FLWAY_ERROR << "Could not load AOT image" << std::endl; + valid_ = false; + return; } + args.icu_data_path = icu_data_path.c_str(); + args.command_line_argc = static_cast(command_line_args_c.size()); + args.command_line_argv = command_line_args_c.data(); + args.platform_message_callback = [](const FlutterPlatformMessage* message, + void* context) { + reinterpret_cast(context) + ->platform_channel_.PlatformMessageCallback(message); + }; - if (egl_display_) { - eglTerminate(egl_display_); - egl_display_ = nullptr; - } + // Configure task runner interop + FlutterTaskRunnerDescription platform_task_runner = {}; + platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); + platform_task_runner.user_data = this; + platform_task_runner.runs_task_on_current_thread_callback = + [](void* context) -> bool { return true; }; + platform_task_runner.post_task_callback = + [](FlutterTask task, uint64_t target_time, void* context) -> void { + reinterpret_cast(context)->PostTaskCallback(task, + target_time); + }; - if (window_) { - wl_egl_window_destroy(window_); - window_ = nullptr; - } + FlutterCustomTaskRunners custom_task_runners = {}; + custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); + custom_task_runners.platform_task_runner = &platform_task_runner; + args.custom_task_runners = &custom_task_runners; - if (surface_) { - wl_surface_destroy(surface_); - surface_ = nullptr; - } + // register platform channel handlers here - if (compositor_) { - wl_compositor_destroy(compositor_); - compositor_ = nullptr; + FlutterEngine engine = nullptr; + auto result = FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &config, &args, + this, &engine_); + if (result != kSuccess) { + FLWAY_ERROR << "Could not Initialize the Flutter engine" << std::endl; + return; } + platform_channel_.SetEngine(engine_); - if (registry_) { - wl_registry_destroy(registry_); - registry_ = nullptr; + result = FlutterEngineRunInitialized(engine_); + if (result != kSuccess) { + FLWAY_ERROR << "Could not run the initialized Flutter engine" << std::endl; + return; } - if (display_) { - wl_display_flush(display_); - wl_display_disconnect(display_); - display_ = nullptr; - } + valid_ = true; } bool WaylandDisplay::IsValid() const { return valid_; } -bool WaylandDisplay::Run() { - if (!valid_) { - FLWAY_ERROR << "Could not run an invalid display." << std::endl; - return false; - } - - while (valid_) { - wl_display_dispatch(display_); - } - - return true; +bool WaylandDisplay::SetWindowSize(size_t width, size_t height) { + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = width; + event.height = height; + event.pixel_ratio = 1.0; + return FlutterEngineSendWindowMetricsEvent(engine_, &event) == kSuccess; } -static void LogLastEGLError() { - struct EGLNameErrorPair { - const char* name; - EGLint code; - }; - -#define _EGL_ERROR_DESC(a) \ - { #a, a } - - const EGLNameErrorPair pairs[] = { - _EGL_ERROR_DESC(EGL_SUCCESS), - _EGL_ERROR_DESC(EGL_NOT_INITIALIZED), - _EGL_ERROR_DESC(EGL_BAD_ACCESS), - _EGL_ERROR_DESC(EGL_BAD_ALLOC), - _EGL_ERROR_DESC(EGL_BAD_ATTRIBUTE), - _EGL_ERROR_DESC(EGL_BAD_CONTEXT), - _EGL_ERROR_DESC(EGL_BAD_CONFIG), - _EGL_ERROR_DESC(EGL_BAD_CURRENT_SURFACE), - _EGL_ERROR_DESC(EGL_BAD_DISPLAY), - _EGL_ERROR_DESC(EGL_BAD_SURFACE), - _EGL_ERROR_DESC(EGL_BAD_MATCH), - _EGL_ERROR_DESC(EGL_BAD_PARAMETER), - _EGL_ERROR_DESC(EGL_BAD_NATIVE_PIXMAP), - _EGL_ERROR_DESC(EGL_BAD_NATIVE_WINDOW), - _EGL_ERROR_DESC(EGL_CONTEXT_LOST), - }; - -#undef _EGL_ERROR_DESC - - const auto count = sizeof(pairs) / sizeof(EGLNameErrorPair); - - EGLint last_error = eglGetError(); - - for (size_t i = 0; i < count; i++) { - if (last_error == pairs[i].code) { - FLWAY_ERROR << "EGL Error: " << pairs[i].name << " (" << pairs[i].code - << ")" << std::endl; - return; - } +WaylandDisplay::WaylandDisplay(size_t width, + size_t height, + const std::vector& args) + : screen_width_(width), screen_height_(height), + has_keyboard(false), has_pointer(false), has_touch(false) + { + if (screen_width_ == 0 || screen_height_ == 0) { + FLWAY_ERROR << "Invalid screen dimensions." << std::endl; + return; } - FLWAY_ERROR << "Unknown EGL Error" << std::endl; -} + // retrieve global objects + registry = display.get_registry(); + registry.on_global() = [&](uint32_t name, std::string interface, + uint32_t version) { + if (interface == compositor_t::interface_name) + registry.bind(name, compositor, version); + else if (interface == shell_t::interface_name) + registry.bind(name, shell, version); + else if (interface == xdg_wm_base_t::interface_name) + registry.bind(name, xdg_wm_base, version); + else if (interface == seat_t::interface_name) + registry.bind(name, seat, version); + else if (interface == shm_t::interface_name) + registry.bind(name, shm, version); + }; + display.roundtrip(); -bool WaylandDisplay::SetupEGL() { - if (!compositor_ || !shell_) { - FLWAY_ERROR << "EGL setup needs missing compositor and shell connection." - << std::endl; - return false; + if (seat) { + seat.on_capabilities() = [&](seat_capability capability) { + has_keyboard = capability & seat_capability::keyboard; + has_pointer = capability & seat_capability::pointer; + has_touch = capability & seat_capability::touch; + }; } - surface_ = wl_compositor_create_surface(compositor_); + // create a surface + surface = compositor.create_surface(); - if (!surface_) { - FLWAY_ERROR << "Could not create compositor surface." << std::endl; - return false; - } + // create a shell surface + if (xdg_wm_base) { + xdg_wm_base.on_ping() = [&](uint32_t serial) { xdg_wm_base.pong(serial); }; + xdg_surface = xdg_wm_base.get_xdg_surface(surface); + xdg_surface.on_configure() = [&](uint32_t serial) { + xdg_surface.ack_configure(serial); + }; + xdg_toplevel = xdg_surface.get_toplevel(); + xdg_toplevel.set_title("Window"); + xdg_toplevel.on_close() = [&]() { running = false; }; + } else { + shell_surface = shell.get_shell_surface(surface); + shell_surface.on_ping() = [&](uint32_t serial) { + shell_surface.pong(serial); + }; + shell_surface.set_title("Flutter"); + shell_surface.set_toplevel(); + } + surface.commit(); + + display.roundtrip(); + + if (has_touch) { + std::cout << "Touch Present" << std::endl; + touch = seat.get_touch(); + static FlutterPointerPhase TouchPhase = kUp; + + touch.on_down() = [&](uint32_t serial, uint32_t time, surface_t surface, + int32_t id, double x, double y) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = kDown; + TouchPhase = event.phase; + cur_x = x; + cur_y = y; + event.x = x; + event.y = y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - shell_surface_ = wl_shell_get_shell_surface(shell_, surface_); + touch.on_up() = [&](uint32_t serial, uint32_t time, int32_t id) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = kUp; + TouchPhase = event.phase; + event.x = cur_x; + event.y = cur_y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - if (!shell_surface_) { - FLWAY_ERROR << "Could not shell surface." << std::endl; - return false; + touch.on_motion() = [&](uint32_t time, int32_t id, double x, double y) { + cur_x = x; + cur_y = y; + + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = (TouchPhase == kUp) ? kHover : kMove; + event.x = cur_x; + event.y = cur_y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; } - wl_shell_surface_add_listener(shell_surface_, &kShellSurfaceListener, this); - - wl_shell_surface_set_title(shell_surface_, "Flutter"); - - wl_shell_surface_set_toplevel(shell_surface_); + if (has_pointer) { + std::cout << "Pointer Present" << std::endl; + pointer = seat.get_pointer(); + static FlutterPointerPhase PointerPhase = kUp; + + pointer.on_enter() = [&](uint32_t serial, surface_t surface, + int32_t surface_x, int32_t surface_y) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = kAdd; + event.x = surface_x; + event.y = surface_y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - window_ = wl_egl_window_create(surface_, screen_width_, screen_height_); + pointer.on_leave() = [&](uint32_t serial, surface_t surface) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = kRemove; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - if (!window_) { - FLWAY_ERROR << "Could not create EGL window." << std::endl; - return false; - } + static bool button = false; + pointer.on_motion() = [&](uint32_t time, double surface_x, + double surface_y) { + cur_x = surface_x; + cur_y = surface_y; + + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = (PointerPhase == kUp) ? kHover : kMove; + event.x = surface_x; + event.y = surface_y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Could not bind the ES API." << std::endl; - return false; - } + pointer.on_button() = [&](uint32_t serial, uint32_t time, uint32_t button, + pointer_button_state state) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = (state == pointer_button_state::pressed) ? kDown : kUp; + PointerPhase = event.phase; + switch (button) { + case BTN_LEFT: + event.buttons = kFlutterPointerButtonMousePrimary; + break; + case BTN_RIGHT: + event.buttons = kFlutterPointerButtonMouseSecondary; + break; + case BTN_MIDDLE: + event.buttons = kFlutterPointerButtonMouseMiddle; + break; + } + event.x = cur_x; + event.y = cur_y; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent(engine_, &event, 1); + }; - egl_display_ = eglGetDisplay(display_); - if (egl_display_ == EGL_NO_DISPLAY) { - LogLastEGLError(); - FLWAY_ERROR << "Could not access EGL display." << std::endl; - return false; + pointer.on_axis() = [&](uint32_t time, pointer_axis axis, double value) { + std::cout << "OnAxis time: " << time + << " axis: " << static_cast(axis) + << " value: " << value << std::endl; + }; } - if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Could not initialize EGL display." << std::endl; - return false; + if (has_keyboard) { + std::cout << "Keyboard Present" << std::endl; } - EGLConfig egl_config = nullptr; + // intitialize egl + egl_window = egl_window_t(surface, screen_width_, screen_height_); + init_egl(); - // Choose an EGL config to use for the surface and context. - { - EGLint attribs[] = { - // clang-format off - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_STENCIL_SIZE, 0, - EGL_NONE, // termination sentinel - // clang-format on - }; + valid_ = true; +} - EGLint config_count = 0; +WaylandDisplay::~WaylandDisplay() noexcept(false) { + if (engine_ != nullptr) { + auto result = FlutterEngineShutdown(engine_); - if (eglChooseConfig(egl_display_, attribs, &egl_config, 1, &config_count) != - EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Error when attempting to choose an EGL surface config." - << std::endl; - return false; + if (result != kSuccess) { + FLWAY_ERROR << "Could not shutdown the Flutter engine." << std::endl; } - - if (config_count == 0 || egl_config == nullptr) { - LogLastEGLError(); - FLWAY_ERROR << "No matching configs." << std::endl; - return false; + if (aot_handle) { + dlclose(aot_handle); } } - // Create an EGL window surface with the matched config. - { - const EGLint attribs[] = {EGL_NONE}; + // finialize EGL + if (eglDestroyContext(egldisplay, eglcontext) == EGL_FALSE) + throw std::runtime_error("eglDestroyContext"); + if (eglDestroyContext(egldisplay, eglresourcecontext) == EGL_FALSE) + throw std::runtime_error("eglDestroyContext Resource"); + if (eglTerminate(egldisplay) == EGL_FALSE) + throw std::runtime_error("eglTerminate"); +} + +void WaylandDisplay::init_egl() { + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) + throw std::runtime_error("eglBindAPI"); - egl_surface_ = - eglCreateWindowSurface(egl_display_, egl_config, window_, attribs); + egldisplay = eglGetDisplay(display); + if (egldisplay == EGL_NO_DISPLAY) + throw std::runtime_error("No EGL Display.."); - if (surface_ == EGL_NO_SURFACE) { - LogLastEGLError(); - FLWAY_ERROR << "EGL surface was null during surface selection." - << std::endl; - return false; - } + EGLint major, minor; + if (eglInitialize(egldisplay, &major, &minor) == EGL_FALSE) + throw std::runtime_error("eglInitialize"); + if (!((major == 1 && minor >= 4) || major >= 2)) + throw std::runtime_error("EGL version too old"); + + std::array config_attribs = {{ + // clang-format off + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_NONE // termination sentinel + // clang-format on + }}; + + EGLConfig config; + EGLint num; + if (eglChooseConfig(egldisplay, config_attribs.data(), &config, 1, &num) == + EGL_FALSE || + num == 0) + throw std::runtime_error("eglChooseConfig"); + + eglsurface = eglCreateWindowSurface(egldisplay, config, egl_window, nullptr); + if (eglsurface == EGL_NO_SURFACE) + throw std::runtime_error("eglCreateWindowSurface"); + + std::array context_attribs = { + {EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_NONE}}; + + eglcontext = eglCreateContext(egldisplay, config, EGL_NO_CONTEXT, + context_attribs.data()); + if (eglcontext == EGL_NO_CONTEXT) + throw std::runtime_error("eglCreateContext"); + + eglresourcecontext = + eglCreateContext(egldisplay, config, eglcontext, context_attribs.data()); + if (eglresourcecontext == EGL_NO_CONTEXT) + throw std::runtime_error("eglCreateContext"); +} + +bool WaylandDisplay::Run() { + if (!valid_) { + FLWAY_ERROR << "Could not run an invalid display." << std::endl; + return false; } - // Create an EGL context with the match config. - { - const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + running = true; - egl_context_ = eglCreateContext(egl_display_, egl_config, - nullptr /* share group */, attribs); + // event loop + while (running && valid_) { + display.dispatch(); - if (egl_context_ == EGL_NO_CONTEXT) { - LogLastEGLError(); - FLWAY_ERROR << "Could not create an onscreen context." << std::endl; - return false; + if (!TaskRunner.empty()) { + uint64_t current = FlutterEngineGetCurrentTime(); + if (current >= TaskRunner.top().first) { + auto item = TaskRunner.top(); + TaskRunner.pop(); + auto result = FlutterEngineRunTask(engine_, &item.second); + } } } return true; } -void WaylandDisplay::AnnounceRegistryInterface(struct wl_registry* wl_registry, - uint32_t name, - const char* interface_name, - uint32_t version) { - if (strcmp(interface_name, "wl_compositor") == 0) { - compositor_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); - return; +bool WaylandDisplay::GLMakeCurrent() { + if (!valid_) { + FLWAY_ERROR << "Invalid display." << std::endl; + return false; } - if (strcmp(interface_name, "wl_shell") == 0) { - shell_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_shell_interface, 1)); - return; - } + return (eglMakeCurrent(egldisplay, eglsurface, eglsurface, eglcontext) == + EGL_TRUE); } -void WaylandDisplay::UnannounceRegistryInterface( - struct wl_registry* wl_registry, - uint32_t name) {} - -// |flutter::FlutterApplication::RenderDelegate| -bool WaylandDisplay::OnApplicationContextMakeCurrent() { +bool WaylandDisplay::GLClearCurrent() { if (!valid_) { FLWAY_ERROR << "Invalid display." << std::endl; return false; } - if (eglMakeCurrent(egl_display_, egl_surface_, egl_surface_, egl_context_) != - EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Could not make the onscreen context current" << std::endl; + return (eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT) == EGL_TRUE); +} + +bool WaylandDisplay::GLPresent() { + if (!valid_) { + FLWAY_ERROR << "Invalid display." << std::endl; return false; } - return true; + return (eglSwapBuffers(egldisplay, eglsurface) == EGL_TRUE); } -// |flutter::FlutterApplication::RenderDelegate| -bool WaylandDisplay::OnApplicationContextClearCurrent() { +uint32_t WaylandDisplay::GLFboCallback() { if (!valid_) { FLWAY_ERROR << "Invalid display." << std::endl; - return false; + return 999; } - if (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT) != EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Could not clear the context." << std::endl; + return 0; // FBO0 +} + +bool WaylandDisplay::GLMakeResourceCurrent() { + if (!valid_) { + FLWAY_ERROR << "Invalid display." << std::endl; return false; } + return (eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + eglresourcecontext) == EGL_TRUE); +} + +bool WaylandDisplay::GLExternalTextureFrameCallback( + int64_t texture_id, + size_t width, + size_t height, + FlutterOpenGLTexture* texture) { return true; } -// |flutter::FlutterApplication::RenderDelegate| -bool WaylandDisplay::OnApplicationPresent() { - if (!valid_) { - FLWAY_ERROR << "Invalid display." << std::endl; +void WaylandDisplay::PostTaskCallback(FlutterTask task, uint64_t target_time) { + TaskRunner.push(std::make_pair(target_time, task)); +} + +bool WaylandDisplay::InitializeAot(std::string& assets_path, + FlutterProjectArgs& args) { + auto file = GetAotFilepath(assets_path); + + std::cout << "Opening " << file << "..." << std::endl; + aot_handle = dlopen(file.c_str(), RTLD_LAZY); + + if (!aot_handle) { + std::cerr << "Cannot open " << dlerror() << std::endl; return false; } - if (eglSwapBuffers(egl_display_, egl_surface_) != EGL_TRUE) { - LogLastEGLError(); - FLWAY_ERROR << "Could not swap the EGL buffer." << std::endl; + dlerror(); + uint8_t* DartVmSnapshotInst = + (uint8_t*)dlsym(aot_handle, kDartVmSnapshotInstructions); + char* dlsym_error = dlerror(); + if (dlsym_error) { + std::cerr << "Cannot load symbol '" << kDartVmSnapshotInstructions + << "': " << dlsym_error << std::endl; + dlclose(aot_handle); + aot_handle = nullptr; return false; } - return true; -} + dlerror(); + uint8_t* DartIsolateSnapshotInst = + (uint8_t*)dlsym(aot_handle, kDartIsolateSnapshotInstructions); + dlsym_error = dlerror(); + if (dlsym_error) { + std::cerr << "Cannot load symbol '" << kDartIsolateSnapshotInstructions + << "': " << dlsym_error << std::endl; + dlclose(aot_handle); + aot_handle = nullptr; + return false; + } -// |flutter::FlutterApplication::RenderDelegate| -uint32_t WaylandDisplay::OnApplicationGetOnscreenFBO() { - if (!valid_) { - FLWAY_ERROR << "Invalid display." << std::endl; - return 999; + dlerror(); + uint8_t* DartVmSnapshotData = + (uint8_t*)dlsym(aot_handle, kDartVmSnapshotData); + dlsym_error = dlerror(); + if (dlsym_error) { + std::cerr << "Cannot load symbol '" << kDartVmSnapshotData + << "': " << dlsym_error << std::endl; + dlclose(aot_handle); + aot_handle = nullptr; + return false; } - return 0; // FBO0 + dlerror(); + uint8_t* DartIsolateSnapshotData = + (uint8_t*)dlsym(aot_handle, kDartIsolateSnapshotData); + dlsym_error = dlerror(); + if (dlsym_error) { + std::cerr << "Cannot load symbol '" << kDartIsolateSnapshotData + << "': " << dlsym_error << std::endl; + dlclose(aot_handle); + aot_handle = nullptr; + return false; + } + + args.vm_snapshot_data = DartVmSnapshotData; + args.vm_snapshot_instructions = DartVmSnapshotInst; + args.isolate_snapshot_instructions = DartIsolateSnapshotInst; + args.isolate_snapshot_data = DartIsolateSnapshotData; + + return true; } } // namespace flutter diff --git a/src/wayland_display.h b/src/wayland_display.h index 04832cb..108fd12 100644 --- a/src/wayland_display.h +++ b/src/wayland_display.h @@ -1,73 +1,121 @@ +// Copyright 2020 Joel Winarske. All rights reserved. // Copyright 2018 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #pragma once -#include -#include -#include - -#include -#include - -#include "flutter_application.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keyboard.h" #include "macros.h" +#include "platform_channel.h" + +using namespace wayland; namespace flutter { -class WaylandDisplay : public FlutterApplication::RenderDelegate { +class WaylandDisplay { public: - WaylandDisplay(size_t width, size_t height); + WaylandDisplay(size_t width, + size_t height, + const std::vector& args); - ~WaylandDisplay(); + virtual ~WaylandDisplay() noexcept(false); bool IsValid() const; + void InitializeApplication(std::string assets_path, + const std::vector& args); + + bool SetWindowSize(size_t width, size_t height); + bool Run(); private: - static const wl_registry_listener kRegistryListener; - static const wl_shell_surface_listener kShellSurfaceListener; + // global objects + display_t display; + registry_t registry; + compositor_t compositor; + shell_t shell; + xdg_wm_base_t xdg_wm_base; + seat_t seat; + shm_t shm; + + // local objects + surface_t surface; + shell_surface_t shell_surface; + xdg_surface_t xdg_surface; + xdg_toplevel_t xdg_toplevel; + pointer_t pointer; + Keyboard* keyboard; + touch_t touch; + + // EGL + egl_window_t egl_window; + EGLDisplay egldisplay; + EGLSurface eglsurface; + EGLContext eglcontext; + EGLContext eglresourcecontext; + + bool running; + bool has_pointer; + bool has_keyboard; + bool has_touch; + bool valid_ = false; const int screen_width_; const int screen_height_; - wl_display* display_ = nullptr; - wl_registry* registry_ = nullptr; - wl_compositor* compositor_ = nullptr; - wl_shell* shell_ = nullptr; - wl_shell_surface* shell_surface_ = nullptr; - wl_surface* surface_ = nullptr; - wl_egl_window* window_ = nullptr; - EGLDisplay egl_display_ = EGL_NO_DISPLAY; - EGLSurface egl_surface_ = nullptr; - EGLContext egl_context_ = EGL_NO_CONTEXT; - - bool SetupEGL(); - - void AnnounceRegistryInterface(struct wl_registry* wl_registry, - uint32_t name, - const char* interface, - uint32_t version); - - void UnannounceRegistryInterface(struct wl_registry* wl_registry, - uint32_t name); - - bool StopRunning(); - - // |flutter::FlutterApplication::RenderDelegate| - bool OnApplicationContextMakeCurrent() override; - - // |flutter::FlutterApplication::RenderDelegate| - bool OnApplicationContextClearCurrent() override; - - // |flutter::FlutterApplication::RenderDelegate| - bool OnApplicationPresent() override; - - // |flutter::FlutterApplication::RenderDelegate| - uint32_t OnApplicationGetOnscreenFBO() override; + int32_t cur_x; + int32_t cur_y; + + FlutterEngine engine_ = nullptr; + PlatformChannel platform_channel_; + void* aot_handle = nullptr; + bool load_aot = false; + int last_button_ = 0; + + void init_egl(); + + bool GLMakeCurrent(); + bool GLClearCurrent(); + bool GLPresent(); + uint32_t GLFboCallback(); + bool GLMakeResourceCurrent(); + bool GLExternalTextureFrameCallback(int64_t texture_id, + size_t width, + size_t height, + FlutterOpenGLTexture* texture); + + class CompareFlutterTask { + public: + bool operator()(std::pair n1, + std::pair n2) { + return n1.first > n2.first; + } + }; + std::priority_queue, + std::vector>, + CompareFlutterTask> + TaskRunner; + + void PostTaskCallback(FlutterTask task, uint64_t target_time); + + bool InitializeAot(std::string& assets_path, FlutterProjectArgs& args); FLWAY_DISALLOW_COPY_AND_ASSIGN(WaylandDisplay); -}; +}; // namespace flutter } // namespace flutter