diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ddd680ed..69b58791 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install libraries run: | diff --git a/.gitignore b/.gitignore index 4b17e1c8..d199025c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ /build/ +/src/third_party/wayland/protocols/* \ No newline at end of file diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..6a816263 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,20 @@ +# Below is a list of people and organizations that have contributed +# to sony/flutter-embedded-linux. Names should be added to the list +# like so: +# +# Name/Organization + +Sony Group Corporation +Hidenori Matsubayashi (hidenori.matsubayashi@gmail.com) +Andrea Daoud (andreadaoud6@gmail.com) +Valentin Hăloiu (valentin.haloiu@gmail.com) +FlafyDev +Makoto Sato (makoto.sato@atmark-techno.com) +Yunhao Tian (t123yh@outlook.com) +Luke Howard +Stanislav Shmarov +Sebastian Urban +Ómar Högni Guðmarsson +Athaariq Ardhiansyah +Anton Sakhon +Bari Rao diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fe0192a..a467410a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,16 +10,26 @@ set(CMAKE_CXX_STANDARD 17) project("flutter_elinux" LANGUAGES CXX C) # Build options. -option(BACKEND_TYPE "Select WAYLAND, DRM-GBM, DRM-EGLSTREAM, or X11 as the display backend type" WAYLAND) +set(BACKEND_TYPE "WAYLAND" CACHE STRING "Select WAYLAND, DRM-GBM, DRM-EGLSTREAM, or X11 as the display backend type") +set_property(CACHE BACKEND_TYPE PROPERTY STRINGS "WAYLAND" "DRM-GBM" "DRM-EGLSTREAM" "X11") +# Disabled USE_DIRTY_REGION_MANAGEMENT due flicker issue. +# See https://github.com/sony/flutter-embedded-linux/issues/334 +option(USE_DIRTY_REGION_MANAGEMENT "Use Flutter dirty region management" OFF) option(USE_GLES3 "Use OpenGL ES3 (default is OpenGL ES2)" OFF) +option(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER "Enable alpha component of the EGL color buffer" ON) + # todo: need to investigate https://github.com/sony/flutter-embedded-linux/pull/376 when enabling this option. +option(ENABLE_VSYNC "Enable embedder vsync" OFF) option(BUILD_ELINUX_SO "Build .so file of elinux embedder" OFF) option(ENABLE_ELINUX_EMBEDDER_LOG "Enable logger of eLinux embedder" ON) option(FLUTTER_RELEASE "Build Flutter Engine with release mode" OFF) if(NOT BUILD_ELINUX_SO) # Load the user project. - set(USER_PROJECT_PATH "examples/flutter-wayland-client" CACHE STRING "") + set(USER_PROJECT_PATH "" CACHE STRING "examples/flutter-wayland-client") message("User project: ${USER_PROJECT_PATH}") + if(NOT ${BACKEND_TYPE} STREQUAL "WAYLAND") + message(WARNING "BACKEND_TYPE variable is ignored because BUILD_ELINUX_SO is OFF") + endif() include(${USER_PROJECT_PATH}/cmake/user_config.cmake) else() # Set the filename of elinux .so file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4f2807de --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,2 @@ +# Contributing to flutter-embedded-linux +Welcome to this project. We welcome all your contribution and feedback. If you've never submitted code before, you must add your (or your organization's) name and contact info to the [AUTHORS](./AUTHORS) file. diff --git a/README.md b/README.md index 2244d108..c5c6bd64 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,16 @@ [![build-test](https://github.com/sony/flutter-embedded-linux/actions/workflows/build-test.yml/badge.svg)](https://github.com/sony/flutter-embedded-linux/actions/workflows/build-test.yml) -This project was created to develop **non-official** embedded Linux embeddings of [Flutter](https://flutter.dev/). This embedder is focusing on embedded Linux system use cases. It is also implemented based on Flutter desktop for Windows and has some unique features to use it in embedded systems. +This project was created to develop **non-official** embedded Linux embeddings of [Flutter](https://flutter.dev/). This embedder is focusing on embedded Linux (eLinux) system use cases. It is also implemented based on Flutter desktop for Windows and has some unique features to use it in embedded systems. -### flutter-elinux -Note that this project is the source code of the embedder. If you develop your flutter app for eLinux, use [flutter-elinux](https://github.com/sony/flutter-elinux), which is a non-official extension to the [Flutter SDK](https://github.com/flutter/flutter) to build and debug Flutter apps for embedded Linux devices. +If you develop flutter apps for eLinux, use [flutter-elinux](https://github.com/sony/flutter-elinux), which is a non-official extension to the [Flutter SDK](https://github.com/flutter/flutter) to build and debug Flutter apps for embedded Linux devices. + +### Repositories + +- [flutter-elinux](https://github.com/sony/flutter-elinux): Flutter tools for eLinux +- [flutter-elinux-plugins](https://github.com/sony/flutter-elinux-plugins): Flutter plugins for eLinux +- [flutter-embedded-linux](https://github.com/sony/flutter-embedded-linux): eLinux embedding for Flutter +- [meta-flutter](https://github.com/sony/meta-flutter): Yocto recipes of eLinux embedding for Flutter ## Objective & Goal Our objective is to use Flutter in embedded systems. We're developing this embedder to use Flutter in embedded products. Ultimately we would like to propose and contribute this software to the mainline of [Flutter Engine](https://github.com/flutter/engine), which means we would like to add an embedded systems version into the Flutter repo for all embedded developers. Please note that this is just our ideal, not the official opinion of the Flutter community. @@ -30,48 +36,8 @@ We would be grateful if you could give us feedback on bugs and new feature reque - API compatibility with Flutter desktop for Windows and GLFW - APIs such as MethodChannel and EventChannel are completely the same with them -## Companion repos -| Repo | Purpose | -| ------------- | ------------- | -| [flutter-elinux](https://github.com/sony/flutter-elinux) | Flutter tools for eLinux | -| [flutter-elinux-plugins](https://github.com/sony/flutter-elinux-plugins) | Flutter plugins for eLinux | -| [meta-flutter](https://github.com/sony/meta-flutter) | Yocto recipes of eLinux embedding for Flutter | - ## Documentation Documentation for this software can be found at [Wiki](https://github.com/sony/flutter-embedded-linux/wiki). ## Supported platforms -This embedder supports x64 and Arm64 (aarch64, ARMv8) architectures on Linux which supports either Wayland backend or DRM backend. - -### Tested devices -| Board / SoC | Vendor | OS / BSP | Backend | Status | -| :-------------: | :-------------: | :-------------: | :-------------: | :-------------: | -| [Jetson Nano](https://developer.nvidia.com/embedded/jetson-nano-developer-kit) | NVIDIA | JetPack 4.3 | Wayland | :heavy_check_mark: | -| [Jetson Nano](https://developer.nvidia.com/embedded/jetson-nano-developer-kit) | NVIDIA | JetPack 4.3 | DRM | :heavy_check_mark: ([#1](https://github.com/sony/flutter-embedded-linux/issues/1))| -| [Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) | Raspberry Pi Foundation | Ubuntu 20.10 | Wayland | :heavy_check_mark: | -| [Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) | Raspberry Pi Foundation | Ubuntu 20.10 | DRM | :heavy_check_mark: ([#9](https://github.com/sony/flutter-embedded-linux/issues/9)) | -| [i.MX 8MQuad EVK](https://www.nxp.com/design/development-boards/i-mx-evaluation-and-development-boards/evaluation-kit-for-the-i-mx-8m-applications-processor:MCIMX8M-EVK) | NXP | Sumo (kernel 4.14.98) | Wayland | :heavy_check_mark: | -| [i.MX 8M Mini EVKB](https://www.nxp.com/design/development-boards/i-mx-evaluation-and-development-boards/evaluation-kit-for-the-i-mx-8m-mini-applications-processor:8MMINILPD4-EVK) | NXP | Zeus (kernel 5.4.70) | Wayland | :heavy_check_mark: | -| [RB5 Development Kit](https://developer.qualcomm.com/qualcomm-robotics-rb5-kit) | Qualcomm | Ubuntu 18.04.05 | DRM | :heavy_check_mark: | -| Zynq | Xilinx | - | - | Not tested | -| Desktop (x86_64) | Intel | Ubuntu 20.04 | Wayland | :heavy_check_mark: | -| Desktop (x86_64) | Intel | Ubuntu 20.04 | DRM | :heavy_check_mark: | -| Desktop (x86_64) | Intel | Ubuntu 20.04 | X11 | :heavy_check_mark: | -| QEMU (x86_64) | QEMU | [AGL (Automotive Grade Linux)](https://wiki.automotivelinux.org/) koi | Wayland | :heavy_check_mark: | -| QEMU (x86_64) | QEMU | [AGL (Automotive Grade Linux)](https://wiki.automotivelinux.org/) koi | DRM | :heavy_check_mark: | - -Note - - i.MX 8M platforms don't support applications using EGL on GBM, which means the DRM-GBM backend won't work on i.MX 8M devices. - -### Tested Wayland compositors -||||||||||| -| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | -[Weston](https://gitlab.freedesktop.org/wayland/weston/-/blob/master/README.md) | :heavy_check_mark: | [Sway](https://swaywm.org/) | :heavy_check_mark: | [Wayfire](https://wayfire.org/) | :heavy_check_mark: | [Gnome](https://www.gnome.org/) | :heavy_check_mark: | [Phosh](https://source.puri.sm/Librem5/phosh) | :heavy_check_mark: | -| [Cage](https://www.hjdskes.nl/projects/cage/) | :heavy_check_mark: | [Lomiri](https://lomiri.com/) | :heavy_check_mark: | [Plasma Wayland](https://community.kde.org/Plasma/Wayland) | :heavy_check_mark: | [Plasma Mobile](https://www.plasma-mobile.org/) | :heavy_check_mark: | [GlacierUX](https://wiki.merproject.org/wiki/Nemo/Glacier) | :heavy_check_mark: | - -## Contributing -**Now, we cannot accept any Pull Request (PR).** Because We are building a system (e.g. CLA) to accept PRs, so please wait for a while the system is getting ready! However, we are always welcome to report bugs and request new features by creating issues. - -With the assumption, our final goal of this software openly is to be merged this embedder into [Flutter Engine](https://github.com/flutter/engine) after getting feedbacks. And [Google CLA](https://cla.developers.google.com/about/google-corporate) will be required when we do that in the future. Therefore, we cannot easily accept an external PR. However, you can free to create issues for reporting bugs and requesting new features. - -See also: [Contributing to the Flutter engine](https://github.com/flutter/engine/blob/master/CONTRIBUTING.md) +This embedder supports x64 and Arm64 (aarch64, ARMv8) architectures on Linux which supports either Wayland backend or DRM backend. See [Support status](https://github.com/sony/flutter-elinux/wiki/Support-status) for details. diff --git a/cmake/build.cmake b/cmake/build.cmake index a14bdc0d..ad69502b 100644 --- a/cmake/build.cmake +++ b/cmake/build.cmake @@ -38,7 +38,8 @@ elseif(${BACKEND_TYPE} STREQUAL "X11") "src/flutter/shell/platform/linux_embedded/window/native_window_x11.cc") else() include(cmake/generate_wayland_protocols.cmake) - set(_wayland_protocols_xml_dir "$ENV{PKG_CONFIG_SYSROOT_DIR}/usr/share/wayland-protocols") + pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir) + set(_wayland_protocols_xml_dir "${WAYLAND_PROTOCOLS_DATADIR}") set(_wayland_protocols_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/wayland/protocols") file(MAKE_DIRECTORY "${_wayland_protocols_src_dir}") @@ -62,6 +63,11 @@ else() CODE_FILE "${_wayland_protocols_src_dir}/presentation-time-protocol.c" HEADER_FILE "${_wayland_protocols_src_dir}/presentation-time-protocol.h") + generate_wayland_client_protocol( + PROTOCOL_FILE "${_wayland_protocols_xml_dir}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" + CODE_FILE "${_wayland_protocols_src_dir}/xdg-decoration-unstable-v1-protocol.c" + HEADER_FILE "${_wayland_protocols_src_dir}/xdg-decoration-unstable-v1-protocol.h") + add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND) add_definitions(-DDISPLAY_BACKEND_TYPE_WAYLAND) set(DISPLAY_BACKEND_SRC @@ -69,6 +75,7 @@ else() "${_wayland_protocols_src_dir}/text-input-unstable-v1-protocol.c" "${_wayland_protocols_src_dir}/text-input-unstable-v3-protocol.c" "${_wayland_protocols_src_dir}/presentation-time-protocol.c" + "${_wayland_protocols_src_dir}/xdg-decoration-unstable-v1-protocol.c" "src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.cc" "src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc" "src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.cc" @@ -80,6 +87,11 @@ else() "src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc") endif() +# Use flutter dirty region management +if(USE_DIRTY_REGION_MANAGEMENT) + add_definitions(-DUSE_OPENGL_DIRTY_REGION_MANAGEMENT) +endif() + # OpenGL ES version. if(USE_GLES3) add_definitions(-DUSE_GLES3) @@ -99,9 +111,24 @@ if(ENABLE_ELINUX_EMBEDDER_LOG) ) endif() +# Enable embedder vsync. +if(ENABLE_VSYNC) + add_definitions( + -DENABLE_VSYNC + ) +endif() + +# Enable alpha component of the egl color buffer. +if(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER) + add_definitions( + -DENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER + ) +endif() + set(CPP_WRAPPER_SOURCES_CORE "src/flutter/shell/platform/common/client_wrapper/engine_method_result.cc" "src/flutter/shell/platform/common/client_wrapper/standard_codec.cc" + "src/flutter/shell/platform/common/client_wrapper/string_message_codec.cc" ) set(CPP_WRAPPER_SOURCES_PLUGIN "src/flutter/shell/platform/common/client_wrapper/plugin_registrar.cc" @@ -119,7 +146,8 @@ set(ELINUX_COMMON_SRC "src/flutter/shell/platform/linux_embedded/task_runner.cc" "src/flutter/shell/platform/linux_embedded/system_utils.cc" "src/flutter/shell/platform/linux_embedded/logger.cc" - "src/flutter/shell/platform/linux_embedded/external_texture_gl.cc" + "src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.cc" + "src/flutter/shell/platform/linux_embedded/external_texture_egl_image.cc" "src/flutter/shell/platform/linux_embedded/vsync_waiter.cc" "src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.cc" "src/flutter/shell/platform/linux_embedded/plugins/keyboard_glfw_util.cc" @@ -129,6 +157,7 @@ set(ELINUX_COMMON_SRC "src/flutter/shell/platform/linux_embedded/plugins/navigation_plugin.cc" "src/flutter/shell/platform/linux_embedded/plugins/platform_plugin.cc" "src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.cc" + "src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.cc" "src/flutter/shell/platform/linux_embedded/plugins/text_input_plugin.cc" "src/flutter/shell/platform/linux_embedded/surface/context_egl.cc" "src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc" @@ -220,6 +249,7 @@ target_link_libraries(${TARGET} ${LIBINPUT_LIBRARIES} ${LIBUDEV_LIBRARIES} ${LIBSYSTEMD_LIBRARIES} + ${LIBUV_LIBRARIES} ${X11_LIBRARIES} ${LIBWESTON_LIBRARIES} ${FLUTTER_EMBEDDER_LIB} @@ -227,11 +257,16 @@ target_link_libraries(${TARGET} ${USER_APP_LIBRARIES} ) -if(${BACKEND_TYPE} MATCHES "DRM-(GBM|EGLSTREAM)") -target_link_libraries(${TARGET} - PRIVATE - Threads::Threads -) +if(${BACKEND_TYPE} MATCHES "^DRM-(GBM|EGLSTREAM)$") + target_link_libraries(${TARGET} + PRIVATE + Threads::Threads + ) + + # Indicate whether libsystemd must replace libuv + if("${LIBSYSTEMD_FOUND}" STREQUAL "1") + add_definitions(-DUSE_LIBSYSTEMD) + endif() endif() set(FLUTTER_EMBEDDER_LIB "${CMAKE_CURRENT_SOURCE_DIR}/build/libflutter_engine.so") diff --git a/cmake/package.cmake b/cmake/package.cmake index 390d8113..7a2ebcaa 100644 --- a/cmake/package.cmake +++ b/cmake/package.cmake @@ -9,17 +9,26 @@ pkg_check_modules(EGL REQUIRED egl) pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) # depends on backend type. -if(${BACKEND_TYPE} MATCHES "DRM-(GBM|EGLSTREAM)") +if(${BACKEND_TYPE} MATCHES "^DRM-(GBM|EGLSTREAM)$") # DRM backend pkg_check_modules(DRM REQUIRED libdrm) pkg_check_modules(LIBINPUT REQUIRED libinput) pkg_check_modules(LIBUDEV REQUIRED libudev) - pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) + pkg_check_modules(LIBSYSTEMD libsystemd) + pkg_check_modules(LIBUV libuv) if(${BACKEND_TYPE} STREQUAL "DRM-GBM") pkg_check_modules(GBM REQUIRED gbm) endif() set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) + # Check if either systemd or libuv exist + if((NOT "${LIBSYSTEMD_FOUND}" STREQUAL "1") AND (NOT "${LIBUV_FOUND}" STREQUAL "1")) + message(FATAL_ERROR + "${BACKEND_TYPE} backend requires either libsystemd or libuv, but + they're not exist.") + elseif(("${LIBSYSTEMD_FOUND}" STREQUAL "1") AND ("${LIBUV_FOUND}" STREQUAL "1")) + message("!! NOTICE: libsystemd found, libuv won't be used.") + endif() elseif(${BACKEND_TYPE} STREQUAL "X11") pkg_check_modules(X11 REQUIRED x11) else() diff --git a/examples/flutter-drm-eglstream-backend/command_options.h b/examples/flutter-drm-eglstream-backend/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-drm-eglstream-backend/command_options.h +++ b/examples/flutter-drm-eglstream-backend/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-drm-eglstream-backend/flutter_embedder_options.h b/examples/flutter-drm-eglstream-backend/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-drm-eglstream-backend/flutter_embedder_options.h +++ b/examples/flutter-drm-eglstream-backend/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-drm-eglstream-backend/main.cc b/examples/flutter-drm-eglstream-backend/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-drm-eglstream-backend/main.cc +++ b/examples/flutter-drm-eglstream-backend/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/examples/flutter-drm-gbm-backend/command_options.h b/examples/flutter-drm-gbm-backend/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-drm-gbm-backend/command_options.h +++ b/examples/flutter-drm-gbm-backend/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-drm-gbm-backend/flutter_embedder_options.h b/examples/flutter-drm-gbm-backend/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-drm-gbm-backend/flutter_embedder_options.h +++ b/examples/flutter-drm-gbm-backend/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-drm-gbm-backend/main.cc b/examples/flutter-drm-gbm-backend/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-drm-gbm-backend/main.cc +++ b/examples/flutter-drm-gbm-backend/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/examples/flutter-external-texture-plugin/command_options.h b/examples/flutter-external-texture-plugin/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-external-texture-plugin/command_options.h +++ b/examples/flutter-external-texture-plugin/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-external-texture-plugin/flutter_embedder_options.h b/examples/flutter-external-texture-plugin/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-external-texture-plugin/flutter_embedder_options.h +++ b/examples/flutter-external-texture-plugin/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-external-texture-plugin/main.cc b/examples/flutter-external-texture-plugin/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-external-texture-plugin/main.cc +++ b/examples/flutter-external-texture-plugin/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/examples/flutter-video-player-plugin/command_options.h b/examples/flutter-video-player-plugin/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-video-player-plugin/command_options.h +++ b/examples/flutter-video-player-plugin/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-video-player-plugin/flutter_embedder_options.h b/examples/flutter-video-player-plugin/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-video-player-plugin/flutter_embedder_options.h +++ b/examples/flutter-video-player-plugin/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-video-player-plugin/main.cc b/examples/flutter-video-player-plugin/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-video-player-plugin/main.cc +++ b/examples/flutter-video-player-plugin/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/examples/flutter-wayland-client/command_options.h b/examples/flutter-wayland-client/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-wayland-client/command_options.h +++ b/examples/flutter-wayland-client/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-wayland-client/flutter_embedder_options.h b/examples/flutter-wayland-client/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-wayland-client/flutter_embedder_options.h +++ b/examples/flutter-wayland-client/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-wayland-client/main.cc b/examples/flutter-wayland-client/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-wayland-client/main.cc +++ b/examples/flutter-wayland-client/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/examples/flutter-x11-client/command_options.h b/examples/flutter-x11-client/command_options.h index 107a152d..b0de9316 100644 --- a/examples/flutter-x11-client/command_options.h +++ b/examples/flutter-x11-client/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -56,6 +54,15 @@ class CommandOptions { ReaderInt(), required, true); } + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + void AddString(const std::string& name, const std::string& short_name, const std::string& description, @@ -250,6 +257,10 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: Option(const std::string& name, diff --git a/examples/flutter-x11-client/flutter_embedder_options.h b/examples/flutter-x11-client/flutter_embedder_options.h index 4c229135..41d0bd10 100644 --- a/examples/flutter-x11-client/flutter_embedder_options.h +++ b/examples/flutter-x11-client/flutter_embedder_options.h @@ -21,15 +21,31 @@ class FlutterEmbedderOptions { options_.AddInt("rotation", "r", "Window rotation(degree) [0(default)|90|180|270]", 0, false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); options_.AddInt("width", "w", "Window width", 1280, false); options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); options_.AddWithoutValue("window-decoration", "d", @@ -72,6 +88,19 @@ class FlutterEmbedderOptions { } } + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) use_onscreen_keyboard_ = false; @@ -80,6 +109,7 @@ class FlutterEmbedderOptions { #elif defined(FLUTTER_TARGET_BACKEND_X11) use_onscreen_keyboard_ = false; use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); window_view_mode_ = options_.Exist("fullscreen") ? flutter::FlutterViewController::ViewMode::kFullscreen @@ -87,6 +117,8 @@ class FlutterEmbedderOptions { window_width_ = options_.GetValue("width"); window_height_ = options_.GetValue("height"); #else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); use_window_decoration_ = options_.Exist("window-decoration"); window_view_mode_ = @@ -100,23 +132,58 @@ class FlutterEmbedderOptions { return true; } - std::string BundlePath() const { return bundle_path_; } - bool IsUseMouseCursor() const { return use_mouse_cursor_; } - bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } - bool IsUseWindowDecoraation() const { return use_window_decoration_; } + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } flutter::FlutterViewController::ViewMode WindowViewMode() const { return window_view_mode_; } - int WindowWidth() const { return window_width_; } - int WindowHeight() const { return window_height_; } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } flutter::FlutterViewController::ViewRotation WindowRotation() const { return window_view_rotation_; } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } private: commandline::CommandOptions options_; std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; bool use_mouse_cursor_ = true; bool use_onscreen_keyboard_ = false; bool use_window_decoration_ = false; @@ -126,6 +193,11 @@ class FlutterEmbedderOptions { int window_height_ = 720; flutter::FlutterViewController::ViewRotation window_view_rotation_ = flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/examples/flutter-x11-client/main.cc b/examples/flutter-x11-client/main.cc index fa4e11b8..579daee6 100644 --- a/examples/flutter-x11-client/main.cc +++ b/examples/flutter-x11-client/main.cc @@ -30,9 +30,16 @@ int main(int argc, char** argv) { view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/src/client_wrapper/flutter_engine.cc b/src/client_wrapper/flutter_engine.cc index a1fc0c46..4cf26678 100644 --- a/src/client_wrapper/flutter_engine.cc +++ b/src/client_wrapper/flutter_engine.cc @@ -30,7 +30,7 @@ FlutterEngine::FlutterEngine(const DartProject& project) { c_engine_properties.dart_entrypoint_argv = entrypoint_argv.size() > 0 ? entrypoint_argv.data() : nullptr; - engine_ = FlutterDesktopEngineCreate(c_engine_properties); + engine_ = FlutterDesktopEngineCreate(&c_engine_properties); auto core_messenger = FlutterDesktopEngineGetMessenger(engine_); messenger_ = std::make_unique(core_messenger); diff --git a/src/client_wrapper/flutter_view_controller.cc b/src/client_wrapper/flutter_view_controller.cc index 8387919d..47f81529 100644 --- a/src/client_wrapper/flutter_view_controller.cc +++ b/src/client_wrapper/flutter_view_controller.cc @@ -20,22 +20,33 @@ FlutterViewController::FlutterViewController( c_view_properties.view_rotation = (view_properties.view_rotation == ViewRotation::kRotation_90) ? FlutterDesktopViewRotation::kRotation_90 - : (view_properties.view_rotation == ViewRotation::kRotation_180) - ? FlutterDesktopViewRotation::kRotation_180 - : (view_properties.view_rotation == ViewRotation::kRotation_270) - ? FlutterDesktopViewRotation::kRotation_270 - : FlutterDesktopViewRotation::kRotation_0; + : (view_properties.view_rotation == ViewRotation::kRotation_180) + ? FlutterDesktopViewRotation::kRotation_180 + : (view_properties.view_rotation == ViewRotation::kRotation_270) + ? FlutterDesktopViewRotation::kRotation_270 + : FlutterDesktopViewRotation::kRotation_0; c_view_properties.view_mode = (view_properties.view_mode == ViewMode::kFullscreen) ? FlutterDesktopViewMode::kFullscreen - : FlutterDesktopViewMode::kNormal; + : FlutterDesktopViewMode::kNormalscreen; + c_view_properties.title = view_properties.title.has_value() + ? (*view_properties.title).c_str() + : nullptr; + c_view_properties.app_id = view_properties.app_id.has_value() + ? (*view_properties.app_id).c_str() + : nullptr; c_view_properties.use_mouse_cursor = view_properties.use_mouse_cursor; c_view_properties.use_onscreen_keyboard = view_properties.use_onscreen_keyboard; c_view_properties.use_window_decoration = view_properties.use_window_decoration; + c_view_properties.text_scale_factor = view_properties.text_scale_factor; + c_view_properties.enable_high_contrast = view_properties.enable_high_contrast; + c_view_properties.force_scale_factor = view_properties.force_scale_factor; + c_view_properties.scale_factor = view_properties.scale_factor; + c_view_properties.enable_vsync = view_properties.enable_vsync; - controller_ = FlutterDesktopViewControllerCreate(c_view_properties, + controller_ = FlutterDesktopViewControllerCreate(&c_view_properties, engine_->RelinquishEngine()); if (!controller_) { std::cerr << "Failed to create view controller." << std::endl; diff --git a/src/client_wrapper/include/flutter/flutter_view_controller.h b/src/client_wrapper/include/flutter/flutter_view_controller.h index a5762e47..2abf0e6a 100644 --- a/src/client_wrapper/include/flutter/flutter_view_controller.h +++ b/src/client_wrapper/include/flutter/flutter_view_controller.h @@ -9,6 +9,7 @@ #include #include +#include #include "dart_project.h" #include "flutter_engine.h" @@ -58,6 +59,13 @@ class FlutterViewController { // and `height` will be ignored. ViewMode view_mode; + // View title. + std::optional title; + + // View XDG application ID. As a best practice, it is suggested to select an + // app ID that matches the basename of the application's .desktop file. + std::optional app_id; + // Uses mouse cursor. bool use_mouse_cursor; @@ -67,6 +75,21 @@ class FlutterViewController { // Uses the window decoration such as toolbar and max/min buttons. // This option is only active for Wayland backend. bool use_window_decoration; + + // Text scaling factor. + double text_scale_factor; + + // Enable high contrast. Request that UI be rendered with darker colors. + bool enable_high_contrast; + + // Force scale factor specified by command line argument + bool force_scale_factor; + double scale_factor; + + // Enable Vsync. + // True: Sync to compositor redraw/v-blank (eglSwapInterval 1) + // False: Do not sync to compositor redraw/v-blank (eglSwapInterval 0) + bool enable_vsync; } ViewProperties; // Creates a FlutterView that can be parented into a Windows View hierarchy diff --git a/src/flutter/common/constants.h b/src/flutter/common/constants.h new file mode 100644 index 00000000..668aa4f6 --- /dev/null +++ b/src/flutter/common/constants.h @@ -0,0 +1,38 @@ +// 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_COMMON_CONSTANTS_H_ +#define FLUTTER_COMMON_CONSTANTS_H_ + +namespace flutter { +constexpr double kMegaByteSizeInBytes = (1 << 20); + +// The ID for the implicit view if the implicit view is enabled. +// +// The implicit view is a compatibility mechanism to help the transition from +// the older single-view APIs to the newer multi-view APIs. The two sets of APIs +// use different models for view management. The implicit view mechanism allows +// single-view APIs to operate a special view as if other views don't exist. +// +// In the regular multi-view model, all views should be created by +// `Shell::AddView` before being used, and removed by `Shell::RemoveView` to +// signify that they are gone. If a view is added or removed, the framework +// (`PlatformDispatcher`) will be notified. New view IDs are always unique, +// never reused. Operating a non-existing view is an error. +// +// The implicit view is another special view in addition to the "regular views" +// as above. The shell starts up having the implicit view, which has a fixed +// view ID of `kFlutterImplicitViewId` and is available throughout the lifetime +// of the shell. `Shell::AddView` or `RemoveView` must not be called for this +// view. Even when the window that shows the view is closed, the framework is +// unaware and might continue rendering into or operating this view. +// +// The single-view APIs, which are APIs that do not specify view IDs, operate +// the implicit view. The multi-view APIs can operate all views, including the +// implicit view if the target ID is `kFlutterImplicitViewId`, unless specified +// otherwise. +constexpr int64_t kFlutterImplicitViewId = 0; +} // namespace flutter + +#endif // FLUTTER_COMMON_CONSTANTS_H_ diff --git a/src/flutter/fml/closure.h b/src/flutter/fml/closure.h new file mode 100644 index 00000000..264a95f7 --- /dev/null +++ b/src/flutter/fml/closure.h @@ -0,0 +1,77 @@ +// 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_FML_CLOSURE_H_ +#define FLUTTER_FML_CLOSURE_H_ + +#include + +#include "flutter/fml/macros.h" + +namespace fml { + +using closure = std::function; + +//------------------------------------------------------------------------------ +/// @brief Wraps a closure that is invoked in the destructor unless +/// released by the caller. +/// +/// This is especially useful in dealing with APIs that return a +/// resource by accepting ownership of a sub-resource and a closure +/// that releases that resource. When such APIs are chained, each +/// link in the chain must check that the next member in the chain +/// has accepted the resource. If not, it must invoke the closure +/// eagerly. Not doing this results in a resource leak in the +/// erroneous case. Using this wrapper, the closure can be released +/// once the next call in the chain has successfully accepted +/// ownership of the resource. If not, the closure gets invoked +/// automatically at the end of the scope. This covers the cases +/// where there are early returns as well. +/// +class ScopedCleanupClosure final { + public: + ScopedCleanupClosure() = default; + + ScopedCleanupClosure(ScopedCleanupClosure&& other) { + closure_ = other.Release(); + } + + ScopedCleanupClosure& operator=(ScopedCleanupClosure&& other) { + closure_ = other.Release(); + return *this; + } + + explicit ScopedCleanupClosure(const fml::closure& closure) + : closure_(closure) {} + + ~ScopedCleanupClosure() { Reset(); } + + fml::closure SetClosure(const fml::closure& closure) { + auto old_closure = closure_; + closure_ = closure; + return old_closure; + } + + fml::closure Release() { + fml::closure closure = closure_; + closure_ = nullptr; + return closure; + } + + void Reset() { + if (closure_) { + closure_(); + closure_ = nullptr; + } + } + + private: + fml::closure closure_; + + FML_DISALLOW_COPY_AND_ASSIGN(ScopedCleanupClosure); +}; + +} // namespace fml + +#endif // FLUTTER_FML_CLOSURE_H_ diff --git a/src/flutter/fml/macros.h b/src/flutter/fml/macros.h new file mode 100644 index 00000000..7de4ddf1 --- /dev/null +++ b/src/flutter/fml/macros.h @@ -0,0 +1,44 @@ +// 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_FML_MACROS_H_ +#define FLUTTER_FML_MACROS_H_ + +#ifndef FML_USED_ON_EMBEDDER + +#define FML_EMBEDDER_ONLY [[deprecated]] + +#else // FML_USED_ON_EMBEDDER + +#define FML_EMBEDDER_ONLY + +#endif // FML_USED_ON_EMBEDDER + +#define FML_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete + +#define FML_DISALLOW_ASSIGN(TypeName) \ + TypeName& operator=(const TypeName&) = delete + +#define FML_DISALLOW_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete + +#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define FML_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) + +#define FML_FRIEND_TEST(test_case_name, test_name) \ + friend class test_case_name##_##test_name##_Test + +#endif // FLUTTER_FML_MACROS_H_ diff --git a/src/flutter/shell/platform/common/client_wrapper/core_implementations.cc b/src/flutter/shell/platform/common/client_wrapper/core_implementations.cc index 1412db7c..ec0d6dd9 100644 --- a/src/flutter/shell/platform/common/client_wrapper/core_implementations.cc +++ b/src/flutter/shell/platform/common/client_wrapper/core_implementations.cc @@ -66,6 +66,11 @@ BinaryMessengerImpl::BinaryMessengerImpl( BinaryMessengerImpl::~BinaryMessengerImpl() = default; +void CaptureCleaner(void* lambda) { + auto& cleanup = *reinterpret_cast*>(lambda); + cleanup(); +} + void BinaryMessengerImpl::Send(const std::string& channel, const uint8_t* message, size_t message_size, @@ -87,12 +92,13 @@ void BinaryMessengerImpl::Send(const std::string& channel, captures->reply(data, data_size); delete captures; }; - bool result = FlutterDesktopMessengerSendWithReply( + + FlutterDesktopMessengerSendWithReply( messenger_, channel.c_str(), message, message_size, message_reply, - captures); - if (!result) { - delete captures; - } + captures, [](void* captures_data) { + auto captures = reinterpret_cast(captures_data); + delete captures; + }); } void BinaryMessengerImpl::SetMessageHandler(const std::string& channel, @@ -157,25 +163,46 @@ TextureRegistrarImpl::TextureRegistrarImpl( TextureRegistrarImpl::~TextureRegistrarImpl() = default; int64_t TextureRegistrarImpl::RegisterTexture(TextureVariant* texture) { + FlutterDesktopTextureInfo info = {}; if (auto pixel_buffer_texture = std::get_if(texture)) { - FlutterDesktopTextureInfo info = {}; info.type = kFlutterDesktopPixelBufferTexture; info.pixel_buffer_config.user_data = pixel_buffer_texture; info.pixel_buffer_config.callback = [](size_t width, size_t height, void* user_data) -> const FlutterDesktopPixelBuffer* { auto texture = static_cast(user_data); - auto buffer = texture->CopyPixelBuffer(width, height); - return buffer; + return texture->CopyPixelBuffer(width, height); }; - - int64_t texture_id = FlutterDesktopTextureRegistrarRegisterExternalTexture( - texture_registrar_ref_, &info); - return texture_id; + } else if (auto gpu_surface_texture = + std::get_if(texture)) { + info.type = kFlutterDesktopGpuSurfaceTexture; + info.gpu_surface_config.struct_size = + sizeof(FlutterDesktopGpuSurfaceTextureConfig); + info.gpu_surface_config.type = gpu_surface_texture->surface_type(); + info.gpu_surface_config.user_data = gpu_surface_texture; + info.gpu_surface_config.callback = + [](size_t width, size_t height, + void* user_data) -> const FlutterDesktopGpuSurfaceDescriptor* { + auto texture = static_cast(user_data); + return texture->ObtainDescriptor(width, height); + }; + } else if (auto egl_image_texture = std::get_if(texture)) { + info.type = kFlutterDesktopEGLImageTexture; + info.egl_image_config.user_data = egl_image_texture; + info.egl_image_config.callback = + [](size_t width, size_t height, void* egl_display, void* egl_context, + void* user_data) -> const FlutterDesktopEGLImage* { + auto texture = static_cast(user_data); + return texture->GetEGLImage(width, height, egl_display, egl_context); + }; + } else { + std::cerr << "Attempting to register unknown texture variant." << std::endl; + return -1; } - std::cerr << "Attempting to register unknown texture variant." << std::endl; - return -1; + int64_t texture_id = FlutterDesktopTextureRegistrarRegisterExternalTexture( + texture_registrar_ref_, &info); + return texture_id; } // namespace flutter bool TextureRegistrarImpl::MarkTextureFrameAvailable(int64_t texture_id) { @@ -183,9 +210,32 @@ bool TextureRegistrarImpl::MarkTextureFrameAvailable(int64_t texture_id) { texture_registrar_ref_, texture_id); } +void TextureRegistrarImpl::UnregisterTexture(int64_t texture_id, + std::function callback) { + if (callback == nullptr) { + FlutterDesktopTextureRegistrarUnregisterExternalTexture( + texture_registrar_ref_, texture_id, nullptr, nullptr); + return; + } + + struct Captures { + std::function callback; + }; + auto captures = new Captures(); + captures->callback = std::move(callback); + FlutterDesktopTextureRegistrarUnregisterExternalTexture( + texture_registrar_ref_, texture_id, + [](void* opaque) { + auto captures = reinterpret_cast(opaque); + captures->callback(); + delete captures; + }, + captures); +} + bool TextureRegistrarImpl::UnregisterTexture(int64_t texture_id) { - return FlutterDesktopTextureRegistrarUnregisterExternalTexture( - texture_registrar_ref_, texture_id); + UnregisterTexture(texture_id, nullptr); + return true; } } // namespace flutter diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h index 3b46f99f..bf243fde 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h @@ -70,7 +70,9 @@ class CustomEncodableValue { #if defined(FLUTTER_ENABLE_RTTI) && FLUTTER_ENABLE_RTTI // Passthrough to std::any's type(). - const std::type_info& type() const noexcept { return value_.type(); } + const std::type_info& type() const noexcept { + return value_.type(); + } #endif // This operator exists only to provide a stable ordering for use as a @@ -215,6 +217,13 @@ class EncodableValue : public internal::EncodableValueVariant { } return std::get(*this); } + + // Explicitly provide operator<, delegating to std::variant's operator<. + // There are issues with with the way the standard library-provided + // < and <=> comparisons interact with classes derived from variant. + friend bool operator<(const EncodableValue& lhs, const EncodableValue& rhs) { + return static_cast(lhs) < static_cast(rhs); + } }; } // namespace flutter diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_channel.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_channel.h index fe0e1414..c96e6408 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_channel.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_channel.h @@ -47,10 +47,13 @@ class EventChannel { // Registers a stream handler on this channel. // If no handler has been registered, any incoming stream setup requests will // be handled silently by providing an empty stream. + // + // Note that the EventChannel does not own the handler and will not + // unregister it on destruction. The caller is responsible for unregistering + // the handler if it should no longer be called. void SetStreamHandler(std::unique_ptr> handler) { if (!handler) { messenger_->SetMessageHandler(name_, nullptr); - is_listening_ = false; return; } @@ -61,69 +64,75 @@ class EventChannel { const MethodCodec* codec = codec_; const std::string channel_name = name_; const BinaryMessenger* messenger = messenger_; - BinaryMessageHandler binary_handler = [shared_handler, codec, channel_name, - messenger, - this](const uint8_t* message, - const size_t message_size, - BinaryReply reply) { - constexpr char kOnListenMethod[] = "listen"; - constexpr char kOnCancelMethod[] = "cancel"; - - std::unique_ptr> method_call = - codec->DecodeMethodCall(message, message_size); - if (!method_call) { - std::cerr << "Unable to construct method call from message on channel: " - << channel_name << std::endl; - reply(nullptr, 0); - return; - } - - const std::string& method = method_call->method_name(); - if (method.compare(kOnListenMethod) == 0) { - if (is_listening_) { - std::unique_ptr> error = - shared_handler->OnCancel(nullptr); - if (error) { - std::cerr << "Failed to cancel existing stream: " - << (error->error_code) << ", " << (error->error_message) - << ", " << (error->error_details); + BinaryMessageHandler binary_handler = + [shared_handler, codec, channel_name, messenger, + // Mutable state to track the handler's listening status. + is_listening = bool(false)](const uint8_t* message, + const size_t message_size, + BinaryReply reply) mutable { + constexpr char kOnListenMethod[] = "listen"; + constexpr char kOnCancelMethod[] = "cancel"; + + std::unique_ptr> method_call = + codec->DecodeMethodCall(message, message_size); + if (!method_call) { + std::cerr + << "Unable to construct method call from message on channel: " + << channel_name << std::endl; + reply(nullptr, 0); + return; } - } - is_listening_ = true; - - std::unique_ptr> result; - auto sink = std::make_unique( - messenger, channel_name, codec); - std::unique_ptr> error = - shared_handler->OnListen(method_call->arguments(), std::move(sink)); - if (error) { - result = codec->EncodeErrorEnvelope( - error->error_code, error->error_message, error->error_details); - } else { - result = codec->EncodeSuccessEnvelope(); - } - reply(result->data(), result->size()); - } else if (method.compare(kOnCancelMethod) == 0) { - std::unique_ptr> result; - if (is_listening_) { - std::unique_ptr> error = - shared_handler->OnCancel(method_call->arguments()); - if (error) { - result = codec->EncodeErrorEnvelope( - error->error_code, error->error_message, error->error_details); + + const std::string& method = method_call->method_name(); + if (method.compare(kOnListenMethod) == 0) { + if (is_listening) { + std::unique_ptr> error = + shared_handler->OnCancel(nullptr); + if (error) { + std::cerr << "Failed to cancel existing stream: " + << (error->error_code) << ", " + << (error->error_message) << ", " + << (error->error_details.get()); + } + } + is_listening = true; + + std::unique_ptr> result; + auto sink = std::make_unique( + messenger, channel_name, codec); + std::unique_ptr> error = + shared_handler->OnListen(method_call->arguments(), + std::move(sink)); + if (error) { + result = codec->EncodeErrorEnvelope(error->error_code, + error->error_message, + error->error_details.get()); + } else { + result = codec->EncodeSuccessEnvelope(); + } + reply(result->data(), result->size()); + } else if (method.compare(kOnCancelMethod) == 0) { + std::unique_ptr> result; + if (is_listening) { + std::unique_ptr> error = + shared_handler->OnCancel(method_call->arguments()); + if (error) { + result = codec->EncodeErrorEnvelope(error->error_code, + error->error_message, + error->error_details.get()); + } else { + result = codec->EncodeSuccessEnvelope(); + } + is_listening = false; + } else { + result = codec->EncodeErrorEnvelope( + "error", "No active stream to cancel", nullptr); + } + reply(result->data(), result->size()); } else { - result = codec->EncodeSuccessEnvelope(); + reply(nullptr, 0); } - is_listening_ = false; - } else { - result = codec->EncodeErrorEnvelope( - "error", "No active stream to cancel", nullptr); - } - reply(result->data(), result->size()); - } else { - reply(nullptr, 0); - } - }; + }; messenger_->SetMessageHandler(name_, std::move(binary_handler)); } @@ -165,7 +174,6 @@ class EventChannel { BinaryMessenger* messenger_; const std::string name_; const MethodCodec* codec_; - bool is_listening_ = false; }; } // namespace flutter diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_stream_handler.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_stream_handler.h index 9eced6cf..9c1d8941 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_stream_handler.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/event_stream_handler.h @@ -5,6 +5,9 @@ #ifndef FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_H_ +#include +#include + #include "event_sink.h" namespace flutter { @@ -13,16 +16,16 @@ class EncodableValue; template struct StreamHandlerError { - const std::string& error_code; - const std::string& error_message; - const T* error_details; + const std::string error_code; + const std::string error_message; + const std::unique_ptr error_details; - StreamHandlerError(const std::string& error_code, - const std::string& error_message, - const T* error_details) + StreamHandlerError(const std::string error_code, + const std::string error_message, + std::unique_ptr&& error_details) : error_code(error_code), error_message(error_message), - error_details(error_details) {} + error_details(std::move(error_details)) {} }; // Handler for stream setup and teardown requests. diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h index e9ed6161..6e39a644 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h @@ -91,9 +91,10 @@ class MethodChannel { // Registers a handler that should be called any time a method call is // received on this channel. A null handler will remove any previous handler. // - // Note that the MethodChannel does not own the handler, and will not - // unregister it on destruction, so the caller is responsible for - // unregistering explicitly if it should no longer be called. + // The handler will be owned by the underlying BinaryMessageHandler. + // Destroying the MethodChannel will not unregister the handler, so + // the caller is responsible for unregistering explicitly if the handler + // stops being valid before the engine is destroyed. void SetMethodCallHandler(MethodCallHandler handler) const { if (!handler) { messenger_->SetMessageHandler(name_, nullptr); diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h index 858119e3..15cf9968 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h @@ -49,8 +49,8 @@ class PluginRegistrar { // Takes ownership of |plugin|. // // Plugins are not required to call this method if they have other lifetime - // management, but this is a convient place for plugins to be owned to ensure - // that they stay valid for any registered callbacks. + // management, but this is a convenient place for plugins to be owned to + // ensure that they stay valid for any registered callbacks. void AddPlugin(std::unique_ptr plugin); protected: diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/string_message_codec.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/string_message_codec.h new file mode 100644 index 00000000..11f31d41 --- /dev/null +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/string_message_codec.h @@ -0,0 +1,45 @@ +// Copyright 2022 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef __CODE_FLUTTER_EMBEDDED_LINUX_SRC_FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_STRING_MESSAGE_CODEC_H_ +#define __CODE_FLUTTER_EMBEDDED_LINUX_SRC_FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_STRING_MESSAGE_CODEC_H_ + +#include "message_codec.h" + +namespace flutter { + +// A message encoding/decoding mechanism for communications to/from the Flutter +// engine via UTF-8 encoded string messages. +// +// This codec is guaranteed to be compatible with the corresponding +// [StringCodec](https://api.flutter.dev/flutter/services/StringCodec-class.html) +// on the Dart side. These parts of the Flutter SDK are evolved synchronously. +class StringMessageCodec : public MessageCodec { + public: + // Returns the shared instance of the codec. + static const StringMessageCodec& GetInstance(); + + ~StringMessageCodec() = default; + + // Prevent copying. + StringMessageCodec(StringMessageCodec const&) = delete; + StringMessageCodec& operator=(StringMessageCodec const&) = delete; + + protected: + // Instances should be obtained via GetInstance. + StringMessageCodec() = default; + + // |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 std::string& message) const override; +}; + +} // namespace flutter + +#endif // __CODE_FLUTTER_EMBEDDED_LINUX_SRC_FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_STRING_MESSAGE_CODEC_H_ diff --git a/src/flutter/shell/platform/common/client_wrapper/include/flutter/texture_registrar.h b/src/flutter/shell/platform/common/client_wrapper/include/flutter/texture_registrar.h index 6aa2c6b1..43f1147c 100644 --- a/src/flutter/shell/platform/common/client_wrapper/include/flutter/texture_registrar.h +++ b/src/flutter/shell/platform/common/client_wrapper/include/flutter/texture_registrar.h @@ -42,10 +42,73 @@ class PixelBufferTexture { const CopyBufferCallback copy_buffer_callback_; }; +// A EGLImage texture. +class EGLImageTexture { + public: + // A callback used for retrieving pixel buffers. + typedef std::function + GetEGLImageCallback; + + // Creates a EGLImage texture that uses the provided |get_egl_image_cb| to + // retrieve the EGLImage. + // As the callback is usually invoked from the render thread, the callee must + // take care of proper synchronization. It also needs to be ensured that the + // returned buffer isn't released prior to unregistering this texture. + explicit EGLImageTexture(GetEGLImageCallback get_egl_image_callback) + : get_egl_image_callback_(get_egl_image_callback) {} + + // Returns the callback-provided FlutterDesktopEGLImage. + // The intended surface size is specified by |width| and + // |height|. + const FlutterDesktopEGLImage* GetEGLImage(size_t width, + size_t height, + void* egl_display, + void* egl_context) const { + return get_egl_image_callback_(width, height, egl_display, egl_context); + } + + private: + const GetEGLImageCallback get_egl_image_callback_; +}; + +// A GPU surface-based texture. +class GpuSurfaceTexture { + public: + // A callback used for retrieving surface descriptors. + typedef std::function< + const FlutterDesktopGpuSurfaceDescriptor*(size_t width, size_t height)> + ObtainDescriptorCallback; + + GpuSurfaceTexture(FlutterDesktopGpuSurfaceType surface_type, + ObtainDescriptorCallback obtain_descriptor_callback) + : surface_type_(surface_type), + obtain_descriptor_callback_(obtain_descriptor_callback) {} + + // Returns the callback-provided FlutterDesktopGpuSurfaceDescriptor that + // contains the surface handle. The intended surface size is specified by + // |width| and |height|. + const FlutterDesktopGpuSurfaceDescriptor* ObtainDescriptor( + size_t width, + size_t height) const { + return obtain_descriptor_callback_(width, height); + } + + // Gets the surface type. + FlutterDesktopGpuSurfaceType surface_type() const { return surface_type_; } + + private: + const FlutterDesktopGpuSurfaceType surface_type_; + const ObtainDescriptorCallback obtain_descriptor_callback_; +}; + // The available texture variants. // Only PixelBufferTexture is currently implemented. // Other variants are expected to be added in the future. -typedef std::variant TextureVariant; +typedef std::variant + TextureVariant; // An object keeping track of external textures. // @@ -65,10 +128,15 @@ class TextureRegistrar { // the callback that was provided upon creating the texture. virtual bool MarkTextureFrameAvailable(int64_t texture_id) = 0; - // Unregisters an existing Texture object. - // Textures must not be unregistered while they're in use. + // Asynchronously unregisters an existing texture object. + // Upon completion, the optional |callback| gets invoked. + virtual void UnregisterTexture(int64_t texture_id, + std::function callback) = 0; + + // Unregisters an existing texture object. + // DEPRECATED: Use UnregisterTexture(texture_id, optional_callback) instead. virtual bool UnregisterTexture(int64_t texture_id) = 0; -}; +} SWIFT_UNSAFE_REFERENCE; } // namespace flutter diff --git a/src/flutter/shell/platform/common/client_wrapper/standard_codec.cc b/src/flutter/shell/platform/common/client_wrapper/standard_codec.cc index 807e0681..5e93d407 100644 --- a/src/flutter/shell/platform/common/client_wrapper/standard_codec.cc +++ b/src/flutter/shell/platform/common/client_wrapper/standard_codec.cc @@ -98,7 +98,7 @@ EncodableValue StandardCodecSerializer::ReadValue( void StandardCodecSerializer::WriteValue(const EncodableValue& value, ByteStreamWriter* stream) const { stream->WriteByte(static_cast(EncodedTypeForValue(value))); - // TODO: Consider replacing this this with a std::visitor. + // TODO(cbracken): Consider replacing this with std::visit. switch (value.index()) { case 0: case 1: diff --git a/src/flutter/shell/platform/common/client_wrapper/string_message_codec.cc b/src/flutter/shell/platform/common/client_wrapper/string_message_codec.cc new file mode 100644 index 00000000..d0c12bac --- /dev/null +++ b/src/flutter/shell/platform/common/client_wrapper/string_message_codec.cc @@ -0,0 +1,34 @@ +// Copyright 2022 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "include/flutter/string_message_codec.h" + +#include + +namespace flutter { + +// static +const StringMessageCodec& StringMessageCodec::GetInstance() { + static StringMessageCodec sInstance; + return sInstance; +} + +std::unique_ptr> StringMessageCodec::EncodeMessageInternal( + const std::string& message) const { + return std::make_unique>(message.begin(), message.end()); +} + +std::unique_ptr StringMessageCodec::DecodeMessageInternal( + const uint8_t* binary_message, + const size_t message_size) const { + if (!binary_message) { + return nullptr; + } + + auto raw_message = reinterpret_cast(binary_message); + + return std::make_unique(raw_message, message_size); +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/common/client_wrapper/texture_registrar_impl.h b/src/flutter/shell/platform/common/client_wrapper/texture_registrar_impl.h index df51f6dd..bd01839d 100644 --- a/src/flutter/shell/platform/common/client_wrapper/texture_registrar_impl.h +++ b/src/flutter/shell/platform/common/client_wrapper/texture_registrar_impl.h @@ -27,6 +27,10 @@ class TextureRegistrarImpl : public TextureRegistrar { // |flutter::TextureRegistrar| bool MarkTextureFrameAvailable(int64_t texture_id) override; + // |flutter::TextureRegistrar| + void UnregisterTexture(int64_t texture_id, + std::function callback) override; + // |flutter::TextureRegistrar| bool UnregisterTexture(int64_t texture_id) override; diff --git a/src/flutter/shell/platform/common/engine_switches.cc b/src/flutter/shell/platform/common/engine_switches.cc index 5c750f13..37b98fae 100644 --- a/src/flutter/shell/platform/common/engine_switches.cc +++ b/src/flutter/shell/platform/common/engine_switches.cc @@ -16,7 +16,6 @@ std::vector GetSwitchesFromEnvironment() { // Read engine switches from the environment in debug/profile. If release mode // support is needed in the future, it should likely use a whitelist. #ifndef FLUTTER_RELEASE -#ifndef WINUWP const char* switch_count_key = "FLUTTER_ENGINE_SWITCHES"; const int kMaxSwitchCount = 50; const char* switch_count_string = std::getenv(switch_count_key); @@ -37,7 +36,6 @@ std::vector GetSwitchesFromEnvironment() { << ", but " << switch_key.str() << " is missing." << std::endl; } } -#endif // !WINUWP #endif // !FLUTTER_RELEASE return switches; } diff --git a/src/flutter/shell/platform/common/json_method_codec.cc b/src/flutter/shell/platform/common/json_method_codec.cc index cc4ffec6..9ccb79c8 100644 --- a/src/flutter/shell/platform/common/json_method_codec.cc +++ b/src/flutter/shell/platform/common/json_method_codec.cc @@ -104,7 +104,8 @@ JsonMethodCodec::EncodeErrorEnvelopeInternal( rapidjson::Document envelope(rapidjson::kArrayType); auto& allocator = envelope.GetAllocator(); envelope.PushBack(rapidjson::Value(error_code.c_str(), allocator), allocator); - envelope.PushBack(rapidjson::Value(error_message.c_str(), allocator), allocator); + envelope.PushBack(rapidjson::Value(error_message.c_str(), allocator), + allocator); rapidjson::Value details_value; if (error_details) { details_value.CopyFrom(*error_details, allocator); diff --git a/src/flutter/shell/platform/common/public/flutter_export.h b/src/flutter/shell/platform/common/public/flutter_export.h index 38cac85b..5fe8c593 100644 --- a/src/flutter/shell/platform/common/public/flutter_export.h +++ b/src/flutter/shell/platform/common/public/flutter_export.h @@ -25,4 +25,13 @@ #endif // FLUTTER_DESKTOP_LIBRARY +#if __has_include() +#include +#else +#define SWIFT_UNSAFE_REFERENCE +#define SWIFT_SHARED_REFERENCE(_retain, _release) +#define SWIFT_RETURNS_RETAINED +#define SWIFT_RETURNS_UNRETAINED +#endif + #endif // FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_EXPORT_H_ diff --git a/src/flutter/shell/platform/common/public/flutter_messenger.h b/src/flutter/shell/platform/common/public/flutter_messenger.h index 854ff985..d06385ca 100644 --- a/src/flutter/shell/platform/common/public/flutter_messenger.h +++ b/src/flutter/shell/platform/common/public/flutter_messenger.h @@ -63,7 +63,8 @@ FLUTTER_EXPORT bool FlutterDesktopMessengerSendWithReply( const uint8_t* message, const size_t message_size, const FlutterDesktopBinaryReply reply, - void* user_data); + void* user_data, + void (*cleanup)(void* captures_data)); // Sends a reply to a FlutterDesktopMessage for the given response handle. // @@ -87,6 +88,53 @@ FLUTTER_EXPORT void FlutterDesktopMessengerSetCallback( FlutterDesktopMessageCallback callback, void* user_data); +// Increments the reference count for the |messenger|. +// +// Operation is thread-safe. +// +// See also: |FlutterDesktopMessengerRelease| +FLUTTER_EXPORT FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef( + FlutterDesktopMessengerRef messenger) SWIFT_RETURNS_RETAINED; + +// Decrements the reference count for the |messenger|. +// +// Operation is thread-safe. +// +// See also: |FlutterDesktopMessengerAddRef| +FLUTTER_EXPORT void FlutterDesktopMessengerRelease( + FlutterDesktopMessengerRef messenger); + +// Returns `true` if the |FlutterDesktopMessengerRef| still references a running +// engine. +// +// This check should be made inside of a |FlutterDesktopMessengerLock| and +// before any other calls are made to the FlutterDesktopMessengerRef when using +// it from a thread other than the platform thread. +FLUTTER_EXPORT bool FlutterDesktopMessengerIsAvailable( + FlutterDesktopMessengerRef messenger); + +// Locks the `FlutterDesktopMessengerRef` ensuring that +// |FlutterDesktopMessengerIsAvailable| does not change while locked. +// +// All calls to the FlutterDesktopMessengerRef from threads other than the +// platform thread should happen inside of a lock. +// +// Operation is thread-safe. +// +// Returns the |messenger| value. +// +// See also: |FlutterDesktopMessengerUnlock| +FLUTTER_EXPORT FlutterDesktopMessengerRef FlutterDesktopMessengerLock( + FlutterDesktopMessengerRef messenger) SWIFT_RETURNS_UNRETAINED; + +// Unlocks the `FlutterDesktopMessengerRef`. +// +// Operation is thread-safe. +// +// See also: |FlutterDesktopMessengerLock| +FLUTTER_EXPORT void FlutterDesktopMessengerUnlock( + FlutterDesktopMessengerRef messenger); + #if defined(__cplusplus) } // extern "C" #endif diff --git a/src/flutter/shell/platform/common/public/flutter_plugin_registrar.h b/src/flutter/shell/platform/common/public/flutter_plugin_registrar.h index b0847069..23a13b6d 100644 --- a/src/flutter/shell/platform/common/public/flutter_plugin_registrar.h +++ b/src/flutter/shell/platform/common/public/flutter_plugin_registrar.h @@ -26,7 +26,7 @@ typedef void (*FlutterDesktopOnPluginRegistrarDestroyed)( // Returns the engine messenger associated with this registrar. FLUTTER_EXPORT FlutterDesktopMessengerRef FlutterDesktopPluginRegistrarGetMessenger( - FlutterDesktopPluginRegistrarRef registrar); + FlutterDesktopPluginRegistrarRef registrar) SWIFT_RETURNS_UNRETAINED; // Returns the texture registrar associated with this registrar. FLUTTER_EXPORT FlutterDesktopTextureRegistrarRef diff --git a/src/flutter/shell/platform/common/public/flutter_texture_registrar.h b/src/flutter/shell/platform/common/public/flutter_texture_registrar.h index b115234e..a56c3107 100644 --- a/src/flutter/shell/platform/common/public/flutter_texture_registrar.h +++ b/src/flutter/shell/platform/common/public/flutter_texture_registrar.h @@ -23,9 +23,37 @@ typedef struct FlutterDesktopTextureRegistrar* // Additional types may be added in the future. typedef enum { // A Pixel buffer-based texture. - kFlutterDesktopPixelBufferTexture + kFlutterDesktopPixelBufferTexture, + // A platform-specific GPU surface-backed texture. + kFlutterDesktopGpuSurfaceTexture, + // An EGLImage-based texture + kFlutterDesktopEGLImageTexture } FlutterDesktopTextureType; +// Supported GPU surface types. +typedef enum { + // Uninitialized. + kFlutterDesktopGpuSurfaceTypeNone, + // A DXGI shared texture handle (Windows only). + // See + // https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiresource-getsharedhandle + kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, + // A |ID3D11Texture2D| (Windows only). + kFlutterDesktopGpuSurfaceTypeD3d11Texture2D, +} FlutterDesktopGpuSurfaceType; + +// Supported pixel formats. +typedef enum { + // Uninitialized. + kFlutterDesktopPixelFormatNone, + // Represents a 32-bit RGBA color format with 8 bits each for red, green, blue + // and alpha. + kFlutterDesktopPixelFormatRGBA8888, + // Represents a 32-bit BGRA color format with 8 bits each for blue, green, red + // and alpha. + kFlutterDesktopPixelFormatBGRA8888 +} FlutterDesktopPixelFormat; + // An image buffer object. typedef struct { // The pixel data buffer. @@ -34,22 +62,94 @@ typedef struct { size_t width; // Height of the pixel buffer. size_t height; + // An optional callback that gets invoked when the |buffer| can be released. + void (*release_callback)(void* release_context); + // Opaque data passed to |release_callback|. + void* release_context; } FlutterDesktopPixelBuffer; +// An EGLImage object. +typedef struct { + // The EGLImage object.(opaque) + const void* egl_image; + // Width of the EGLImage. + size_t width; + // Height of the EGLImage. + size_t height; + // An optional callback that gets invoked when the |egl_image| can be + // released. + void (*release_callback)(void* release_context); + // Opaque data passed to |release_callback|. + void* release_context; +} FlutterDesktopEGLImage; + +// A GPU surface descriptor. +typedef struct { + // The size of this struct. Must be + // sizeof(FlutterDesktopGpuSurfaceDescriptor). + size_t struct_size; + // The surface handle. The expected type depends on the + // |FlutterDesktopGpuSurfaceType|. + // + // Provide a |ID3D11Texture2D*| when using + // |kFlutterDesktopGpuSurfaceTypeD3d11Texture2D| or a |HANDLE| when using + // |kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle|. + // + // The referenced resource needs to stay valid until it has been opened by + // Flutter. Consider incrementing the resource's reference count in the + // |FlutterDesktopGpuSurfaceTextureCallback| and registering a + // |release_callback| for decrementing the reference count once it has been + // opened. + void* handle; + // The physical width. + size_t width; + // The physical height. + size_t height; + // The visible width. + // It might be less or equal to the physical |width|. + size_t visible_width; + // The visible height. + // It might be less or equal to the physical |height|. + size_t visible_height; + // The pixel format which might be optional depending on the surface type. + FlutterDesktopPixelFormat format; + // An optional callback that gets invoked when the |handle| has been opened. + void (*release_callback)(void* release_context); + // Opaque data passed to |release_callback|. + void* release_context; +} FlutterDesktopGpuSurfaceDescriptor; + // The pixel buffer copy callback definition provided to // the Flutter engine to copy the texture. // It is invoked with the intended surface size specified by |width| and -// |height| and the |user_data| held by FlutterDesktopPixelBufferTextureConfig. +// |height| and the |user_data| held by +// |FlutterDesktopPixelBufferTextureConfig|. // // As this is usually called from the render thread, the callee must take // care of proper synchronization. It also needs to be ensured that the -// returned FlutterDesktopPixelBuffer isn't released prior to unregistering +// returned |FlutterDesktopPixelBuffer| isn't released prior to unregistering // the corresponding texture. typedef const FlutterDesktopPixelBuffer* ( *FlutterDesktopPixelBufferTextureCallback)(size_t width, size_t height, void* user_data); +// The GPU surface callback definition provided to the Flutter engine to obtain +// the surface. It is invoked with the intended surface size specified by +// |width| and |height| and the |user_data| held by +// |FlutterDesktopGpuSurfaceTextureConfig|. +typedef const FlutterDesktopGpuSurfaceDescriptor* ( + *FlutterDesktopGpuSurfaceTextureCallback)(size_t width, + size_t height, + void* user_data); + +typedef const FlutterDesktopEGLImage* (*FlutterDesktopEGLImageTextureCallback)( + size_t width, + size_t height, + void* egl_display, + void* egl_context, + void* user_data); + // An object used to configure pixel buffer textures. typedef struct { // The callback used by the engine to copy the pixel buffer object. @@ -58,10 +158,34 @@ typedef struct { void* user_data; } FlutterDesktopPixelBufferTextureConfig; +// An object used to configure GPU-surface textures. +typedef struct { + // The size of this struct. Must be + // sizeof(FlutterDesktopGpuSurfaceTextureConfig). + size_t struct_size; + // The concrete surface type (e.g. + // |kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle|) + FlutterDesktopGpuSurfaceType type; + // The callback used by the engine to obtain the surface descriptor. + FlutterDesktopGpuSurfaceTextureCallback callback; + // Opaque data that will get passed to the provided |callback|. + void* user_data; +} FlutterDesktopGpuSurfaceTextureConfig; + +// An object used to configure EGLImage textures. +typedef struct { + // The callback used by the engine to get the EGLImage object. + FlutterDesktopEGLImageTextureCallback callback; + // Opaque data that will get passed to the provided |callback|. + void* user_data; +} FlutterDesktopEGLImageTextureConfig; + typedef struct { FlutterDesktopTextureType type; union { FlutterDesktopPixelBufferTextureConfig pixel_buffer_config; + FlutterDesktopGpuSurfaceTextureConfig gpu_surface_config; + FlutterDesktopEGLImageTextureConfig egl_image_config; }; } FlutterDesktopTextureInfo; @@ -71,13 +195,15 @@ FLUTTER_EXPORT int64_t FlutterDesktopTextureRegistrarRegisterExternalTexture( FlutterDesktopTextureRegistrarRef texture_registrar, const FlutterDesktopTextureInfo* info); -// Unregisters an existing texture from the Flutter engine for a |texture_id|. -// Returns true on success or false if the specified texture doesn't exist. +// Asynchronously unregisters the texture identified by |texture_id| from the +// Flutter engine. +// An optional |callback| gets invoked upon completion. // This function can be called from any thread. -// However, textures must not be unregistered while they're in use. -FLUTTER_EXPORT bool FlutterDesktopTextureRegistrarUnregisterExternalTexture( +FLUTTER_EXPORT void FlutterDesktopTextureRegistrarUnregisterExternalTexture( FlutterDesktopTextureRegistrarRef texture_registrar, - int64_t texture_id); + int64_t texture_id, + void (*callback)(void* user_data), + void* user_data); // Marks that a new texture frame is available for a given |texture_id|. // Returns true on success or false if the specified texture doesn't exist. diff --git a/src/flutter/shell/platform/common/text_range.h b/src/flutter/shell/platform/common/text_range.h index a7b3760b..6a0deff6 100644 --- a/src/flutter/shell/platform/common/text_range.h +++ b/src/flutter/shell/platform/common/text_range.h @@ -7,7 +7,7 @@ #include -//#include "flutter/fml/logging.h" +// #include "flutter/fml/logging.h" namespace flutter { @@ -66,7 +66,7 @@ class TextRange { // // Asserts that the range is of length 0. size_t position() const { - //FML_DCHECK(base_ == extent_); + // FML_DCHECK(base_ == extent_); return extent_; } diff --git a/src/flutter/shell/platform/embedder/embedder.h b/src/flutter/shell/platform/embedder/embedder.h index e8b769ef..9e9b875a 100644 --- a/src/flutter/shell/platform/embedder/embedder.h +++ b/src/flutter/shell/platform/embedder/embedder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_EMBEDDER_H_ -#define FLUTTER_EMBEDDER_H_ +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ #include #include @@ -25,9 +25,16 @@ // - Function signatures (names, argument counts, argument order, and argument // type) cannot change. // - The core behavior of existing functions cannot change. +// - Instead of nesting structures by value within another structure/union, +// prefer nesting by pointer. This ensures that adding members to the nested +// struct does not break the ABI of the parent struct/union. +// - Instead of array of structures, prefer array of pointers to structures. +// This ensures that array indexing does not break if members are added +// to the structure. // // These changes are allowed: -// - Adding new struct members at the end of a structure. +// - Adding new struct members at the end of a structure as long as the struct +// is not nested within another struct by value. // - Adding new enum members with a new value. // - Renaming a struct member as long as its type, size, and intent remain the // same. @@ -76,6 +83,7 @@ typedef enum { /// iOS version >= 10.0 (device), 13.0 (simulator) /// macOS version >= 10.14 kMetal, + kVulkan, } FlutterRendererType; /// Additional accessibility features that may be enabled by the platform. @@ -91,8 +99,12 @@ typedef enum { /// Request that text be rendered at a bold font weight. kFlutterAccessibilityFeatureBoldText = 1 << 3, /// Request that certain animations be simplified and parallax effects - // removed. + /// removed. kFlutterAccessibilityFeatureReduceMotion = 1 << 4, + /// Request that UI be rendered with darker colors. + kFlutterAccessibilityFeatureHighContrast = 1 << 5, + /// Request to show on/off labels inside switches. + kFlutterAccessibilityFeatureOnOffSwitchLabels = 1 << 6, } FlutterAccessibilityFeature; /// The set of possible actions that can be conveyed to a semantics node. @@ -148,6 +160,10 @@ typedef enum { kFlutterSemanticsActionMoveCursorForwardByWord = 1 << 19, /// Move the cursor backward by one word. kFlutterSemanticsActionMoveCursorBackwardByWord = 1 << 20, + /// Replace the current text in the text field. + kFlutterSemanticsActionSetText = 1 << 21, + /// Request that the respective focusable widget gain input focus. + kFlutterSemanticsActionFocus = 1 << 22, } FlutterSemanticsAction; /// The set of properties that may be associated with a semantics node. @@ -202,6 +218,12 @@ typedef enum { /// `PageView` widget does not have implicit scrolling, so that users don't /// navigate to the next page when reaching the end of the current one. kFlutterSemanticsFlagHasImplicitScrolling = 1 << 18, + /// Whether the value of the semantics node is coming from a multi-line text + /// field. + /// + /// This is used for text fields to distinguish single-line text fields from + /// multi-line ones. + kFlutterSemanticsFlagIsMultiline = 1 << 19, /// Whether the semantic node is read only. /// /// Only applicable when kFlutterSemanticsFlagIsTextField flag is on. @@ -214,6 +236,13 @@ typedef enum { kFlutterSemanticsFlagIsSlider = 1 << 23, /// Whether the semantics node represents a keyboard key. kFlutterSemanticsFlagIsKeyboardKey = 1 << 24, + /// Whether the semantics node represents a tristate checkbox in mixed state. + kFlutterSemanticsFlagIsCheckStateMixed = 1 << 25, + /// The semantics node has the quality of either being "expanded" or + /// "collapsed". + kFlutterSemanticsFlagHasExpandedState = 1 << 26, + /// Whether a semantic node that hasExpandedState is currently expanded. + kFlutterSemanticsFlagIsExpanded = 1 << 27, } FlutterSemanticsFlag; typedef enum { @@ -225,8 +254,26 @@ typedef enum { kFlutterTextDirectionLTR = 2, } FlutterTextDirection; +/// Valid values for priority of Thread. +typedef enum { + /// Suitable for threads that shouldn't disrupt high priority work. + kBackground = 0, + /// Default priority level. + kNormal = 1, + /// Suitable for threads which generate data for the display. + kDisplay = 2, + /// Suitable for thread which raster data. + kRaster = 3, +} FlutterThreadPriority; + typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); +/// Unique identifier for views. +/// +/// View IDs are generated by the embedder and are +/// opaque to the engine; the engine does not interpret view IDs in any way. +typedef int64_t FlutterViewId; + typedef struct { /// horizontal scale factor double scaleX; @@ -257,8 +304,68 @@ typedef enum { /// Specifies an OpenGL frame-buffer target type. Framebuffers are specified /// using the FlutterOpenGLFramebuffer struct. kFlutterOpenGLTargetTypeFramebuffer, + /// Specifies an OpenGL on-screen surface target type. Surfaces are specified + /// using the FlutterOpenGLSurface struct. + kFlutterOpenGLTargetTypeSurface, } FlutterOpenGLTargetType; +/// A pixel format to be used for software rendering. +/// +/// A single pixel always stored as a POT number of bytes. (so in practice +/// either 1, 2, 4, 8, 16 bytes per pixel) +/// +/// There are two kinds of pixel formats: +/// - formats where all components are 8 bits, called array formats +/// The component order as specified in the pixel format name is the +/// order of the components' bytes in memory, with the leftmost component +/// occupying the lowest memory address. +/// +/// - all other formats are called packed formats, and the component order +/// as specified in the format name refers to the order in the native type. +/// for example, for kFlutterSoftwarePixelFormatRGB565, the R component +/// uses the 5 least significant bits of the uint16_t pixel value. +/// +/// Each pixel format in this list is documented with an example on how to get +/// the color components from the pixel. +/// - for packed formats, p is the pixel value as a word. For example, you can +/// get the pixel value for a RGB565 formatted buffer like this: +/// uint16_t p = ((const uint16_t*) allocation)[row_bytes * y / bpp + x]; +/// (with bpp being the bytes per pixel, so 2 for RGB565) +/// +/// - for array formats, p is a pointer to the pixel value. For example, you +/// can get the p for a RGBA8888 formatted buffer like this: +/// const uint8_t *p = ((const uint8_t*) allocation) + row_bytes*y + x*4; +typedef enum { + /// pixel with 8 bit grayscale value. + /// The grayscale value is the luma value calculated from r, g, b + /// according to BT.709. (gray = r*0.2126 + g*0.7152 + b*0.0722) + kFlutterSoftwarePixelFormatGray8, + + /// pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word. + /// r = p & 0x3F; g = (p>>5) & 0x3F; b = p>>11; + kFlutterSoftwarePixelFormatRGB565, + + /// pixel with 4 bits for alpha, red, green, blue; in 16-bit word. + /// r = p & 0xF; g = (p>>4) & 0xF; b = (p>>8) & 0xF; a = p>>12; + kFlutterSoftwarePixelFormatRGBA4444, + + /// pixel with 8 bits for red, green, blue, alpha. + /// r = p[0]; g = p[1]; b = p[2]; a = p[3]; + kFlutterSoftwarePixelFormatRGBA8888, + + /// pixel with 8 bits for red, green and blue and 8 unused bits. + /// r = p[0]; g = p[1]; b = p[2]; + kFlutterSoftwarePixelFormatRGBX8888, + + /// pixel with 8 bits for blue, green, red and alpha. + /// r = p[2]; g = p[1]; b = p[0]; a = p[3]; + kFlutterSoftwarePixelFormatBGRA8888, + + /// either kFlutterSoftwarePixelFormatBGRA8888 or + /// kFlutterSoftwarePixelFormatRGBA8888 depending on CPU endianess and OS + kFlutterSoftwarePixelFormatNative32, +} FlutterSoftwarePixelFormat; + typedef struct { /// Target texture of the active texture unit (example GL_TEXTURE_2D or /// GL_TEXTURE_RECTANGLE). @@ -283,9 +390,14 @@ typedef struct { } FlutterOpenGLTexture; typedef struct { - /// The target of the color attachment of the frame-buffer. For example, - /// GL_TEXTURE_2D or GL_RENDERBUFFER. In case of ambiguity when dealing with - /// Window bound frame-buffers, 0 may be used. + /// The format of the color attachment of the frame-buffer. For example, + /// GL_RGBA8. + /// + /// In case of ambiguity when dealing with Window bound frame-buffers, 0 may + /// be used. + /// + /// @bug This field is incorrectly named as "target" when it actually + /// refers to a format. uint32_t target; /// The name of the framebuffer. @@ -299,6 +411,62 @@ typedef struct { VoidCallback destruction_callback; } FlutterOpenGLFramebuffer; +typedef bool (*FlutterOpenGLSurfaceCallback)(void* /* user data */, + bool* /* opengl state changed */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterOpenGLSurface). + size_t struct_size; + + /// User data to be passed to the make_current, clear_current and + /// destruction callbacks. + void* user_data; + + /// Callback invoked (on an engine-managed thread) that asks the embedder to + /// make the surface current. + /// + /// Should return true if the operation succeeded, false if the surface could + /// not be made current and rendering should be cancelled. + /// + /// The second parameter 'opengl state changed' should be set to true if + /// any OpenGL API state is different than before this callback was called. + /// In that case, Flutter will invalidate the internal OpenGL API state cache, + /// which is a somewhat expensive operation. + /// + /// @attention required. (non-null) + FlutterOpenGLSurfaceCallback make_current_callback; + + /// Callback invoked (on an engine-managed thread) when the current surface + /// can be cleared. + /// + /// Should return true if the operation succeeded, false if an error ocurred. + /// That error will be logged but otherwise not handled by the engine. + /// + /// The second parameter 'opengl state changed' is the same as with the + /// @ref make_current_callback. + /// + /// The embedder might clear the surface here after it was previously made + /// current. That's not required however, it's also possible to clear it in + /// the destruction callback. There's no way to signal OpenGL state + /// changes in the destruction callback though. + /// + /// @attention required. (non-null) + FlutterOpenGLSurfaceCallback clear_current_callback; + + /// Callback invoked (on an engine-managed thread) that asks the embedder to + /// collect the surface. + /// + /// @attention required. (non-null) + VoidCallback destruction_callback; + + /// The surface format. + /// + /// Allowed values: + /// - GL_RGBA8 + /// - GL_BGRA8_EXT + uint32_t format; +} FlutterOpenGLSurface; + typedef bool (*BoolCallback)(void* /* user data */); typedef FlutterTransformation (*TransformationCallback)(void* /* user data */); typedef uint32_t (*UIntCallback)(void* /* user data */); @@ -352,10 +520,22 @@ typedef struct { FlutterSize lower_left_corner_radius; } FlutterRoundedRect; +/// A structure to represent a damage region. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDamage). + size_t struct_size; + /// The number of rectangles within the damage region. + size_t num_rects; + /// The actual damage region(s) in question. + FlutterRect* damage; +} FlutterDamage; + /// This information is passed to the embedder when requesting a frame buffer /// object. /// -/// See: \ref FlutterOpenGLRendererConfig.fbo_with_frame_info_callback. +/// See: \ref FlutterOpenGLRendererConfig.fbo_with_frame_info_callback, +/// \ref FlutterMetalRendererConfig.get_next_drawable_callback, +/// and \ref FlutterVulkanRendererConfig.get_next_image_callback. typedef struct { /// The size of this struct. Must be sizeof(FlutterFrameInfo). size_t struct_size; @@ -368,6 +548,13 @@ typedef uint32_t (*UIntFrameInfoCallback)( void* /* user data */, const FlutterFrameInfo* /* frame info */); +/// Callback for when a frame buffer object is requested with necessary +/// information for partial repaint. +typedef void (*FlutterFrameBufferWithDamageCallback)( + void* /* user data */, + const intptr_t /* fbo id */, + FlutterDamage* /* existing damage */); + /// This information is passed to the embedder when a surface is presented. /// /// See: \ref FlutterOpenGLRendererConfig.present_with_info. @@ -376,6 +563,10 @@ typedef struct { size_t struct_size; /// Id of the fbo backing the surface that was presented. uint32_t fbo_id; + /// Damage representing the area that the compositor needs to render. + FlutterDamage frame_damage; + /// Damage used to set the buffer's damage region. + FlutterDamage buffer_damage; } FlutterPresentInfo; /// Callback for when a surface is presented. @@ -390,7 +581,10 @@ typedef struct { BoolCallback clear_current; /// Specifying one (and only one) of `present` or `present_with_info` is /// required. Specifying both is an error and engine initialization will be - /// terminated. The return value indicates success of the present call. + /// terminated. The return value indicates success of the present call. If + /// the intent is to use dirty region management, present_with_info must be + /// defined as present will not succeed in communicating information about + /// damage. BoolCallback present; /// Specifying one (and only one) of the `fbo_callback` or /// `fbo_with_frame_info_callback` is required. Specifying both is an error @@ -439,8 +633,27 @@ typedef struct { /// required. Specifying both is an error and engine initialization will be /// terminated. When using this variant, the embedder is passed a /// `FlutterPresentInfo` struct that the embedder can use to release any - /// resources. The return value indicates success of the present call. + /// resources. The return value indicates success of the present call. This + /// callback is essential for dirty region management. If not defined, all the + /// pixels on the screen will be rendered at every frame (regardless of + /// whether damage is actually being computed or not). This is because the + /// information that is passed along to the callback contains the frame and + /// buffer damage that are essential for dirty region management. BoolPresentInfoCallback present_with_info; + /// Specifying this callback is a requirement for dirty region management. + /// Dirty region management will only render the areas of the screen that have + /// changed in between frames, greatly reducing rendering times and energy + /// consumption. To take advantage of these benefits, it is necessary to + /// define populate_existing_damage as a callback that takes user + /// data, an FBO ID, and an existing damage FlutterDamage. The callback should + /// use the given FBO ID to identify the FBO's exisiting damage (i.e. areas + /// that have changed since the FBO was last used) and use it to populate the + /// given existing damage variable. This callback is dependent on either + /// fbo_callback or fbo_with_frame_info_callback being defined as they are + /// responsible for providing populate_existing_damage with the FBO's + /// ID. Not specifying populate_existing_damage will result in full + /// repaint (i.e. rendering all the pixels on the screen at every frame). + FlutterFrameBufferWithDamageCallback populate_existing_damage; } FlutterOpenGLRendererConfig; /// Alias for id. @@ -458,6 +671,12 @@ typedef enum { kRGBA, } FlutterMetalExternalTexturePixelFormat; +/// YUV color space for the YUV external texture. +typedef enum { + kBT601FullRange, + kBT601LimitedRange, +} FlutterMetalExternalTextureYUVColorSpace; + typedef struct { /// The size of this struct. Must be sizeof(FlutterMetalExternalTexture). size_t struct_size; @@ -478,6 +697,8 @@ typedef struct { /// `FlutterEngineUnregisterExternalTexture`, the embedder has to release /// these textures. FlutterMetalTextureHandle* textures; + /// The YUV color space of the YUV external texture. + FlutterMetalExternalTextureYUVColorSpace yuv_color_space; } FlutterMetalExternalTexture; /// Callback to provide an external texture for a given texture_id. @@ -494,11 +715,13 @@ typedef struct { size_t struct_size; /// Embedder provided unique identifier to the texture buffer. Given that the /// `texture` handle is passed to the engine to render to, the texture buffer - /// is itseld owned by the embedder. This `texture_id` is then also given to + /// is itself owned by the embedder. This `texture_id` is then also given to /// the embedder in the present callback. int64_t texture_id; /// Handle to the MTLTexture that is owned by the embedder. Engine will render /// the frame into this texture. + /// + /// A NULL texture is considered invalid. FlutterMetalTextureHandle texture; /// A baton that is not interpreted by the engine in any way. It will be given /// back to the embedder in the destruction callback below. Embedder resources @@ -530,9 +753,13 @@ typedef struct { FlutterMetalCommandQueueHandle present_command_queue; /// The callback that gets invoked when the engine requests the embedder for a /// texture to render to. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalTextureCallback get_next_drawable_callback; /// The callback presented to the embedder to present a fully populated metal /// texture to the user. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalPresentCallback present_drawable_callback; /// When the embedder specifies that a texture has a frame available, the /// engine will call this method (on an internal engine managed thread) so @@ -541,6 +768,115 @@ typedef struct { FlutterMetalTextureFrameCallback external_texture_frame_callback; } FlutterMetalRendererConfig; +/// Alias for VkInstance. +typedef void* FlutterVulkanInstanceHandle; + +/// Alias for VkPhysicalDevice. +typedef void* FlutterVulkanPhysicalDeviceHandle; + +/// Alias for VkDevice. +typedef void* FlutterVulkanDeviceHandle; + +/// Alias for VkQueue. +typedef void* FlutterVulkanQueueHandle; + +/// Alias for VkImage. +typedef uint64_t FlutterVulkanImageHandle; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanImage). + size_t struct_size; + /// Handle to the VkImage that is owned by the embedder. The engine will + /// bind this image for writing the frame. + FlutterVulkanImageHandle image; + /// The VkFormat of the image (for example: VK_FORMAT_R8G8B8A8_UNORM). + uint32_t format; +} FlutterVulkanImage; + +/// Callback to fetch a Vulkan function pointer for a given instance. Normally, +/// this should return the results of vkGetInstanceProcAddr. +typedef void* (*FlutterVulkanInstanceProcAddressCallback)( + void* /* user data */, + FlutterVulkanInstanceHandle /* instance */, + const char* /* name */); + +/// Callback for when a VkImage is requested. +typedef FlutterVulkanImage (*FlutterVulkanImageCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a VkImage has been written to and is ready for use by the +/// embedder. +typedef bool (*FlutterVulkanPresentCallback)( + void* /* user data */, + const FlutterVulkanImage* /* image */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanRendererConfig). + size_t struct_size; + + /// The Vulkan API version. This should match the value set in + /// VkApplicationInfo::apiVersion when the VkInstance was created. + uint32_t version; + /// VkInstance handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanInstanceHandle instance; + /// VkPhysicalDevice handle. + FlutterVulkanPhysicalDeviceHandle physical_device; + /// VkDevice handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanDeviceHandle device; + /// The queue family index of the VkQueue supplied in the next field. + uint32_t queue_family_index; + /// VkQueue handle. + /// The queue should not be used without protection from a mutex to make sure + /// it is not used simultaneously with other threads. That mutex should match + /// the one injected via the |get_instance_proc_address_callback|. + /// There is a proposal to remove the need for the mutex at + /// https://github.com/flutter/flutter/issues/134573. + FlutterVulkanQueueHandle queue; + /// The number of instance extensions available for enumerating in the next + /// field. + size_t enabled_instance_extension_count; + /// Array of enabled instance extension names. This should match the names + /// passed to `VkInstanceCreateInfo.ppEnabledExtensionNames` when the instance + /// was created, but any subset of enabled instance extensions may be + /// specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + const char** enabled_instance_extensions; + /// The number of device extensions available for enumerating in the next + /// field. + size_t enabled_device_extension_count; + /// Array of enabled logical device extension names. This should match the + /// names passed to `VkDeviceCreateInfo.ppEnabledExtensionNames` when the + /// logical device was created, but any subset of enabled logical device + /// extensions may be specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + /// For example: VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME + const char** enabled_device_extensions; + /// The callback invoked when resolving Vulkan function pointers. + /// At a bare minimum this should be used to swap out any calls that operate + /// on vkQueue's for threadsafe variants that obtain locks for their duration. + /// The functions to swap out are "vkQueueSubmit" and "vkQueueWaitIdle". An + /// example of how to do that can be found in the test + /// "EmbedderTest.CanSwapOutVulkanCalls" unit-test in + /// //shell/platform/embedder/tests/embedder_vk_unittests.cc. + FlutterVulkanInstanceProcAddressCallback get_instance_proc_address_callback; + /// The callback invoked when the engine requests a VkImage from the embedder + /// for rendering the next frame. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanImageCallback get_next_image_callback; + /// The callback invoked when a VkImage has been written to and is ready for + /// use by the embedder. Prior to calling this callback, the engine performs + /// a host sync, and so the VkImage can be used in a pipeline by the embedder + /// without any additional synchronization. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanPresentCallback present_image_callback; + +} FlutterVulkanRendererConfig; + typedef struct { /// The size of this struct. Must be sizeof(FlutterSoftwareRendererConfig). size_t struct_size; @@ -557,9 +893,15 @@ typedef struct { FlutterOpenGLRendererConfig open_gl; FlutterSoftwareRendererConfig software; FlutterMetalRendererConfig metal; + FlutterVulkanRendererConfig vulkan; }; } FlutterRendererConfig; +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + typedef struct { /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). size_t struct_size; @@ -581,8 +923,108 @@ typedef struct { double physical_view_inset_bottom; /// Left inset of window. double physical_view_inset_left; + /// The identifier of the display the view is rendering on. + FlutterEngineDisplayId display_id; + /// The view that this event is describing. + int64_t view_id; } FlutterWindowMetricsEvent; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewResult). + size_t struct_size; + + /// True if the add view operation succeeded. + bool added; + + /// The |FlutterAddViewInfo.user_data|. + void* user_data; +} FlutterAddViewResult; + +/// The callback invoked by the engine when the engine has attempted to add a +/// view. +/// +/// The |FlutterAddViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterAddViewCallback)(const FlutterAddViewResult* result); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewInfo). + size_t struct_size; + + /// The identifier for the view to add. This must be unique. + FlutterViewId view_id; + + /// The view's properties. + /// + /// The metric's |view_id| must match this struct's |view_id|. + const FlutterWindowMetricsEvent* view_metrics; + + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in |add_view_callback|. Embedder resources may be + /// associated with this baton. + void* user_data; + + /// Called once the engine has attempted to add the view. This callback is + /// required. + /// + /// The embedder/app must not use the view until the callback is invoked with + /// an `added` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. Embedders + /// must re-thread if necessary. + FlutterAddViewCallback add_view_callback; +} FlutterAddViewInfo; + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewResult). + size_t struct_size; + + /// True if the remove view operation succeeded. + bool removed; + + /// The |FlutterRemoveViewInfo.user_data|. + void* user_data; +} FlutterRemoveViewResult; + +/// The callback invoked by the engine when the engine has attempted to remove +/// a view. +/// +/// The |FlutterRemoveViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterRemoveViewCallback)( + const FlutterRemoveViewResult* /* result */); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewInfo). + size_t struct_size; + + /// The identifier for the view to remove. + /// + /// The implicit view cannot be removed if it is enabled. + FlutterViewId view_id; + + /// A baton that is not interpreted by the engine in any way. + /// It will be given back to the embedder in |remove_view_callback|. + /// Embedder resources may be associated with this baton. + void* user_data; + + /// Called once the engine has attempted to remove the view. + /// This callback is required. + /// + /// The embedder must not destroy the underlying surface until the callback is + /// invoked with a `removed` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. + /// Embedders must re-thread if necessary. + /// + /// The |result| argument will be deallocated when the callback returns. + FlutterRemoveViewCallback remove_view_callback; +} FlutterRemoveViewInfo; + /// The phase of the pointer event. typedef enum { kCancel, @@ -593,7 +1035,7 @@ typedef enum { /// any other buttons are still pressed when one button is released, that /// should be sent as a kMove rather than a kUp. kUp, - /// The pointer, which must have been been up, is now down. + /// The pointer, which must have been up, is now down. /// /// For touch, this means that the pointer has come into contact with the /// screen. For a mouse, it means a button is now pressed. Note that if any @@ -617,6 +1059,12 @@ typedef enum { kRemove, /// The pointer moved while up. kHover, + /// A pan/zoom started on this pointer. + kPanZoomStart, + /// The pan/zoom updated. + kPanZoomUpdate, + /// The pan/zoom ended. + kPanZoomEnd, } FlutterPointerPhase; /// The device type that created a pointer event. @@ -624,6 +1072,7 @@ typedef enum { kFlutterPointerDeviceKindMouse = 1, kFlutterPointerDeviceKindTouch, kFlutterPointerDeviceKindStylus, + kFlutterPointerDeviceKindTrackpad, } FlutterPointerDeviceKind; /// Flags for the `buttons` field of `FlutterPointerEvent` when `device_kind` @@ -642,6 +1091,8 @@ typedef enum { typedef enum { kFlutterPointerSignalKindNone, kFlutterPointerSignalKindScroll, + kFlutterPointerSignalKindScrollInertiaCancel, + kFlutterPointerSignalKindScale, } FlutterPointerSignalKind; typedef struct { @@ -672,6 +1123,16 @@ typedef struct { FlutterPointerDeviceKind device_kind; /// The buttons currently pressed, if any. int64_t buttons; + /// The x offset of the pan/zoom in physical pixels. + double pan_x; + /// The y offset of the pan/zoom in physical pixels. + double pan_y; + /// The scale of the pan/zoom, where 1.0 is the initial scale. + double scale; + /// The rotation of the pan/zoom in radians, where 0.0 is the initial angle. + double rotation; + /// The identifier of the view that received the pointer event. + FlutterViewId view_id; } FlutterPointerEvent; typedef enum { @@ -680,6 +1141,14 @@ typedef enum { kFlutterKeyEventTypeRepeat, } FlutterKeyEventType; +typedef enum { + kFlutterKeyEventDeviceTypeKeyboard = 1, + kFlutterKeyEventDeviceTypeDirectionalPad, + kFlutterKeyEventDeviceTypeGamepad, + kFlutterKeyEventDeviceTypeJoystick, + kFlutterKeyEventDeviceTypeHdmi, +} FlutterKeyEventDeviceType; + /// A structure to represent a key event. /// /// Sending `FlutterKeyEvent` via `FlutterEngineSendKeyEvent` results in a @@ -743,6 +1212,8 @@ typedef struct { /// An event being synthesized means that the `timestamp` might greatly /// deviate from the actual time when the event occurs physically. bool synthesized; + /// The source device for the key event. + FlutterKeyEventDeviceType device_type; } FlutterKeyEvent; typedef void (*FlutterKeyEventCallback)(bool /* handled */, @@ -781,16 +1252,73 @@ typedef void (*FlutterDataCallback)(const uint8_t* /* data */, typedef int64_t FlutterPlatformViewIdentifier; /// `FlutterSemanticsNode` ID used as a sentinel to signal the end of a batch of -/// semantics node updates. +/// semantics node updates. This is unused if using +/// `FlutterUpdateSemanticsCallback2`. FLUTTER_EXPORT extern const int32_t kFlutterSemanticsNodeIdBatchEnd; +// The enumeration of possible string attributes that affect how assistive +// technologies announce a string. +// +// See dart:ui's implementers of the StringAttribute abstract class. +typedef enum { + // Indicates the string should be announced character by character. + kSpellOut, + // Indicates the string should be announced using the specified locale. + kLocale, +} FlutterStringAttributeType; + +// Indicates the assistive technology should announce out the string character +// by character. +// +// See dart:ui's SpellOutStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSpellOutStringAttribute). + size_t struct_size; +} FlutterSpellOutStringAttribute; + +// Indicates the assistive technology should announce the string using the +// specified locale. +// +// See dart:ui's LocaleStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterLocaleStringAttribute). + size_t struct_size; + // The locale of this attribute. + const char* locale; +} FlutterLocaleStringAttribute; + +// Indicates how the assistive technology should treat the string. +// +// See dart:ui's StringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterStringAttribute). + size_t struct_size; + // The position this attribute starts. + size_t start; + // The next position after the attribute ends. + size_t end; + /// The type of the attribute described by the subsequent union. + FlutterStringAttributeType type; + union { + // Indicates the string should be announced character by character. + const FlutterSpellOutStringAttribute* spell_out; + // Indicates the string should be announced using the specified locale. + const FlutterLocaleStringAttribute* locale; + }; +} FlutterStringAttribute; + /// A node that represents some semantic data. /// /// The semantics tree is maintained during the semantics phase of the pipeline /// (i.e., during PipelineOwner.flushSemantics), which happens after /// compositing. Updates are then pushed to embedders via the registered -/// `FlutterUpdateSemanticsNodeCallback`. +/// `FlutterUpdateSemanticsCallback`. +/// +/// @deprecated Use `FlutterSemanticsNode2` instead. In order to preserve +/// ABI compatibility for existing users, no new fields will be +/// added to this struct. New fields will continue to be added +/// to `FlutterSemanticsNode2`. typedef struct { /// The size of this struct. Must be sizeof(FlutterSemanticsNode). size_t struct_size; @@ -832,8 +1360,8 @@ typedef struct { /// A value that `value` will have after a kFlutterSemanticsActionDecrease` /// action has been performed. const char* decreased_value; - /// The reading direction for `label`, `value`, `hint`, `increasedValue`, and - /// `decreasedValue`. + /// The reading direction for `label`, `value`, `hint`, `increasedValue`, + /// `decreasedValue`, and `tooltip`. FlutterTextDirection text_direction; /// The bounding box for this node in its coordinate system. FlutterRect rect; @@ -854,10 +1382,113 @@ typedef struct { /// Identifier of the platform view associated with this semantics node, or /// -1 if none. FlutterPlatformViewIdentifier platform_view_id; + /// A textual tooltip attached to the node. + const char* tooltip; } FlutterSemanticsNode; +/// A node in the Flutter semantics tree. +/// +/// The semantics tree is maintained during the semantics phase of the pipeline +/// (i.e., during PipelineOwner.flushSemantics), which happens after +/// compositing. Updates are then pushed to embedders via the registered +/// `FlutterUpdateSemanticsCallback2`. +/// +/// @see https://api.flutter.dev/flutter/semantics/SemanticsNode-class.html +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSemanticsNode). + size_t struct_size; + /// The unique identifier for this node. + int32_t id; + /// The set of semantics flags associated with this node. + FlutterSemanticsFlag flags; + /// The set of semantics actions applicable to this node. + FlutterSemanticsAction actions; + /// The position at which the text selection originates. + int32_t text_selection_base; + /// The position at which the text selection terminates. + int32_t text_selection_extent; + /// The total number of scrollable children that contribute to semantics. + int32_t scroll_child_count; + /// The index of the first visible semantic child of a scroll node. + int32_t scroll_index; + /// The current scrolling position in logical pixels if the node is + /// scrollable. + double scroll_position; + /// The maximum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_max; + /// The minimum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_min; + /// The elevation along the z-axis at which the rect of this semantics node is + /// located above its parent. + double elevation; + /// Describes how much space the semantics node takes up along the z-axis. + double thickness; + /// A textual description of the node. + const char* label; + /// A brief description of the result of performing an action on the node. + const char* hint; + /// A textual description of the current value of the node. + const char* value; + /// A value that `value` will have after a kFlutterSemanticsActionIncrease` + /// action has been performed. + const char* increased_value; + /// A value that `value` will have after a kFlutterSemanticsActionDecrease` + /// action has been performed. + const char* decreased_value; + /// The reading direction for `label`, `value`, `hint`, `increasedValue`, + /// `decreasedValue`, and `tooltip`. + FlutterTextDirection text_direction; + /// The bounding box for this node in its coordinate system. + FlutterRect rect; + /// The transform from this node's coordinate system to its parent's + /// coordinate system. + FlutterTransformation transform; + /// The number of children this node has. + size_t child_count; + /// Array of child node IDs in traversal order. Has length `child_count`. + const int32_t* children_in_traversal_order; + /// Array of child node IDs in hit test order. Has length `child_count`. + const int32_t* children_in_hit_test_order; + /// The number of custom accessibility action associated with this node. + size_t custom_accessibility_actions_count; + /// Array of `FlutterSemanticsCustomAction` IDs associated with this node. + /// Has length `custom_accessibility_actions_count`. + const int32_t* custom_accessibility_actions; + /// Identifier of the platform view associated with this semantics node, or + /// -1 if none. + FlutterPlatformViewIdentifier platform_view_id; + /// A textual tooltip attached to the node. + const char* tooltip; + // The number of string attributes associated with the `label`. + size_t label_attribute_count; + // Array of string attributes associated with the `label`. + // Has length `label_attribute_count`. + const FlutterStringAttribute** label_attributes; + // The number of string attributes associated with the `hint`. + size_t hint_attribute_count; + // Array of string attributes associated with the `hint`. + // Has length `hint_attribute_count`. + const FlutterStringAttribute** hint_attributes; + // The number of string attributes associated with the `value`. + size_t value_attribute_count; + // Array of string attributes associated with the `value`. + // Has length `value_attribute_count`. + const FlutterStringAttribute** value_attributes; + // The number of string attributes associated with the `increased_value`. + size_t increased_value_attribute_count; + // Array of string attributes associated with the `increased_value`. + // Has length `increased_value_attribute_count`. + const FlutterStringAttribute** increased_value_attributes; + // The number of string attributes associated with the `decreased_value`. + size_t decreased_value_attribute_count; + // Array of string attributes associated with the `decreased_value`. + // Has length `decreased_value_attribute_count`. + const FlutterStringAttribute** decreased_value_attributes; +} FlutterSemanticsNode2; + /// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a -/// batch of semantics custom action updates. +/// batch of semantics custom action updates. This is unused if using +/// `FlutterUpdateSemanticsCallback2`. FLUTTER_EXPORT extern const int32_t kFlutterSemanticsCustomActionIdBatchEnd; @@ -870,6 +1501,11 @@ extern const int32_t kFlutterSemanticsCustomActionIdBatchEnd; /// Action overrides are custom actions that the application developer requests /// to be used in place of the standard actions in the `FlutterSemanticsAction` /// enum. +/// +/// @deprecated Use `FlutterSemanticsCustomAction2` instead. In order to +/// preserve ABI compatility for existing users, no new fields +/// will be added to this struct. New fields will continue to +/// be added to `FlutterSemanticsCustomAction2`. typedef struct { /// The size of the struct. Must be sizeof(FlutterSemanticsCustomAction). size_t struct_size; @@ -884,6 +1520,65 @@ typedef struct { const char* hint; } FlutterSemanticsCustomAction; +/// A custom semantics action, or action override. +/// +/// Custom actions can be registered by applications in order to provide +/// semantic actions other than the standard actions available through the +/// `FlutterSemanticsAction` enum. +/// +/// Action overrides are custom actions that the application developer requests +/// to be used in place of the standard actions in the `FlutterSemanticsAction` +/// enum. +/// +/// @see +/// https://api.flutter.dev/flutter/semantics/CustomSemanticsAction-class.html +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsCustomAction). + size_t struct_size; + /// The unique custom action or action override ID. + int32_t id; + /// For overridden standard actions, corresponds to the + /// `FlutterSemanticsAction` to override. + FlutterSemanticsAction override_action; + /// The user-readable name of this custom semantics action. + const char* label; + /// The hint description of this custom semantics action. + const char* hint; +} FlutterSemanticsCustomAction2; + +/// A batch of updates to semantics nodes and custom actions. +/// +/// @deprecated Use `FlutterSemanticsUpdate2` instead. Adding members +/// to `FlutterSemanticsNode` or `FlutterSemanticsCustomAction` +/// breaks the ABI of this struct. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsUpdate). + size_t struct_size; + /// The number of semantics node updates. + size_t nodes_count; + // Array of semantics nodes. Has length `nodes_count`. + FlutterSemanticsNode* nodes; + /// The number of semantics custom action updates. + size_t custom_actions_count; + /// Array of semantics custom actions. Has length `custom_actions_count`. + FlutterSemanticsCustomAction* custom_actions; +} FlutterSemanticsUpdate; + +/// A batch of updates to semantics nodes and custom actions. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsUpdate2). + size_t struct_size; + /// The number of semantics node updates. + size_t node_count; + // Array of semantics node pointers. Has length `node_count`. + FlutterSemanticsNode2** nodes; + /// The number of semantics custom action updates. + size_t custom_action_count; + /// Array of semantics custom action pointers. Has length + /// `custom_action_count`. + FlutterSemanticsCustomAction2** custom_actions; +} FlutterSemanticsUpdate2; + typedef void (*FlutterUpdateSemanticsNodeCallback)( const FlutterSemanticsNode* /* semantics node */, void* /* user data */); @@ -892,6 +1587,28 @@ typedef void (*FlutterUpdateSemanticsCustomActionCallback)( const FlutterSemanticsCustomAction* /* semantics custom action */, void* /* user data */); +typedef void (*FlutterUpdateSemanticsCallback)( + const FlutterSemanticsUpdate* /* semantics update */, + void* /* user data*/); + +typedef void (*FlutterUpdateSemanticsCallback2)( + const FlutterSemanticsUpdate2* /* semantics update */, + void* /* user data*/); + +/// An update to whether a message channel has a listener set or not. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterChannelUpdate). + size_t struct_size; + /// The name of the channel. + const char* channel; + /// True if a listener has been set, false if one has been cleared. + bool listening; +} FlutterChannelUpdate; + +typedef void (*FlutterChannelUpdateCallback)( + const FlutterChannelUpdate* /* channel update */, + void* /* user data */); + typedef struct _FlutterTaskRunner* FlutterTaskRunner; typedef struct { @@ -946,6 +1663,9 @@ typedef struct { /// and platform task runners. This makes the Flutter engine use the same /// thread for both task runners. const FlutterTaskRunnerDescription* render_task_runner; + /// Specify a callback that is used to set the thread priority for embedder + /// task runners. + void (*thread_priority_setter)(FlutterThreadPriority); } FlutterCustomTaskRunners; typedef struct { @@ -958,6 +1678,9 @@ typedef struct { /// A framebuffer for Flutter to render into. The embedder must ensure that /// the framebuffer is complete. FlutterOpenGLFramebuffer framebuffer; + /// A surface for Flutter to render into. Basically a wrapper around + /// a closure that'll be called when the surface should be made current. + FlutterOpenGLSurface surface; }; } FlutterOpenGLBackingStore; @@ -978,6 +1701,28 @@ typedef struct { VoidCallback destruction_callback; } FlutterSoftwareBackingStore; +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSoftwareBackingStore2). + size_t struct_size; + /// A pointer to the raw bytes of the allocation described by this software + /// backing store. + const void* allocation; + /// The number of bytes in a single row of the allocation. + size_t row_bytes; + /// The number of rows in the allocation. + size_t height; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; + /// The pixel format that the engine should use to render into the allocation. + /// In most cases, kR + FlutterSoftwarePixelFormat pixel_format; +} FlutterSoftwareBackingStore2; + typedef struct { /// The size of this struct. Must be sizeof(FlutterMetalBackingStore). size_t struct_size; @@ -989,6 +1734,25 @@ typedef struct { }; } FlutterMetalBackingStore; +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanBackingStore). + size_t struct_size; + /// The image that the layer will be rendered to. This image must already be + /// available for the engine to bind for writing when it's given to the engine + /// via the backing store creation callback. The engine will perform a host + /// sync for all layers prior to calling the compositor present callback, and + /// so the written layer images can be freely bound by the embedder without + /// any additional synchronization. + const FlutterVulkanImage* image; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; +} FlutterVulkanBackingStore; + typedef enum { /// Indicates that the Flutter application requested that an opacity be /// applied to the platform view. @@ -1048,6 +1812,11 @@ typedef enum { kFlutterBackingStoreTypeSoftware, /// Specifies a Metal backing store. This is backed by a Metal texture. kFlutterBackingStoreTypeMetal, + /// Specifies a Vulkan backing store. This is backed by a Vulkan VkImage. + kFlutterBackingStoreTypeVulkan, + /// Specifies a allocation that the engine should render into using + /// software rendering. + kFlutterBackingStoreTypeSoftware2, } FlutterBackingStoreType; typedef struct { @@ -1067,8 +1836,12 @@ typedef struct { FlutterOpenGLBackingStore open_gl; /// The description of the software backing store. FlutterSoftwareBackingStore software; + /// The description of the software backing store. + FlutterSoftwareBackingStore2 software2; // The description of the Metal backing store. FlutterMetalBackingStore metal; + // The description of the Vulkan backing store. + FlutterVulkanBackingStore vulkan; }; } FlutterBackingStore; @@ -1077,6 +1850,9 @@ typedef struct { size_t struct_size; /// The size of the render target the engine expects to render into. FlutterSize size; + /// The identifier for the view that the engine will use this backing store to + /// render into. + FlutterViewId view_id; } FlutterBackingStoreConfig; typedef enum { @@ -1087,6 +1863,28 @@ typedef enum { kFlutterLayerContentTypePlatformView, } FlutterLayerContentType; +/// A region represented by a collection of non-overlapping rectangles. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterRegion). + size_t struct_size; + /// Number of rectangles in the region. + size_t rects_count; + /// The rectangles that make up the region. + FlutterRect* rects; +} FlutterRegion; + +/// Contains additional information about the backing store provided +/// during presentation to the embedder. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStorePresentInfo). + size_t struct_size; + + /// The area of the backing store that contains Flutter contents. Pixels + /// outside of this area are transparent and the embedder may choose not + /// to render them. Coordinates are in physical pixels. + FlutterRegion* paint_region; +} FlutterBackingStorePresentInfo; + typedef struct { /// This size of this struct. Must be sizeof(FlutterLayer). size_t struct_size; @@ -1106,8 +1904,34 @@ typedef struct { FlutterPoint offset; /// The size of the layer (in physical pixels). FlutterSize size; + + /// Extra information for the backing store that the embedder may + /// use during presentation. + FlutterBackingStorePresentInfo* backing_store_present_info; + + // Time in nanoseconds at which this frame is scheduled to be presented. 0 if + // not known. See FlutterEngineGetCurrentTime(). + uint64_t presentation_time; } FlutterLayer; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterPresentViewInfo). + size_t struct_size; + + /// The identifier of the target view. + FlutterViewId view_id; + + /// The layers that should be composited onto the view. + const FlutterLayer** layers; + + /// The count of layers. + size_t layers_count; + + /// The |FlutterCompositor.user_data|. + void* user_data; +} FlutterPresentViewInfo; + typedef bool (*FlutterBackingStoreCreateCallback)( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out, @@ -1121,13 +1945,20 @@ typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, size_t layers_count, void* user_data); +/// The callback invoked when the embedder should present to a view. +/// +/// The |FlutterPresentViewInfo| will be deallocated once the callback returns. +typedef bool (*FlutterPresentViewCallback)( + const FlutterPresentViewInfo* /* present info */); + typedef struct { /// This size of this struct. Must be sizeof(FlutterCompositor). size_t struct_size; /// A baton that in not interpreted by the engine in any way. If it passed /// back to the embedder in `FlutterCompositor.create_backing_store_callback`, - /// `FlutterCompositor.collect_backing_store_callback` and - /// `FlutterCompositor.present_layers_callback` + /// `FlutterCompositor.collect_backing_store_callback`, + /// `FlutterCompositor.present_layers_callback`, and + /// `FlutterCompositor.present_view_callback`. void* user_data; /// A callback invoked by the engine to obtain a backing store for a specific /// `FlutterLayer`. @@ -1136,15 +1967,38 @@ typedef struct { /// `FlutterBackingStore::struct_size` when specifying a new backing store to /// the engine. This only matters if the embedder expects to be used with /// engines older than the version whose headers it used during compilation. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCreateCallback create_backing_store_callback; /// A callback invoked by the engine to release the backing store. The /// embedder may collect any resources associated with the backing store. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCollectCallback collect_backing_store_callback; /// Callback invoked by the engine to composite the contents of each layer - /// onto the screen. + /// onto the implicit view. + /// + /// DEPRECATED: Use `present_view_callback` to support multiple views. + /// If this callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. FlutterLayersPresentCallback present_layers_callback; /// Avoid caching backing stores provided by this compositor. bool avoid_backing_store_cache; + /// Callback invoked by the engine to composite the contents of each layer + /// onto the specified view. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. + FlutterPresentViewCallback present_view_callback; } FlutterCompositor; typedef struct { @@ -1183,13 +2037,8 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( const FlutterLocale** /* supported_locales*/, size_t /* Number of locales*/); -/// Display refers to a graphics hardware system consisting of a framebuffer, -/// typically a monitor or a screen. This ID is unique per display and is -/// stable until the Flutter application restarts. -typedef uint64_t FlutterEngineDisplayId; - typedef struct { - /// This size of this struct. Must be sizeof(FlutterDisplay). + /// The size of this struct. Must be sizeof(FlutterEngineDisplay). size_t struct_size; FlutterEngineDisplayId display_id; @@ -1203,6 +2052,16 @@ typedef struct { /// This represents the refresh period in frames per second. This value may be /// zero if the device is not running or unavailable or unknown. double refresh_rate; + + /// The width of the display, in physical pixels. + size_t width; + + /// The height of the display, in physical pixels. + size_t height; + + /// The pixel ratio of the display, which is used to convert physical pixels + /// to logical pixels. + double device_pixel_ratio; } FlutterEngineDisplay; /// The update type parameter that is passed to @@ -1435,24 +2294,44 @@ typedef struct { /// The callback invoked by the engine in root isolate scope. Called /// immediately after the root isolate has been created and marked runnable. VoidCallback root_isolate_create_callback; - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to semantics node updates from the Dart application. + /// The legacy callback invoked by the engine in order to give the embedder + /// the chance to respond to semantics node updates from the Dart application. /// Semantics node updates are sent in batches terminated by a 'batch end' /// callback that is passed a sentinel `FlutterSemanticsNode` whose `id` field /// has the value `kFlutterSemanticsNodeIdBatchEnd`. /// /// The callback will be invoked on the thread on which the `FlutterEngineRun` /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsNodeCallback update_semantics_node_callback; - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to updates to semantics custom actions from the Dart - /// application. Custom action updates are sent in batches terminated by a + /// The legacy callback invoked by the engine in order to give the embedder + /// the chance to respond to updates to semantics custom actions from the Dart + /// application. Custom action updates are sent in batches terminated by a /// 'batch end' callback that is passed a sentinel /// `FlutterSemanticsCustomAction` whose `id` field has the value /// `kFlutterSemanticsCustomActionIdBatchEnd`. /// /// The callback will be invoked on the thread on which the `FlutterEngineRun` /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsCustomActionCallback update_semantics_custom_action_callback; /// Path to a directory used to store data that is cached across runs of a @@ -1589,10 +2468,47 @@ typedef struct { // // The first argument is the `user_data` from `FlutterEngineInitialize`. OnPreEngineRestartCallback on_pre_engine_restart_callback; + + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to updates to semantics nodes and custom actions from + /// the Dart application. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// must be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. + FlutterUpdateSemanticsCallback update_semantics_callback; + + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to updates to semantics nodes and custom actions from + /// the Dart application. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// Only one of `update_semantics_node_callback`, `update_semantics_callback`, + /// and `update_semantics_callback2` may be provided; the others must be set + /// to null. + FlutterUpdateSemanticsCallback2 update_semantics_callback2; + + /// The callback invoked by the engine in response to a channel listener + /// being registered on the framework side. The callback is invoked from + /// a task posted to the platform thread. + FlutterChannelUpdateCallback channel_update_callback; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES +// NOLINTBEGIN(google-objc-function-naming) + //------------------------------------------------------------------------------ /// @brief Creates the necessary data structures to launch a Flutter Dart /// application in AOT mode. The data may only be collected after @@ -1734,6 +2650,63 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineRunInitialized( FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Adds a view. +/// +/// This is an asynchronous operation. The view should not be used +/// until the |info.add_view_callback| is invoked with an |added| +/// value of true. The embedder should prepare resources in advance +/// but be ready to clean up on failure. +/// +/// A frame is scheduled if the operation succeeds. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to add the implicit view will fail and will return +/// kInvalidArguments. Attempting to add a view with an already +/// existing view ID will fail, and |info.add_view_callback| will be +/// invoked with an |added| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The add view arguments. This can be deallocated +/// once |FlutterEngineAddView| returns, before +/// |add_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |add_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineAddView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterAddViewInfo* info); + +//------------------------------------------------------------------------------ +/// @brief Removes a view. +/// +/// This is an asynchronous operation. The view's resources must not +/// be cleaned up until |info.remove_view_callback| is invoked with +/// a |removed| value of true. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to remove the implicit view will fail and will return +/// kInvalidArguments. Attempting to remove a view with a +/// non-existent view ID will fail, and |info.remove_view_callback| +/// will be invoked with a |removed| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The remove view arguments. This can be deallocated +/// once |FlutterEngineRemoveView| returns, before +/// |remove_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |remove_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterRemoveViewInfo* info); + FLUTTER_EXPORT FlutterEngineResult FlutterEngineSendWindowMetricsEvent( FLUTTER_API_SYMBOL(FlutterEngine) engine, @@ -1930,8 +2903,8 @@ FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable( /// @param[in] engine A running engine instance. /// @param[in] enabled When enabled, changes to the semantic contents of the /// window are sent via the -/// `FlutterUpdateSemanticsNodeCallback` registered to -/// `update_semantics_node_callback` in +/// `FlutterUpdateSemanticsCallback2` registered to +/// `update_semantics_callback2` in /// `FlutterProjectArgs`. /// /// @return The result of the call. @@ -1958,7 +2931,7 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( /// @brief Dispatch a semantics action to the specified semantics node. /// /// @param[in] engine A running engine instance. -/// @param[in] identifier The semantics action identifier. +/// @param[in] node_id The semantics node identifier. /// @param[in] action The semantics action. /// @param[in] data Data associated with the action. /// @param[in] data_length The data length. @@ -1968,7 +2941,7 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( FLUTTER_EXPORT FlutterEngineResult FlutterEngineDispatchSemanticsAction( FLUTTER_API_SYMBOL(FlutterEngine) engine, - uint64_t id, + uint64_t node_id, FlutterSemanticsAction action, const uint8_t* data, size_t data_length); @@ -2251,6 +3224,37 @@ FlutterEngineResult FlutterEngineNotifyDisplayUpdate( const FlutterEngineDisplay* displays, size_t display_count); +//------------------------------------------------------------------------------ +/// @brief Schedule a new frame to redraw the content. +/// +/// @param[in] engine A running engine instance. +/// +/// @return the result of the call made to the engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) + engine); + +//------------------------------------------------------------------------------ +/// @brief Schedule a callback to be called after the next frame is drawn. +/// This must be called from the platform thread. The callback is +/// executed only once from the raster thread; embedders must +/// re-thread if necessary. Performing blocking calls +/// in this callback may introduce application jank. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback to execute. +/// @param[in] user_data A baton passed by the engine to the callback. This +/// baton is not interpreted by the engine in any way. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); + #endif // !FLUTTER_ENGINE_NO_PROTOTYPES // Typedefs for the function pointers in FlutterEngineProcTable. @@ -2367,6 +3371,18 @@ typedef FlutterEngineResult (*FlutterEngineNotifyDisplayUpdateFnPtr)( FlutterEngineDisplaysUpdateType update_type, const FlutterEngineDisplay* displays, size_t display_count); +typedef FlutterEngineResult (*FlutterEngineScheduleFrameFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); +typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterAddViewInfo* info); +typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterRemoveViewInfo* info); /// Function-pointer-based versions of the APIs above. typedef struct { @@ -2411,6 +3427,10 @@ typedef struct { FlutterEnginePostCallbackOnAllNativeThreadsFnPtr PostCallbackOnAllNativeThreads; FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; + FlutterEngineScheduleFrameFnPtr ScheduleFrame; + FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; + FlutterEngineAddViewFnPtr AddView; + FlutterEngineRemoveViewFnPtr RemoveView; } FlutterEngineProcTable; //------------------------------------------------------------------------------ @@ -2425,8 +3445,10 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEngineProcTable* table); +// NOLINTEND(google-objc-function-naming) + #if defined(__cplusplus) } // extern "C" #endif -#endif // FLUTTER_EMBEDDER_H_ +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ diff --git a/src/flutter/shell/platform/embedder/embedder_struct_macros.h b/src/flutter/shell/platform/embedder/embedder_struct_macros.h new file mode 100644 index 00000000..483fd2ab --- /dev/null +++ b/src/flutter/shell/platform/embedder/embedder_struct_macros.h @@ -0,0 +1,32 @@ +// 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_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_ + +#include + +// Checks if the given struct contains a member, whether set or not. +#define STRUCT_HAS_MEMBER(pointer, member) \ + ((offsetof(std::remove_pointer::type, member) + \ + sizeof(pointer->member) <= \ + pointer->struct_size)) + +#define SAFE_ACCESS(pointer, member, default_value) \ + ([=]() { \ + if (STRUCT_HAS_MEMBER(pointer, member)) { \ + return pointer->member; \ + } \ + return static_castmember)>((default_value)); \ + })() + +/// Checks if the member exists and is non-null. +#define SAFE_EXISTS(pointer, member) \ + (SAFE_ACCESS(pointer, member, nullptr) != nullptr) + +/// Checks if exactly one of member1 or member2 exists and is non-null. +#define SAFE_EXISTS_ONE_OF(pointer, member1, member2) \ + (SAFE_EXISTS(pointer, member1) != SAFE_EXISTS(pointer, member2)) + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_ diff --git a/src/flutter/shell/platform/linux_embedded/external_texture.h b/src/flutter/shell/platform/linux_embedded/external_texture.h new file mode 100644 index 00000000..0a4ce93d --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/external_texture.h @@ -0,0 +1,64 @@ +// 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_WINDOWS_EXTERNAL_TEXTURE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_EXTERNAL_TEXTURE_H_ + +#include "flutter/shell/platform/embedder/embedder.h" + +#ifdef USE_GLES3 +#include +#else +#include +#include +#endif + +namespace flutter { + +typedef void (*glGenTexturesProc)(GLsizei n, GLuint* textures); +typedef void (*glDeleteTexturesProc)(GLsizei n, const GLuint* textures); +typedef void (*glBindTextureProc)(GLenum target, GLuint texture); +typedef void (*glTexParameteriProc)(GLenum target, GLenum pname, GLint param); +typedef void (*glTexImage2DProc)(GLenum target, + GLint level, + GLint internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLenum format, + GLenum type, + const void* data); +typedef void (*glEGLImageTargetTexture2DOESProc)(GLenum target, + GLeglImageOES image); + +// A struct containing pointers to resolved gl* functions. +struct GlProcs { + glGenTexturesProc glGenTextures; + glDeleteTexturesProc glDeleteTextures; + glBindTextureProc glBindTexture; + glTexParameteriProc glTexParameteri; + glTexImage2DProc glTexImage2D; + glEGLImageTargetTexture2DOESProc glEGLImageTargetTexture2DOES; + bool valid; +}; + +// Abstract external texture. +class ExternalTexture { + public: + virtual ~ExternalTexture() = default; + + // Returns the unique id of this texture. + int64_t texture_id() const { return reinterpret_cast(this); }; + + // Attempts to populate the specified |opengl_texture| with texture details + // such as the name, width, height and the pixel format. + // Returns true on success. + virtual bool PopulateTexture(size_t width, + size_t height, + FlutterOpenGLTexture* opengl_texture) = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_EXTERNAL_TEXTURE_H_ diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.cc b/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.cc new file mode 100644 index 00000000..843b2f98 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.cc @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux_embedded/external_texture_egl_image.h" +#include +#include + +namespace flutter { + +struct ExternalTextureEGLImageState { + GLuint gl_texture = 0; +}; + +ExternalTextureEGLImage::ExternalTextureEGLImage( + FlutterDesktopEGLImageTextureCallback texture_callback, + void* user_data, + const GlProcs& gl_procs) + : state_(std::make_unique()), + texture_callback_(texture_callback), + user_data_(user_data), + gl_(gl_procs) {} + +ExternalTextureEGLImage::~ExternalTextureEGLImage() { + if (state_->gl_texture != 0) { + gl_.glDeleteTextures(1, &state_->gl_texture); + } +} + +bool ExternalTextureEGLImage::PopulateTexture( + size_t width, + size_t height, + FlutterOpenGLTexture* opengl_texture) { + if (!GetEGLImage(width, height, eglGetCurrentDisplay(), + eglGetCurrentContext())) { + return false; + } + + // Populate the texture object used by the engine. + opengl_texture->target = GL_TEXTURE_2D; + opengl_texture->name = state_->gl_texture; +#ifdef USE_GLES3 + opengl_texture->format = GL_RGBA8; +#else + opengl_texture->format = GL_RGBA8_OES; +#endif + opengl_texture->destruction_callback = nullptr; + opengl_texture->user_data = nullptr; + opengl_texture->width = width; + opengl_texture->height = height; + + return true; +} + +bool ExternalTextureEGLImage::GetEGLImage(size_t& width, + size_t& height, + void* egl_display, + void* egl_context) { + using namespace std; + + const FlutterDesktopEGLImage* egl_image = + texture_callback_(width, height, egl_display, egl_context, user_data_); + if (!egl_image || !egl_image->egl_image) { + return false; + } + width = egl_image->width; + height = egl_image->height; + + if (state_->gl_texture == 0) { + gl_.glGenTextures(1, &state_->gl_texture); + + gl_.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + gl_.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); + } + gl_.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, + (EGLImageKHR)egl_image->egl_image); + + if (egl_image->release_callback) { + egl_image->release_callback(egl_image->release_context); + } + return true; +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.h b/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.h new file mode 100644 index 00000000..7dfaaeb4 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/external_texture_egl_image.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_LINUX_EMBEDDED_EXTERNAL_TEXTURE_EGL_IMAGE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_EGL_IMAGE_H_ + +#include + +#include + +#include "flutter/shell/platform/common/public/flutter_texture_registrar.h" + +#include "flutter/shell/platform/linux_embedded/external_texture.h" + +namespace flutter { + +typedef struct ExternalTextureEGLImageState ExternalTextureEGLImageState; + +// An abstraction of an EGL Image based texture. +class ExternalTextureEGLImage : public ExternalTexture { + public: + ExternalTextureEGLImage( + FlutterDesktopEGLImageTextureCallback texture_callback, + void* user_data, + const GlProcs& gl_procs); + + virtual ~ExternalTextureEGLImage(); + + // |ExternalTexture| + bool PopulateTexture(size_t width, + size_t height, + FlutterOpenGLTexture* opengl_texture) override; + + private: + // Attempts to get the EGLImage returned by |texture_callback_| to + // OpenGL. + // The |width| and |height| will be set to the actual bounds of the EGLImage + // Returns true on success or false if the EGLImage returned + // by |texture_callback_| was invalid. + bool GetEGLImage(size_t& width, + size_t& height, + void* egl_display, + void* egl_context); + + std::unique_ptr state_; + FlutterDesktopEGLImageTextureCallback texture_callback_ = nullptr; + void* const user_data_ = nullptr; + const GlProcs& gl_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_EGL_IMAGE_H_ diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_gl.cc b/src/flutter/shell/platform/linux_embedded/external_texture_gl.cc deleted file mode 100644 index 0b0935ea..00000000 --- a/src/flutter/shell/platform/linux_embedded/external_texture_gl.cc +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/shell/platform/linux_embedded/external_texture_gl.h" - -#include - -#ifdef USE_GLES3 -#include -#else -#include -#include -#endif - -namespace { - -typedef void (*glGenTexturesProc)(GLsizei n, GLuint* textures); -typedef void (*glDeleteTexturesProc)(GLsizei n, const GLuint* textures); -typedef void (*glBindTextureProc)(GLenum target, GLuint texture); -typedef void (*glTexParameteriProc)(GLenum target, GLenum pname, GLint param); -typedef void (*glTexImage2DProc)(GLenum target, - GLint level, - GLint internalformat, - GLsizei width, - GLsizei height, - GLint border, - GLenum format, - GLenum type, - const void* data); - -// A struct containing pointers to resolved gl* functions. -struct GlProcs { - glGenTexturesProc glGenTextures; - glDeleteTexturesProc glDeleteTextures; - glBindTextureProc glBindTexture; - glTexParameteriProc glTexParameteri; - glTexImage2DProc glTexImage2D; - bool valid; -}; - -static const GlProcs& GlProcs() { - static struct GlProcs procs = {}; - static bool initialized = false; - if (!initialized) { - procs.glGenTextures = - reinterpret_cast(eglGetProcAddress("glGenTextures")); - procs.glDeleteTextures = reinterpret_cast( - eglGetProcAddress("glDeleteTextures")); - procs.glBindTexture = - reinterpret_cast(eglGetProcAddress("glBindTexture")); - procs.glTexParameteri = reinterpret_cast( - eglGetProcAddress("glTexParameteri")); - procs.glTexImage2D = - reinterpret_cast(eglGetProcAddress("glTexImage2D")); - - procs.valid = procs.glGenTextures && procs.glDeleteTextures && - procs.glBindTexture && procs.glTexParameteri && - procs.glTexImage2D; - initialized = true; - } - return procs; -} - -} // namespace - -namespace flutter { - -struct ExternalTextureGLState { - GLuint gl_texture = 0; -}; - -ExternalTextureGL::ExternalTextureGL( - FlutterDesktopPixelBufferTextureCallback texture_callback, - void* user_data) - : state_(std::make_unique()), - texture_callback_(texture_callback), - user_data_(user_data) {} - -ExternalTextureGL::~ExternalTextureGL() { - const auto& gl = GlProcs(); - if (gl.valid && state_->gl_texture != 0) { - gl.glDeleteTextures(1, &state_->gl_texture); - } -} - -bool ExternalTextureGL::PopulateTexture(size_t width, - size_t height, - FlutterOpenGLTexture* opengl_texture) { - if (!CopyPixelBuffer(width, height)) { - return false; - } - - // Populate the texture object used by the engine. - opengl_texture->target = GL_TEXTURE_2D; - opengl_texture->name = state_->gl_texture; -#ifdef USE_GLES3 - opengl_texture->format = GL_RGBA8; -#else - opengl_texture->format = GL_RGBA8_OES; -#endif - opengl_texture->destruction_callback = nullptr; - opengl_texture->user_data = nullptr; - opengl_texture->width = width; - opengl_texture->height = height; - - return true; -} - -bool ExternalTextureGL::CopyPixelBuffer(size_t& width, size_t& height) { - const FlutterDesktopPixelBuffer* pixel_buffer = - texture_callback_(width, height, user_data_); - const auto& gl = GlProcs(); - if (!gl.valid || !pixel_buffer || !pixel_buffer->buffer) { - return false; - } - width = pixel_buffer->width; - height = pixel_buffer->height; - - if (state_->gl_texture == 0) { - gl.glGenTextures(1, &state_->gl_texture); - - gl.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); - -#ifdef USE_GLES3 - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); -#else - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - GL_CLAMP_TO_BORDER_OES); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - GL_CLAMP_TO_BORDER_OES); -#endif - - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - } else { - gl.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); - } -#ifdef USE_GLES3 - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pixel_buffer->width, - pixel_buffer->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, - pixel_buffer->buffer); -#else - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixel_buffer->width, - pixel_buffer->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, - pixel_buffer->buffer); -#endif - return true; -} - -} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_gl.h b/src/flutter/shell/platform/linux_embedded/external_texture_gl.h deleted file mode 100644 index 45819d6c..00000000 --- a/src/flutter/shell/platform/linux_embedded/external_texture_gl.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_GL_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_GL_H_ - -#include - -#include - -#include "flutter/shell/platform/common/public/flutter_texture_registrar.h" -#include "flutter/shell/platform/embedder/embedder.h" - -namespace flutter { - -typedef struct ExternalTextureGLState ExternalTextureGLState; - -// An abstraction of an OpenGL texture. -class ExternalTextureGL { - public: - ExternalTextureGL(FlutterDesktopPixelBufferTextureCallback texture_callback, - void* user_data); - - virtual ~ExternalTextureGL(); - - // Returns the unique id of this texture. - int64_t texture_id() { return reinterpret_cast(this); } - - void MarkFrameAvailable(); - - // Attempts to populate the specified |opengl_texture| with texture details - // such as the name, width, height and the pixel format upon successfully - // copying the buffer provided by |texture_callback_|. See |CopyPixelBuffer|. - // Returns true on success or false if the pixel buffer could not be copied. - bool PopulateTexture(size_t width, - size_t height, - FlutterOpenGLTexture* opengl_texture); - - private: - // Attempts to copy the pixel buffer returned by |texture_callback_| to - // OpenGL. - // The |width| and |height| will be set to the actual bounds of the copied - // pixel buffer. - // Returns true on success or false if the pixel buffer returned - // by |texture_callback_| was invalid. - bool CopyPixelBuffer(size_t& width, size_t& height); - - std::unique_ptr state_; - FlutterDesktopPixelBufferTextureCallback texture_callback_ = nullptr; - void* user_data_ = nullptr; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_GL_H_ diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.cc b/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.cc new file mode 100644 index 00000000..1b962691 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.cc @@ -0,0 +1,82 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.h" + +namespace flutter { + +struct ExternalTexturePixelBufferState { + GLuint gl_texture = 0; +}; + +ExternalTexturePixelBuffer::ExternalTexturePixelBuffer( + FlutterDesktopPixelBufferTextureCallback texture_callback, + void* user_data, + const GlProcs& gl_procs) + : state_(std::make_unique()), + texture_callback_(texture_callback), + user_data_(user_data), + gl_(gl_procs) {} + +ExternalTexturePixelBuffer::~ExternalTexturePixelBuffer() { + if (state_->gl_texture != 0) { + gl_.glDeleteTextures(1, &state_->gl_texture); + } +} + +bool ExternalTexturePixelBuffer::PopulateTexture( + size_t width, + size_t height, + FlutterOpenGLTexture* opengl_texture) { + if (!CopyPixelBuffer(width, height)) { + return false; + } + + // Populate the texture object used by the engine. + opengl_texture->target = GL_TEXTURE_2D; + opengl_texture->name = state_->gl_texture; +#ifdef USE_GLES3 + opengl_texture->format = GL_RGBA8; +#else + opengl_texture->format = GL_RGBA8_OES; +#endif + opengl_texture->destruction_callback = nullptr; + opengl_texture->user_data = nullptr; + opengl_texture->width = width; + opengl_texture->height = height; + + return true; +} + +bool ExternalTexturePixelBuffer::CopyPixelBuffer(size_t& width, + size_t& height) { + const FlutterDesktopPixelBuffer* pixel_buffer = + texture_callback_(width, height, user_data_); + if (!pixel_buffer || !pixel_buffer->buffer) { + return false; + } + width = pixel_buffer->width; + height = pixel_buffer->height; + + if (state_->gl_texture == 0) { + gl_.glGenTextures(1, &state_->gl_texture); + + gl_.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl_.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + gl_.glBindTexture(GL_TEXTURE_2D, state_->gl_texture); + } + gl_.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixel_buffer->width, + pixel_buffer->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixel_buffer->buffer); + if (pixel_buffer->release_callback) { + pixel_buffer->release_callback(pixel_buffer->release_context); + } + return true; +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.h b/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.h new file mode 100644 index 00000000..0d6dc67c --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.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_LINUX_EMBEDDED_EXTERNAL_TEXTURE_PIXELBUFFER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_PIXELBUFFER_H_ + +#include + +#include + +#include "flutter/shell/platform/common/public/flutter_texture_registrar.h" + +#include "flutter/shell/platform/linux_embedded/external_texture.h" + +namespace flutter { + +typedef struct ExternalTexturePixelBufferState ExternalTexturePixelBufferState; + +// An abstraction of an pixel-buffer based texture. +class ExternalTexturePixelBuffer : public ExternalTexture { + public: + ExternalTexturePixelBuffer( + FlutterDesktopPixelBufferTextureCallback texture_callback, + void* user_data, + const GlProcs& gl_procs); + + virtual ~ExternalTexturePixelBuffer(); + + // |ExternalTexture| + bool PopulateTexture(size_t width, + size_t height, + FlutterOpenGLTexture* opengl_texture) override; + + private: + // Attempts to copy the pixel buffer returned by |texture_callback_| to + // OpenGL. + // The |width| and |height| will be set to the actual bounds of the copied + // pixel buffer. + // Returns true on success or false if the pixel buffer returned + // by |texture_callback_| was invalid. + bool CopyPixelBuffer(size_t& width, size_t& height); + + std::unique_ptr state_; + FlutterDesktopPixelBufferTextureCallback texture_callback_ = nullptr; + void* const user_data_ = nullptr; + const GlProcs& gl_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_EXTERNAL_TEXTURE_PIXELBUFFER_H_ diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux.cc b/src/flutter/shell/platform/linux_embedded/flutter_elinux.cc index 1279155b..2d3587f3 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux.cc @@ -72,37 +72,38 @@ uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) { } FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( - const FlutterDesktopViewProperties& view_properties, + const FlutterDesktopViewProperties* view_properties, FlutterDesktopEngineRef engine) { std::unique_ptr window_wrapper = #if defined(DISPLAY_BACKEND_TYPE_DRM_GBM) std::make_unique>( - view_properties); + *view_properties); #elif defined(DISPLAY_BACKEND_TYPE_DRM_EGLSTREAM) std::make_unique< flutter::ELinuxWindowDrm>( - view_properties); + *view_properties); #elif defined(DISPLAY_BACKEND_TYPE_X11) - std::make_unique(view_properties); + std::make_unique(*view_properties); #else - std::make_unique(view_properties); + std::make_unique(*view_properties); #endif auto state = std::make_unique(); state->view = std::make_unique(std::move(window_wrapper)); - if (!state->view->CreateRenderSurface()) { - return nullptr; - } - // Take ownership of the engine, starting it if necessary. state->view->SetEngine( std::unique_ptr(EngineFromHandle(engine))); + state->view->CreateRenderSurface(); if (!state->view->GetEngine()->running()) { if (!state->view->GetEngine()->RunWithEntrypoint(nullptr)) { return nullptr; } + + const float text_scaling_factor = view_properties->text_scale_factor; + state->view->GetEngine()->SetSystemSettings( + text_scaling_factor, view_properties->enable_high_contrast); } // Must happen after engine is running. @@ -134,8 +135,8 @@ int32_t FlutterDesktopViewGetFrameRate(FlutterDesktopViewRef view) { } FlutterDesktopEngineRef FlutterDesktopEngineCreate( - const FlutterDesktopEngineProperties& engine_properties) { - flutter::FlutterProjectBundle project(engine_properties); + const FlutterDesktopEngineProperties* engine_properties) { + flutter::FlutterProjectBundle project(*engine_properties); auto engine = std::make_unique(project); return HandleForEngine(engine.release()); } @@ -198,22 +199,36 @@ void FlutterDesktopPluginRegistrarSetDestructionHandler( registrar->engine->SetPluginRegistrarDestructionCallback(callback); } -bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, - const char* channel, - const uint8_t* message, - const size_t message_size, - const FlutterDesktopBinaryReply reply, - void* user_data) { - return messenger->engine->SendPlatformMessage(channel, message, message_size, - reply, user_data); +bool FlutterDesktopMessengerSendWithReply( + FlutterDesktopMessengerRef messenger, + const char* channel, + const uint8_t* message, + const size_t message_size, + const FlutterDesktopBinaryReply reply, + void* user_data, + void (*cleanup)(void* captures_data)) { + // As we pass data to lambda and it's pointers we need to make sure that we + // send valid data + std::string channel_copy(channel); + std::vector message_copy(message, message + message_size); + + messenger->GetEngine()->task_runner()->PostTask([=]() { + if (!messenger->GetEngine()->SendPlatformMessage( + channel_copy.c_str(), message_copy.data(), message_copy.size(), + reply, user_data) && + user_data) { + cleanup(user_data); + } + }); + return true; } bool FlutterDesktopMessengerSend(FlutterDesktopMessengerRef messenger, const char* channel, const uint8_t* message, const size_t message_size) { - return FlutterDesktopMessengerSendWithReply(messenger, channel, message, - message_size, nullptr, nullptr); + return FlutterDesktopMessengerSendWithReply( + messenger, channel, message, message_size, nullptr, nullptr, nullptr); } void FlutterDesktopMessengerSendResponse( @@ -221,15 +236,40 @@ void FlutterDesktopMessengerSendResponse( const FlutterDesktopMessageResponseHandle* handle, const uint8_t* data, size_t data_length) { - messenger->engine->SendPlatformMessageResponse(handle, data, data_length); + messenger->GetEngine()->SendPlatformMessageResponse(handle, data, + data_length); } void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger, const char* channel, FlutterDesktopMessageCallback callback, void* user_data) { - messenger->engine->message_dispatcher()->SetMessageCallback(channel, callback, - user_data); + messenger->GetEngine()->message_dispatcher()->SetMessageCallback( + channel, callback, user_data); +} + +FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef( + FlutterDesktopMessengerRef messenger) { + messenger->AddRef(); + return messenger; +} + +void FlutterDesktopMessengerRelease(FlutterDesktopMessengerRef messenger) { + messenger->Release(); +} + +bool FlutterDesktopMessengerIsAvailable(FlutterDesktopMessengerRef messenger) { + return messenger->GetEngine() != nullptr; +} + +FlutterDesktopMessengerRef FlutterDesktopMessengerLock( + FlutterDesktopMessengerRef messenger) { + messenger->GetMutex().lock(); + return messenger; +} + +void FlutterDesktopMessengerUnlock(FlutterDesktopMessengerRef messenger) { + messenger->GetMutex().unlock(); } FlutterDesktopTextureRegistrarRef FlutterDesktopRegistrarGetTextureRegistrar( @@ -244,11 +284,18 @@ int64_t FlutterDesktopTextureRegistrarRegisterExternalTexture( ->RegisterTexture(texture_info); } -bool FlutterDesktopTextureRegistrarUnregisterExternalTexture( +void FlutterDesktopTextureRegistrarUnregisterExternalTexture( FlutterDesktopTextureRegistrarRef texture_registrar, - int64_t texture_id) { - return TextureRegistrarFromHandle(texture_registrar) - ->UnregisterTexture(texture_id); + int64_t texture_id, + void (*callback)(void* user_data), + void* user_data) { + auto registrar = TextureRegistrarFromHandle(texture_registrar); + if (callback) { + registrar->UnregisterTexture( + texture_id, [callback, user_data]() { callback(user_data); }); + return; + } + registrar->UnregisterTexture(texture_id); } bool FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable( diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.cc b/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.cc index 6df08e56..45a922e2 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.cc @@ -42,6 +42,25 @@ FlutterRendererConfig GetRendererConfig() { } return host->view()->ClearCurrent(); }; + config.open_gl.fbo_reset_after_present = true; +#if defined(USE_OPENGL_DIRTY_REGION_MANAGEMENT) + config.open_gl.present_with_info = + [](void* user_data, const FlutterPresentInfo* info) -> bool { + auto host = static_cast(user_data); + if (!host->view()) { + return false; + } + return host->view()->PresentWithInfo(info); + }; + config.open_gl.populate_existing_damage = + [](void* user_data, const intptr_t fbo_id, + FlutterDamage* existing_damage) -> void { + auto host = static_cast(user_data); + if (host->view()) { + host->view()->PopulateExistingDamage(fbo_id, existing_damage); + } + }; +#else config.open_gl.present = [](void* user_data) -> bool { auto host = static_cast(user_data); if (!host->view()) { @@ -49,6 +68,7 @@ FlutterRendererConfig GetRendererConfig() { } return host->view()->Present(); }; +#endif config.open_gl.fbo_callback = [](void* user_data) -> uint32_t { auto host = static_cast(user_data); if (!host->view()) { @@ -122,24 +142,26 @@ FlutterELinuxEngine::FlutterELinuxEngine(const FlutterProjectBundle& project) } }); + // Check for impeller support. + auto& switches = project_->GetSwitches(); + enable_impeller_ = std::find(switches.begin(), switches.end(), + "--enable-impeller=true") != switches.end(); + // Set up the legacy structs backing the API handles. - messenger_ = std::make_unique(); - messenger_->engine = this; + messenger_ = FlutterDesktopMessengerReferenceOwner( + FlutterDesktopMessengerAddRef(new FlutterDesktopMessenger()), + &FlutterDesktopMessengerRelease); + messenger_->SetEngine(this); plugin_registrar_ = std::make_unique(); plugin_registrar_->engine = this; messenger_wrapper_ = std::make_unique(messenger_.get()); message_dispatcher_ = std::make_unique(messenger_.get()); - texture_registrar_ = std::make_unique(this); - // Set up internal channels. - // TODO: Replace this with an embedder.h API. See - // https://github.com/flutter/flutter/issues/71099 - settings_channel_ = - std::make_unique>( - messenger_wrapper_.get(), "flutter/settings", - &JsonMessageCodec::GetInstance()); + FlutterELinuxTextureRegistrar::ResolveGlFunctions(gl_procs_); + texture_registrar_ = + std::make_unique(this, gl_procs_); vsync_waiter_ = std::make_unique(); } @@ -148,6 +170,11 @@ FlutterELinuxEngine::~FlutterELinuxEngine() { Stop(); } +void FlutterELinuxEngine::SetSwitches( + const std::vector& switches) { + project_->SetSwitches(switches); +} + bool FlutterELinuxEngine::RunWithEntrypoint(const char* entrypoint) { if (!project_->HasValidPaths()) { ELINUX_LOG(ERROR) << "Missing or unresolvable paths to assets."; @@ -213,10 +240,7 @@ bool FlutterELinuxEngine::RunWithEntrypoint(const char* entrypoint) { auto host = static_cast(user_data); return host->HandlePlatformMessage(engine_message); }; -// todo: disable vsync temporarily because flutter apps will freeze when we use -// this interface. See also: -// https://github.com/sony/flutter-embedded-linux/issues/176 -#if 0 +#if defined(ENABLE_VSYNC) // todo: add drm/x11 support. // https://github.com/sony/flutter-embedded-linux/issues/136 // https://github.com/sony/flutter-embedded-linux/issues/137 @@ -253,8 +277,6 @@ bool FlutterELinuxEngine::RunWithEntrypoint(const char* entrypoint) { return false; } - SendSystemSettings(); - return true; } @@ -349,38 +371,40 @@ void FlutterELinuxEngine::HandlePlatformMessage( auto message = ConvertToDesktopMessage(*engine_message); - message_dispatcher_->HandleMessage( - message, [this] {}, [this] {}); + message_dispatcher_->HandleMessage(message, [this] {}, [this] {}); } void FlutterELinuxEngine::ReloadSystemFonts() { embedder_api_.ReloadSystemFonts(engine_); } -void FlutterELinuxEngine::SendSystemSettings() { +void FlutterELinuxEngine::SetSystemSettings(float text_scaling_factor, + bool enable_high_contrast) { + if (text_scaling_factor == 0) { + ELINUX_LOG(WARNING) << "text-scaling-factor value must be greater than 0"; + text_scaling_factor = 1.0; + } + + view_->UpdateTextScaleFactor(text_scaling_factor); + view_->UpdateHighContrastEnabled(enable_high_contrast); + SendSystemLocales(); +} + +void FlutterELinuxEngine::SendSystemLocales() { auto languages = flutter::GetPreferredLanguageInfo(); auto flutter_locales = flutter::ConvertToFlutterLocale(languages); // Convert the locale list to the locale pointer list that must be provided. std::vector flutter_locale_list; flutter_locale_list.reserve(flutter_locales.size()); - std::transform( - flutter_locales.begin(), flutter_locales.end(), - std::back_inserter(flutter_locale_list), - [](const auto& arg) -> const auto* { return &arg; }); + std::transform(flutter_locales.begin(), flutter_locales.end(), + std::back_inserter(flutter_locale_list), + [](const auto& arg) -> const auto* { return &arg; }); auto result = embedder_api_.UpdateLocales(engine_, flutter_locale_list.data(), flutter_locale_list.size()); if (result != kSuccess) { ELINUX_LOG(ERROR) << "Failed to set up Flutter locales."; } - - rapidjson::Document settings(rapidjson::kObjectType); - auto& allocator = settings.GetAllocator(); - // todo: Use set values instead of fixed values. - settings.AddMember("alwaysUse24HourFormat", true, allocator); - settings.AddMember("textScaleFactor", 1.0, allocator); - settings.AddMember("platformBrightness", "light", allocator); - settings_channel_->Send(settings); } bool FlutterELinuxEngine::RegisterExternalTexture(int64_t texture_id) { @@ -399,6 +423,26 @@ bool FlutterELinuxEngine::MarkExternalTextureFrameAvailable( engine_, texture_id) == kSuccess); } +bool FlutterELinuxEngine::PostRasterThreadTask(fml::closure callback) { + struct Captures { + fml::closure callback; + }; + auto captures = new Captures(); + captures->callback = std::move(callback); + if (embedder_api_.PostRenderThreadTask( + engine_, + [](void* opaque) { + auto captures = reinterpret_cast(opaque); + captures->callback(); + delete captures; + }, + captures) == kSuccess) { + return true; + } + delete captures; + return false; +} + void FlutterELinuxEngine::OnVsync(uint64_t last_frame_time_nanos, uint64_t vsync_interval_time_nanos) { uint64_t current_time_nanos = embedder_api_.GetCurrentTime(); @@ -414,4 +458,17 @@ void FlutterELinuxEngine::OnVsync(uint64_t last_frame_time_nanos, frame_target_time_nanos); } +void FlutterELinuxEngine::UpdateAccessibilityFeatures( + FlutterAccessibilityFeature flags) { + embedder_api_.UpdateAccessibilityFeatures(engine_, flags); +} + +void FlutterELinuxEngine::UpdateDisplayInfo( + FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* displays, + size_t display_count) { + embedder_api_.NotifyDisplayUpdate(engine_, update_type, displays, + display_count); +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.h b/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.h index 11a378b6..c7eeee4d 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.h @@ -27,6 +27,10 @@ namespace flutter { class FlutterELinuxView; +using FlutterDesktopMessengerReferenceOwner = + std::unique_ptr; + class FlutterELinuxEngine { public: explicit FlutterELinuxEngine(const FlutterProjectBundle& project); @@ -64,7 +68,12 @@ class FlutterELinuxEngine { void SetPluginRegistrarDestructionCallback( FlutterDesktopOnPluginRegistrarDestroyed callback); - FlutterDesktopMessengerRef messenger() { return messenger_.get(); } + // Sets switches member to the given switches. + void SetSwitches(const std::vector& switches); + + FlutterDesktopMessengerRef messenger() SWIFT_RETURNS_UNRETAINED { + return messenger_.get(); + } IncomingMessageDispatcher* message_dispatcher() { return message_dispatcher_.get(); @@ -113,19 +122,36 @@ class FlutterELinuxEngine { // given |texture_id|. bool MarkExternalTextureFrameAvailable(int64_t texture_id); + // Posts the given callback onto the raster thread. + bool PostRasterThreadTask(fml::closure callback); + // Notifies the engine about the vsync event. void OnVsync(uint64_t last_frame_time_nanos, uint64_t vsync_interval_time_nanos); + // Gets the status whether Impeller is enabled. + bool IsImpellerEnabled() const { return enable_impeller_; } + + // Sets system settings. + void SetSystemSettings(float text_scaling_factor, bool enable_high_contrast); + + // Updates accessibility, e.g. switch to high contrast mode + void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags); + + // Update display information. + void UpdateDisplayInfo(FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* displays, + size_t display_count); + private: // Allows swapping out embedder_api_ calls in tests. friend class EngineEmbedderApiModifier; - // Sends system settings (e.g., locale) to the engine. + // Sends system locales to the engine. // // Should be called just after the engine is run, and after any relevant // system changes. - void SendSystemSettings(); + void SendSystemLocales(); // The handle to the embedder.h engine instance. FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr; @@ -144,7 +170,8 @@ class FlutterELinuxEngine { std::unique_ptr task_runner_; // The plugin messenger handle given to API clients. - std::unique_ptr messenger_; + FlutterDesktopMessengerReferenceOwner messenger_ = { + nullptr, [](FlutterDesktopMessengerRef ref) {}}; // A wrapper around messenger_ for interacting with client_wrapper-level APIs. std::unique_ptr messenger_wrapper_; @@ -158,8 +185,8 @@ class FlutterELinuxEngine { // The texture registrar. std::unique_ptr texture_registrar_; - // The MethodChannel used for communication with the Flutter engine. - std::unique_ptr> settings_channel_; + // Resolved OpenGL functions used by external texture implementations. + GlProcs gl_procs_ = {}; // A callback to be called when the engine (and thus the plugin registrar) // is being destroyed. @@ -168,7 +195,9 @@ class FlutterELinuxEngine { // The vsync waiter. std::unique_ptr vsync_waiter_; -}; + + bool enable_impeller_ = false; +} SWIFT_UNSAFE_REFERENCE; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_state.h b/src/flutter/shell/platform/linux_embedded/flutter_elinux_state.h index 92a9d582..061f1e87 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_state.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_state.h @@ -5,7 +5,9 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_FLUTTER_ELINUX_STATE_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_FLUTTER_ELINUX_STATE_H_ +#include #include +#include #include "flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h" #include "flutter/shell/platform/common/incoming_message_dispatcher.h" @@ -26,6 +28,8 @@ struct FlutterELinuxView; struct FlutterDesktopViewControllerState { // The view that backs this state object. std::unique_ptr view; + + ~FlutterDesktopViewControllerState() = default; }; // Wrapper to distinguish the plugin registrar ref from the engine ref given out @@ -38,8 +42,50 @@ struct FlutterDesktopPluginRegistrar { // Wrapper to distinguish the messenger ref from the engine ref given out // in the C API. struct FlutterDesktopMessenger { - // The engine that owns this state object. - flutter::FlutterELinuxEngine* engine = nullptr; -}; + FlutterDesktopMessenger() = default; + + /// Increments the reference count. + /// + /// Thread-safe. + void AddRef() { ref_count_.fetch_add(1); } + + /// Decrements the reference count and deletes the object if the count has + /// gone to zero. + /// + /// Thread-safe. + void Release() { + int32_t old_count = ref_count_.fetch_sub(1); + if (old_count <= 1) { + delete this; + } + } + + /// Getter for the engine field. + flutter::FlutterELinuxEngine* GetEngine() const { return engine_; } + + /// Setter for the engine field. + /// Thread-safe. + void SetEngine(flutter::FlutterELinuxEngine* engine) { + std::scoped_lock lock(mutex_); + engine_ = engine; + } + + /// Returns the mutex associated with the |FlutterDesktopMessenger|. + /// + /// This mutex is used to synchronize reading or writing state inside the + /// |FlutterDesktopMessenger| (ie |engine_|). + std::mutex& GetMutex() { return mutex_; } + + FlutterDesktopMessenger(const FlutterDesktopMessenger& value) = delete; + FlutterDesktopMessenger& operator=(const FlutterDesktopMessenger& value) = + delete; + + private: + // The engine that backs this messenger. + flutter::FlutterELinuxEngine* engine_; + std::atomic ref_count_ = 0; + std::mutex mutex_; +} SWIFT_SHARED_REFERENCE(FlutterDesktopMessengerAddRef, + FlutterDesktopMessengerRelease); #endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_FLUTTER_ELINUX_STATE_H_ diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.cc b/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.cc index 8899c5fc..66a603c3 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.cc @@ -4,38 +4,65 @@ #include "flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.h" -#include "flutter/shell/platform/linux_embedded/flutter_elinux_engine.h" - #include #include +#include "flutter/shell/platform/embedder/embedder_struct_macros.h" +#include "flutter/shell/platform/linux_embedded/external_texture_egl_image.h" +#include "flutter/shell/platform/linux_embedded/external_texture_pixelbuffer.h" +#include "flutter/shell/platform/linux_embedded/flutter_elinux_engine.h" +#include "flutter/shell/platform/linux_embedded/flutter_elinux_view.h" + +namespace { +static constexpr int64_t kInvalidTexture = -1; +} + namespace flutter { FlutterELinuxTextureRegistrar::FlutterELinuxTextureRegistrar( - FlutterELinuxEngine* engine) - : engine_(engine) {} + FlutterELinuxEngine* engine, + const GlProcs& gl_procs) + : engine_(engine), gl_procs_(gl_procs) {} int64_t FlutterELinuxTextureRegistrar::RegisterTexture( const FlutterDesktopTextureInfo* texture_info) { - if (texture_info->type != kFlutterDesktopPixelBufferTexture) { - std::cerr << "Attempted to register texture of unsupport type." - << std::endl; - return -1; + if (!gl_procs_.valid) { + return kInvalidTexture; } - if (!texture_info->pixel_buffer_config.callback) { - std::cerr << "Invalid pixel buffer texture callback." << std::endl; - return -1; + if (texture_info->type == kFlutterDesktopPixelBufferTexture) { + if (!texture_info->pixel_buffer_config.callback) { + std::cerr << "Invalid pixel buffer texture callback." << std::endl; + return kInvalidTexture; + } + + return EmplaceTexture(std::make_unique( + texture_info->pixel_buffer_config.callback, + texture_info->pixel_buffer_config.user_data, gl_procs_)); + } else if (texture_info->type == kFlutterDesktopEGLImageTexture) { + if (!texture_info->egl_image_config.callback) { + std::cerr << "Invalid EGLImage texture callback." << std::endl; + return kInvalidTexture; + } + + return EmplaceTexture(std::make_unique( + texture_info->egl_image_config.callback, + texture_info->egl_image_config.user_data, gl_procs_)); + } else if (texture_info->type == kFlutterDesktopGpuSurfaceTexture) { + std::cerr << "GpuSurfaceTexture is not yet supported." << std::endl; + return kInvalidTexture; } - auto texture_gl = std::make_unique( - texture_info->pixel_buffer_config.callback, - texture_info->pixel_buffer_config.user_data); - int64_t texture_id = texture_gl->texture_id(); + std::cerr << "Attempted to register texture of unsupport type." << std::endl; + return kInvalidTexture; +} +int64_t FlutterELinuxTextureRegistrar::EmplaceTexture( + std::unique_ptr texture) { + int64_t texture_id = texture->texture_id(); { std::lock_guard lock(map_mutex_); - textures_[texture_id] = std::move(texture_gl); + textures_[texture_id] = std::move(texture); } engine_->task_runner()->RunNowOrPostTask([engine = engine_, texture_id]() { @@ -45,20 +72,28 @@ int64_t FlutterELinuxTextureRegistrar::RegisterTexture( return texture_id; } -bool FlutterELinuxTextureRegistrar::UnregisterTexture(int64_t texture_id) { - { - std::lock_guard lock(map_mutex_); - auto it = textures_.find(texture_id); - if (it == textures_.end()) { - return false; - } - textures_.erase(it); - } - +void FlutterELinuxTextureRegistrar::UnregisterTexture(int64_t texture_id, + fml::closure callback) { engine_->task_runner()->RunNowOrPostTask([engine = engine_, texture_id]() { engine->UnregisterExternalTexture(texture_id); }); - return true; + + bool posted = engine_->PostRasterThreadTask([this, texture_id, callback]() { + { + std::lock_guard lock(map_mutex_); + auto it = textures_.find(texture_id); + if (it != textures_.end()) { + textures_.erase(it); + } + } + if (callback) { + callback(); + } + }); + + if (!posted && callback) { + callback(); + } } bool FlutterELinuxTextureRegistrar::MarkTextureFrameAvailable( @@ -74,7 +109,7 @@ bool FlutterELinuxTextureRegistrar::PopulateTexture( size_t width, size_t height, FlutterOpenGLTexture* opengl_texture) { - flutter::ExternalTextureGL* texture; + flutter::ExternalTexture* texture; { std::lock_guard lock(map_mutex_); auto it = textures_.find(texture_id); @@ -86,4 +121,23 @@ bool FlutterELinuxTextureRegistrar::PopulateTexture( return texture->PopulateTexture(width, height, opengl_texture); } +void FlutterELinuxTextureRegistrar::ResolveGlFunctions(GlProcs& procs) { + procs.glGenTextures = + reinterpret_cast(eglGetProcAddress("glGenTextures")); + procs.glDeleteTextures = reinterpret_cast( + eglGetProcAddress("glDeleteTextures")); + procs.glBindTexture = + reinterpret_cast(eglGetProcAddress("glBindTexture")); + procs.glTexParameteri = reinterpret_cast( + eglGetProcAddress("glTexParameteri")); + procs.glTexImage2D = + reinterpret_cast(eglGetProcAddress("glTexImage2D")); + procs.glEGLImageTargetTexture2DOES = + reinterpret_cast( + eglGetProcAddress("glEGLImageTargetTexture2DOES")); + procs.valid = procs.glGenTextures && procs.glDeleteTextures && + procs.glBindTexture && procs.glTexParameteri && + procs.glTexImage2D && procs.glEGLImageTargetTexture2DOES; +} + }; // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.h b/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.h index 0b46a1fa..6e75cb00 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_texture_registrar.h @@ -9,7 +9,9 @@ #include #include -#include "flutter/shell/platform/linux_embedded/external_texture_gl.h" +#include "flutter/fml/closure.h" +#include "flutter/shell/platform/common/public/flutter_texture_registrar.h" +#include "flutter/shell/platform/linux_embedded/external_texture.h" namespace flutter { @@ -19,15 +21,15 @@ class FlutterELinuxEngine; // Thread safety: All member methods are thread safe. class FlutterELinuxTextureRegistrar { public: - explicit FlutterELinuxTextureRegistrar(FlutterELinuxEngine* engine); + explicit FlutterELinuxTextureRegistrar(FlutterELinuxEngine* engine, + const GlProcs& gl_procs); // Registers a texture described by the given |texture_info| object. // Returns the non-zero, positive texture id or -1 on error. int64_t RegisterTexture(const FlutterDesktopTextureInfo* texture_info); // Attempts to unregister the texture identified by |texture_id|. - // Returns true if the texture was successfully unregistered. - bool UnregisterTexture(int64_t texture_id); + void UnregisterTexture(int64_t texture_id, fml::closure callback = nullptr); // Notifies the engine about a new frame being available. // Returns true on success. @@ -41,14 +43,20 @@ class FlutterELinuxTextureRegistrar { size_t height, FlutterOpenGLTexture* texture); + // Populates the OpenGL function pointers in |gl_procs|. + static void ResolveGlFunctions(GlProcs& gl_procs); + private: FlutterELinuxEngine* engine_ = nullptr; + const GlProcs& gl_procs_; // All registered textures, keyed by their IDs. - std::unordered_map> + std::unordered_map> textures_; std::mutex map_mutex_; -}; + + int64_t EmplaceTexture(std::unique_ptr texture); +} SWIFT_UNSAFE_REFERENCE; }; // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.cc b/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.cc index d5461760..9775d200 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.cc @@ -7,6 +7,7 @@ #include #include +#include "flutter/common/constants.h" #include "flutter/shell/platform/linux_embedded/logger.h" namespace flutter { @@ -60,6 +61,10 @@ void FlutterELinuxView::SetEngine(std::unique_ptr engine) { // Set up the system channel handlers. auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); + + // Set up internal channels. + // TODO: Replace this with an embedder.h API. See + // https://github.com/flutter/flutter/issues/71099 keyboard_handler_ = std::make_unique(internal_plugin_messenger); textinput_handler_ = std::make_unique( @@ -74,6 +79,8 @@ void FlutterELinuxView::SetEngine(std::unique_ptr engine) { std::make_unique(internal_plugin_messenger); platform_views_handler_ = std::make_unique(internal_plugin_messenger); + settings_handler_ = std::make_unique( + internal_plugin_messenger, binding_handler_.get()); PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); SendWindowMetrics(bounds.width, bounds.height, @@ -86,36 +93,37 @@ void FlutterELinuxView::RegisterPlatformViewFactory( platform_views_handler_->RegisterViewFactory(view_type, std::move(factory)); } -void FlutterELinuxView::OnWindowSizeChanged(size_t width, size_t height) const { - if (!GetRenderSurfaceTarget()->OnScreenSurfaceResize(width, height)) { +void FlutterELinuxView::OnWindowSizeChanged(size_t width_px, + size_t height_px) const { + if (!GetRenderSurfaceTarget()->OnScreenSurfaceResize(width_px, height_px)) { ELINUX_LOG(ERROR) << "Failed to change surface size."; return; } - SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); + SendWindowMetrics(width_px, height_px, binding_handler_->GetDpiScale()); } -void FlutterELinuxView::OnPointerMove(double x, double y) { - auto trimmed_xy = GetPointerRotation(x, y); +void FlutterELinuxView::OnPointerMove(double x_px, double y_px) { + auto trimmed_xy = GetPointerRotation(x_px, y_px); SendPointerMove(trimmed_xy.first, trimmed_xy.second); } void FlutterELinuxView::OnPointerDown( - double x, - double y, + double x_px, + double y_px, FlutterPointerMouseButtons flutter_button) { if (flutter_button != 0) { uint64_t mouse_buttons = mouse_state_.buttons | flutter_button; - auto trimmed_xy = GetPointerRotation(x, y); + auto trimmed_xy = GetPointerRotation(x_px, y_px); SetMouseButtons(mouse_buttons); SendPointerDown(trimmed_xy.first, trimmed_xy.second); } } -void FlutterELinuxView::OnPointerUp(double x, - double y, +void FlutterELinuxView::OnPointerUp(double x_px, + double y_px, FlutterPointerMouseButtons flutter_button) { if (flutter_button != 0) { - auto trimmed_xy = GetPointerRotation(x, y); + auto trimmed_xy = GetPointerRotation(x_px, y_px); uint64_t mouse_buttons = mouse_state_.buttons & ~flutter_button; SetMouseButtons(mouse_buttons); SendPointerUp(trimmed_xy.first, trimmed_xy.second); @@ -130,6 +138,13 @@ void FlutterELinuxView::OnTouchDown(uint32_t time, int32_t id, double x, double y) { + // Increase device-id to avoid + // "FML_DCHECK(states_.find(pointer_data.device) == states_.end());" + // exception in flutter/engine. + // This is because "device-id = 0" is used for mouse inputs. + // See engine/lib/ui/window/pointer_data_packet_converter.cc + id += 1; + auto trimmed_xy = GetPointerRotation(x, y); auto* point = GgeTouchPoint(id); if (!point) { @@ -151,15 +166,33 @@ void FlutterELinuxView::OnTouchDown(uint32_t time, .scroll_delta_y = 0, .device_kind = kFlutterPointerDeviceKindTouch, .buttons = 0, + // TODO: Use the correct view ID for pointer events once the + // eLinux embedder supports multiple views. + .view_id = flutter::kFlutterImplicitViewId, }; engine_->SendPointerEvent(event); } void FlutterELinuxView::OnTouchUp(uint32_t time, int32_t id) { + // Increase device-id to avoid + // "FML_DCHECK(states_.find(pointer_data.device) == states_.end());" + // exception in flutter/engine. + // This is because "device-id = 0" is used for mouse inputs. + // See engine/lib/ui/window/pointer_data_packet_converter.cc + id += 1; + auto* point = GgeTouchPoint(id); if (!point) { return; } + + // Makes sure we have an existing touch pointer in down state to + // avoid "FML_DCHECK(iter != states_.end())" exception in flutter/engine. + // See engine/lib/ui/window/pointer_data_packet_converter.cc + if (point->event_mask != TouchEvent::kDown && + point->event_mask != TouchEvent::kMotion) { + return; + } point->event_mask = TouchEvent::kUp; FlutterPointerEvent event = { @@ -174,6 +207,9 @@ void FlutterELinuxView::OnTouchUp(uint32_t time, int32_t id) { .scroll_delta_y = 0, .device_kind = kFlutterPointerDeviceKindTouch, .buttons = 0, + // TODO: Use the correct view ID for pointer events once the + // eLinux embedder supports multiple views. + .view_id = flutter::kFlutterImplicitViewId, }; engine_->SendPointerEvent(event); } @@ -182,11 +218,26 @@ void FlutterELinuxView::OnTouchMotion(uint32_t time, int32_t id, double x, double y) { + // Increase device-id to avoid avoid + // "FML_DCHECK(states_.find(pointer_data.device) == states_.end());" + // exception in flutter/engine. + // This is because "device-id = 0" is used for mouse inputs. + // See engine/lib/ui/window/pointer_data_packet_converter.cc + id += 1; + auto trimmed_xy = GetPointerRotation(x, y); auto* point = GgeTouchPoint(id); if (!point) { return; } + + // Makes sure we have an existing touch pointer in down state to + // avoid "FML_DCHECK(iter != states_.end())" exception in flutter/engine. + // See engine/lib/ui/window/pointer_data_packet_converter.cc + if (point->event_mask != TouchEvent::kDown && + point->event_mask != TouchEvent::kMotion) { + return; + } point->event_mask = TouchEvent::kMotion; point->x = trimmed_xy.first; point->y = trimmed_xy.second; @@ -203,6 +254,9 @@ void FlutterELinuxView::OnTouchMotion(uint32_t time, .scroll_delta_y = 0, .device_kind = kFlutterPointerDeviceKindTouch, .buttons = 0, + // TODO: Use the correct view ID for pointer events once the + // eLinux embedder supports multiple views. + .view_id = flutter::kFlutterImplicitViewId, }; engine_->SendPointerEvent(event); } @@ -277,13 +331,13 @@ FlutterELinuxView::touch_point* FlutterELinuxView::GgeTouchPoint(int32_t id) { } // Sends new size information to FlutterEngine. -void FlutterELinuxView::SendWindowMetrics(size_t width, - size_t height, +void FlutterELinuxView::SendWindowMetrics(size_t width_px, + size_t height_px, double dpiScale) const { FlutterWindowMetricsEvent event = {}; event.struct_size = sizeof(event); - event.width = width; - event.height = height; + event.width = width_px; + event.height = height_px; event.pixel_ratio = dpiScale; engine_->SendWindowMetricsEvent(event); } @@ -301,35 +355,35 @@ void FlutterELinuxView::SetEventPhaseFromCursorButtonState( // For details about this logic, see FlutterPointerPhase in the embedder.h // file. event_data->phase = - mouse_state_.buttons == 0 - ? mouse_state_.flutter_state_is_down ? FlutterPointerPhase::kUp - : FlutterPointerPhase::kHover - : mouse_state_.flutter_state_is_down ? FlutterPointerPhase::kMove - : FlutterPointerPhase::kDown; + mouse_state_.buttons == 0 ? mouse_state_.flutter_state_is_down + ? FlutterPointerPhase::kUp + : FlutterPointerPhase::kHover + : mouse_state_.flutter_state_is_down ? FlutterPointerPhase::kMove + : FlutterPointerPhase::kDown; } -void FlutterELinuxView::SendPointerMove(double x, double y) { +void FlutterELinuxView::SendPointerMove(double x_px, double y_px) { FlutterPointerEvent event = {}; - event.x = x; - event.y = y; + event.x = x_px; + event.y = y_px; SetEventPhaseFromCursorButtonState(&event); SendPointerEventWithData(event); } -void FlutterELinuxView::SendPointerDown(double x, double y) { +void FlutterELinuxView::SendPointerDown(double x_px, double y_px) { FlutterPointerEvent event = {}; SetEventPhaseFromCursorButtonState(&event); - event.x = x; - event.y = y; + event.x = x_px; + event.y = y_px; SendPointerEventWithData(event); SetMouseFlutterStateDown(true); } -void FlutterELinuxView::SendPointerUp(double x, double y) { +void FlutterELinuxView::SendPointerUp(double x_px, double y_px) { FlutterPointerEvent event = {}; SetEventPhaseFromCursorButtonState(&event); - event.x = x; - event.y = y; + event.x = x_px; + event.y = y_px; SendPointerEventWithData(event); if (event.phase == FlutterPointerPhase::kUp) { SetMouseFlutterStateDown(false); @@ -380,6 +434,9 @@ void FlutterELinuxView::SendPointerEventWithData( FlutterPointerEvent event = event_data; event.device_kind = kFlutterPointerDeviceKindMouse; event.buttons = mouse_state_.buttons; + // TODO: Use the correct view ID for pointer events once the + // eLinux embedder supports multiple views. + event.view_id = flutter::kFlutterImplicitViewId; // Set metadata that's always the same regardless of the event. event.struct_size = sizeof(event); @@ -414,6 +471,15 @@ bool FlutterELinuxView::Present() { return GetRenderSurfaceTarget()->GLContextPresent(0); } +bool FlutterELinuxView::PresentWithInfo(const FlutterPresentInfo* info) { + return GetRenderSurfaceTarget()->GLContextPresentWithInfo(info); +} + +void FlutterELinuxView::PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) { + GetRenderSurfaceTarget()->PopulateExistingDamage(fbo_id, existing_damage); +} + uint32_t FlutterELinuxView::GetOnscreenFBO() { return GetRenderSurfaceTarget()->GLContextFBO(); } @@ -424,7 +490,9 @@ bool FlutterELinuxView::MakeResourceCurrent() { bool FlutterELinuxView::CreateRenderSurface() { PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); - return binding_handler_->CreateRenderSurface(bounds.width, bounds.height); + auto impeller_enable = engine_.get()->IsImpellerEnabled(); + return binding_handler_->CreateRenderSurface(bounds.width, bounds.height, + impeller_enable); } void FlutterELinuxView::DestroyRenderSurface() { @@ -468,22 +536,60 @@ FlutterTransformation FlutterELinuxView::GetRootSurfaceTransformation() { return view_rotation_transformation_; } -std::pair FlutterELinuxView::GetPointerRotation(double x, - double y) { +std::pair FlutterELinuxView::GetPointerRotation(double x_px, + double y_px) { auto degree = binding_handler_->GetRotationDegree(); auto bounds = binding_handler_->GetPhysicalWindowBounds(); - std::pair res = {x, y}; + std::pair res = {x_px, y_px}; if (degree == 90) { - res.first = y; - res.second = bounds.height - x; + res.first = y_px; + res.second = bounds.height - x_px; } else if (degree == 180) { - res.first = bounds.width - x; - res.second = bounds.height - y; + res.first = bounds.width - x_px; + res.second = bounds.height - y_px; } else if (degree == 270) { - res.first = bounds.width - y; - res.second = x; + res.first = bounds.width - y_px; + res.second = x_px; } return res; } + +void FlutterELinuxView::UpdateHighContrastEnabled(bool enabled) { + int flags = 0; + if (enabled) { + flags |= + FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast; + } else { + flags &= + ~FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast; + } + engine_.get()->UpdateAccessibilityFeatures( + static_cast(flags)); + settings_handler_->UpdateHighContrastMode(enabled); +} + +void FlutterELinuxView::UpdateTextScaleFactor(float factor) { + settings_handler_->UpdateTextScaleFactor(factor); +} + +void FlutterELinuxView::UpdateDisplayInfo(double refresh_rate, + size_t width_px, + size_t height_px, + double pixel_ratio) { + const FlutterEngineDisplaysUpdateType update_type = + kFlutterEngineDisplaysUpdateTypeStartup; + const FlutterEngineDisplay displays = { + .struct_size = sizeof(FlutterEngineDisplay), + .display_id = 0, + .single_display = true, + .refresh_rate = refresh_rate, + .width = width_px, + .height = height_px, + .device_pixel_ratio = pixel_ratio, + }; + const size_t display_count = 1; + engine_.get()->UpdateDisplayInfo(update_type, &displays, display_count); +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.h b/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.h index 1133443f..0d6ed8d5 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_elinux_view.h @@ -19,6 +19,7 @@ #include "flutter/shell/platform/linux_embedded/plugins/navigation_plugin.h" #include "flutter/shell/platform/linux_embedded/plugins/platform_plugin.h" #include "flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.h" +#include "flutter/shell/platform/linux_embedded/plugins/settings_plugin.h" #include "flutter/shell/platform/linux_embedded/plugins/text_input_plugin.h" #include "flutter/shell/platform/linux_embedded/public/flutter_elinux.h" #include "flutter/shell/platform/linux_embedded/public/flutter_platform_views.h" @@ -75,6 +76,9 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { bool MakeCurrent(); bool ClearCurrent(); bool Present(); + bool PresentWithInfo(const FlutterPresentInfo* info); + void PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage); uint32_t GetOnscreenFBO(); bool MakeResourceCurrent(); @@ -82,19 +86,19 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { void SendInitialBounds(); // |WindowBindingHandlerDelegate| - void OnWindowSizeChanged(size_t width, size_t height) const override; + void OnWindowSizeChanged(size_t width_px, size_t height_px) const override; // |WindowBindingHandlerDelegate| - void OnPointerMove(double x, double y) override; + void OnPointerMove(double x_px, double y_px) override; // |WindowBindingHandlerDelegate| - void OnPointerDown(double x, - double y, + void OnPointerDown(double x_px, + double y_px, FlutterPointerMouseButtons button) override; // |WindowBindingHandlerDelegate| - void OnPointerUp(double x, - double y, + void OnPointerUp(double x_px, + double y_px, FlutterPointerMouseButtons button) override; // |WindowBindingHandlerDelegate| @@ -141,6 +145,18 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { void OnVsync(uint64_t frame_start_time_nanos, uint64_t frame_target_time_nanos) override; + // |WindowBindingHandlerDelegate| + void UpdateHighContrastEnabled(bool enabled) override; + + // |WindowBindingHandlerDelegate| + void UpdateTextScaleFactor(float factor) override; + + // |WindowBindingHandlerDelegate| + void UpdateDisplayInfo(double refresh_rate, + size_t width_px, + size_t height_px, + double pixel_ratio) override; + private: // Struct holding the mouse state. The engine doesn't keep track of which // mouse buttons have been pressed, so it's the embedding's responsibility. @@ -176,24 +192,39 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { }; struct touch_event { - touch_point points[10]; + touch_point points[10] = { + {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, + {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, + {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, {false, -1, 0, 0, 0}, + {false, -1, 0, 0, 0}, + }; }; // touch_point* GgeTouchPoint(int32_t id); // Sends a window metrics update to the Flutter engine using current window - // dimensions in physical - void SendWindowMetrics(size_t width, size_t height, double dpiscale) const; + // dimensions in physical pixels. + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. + void SendWindowMetrics(size_t width_px, + size_t height_px, + double dpiscale) const; // Reports a mouse movement to Flutter engine. - void SendPointerMove(double x, double y); + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + void SendPointerMove(double x_px, double y_px); // Reports mouse press to Flutter engine. - void SendPointerDown(double x, double y); + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + void SendPointerDown(double x_px, double y_px); // Reports mouse release to Flutter engine. - void SendPointerUp(double x, double y); + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + void SendPointerUp(double x_px, double y_px); // Reports mouse left the window client area. // @@ -238,7 +269,9 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { void SetMouseButtons(uint64_t buttons) { mouse_state_.buttons = buttons; } // Returns a trimmed pointer of user inputs with the window rotation. - std::pair GetPointerRotation(double x, double y); + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + std::pair GetPointerRotation(double x_px, double y_px); // The engine associated with this view. std::unique_ptr engine_; @@ -270,6 +303,9 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { // Handler for flutter/platform_views channel. std::unique_ptr platform_views_handler_; + // Handler for flutter/settings channel. + std::unique_ptr settings_handler_; + // Currently configured WindowBindingHandler for view. std::unique_ptr binding_handler_; @@ -282,7 +318,7 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate { // Current view rotation (FlutterTransformation). FlutterTransformation view_rotation_transformation_ = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; -}; +} SWIFT_UNSAFE_REFERENCE; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.cc b/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.cc index bd974261..31681244 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.cc @@ -85,8 +85,23 @@ UniqueAotDataPtr FlutterProjectBundle::LoadAotData( return UniqueAotDataPtr(data); } +void FlutterProjectBundle::SetSwitches( + const std::vector& switches) { + engine_switches_ = switches; +} + const std::vector FlutterProjectBundle::GetSwitches() { - return GetSwitchesFromEnvironment(); + if (engine_switches_.size() == 0) { + return GetSwitchesFromEnvironment(); + } + std::vector switches; + switches.insert(switches.end(), engine_switches_.begin(), + engine_switches_.end()); + + auto env_switches = GetSwitchesFromEnvironment(); + switches.insert(switches.end(), env_switches.begin(), env_switches.end()); + + return switches; } const std::string FlutterProjectBundle::GetExecutableDirectory() { diff --git a/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.h b/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.h index fdec398d..503fcd38 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_project_bundle.h @@ -45,6 +45,9 @@ class FlutterProjectBundle { // Returns any switches that should be passed to the engine. const std::vector GetSwitches(); + // Sets engine switches. + void SetSwitches(const std::vector& switches); + // Attempts to load AOT data for this bundle. The returned data must be // retained until any engine instance it is passed to has been shut down. // @@ -69,6 +72,9 @@ class FlutterProjectBundle { // Dart entrypoint arguments. std::vector dart_entrypoint_arguments_; + + // Engine switches. + std::vector engine_switches_; }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.cc b/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.cc index 59a9fc0f..002457db 100644 --- a/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.cc +++ b/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.cc @@ -4,7 +4,7 @@ #include "flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.h" -#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/string_message_codec.h" #include "flutter/shell/platform/linux_embedded/logger.h" namespace flutter { @@ -15,32 +15,39 @@ constexpr char kInactive[] = "AppLifecycleState.inactive"; constexpr char kResumed[] = "AppLifecycleState.resumed"; constexpr char kPaused[] = "AppLifecycleState.paused"; constexpr char kDetached[] = "AppLifecycleState.detached"; +constexpr char kHidden[] = "AppLifecycleState.hidden"; + } // namespace LifecyclePlugin::LifecyclePlugin(BinaryMessenger* messenger) - : channel_(std::make_unique>( + : channel_(std::make_unique>( messenger, kChannelName, - &StandardMessageCodec::GetInstance())) {} + &StringMessageCodec::GetInstance())) {} void LifecyclePlugin::OnInactive() const { ELINUX_LOG(DEBUG) << "App lifecycle changed to inactive state."; - channel_->Send(EncodableValue(std::string(kInactive))); + channel_->Send(std::string(kInactive)); } void LifecyclePlugin::OnResumed() const { ELINUX_LOG(DEBUG) << "App lifecycle changed to resumed state."; - channel_->Send(EncodableValue(std::string(kResumed))); + channel_->Send(std::string(kResumed)); } void LifecyclePlugin::OnPaused() const { ELINUX_LOG(DEBUG) << "App lifecycle changed to paused state."; - channel_->Send(EncodableValue(std::string(kPaused))); + channel_->Send(std::string(kPaused)); } void LifecyclePlugin::OnDetached() const { ELINUX_LOG(DEBUG) << "App lifecycle changed to detached state."; - channel_->Send(EncodableValue(std::string(kDetached))); + channel_->Send(std::string(kDetached)); +} + +void LifecyclePlugin::OnHidden() const { + ELINUX_LOG(DEBUG) << "App lifecycle changed to hidden state."; + channel_->Send(std::string(kHidden)); } } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.h b/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.h index f93881c2..784fbd61 100644 --- a/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.h +++ b/src/flutter/shell/platform/linux_embedded/plugins/lifecycle_plugin.h @@ -25,8 +25,10 @@ class LifecyclePlugin { void OnDetached() const; + void OnHidden() const; + private: - std::unique_ptr> channel_; + std::unique_ptr> channel_; }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.cc b/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.cc index 85c553e8..fb2a513e 100644 --- a/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.cc +++ b/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.cc @@ -21,12 +21,16 @@ constexpr char kAcceptGestureMethod[] = "acceptGesture"; constexpr char kRejectGestureMethod[] = "rejectGesture"; constexpr char kEnterMethod[] = "enter"; constexpr char kExitMethod[] = "exit"; +constexpr char kOffsetMethod[] = "offset"; constexpr char kViewTypeKey[] = "viewType"; constexpr char kIdKey[] = "id"; constexpr char kWidthKey[] = "width"; constexpr char kHeightKey[] = "height"; constexpr char kParamsKey[] = "params"; + +constexpr char kTopKey[] = "top"; +constexpr char kLeftKey[] = "left"; } // namespace PlatformViewsPlugin::PlatformViewsPlugin(BinaryMessenger* messenger) @@ -63,6 +67,7 @@ void PlatformViewsPlugin::RegisterViewFactory( << view_type; return; } + ELINUX_LOG(DEBUG) << view_type << " was registered"; platform_view_factories_[view_type] = std::move(factory); } @@ -78,13 +83,13 @@ void PlatformViewsPlugin::HandleMethodCall( } else if (method.compare(kDisposeMethod) == 0) { PlatformViewsDispose(arguments, std::move(result)); } else if (method.compare(kResizeMethod) == 0) { - result->NotImplemented(); + PlatformViewsResize(arguments, std::move(result)); } else if (method.compare(kSetDirectionMethod) == 0) { result->NotImplemented(); } else if (method.compare(kClearFocusMethod) == 0) { - result->NotImplemented(); + PlatformViewsClearFocus(arguments, std::move(result)); } else if (method.compare(kTouchMethod) == 0) { - result->NotImplemented(); + PlatformViewsTouch(arguments, std::move(result)); } else if (method.compare(kAcceptGestureMethod) == 0) { result->NotImplemented(); } else if (method.compare(kRejectGestureMethod) == 0) { @@ -93,6 +98,8 @@ void PlatformViewsPlugin::HandleMethodCall( result->NotImplemented(); } else if (method.compare(kExitMethod) == 0) { result->NotImplemented(); + } else if (method.compare(kOffsetMethod) == 0) { + PlatformViewsOffset(arguments, std::move(result)); } else { ELINUX_LOG(WARNING) << "Platform Views unexpected method is called: " << method; @@ -109,10 +116,6 @@ void PlatformViewsPlugin::PlatformViewsCreate( return; } auto view_id = LookupEncodableMap(arguments, kIdKey); - if (!view_id) { - result->Error("Couldn't find the view id in the arguments"); - return; - } auto view_width = LookupEncodableMap(arguments, kWidthKey); if (!view_width) { result->Error("Couldn't find width in the arguments"); @@ -153,17 +156,109 @@ void PlatformViewsPlugin::PlatformViewsDispose( const flutter::EncodableValue& arguments, std::unique_ptr> result) { auto view_id = LookupEncodableMap(arguments, kIdKey); - if (!view_id) { + ELINUX_LOG(DEBUG) << "Dispose the platform view: id = " << view_id; + if (platform_views_.find(view_id) == platform_views_.end()) { result->Error("Couldn't find the view id in the arguments"); return; } + platform_views_[view_id]->Dispose(); + result->Success(); +} - ELINUX_LOG(DEBUG) << "Dispose the platform view: id = " << view_id; +void PlatformViewsPlugin::PlatformViewsClearFocus( + const flutter::EncodableValue& arguments, + std::unique_ptr> result) { + auto view_id = LookupEncodableMap(arguments, kIdKey); + ELINUX_LOG(DEBUG) << "ClearFocus the platform view: id = " << view_id; if (platform_views_.find(view_id) == platform_views_.end()) { result->Error("Couldn't find the view id in the arguments"); return; } - platform_views_[view_id]->Dispose(); + platform_views_[view_id]->SetFocus(false); + platform_views_[view_id]->ClearFocus(); + + result->Success(); +} + +void PlatformViewsPlugin::PlatformViewsResize( + const flutter::EncodableValue& arguments, + std::unique_ptr> result) { + auto view_id = LookupEncodableMap(arguments, kIdKey); + auto view_width = LookupEncodableMap(arguments, kWidthKey); + if (!view_width) { + result->Error("Couldn't find width in the arguments"); + return; + } + auto view_height = LookupEncodableMap(arguments, kHeightKey); + if (!view_height) { + result->Error("Couldn't find height in the arguments"); + return; + } + + ELINUX_LOG(DEBUG) << "Resize the platform view: id = " << view_id + << ", width = " << view_width + << ", height = " << view_height; + if (platform_views_.find(view_id) == platform_views_.end()) { + result->Error("Couldn't find the view id in the arguments"); + return; + } + + if (!view_width || !view_height) { + result->Error("width and height must be greater than zero"); + return; + } + + platform_views_[view_id]->Resize(view_width, view_height); + + result->Success(arguments); +} + +void PlatformViewsPlugin::PlatformViewsTouch( + const flutter::EncodableValue& arguments, + std::unique_ptr> result) { + auto list = std::get(arguments); + auto view_id = std::get(list[0]); + auto event_type = std::get(list[3]); + auto device_id = std::get(list[11]); + + auto pointer_coords = std::get(list[6]); + if (pointer_coords.size() < 1) { + result->Error("Couldn't find the pointer_coords in the arguments"); + return; + } + auto pointer_coord = std::get(pointer_coords[0]); + auto x = std::get(pointer_coord[7]); + auto y = std::get(pointer_coord[8]); + + ELINUX_LOG(TRACE) << "Touch the platform view: id = " << view_id + << ", device_id = " << device_id + << ", event_type = " << event_type << ", x = " << x + << ", y = " << y; + if (platform_views_.find(view_id) == platform_views_.end()) { + result->Error("Couldn't find the view id in the arguments"); + return; + } + + platform_views_[view_id]->Touch(device_id, event_type, x, y); + + result->Success(); +} + +void PlatformViewsPlugin::PlatformViewsOffset( + const flutter::EncodableValue& arguments, + std::unique_ptr> result) { + auto view_id = LookupEncodableMap(arguments, kIdKey); + auto view_top = LookupEncodableMap(arguments, kTopKey); + auto view_left = LookupEncodableMap(arguments, kLeftKey); + ELINUX_LOG(DEBUG) << "Offset the platform view: " << "id = " << view_id + << ", top = " << view_top << ", left = " << view_left; + if (platform_views_.find(view_id) == platform_views_.end()) { + result->Error("Couldn't find the view id in the arguments"); + return; + } + + platform_views_[view_id]->Offset(view_top, view_left); + result->Success(); } diff --git a/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.h b/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.h index 2530481f..8c17e078 100644 --- a/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.h +++ b/src/flutter/shell/platform/linux_embedded/plugins/platform_views_plugin.h @@ -52,6 +52,26 @@ class PlatformViewsPlugin { const flutter::EncodableValue& arguments, std::unique_ptr> result); + // Called when "clearFocus" method is called + void PlatformViewsClearFocus( + const flutter::EncodableValue& arguments, + std::unique_ptr> result); + + // Called when "resize" method is called + void PlatformViewsResize( + const flutter::EncodableValue& arguments, + std::unique_ptr> result); + + // Called when "touch" method is called + void PlatformViewsTouch( + const flutter::EncodableValue& arguments, + std::unique_ptr> result); + + // Called when "offset" method is called + void PlatformViewsOffset( + const flutter::EncodableValue& arguments, + std::unique_ptr> result); + // Method channel instance. std::unique_ptr> channel_; diff --git a/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.cc b/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.cc new file mode 100644 index 00000000..7603bfe9 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.cc @@ -0,0 +1,76 @@ +// Copyright 2023 Sony Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux_embedded/plugins/settings_plugin.h" + +#include "flutter/shell/platform/common/json_message_codec.h" + +namespace flutter { + +namespace { +constexpr char kChannelName[] = "flutter/settings"; + +constexpr char kAlwaysUse24HourFormat[] = "alwaysUse24HourFormat"; +constexpr char kTextScaleFactor[] = "textScaleFactor"; +constexpr char kPlatformBrightness[] = "platformBrightness"; + +constexpr char kPlatformBrightnessDark[] = "dark"; +constexpr char kPlatformBrightnessLight[] = "light"; +} // namespace + +SettingsPlugin::SettingsPlugin(BinaryMessenger* messenger, + WindowBindingHandler* delegate) + : channel_(std::make_unique>( + messenger, + kChannelName, + &JsonMessageCodec::GetInstance())), + delegate_(delegate) {} + +void SettingsPlugin::SendSettings() { + rapidjson::Document settings(rapidjson::kObjectType); + auto& allocator = settings.GetAllocator(); + settings.AddMember(kAlwaysUse24HourFormat, GetAlwaysUse24HourFormat(), + allocator); + settings.AddMember(kTextScaleFactor, GetTextScaleFactor(), allocator); + + if (GetPreferredBrightness() == PlatformBrightness::kDark) { + settings.AddMember(kPlatformBrightness, kPlatformBrightnessDark, allocator); + } else { + settings.AddMember(kPlatformBrightness, kPlatformBrightnessLight, + allocator); + } + channel_->Send(settings); +} + +bool SettingsPlugin::GetAlwaysUse24HourFormat() { + return is_always_use_24hour_format_; +} + +void SettingsPlugin::UpdateAlwaysUse24HourFormat( + bool is_always_use_24hour_format) { + is_always_use_24hour_format_ = is_always_use_24hour_format; + SendSettings(); +} + +float SettingsPlugin::GetTextScaleFactor() { + return text_scaling_factor_; +} + +void SettingsPlugin::UpdateTextScaleFactor(float factor) { + text_scaling_factor_ = factor; + SendSettings(); +} + +SettingsPlugin::PlatformBrightness SettingsPlugin::GetPreferredBrightness() { + // The current OS does not have brightness factor. + return PlatformBrightness::kLight; +} + +void SettingsPlugin::UpdateHighContrastMode(bool is_high_contrast) { + is_high_contrast_ = is_high_contrast; + SendSettings(); +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.h b/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.h new file mode 100644 index 00000000..03a1be37 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/plugins/settings_plugin.h @@ -0,0 +1,62 @@ +// Copyright 2023 Sony Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_PLUGINS_SETTINGS_PLUGIN_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_PLUGINS_SETTINGS_PLUGIN_H_ + +#include + +#include + +#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/linux_embedded/window_binding_handler.h" + +namespace flutter { + +// Abstract settings plugin. +// +// Used to look up and notify Flutter of user-configured system settings. +// These are typically set in the control panel. +class SettingsPlugin { + public: + enum struct PlatformBrightness { kDark, kLight }; + + explicit SettingsPlugin(BinaryMessenger* messenger, + WindowBindingHandler* delegate); + ~SettingsPlugin() = default; + + // Sends settings (e.g., platform brightness) to the engine. + void SendSettings(); + + // Update the always use 24hour-format status of the system. + void UpdateAlwaysUse24HourFormat(bool is_always_use_24hour_format); + + // Update the high contrast status of the system. + void UpdateHighContrastMode(bool is_high_contrast); + + // Update the text scale factor of the system. + void UpdateTextScaleFactor(float factor); + + private: + // Returns `true` if the user uses 24 hour time. + bool GetAlwaysUse24HourFormat(); + + // Returns the user-preferred text scale factor. + float GetTextScaleFactor(); + + // Returns the user-preferred brightness. + PlatformBrightness GetPreferredBrightness(); + + std::unique_ptr> channel_; + WindowBindingHandler* delegate_; + bool is_high_contrast_ = false; + bool is_always_use_24hour_format_ = true; + float text_scaling_factor_ = 1.0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_PLUGINS_SETTINGS_PLUGIN_H_ diff --git a/src/flutter/shell/platform/linux_embedded/plugins/text_input_plugin.h b/src/flutter/shell/platform/linux_embedded/plugins/text_input_plugin.h index af9caf27..3f48d9b6 100644 --- a/src/flutter/shell/platform/linux_embedded/plugins/text_input_plugin.h +++ b/src/flutter/shell/platform/linux_embedded/plugins/text_input_plugin.h @@ -39,7 +39,7 @@ class TextInputPlugin { std::unique_ptr> channel_; // The active client id. - int client_id_; + int client_id_ = 0; // The active model. nullptr if not set. std::unique_ptr active_model_; diff --git a/src/flutter/shell/platform/linux_embedded/public/flutter_elinux.h b/src/flutter/shell/platform/linux_embedded/public/flutter_elinux.h index dd6da5e2..eacb5269 100644 --- a/src/flutter/shell/platform/linux_embedded/public/flutter_elinux.h +++ b/src/flutter/shell/platform/linux_embedded/public/flutter_elinux.h @@ -57,7 +57,7 @@ typedef struct { // The View display mode. enum FlutterDesktopViewMode { // Shows the Flutter view by user specific size. - kNormal = 0, + kNormalscreen = 0, // Shows always the Flutter view by fullscreen. kFullscreen = 1, }; @@ -76,10 +76,10 @@ enum FlutterDesktopViewRotation { // Properties for configuring a Flutter view instance. typedef struct { - // View width. + // View width in logical pixels. int width; - // View height. + // View height in logical pixels. int height; // View rotation setting. @@ -89,6 +89,13 @@ typedef struct { // and `height` will be ignored. FlutterDesktopViewMode view_mode; + // View title. + const char* title; + + // View XDG application ID. As a best practice, it is suggested to select an + // app ID that matches the basename of the application's .desktop file. + const char* app_id; + // Uses mouse cursor. bool use_mouse_cursor; @@ -98,6 +105,21 @@ typedef struct { // Uses the window decoration such as toolbar and max/min buttons. // This option is only active for Wayland backend. bool use_window_decoration; + + // Text scaling factor. + double text_scale_factor; + + // Enable high contrast. Request that UI be rendered with darker colors. + bool enable_high_contrast; + + // Force scale factor specified by command line argument + bool force_scale_factor; + double scale_factor; + + // Enable Vsync. + // True: Sync to compositor redraw/v-blank (eglSwapInterval 1) + // False: Do not sync to compositor redraw/v-blank (eglSwapInterval 0) + bool enable_vsync; } FlutterDesktopViewProperties; // ========== View Controller ========== @@ -117,7 +139,7 @@ typedef struct { // an error. FLUTTER_EXPORT FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( - const FlutterDesktopViewProperties& view_properties, + const FlutterDesktopViewProperties* view_properties, FlutterDesktopEngineRef engine); // Shuts down the engine instance associated with |controller|, and cleans up @@ -150,7 +172,7 @@ FlutterDesktopViewGetFrameRate(FlutterDesktopViewRef view); // The caller owns the returned reference, and is responsible for calling // FlutterDesktopEngineDestroy. FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineCreate( - const FlutterDesktopEngineProperties& engine_properties); + const FlutterDesktopEngineProperties* engine_properties); // Shuts down and destroys the given engine instance. Returns true if the // shutdown was successful, or if the engine was not running. @@ -189,14 +211,17 @@ FLUTTER_EXPORT FlutterDesktopPluginRegistrarRef FlutterDesktopEngineGetPluginRegistrar(FlutterDesktopEngineRef engine, const char* plugin_name); +// Returns the view associated with this registrar's engine instance. +FLUTTER_EXPORT FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView( + FlutterDesktopPluginRegistrarRef registrar); + // Returns the messenger associated with the engine. -FLUTTER_EXPORT FlutterDesktopMessengerRef -FlutterDesktopEngineGetMessenger(FlutterDesktopEngineRef engine); +FLUTTER_EXPORT FlutterDesktopMessengerRef FlutterDesktopEngineGetMessenger( + FlutterDesktopEngineRef engine) SWIFT_RETURNS_UNRETAINED; // Returns the texture registrar associated with the engine. FLUTTER_EXPORT FlutterDesktopTextureRegistrarRef -FlutterDesktopEngineGetTextureRegistrar( - FlutterDesktopTextureRegistrarRef texture_registrar); +FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine); #if defined(__cplusplus) } // extern "C" diff --git a/src/flutter/shell/platform/linux_embedded/public/flutter_platform_views.h b/src/flutter/shell/platform/linux_embedded/public/flutter_platform_views.h index 5ecb2f30..0e7c9d52 100644 --- a/src/flutter/shell/platform/linux_embedded/public/flutter_platform_views.h +++ b/src/flutter/shell/platform/linux_embedded/public/flutter_platform_views.h @@ -9,10 +9,11 @@ #include #include +#include +#include #include "flutter_export.h" #include "flutter_messenger.h" -#include "plugin_registrar.h" class FlutterDesktopPlatformView { public: @@ -31,16 +32,24 @@ class FlutterDesktopPlatformView { void SetFocus(bool focus) { focused_ = focus; } + virtual void ClearFocus() = 0; + bool IsFocused() const { return focused_; } - void SetTextureId(int texture_id) { texture_id_ = texture_id; } + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + virtual void Resize(double width, double height) = 0; + + virtual void Touch(int device_id, int event_type, double x, double y) = 0; - int GetTextureId() const { return texture_id_; } + virtual void Offset(double top, double left) = 0; private: flutter::PluginRegistrar* registrar_; int view_id_; - int texture_id_; + int64_t texture_id_; bool focused_; }; diff --git a/src/flutter/shell/platform/linux_embedded/surface/context_egl.cc b/src/flutter/shell/platform/linux_embedded/surface/context_egl.cc index 978af656..855687c2 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/context_egl.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/context_egl.cc @@ -10,27 +10,76 @@ namespace flutter { ContextEgl::ContextEgl(std::unique_ptr environment, + bool enable_impeller, EGLint egl_surface_type) : environment_(std::move(environment)), config_(nullptr) { EGLint config_count = 0; const EGLint attribs[] = { // clang-format off - EGL_SURFACE_TYPE, egl_surface_type, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_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 + EGL_SURFACE_TYPE, egl_surface_type, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, +#if defined(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER) + EGL_ALPHA_SIZE, 8, +#endif + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_NONE // clang-format on }; - if (eglChooseConfig(environment_->Display(), attribs, &config_, 1, - &config_count) != EGL_TRUE) { - ELINUX_LOG(ERROR) << "Failed to choose EGL surface config: " - << get_egl_error_cause(); - return; + const EGLint impeller_config_attributes[] = { + // clang-format off + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, +#if defined(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER) + EGL_ALPHA_SIZE, 8, +#endif + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, 1, + EGL_SAMPLES, 4, + EGL_NONE + // clang-format on + }; + const EGLint impeller_config_attributes_no_msaa[] = { + // clang-format off + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, +#if defined(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER) + EGL_ALPHA_SIZE, 8, +#endif + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 8, + EGL_NONE + // clang-format on + }; + + if (enable_impeller) { + // First try the MSAA configuration. + if ((eglChooseConfig(environment_->Display(), impeller_config_attributes, + &config_, 1, &config_count) == EGL_FALSE) || + (config_count == 0)) { + // Next fall back to disabled MSAA. + if ((eglChooseConfig(environment_->Display(), + impeller_config_attributes_no_msaa, &config_, 1, + &config_count) == EGL_FALSE) || + (config_count == 0)) { + ELINUX_LOG(ERROR) << "Failed to choose EGL surface config: " + << get_egl_error_cause(); + return; + } + } + } else { + if (eglChooseConfig(environment_->Display(), attribs, &config_, 1, + &config_count) != EGL_TRUE) { + ELINUX_LOG(ERROR) << "Failed to choose EGL surface config: " + << get_egl_error_cause(); + return; + } } if (config_count == 0 || config_ == nullptr) { @@ -70,7 +119,7 @@ std::unique_ptr ContextEgl::CreateOnscreenSurface( << get_egl_error_cause(); } return std::make_unique(surface, environment_->Display(), - context_); + context_, window->EnableVsync()); } std::unique_ptr ContextEgl::CreateOffscreenSurface( @@ -87,8 +136,8 @@ std::unique_ptr ContextEgl::CreateOffscreenSurface( EGLSurface surface = eglCreatePbufferSurface(environment_->Display(), config_, attribs); if (surface == EGL_NO_SURFACE) { - ELINUX_LOG(WARNING) << "Failed to create EGL off-screen surface." - << "(" << get_egl_error_cause() << ")"; + ELINUX_LOG(WARNING) << "Failed to create EGL off-screen surface." << "(" + << get_egl_error_cause() << ")"; } #else // eglCreatePbufferSurface isn't supported on both Wayland and GBM. @@ -97,12 +146,13 @@ std::unique_ptr ContextEgl::CreateOffscreenSurface( EGLSurface surface = eglCreateWindowSurface( environment_->Display(), config_, window->WindowOffscreen(), attribs); if (surface == EGL_NO_SURFACE) { - ELINUX_LOG(WARNING) << "Failed to create EGL off-screen surface." - << "(" << get_egl_error_cause() << ")"; + ELINUX_LOG(WARNING) << "Failed to create EGL off-screen surface." << "(" + << get_egl_error_cause() << ")"; } #endif return std::make_unique(surface, environment_->Display(), - resource_context_); + resource_context_, + window->EnableVsync()); } bool ContextEgl::IsValid() const { diff --git a/src/flutter/shell/platform/linux_embedded/surface/context_egl.h b/src/flutter/shell/platform/linux_embedded/surface/context_egl.h index b443e191..4950a816 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/context_egl.h +++ b/src/flutter/shell/platform/linux_embedded/surface/context_egl.h @@ -18,6 +18,7 @@ namespace flutter { class ContextEgl { public: ContextEgl(std::unique_ptr environment, + bool enable_impeller, EGLint egl_surface_type = EGL_WINDOW_BIT); ~ContextEgl() = default; diff --git a/src/flutter/shell/platform/linux_embedded/surface/context_egl_stream.cc b/src/flutter/shell/platform/linux_embedded/surface/context_egl_stream.cc index 40822ccb..4a13f498 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/context_egl_stream.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/context_egl_stream.cc @@ -11,7 +11,7 @@ namespace flutter { ContextEglStream::ContextEglStream( std::unique_ptr environment) - : ContextEgl(std::move(environment), EGL_STREAM_BIT_KHR) { + : ContextEgl(std::move(environment), false, EGL_STREAM_BIT_KHR) { if (!valid_) { return; } @@ -28,7 +28,7 @@ std::unique_ptr ContextEglStream::CreateOnscreenSurface( EGLOutputLayerEXT layer; EGLAttrib layer_attribs[] = { // clang-format off - EGL_DRM_PLANE_EXT, static_cast(window)->PlaneId(), + EGL_DRM_PLANE_EXT, static_cast(static_cast(window)->PlaneId()), EGL_NONE // clang-format on }; @@ -65,7 +65,7 @@ std::unique_ptr ContextEglStream::CreateOnscreenSurface( ELINUX_LOG(ERROR) << "Failed to create EGL stream producer surface"; } return std::make_unique(surface, environment_->Display(), - context_); + context_, window->EnableVsync()); } bool ContextEglStream::SetEglExtensionFunctionPointers() { diff --git a/src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc b/src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc index 619c51bf..23647bcd 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc @@ -1,5 +1,5 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,6 +7,7 @@ #include +#include #include #include @@ -40,4 +41,14 @@ std::string get_egl_error_cause() { return nullptr; } -} // namespace flutter \ No newline at end of file +// Auxiliary function used to check if the given list of extensions contains the +// requested extension name. +bool has_egl_extension(const char* extensions, const char* name) { + const char* r = std::strstr(extensions, name); + auto len = std::strlen(name); + + // check that the extension name is terminated by space or null terminator + return r != nullptr && (r[len] == ' ' || r[len] == 0); +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/surface/egl_utils.h b/src/flutter/shell/platform/linux_embedded/surface/egl_utils.h index a64196df..80692000 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/egl_utils.h +++ b/src/flutter/shell/platform/linux_embedded/surface/egl_utils.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,6 +10,7 @@ namespace flutter { std::string get_egl_error_cause(); +bool has_egl_extension(const char* extensions, const char* name); } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.cc b/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.cc index 8a758174..914ab736 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,10 +9,43 @@ namespace flutter { +constexpr size_t kInitialWindowWidthPx = 1280; +constexpr size_t kInitialWindowHeightPx = 720; + +// Maximum damage history - for triple buffering we need to store damage for +// last two frames; Some Android devices (Pixel 4) use quad buffering. +constexpr const int kMaxHistorySize = 10; + ELinuxEGLSurface::ELinuxEGLSurface(EGLSurface surface, EGLDisplay display, - EGLContext context) - : surface_(surface), display_(display), context_(context){}; + EGLContext context, + bool vsync_enabled) + : surface_(surface), + display_(display), + context_(context), + vsync_enabled_(vsync_enabled), + width_px_(kInitialWindowWidthPx), + height_px_(kInitialWindowHeightPx) { + const char* extensions = eglQueryString(display_, EGL_EXTENSIONS); + + if (has_egl_extension(extensions, "EGL_KHR_partial_update")) { + eglSetDamageRegionKHR_ = reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); + } + + if (has_egl_extension(extensions, "EGL_EXT_swap_buffers_with_damage")) { + eglSwapBuffersWithDamageEXT_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageEXT")); + } else if (has_egl_extension(extensions, + "EGL_KHR_swap_buffers_with_damage")) { + eglSwapBuffersWithDamageEXT_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + } else { + // do nothing. + } +}; ELinuxEGLSurface::~ELinuxEGLSurface() { if (surface_ != EGL_NO_SURFACE) { @@ -28,6 +61,12 @@ bool ELinuxEGLSurface::IsValid() const { return surface_ != EGL_NO_SURFACE; } +void ELinuxEGLSurface::SurfaceResize(const size_t width_px, + const size_t height_px) { + width_px_ = width_px; + height_px_ = height_px; +} + bool ELinuxEGLSurface::MakeCurrent() const { if (eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) { ELINUX_LOG(ERROR) << "Failed to make the EGL context current: " @@ -35,30 +74,144 @@ bool ELinuxEGLSurface::MakeCurrent() const { return false; } -#if defined(ENABLE_EGL_ASYNC_BUFFER_SWAPPING) // Non-blocking when swappipping buffers on Wayland. + // OpenGL swap intervals can be used to prevent screen tearing. + // If enabled, the raster thread blocks until the v-blank. + // This is unnecessary if DWM composition is enabled. + // See: https://www.khronos.org/opengl/wiki/Swap_Interval + // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw + // // However, we might encounter rendering problems on some Wayland compositors - // (e.g. weston 9.0) when we use them. + // (e.g. weston 9.0). // See also: // - https://github.com/sony/flutter-embedded-linux/issues/230 // - https://github.com/sony/flutter-embedded-linux/issues/234 // - https://github.com/sony/flutter-embedded-linux/issues/220 - if (eglSwapInterval(display_, 0) != EGL_TRUE) { - ELINUX_LOG(ERROR) << "Failed to eglSwapInterval(Free): " - << get_egl_error_cause(); + if (!vsync_enabled_) { + if (eglSwapInterval(display_, 0) != EGL_TRUE) { + ELINUX_LOG(ERROR) << "Failed to eglSwapInterval(Free): " + << get_egl_error_cause(); + } } -#endif return true; } bool ELinuxEGLSurface::SwapBuffers() const { if (eglSwapBuffers(display_, surface_) != EGL_TRUE) { - ELINUX_LOG(ERROR) << "Failed to swap the EGL buffer: " - << get_egl_error_cause(); + ELINUX_LOG(ERROR) << "eglSwapBuffers failed: " << get_egl_error_cause(); return false; } return true; } +// Reference of dirty region management: +// https://github.com/flutter/engine/blob/main/examples/glfw_drm/FlutterEmbedderGLFW.cc + +bool ELinuxEGLSurface::SwapBuffers(const FlutterPresentInfo* info) { + // Free the existing damage that was allocated to this frame. + if (existing_damage_map_.find(info->fbo_id) != existing_damage_map_.end() && + existing_damage_map_[info->fbo_id] != nullptr) { + free(existing_damage_map_[info->fbo_id]); + existing_damage_map_[info->fbo_id] = nullptr; + } + + // Set the buffer damage as the damage region. + if (eglSetDamageRegionKHR_) { + auto buffer_rects = RectToInts(info->buffer_damage.damage[0]); + if (eglSetDamageRegionKHR_(display_, surface_, buffer_rects.data(), 1) != + EGL_TRUE) { + ELINUX_LOG(ERROR) << "eglSetDamageRegionKHR failed: " + << get_egl_error_cause(); + return false; + } + } + + // Add frame damage to damage history + damage_history_.push_back(info->frame_damage.damage[0]); + if (damage_history_.size() > kMaxHistorySize) { + damage_history_.pop_front(); + } + + if (eglSwapBuffersWithDamageEXT_) { + auto frame_rects = RectToInts(info->frame_damage.damage[0]); + if (eglSwapBuffersWithDamageEXT_(display_, surface_, frame_rects.data(), + 1) != EGL_TRUE) { + ELINUX_LOG(ERROR) << "eglSwapBuffersWithDamageEXT failed: " + << get_egl_error_cause(); + return false; + } + } else { + // If the required extensions for partial repaint were not provided, do + // full repaint. + if (eglSwapBuffers(display_, surface_) != EGL_TRUE) { + ELINUX_LOG(ERROR) << "eglSwapBuffers failed: " << get_egl_error_cause(); + return false; + } + } + + return true; +} + +void ELinuxEGLSurface::PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) { + // Given the FBO age, create existing damage region by joining all frame + // damages since FBO was last used + EGLint age = 0; + if (eglQuerySurface(display_, surface_, EGL_BUFFER_AGE_EXT, &age) != + EGL_TRUE || + age == 0) { + age = 4; // Virtually no driver should have a swapchain length > 4. + } + + existing_damage->num_rects = 1; + + // Allocate the array of rectangles for the existing damage. + existing_damage_map_[fbo_id] = static_cast( + malloc(sizeof(FlutterRect) * existing_damage->num_rects)); + + existing_damage_map_[fbo_id][0] = FlutterRect{ + 0, 0, static_cast(width_px_), static_cast(height_px_)}; + existing_damage->damage = existing_damage_map_[fbo_id]; + + if (age > 1) { + --age; + // join up to (age - 1) last rects from damage history + for (auto i = damage_history_.rbegin(); + i != damage_history_.rend() && age > 0; ++i, --age) { + if (i == damage_history_.rbegin()) { + if (i != damage_history_.rend()) { + existing_damage->damage[0] = {i->left, i->top, i->right, i->bottom}; + } + } else { + // Auxiliary function to union the damage regions comprised by two + // FlutterRect element. It saves the result of this join in the rect + // variable. + FlutterRect* rect = &(existing_damage->damage[0]); + const FlutterRect additional_rect = *i; + + rect->left = std::min(rect->left, additional_rect.left); + rect->top = std::min(rect->top, additional_rect.top); + rect->right = std::max(rect->right, additional_rect.right); + rect->bottom = std::max(rect->bottom, additional_rect.bottom); + } + } + } +} + +// Auxiliary function used to transform a FlutterRect into the format that is +// expected by the EGL functions (i.e. array of EGLint). +std::array ELinuxEGLSurface::RectToInts(const FlutterRect rect) { + EGLint height; + eglQuerySurface(display_, surface_, EGL_HEIGHT, &height); + + std::array res{ + static_cast(rect.left), + height - static_cast(rect.bottom), + static_cast(rect.right) - static_cast(rect.left), + static_cast(rect.bottom) - static_cast(rect.top), + }; + return res; +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.h b/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.h index 452a7df7..885b1138 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.h +++ b/src/flutter/shell/platform/linux_embedded/surface/elinux_egl_surface.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,25 +6,60 @@ #define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_SURFACE_ELINUX_EGL_SURFACE_H_ #include +#include + +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" namespace flutter { class ELinuxEGLSurface { public: // Note that EGLSurface will be destroyed in this class's destructor. - ELinuxEGLSurface(EGLSurface surface, EGLDisplay display, EGLContext context); + ELinuxEGLSurface(EGLSurface surface, + EGLDisplay display, + EGLContext context, + bool vsync_enabled); ~ELinuxEGLSurface(); bool IsValid() const; + void SurfaceResize(const size_t width_px, const size_t height_px); + bool MakeCurrent() const; bool SwapBuffers() const; + bool SwapBuffers(const FlutterPresentInfo* info); + + void PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage); + private: + // Auxiliary function used to transform a FlutterRect into the format that is + // expected by the EGL functions (i.e. array of EGLint). + std::array RectToInts(const FlutterRect rect); + EGLDisplay display_; EGLSurface surface_; EGLContext context_; + bool vsync_enabled_; + + size_t width_px_; + size_t height_px_; + + PFNEGLSETDAMAGEREGIONKHRPROC eglSetDamageRegionKHR_ = nullptr; + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC eglSwapBuffersWithDamageEXT_ = nullptr; + + // Keeps track of the most recent frame damages so that existing damage can + // be easily computed. + std::list damage_history_; + + // Keeps track of the existing damage associated with each FBO ID + std::unordered_map existing_damage_map_; }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_base.cc b/src/flutter/shell/platform/linux_embedded/surface/surface_base.cc index f27f4f21..91ca8a1d 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_base.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_base.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -29,9 +29,10 @@ bool SurfaceBase::SetNativeWindow(NativeWindow* window) { return true; }; -bool SurfaceBase::OnScreenSurfaceResize(const size_t width, - const size_t height) { - if (!native_window_->Resize(width, height)) { +bool SurfaceBase::OnScreenSurfaceResize(const size_t width_px, + const size_t height_px) { + onscreen_surface_->SurfaceResize(width_px, height_px); + if (!native_window_->Resize(width_px, height_px)) { ELINUX_LOG(ERROR) << "Failed to resize."; return false; } @@ -44,6 +45,7 @@ bool SurfaceBase::OnScreenSurfaceResize(const size_t width, onscreen_surface_ = nullptr; return false; } + onscreen_surface_->SurfaceResize(width_px, height_px); } return true; }; diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_base.h b/src/flutter/shell/platform/linux_embedded/surface/surface_base.h index d1c5e2af..1b7e23a8 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_base.h +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_base.h @@ -28,7 +28,9 @@ class SurfaceBase { // On-screen surface needs to be recreated after window size changed only when // using DRM-GBM backend. Because gbm-surface is recreated when the window // size changed. - bool OnScreenSurfaceResize(const size_t width, const size_t height); + // @param[in] width_px Physical width of the surface. + // @param[in] height_px Physical height of the surface. + bool OnScreenSurfaceResize(const size_t width_px, const size_t height_px); // Clears current on-screen context. bool ClearCurrentContext() const; diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.cc b/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.cc index dc7e932c..e33f92f8 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -27,8 +27,8 @@ bool SurfaceDecoration::SetNativeWindow(NativeWindow* window) { return true; }; -bool SurfaceDecoration::Resize(const size_t width, const size_t height) { - if (!native_window_->Resize(width, height)) { +bool SurfaceDecoration::Resize(const size_t width_px, const size_t height_px) { + if (!native_window_->Resize(width_px, height_px)) { ELINUX_LOG(ERROR) << "Failed to resize."; return false; } @@ -62,6 +62,15 @@ bool SurfaceDecoration::GLContextPresent(uint32_t fbo_id) const { return surface_->SwapBuffers(); } +bool SurfaceDecoration::GLContextPresentWithInfo( + const FlutterPresentInfo* info) const { + return true; +} + +void SurfaceDecoration::PopulateExistingDamage( + const intptr_t fbo_id, + FlutterDamage* existing_damage) const {} + uint32_t SurfaceDecoration::GLContextFBO() const { return 0; } diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.h b/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.h index 243a3ee1..8c051712 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.h +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_decoration.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -26,8 +26,10 @@ class SurfaceDecoration : public SurfaceGlDelegate { // Sets a netive platform's window. bool SetNativeWindow(NativeWindow* window); - // Changes an decoration surface size. - bool Resize(const size_t width, const size_t height); + // Changes a decoration surface size. + // @param[in] width_px Physical width of the surface. + // @param[in] height_px Physical height of the surface. + bool Resize(const size_t width_px, const size_t height_px); // Clears and destroys current decoration context. void DestroyContext(); @@ -41,6 +43,13 @@ class SurfaceDecoration : public SurfaceGlDelegate { // |SurfaceGlDelegate| bool GLContextPresent(uint32_t fbo_id) const override; + // |SurfaceGlDelegate| + bool GLContextPresentWithInfo(const FlutterPresentInfo* info) const override; + + // |SurfaceGlDelegate| + void PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) const override; + // |SurfaceGlDelegate| uint32_t GLContextFBO() const override; diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_gl.cc b/src/flutter/shell/platform/linux_embedded/surface/surface_gl.cc index 7467dd84..cdd0eb40 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_gl.cc +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_gl.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -26,6 +26,19 @@ bool SurfaceGl::GLContextPresent(uint32_t fbo_id) const { return true; } +bool SurfaceGl::GLContextPresentWithInfo(const FlutterPresentInfo* info) const { + if (!onscreen_surface_->SwapBuffers(info)) { + return false; + } + native_window_->SwapBuffers(); + return true; +} + +void SurfaceGl::PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) const { + onscreen_surface_->PopulateExistingDamage(fbo_id, existing_damage); +} + uint32_t SurfaceGl::GLContextFBO() const { return 0; } diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_gl.h b/src/flutter/shell/platform/linux_embedded/surface/surface_gl.h index e6b19997..44dee0bd 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_gl.h +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_gl.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -27,6 +27,13 @@ class SurfaceGl final : public SurfaceBase, public SurfaceGlDelegate { // |SurfaceGlDelegate| bool GLContextPresent(uint32_t fbo_id) const override; + // |SurfaceGlDelegate| + bool GLContextPresentWithInfo(const FlutterPresentInfo* info) const override; + + // |SurfaceGlDelegate| + void PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) const override; + // |SurfaceGlDelegate| uint32_t GLContextFBO() const override; diff --git a/src/flutter/shell/platform/linux_embedded/surface/surface_gl_delegate.h b/src/flutter/shell/platform/linux_embedded/surface/surface_gl_delegate.h index 392b277b..709185f7 100644 --- a/src/flutter/shell/platform/linux_embedded/surface/surface_gl_delegate.h +++ b/src/flutter/shell/platform/linux_embedded/surface/surface_gl_delegate.h @@ -1,5 +1,5 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,6 +8,8 @@ #include +#include "flutter/shell/platform/embedder/embedder.h" + namespace flutter { class SurfaceGlDelegate { @@ -18,6 +20,12 @@ class SurfaceGlDelegate { virtual bool GLContextPresent(uint32_t fbo_id) const = 0; + virtual bool GLContextPresentWithInfo( + const FlutterPresentInfo* info) const = 0; + + virtual void PopulateExistingDamage(const intptr_t fbo_id, + FlutterDamage* existing_damage) const = 0; + virtual uint32_t GLContextFBO() const = 0; virtual void* GlProcResolver(const char* name) const = 0; diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window.h b/src/flutter/shell/platform/linux_embedded/window/elinux_window.h index d5ac0c4e..e9b6bf66 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window.h +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window.h @@ -5,7 +5,10 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_WINDOW_ELINUX_WINDOW_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_WINDOW_ELINUX_WINDOW_H_ +#include + #include "flutter/shell/platform/linux_embedded/public/flutter_elinux.h" +#include "flutter/shell/platform/linux_embedded/window_binding_handler.h" namespace flutter { @@ -17,9 +20,15 @@ class ELinuxWindow { protected: virtual bool IsValid() const = 0; - uint32_t GetCurrentWidth() const { return view_properties_.width; } + // Get current window width in physical pixels. + uint32_t GetCurrentWidth() const { + return view_properties_.width * current_scale_; + } - uint32_t GetCurrentHeight() const { return view_properties_.height; } + // Get current window height in physical pixels. + uint32_t GetCurrentHeight() const { + return view_properties_.height * current_scale_; + } void SetRotation(FlutterDesktopViewRotation rotation) { if (rotation == FlutterDesktopViewRotation::kRotation_90) { @@ -33,10 +42,28 @@ class ELinuxWindow { } } + void NotifyDisplayInfoUpdates() const { + if (binding_handler_delegate_) { + binding_handler_delegate_->UpdateDisplayInfo( + std::trunc(1000000.0 / frame_rate_), GetCurrentWidth(), + GetCurrentHeight(), current_scale_); + } + } + FlutterDesktopViewProperties view_properties_; + + // A pointer to a FlutterWindowsView that can be used to update engine + // windowing and input state. + WindowBindingHandlerDelegate* binding_handler_delegate_ = nullptr; + + int32_t display_max_width_ = -1; + int32_t display_max_height_ = -1; + int32_t frame_rate_ = 60000; double current_scale_ = 1.0; uint16_t current_rotation_ = 0; + // The x coordinate of the pointer in physical pixels. double pointer_x_ = 0; + // The y coordinate of the pointer in physical pixels. double pointer_y_ = 0; std::string clipboard_data_ = ""; }; diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window_drm.h b/src/flutter/shell/platform/linux_embedded/window/elinux_window_drm.h index d616a945..fa1fb9e9 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window_drm.h +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window_drm.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,12 +8,20 @@ #include #include #include -#include #include +#ifdef USE_LIBSYSTEMD +#include +#else +#include +#endif + #include #include +#include +#include #include +#include #include "flutter/shell/platform/linux_embedded/logger.h" #include "flutter/shell/platform/linux_embedded/surface/surface_gl.h" @@ -34,6 +42,8 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { ELinuxWindowDrm(FlutterDesktopViewProperties view_properties) : display_valid_(false), is_pending_cursor_add_event_(false) { view_properties_ = view_properties; + current_scale_ = + view_properties.force_scale_factor ? view_properties.scale_factor : 1.0; SetRotation(view_properties_.view_rotation); auto udev = udev_new(); @@ -56,6 +66,7 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { return; } +#ifdef USE_LIBSYSTEMD ret = sd_event_new(&libinput_event_loop_); if (ret < 0) { ELINUX_LOG(ERROR) << "Failed to create libinput event loop."; @@ -69,20 +80,46 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { libinput_event_loop_ = sd_event_unref(libinput_event_loop_); return; } +#else // #ifdef USE_LIBSYSTEMD + ret = uv_loop_init(&main_loop_); + if (ret) { + ELINUX_LOG(ERROR) << "Failed to create main event loop."; + return; + } + ret = uv_poll_init(&main_loop_, &libinput_event_loop_, + libinput_get_fd(libinput_)); + if (ret) { + ELINUX_LOG(ERROR) << "Failed to create libinput event loop."; + return; + } + libinput_event_loop_.data = this; + ret = uv_poll_start(&libinput_event_loop_, + UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED, + OnLibinputEvent); + if (ret) { + ELINUX_LOG(ERROR) << "Failed to listen for user input."; + return; + } +#endif // #ifdef USE_LIBSYSTEMD } ~ELinuxWindowDrm() { +#ifdef USE_LIBSYSTEMD if (udev_drm_event_loop_) { sd_event_unref(udev_drm_event_loop_); } +#endif if (udev_monitor_) { udev_monitor_unref(udev_monitor_); } +#ifdef USE_LIBSYSTEMD if (libinput_event_loop_) { sd_event_unref(libinput_event_loop_); } +#endif + libinput_unref(libinput_); display_valid_ = false; } @@ -98,37 +135,59 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { // |FlutterWindowBindingHandler| bool DispatchEvent() override { +#ifdef USE_LIBSYSTEMD constexpr uint64_t kMaxWaitTime = 0; sd_event_run(libinput_event_loop_, kMaxWaitTime); sd_event_run(udev_drm_event_loop_, kMaxWaitTime); +#else + uv_run(&main_loop_, UV_RUN_NOWAIT); +#endif return true; } // |FlutterWindowBindingHandler| - bool CreateRenderSurface(int32_t width, int32_t height) override { + bool CreateRenderSurface(int32_t width, + int32_t height, + bool enable_impeller) override { + std::vector devices; auto device_filename = std::getenv(kFlutterDrmDeviceEnvironmentKey); - if ((!device_filename) || (device_filename[0] == '\0')) { + if (device_filename && device_filename[0] != '\0') { + devices = split(std::string(device_filename), ':'); + } + if (devices.empty()) { ELINUX_LOG(WARNING) << kFlutterDrmDeviceEnvironmentKey << " is not set, use " << kDrmDeviceDefaultFilename; - device_filename = const_cast(kDrmDeviceDefaultFilename); - } + devices.push_back(const_cast(kDrmDeviceDefaultFilename)); + } + + bool device_found = false; + for (auto i = 0; i < devices.size(); i++) { + native_window_ = std::make_unique( + devices[i].c_str(), current_rotation_, view_properties_.enable_vsync); + if (!native_window_->IsValid()) { + ELINUX_LOG(ERROR) << "Failed to create the native window (" + << devices[i] << ")."; + native_window_ = nullptr; + continue; + } - if (current_rotation_ == 90 || current_rotation_ == 270) { - std::swap(width, height); + if (!RegisterUdevDrmEventLoop(devices[i].c_str())) { + ELINUX_LOG(ERROR) << "Failed to register udev drm event loop (" + << devices[i] << ")."; + native_window_ = nullptr; + continue; + } + + device_found = true; + ELINUX_LOG(INFO) << devices[i] << " was selected as the DRM device."; } - native_window_ = std::make_unique(device_filename); - if (!native_window_->IsValid()) { - ELINUX_LOG(ERROR) << "Failed to create the native window"; + if (!device_found) { return false; } - if (!RegisterUdevDrmEventLoop(device_filename)) { - ELINUX_LOG(ERROR) << "Failed to register udev drm event loop."; - return false; - } display_valid_ = true; - render_surface_ = native_window_->CreateRenderSurface(); + render_surface_ = native_window_->CreateRenderSurface(enable_impeller); if (!render_surface_->SetNativeWindow(native_window_.get())) { return false; } @@ -240,31 +299,34 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { } constexpr char kFileNameSeparator[] = "/"; - auto pos = device_filename.find_last_of(kFileNameSeparator); - if (pos == std::string::npos) { - ELINUX_LOG(ERROR) << "Failed to get device name position."; - udev_unref(udev); - return false; - } + if (device_filename != "drm-nvdc") { + auto pos = device_filename.find_last_of(kFileNameSeparator); + if (pos == std::string::npos) { + ELINUX_LOG(ERROR) << "Failed to get device name position."; + udev_unref(udev); + return false; + } - auto device_name = device_filename.substr(pos + 1); - auto device = udev_device_new_from_subsystem_sysname( - udev, kUdevMonitorSubsystemDrm, device_name.c_str()); - if (!device) { - ELINUX_LOG(ERROR) << "Failed to get device from " << device_name; - udev_unref(udev); - return false; - } + auto device_name = device_filename.substr(pos + 1); + auto device = udev_device_new_from_subsystem_sysname( + udev, kUdevMonitorSubsystemDrm, device_name.c_str()); + if (!device) { + ELINUX_LOG(ERROR) << "Failed to get device from " << device_name; + udev_unref(udev); + return false; + } - auto sysnum = udev_device_get_sysnum(device); - if (!sysnum) { - ELINUX_LOG(ERROR) << "Failed to get device id."; - udev_unref(udev); - return false; + auto sysnum = udev_device_get_sysnum(device); + if (!sysnum) { + ELINUX_LOG(ERROR) << "Failed to get device id."; + udev_unref(udev); + return false; + } + drm_device_id_ = std::atoi(sysnum); } - drm_device_id_ = std::atoi(sysnum); udev_unref(udev); +#ifdef USE_LIBSYSTEMD if (sd_event_new(&udev_drm_event_loop_) < 0) { ELINUX_LOG(ERROR) << "Failed to create udev drm event loop."; return false; @@ -276,6 +338,22 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { ELINUX_LOG(ERROR) << "Failed to listen for udev drm event."; return false; } +#else // #ifdef USE_LIBSYSTEMD + if (uv_poll_init(&main_loop_, &udev_drm_event_loop_, + udev_monitor_get_fd(udev_monitor_))) { + ELINUX_LOG(ERROR) << "Failed to create udev drm event loop."; + return false; + } + + udev_drm_event_loop_.data = this; + + if (uv_poll_start(&udev_drm_event_loop_, + UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED, + OnUdevDrmEvent)) { + ELINUX_LOG(ERROR) << "Failed to listen for udev drm event."; + return false; + } +#endif // #ifdef USE_LIBSYSTEMD if (udev_monitor_enable_receiving(udev_monitor_) < 0) { ELINUX_LOG(ERROR) << "Failed to enable udev monitor receiving."; @@ -285,6 +363,7 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { return true; } +#ifdef USE_LIBSYSTEMD static int OnUdevDrmEvent(sd_event_source* source, int fd, uint32_t revents, @@ -295,9 +374,18 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { ELINUX_LOG(ERROR) << "Failed to receive udev device."; return -1; } +#else + static void OnUdevDrmEvent(uv_poll_t* handle, int status, int events) { + auto self = reinterpret_cast(handle->data); + auto device = udev_monitor_receive_device(self->udev_monitor_); + if (!device) { + ELINUX_LOG(ERROR) << "Failed to receive udev device."; + return; + } +#endif if (self->IsUdevEventHotplug(*device) && - self->native_window_->ConfigureDisplay()) { + self->native_window_->ConfigureDisplay(self->current_rotation_)) { auto width = self->native_window_->Width(); auto height = self->native_window_->Height(); if (self->current_rotation_ == 90 || self->current_rotation_ == 270) { @@ -318,7 +406,10 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { } udev_device_unref(device); + +#ifdef USE_LIBSYSTEMD return 0; +#endif } bool IsUdevEventHotplug(udev_device& device) { @@ -326,7 +417,7 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { if (!sysnum) { ELINUX_LOG(ERROR) << "Failed to get device id."; return false; - } else if (std::atoi(sysnum) != drm_device_id_) { + } else if (drm_device_id_ && std::atoi(sysnum) != *drm_device_id_) { ELINUX_LOG(ERROR) << "Not expected device id."; return false; } @@ -343,6 +434,7 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { return std::strcmp(value, kPropertyOn) == 0; } +#ifdef USE_LIBSYSTEMD static int OnLibinputEvent(sd_event_source* source, int fd, uint32_t revents, @@ -353,6 +445,15 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { ELINUX_LOG(ERROR) << "Failed to dispatch libinput events."; return -ret; } +#else + static void OnLibinputEvent(uv_poll_t* handle, int uv_status, int uv_events) { + auto self = reinterpret_cast(handle->data); + auto ret = libinput_dispatch(self->libinput_); + if (ret < 0) { + ELINUX_LOG(ERROR) << "Failed to dispatch libinput events."; + return; + } +#endif auto previous_pointer_x = self->pointer_x_; auto previous_pointer_y = self->pointer_y_; @@ -410,7 +511,9 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { self->native_window_->MoveCursor(self->pointer_x_, self->pointer_y_); } +#ifdef USE_LIBSYSTEMD return 0; +#endif } void OnDeviceAdded(libinput_event* event) { @@ -462,18 +565,22 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { void OnPointerMotion(libinput_event* event) { DetectPointerDevice(event); if (binding_handler_delegate_) { + auto width = view_properties_.width; + auto height = view_properties_.height; + if (current_rotation_ == 90 || current_rotation_ == 270) { + std::swap(width, height); + } + auto pointer_event = libinput_event_get_pointer_event(event); auto dx = libinput_event_pointer_get_dx(pointer_event); auto dy = libinput_event_pointer_get_dy(pointer_event); auto new_pointer_x = pointer_x_ + dx; new_pointer_x = std::max(0.0, new_pointer_x); - new_pointer_x = std::min(static_cast(view_properties_.width - 1), - new_pointer_x); + new_pointer_x = std::min(static_cast(width - 1), new_pointer_x); auto new_pointer_y = pointer_y_ + dy; new_pointer_y = std::max(0.0, new_pointer_y); - new_pointer_y = std::min(static_cast(view_properties_.height - 1), - new_pointer_y); + new_pointer_y = std::min(static_cast(height - 1), new_pointer_y); binding_handler_delegate_->OnPointerMove(new_pointer_x, new_pointer_y); pointer_x_ = new_pointer_x; @@ -482,13 +589,19 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { } void OnPointerMotionAbsolute(libinput_event* event) { + auto width = view_properties_.width; + auto height = view_properties_.height; + if (current_rotation_ == 90 || current_rotation_ == 270) { + std::swap(width, height); + } + DetectPointerDevice(event); if (binding_handler_delegate_) { auto pointer_event = libinput_event_get_pointer_event(event); - auto x = libinput_event_pointer_get_absolute_x_transformed( - pointer_event, view_properties_.width); - auto y = libinput_event_pointer_get_absolute_y_transformed( - pointer_event, view_properties_.height); + auto x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, + width); + auto y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, + height); binding_handler_delegate_->OnPointerMove(x, y); pointer_x_ = x; @@ -580,13 +693,17 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { void OnTouchDown(libinput_event* event) { if (binding_handler_delegate_) { + auto width = view_properties_.width; + auto height = view_properties_.height; + if (current_rotation_ == 90 || current_rotation_ == 270) { + std::swap(width, height); + } + auto touch_event = libinput_event_get_touch_event(event); auto time = libinput_event_touch_get_time(touch_event); auto slot = libinput_event_touch_get_seat_slot(touch_event); - auto x = libinput_event_touch_get_x_transformed(touch_event, - view_properties_.width); - auto y = libinput_event_touch_get_y_transformed(touch_event, - view_properties_.height); + auto x = libinput_event_touch_get_x_transformed(touch_event, width); + auto y = libinput_event_touch_get_y_transformed(touch_event, height); binding_handler_delegate_->OnTouchDown(time, slot, x, y); } } @@ -602,13 +719,17 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { void OnTouchMotion(libinput_event* event) { if (binding_handler_delegate_) { + auto width = view_properties_.width; + auto height = view_properties_.height; + if (current_rotation_ == 90 || current_rotation_ == 270) { + std::swap(width, height); + } + auto touch_event = libinput_event_get_touch_event(event); auto time = libinput_event_touch_get_time(touch_event); auto slot = libinput_event_touch_get_seat_slot(touch_event); - auto x = libinput_event_touch_get_x_transformed(touch_event, - view_properties_.width); - auto y = libinput_event_touch_get_y_transformed(touch_event, - view_properties_.height); + auto x = libinput_event_touch_get_x_transformed(touch_event, width); + auto y = libinput_event_touch_get_y_transformed(touch_event, height); binding_handler_delegate_->OnTouchMotion(time, slot, x, y); } } @@ -650,29 +771,45 @@ class ELinuxWindowDrm : public ELinuxWindow, public WindowBindingHandler { kScrollOffsetMultiplier); } + std::vector split(std::string s, char delim) { + std::vector res; + std::string item; + std::stringstream ss(s); + + while (getline(ss, item, delim)) { + if (!item.empty()) { + res.push_back(item); + } + } + return res; + } + struct LibinputDeviceData { size_t id; bool is_pointer_device; }; - // A pointer to a FlutterWindowsView that can be used to update engine - // windowing and input state. - WindowBindingHandlerDelegate* binding_handler_delegate_ = nullptr; - std::unique_ptr native_window_; std::unique_ptr render_surface_; bool display_valid_; bool is_pending_cursor_add_event_; - sd_event* libinput_event_loop_; libinput* libinput_; std::unordered_map> libinput_devices_; int libinput_pointer_devices_ = 0; - sd_event* udev_drm_event_loop_ = nullptr; udev_monitor* udev_monitor_ = nullptr; - int drm_device_id_; + std::optional drm_device_id_; + +#ifdef USE_LIBSYSTEMD + sd_event* libinput_event_loop_; + sd_event* udev_drm_event_loop_ = nullptr; +#else + uv_loop_t main_loop_; + uv_poll_t libinput_event_loop_; + uv_poll_t udev_drm_event_loop_; +#endif }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.cc b/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.cc index be0c75e7..6467bf94 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.cc +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,8 +10,11 @@ #include #include +#include #include #include +#include +#include #include #include "flutter/shell/platform/linux_embedded/logger.h" @@ -22,6 +25,7 @@ namespace flutter { namespace { constexpr char kZwpTextInputManagerV1[] = "zwp_text_input_manager_v1"; constexpr char kZwpTextInputManagerV3[] = "zwp_text_input_manager_v3"; +constexpr char kZxdgDecorationManagerV1[] = "zxdg_decoration_manager_v1"; constexpr char kWlCursorThemeBottomLeftCorner[] = "bottom_left_corner"; constexpr char kWlCursorThemeBottomRightCorner[] = "bottom_right_corner"; @@ -48,38 +52,60 @@ const wl_registry_listener ELinuxWindowWayland::kWlRegistryListener = { uint32_t name, const char* interface, uint32_t version) { + ELINUX_LOG(TRACE) << "wl_registry_listener.global"; + auto self = reinterpret_cast(data); self->WlRegistryHandler(wl_registry, name, interface, version); }, .global_remove = [](void* data, wl_registry* wl_registry, uint32_t name) { + ELINUX_LOG(TRACE) << "wl_registry_listener.global_remove"; + auto self = reinterpret_cast(data); self->WlUnRegistryHandler(wl_registry, name); }, }; const xdg_wm_base_listener ELinuxWindowWayland::kXdgWmBaseListener = { - .ping = [](void* data, - xdg_wm_base* xdg_wm_base, - uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); }, + .ping = + [](void* data, xdg_wm_base* xdg_wm_base, uint32_t serial) { + ELINUX_LOG(TRACE) << "xdg_wm_base_listener.ping"; + xdg_wm_base_pong(xdg_wm_base, serial); + }, }; const xdg_surface_listener ELinuxWindowWayland::kXdgSurfaceListener = { .configure = [](void* data, xdg_surface* xdg_surface, uint32_t serial) { + ELINUX_LOG(TRACE) << "xdg_surface_listener.configure"; + auto self = reinterpret_cast(data); constexpr int32_t x = 0; int32_t y = 0; - if (self->view_properties_.use_window_decoration) { - // TODO: Moves the window to the bottom to show the window - // decorations, but the bottom area of the window will be hidden - // because of this shifting. + auto width = self->view_properties_.width; + auto height = self->view_properties_.height; + + if (self->window_decorations_) { + // Shift the window position to the bottom to show decoration + // even when the window is displayed in the upper left corner + // of the screen y = -self->window_decorations_->Height(); + if (!self->maximised_) { + height -= y; + } + + // clip + if (self->display_max_height_ > 0) { + height = std::min(height, self->display_max_height_); + } } - xdg_surface_set_window_geometry(xdg_surface, x, y, - self->view_properties_.width, - self->view_properties_.height); + xdg_surface_set_window_geometry(xdg_surface, x, y, width, height); + xdg_surface_ack_configure(xdg_surface, serial); + if (self->wait_for_configure_) { + self->wait_for_configure_ = false; + } + self->request_redraw_ = true; }, }; @@ -90,69 +116,88 @@ const xdg_toplevel_listener ELinuxWindowWayland::kXdgToplevelListener = { int32_t width, int32_t height, wl_array* states) { - auto is_maximized = false; - auto is_resizing = false; - uint32_t* state = static_cast(states->data); - for (auto i = 0; i < states->size; i++) { - switch (*state) { - case XDG_TOPLEVEL_STATE_MAXIMIZED: - is_maximized = true; - break; - case XDG_TOPLEVEL_STATE_RESIZING: - is_resizing = true; - break; - case XDG_TOPLEVEL_STATE_ACTIVATED: - case XDG_TOPLEVEL_STATE_FULLSCREEN: - default: - break; - } - state++; - } + ELINUX_LOG(TRACE) + << "xdg_toplevel_listener.configure: " << width << ", " << height; auto self = reinterpret_cast(data); if (self->current_rotation_ == 90 || self->current_rotation_ == 270) { std::swap(width, height); } - int32_t next_width = 0; - int32_t next_height = 0; - if (is_maximized || is_resizing) { - next_width = width; - next_height = height; - } else if (self->restore_window_required_) { + int32_t next_width_dip = width / self->current_scale_; + int32_t next_height_dip = height / self->current_scale_; + if (self->restore_window_required_) { self->restore_window_required_ = false; - next_width = self->restore_window_width_; - next_height = self->restore_window_height_; + next_width_dip = self->restore_window_width_; + next_height_dip = self->restore_window_height_; } - if (!next_width || !next_height || - (self->view_properties_.width == next_width && - self->view_properties_.height == next_height)) { + if (!next_width_dip || !next_height_dip || + (self->view_properties_.width == next_width_dip && + self->view_properties_.height == next_height_dip)) { return; } - self->view_properties_.width = next_width; - self->view_properties_.height = next_height; - if (self->window_decorations_) { - self->window_decorations_->Resize(next_width, next_height); - } - if (self->binding_handler_delegate_) { - self->binding_handler_delegate_->OnWindowSizeChanged(next_width, - next_height); - } + ELINUX_LOG(TRACE) << "request redraw: " << next_width_dip << ", " + << next_height_dip; + self->view_properties_.width = next_width_dip; + self->view_properties_.height = next_height_dip; + self->request_redraw_ = true; }, .close = [](void* data, xdg_toplevel* xdg_toplevel) { + ELINUX_LOG(TRACE) << "xdg_toplevel_listener.close"; + auto self = reinterpret_cast(data); self->running_ = false; }}; +const wl_surface_listener ELinuxWindowWayland::kWlSurfaceListener = { + .enter = + [](void* data, wl_surface* wl_surface, wl_output* output) { + ELINUX_LOG(TRACE) << "wl_surface_listener.enter"; + + // The compositor can send a null output: crbug.com/1332540 + if (!output) { + ELINUX_LOG(ERROR) << "cannot enter a NULL output"; + return; + } + + const uint32_t output_id = + wl_proxy_get_id(reinterpret_cast(output)); + ELINUX_LOG(TRACE) << "window entered output " << output_id; + auto self = reinterpret_cast(data); + + self->entered_outputs_.insert(output_id); + self->UpdateWindowScale(); + }, + .leave = + [](void* data, wl_surface* wl_surface, wl_output* output) { + ELINUX_LOG(TRACE) << "wl_surface_listener.leave"; + + // The compositor can send a null output: crbug.com/1332540 + if (!output) { + ELINUX_LOG(ERROR) << "cannot leave a NULL output"; + return; + } + + const uint32_t output_id = + wl_proxy_get_id(reinterpret_cast(output)); + ELINUX_LOG(TRACE) << "window left output " << output_id; + auto self = reinterpret_cast(data); + + self->entered_outputs_.erase(output_id); + self->UpdateWindowScale(); + }}; + const wp_presentation_listener ELinuxWindowWayland::kWpPresentationListener = { .clock_id = [](void* data, wp_presentation* wp_presentation, uint32_t clk_id) { + ELINUX_LOG(TRACE) << "wp_presentation_listener.clock_id"; + auto self = reinterpret_cast(data); self->wp_presentation_clk_id_ = clk_id; - ELINUX_LOG(TRACE) << "presentation info: clk_id = " << clk_id; + ELINUX_LOG(TRACE) << "clk_id = " << clk_id; }, }; @@ -161,7 +206,10 @@ const wp_presentation_feedback_listener .sync_output = [](void* data, struct wp_presentation_feedback* wp_presentation_feedback, - wl_output* output) {}, + wl_output* output) { + ELINUX_LOG(TRACE) + << "wp_presentation_feedback_listener.sync_output"; + }, .presented = [](void* data, struct wp_presentation_feedback* wp_presentation_feedback, @@ -172,6 +220,9 @@ const wp_presentation_feedback_listener uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { + ELINUX_LOG(TRACE) + << "wp_presentation_feedback_listener.presented"; + auto self = reinterpret_cast(data); self->last_frame_time_nanos_ = (((static_cast(tv_sec_hi) << 32) + tv_sec_lo) * @@ -184,6 +235,7 @@ const wp_presentation_feedback_listener self->window_decorations_->Draw(); } + wp_presentation_feedback_destroy(wp_presentation_feedback); wp_presentation_feedback_add_listener( ::wp_presentation_feedback(self->wp_presentation_, self->native_window_->Surface()), @@ -191,12 +243,28 @@ const wp_presentation_feedback_listener }, .discarded = [](void* data, - struct wp_presentation_feedback* wp_presentation_feedback) {}, + struct wp_presentation_feedback* wp_presentation_feedback) { + ELINUX_LOG(TRACE) + << "wp_presentation_feedback_listener.discarded"; + + auto self = reinterpret_cast(data); + if (self->window_decorations_) { + self->window_decorations_->Draw(); + } + + wp_presentation_feedback_destroy(wp_presentation_feedback); + wp_presentation_feedback_add_listener( + ::wp_presentation_feedback(self->wp_presentation_, + self->native_window_->Surface()), + &kWpPresentationFeedbackListener, data); + }, }; const wl_callback_listener ELinuxWindowWayland::kWlSurfaceFrameListener = { .done = [](void* data, wl_callback* wl_callback, uint32_t time) { + ELINUX_LOG(TRACE) << "wl_callback_listener.done"; + // The presentation-time is an extended protocol and isn't supported // by all compositors. This path is for when it wasn't supported. auto self = reinterpret_cast(data); @@ -218,33 +286,43 @@ const wl_callback_listener ELinuxWindowWayland::kWlSurfaceFrameListener = { const wl_seat_listener ELinuxWindowWayland::kWlSeatListener = { .capabilities = [](void* data, wl_seat* seat, uint32_t caps) -> void { - auto self = reinterpret_cast(data); + ELINUX_LOG(TRACE) << "wl_seat_listener.capabilities"; - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !self->wl_pointer_) { - self->wl_pointer_ = wl_seat_get_pointer(seat); - wl_pointer_add_listener(self->wl_pointer_, &kWlPointerListener, self); - } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && self->wl_pointer_) { - wl_pointer_destroy(self->wl_pointer_); - self->wl_pointer_ = nullptr; + auto self = reinterpret_cast(data); + auto seat_iter = self->seat_inputs_map_.find(seat); + if (seat_iter == self->seat_inputs_map_.end()) { + ELINUX_LOG(ERROR) << "Failed to find the seat"; + return; + } + auto& inputs = seat_iter->second; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !inputs.pointer) { + inputs.pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(inputs.pointer, &kWlPointerListener, self); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && inputs.pointer) { + wl_pointer_release(inputs.pointer); + inputs.pointer = nullptr; } - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !self->wl_touch_) { - self->wl_touch_ = wl_seat_get_touch(seat); - wl_touch_add_listener(self->wl_touch_, &kWlTouchListener, self); - } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && self->wl_touch_) { - wl_touch_destroy(self->wl_touch_); - self->wl_touch_ = nullptr; + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !inputs.touch) { + inputs.touch = wl_seat_get_touch(seat); + wl_touch_add_listener(inputs.touch, &kWlTouchListener, self); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && inputs.touch) { + wl_touch_release(inputs.touch); + inputs.touch = nullptr; } - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !self->wl_keyboard_) { - self->wl_keyboard_ = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(self->wl_keyboard_, &kWlKeyboardListener, - self); - } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && self->wl_keyboard_) { - wl_keyboard_destroy(self->wl_keyboard_); - self->wl_keyboard_ = nullptr; + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !inputs.keyboard) { + inputs.keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(inputs.keyboard, &kWlKeyboardListener, self); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && inputs.keyboard) { + wl_keyboard_release(inputs.keyboard); + inputs.keyboard = nullptr; } }, + .name = [](void* data, struct wl_seat* wl_seat, const char* name) -> void { + ELINUX_LOG(TRACE) << "wl_seat_listener.name"; + }, }; const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { @@ -254,6 +332,8 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) -> void { + ELINUX_LOG(TRACE) << "wl_pointer_listener.enter"; + auto self = reinterpret_cast(data); self->wl_current_surface_ = surface; self->serial_ = serial; @@ -264,21 +344,34 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { } if (self->binding_handler_delegate_) { - double x = wl_fixed_to_double(surface_x); - double y = wl_fixed_to_double(surface_y); - self->binding_handler_delegate_->OnPointerMove(x, y); - self->pointer_x_ = x; - self->pointer_y_ = y; + double x_px = wl_fixed_to_double(surface_x) * self->current_scale_; + double y_px = wl_fixed_to_double(surface_y) * self->current_scale_; + self->binding_handler_delegate_->OnPointerMove(x_px, y_px); + self->pointer_x_ = x_px; + self->pointer_y_ = y_px; } + + // Force redraw even though it is the same cursor name + auto copy = self->cursor_info_.cursor_name; + self->cursor_info_.cursor_name.clear(); + self->UpdateFlutterCursor(copy); }, .leave = [](void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface) -> void { + ELINUX_LOG(TRACE) << "wl_pointer_listener.leave"; + auto self = reinterpret_cast(data); self->wl_current_surface_ = surface; self->serial_ = serial; + if (self->view_properties_.use_mouse_cursor) { + // Clear the cursor name in order to make sure it gets redrawn next time + // it enters the surface. + self->cursor_info_.cursor_name.clear(); + } + if (self->binding_handler_delegate_) { self->binding_handler_delegate_->OnPointerLeave(); self->pointer_x_ = -1; @@ -290,13 +383,15 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) -> void { + ELINUX_LOG(TRACE) << "wl_pointer_listener.motion"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { - double x = wl_fixed_to_double(surface_x); - double y = wl_fixed_to_double(surface_y); - self->binding_handler_delegate_->OnPointerMove(x, y); - self->pointer_x_ = x; - self->pointer_y_ = y; + double x_px = wl_fixed_to_double(surface_x) * self->current_scale_; + double y_px = wl_fixed_to_double(surface_y) * self->current_scale_; + self->binding_handler_delegate_->OnPointerMove(x_px, y_px); + self->pointer_x_ = x_px; + self->pointer_y_ = y_px; } }, .button = [](void* data, @@ -305,6 +400,8 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { uint32_t time, uint32_t button, uint32_t status) -> void { + ELINUX_LOG(TRACE) << "wl_pointer_listener.button"; + auto self = reinterpret_cast(data); self->serial_ = serial; @@ -313,7 +410,14 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { self->window_decorations_->IsMatched( self->wl_current_surface_, WindowDecoration::DecorationType::TITLE_BAR)) { - xdg_toplevel_move(self->xdg_toplevel_, self->wl_seat_, serial); + for (auto& [seat, inputs] : self->seat_inputs_map_) { + if (inputs.pointer == pointer) { + xdg_toplevel_move(self->xdg_toplevel_, seat, serial); + return; + } + } + ELINUX_LOG(TRACE) + << "Failed to find the pointer for moving the window"; return; } @@ -392,6 +496,8 @@ const wl_pointer_listener ELinuxWindowWayland::kWlPointerListener = { uint32_t time, uint32_t axis, wl_fixed_t value) -> void { + ELINUX_LOG(TRACE) << "wl_pointer_listener.axis"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { double delta = wl_fixed_to_double(value); @@ -414,6 +520,8 @@ const wl_touch_listener ELinuxWindowWayland::kWlTouchListener = { int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y) -> void { + ELINUX_LOG(TRACE) << "wl_touch_listener.down"; + auto self = reinterpret_cast(data); self->serial_ = serial; if (self->binding_handler_delegate_) { @@ -427,6 +535,8 @@ const wl_touch_listener ELinuxWindowWayland::kWlTouchListener = { uint32_t serial, uint32_t time, int32_t id) -> void { + ELINUX_LOG(TRACE) << "wl_touch_listener.up"; + auto self = reinterpret_cast(data); self->serial_ = serial; if (self->binding_handler_delegate_) { @@ -439,6 +549,8 @@ const wl_touch_listener ELinuxWindowWayland::kWlTouchListener = { int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y) -> void { + ELINUX_LOG(TRACE) << "wl_touch_listener.motion"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { double x = wl_fixed_to_double(surface_x); @@ -448,6 +560,8 @@ const wl_touch_listener ELinuxWindowWayland::kWlTouchListener = { }, .frame = [](void* data, wl_touch* wl_touch) -> void {}, .cancel = [](void* data, wl_touch* wl_touch) -> void { + ELINUX_LOG(TRACE) << "wl_touch_listener.cancel"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { self->binding_handler_delegate_->OnTouchCancel(); @@ -461,6 +575,8 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { uint32_t format, int fd, uint32_t size) -> void { + ELINUX_LOG(TRACE) << "wl_keyboard_listener.keymap"; + auto self = reinterpret_cast(data); assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); if (self->binding_handler_delegate_) { @@ -472,6 +588,8 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { uint32_t serial, wl_surface* surface, wl_array* keys) -> void { + ELINUX_LOG(TRACE) << "wl_keyboard_listener.enter"; + auto self = reinterpret_cast(data); self->serial_ = serial; }, @@ -479,6 +597,8 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface) -> void { + ELINUX_LOG(TRACE) << "wl_keyboard_listener.leave"; + auto self = reinterpret_cast(data); self->serial_ = serial; }, @@ -488,6 +608,8 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { uint32_t time, uint32_t key, uint32_t state) -> void { + ELINUX_LOG(TRACE) << "wl_keyboard_listener.key"; + auto self = reinterpret_cast(data); self->serial_ = serial; if (self->binding_handler_delegate_) { @@ -502,6 +624,8 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { uint32_t mods_latched, uint32_t mods_locked, uint32_t group) -> void { + ELINUX_LOG(TRACE) << "wl_keyboard_listener.modifiers"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { self->binding_handler_delegate_->OnKeyModifiers( @@ -509,7 +633,7 @@ const wl_keyboard_listener ELinuxWindowWayland::kWlKeyboardListener = { } }, .repeat_info = [](void* data, wl_keyboard* wl_keyboard, int rate, int delay) - -> void {}, + -> void { ELINUX_LOG(TRACE) << "wl_keyboard_listener.repeat_info"; }, }; const wl_output_listener ELinuxWindowWayland::kWlOutputListener = { @@ -522,22 +646,35 @@ const wl_output_listener ELinuxWindowWayland::kWlOutputListener = { int32_t subpixel, const char* make, const char* model, - int32_t output_transform) -> void {}, + int32_t output_transform) -> void { + ELINUX_LOG(TRACE) << "wl_output_listener.geometry"; + auto self = reinterpret_cast(data); + self->transform_ = output_transform; + }, .mode = [](void* data, wl_output* wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) -> void { + ELINUX_LOG(TRACE) << "wl_output_listener.mode"; + auto self = reinterpret_cast(data); if (flags & WL_OUTPUT_MODE_CURRENT) { - if (self->current_rotation_ == 90 || self->current_rotation_ == 270) { + if (self->current_rotation_ == 90 || self->current_rotation_ == 270 || + self->transform_ == WL_OUTPUT_TRANSFORM_90 || + self->transform_ == WL_OUTPUT_TRANSFORM_270 || + self->transform_ == WL_OUTPUT_TRANSFORM_FLIPPED_90 || + self->transform_ == WL_OUTPUT_TRANSFORM_FLIPPED_270) { std::swap(width, height); } ELINUX_LOG(INFO) << "Display output info: width = " << width << ", height = " << height << ", refresh = " << refresh; + self->display_max_width_ = width; + self->display_max_height_ = height; + // Some composers send 0 for the refresh value. if (refresh != 0) { self->frame_rate_ = refresh; @@ -547,22 +684,45 @@ const wl_output_listener ELinuxWindowWayland::kWlOutputListener = { FlutterDesktopViewMode::kFullscreen) { self->view_properties_.width = width; self->view_properties_.height = height; + self->request_redraw_ = true; + } - if (self->window_decorations_) { - self->window_decorations_->Resize(width, height); - } - - if (self->binding_handler_delegate_) { - self->binding_handler_delegate_->OnWindowSizeChanged(width, height); - } + if (self->view_properties_.width > width) { + ELINUX_LOG(WARNING) + << "Requested width size(" << self->view_properties_.width << ") " + << "is larger than display size(" << width + << ")" + ", clipping"; + self->view_properties_.width = width; + self->request_redraw_ = true; + } + if (self->view_properties_.height > height) { + ELINUX_LOG(WARNING) + << "Requested height size(" << self->view_properties_.height + << ") " << "is larger than display size(" << height + << ")" + ", clipping"; + self->view_properties_.height = height; + self->request_redraw_ = true; } } }, - .done = [](void* data, wl_output* wl_output) -> void {}, + .done = [](void* data, wl_output* wl_output) -> void { + ELINUX_LOG(TRACE) << "wl_output_listener.done"; + }, .scale = [](void* data, wl_output* wl_output, int32_t scale) -> void { + ELINUX_LOG(TRACE) << "wl_output_listener.scale"; + auto self = reinterpret_cast(data); - ELINUX_LOG(INFO) << "Display output scale: " << scale; - self->current_scale_ = scale; + const uint32_t output_id = + wl_proxy_get_id(reinterpret_cast(wl_output)); + + ELINUX_LOG(INFO) << "Display scale for output(" << output_id + << "): " << scale; + + self->wl_output_scale_factors_[output_id] = scale; + + self->UpdateWindowScale(); }, }; @@ -571,6 +731,8 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = .enter = [](void* data, zwp_text_input_v1* zwp_text_input_v1, wl_surface* surface) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.enter"; + auto self = reinterpret_cast(data); // If there is no input data, the backspace key cannot be used, // so set dummy data. @@ -580,6 +742,8 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = } }, .leave = [](void* data, zwp_text_input_v1* zwp_text_input_v1) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.leave"; + auto self = reinterpret_cast(data); if (self->zwp_text_input_v1_) { zwp_text_input_v1_hide_input_panel(self->zwp_text_input_v1_); @@ -587,18 +751,27 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = }, .modifiers_map = [](void* data, zwp_text_input_v1* zwp_text_input_v1, - wl_array* map) -> void {}, + wl_array* map) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.modifiers_map"; + }, .input_panel_state = [](void* data, zwp_text_input_v1* zwp_text_input_v1, - uint32_t state) -> void {}, + uint32_t state) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.input_panel_state"; + }, .preedit_string = [](void* data, zwp_text_input_v1* zwp_text_input_v1, uint32_t serial, const char* text, const char* commit) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.preedit_string"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_ && strlen(text)) { - self->binding_handler_delegate_->OnVirtualKey(text[0]); + std::wstring_convert, char16_t> + utf8_converter; + std::u16string utf16_text = utf8_converter.from_bytes(text); + self->binding_handler_delegate_->OnVirtualKey(utf16_text[0]); } if (self->zwp_text_input_v1_) { zwp_text_input_v1_reset(self->zwp_text_input_v1_); @@ -612,18 +785,27 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = zwp_text_input_v1* zwp_text_input_v1, uint32_t index, uint32_t length, - uint32_t style) -> void {}, + uint32_t style) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.preedit_styling"; + }, .preedit_cursor = [](void* data, zwp_text_input_v1* zwp_text_input_v1, - int32_t index) -> void {}, + int32_t index) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.preedit_cursor"; + }, .commit_string = [](void* data, zwp_text_input_v1* zwp_text_input_v1, uint32_t serial, const char* text) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.commit_string"; + // commit_string is notified only when the space key is pressed. auto self = reinterpret_cast(data); if (self->binding_handler_delegate_ && strlen(text)) { - self->binding_handler_delegate_->OnVirtualKey(text[0]); + std::wstring_convert, char16_t> + utf8_converter; + std::u16string utf16_text = utf8_converter.from_bytes(text); + self->binding_handler_delegate_->OnVirtualKey(utf16_text[0]); } // If there is no input data, the backspace key cannot be used, // so set dummy data. @@ -635,11 +817,16 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = .cursor_position = [](void* data, zwp_text_input_v1* zwp_text_input_v1, int32_t index, - int32_t anchor) -> void {}, + int32_t anchor) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.cursor_position"; + }, .delete_surrounding_text = [](void* data, zwp_text_input_v1* zwp_text_input_v1, int32_t index, uint32_t length) -> void { + ELINUX_LOG(TRACE) + << "zwp_text_input_v1_listener.delete_surrounding_text"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_) { self->binding_handler_delegate_->OnVirtualSpecialKey(KEY_BACKSPACE); @@ -658,6 +845,8 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = uint32_t sym, uint32_t state, uint32_t modifiers) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.keysym"; + auto self = reinterpret_cast(data); if ((state == WL_KEYBOARD_KEY_STATE_PRESSED) && (self->binding_handler_delegate_)) { @@ -688,11 +877,15 @@ const zwp_text_input_v1_listener ELinuxWindowWayland::kZwpTextInputV1Listener = .language = [](void* data, zwp_text_input_v1* zwp_text_input_v1, uint32_t serial, - const char* language) -> void {}, + const char* language) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.language"; + }, .text_direction = [](void* data, zwp_text_input_v1* zwp_text_input_v1, uint32_t serial, - uint32_t direction) -> void {}, + uint32_t direction) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v1_listener.text_direction"; + }, }; const zwp_text_input_v3_listener ELinuxWindowWayland::kZwpTextInputV3Listener = @@ -700,6 +893,8 @@ const zwp_text_input_v3_listener ELinuxWindowWayland::kZwpTextInputV3Listener = .enter = [](void* data, zwp_text_input_v3* zwp_text_input_v3, wl_surface* surface) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v3_listener.enter"; + // To appear the on-screen keyboard when the user returns to a Flutter // app which needs to show the on-screen keyboard. auto self = reinterpret_cast(data); @@ -709,50 +904,76 @@ const zwp_text_input_v3_listener ELinuxWindowWayland::kZwpTextInputV3Listener = }, .leave = [](void* data, zwp_text_input_v3* zwp_text_input_v3, - wl_surface* surface) -> void {}, + wl_surface* surface) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v3_listener.leave"; + }, .preedit_string = [](void* data, zwp_text_input_v3* zwp_text_input_v3, const char* text, int32_t cursor_begin, - int32_t cursor_end) -> void {}, + int32_t cursor_end) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v3_listener.preedit_string"; + }, .commit_string = [](void* data, zwp_text_input_v3* zwp_text_input_v3, const char* text) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v3_listener.commit_string"; + auto self = reinterpret_cast(data); if (self->binding_handler_delegate_ && strlen(text)) { - self->binding_handler_delegate_->OnVirtualKey(text[0]); + std::wstring_convert, char16_t> + utf8_converter; + std::u16string utf16_text = utf8_converter.from_bytes(text); + self->binding_handler_delegate_->OnVirtualKey(utf16_text[0]); } }, .delete_surrounding_text = [](void* data, zwp_text_input_v3* zwp_text_input_v3, uint32_t before_length, - uint32_t after_length) -> void {}, + uint32_t after_length) -> void { + ELINUX_LOG(TRACE) + << "zwp_text_input_v3_listener.delete_surrounding_text"; + }, .done = [](void* data, zwp_text_input_v3* zwp_text_input_v3, - uint32_t serial) -> void {}, + uint32_t serial) -> void { + ELINUX_LOG(TRACE) << "zwp_text_input_v3_listener.done"; + }, }; const wl_data_device_listener ELinuxWindowWayland::kWlDataDeviceListener = { .data_offer = [](void* data, wl_data_device* wl_data_device, - wl_data_offer* offer) -> void {}, + wl_data_offer* offer) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.data_offer"; + }, .enter = [](void* data, wl_data_device* wl_data_device, uint32_t serial, wl_surface* surface, wl_fixed_t x, wl_fixed_t y, - wl_data_offer* offer) -> void {}, - .leave = [](void* data, wl_data_device* wl_data_device) -> void {}, + wl_data_offer* offer) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.enter"; + }, + .leave = [](void* data, wl_data_device* wl_data_device) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.leave"; + }, .motion = [](void* data, wl_data_device* wl_data_device, uint32_t time, wl_fixed_t x, - wl_fixed_t y) -> void {}, - .drop = [](void* data, wl_data_device* wl_data_device) -> void {}, + wl_fixed_t y) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.motion"; + }, + .drop = [](void* data, wl_data_device* wl_data_device) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.drop"; + }, .selection = [](void* data, wl_data_device* wl_data_device, wl_data_offer* offer) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.selection"; + auto self = reinterpret_cast(data); if (self->wl_data_offer_) { wl_data_offer_destroy(self->wl_data_offer_); @@ -764,11 +985,15 @@ const wl_data_device_listener ELinuxWindowWayland::kWlDataDeviceListener = { const wl_data_source_listener ELinuxWindowWayland::kWlDataSourceListener = { .target = [](void* data, wl_data_source* wl_data_source, - const char* mime_type) -> void {}, + const char* mime_type) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.target"; + }, .send = [](void* data, wl_data_source* wl_data_source, const char* mime_type, int32_t fd) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.send"; + if (strcmp(mime_type, kClipboardMimeTypeText)) { ELINUX_LOG(ERROR) << "Not expected mime_type: " << mime_type; return; @@ -780,6 +1005,8 @@ const wl_data_source_listener ELinuxWindowWayland::kWlDataSourceListener = { close(fd); }, .cancelled = [](void* data, wl_data_source* wl_data_source) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.cancelled"; + auto self = reinterpret_cast(data); self->clipboard_data_ = ""; if (self->wl_data_source_) { @@ -788,11 +1015,39 @@ const wl_data_source_listener ELinuxWindowWayland::kWlDataSourceListener = { } }, .dnd_drop_performed = [](void* data, - wl_data_source* wl_data_source) -> void {}, - .dnd_finished = [](void* data, wl_data_source* wl_data_source) -> void {}, + wl_data_source* wl_data_source) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.dnd_drop_performed"; + }, + .dnd_finished = [](void* data, wl_data_source* wl_data_source) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.dnd_finished"; + }, .action = [](void* data, wl_data_source* wl_data_source, - uint32_t dnd_action) -> void {}, + uint32_t dnd_action) -> void { + ELINUX_LOG(TRACE) << "wl_data_device_listener.action"; + }, +}; + +const zxdg_toplevel_decoration_v1_listener + ELinuxWindowWayland::kZxdgToplevelDecorationV1Listener = { + .configure = + [](void* data, + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, + uint32_t mode) -> void { + ELINUX_LOG(INFO) + << "zxdg_toplevel_decoration_v1_listener.configure: mode is " + << ((mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) + ? "server-side" + : "client-side"); + auto self = reinterpret_cast(data); + if (self->view_properties_.use_window_decoration && + mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE && + !self->window_decorations_) { + int32_t width_dip = self->view_properties_.width; + int32_t height_dip = self->view_properties_.height; + self->CreateDecoration(width_dip, height_dip); + } + }, }; ELinuxWindowWayland::ELinuxWindowWayland( @@ -806,16 +1061,12 @@ ELinuxWindowWayland::ELinuxWindowWayland( wl_compositor_(nullptr), wl_subcompositor_(nullptr), wl_current_surface_(nullptr), - wl_seat_(nullptr), - wl_pointer_(nullptr), - wl_touch_(nullptr), - wl_keyboard_(nullptr), wl_shm_(nullptr), + seat_inputs_map_(), wl_data_device_manager_(nullptr), wl_data_device_(nullptr), wl_data_offer_(nullptr), wl_data_source_(nullptr), - wl_cursor_theme_(nullptr), serial_(0), zwp_text_input_manager_v1_(nullptr), zwp_text_input_manager_v3_(nullptr), @@ -823,11 +1074,19 @@ ELinuxWindowWayland::ELinuxWindowWayland( zwp_text_input_v3_(nullptr), wp_presentation_(nullptr), wp_presentation_clk_id_(UINT32_MAX), - frame_rate_(60000), window_decorations_(nullptr) { view_properties_ = view_properties; + current_scale_ = + view_properties.force_scale_factor ? view_properties.scale_factor : 1.0; SetRotation(view_properties_.view_rotation); + auto xcursor_size_string = std::getenv(kXcursorSizeEnvironmentKey); + cursor_size_ = + xcursor_size_string ? atoi(xcursor_size_string) : kDefaultPointerSize; + if (cursor_size_ <= 0) { + cursor_size_ = kDefaultPointerSize; + } + wl_display_ = wl_display_connect(nullptr); if (!wl_display_) { ELINUX_LOG(ERROR) << "Failed to connect to the Wayland display."; @@ -841,37 +1100,39 @@ ELinuxWindowWayland::ELinuxWindowWayland( } wl_registry_add_listener(wl_registry_, &kWlRegistryListener, this); - wl_display_dispatch(wl_display_); wl_display_roundtrip(wl_display_); - if (wl_data_device_manager_ && wl_seat_) { - wl_data_device_ = wl_data_device_manager_get_data_device( - wl_data_device_manager_, wl_seat_); - wl_data_device_add_listener(wl_data_device_, &kWlDataDeviceListener, this); - } + for (auto& [seat, _] : seat_inputs_map_) { + if (wl_data_device_manager_ && seat) { + wl_data_device_ = + wl_data_device_manager_get_data_device(wl_data_device_manager_, seat); + wl_data_device_add_listener(wl_data_device_, &kWlDataDeviceListener, + this); + } - // Setup text-input protocol for onscreen keyboard inputs. - { - if (zwp_text_input_manager_v3_ && wl_seat_) { - zwp_text_input_v3_ = zwp_text_input_manager_v3_get_text_input( - zwp_text_input_manager_v3_, wl_seat_); - if (!zwp_text_input_v3_) { - ELINUX_LOG(ERROR) << "Failed to create the text input manager v3."; - return; - } - zwp_text_input_v3_add_listener(zwp_text_input_v3_, - &kZwpTextInputV3Listener, this); - } else if (zwp_text_input_manager_v1_) { - zwp_text_input_v1_ = zwp_text_input_manager_v1_create_text_input( - zwp_text_input_manager_v1_); - if (!zwp_text_input_v1_) { - ELINUX_LOG(ERROR) << "Failed to create text input manager v1."; - return; + // Setup text-input protocol for onscreen keyboard inputs. + { + if (zwp_text_input_manager_v3_ && seat) { + zwp_text_input_v3_ = zwp_text_input_manager_v3_get_text_input( + zwp_text_input_manager_v3_, seat); + if (!zwp_text_input_v3_) { + ELINUX_LOG(ERROR) << "Failed to create the text input manager v3."; + return; + } + zwp_text_input_v3_add_listener(zwp_text_input_v3_, + &kZwpTextInputV3Listener, this); + } else if (zwp_text_input_manager_v1_) { + zwp_text_input_v1_ = zwp_text_input_manager_v1_create_text_input( + zwp_text_input_manager_v1_); + if (!zwp_text_input_v1_) { + ELINUX_LOG(ERROR) << "Failed to create text input manager v1."; + return; + } + zwp_text_input_v1_add_listener(zwp_text_input_v1_, + &kZwpTextInputV1Listener, this); + } else { + // do nothing. } - zwp_text_input_v1_add_listener(zwp_text_input_v1_, - &kZwpTextInputV1Listener, this); - } else { - // do nothing. } } @@ -883,10 +1144,10 @@ ELinuxWindowWayland::~ELinuxWindowWayland() { display_valid_ = false; running_ = false; - if (wl_cursor_theme_) { - wl_cursor_theme_destroy(wl_cursor_theme_); - wl_cursor_theme_ = nullptr; + for (auto theme : wl_cursor_themes_) { + wl_cursor_theme_destroy(theme.second); } + wl_cursor_themes_.clear(); { if (zwp_text_input_v1_) { @@ -910,6 +1171,18 @@ ELinuxWindowWayland::~ELinuxWindowWayland() { } } + { + if (zxdg_decoration_manager_v1_) { + zxdg_decoration_manager_v1_destroy(zxdg_decoration_manager_v1_); + zxdg_decoration_manager_v1_ = nullptr; + } + + if (zxdg_toplevel_decoration_v1_) { + zxdg_toplevel_decoration_v1_destroy(zxdg_toplevel_decoration_v1_); + zxdg_toplevel_decoration_v1_ = nullptr; + } + } + if (wl_data_offer_) { wl_data_offer_destroy(wl_data_offer_); wl_data_offer_ = nullptr; @@ -935,26 +1208,29 @@ ELinuxWindowWayland::~ELinuxWindowWayland() { wl_data_device_manager_ = nullptr; } - if (wl_pointer_) { - wl_pointer_destroy(wl_pointer_); - wl_pointer_ = nullptr; - } + for (auto& [seat, inputs] : seat_inputs_map_) { + if (inputs.pointer) { + wl_pointer_destroy(inputs.pointer); + inputs.pointer = nullptr; + } - if (wl_touch_) { - wl_touch_destroy(wl_touch_); - wl_touch_ = nullptr; - } + if (inputs.touch) { + wl_touch_destroy(inputs.touch); + inputs.touch = nullptr; + } - if (wl_keyboard_) { - wl_keyboard_destroy(wl_keyboard_); - wl_keyboard_ = nullptr; - } + if (inputs.keyboard) { + wl_keyboard_destroy(inputs.keyboard); + inputs.keyboard = nullptr; + } - if (wl_seat_) { - wl_seat_destroy(wl_seat_); - wl_seat_ = nullptr; + if (seat) { + wl_seat_destroy(seat); + } } + seat_inputs_map_.clear(); + if (wl_output_) { wl_output_destroy(wl_output_); wl_output_ = nullptr; @@ -1031,6 +1307,22 @@ bool ELinuxWindowWayland::DispatchEvent() { return false; } + if (request_redraw_) { + request_redraw_ = false; + if (window_decorations_) { + window_decorations_->Resize(view_properties_.width, + view_properties_.height, current_scale_); + } + + if (binding_handler_delegate_) { + binding_handler_delegate_->OnWindowSizeChanged( + view_properties_.width * current_scale_, + view_properties_.height * current_scale_ - + WindowDecorationsPhysicalHeight()); + } + NotifyDisplayInfoUpdates(); + } + // Prepare to call wl_display_read_events. while (wl_display_prepare_read(wl_display_) != 0) { // If Wayland compositor terminates, -1 is returned. @@ -1069,7 +1361,11 @@ bool ELinuxWindowWayland::DispatchEvent() { return true; } -bool ELinuxWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { +bool ELinuxWindowWayland::CreateRenderSurface(int32_t width_px, + int32_t height_px, + bool enable_impeller) { + enable_impeller_ = enable_impeller; + if (!display_valid_) { ELINUX_LOG(ERROR) << "Wayland display is invalid."; return false; @@ -1086,12 +1382,12 @@ bool ELinuxWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { } if (view_properties_.view_mode == FlutterDesktopViewMode::kFullscreen) { - width = view_properties_.width; - height = view_properties_.height; + width_px = view_properties_.width * current_scale_; + height_px = view_properties_.height * current_scale_; } - ELINUX_LOG(TRACE) << "Created the Wayland surface: " << width << "x" - << height; + ELINUX_LOG(TRACE) << "Created the Wayland surface: " << width_px << "x" + << height_px; if (view_properties_.use_mouse_cursor) { wl_cursor_surface_ = wl_compositor_create_surface(wl_compositor_); if (!wl_cursor_surface_) { @@ -1102,10 +1398,12 @@ bool ELinuxWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { } if (current_rotation_ == 90 || current_rotation_ == 270) { - std::swap(width, height); + std::swap(width_px, height_px); } - native_window_ = - std::make_unique(wl_compositor_, width, height); + native_window_ = std::make_unique( + wl_compositor_, width_px, height_px, view_properties_.enable_vsync); + + wl_surface_add_listener(native_window_->Surface(), &kWlSurfaceListener, this); xdg_surface_ = xdg_wm_base_get_xdg_surface(xdg_wm_base_, native_window_->Surface()); @@ -1116,9 +1414,14 @@ bool ELinuxWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { xdg_surface_add_listener(xdg_surface_, &kXdgSurfaceListener, this); xdg_toplevel_ = xdg_surface_get_toplevel(xdg_surface_); - xdg_toplevel_set_title(xdg_toplevel_, "Flutter"); + if (view_properties_.title != nullptr) { + xdg_toplevel_set_title(xdg_toplevel_, view_properties_.title); + } + if (view_properties_.app_id != nullptr) { + xdg_toplevel_set_app_id(xdg_toplevel_, view_properties_.app_id); + } xdg_toplevel_add_listener(xdg_toplevel_, &kXdgToplevelListener, this); - wl_surface_commit(native_window_->Surface()); + wl_surface_set_buffer_scale(native_window_->Surface(), current_scale_); { auto* callback = wl_surface_frame(native_window_->Surface()); @@ -1132,19 +1435,57 @@ bool ELinuxWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { } } + if (view_properties_.view_mode == FlutterDesktopViewMode::kFullscreen) { + xdg_toplevel_set_fullscreen(xdg_toplevel_, NULL); + } + + wait_for_configure_ = true; + wl_surface_commit(native_window_->Surface()); + render_surface_ = std::make_unique(std::make_unique( - std::make_unique(wl_display_))); + std::make_unique(wl_display_), enable_impeller)); render_surface_->SetNativeWindow(native_window_.get()); if (view_properties_.use_window_decoration) { - window_decorations_ = std::make_unique( - wl_display_, wl_compositor_, wl_subcompositor_, - native_window_->Surface(), width, height); + if (zxdg_decoration_manager_v1_) { + ELINUX_LOG(INFO) << "Use server-side xdg-decoration mode"; + zxdg_toplevel_decoration_v1_ = + zxdg_decoration_manager_v1_get_toplevel_decoration( + zxdg_decoration_manager_v1_, xdg_toplevel_); + zxdg_toplevel_decoration_v1_set_mode( + zxdg_toplevel_decoration_v1_, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + zxdg_toplevel_decoration_v1_add_listener( + zxdg_toplevel_decoration_v1_, &kZxdgToplevelDecorationV1Listener, + this); + } else { + int32_t width_dip = width_px / current_scale_; + int32_t height_dip = height_px / current_scale_; + CreateDecoration(width_dip, height_dip); + } + } + + // Wait for making sure that xdg_surface has been configured. + while (wait_for_configure_) { + wl_display_dispatch(wl_display_); } return true; } +void ELinuxWindowWayland::CreateDecoration(int32_t width_dip, + int32_t height_dip) { + if (window_decorations_) { + ELINUX_LOG(WARNING) << "Window decoration has already created"; + return; + } + + window_decorations_ = std::make_unique( + wl_display_, wl_compositor_, wl_subcompositor_, native_window_->Surface(), + width_dip, height_dip, current_scale_, enable_impeller_, + view_properties_.enable_vsync); +} + void ELinuxWindowWayland::DestroyRenderSurface() { // destroy the main surface before destroying the client window on Wayland. if (window_decorations_) { @@ -1166,7 +1507,7 @@ void ELinuxWindowWayland::DestroyRenderSurface() { void ELinuxWindowWayland::UpdateVirtualKeyboardStatus(const bool show) { // Not supported virtual keyboard. - if (!(zwp_text_input_v1_ || zwp_text_input_v3_) || !wl_seat_) { + if (!(zwp_text_input_v1_ || zwp_text_input_v3_) || seat_inputs_map_.empty()) { return; } @@ -1195,18 +1536,19 @@ void ELinuxWindowWayland::UpdateFlutterCursor(const std::string& cursor_name) { return; } - auto wl_cursor = GetWlCursor(cursor_name); + auto wl_cursor = GetWlCursor(cursor_name, cursor_size_ * current_scale_); if (!wl_cursor) { return; } auto image = wl_cursor->images[0]; auto buffer = wl_cursor_image_get_buffer(image); if (buffer) { - wl_pointer_set_cursor(cursor_info_.pointer, cursor_info_.serial, - wl_cursor_surface_, image->hotspot_x, - image->hotspot_y); + wl_pointer_set_cursor( + cursor_info_.pointer, cursor_info_.serial, wl_cursor_surface_, + image->hotspot_x / current_scale_, image->hotspot_y / current_scale_); wl_surface_attach(wl_cursor_surface_, buffer, 0, 0); wl_surface_damage(wl_cursor_surface_, 0, 0, image->width, image->height); + wl_surface_set_buffer_scale(wl_cursor_surface_, current_scale_); wl_surface_commit(wl_cursor_surface_); } } @@ -1271,65 +1613,76 @@ void ELinuxWindowWayland::WlRegistryHandler(wl_registry* wl_registry, const char* interface, uint32_t version) { if (!strcmp(interface, wl_compositor_interface.name)) { + constexpr uint32_t kMaxVersion = 5; wl_compositor_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); + wl_registry_bind(wl_registry, name, &wl_compositor_interface, + std::min(kMaxVersion, version))); return; } if (!strcmp(interface, wl_subcompositor_interface.name)) { - wl_subcompositor_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_subcompositor_interface, 1)); + constexpr uint32_t kMaxVersion = 1; + wl_subcompositor_ = static_cast(wl_registry_bind( + wl_registry, name, &wl_subcompositor_interface, kMaxVersion)); } if (!strcmp(interface, xdg_wm_base_interface.name)) { + constexpr uint32_t kMaxVersion = 3; xdg_wm_base_ = static_cast( - wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1)); + wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, + std::min(kMaxVersion, version))); xdg_wm_base_add_listener(xdg_wm_base_, &kXdgWmBaseListener, this); return; } if (!strcmp(interface, wl_seat_interface.name)) { - wl_seat_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_seat_interface, 1)); - wl_seat_add_listener(wl_seat_, &kWlSeatListener, this); + constexpr uint32_t kMaxVersion = 4; + auto [inserted, _] = + seat_inputs_map_.emplace(static_cast(wl_registry_bind( + wl_registry, name, &wl_seat_interface, + std::min(kMaxVersion, version))), + seat_inputs()); + registry_names_to_seat_ptr_.emplace(name, inserted->first); + wl_seat_add_listener(inserted->first, &kWlSeatListener, this); return; } if (!strcmp(interface, wl_output_interface.name)) { + constexpr uint32_t kMaxVersion = 2; wl_output_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_output_interface, 1)); + wl_registry_bind(wl_registry, name, &wl_output_interface, + std::min(kMaxVersion, version))); wl_output_add_listener(wl_output_, &kWlOutputListener, this); return; } if (!strcmp(interface, wl_shm_interface.name)) { if (view_properties_.use_mouse_cursor) { + constexpr uint32_t kMaxVersion = 1; wl_shm_ = static_cast( - wl_registry_bind(wl_registry, name, &wl_shm_interface, 1)); - wl_cursor_theme_ = wl_cursor_theme_load(nullptr, 32, wl_shm_); - if (!wl_cursor_theme_) { - ELINUX_LOG(ERROR) << "Failed to load cursor theme."; - return; - } - CreateSupportedWlCursorList(); + wl_registry_bind(wl_registry, name, &wl_shm_interface, kMaxVersion)); } return; } if (!strcmp(interface, kZwpTextInputManagerV1)) { if (view_properties_.use_onscreen_keyboard) { + constexpr uint32_t kMaxVersion = 1; zwp_text_input_manager_v1_ = static_cast(wl_registry_bind( - wl_registry, name, &zwp_text_input_manager_v1_interface, 1)); + wl_registry, name, &zwp_text_input_manager_v1_interface, + kMaxVersion)); } return; } if (!strcmp(interface, kZwpTextInputManagerV3)) { if (view_properties_.use_onscreen_keyboard) { + constexpr uint32_t kMaxVersion = 1; zwp_text_input_manager_v3_ = static_cast(wl_registry_bind( - wl_registry, name, &zwp_text_input_manager_v3_interface, 1)); + wl_registry, name, &zwp_text_input_manager_v3_interface, + std::min(kMaxVersion, version))); } return; } @@ -1354,13 +1707,64 @@ void ELinuxWindowWayland::WlRegistryHandler(wl_registry* wl_registry, this); return; } + + if (!strcmp(interface, kZxdgDecorationManagerV1)) { + constexpr uint32_t kMaxVersion = 1; + zxdg_decoration_manager_v1_ = + static_cast(wl_registry_bind( + wl_registry, name, &zxdg_decoration_manager_v1_interface, + std::min(kMaxVersion, version))); + return; + } } void ELinuxWindowWayland::WlUnRegistryHandler(wl_registry* wl_registry, - uint32_t name) {} + uint32_t name) { + // Just remove seats for now, as we don't need to do anything else. + // But there is also no interface name here. + auto seat_iter = registry_names_to_seat_ptr_.find(name); + if (seat_iter != registry_names_to_seat_ptr_.end()) { + auto seat = seat_iter->second; + auto seat_inputs_iter = seat_inputs_map_.find(seat); + if (seat_inputs_iter != seat_inputs_map_.end()) { + auto& inputs = seat_inputs_iter->second; + if (inputs.pointer) { + wl_pointer_release(inputs.pointer); + inputs.pointer = nullptr; + } + if (inputs.touch) { + wl_touch_release(inputs.touch); + inputs.touch = nullptr; + } + if (inputs.keyboard) { + wl_keyboard_release(inputs.keyboard); + inputs.keyboard = nullptr; + } + seat_inputs_map_.erase(seat_inputs_iter); + } + if (seat) { + wl_seat_destroy(seat); + } + registry_names_to_seat_ptr_.erase(name); + } +} + +bool ELinuxWindowWayland::LoadCursorTheme(uint32_t size) { + if (!wl_shm_) { + ELINUX_LOG(ERROR) << "Failed to load cursor theme because shared memory " + "buffers are not available."; + return false; + } -void ELinuxWindowWayland::CreateSupportedWlCursorList() { - std::vector wl_cursor_themes{ + auto theme = wl_cursor_theme_load(nullptr, size, wl_shm_); + if (!theme) { + ELINUX_LOG(ERROR) << "Failed to load cursor theme for size: " << size; + return false; + } + + wl_cursor_themes_[size] = theme; + + std::vector wl_cursor_names{ kWlCursorThemeLeftPtr, kWlCursorThemeBottomLeftCorner, kWlCursorThemeBottomRightCorner, @@ -1376,18 +1780,24 @@ void ELinuxWindowWayland::CreateSupportedWlCursorList() { kWlCursorThemeWatch, }; - for (const auto& theme : wl_cursor_themes) { - auto wl_cursor = - wl_cursor_theme_get_cursor(wl_cursor_theme_, theme.c_str()); + std::unordered_map cursor_list; + + for (const auto& cursor_name : wl_cursor_names) { + auto wl_cursor = wl_cursor_theme_get_cursor(theme, cursor_name.c_str()); if (!wl_cursor) { - ELINUX_LOG(ERROR) << "Unsupported cursor theme: " << theme.c_str(); + ELINUX_LOG(ERROR) << "Unsupported cursor theme: " << cursor_name.c_str(); continue; } - supported_wl_cursor_list_[theme] = wl_cursor; + cursor_list[cursor_name] = wl_cursor; } + + supported_wl_cursor_list_.insert(std::make_pair(size, cursor_list)); + + return true; } -wl_cursor* ELinuxWindowWayland::GetWlCursor(const std::string& cursor_name) { +wl_cursor* ELinuxWindowWayland::GetWlCursor(const std::string& cursor_name, + uint32_t size) { // Convert the cursor theme name from Flutter's cursor value to Wayland's one. // However, Wayland has not all cursor themes corresponding to Flutter. // If there is no Wayland's cursor theme corresponding to the Flutter's cursor @@ -1431,17 +1841,24 @@ wl_cursor* ELinuxWindowWayland::GetWlCursor(const std::string& cursor_name) { {"zoomOut", ""}, }; + if (supported_wl_cursor_list_.find(size) == supported_wl_cursor_list_.end()) { + if (!LoadCursorTheme(size)) { + return nullptr; + } + } + + auto cursor_list = supported_wl_cursor_list_.at(size); + if (flutter_to_wayland_cursor_map.find(cursor_name) != flutter_to_wayland_cursor_map.end()) { auto theme = flutter_to_wayland_cursor_map.at(cursor_name); - if (!theme.empty() && supported_wl_cursor_list_.find(theme) != - supported_wl_cursor_list_.end()) { - return supported_wl_cursor_list_[theme]; + if (!theme.empty() && cursor_list.find(theme) != cursor_list.end()) { + return cursor_list[theme]; } } ELINUX_LOG(ERROR) << "Unsupported cursor: " << cursor_name.c_str(); - return supported_wl_cursor_list_[kWlCursorThemeLeftPtr]; + return cursor_list[kWlCursorThemeLeftPtr]; } void ELinuxWindowWayland::ShowVirtualKeyboard() { @@ -1459,7 +1876,8 @@ void ELinuxWindowWayland::ShowVirtualKeyboard() { } else { if (native_window_) { zwp_text_input_v1_show_input_panel(zwp_text_input_v1_); - zwp_text_input_v1_activate(zwp_text_input_v1_, wl_seat_, + zwp_text_input_v1_activate(zwp_text_input_v1_, + seat_inputs_map_.begin()->first, native_window_->Surface()); } } @@ -1470,8 +1888,44 @@ void ELinuxWindowWayland::DismissVirtualKeybaord() { zwp_text_input_v3_disable(zwp_text_input_v3_); zwp_text_input_v3_commit(zwp_text_input_v3_); } else { - zwp_text_input_v1_deactivate(zwp_text_input_v1_, wl_seat_); + zwp_text_input_v1_deactivate(zwp_text_input_v1_, + seat_inputs_map_.begin()->first); } } +void ELinuxWindowWayland::UpdateWindowScale() { + if (view_properties_.force_scale_factor) { + return; + } + + double scale_factor = 1.0; + for (auto output_id : entered_outputs_) { + if (wl_output_scale_factors_.find(output_id) == + wl_output_scale_factors_.end()) + continue; + + auto output_scale_factor = wl_output_scale_factors_.at(output_id); + if (output_scale_factor > scale_factor) + scale_factor = output_scale_factor; + } + + if (current_scale_ == scale_factor) { + return; + } + + ELINUX_LOG(TRACE) << "Window scale has changed: " << scale_factor; + current_scale_ = scale_factor; + + wl_surface_set_buffer_scale(native_window_->Surface(), current_scale_); + request_redraw_ = true; +} + +uint32_t ELinuxWindowWayland::WindowDecorationsPhysicalHeight() const { + if (!window_decorations_) { + return 0; + } + + return window_decorations_->Height() * current_scale_; +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.h b/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.h index d62ebd5a..8ccb84ba 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.h +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window_wayland.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,7 +10,7 @@ #include #include -#include +#include #include "flutter/shell/platform/linux_embedded/surface/surface_gl.h" #include "flutter/shell/platform/linux_embedded/window/elinux_window.h" @@ -24,11 +24,23 @@ extern "C" { #include "wayland/protocols/presentation-time-protocol.h" #include "wayland/protocols/text-input-unstable-v1-client-protocol.h" #include "wayland/protocols/text-input-unstable-v3-client-protocol.h" +#include "wayland/protocols/xdg-decoration-unstable-v1-protocol.h" #include "wayland/protocols/xdg-shell-client-protocol.h" } namespace flutter { +namespace { +constexpr char kXcursorSizeEnvironmentKey[] = "XCURSOR_SIZE"; +} // namespace + +// Track input devices for each seat +struct seat_inputs { + wl_pointer* pointer = nullptr; + wl_touch* touch = nullptr; + wl_keyboard* keyboard = nullptr; +}; + class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { public: ELinuxWindowWayland(FlutterDesktopViewProperties view_properties); @@ -41,7 +53,9 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { bool DispatchEvent() override; // |FlutterWindowBindingHandler| - bool CreateRenderSurface(int32_t width, int32_t height) override; + bool CreateRenderSurface(int32_t width_px, + int32_t height_px, + bool enable_impeller) override; // |FlutterWindowBindingHandler| void DestroyRenderSurface() override; @@ -90,18 +104,27 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { void WlUnRegistryHandler(wl_registry* wl_registry, uint32_t name); - void CreateSupportedWlCursorList(); + bool LoadCursorTheme(uint32_t size); - wl_cursor* GetWlCursor(const std::string& cursor_name); + wl_cursor* GetWlCursor(const std::string& cursor_name, uint32_t size); void ShowVirtualKeyboard(); void DismissVirtualKeybaord(); + // Updates the surface scale of the window from the list of entered outputs. + void UpdateWindowScale(); + + void CreateDecoration(int32_t width_dip, int32_t height_dip); + + // Get window decorations height in physical pixels. + uint32_t WindowDecorationsPhysicalHeight() const; + static const wl_registry_listener kWlRegistryListener; static const xdg_wm_base_listener kXdgWmBaseListener; static const xdg_surface_listener kXdgSurfaceListener; static const xdg_toplevel_listener kXdgToplevelListener; + static const wl_surface_listener kWlSurfaceListener; static const wl_seat_listener kWlSeatListener; static const wl_pointer_listener kWlPointerListener; static const wl_touch_listener kWlTouchListener; @@ -115,10 +138,9 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { static const wp_presentation_listener kWpPresentationListener; static const wp_presentation_feedback_listener kWpPresentationFeedbackListener; - - // A pointer to a FlutterWindowsView that can be used to update engine - // windowing and input state. - WindowBindingHandlerDelegate* binding_handler_delegate_ = nullptr; + static const zxdg_toplevel_decoration_v1_listener + kZxdgToplevelDecorationV1Listener; + static constexpr size_t kDefaultPointerSize = 24; std::unique_ptr native_window_; std::unique_ptr render_surface_; @@ -133,8 +155,12 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { bool display_valid_; bool running_; + bool wait_for_configure_ = false; + bool request_redraw_ = false; bool maximised_; uint32_t last_frame_time_; + bool enable_impeller_ = false; + int32_t transform_ = WL_OUTPUT_TRANSFORM_NORMAL; // Indicates that exists a keyboard show request from Flutter Engine. bool is_requested_show_virtual_keyboard_; @@ -142,14 +168,11 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { wl_display* wl_display_; wl_registry* wl_registry_; wl_compositor* wl_compositor_; - wl_seat* wl_seat_; wl_output* wl_output_; wl_shm* wl_shm_; - wl_pointer* wl_pointer_; - wl_touch* wl_touch_; - wl_keyboard* wl_keyboard_; + std::unordered_map seat_inputs_map_; + std::unordered_map registry_names_to_seat_ptr_; wl_surface* wl_cursor_surface_; - wl_cursor_theme* wl_cursor_theme_; xdg_wm_base* xdg_wm_base_; xdg_surface* xdg_surface_; xdg_toplevel* xdg_toplevel_; @@ -160,16 +183,31 @@ class ELinuxWindowWayland : public ELinuxWindow, public WindowBindingHandler { zwp_text_input_v1* zwp_text_input_v1_; zwp_text_input_v3* zwp_text_input_v3_; + // xdg-decoration protocol for window decoration (server-side). + zxdg_decoration_manager_v1* zxdg_decoration_manager_v1_ = nullptr; + zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1_ = nullptr; + // Frame information for Vsync events. wp_presentation* wp_presentation_; uint32_t wp_presentation_clk_id_; uint64_t last_frame_time_nanos_; - int32_t frame_rate_; CursorInfo cursor_info_; + size_t cursor_size_; + + // List of cursor name and wl_cursor supported by Wayland keyed by their + // (scaled) size. + std::unordered_map> + supported_wl_cursor_list_; + + // List of loaded Wayland cursor themes keyed by their (scaled) size. + std::unordered_map wl_cursor_themes_; + + // List of outputs that the window is currently rendered on. + std::unordered_set entered_outputs_; - // List of cursor name and wl_cursor supported by Wayland. - std::unordered_map supported_wl_cursor_list_; + // List of output scale factors keyed by their ids. + std::unordered_map wl_output_scale_factors_; wl_data_device_manager* wl_data_device_manager_; wl_data_device* wl_data_device_; diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.cc b/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.cc index 792fa76a..862c4d64 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.cc +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -23,6 +23,8 @@ constexpr int kButton9 = 9; ELinuxWindowX11::ELinuxWindowX11(FlutterDesktopViewProperties view_properties) { view_properties_ = view_properties; + current_scale_ = + view_properties.force_scale_factor ? view_properties.scale_factor : 1.0; SetRotation(view_properties_.view_rotation); display_ = XOpenDisplay(NULL); @@ -119,15 +121,19 @@ bool ELinuxWindowX11::DispatchEvent() { return true; } -bool ELinuxWindowX11::CreateRenderSurface(int32_t width, int32_t height) { - auto context_egl = - std::make_unique(std::make_unique(display_)); +bool ELinuxWindowX11::CreateRenderSurface(int32_t width, + int32_t height, + bool enable_impeller) { + auto context_egl = std::make_unique( + std::make_unique(display_), enable_impeller); if (current_rotation_ == 90 || current_rotation_ == 270) { std::swap(width, height); } native_window_ = std::make_unique( - display_, context_egl->GetAttrib(EGL_NATIVE_VISUAL_ID), width, height); + display_, context_egl->GetAttrib(EGL_NATIVE_VISUAL_ID), + view_properties_.title, width, height, view_properties_.enable_vsync, + view_properties_.view_mode == FlutterDesktopViewMode::kFullscreen); if (!native_window_->IsValid()) { ELINUX_LOG(ERROR) << "Failed to create the native window"; return false; diff --git a/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.h b/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.h index 80683534..eb11171e 100644 --- a/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.h +++ b/src/flutter/shell/platform/linux_embedded/window/elinux_window_x11.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -26,7 +26,9 @@ class ELinuxWindowX11 : public ELinuxWindow, public WindowBindingHandler { bool DispatchEvent() override; // |FlutterWindowBindingHandler| - bool CreateRenderSurface(int32_t width, int32_t height) override; + bool CreateRenderSurface(int32_t width, + int32_t height, + bool enable_impeller) override; // |FlutterWindowBindingHandler| void DestroyRenderSurface() override; @@ -68,10 +70,6 @@ class ELinuxWindowX11 : public ELinuxWindow, public WindowBindingHandler { int16_t x, int16_t y); - // A pointer to a FlutterWindowsView that can be used to update engine - // windowing and input state. - WindowBindingHandlerDelegate* binding_handler_delegate_ = nullptr; - Display* display_ = nullptr; std::unique_ptr native_window_; std::unique_ptr render_surface_; diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window.h b/src/flutter/shell/platform/linux_embedded/window/native_window.h index bbe80954..57c40362 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window.h @@ -21,6 +21,7 @@ class NativeWindow { // Gets a window (GBM surface) for offscreen resource. EGLNativeWindowType WindowOffscreen() const { return window_offscreen_; } + // Get physical width of the window. int32_t Width() const { if (!valid_) { return -1; @@ -28,6 +29,7 @@ class NativeWindow { return width_; } + // Get physical height of the window. int32_t Height() const { if (!valid_) { return -1; @@ -35,27 +37,38 @@ class NativeWindow { return height_; } + bool EnableVsync() const { return enable_vsync_; } + virtual bool IsNeedRecreateSurfaceAfterResize() const { return false; } // Sets a window position. Basically, this API is used for window decorations // such as titlebar. - virtual void SetPosition(const int32_t x, const int32_t y) { - x_ = x; - y_ = y; + // @param[in] x_dip The x coordinate in logical pixels. + // @param[in] y_dip The y coordinate in logical pixels. + virtual void SetPosition(const int32_t x_dip, const int32_t y_dip) { + x_ = x_dip; + y_ = y_dip; }; - virtual bool Resize(const size_t width, const size_t height) = 0; + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. + virtual bool Resize(const size_t width_px, const size_t height_px) = 0; // Swaps frame buffers. This API performs processing only for the DRM-GBM // backend. It is prepared to make the interface common. - virtual void SwapBuffers(){/* do nothing. */}; + virtual void SwapBuffers() { /* do nothing. */ }; protected: EGLNativeWindowType window_; EGLNativeWindowType window_offscreen_; + bool enable_vsync_; + // Physical width of the window. int32_t width_; + // Physical height of the window. int32_t height_; + // The x coordinate of the window in logical pixels. int32_t x_; + // The y coordinate of the window in logical pixels. int32_t y_; bool valid_ = false; }; diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_drm.cc index cf9c48ff..73023fd2 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm.cc @@ -14,18 +14,50 @@ #include "flutter/shell/platform/linux_embedded/surface/cursor_data.h" namespace flutter { +namespace { +constexpr char kFlutterDrmConnectorEnvironmentKey[] = "FLUTTER_DRM_CONNECTOR"; +static const std::unordered_map connector_names = { + {DRM_MODE_CONNECTOR_Unknown, "Unknown"}, + {DRM_MODE_CONNECTOR_VGA, "VGA"}, + {DRM_MODE_CONNECTOR_DVII, "DVI-I"}, + {DRM_MODE_CONNECTOR_DVID, "DVI-D"}, + {DRM_MODE_CONNECTOR_DVIA, "DVI-A"}, + {DRM_MODE_CONNECTOR_Composite, "Composite"}, + {DRM_MODE_CONNECTOR_SVIDEO, "SVIDEO"}, + {DRM_MODE_CONNECTOR_LVDS, "LVDS"}, + {DRM_MODE_CONNECTOR_Component, "Component"}, + {DRM_MODE_CONNECTOR_9PinDIN, "DIN"}, + {DRM_MODE_CONNECTOR_DisplayPort, "DP"}, + {DRM_MODE_CONNECTOR_HDMIA, "HDMI-A"}, + {DRM_MODE_CONNECTOR_HDMIB, "HDMI-B"}, + {DRM_MODE_CONNECTOR_TV, "TV"}, + {DRM_MODE_CONNECTOR_eDP, "eDP"}, + {DRM_MODE_CONNECTOR_VIRTUAL, "Virtual"}, + {DRM_MODE_CONNECTOR_DSI, "DSI"}, + {DRM_MODE_CONNECTOR_DPI, "DPI"}, + {DRM_MODE_CONNECTOR_WRITEBACK, "Writeback"}, + {DRM_MODE_CONNECTOR_SPI, "SPI"}, + {DRM_MODE_CONNECTOR_USB, "USB"}}; +} // namespace -NativeWindowDrm::NativeWindowDrm(const char* device_filename) { - drm_device_ = open(device_filename, O_RDWR | O_CLOEXEC); +NativeWindowDrm::NativeWindowDrm(const char* device_filename, + const uint16_t rotation, + bool enable_vsync) { + if (!strcmp("drm-nvdc", device_filename)) { + drm_device_ = drmOpen(device_filename, nullptr); + } else { + drm_device_ = open(device_filename, O_RDWR | O_CLOEXEC); + } if (drm_device_ == -1) { ELINUX_LOG(ERROR) << "Couldn't open " << device_filename; return; } - if (!ConfigureDisplay()) { + if (!ConfigureDisplay(rotation)) { return; } + enable_vsync_ = enable_vsync; valid_ = true; } @@ -46,7 +78,7 @@ bool NativeWindowDrm::MoveCursor(double x, double y) { return true; } -bool NativeWindowDrm::ConfigureDisplay() { +bool NativeWindowDrm::ConfigureDisplay(const uint16_t rotation) { auto resources = drmModeGetResources(drm_device_); if (!resources) { ELINUX_LOG(ERROR) << "Couldn't get resources"; @@ -64,6 +96,9 @@ bool NativeWindowDrm::ConfigureDisplay() { drm_mode_info_ = connector->modes[0]; width_ = drm_mode_info_.hdisplay; height_ = drm_mode_info_.vdisplay; + if (rotation == 90 || rotation == 270) { + std::swap(width_, height_); + } ELINUX_LOG(INFO) << "resolution: " << width_ << "x" << height_; auto* encoder = FindEncoder(resources, connector); @@ -73,8 +108,18 @@ bool NativeWindowDrm::ConfigureDisplay() { drmModeFreeResources(resources); return false; } - if (encoder->crtc_id) { - drm_crtc_ = drmModeGetCrtc(drm_device_, encoder->crtc_id); + if (!encoder->crtc_id) { + // if there is no current CRTC, make sure to attach a suitable one + for (int c = 0; c < resources->count_crtcs; c++) { + if (encoder->possible_crtcs & (1 << c)) { + encoder->crtc_id = resources->crtcs[c]; + break; + } + } + } + drm_crtc_ = drmModeGetCrtc(drm_device_, encoder->crtc_id); + if (!drm_crtc_) { + ELINUX_LOG(WARNING) << "Couldn't find a suitable crtc"; } drmModeFreeEncoder(encoder); @@ -84,11 +129,62 @@ bool NativeWindowDrm::ConfigureDisplay() { return true; } +std::string NativeWindowDrm::GetConnectorName(uint32_t connector_type, + uint32_t connector_type_id) { + auto it = connector_names.find(connector_type); + std::string name = it != connector_names.end() ? it->second : "Unknown"; + return name + "-" + std::to_string(connector_type_id); +} + +drmModeConnectorPtr NativeWindowDrm::GetConnectorByName( + drmModeResPtr resources, + const char* connector_name) { + for (int i = 0; i < resources->count_connectors; i++) { + auto connector = drmModeGetConnector(drm_device_, resources->connectors[i]); + if (!connector) { + continue; + } + auto other_connector_name = GetConnectorName(connector->connector_type, + connector->connector_type_id); + if (connector_name == other_connector_name) { + return connector; + } + drmModeFreeConnector(connector); + } + return nullptr; +} + drmModeConnectorPtr NativeWindowDrm::FindConnector(drmModeResPtr resources) { + auto connector_name = std::getenv(kFlutterDrmConnectorEnvironmentKey); + if (connector_name && connector_name[0] != '\0') { + auto connector = GetConnectorByName(resources, connector_name); + if (!connector) { + ELINUX_LOG(ERROR) << "Couldn't find connector with name " + << connector_name; + return nullptr; + } + + if (connector->connection == DRM_MODE_CONNECTED) { + ELINUX_LOG(DEBUG) << "Using connector " << connector_name; + return connector; + } else { + ELINUX_LOG(ERROR) << "Connector " << connector_name + << " is not connected."; + drmModeFreeConnector(connector); + return nullptr; + } + } + for (int i = 0; i < resources->count_connectors; i++) { auto connector = drmModeGetConnector(drm_device_, resources->connectors[i]); + if (!connector) { + continue; + } // pick the first connected connector if (connector->connection == DRM_MODE_CONNECTED) { + auto other_connetor_name = GetConnectorName(connector->connector_type, + connector->connector_type_id); + ELINUX_LOG(DEBUG) << "Using connector " << other_connetor_name; return connector; } drmModeFreeConnector(connector); @@ -99,11 +195,44 @@ drmModeConnectorPtr NativeWindowDrm::FindConnector(drmModeResPtr resources) { drmModeEncoder* NativeWindowDrm::FindEncoder(drmModeRes* resources, drmModeConnector* connector) { - if (connector->encoder_id) { - return drmModeGetEncoder(drm_device_, connector->encoder_id); + drmModeEncoder* encoder = nullptr; + // Find a suitable encoder + for (int e = 0; e < resources->count_encoders; e++) { + bool found = false; + encoder = drmModeGetEncoder(drm_device_, resources->encoders[e]); + for (int ce = 0; ce < connector->count_encoders; ce++) { + if (encoder && encoder->encoder_id == connector->encoders[ce]) { + ELINUX_LOG(DEBUG) << "Using encoder id " << encoder->encoder_id; + found = true; + break; + } + } + if (found) + break; + drmModeFreeEncoder(encoder); + encoder = nullptr; } - // no encoder found - return nullptr; + + // If encoder is not connected to the connector, + // try to find a suitable one + if (!encoder) { + for (int e = 0; e < connector->count_encoders; e++) { + encoder = drmModeGetEncoder(drm_device_, connector->encoders[e]); + for (int c = 0; c < resources->count_crtcs; c++) { + if (encoder->possible_crtcs & (1 << c)) { + encoder->crtc_id = resources->crtcs[c]; + break; + } + } + if (encoder->crtc_id) + break; + drmModeFreeEncoder(encoder); + encoder = nullptr; + } + } + + // Will return nullptr if a suitable encoder is still not found + return encoder; } const uint32_t* NativeWindowDrm::GetCursorData(const std::string& cursor_name) { diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm.h b/src/flutter/shell/platform/linux_embedded/window/native_window_drm.h index 4ec29050..3d72f7ac 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,6 +7,8 @@ #include +#include +#include #include #include "flutter/shell/platform/linux_embedded/surface/surface_gl.h" @@ -16,10 +18,12 @@ namespace flutter { class NativeWindowDrm : public NativeWindow { public: - NativeWindowDrm(const char* device_filename); + NativeWindowDrm(const char* device_filename, + const uint16_t rotation, + bool enable_vsync); virtual ~NativeWindowDrm(); - bool ConfigureDisplay(); + bool ConfigureDisplay(const uint16_t rotation); bool MoveCursor(double x, double y); @@ -31,9 +35,14 @@ class NativeWindowDrm : public NativeWindow { virtual bool DismissCursor() = 0; - virtual std::unique_ptr CreateRenderSurface() = 0; + virtual std::unique_ptr CreateRenderSurface( + bool enable_impeller) = 0; protected: + std::string GetConnectorName(uint32_t connector_type, + uint32_t connector_type_id); + drmModeConnectorPtr GetConnectorByName(drmModeResPtr resources, + const char* connector_name); drmModeConnectorPtr FindConnector(drmModeResPtr resources); drmModeEncoder* FindEncoder(drmModeRes* resources, diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.cc index b762321a..48f8e969 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -18,12 +18,15 @@ namespace { constexpr char kCursorNameNone[] = "none"; } // namespace -NativeWindowDrmEglstream::NativeWindowDrmEglstream(const char* device_filename) - : NativeWindowDrm(device_filename) { +NativeWindowDrmEglstream::NativeWindowDrmEglstream(const char* device_filename, + const uint16_t rotation, + bool enable_vsync) + : NativeWindowDrm(device_filename, rotation, enable_vsync) { if (!valid_) { return; } + enable_vsync_ = enable_vsync; valid_ = ConfigureDisplayAdditional(); // drmIsMaster() is a relatively new API, and the main target of EGLStream is @@ -90,7 +93,8 @@ bool NativeWindowDrmEglstream::DismissCursor() { return true; } -std::unique_ptr NativeWindowDrmEglstream::CreateRenderSurface() { +std::unique_ptr NativeWindowDrmEglstream::CreateRenderSurface( + bool enable_impeller) { return std::make_unique(std::make_unique( std::make_unique())); } diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.h b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.h index 994dfe38..4bbe59bc 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_eglstream.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -16,7 +16,9 @@ namespace flutter { class NativeWindowDrmEglstream : public NativeWindowDrm { public: - NativeWindowDrmEglstream(const char* device_filename); + NativeWindowDrmEglstream(const char* device_filename, + const uint16_t rotation, + bool enable_vsync); ~NativeWindowDrmEglstream(); // |NativeWindowDrm| @@ -31,7 +33,7 @@ class NativeWindowDrmEglstream : public NativeWindowDrm { bool DismissCursor() override; // |NativeWindowDrm| - std::unique_ptr CreateRenderSurface() override; + std::unique_ptr CreateRenderSurface(bool enable_impeller) override; // |NativeWindow| bool Resize(const size_t width, const size_t height) override; diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.cc index a24443a7..4565fcb8 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -19,11 +19,14 @@ constexpr uint32_t kCursorBufferWidth = 64; constexpr uint32_t kCursorBufferHeight = 64; } // namespace -NativeWindowDrmGbm::NativeWindowDrmGbm(const char* device_filename) - : NativeWindowDrm(device_filename) { +NativeWindowDrmGbm::NativeWindowDrmGbm(const char* device_filename, + const uint16_t rotation, + bool enable_vsync) + : NativeWindowDrm(device_filename, rotation, enable_vsync) { if (!valid_) { return; } + enable_vsync_ = enable_vsync; if (!drmIsMaster(drm_device_)) { ELINUX_LOG(ERROR) @@ -128,9 +131,10 @@ bool NativeWindowDrmGbm::DismissCursor() { return true; } -std::unique_ptr NativeWindowDrmGbm::CreateRenderSurface() { +std::unique_ptr NativeWindowDrmGbm::CreateRenderSurface( + bool enable_impeller) { return std::make_unique(std::make_unique( - std::make_unique(gbm_device_))); + std::make_unique(gbm_device_), enable_impeller)); } bool NativeWindowDrmGbm::IsNeedRecreateSurfaceAfterResize() const { @@ -174,10 +178,14 @@ void NativeWindowDrmGbm::SwapBuffers() { if (result != 0) { ELINUX_LOG(ERROR) << "Failed to add a framebuffer. (" << result << ")"; } - result = drmModeSetCrtc(drm_device_, drm_crtc_->crtc_id, fb, 0, 0, - &drm_connector_id_, 1, &drm_mode_info_); - if (result != 0) { - ELINUX_LOG(ERROR) << "Failed to set crct mode. (" << result << ")"; + if (!drm_crtc_) { + ELINUX_LOG(ERROR) << "crtc is null, cannot set mode."; + } else { + result = drmModeSetCrtc(drm_device_, drm_crtc_->crtc_id, fb, 0, 0, + &drm_connector_id_, 1, &drm_mode_info_); + if (result != 0) { + ELINUX_LOG(ERROR) << "Failed to set crct mode. (" << result << ")"; + } } if (gbm_previous_bo_) { diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.h b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.h index 0a062273..d0a2ca5f 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_drm_gbm.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2023 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -17,7 +17,9 @@ namespace flutter { class NativeWindowDrmGbm : public NativeWindowDrm { public: - NativeWindowDrmGbm(const char* device_filename); + NativeWindowDrmGbm(const char* device_filename, + const uint16_t rotation, + bool enable_vsync); ~NativeWindowDrmGbm(); // |NativeWindowDrm| @@ -32,7 +34,7 @@ class NativeWindowDrmGbm : public NativeWindowDrm { bool DismissCursor() override; // |NativeWindowDrm| - std::unique_ptr CreateRenderSurface() override; + std::unique_ptr CreateRenderSurface(bool enable_impeller) override; // |NativeWindow| bool IsNeedRecreateSurfaceAfterResize() const override; diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc index 6f5aa398..46087281 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc @@ -9,15 +9,16 @@ namespace flutter { NativeWindowWayland::NativeWindowWayland(wl_compositor* compositor, - const size_t width, - const size_t height) { + const size_t width_px, + const size_t height_px, + bool enable_vsync) { surface_ = wl_compositor_create_surface(compositor); if (!surface_) { ELINUX_LOG(ERROR) << "Failed to create the compositor surface."; return; } - window_ = wl_egl_window_create(surface_, width, height); + window_ = wl_egl_window_create(surface_, width_px, height_px); if (!window_) { ELINUX_LOG(ERROR) << "Failed to create the EGL window."; return; @@ -40,8 +41,9 @@ NativeWindowWayland::NativeWindowWayland(wl_compositor* compositor, } } - width_ = width; - height_ = height; + enable_vsync_ = enable_vsync; + width_ = width_px; + height_ = height_px; valid_ = true; } @@ -67,15 +69,16 @@ NativeWindowWayland::~NativeWindowWayland() { } } -bool NativeWindowWayland::Resize(const size_t width, const size_t height) { +bool NativeWindowWayland::Resize(const size_t width_px, + const size_t height_px) { if (!valid_) { ELINUX_LOG(ERROR) << "Failed to resize the window."; return false; } - wl_egl_window_resize(window_, width, height, 0, 0); + wl_egl_window_resize(window_, width_px, height_px, 0, 0); - width_ = width; - height_ = height; + width_ = width_px; + height_ = height_px; return true; } diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.h b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.h index 5688058f..c7b8c275 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland.h @@ -13,13 +13,16 @@ namespace flutter { class NativeWindowWayland : public NativeWindow { public: + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. NativeWindowWayland(wl_compositor* compositor, - const size_t width, - const size_t height); + const size_t width_px, + const size_t height_px, + bool enable_vsync); ~NativeWindowWayland(); // |NativeWindow| - bool Resize(const size_t width, const size_t height) override; + bool Resize(const size_t width_px, const size_t height_px) override; wl_surface* Surface() const { return surface_; } diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.cc index 20486eee..5f813962 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.cc @@ -12,8 +12,9 @@ NativeWindowWaylandDecoration::NativeWindowWaylandDecoration( wl_compositor* compositor, wl_subcompositor* subcompositor, wl_surface* parent_surface, - const size_t width, - const size_t height) { + const size_t width_px, + const size_t height_px, + bool enable_vsync) { surface_ = wl_compositor_create_surface(compositor); if (!surface_) { ELINUX_LOG(ERROR) << "Failed to create the compositor surface."; @@ -35,8 +36,9 @@ NativeWindowWaylandDecoration::NativeWindowWaylandDecoration( return; } - width_ = width; - height_ = height; + enable_vsync_ = enable_vsync; + width_ = width_px; + height_ = height_px; valid_ = true; } @@ -52,29 +54,38 @@ NativeWindowWaylandDecoration::~NativeWindowWaylandDecoration() { } } -bool NativeWindowWaylandDecoration::Resize(const size_t width, - const size_t height) { +bool NativeWindowWaylandDecoration::Resize(const size_t width_px, + const size_t height_px) { if (!valid_) { ELINUX_LOG(ERROR) << "Failed to resize the window."; return false; } - width_ = width; - height_ = height; - wl_egl_window_resize(window_, width, height, 0, 0); + width_ = width_px; + height_ = height_px; + wl_egl_window_resize(window_, width_px, height_px, 0, 0); return true; } -void NativeWindowWaylandDecoration::SetPosition(const int32_t x, - const int32_t y) { +void NativeWindowWaylandDecoration::SetPosition(const int32_t x_dip, + const int32_t y_dip) { if (!valid_) { ELINUX_LOG(ERROR) << "Failed to set the position of the window."; return; } - x_ = x; - y_ = y; - wl_subsurface_set_position(subsurface_, x, y); + x_ = x_dip; + y_ = y_dip; + wl_subsurface_set_position(subsurface_, x_dip, y_dip); +} + +void NativeWindowWaylandDecoration::SetScaleFactor(float scale_factor) { + if (!valid_) { + ELINUX_LOG(ERROR) << "Failed to set the scale factor of the window."; + return; + } + + wl_surface_set_buffer_scale(surface_, scale_factor); } } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.h b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.h index d270c338..dc84f3f4 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_wayland_decoration.h @@ -14,18 +14,25 @@ namespace flutter { class NativeWindowWaylandDecoration : public NativeWindow { public: + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. NativeWindowWaylandDecoration(wl_compositor* compositor, wl_subcompositor* subcompositor, wl_surface* parent_surface, - const size_t width, - const size_t height); + const size_t width_px, + const size_t height_px, + bool enable_vsync); ~NativeWindowWaylandDecoration(); // |NativeWindow| - bool Resize(const size_t width, const size_t height) override; + bool Resize(const size_t width_px, const size_t height_px) override; // |NativeWindow| - void SetPosition(const int32_t x, const int32_t y) override; + void SetPosition(const int32_t x_dip, const int32_t y_dip) override; + + // Sets the scale factor for the next commit. Scale factor persists until a + // new one is set. + void SetScaleFactor(float scale_factor); wl_surface* Surface() const { return surface_; } diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_x11.cc b/src/flutter/shell/platform/linux_embedded/window/native_window_x11.cc index 05374de3..e6da1ea1 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_x11.cc +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_x11.cc @@ -16,13 +16,15 @@ namespace flutter { namespace { static constexpr char kWmDeleteWindow[] = "WM_DELETE_WINDOW"; -static constexpr char kWindowTitle[] = "Flutter for Embedded Linux"; } // namespace NativeWindowX11::NativeWindowX11(Display* display, VisualID visual_id, + const char* title, const size_t width, - const size_t height) { + const size_t height, + bool enable_vsync, + bool fullscreen) { XVisualInfo visualTemplate; visualTemplate.visualid = visual_id; @@ -43,10 +45,20 @@ NativeWindowX11::NativeWindowX11(Display* display, ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | StructureNotifyMask; - window_ = - XCreateWindow(display, RootWindow(display, visual->screen), 0, 0, width, - height, 0, visual->depth, InputOutput, visual->visual, - CWBorderPixel | CWColormap | CWEventMask, &windowAttribs); + auto window_width = width; + auto window_height = height; + if (fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(display, RootWindow(display, visual->screen), &attr); + window_width = attr.width; + window_height = attr.height; + windowAttribs.override_redirect = True; + } + + window_ = XCreateWindow( + display, RootWindow(display, visual->screen), 0, 0, window_width, + window_height, 0, visual->depth, InputOutput, visual->visual, + CWBorderPixel | CWColormap | CWEventMask, &windowAttribs); if (!window_) { ELINUX_LOG(ERROR) << "Failed to the create window."; return; @@ -59,16 +71,16 @@ NativeWindowX11::NativeWindowX11(Display* display, // Set the window title. { XTextProperty property; - property.value = - reinterpret_cast(const_cast(kWindowTitle)); + property.value = reinterpret_cast(const_cast(title)); property.encoding = XA_STRING; property.format = 8; - property.nitems = std::strlen(kWindowTitle); + property.nitems = std::strlen(title); XSetWMName(display, window_, &property); } XMapWindow(display, window_); + enable_vsync_ = enable_vsync; width_ = width; height_ = height; valid_ = true; diff --git a/src/flutter/shell/platform/linux_embedded/window/native_window_x11.h b/src/flutter/shell/platform/linux_embedded/window/native_window_x11.h index 03afa8c3..9cb9f786 100644 --- a/src/flutter/shell/platform/linux_embedded/window/native_window_x11.h +++ b/src/flutter/shell/platform/linux_embedded/window/native_window_x11.h @@ -15,8 +15,11 @@ class NativeWindowX11 : public NativeWindow { public: NativeWindowX11(Display* display, VisualID visual_id, + const char* title, const size_t width, - const size_t height); + const size_t height, + bool enable_vsync, + bool fullscreen); ~NativeWindowX11() = default; // |NativeWindow| diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration.h b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration.h index d722d54c..9b36eebf 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration.h +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration.h @@ -28,9 +28,17 @@ class WindowDecoration { virtual void Draw() = 0; - virtual void SetPosition(const int32_t x, const int32_t y) = 0; + // @param[in] x_dip The x coordinate in logical pixels. + // @param[in] y_dip The y coordinate in logical pixels. + virtual void SetPosition(const int32_t x_dip, const int32_t y_dip) = 0; - virtual void Resize(const int32_t width, const int32_t height) = 0; + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. + virtual void Resize(const size_t width_px, const size_t height_px) = 0; + + // Sets the scale factor for the next commit. Scale factor persists until a + // new one is set. + virtual void SetScaleFactor(float scale_factor) = 0; void DestroyContext() const { render_surface_->DestroyContext(); }; diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.cc b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.cc index 60f071a7..a13b8954 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.cc +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.cc @@ -21,6 +21,7 @@ struct GlProcs { PFNGLENABLEPROC glEnable; PFNGLCLEARCOLORPROC glClearColor; PFNGLCLEARPROC glClear; + PFNGLVIEWPORTPROC glViewport; PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; PFNGLDRAWARRAYSPROC glDrawArrays; @@ -41,6 +42,8 @@ static const GlProcs& GlProcs() { eglGetProcAddress("glClearColor")); procs.glClear = reinterpret_cast(eglGetProcAddress("glClear")); + procs.glViewport = + reinterpret_cast(eglGetProcAddress("glViewport")); procs.glEnableVertexAttribArray = reinterpret_cast( eglGetProcAddress("glEnableVertexAttribArray")); @@ -61,7 +64,7 @@ static const GlProcs& GlProcs() { procs.valid = procs.glEnable && procs.glClearColor && procs.glClear && procs.glEnableVertexAttribArray && procs.glLineWidth && procs.glVertexAttribPointer && procs.glDrawArrays && - procs.glDisableVertexAttribArray && + procs.glDisableVertexAttribArray && procs.glViewport && procs.glBindAttribLocation && procs.glUseProgram; if (!procs.valid) { ELINUX_LOG(ERROR) << "Failed to load GlProcs"; @@ -116,6 +119,7 @@ void WindowDecorationButton::Draw() { { gl.glClearColor(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 1.0f); gl.glClear(GL_COLOR_BUFFER_BIT); + gl.glViewport(0, 0, native_window_->Width(), native_window_->Height()); { if (!shader_) { LoadShader(); @@ -198,12 +202,18 @@ void WindowDecorationButton::Draw() { render_surface_->GLContextPresent(0); } -void WindowDecorationButton::SetPosition(const int32_t x, const int32_t y) { - native_window_->SetPosition(x, y); +void WindowDecorationButton::SetPosition(const int32_t x_dip, + const int32_t y_dip) { + native_window_->SetPosition(x_dip, y_dip); } -void WindowDecorationButton::Resize(const int32_t width, const int32_t height) { - render_surface_->Resize(width, height); +void WindowDecorationButton::Resize(const size_t width_px, + const size_t height_px) { + render_surface_->Resize(width_px, height_px); +} + +void WindowDecorationButton::SetScaleFactor(float scale_factor) { + native_window_->SetScaleFactor(scale_factor); } void WindowDecorationButton::LoadShader() { diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.h b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.h index 89e8fc95..abd4e04a 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.h +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_button.h @@ -21,10 +21,13 @@ class WindowDecorationButton : public WindowDecoration { void Draw() override; // |WindowDecoration| - void SetPosition(const int32_t x, const int32_t y) override; + void SetPosition(const int32_t x_dip, const int32_t y_dip) override; // |WindowDecoration| - void Resize(const int32_t width, const int32_t height) override; + void Resize(const size_t width_px, const size_t height_px) override; + + // |WindowDecoration| + void SetScaleFactor(float scale_factor) override; private: void LoadShader(); diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.cc b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.cc index 47b8e69c..fd9f2da3 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.cc +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.cc @@ -69,13 +69,18 @@ void WindowDecorationTitlebar::Draw() { render_surface_->GLContextPresent(0); } -void WindowDecorationTitlebar::SetPosition(const int32_t x, const int32_t y) { - native_window_->SetPosition(x, y); +void WindowDecorationTitlebar::SetPosition(const int32_t x_dip, + const int32_t y_dip) { + native_window_->SetPosition(x_dip, y_dip); } -void WindowDecorationTitlebar::Resize(const int32_t width, - const int32_t height) { - render_surface_->Resize(width, height); +void WindowDecorationTitlebar::Resize(const size_t width_px, + const size_t height_px) { + render_surface_->Resize(width_px, height_px); +} + +void WindowDecorationTitlebar::SetScaleFactor(float scale_factor) { + native_window_->SetScaleFactor(scale_factor); } } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.h b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.h index 9cb5fce5..a8ce9093 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.h +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decoration_titlebar.h @@ -19,10 +19,13 @@ class WindowDecorationTitlebar : public WindowDecoration { void Draw() override; // |WindowDecoration| - void SetPosition(const int32_t x, const int32_t y) override; + void SetPosition(const int32_t x_dip, const int32_t y_dip) override; // |WindowDecoration| - void Resize(const int32_t width, const int32_t height) override; + void Resize(const size_t width_px, const size_t height_px) override; + + // |WindowDecoration| + void SetScaleFactor(float scale_factor) override; }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc index 35af9d04..b6c63c3b 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc @@ -10,11 +10,11 @@ namespace flutter { namespace { -constexpr uint kTitleBarHeight = 30; +constexpr uint kTitleBarHeightDIP = 30; -constexpr uint kButtonWidth = 15; -constexpr uint kButtonHeight = 15; -constexpr uint kButtonMargin = 10; +constexpr uint kButtonWidthDIP = 15; +constexpr uint kButtonHeightDIP = 15; +constexpr uint kButtonMarginDIP = 10; } // namespace WindowDecorationsWayland::WindowDecorationsWayland( @@ -22,53 +22,67 @@ WindowDecorationsWayland::WindowDecorationsWayland( wl_compositor* compositor, wl_subcompositor* subcompositor, wl_surface* root_surface, - int32_t width, - int32_t height) { + int32_t width_dip, + int32_t height_dip, + double pixel_ratio, + bool enable_impeller, + bool enable_vsync) { constexpr bool sub_egl_display = true; // title-bar. titlebar_ = std::make_unique( std::make_unique( - compositor, subcompositor, root_surface, width, kTitleBarHeight), + compositor, subcompositor, root_surface, width_dip * pixel_ratio, + kTitleBarHeightDIP * pixel_ratio, enable_vsync), std::make_unique(std::make_unique( - std::make_unique(display, sub_egl_display)))); - titlebar_->SetPosition(0, -kTitleBarHeight); + std::make_unique(display, sub_egl_display), + enable_impeller))); + titlebar_->SetPosition(0, -kTitleBarHeightDIP); // close button. auto type = WindowDecorationButton::DecorationType::CLOSE_BUTTON; buttons_.push_back(std::make_unique( type, std::make_unique( - compositor, subcompositor, root_surface, kButtonWidth, kButtonHeight), + compositor, subcompositor, root_surface, + kButtonWidthDIP * pixel_ratio, kButtonHeightDIP * pixel_ratio, + enable_vsync), std::make_unique(std::make_unique( - std::make_unique(display, sub_egl_display))))); + std::make_unique(display, sub_egl_display), + enable_impeller)))); buttons_[type]->SetPosition( - width - kButtonWidth - kButtonMargin, - -(kButtonHeight + (kTitleBarHeight - kButtonHeight) / 2)); + width_dip * pixel_ratio - kButtonWidthDIP - kButtonMarginDIP, + -(kButtonHeightDIP + (kTitleBarHeightDIP - kButtonHeightDIP) / 2)); // maximise button. type = WindowDecorationButton::DecorationType::MAXIMISE_BUTTON; buttons_.push_back(std::make_unique( type, std::make_unique( - compositor, subcompositor, root_surface, kButtonWidth, kButtonHeight), + compositor, subcompositor, root_surface, + kButtonWidthDIP * pixel_ratio, kButtonHeightDIP * pixel_ratio, + enable_vsync), std::make_unique(std::make_unique( - std::make_unique(display, sub_egl_display))))); + std::make_unique(display, sub_egl_display), + enable_impeller)))); buttons_[type]->SetPosition( - width - kButtonWidth * 2 - kButtonMargin * 2, - -(kButtonHeight + (kTitleBarHeight - kButtonHeight) / 2)); + width_dip * pixel_ratio - kButtonWidthDIP * 2 - kButtonMarginDIP * 2, + -(kButtonHeightDIP + (kTitleBarHeightDIP - kButtonHeightDIP) / 2)); // minimise button. type = WindowDecorationButton::DecorationType::MINIMISE_BUTTON; buttons_.push_back(std::make_unique( type, std::make_unique( - compositor, subcompositor, root_surface, kButtonWidth, kButtonHeight), + compositor, subcompositor, root_surface, + kButtonWidthDIP * pixel_ratio, kButtonHeightDIP * pixel_ratio, + enable_vsync), std::make_unique(std::make_unique( - std::make_unique(display, sub_egl_display))))); + std::make_unique(display, sub_egl_display), + enable_impeller)))); buttons_[type]->SetPosition( - width - kButtonWidth * 3 - kButtonMargin * 3, - -(kButtonHeight + (kTitleBarHeight - kButtonHeight) / 2)); + width_dip * pixel_ratio - kButtonWidthDIP * 3 - kButtonMarginDIP * 3, + -(kButtonHeightDIP + (kTitleBarHeightDIP - kButtonHeightDIP) / 2)); } WindowDecorationsWayland::~WindowDecorationsWayland() { @@ -86,16 +100,21 @@ void WindowDecorationsWayland::Draw() { } } -void WindowDecorationsWayland::Resize(const int32_t width, - const int32_t height) { - titlebar_->SetPosition(0, -kTitleBarHeight); - titlebar_->Resize(width, kTitleBarHeight); +void WindowDecorationsWayland::Resize(const int32_t width_dip, + const int32_t height_dip, + double pixel_ratio) { + titlebar_->SetScaleFactor(pixel_ratio); + titlebar_->SetPosition(0, -kTitleBarHeightDIP); + titlebar_->Resize(width_dip * pixel_ratio, kTitleBarHeightDIP * pixel_ratio); for (auto i = 0; i < buttons_.size(); i++) { + buttons_[i]->SetScaleFactor(pixel_ratio); buttons_[i]->SetPosition( - width - kButtonWidth * (i + 1) - kButtonMargin * (i + 1), - -(kButtonHeight + (kTitleBarHeight - kButtonHeight) / 2)); - buttons_[i]->Resize(kButtonWidth, -kButtonHeight); + width_dip * pixel_ratio - kButtonWidthDIP * (i + 1) - + kButtonMarginDIP * (i + 1), + -(kButtonHeightDIP + (kTitleBarHeightDIP - kButtonHeightDIP) / 2)); + buttons_[i]->Resize(kButtonWidthDIP * pixel_ratio, + kButtonHeightDIP * pixel_ratio); } } @@ -118,7 +137,7 @@ void WindowDecorationsWayland::DestroyContext() { } int32_t WindowDecorationsWayland::Height() const { - return kTitleBarHeight; + return kTitleBarHeightDIP; } } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.h b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.h index 607d0c3e..910bf009 100644 --- a/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.h +++ b/src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.h @@ -26,21 +26,33 @@ namespace flutter { class WindowDecorationsWayland { public: + // @param[in] width_dip Logical width of the window (i.e. surface width). + // @param[in] height_dip Logical height of the window (i.e. surface height). + // @param[in] pixel_ratio Physical / logical pixels ratio. WindowDecorationsWayland(wl_display* display, wl_compositor* compositor, wl_subcompositor* subcompositor, wl_surface* root_surface, - int32_t width, - int32_t height); + int32_t width_dip, + int32_t height_dip, + double pixel_ratio, + bool enable_impeller, + bool enable_vsync); ~WindowDecorationsWayland(); void Draw(); - void Resize(const int32_t width, const int32_t height); + // @param[in] width_dip Logical width of the window (i.e. surface width). + // @param[in] height_dip Logical height of the window (i.e. surface height). + // @param[in] pixel_ratio Physical / logical pixels ratio. + void Resize(const int32_t width_dip, + const int32_t height_dip, + double pixel_ratio); bool IsMatched(wl_surface* surface, WindowDecoration::DecorationType decoration_type) const; + // Get height in logical pixels. int32_t Height() const; private: diff --git a/src/flutter/shell/platform/linux_embedded/window_binding_handler.h b/src/flutter/shell/platform/linux_embedded/window_binding_handler.h index 83aa7920..34499df8 100644 --- a/src/flutter/shell/platform/linux_embedded/window_binding_handler.h +++ b/src/flutter/shell/platform/linux_embedded/window_binding_handler.h @@ -34,7 +34,12 @@ class WindowBindingHandler { virtual bool DispatchEvent() = 0; // Create a surface. - virtual bool CreateRenderSurface(int32_t width, int32_t height) = 0; + // @param[in] width_px Physical width of the surface. + // @param[in] height_px Physical height of the surface. + // @param[in] enable_impeller Enable impeller. + virtual bool CreateRenderSurface(int32_t width_px, + int32_t height_px, + bool enable_impeller) = 0; // Destroy a surface which is currently used. virtual void DestroyRenderSurface() = 0; diff --git a/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h b/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h index 73f1330d..ad51a5d2 100644 --- a/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h +++ b/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h @@ -11,24 +11,32 @@ namespace flutter { class WindowBindingHandlerDelegate { public: - // Notifies delegate that backing window size has changed. - // Typically called by currently configured WindowBindingHandler - virtual void OnWindowSizeChanged(size_t width, size_t height) const = 0; - - // Notifies delegate that backing window mouse has moved. - // Typically called by currently configured WindowBindingHandler - virtual void OnPointerMove(double x, double y) = 0; + // Notifies delegate that backing window size has changed. Typically called by + // currently configured WindowBindingHandler + // @param[in] width_px Physical width of the window. + // @param[in] height_px Physical height of the window. + virtual void OnWindowSizeChanged(size_t width_px, size_t height_px) const = 0; + + // Notifies delegate that backing window mouse has moved. Typically called by + // currently configured WindowBindingHandler + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + virtual void OnPointerMove(double x_px, double y_px) = 0; // Notifies delegate that backing window mouse pointer button has been // pressed. Typically called by currently configured WindowBindingHandler - virtual void OnPointerDown(double x, - double y, + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + virtual void OnPointerDown(double x_px, + double y_px, FlutterPointerMouseButtons button) = 0; // Notifies delegate that backing window mouse pointer button has been // released. Typically called by currently configured WindowBindingHandler - virtual void OnPointerUp(double x, - double y, + // @param[in] x_px The x coordinate of the pointer event in physical pixels. + // @param[in] y_px The y coordinate of the pointer event in physical pixels. + virtual void OnPointerUp(double x_px, + double y_px, FlutterPointerMouseButtons button) = 0; // Notifies delegate that backing window mouse pointer has left the window. @@ -96,6 +104,22 @@ class WindowBindingHandlerDelegate { // Typically called by currently configured WindowBindingHandler virtual void OnVsync(uint64_t last_frame_time_nanos, uint64_t vsync_interval_time_nanos) = 0; + + // Update the status of the high contrast feature + virtual void UpdateHighContrastEnabled(bool enabled) = 0; + + // Update the status of the text scaling factor feature + virtual void UpdateTextScaleFactor(float factor) = 0; + + // Update the status of the corresponding to display changes. + // @param[in] refresh_rate Refresh rate of the display. + // @param[in] width_px Physical width of the display. + // @param[in] height_px Physical height of the display. + // @param[in] pixel_ratio Pixel ratio of the display. + virtual void UpdateDisplayInfo(double refresh_rate, + size_t width_px, + size_t height_px, + double pixel_ratio) = 0; }; } // namespace flutter