├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets └── image.png └── src ├── debug.cc ├── debug.h ├── egl_utils.cc ├── egl_utils.h ├── elf.cc ├── elf.h ├── event_loop.cc ├── event_loop.h ├── keys.cc ├── keys.h ├── macros.h ├── main.cc ├── utils.cc ├── utils.h ├── wayland_display.cc └── wayland_display.h /.clang-format: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Damian Wrobel 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | --- 18 | Language: Cpp 19 | # BasedOnStyle: LLVM 20 | AccessModifierOffset: -2 21 | AlignAfterOpenBracket: Align 22 | AlignConsecutiveAssignments: true 23 | AlignConsecutiveDeclarations: false 24 | AlignEscapedNewlines: Right 25 | AlignOperands: true 26 | AlignTrailingComments: true 27 | AllowAllParametersOfDeclarationOnNextLine: true 28 | AllowShortBlocksOnASingleLine: false 29 | AllowShortCaseLabelsOnASingleLine: false 30 | AllowShortFunctionsOnASingleLine: None 31 | AllowShortIfStatementsOnASingleLine: false 32 | AllowShortLoopsOnASingleLine: false 33 | AlwaysBreakAfterDefinitionReturnType: None 34 | AlwaysBreakAfterReturnType: None 35 | AlwaysBreakBeforeMultilineStrings: false 36 | AlwaysBreakTemplateDeclarations: false 37 | BinPackArguments: true 38 | BinPackParameters: true 39 | BraceWrapping: 40 | AfterClass: false 41 | AfterControlStatement: false 42 | AfterEnum: false 43 | AfterFunction: false 44 | AfterNamespace: false 45 | AfterObjCDeclaration: false 46 | AfterStruct: false 47 | AfterUnion: false 48 | AfterExternBlock: false 49 | BeforeCatch: false 50 | BeforeElse: false 51 | IndentBraces: false 52 | SplitEmptyFunction: true 53 | SplitEmptyRecord: true 54 | SplitEmptyNamespace: true 55 | BreakBeforeBinaryOperators: None 56 | BreakBeforeBraces: Attach 57 | BreakBeforeInheritanceComma: false 58 | BreakBeforeTernaryOperators: true 59 | BreakConstructorInitializersBeforeComma: true 60 | BreakConstructorInitializers: BeforeColon 61 | BreakAfterJavaFieldAnnotations: false 62 | BreakStringLiterals: true 63 | ColumnLimit: 240 64 | CommentPragmas: '^ IWYU pragma:' 65 | CompactNamespaces: false 66 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 67 | ConstructorInitializerIndentWidth: 4 68 | ContinuationIndentWidth: 4 69 | Cpp11BracedListStyle: true 70 | DerivePointerAlignment: false 71 | DisableFormat: false 72 | ExperimentalAutoDetectBinPacking: false 73 | FixNamespaceComments: true 74 | ForEachMacros: 75 | - foreach 76 | - Q_FOREACH 77 | - BOOST_FOREACH 78 | IncludeBlocks: Preserve 79 | IncludeCategories: 80 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 81 | Priority: 2 82 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 83 | Priority: 3 84 | - Regex: '.*' 85 | Priority: 1 86 | IncludeIsMainRegex: '(Test)?$' 87 | IndentCaseLabels: false 88 | IndentPPDirectives: None 89 | IndentWidth: 2 90 | IndentWrappedFunctionNames: false 91 | JavaScriptQuotes: Leave 92 | JavaScriptWrapImports: true 93 | KeepEmptyLinesAtTheStartOfBlocks: true 94 | MacroBlockBegin: '' 95 | MacroBlockEnd: '' 96 | MaxEmptyLinesToKeep: 1 97 | NamespaceIndentation: None 98 | ObjCBlockIndentWidth: 2 99 | ObjCSpaceAfterProperty: false 100 | ObjCSpaceBeforeProtocolList: true 101 | PenaltyBreakAssignment: 2 102 | PenaltyBreakBeforeFirstCallParameter: 19 103 | PenaltyBreakComment: 300 104 | PenaltyBreakFirstLessLess: 120 105 | PenaltyBreakString: 1000 106 | PenaltyExcessCharacter: 1000000 107 | PenaltyReturnTypeOnItsOwnLine: 60 108 | PointerAlignment: Right 109 | RawStringFormats: 110 | ReflowComments: true 111 | SortIncludes: false 112 | SortUsingDeclarations: true 113 | SpaceAfterCStyleCast: false 114 | SpaceAfterTemplateKeyword: true 115 | SpaceBeforeAssignmentOperators: true 116 | SpaceBeforeParens: ControlStatements 117 | SpaceInEmptyParentheses: false 118 | SpacesBeforeTrailingComments: 1 119 | SpacesInAngles: false 120 | SpacesInContainerLiterals: true 121 | SpacesInCStyleCastParentheses: false 122 | SpacesInParentheses: false 123 | SpacesInSquareBrackets: false 124 | Standard: Cpp11 125 | TabWidth: 8 126 | UseTab: Never 127 | ... 128 | 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | *.lock 9 | profile 10 | 11 | DerivedData/ 12 | build/ 13 | 14 | *.pbxuser 15 | *.mode1v3 16 | *.mode2v3 17 | *.perspectivev3 18 | 19 | !default.pbxuser 20 | !default.mode1v3 21 | !default.mode2v3 22 | !default.perspectivev3 23 | 24 | xcuserdata 25 | *.xcscmblueprint 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | *.sublime-workspace 35 | cmake-*/ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Flutter Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | 6 | cmake_minimum_required(VERSION 3.9) 7 | 8 | project(flutter-launcher-wayland) 9 | 10 | find_package(PkgConfig) 11 | find_package(ECM REQUIRED NO_MODULE) 12 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 13 | find_package(WaylandScanner REQUIRED) 14 | find_package(WaylandProtocols REQUIRED) 15 | pkg_search_module(XKB xkbcommon REQUIRED) 16 | pkg_search_module(EGL egl REQUIRED) 17 | pkg_search_module(WAYLAND_CLIENT wayland-client REQUIRED) 18 | pkg_search_module(WAYLAND_EGL wayland-egl REQUIRED) 19 | pkg_search_module(GDK gdk-3.0 REQUIRED) # dw: Not used for linking, we just need an access to the header 20 | # If you do not have flutter-engine.pc file 21 | # just provide all FLUTTER_ENGINE* variables using -D 22 | pkg_search_module(FLUTTER_ENGINE flutter-engine) 23 | 24 | set(CMAKE_CXX_STANDARD 20) 25 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 26 | set(CMAKE_CXX_EXTENSIONS OFF) 27 | 28 | set (CMAKE_CXX_FLAGS "-Wall -Wextra -Woverloaded-virtual -Wno-unused-parameter ${CMAKE_CXX_FLAGS}") 29 | 30 | set(SOURCES 31 | src/main.cc 32 | src/elf.cc 33 | src/keys.cc 34 | src/egl_utils.cc 35 | src/utils.cc 36 | src/debug.cc 37 | src/wayland_display.cc 38 | src/event_loop.cc 39 | src/elf.h 40 | src/macros.h 41 | src/keys.h 42 | src/utils.h 43 | src/egl_utils.h 44 | src/debug.h 45 | src/wayland_display.h 46 | src/event_loop.h 47 | ) 48 | 49 | ecm_add_wayland_client_protocol( 50 | SOURCES 51 | PROTOCOL "${WaylandProtocols_DATADIR}/stable/presentation-time/presentation-time.xml" 52 | BASENAME "presentation-time" 53 | ) 54 | 55 | ecm_add_wayland_client_protocol( 56 | SOURCES 57 | PROTOCOL "${WaylandProtocols_DATADIR}/unstable/xwayland-keyboard-grab/xwayland-keyboard-grab-unstable-v1.xml" 58 | BASENAME "xwayland-keyboard-grab" 59 | ) 60 | 61 | link_directories( 62 | ${XKB_LIBRARY_DIRS} 63 | ${EGL_LIBRARY_DIRS} 64 | ${WAYLAND_CLIENT_LIBRARY_DIRS} 65 | ${WAYLAND_EGL_LIBRARY_DIRS} 66 | ${FLUTTER_ENGINE_LIBRARY_DIRS} 67 | ) 68 | 69 | add_executable(flutter-launcher-wayland ${SOURCES}) 70 | 71 | target_include_directories(flutter-launcher-wayland 72 | PRIVATE 73 | ${CMAKE_CURRENT_BINARY_DIR} 74 | ${GLFW_INCLUDE_DIRS} 75 | ${GLFW_INCLUDE_DIRS} 76 | ${XKB_INCLUDE_DIRS} 77 | ${EGL_INCLUDE_DIRS} 78 | ${WAYLAND_CLIENT_INCLUDE_DIRS} 79 | ${WAYLAND_EGL_INCLUDE_DIRS} 80 | ${GDK_INCLUDE_DIRS} 81 | ${FLUTTER_ENGINE_INCLUDE_DIRS} 82 | ) 83 | 84 | target_link_libraries(flutter-launcher-wayland 85 | ${CMAKE_DL_LIBS} 86 | ${XKB_LIBRARIES} 87 | ${EGL_LIBRARIES} 88 | ${WAYLAND_CLIENT_LIBRARIES} 89 | ${WAYLAND_EGL_LIBRARIES} 90 | ${FLUTTER_ENGINE_LIBRARIES} 91 | ) 92 | 93 | install(TARGETS flutter-launcher-wayland DESTINATION bin) 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 The Chromium Authors. All rights reserved. 2 | Copyright 2019 Damian Wrobel 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flutter Wayland 2 | ============ 3 | 4 | A Flutter Embedder/Launcher that talks to Wayland. 5 | 6 | ![Running in Weston](assets/image.png) 7 | 8 | Features: 9 | - Suited for running on [STB](https://en.wikipedia.org/wiki/Set-top_box) or any other embedded devices, 10 | - Provides only mininum required implementataion for launching Flutter application _(both JIT or AOT modes are supported)_: 11 | - keyboard support with repetition _(no touch support, only basic pointer events are implemented)_, 12 | - support the following Wayland extensions: 13 | 14 | - [presentation-time](https://github.com/wayland-project/wayland-protocols/blob/master/stable/presentation-time/presentation-time.xml) with a fallback to frame callback, 15 | 16 | - [XWayland keyboard grabbing protocol](https://github.com/wayland-project/wayland-protocols/tree/0a61d3516b10da4e65607a6dd97937ebedf6bcfa/unstable/xwayland-keyboard-grab) assumes [Wayland server](https://gitlab.freedesktop.org/dwrobel/weston/-/commits/dw-master-key-grab-2) implements it 17 | _(allows to run Flutter application as a main UI which will receive all key events even when not having a focus, which is needed to control other applications)_, 18 | 19 | Build dependencies: 20 | ------------------- 21 | - Fedora >=33: 22 | ``` 23 | dnf install cmake clang extra-cmake-modules pkgconf-pkg-config libxkbcommon-devel wayland-devel wayland-protocols-devel libwayland-client libwayland-egl gtk3-devel 24 | ``` 25 | 26 | - Yocto 27 | ``` 28 | TOOLCHAIN = "clang" 29 | DEPENDS = "flutter-engine gtk+3 libxkbcommon wayland-protocols extra-cmake-modules" 30 | inherit pkgconfig cmake 31 | ``` 32 | 33 | Build instructions: 34 | ------------------- 35 | 36 | ``` 37 | # If you don't have pkg-config flutter-engine.pc file 38 | # provide directory location of flutter_embedder.h file: 39 | EMBEDDER_INC=/src/out/linux_release_x64 40 | # directory location of libflutter_engine.so (typically the same as for header files) 41 | FLUTTER_LIBDIR=${EMBEDDER_INC} 42 | 43 | # Preferably use the same compiler as was used for compiling the engine: 44 | export CC=clang 45 | export CXX=clang++ 46 | 47 | mkdir -p build 48 | pushd build 49 | 50 | # If you don't have flutter-engine.pc file 51 | cmake .. -DFLUTTER_ENGINE_LIBRARIES=flutter_engine -DFLUTTER_ENGINE_LIBRARY_DIRS=${FLUTTER_LIBDIR} -DFLUTTER_ENGINE_INCLUDE_DIRS=${EMBEDDER_INC} 52 | # otherwise 53 | cmake .. 54 | 55 | # Common step to compile sources 56 | cmake --build . -j $(getconf _NPROCESSORS_ONLN) --verbose 57 | popd 58 | ``` 59 | 60 | Direct runtime dependencies: 61 | --------------------- 62 | ``` 63 | $ readelf -d flutter-launcher-wayland | grep NEEDED | awk '{gsub(/\[|\]/,"",$NF); print " - "$5}' 64 | - libdl.so.2 65 | - libxkbcommon.so.0 66 | - libEGL.so.1 67 | - libwayland-client.so.0 68 | - libwayland-egl.so.1 69 | - libflutter_engine.so 70 | - libstdc++.so.6 71 | - libm.so.6 72 | - libgcc_s.so.1 73 | - libc.so.6 74 | ``` 75 | 76 | Running Flutter Applications 77 | ---------------------------- 78 | 79 | ``` 80 | Usage: `flutter-launcher-wayland ` 81 | 82 | 83 | This utility runs an instance of a Flutter application and renders using 84 | Wayland core protocols. 85 | 86 | The Flutter tools can be obtained at https://flutter.io/ 87 | 88 | asset_bundle_path: The Flutter application code needs to be snapshotted using 89 | the Flutter tools and the assets packaged in the appropriate 90 | location. This can be done for any Flutter application by 91 | running `flutter build bundle` while in the directory of a 92 | valid Flutter project. This should package all the code and 93 | assets in the "build/flutter_assets" directory. Specify this 94 | directory as the first argument to this utility. 95 | 96 | flutter_flags: Typically empty. These extra flags are passed directly to the 97 | Flutter engine. To see all supported flags, run 98 | `flutter_tester --help` using the test binary included in the 99 | Flutter tools. 100 | 101 | Supported environment variables: 102 | LANG= 103 | Value encoded as per setlocale(3) (e.g. "szl_PL.utf8") is 104 | passed to the engine via UpdateLocales(). 105 | 106 | See also: https://man7.org/linux/man-pages/man3/setlocale.3.html 107 | 108 | FLUTTER_WAYLAND_PIXEL_RATIO= 109 | Overwrites the pixel aspect ratio reported 110 | to the engine by FlutterEngineSendWindowMetricsEvent(). 111 | 112 | See also: https://api.flutter.dev/flutter/dart-ui/Window/devicePixelRatio.html 113 | 114 | FLUTTER_WAYLAND_MAIN_UI= 115 | Non-zero value enables grabbing all keys (even without having 116 | a focus) using Xwayland keyboard grabbing protocol (assuming 117 | server implement this extension). 118 | 119 | See also: https://github.com/wayland-project/wayland-protocols/tree/0a61d3516b10da4e65607a6dd97937ebedf6bcfa/unstable/xwayland-keyboard-grab 120 | 121 | FLUTTER_LAUNCHER_WAYLAND_DEBUG= 122 | where can be any of syslog(3) prioritynames or its 123 | unique abbreviation e.g. "err", "warning", "info" or "debug". 124 | 125 | FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH= 126 | if the app is run inside lxc container - path to where cgroup/memory memory.usage_in_bytes & cgroup.event_control are located 127 | necessary for memory watcher to work 128 | 129 | FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES= 130 | if FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH is defined, this specifies container memory usage levels at which the application 131 | will get FlutterEngineNotifyLowMemoryWarning notifications; the format is comma-separated memory (in bytes) values, like: 132 | "1000000,70000000,148478361,167038156,176318054" 133 | At least one memory level needs to be defined for memory watcher to work. After each notification, there is 20sec cooldown period, 134 | when additional FlutterEngineNotifyLowMemoryWarning notifications will not be called. 135 | 136 | ``` 137 | 138 | Contributing: 139 | ------------- 140 | Before submitting a new PR: 141 | - Format the code with `clang-format -i src/*.*`. 142 | - Make sure that code compiles fine with both `gcc >=11` and `clang >= 11` _(no single warning on clang)_. 143 | - Test on PC with both: 144 | 145 | - AddressSanitizer (recompile code with: `-fsanitize=address -fPIE -fno-omit-frame-pointer`), 146 | 147 | - TheadSanitizer (recompile code with: `-fsanitize=thread -fPIE -fno-omit-frame-pointer`). 148 | 149 | Authors: 150 | -------- 151 | Based on the original code from: https://github.com/chinmaygarde/flutter_wayland 152 | 153 | Heavily rewritten by: Damian Wrobel <> 154 | 155 | -------------------------------------------------------------------------------- /assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibertyGlobal/flutter-embedder-wayland/88746be988d105e552deb2087ed45ef0174a0b94/assets/image.png -------------------------------------------------------------------------------- /src/debug.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | //. 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifndef SYSLOG_NAMES 31 | #define SYSLOG_NAMES 32 | #endif 33 | #include 34 | 35 | #include "utils.h" 36 | #include "debug.h" 37 | 38 | namespace flutter { 39 | 40 | static int debug_level; 41 | 42 | int syslog2level(const std::string &priority) { 43 | const char *c_name = priority.c_str(); 44 | for (size_t i = 0; prioritynames[i].c_name != NULL; i++) { 45 | if (strstr(prioritynames[i].c_name, c_name)) { 46 | return prioritynames[i].c_val; 47 | } 48 | } 49 | 50 | return LOG_INFO; 51 | } 52 | 53 | void dbg_init() { 54 | debug_level = syslog2level(getEnv("FLUTTER_LAUNCHER_WAYLAND_DEBUG", std::string("info"))); 55 | } 56 | 57 | static const char *priority2prefix(const int priority) { 58 | static const struct { 59 | const int priority; 60 | const char *const prefix; 61 | } map[] = { 62 | {LOG_ERR, "ERROR: "}, 63 | {LOG_WARNING, "WARNING: "}, 64 | {LOG_INFO, "INFO: "}, 65 | {LOG_DEBUG, "TRACE: "}, 66 | }; 67 | 68 | for (size_t i = 0; i < std::size(map); i++) { 69 | if (priority == map[i].priority) { 70 | return map[i].prefix; 71 | } 72 | } 73 | 74 | return "UNKNOWN: "; 75 | } 76 | 77 | static void vdbg(const int priority, const char *format, va_list args1) { 78 | va_list args2; 79 | va_copy(args2, args1); 80 | 81 | char buf[256]; 82 | int rv = std::vsnprintf(buf, std::size(buf), format, args1); 83 | 84 | if (rv < 0) { 85 | va_end(args2); 86 | return; 87 | } 88 | 89 | if (static_cast(rv) < std::size(buf)) { 90 | va_end(args2); 91 | 92 | printf("%s%s", priority2prefix(priority), buf); 93 | fflush(stdout); 94 | } else { 95 | const size_t len = rv + 1; 96 | std::vector str(len); 97 | std::vsnprintf(str.data(), len, format, args2); 98 | va_end(args2); 99 | 100 | printf("%s%s", priority2prefix(priority), str.data()); 101 | fflush(stdout); 102 | } 103 | } 104 | 105 | // clang-format off 106 | #define dbg(type, priority) \ 107 | void dbg##type(const char *format, ...) { \ 108 | if (debug_level < (priority)) \ 109 | return; \ 110 | \ 111 | va_list args; \ 112 | va_start(args, format); \ 113 | vdbg((priority), format, args); \ 114 | va_end(args); \ 115 | } 116 | 117 | dbg(E, LOG_ERR) 118 | dbg(W, LOG_WARNING) 119 | dbg(I, LOG_INFO) 120 | dbg(T, LOG_DEBUG) 121 | // clang-format once 122 | 123 | } // namespace flutter 124 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | //. 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | #pragma once 23 | 24 | namespace flutter { 25 | 26 | void dbg_init(); 27 | 28 | void dbgE(const char *format, ...) __attribute__((format(printf, 1, 2))); 29 | void dbgW(const char *format, ...) __attribute__((format(printf, 1, 2))); 30 | void dbgI(const char *format, ...) __attribute__((format(printf, 1, 2))); 31 | void dbgT(const char *format, ...) __attribute__((format(printf, 1, 2))); 32 | 33 | } // namespace flutter 34 | -------------------------------------------------------------------------------- /src/egl_utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Copyright 2019 Damian Wrobel 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | #include 7 | #include 8 | #include "egl_utils.h" 9 | #include "utils.h" 10 | #include "debug.h" 11 | 12 | namespace flutter { 13 | 14 | void LogLastEGLError() { 15 | struct EGLNameErrorPair { 16 | const char *name; 17 | EGLint code; 18 | }; 19 | 20 | #define _EGL_ERROR_DESC(a) \ 21 | { #a, a } 22 | 23 | const EGLNameErrorPair pairs[] = { 24 | _EGL_ERROR_DESC(EGL_SUCCESS), _EGL_ERROR_DESC(EGL_NOT_INITIALIZED), _EGL_ERROR_DESC(EGL_BAD_ACCESS), _EGL_ERROR_DESC(EGL_BAD_ALLOC), _EGL_ERROR_DESC(EGL_BAD_ATTRIBUTE), 25 | _EGL_ERROR_DESC(EGL_BAD_CONTEXT), _EGL_ERROR_DESC(EGL_BAD_CONFIG), _EGL_ERROR_DESC(EGL_BAD_CURRENT_SURFACE), _EGL_ERROR_DESC(EGL_BAD_DISPLAY), _EGL_ERROR_DESC(EGL_BAD_SURFACE), 26 | _EGL_ERROR_DESC(EGL_BAD_MATCH), _EGL_ERROR_DESC(EGL_BAD_PARAMETER), _EGL_ERROR_DESC(EGL_BAD_NATIVE_PIXMAP), _EGL_ERROR_DESC(EGL_BAD_NATIVE_WINDOW), _EGL_ERROR_DESC(EGL_CONTEXT_LOST), 27 | }; 28 | 29 | #undef _EGL_ERROR_DESC 30 | 31 | const auto count = sizeof(pairs) / sizeof(EGLNameErrorPair); 32 | 33 | EGLint last_error = eglGetError(); 34 | 35 | for (size_t i = 0; i < count; i++) { 36 | if (last_error == pairs[i].code) { 37 | dbgE("EGL Error: %s (code: %d)\n", pairs[i].name, pairs[i].code); 38 | return; 39 | } 40 | } 41 | 42 | dbgE("Unknown EGL Error (%d)\n", last_error); 43 | } 44 | 45 | } // namespace flutter 46 | -------------------------------------------------------------------------------- /src/egl_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Copyright 2019 Damian Wrobel 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | #pragma once 7 | 8 | #include "macros.h" 9 | 10 | namespace flutter { 11 | 12 | void LogLastEGLError(); 13 | 14 | } // namespace flutter 15 | -------------------------------------------------------------------------------- /src/elf.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | //. 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | 23 | #include 24 | #include 25 | #include 26 | #include "elf.h" 27 | #include "macros.h" 28 | 29 | namespace flutter { 30 | 31 | class LoadedElf { 32 | public: 33 | explicit LoadedElf(const char *const filename, const uint64_t elf_data_offset) 34 | : filename(strdup(filename), std::free) 35 | , elf_data_offset(elf_data_offset) 36 | , error_(nullptr) 37 | , fd(NULL) 38 | , vm_snapshot_data_(NULL) 39 | , vm_snapshot_instructions_(NULL) 40 | , vm_isolate_snapshot_data_(NULL) 41 | , vm_isolate_snapshot_instructions_(NULL) { 42 | } 43 | 44 | ~LoadedElf() { 45 | if (fd) { 46 | dlclose(fd); 47 | fd = NULL; 48 | } 49 | } 50 | 51 | bool Load() { 52 | if (elf_data_offset) { 53 | return false; // not supported 54 | } 55 | 56 | fd = dlopen(filename.get(), RTLD_LOCAL | RTLD_NOW); 57 | 58 | if (fd == NULL) { 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | bool ResolveSymbols(const uint8_t **vm_snapshot_data, const uint8_t **vm_snapshot_instructions, const uint8_t **vm_isolate_snapshot_data, const uint8_t **vm_isolate_snapshot_instructions) { 66 | if (error_ != nullptr) { 67 | return false; 68 | } 69 | 70 | *vm_snapshot_data = *vm_snapshot_instructions = *vm_isolate_snapshot_data = *vm_isolate_snapshot_instructions = nullptr; 71 | 72 | do { 73 | 74 | vm_snapshot_instructions_ = dlsym(fd, "_kDartVmSnapshotInstructions"); 75 | 76 | if (vm_snapshot_instructions_ == NULL) { 77 | error_ = strerror(errno); 78 | break; 79 | } 80 | 81 | vm_isolate_snapshot_instructions_ = dlsym(fd, "_kDartIsolateSnapshotInstructions"); 82 | 83 | if (vm_isolate_snapshot_instructions_ == NULL) { 84 | error_ = strerror(errno); 85 | break; 86 | } 87 | 88 | vm_snapshot_data_ = dlsym(fd, "_kDartVmSnapshotData"); 89 | 90 | if (vm_snapshot_data_ == NULL) { 91 | error_ = strerror(errno); 92 | break; 93 | } 94 | 95 | vm_isolate_snapshot_data_ = dlsym(fd, "_kDartIsolateSnapshotData"); 96 | 97 | if (vm_isolate_snapshot_data_ == NULL) { 98 | error_ = strerror(errno); 99 | break; 100 | } 101 | } while (0); 102 | 103 | if (vm_snapshot_data_ == NULL || vm_snapshot_instructions_ == NULL || vm_isolate_snapshot_data_ == NULL || vm_isolate_snapshot_instructions_ == NULL) { 104 | return false; 105 | } 106 | 107 | *vm_snapshot_data = reinterpret_cast(vm_snapshot_data_); 108 | *vm_snapshot_instructions = reinterpret_cast(vm_snapshot_instructions_); 109 | *vm_isolate_snapshot_data = reinterpret_cast(vm_isolate_snapshot_data_); 110 | *vm_isolate_snapshot_instructions = reinterpret_cast(vm_isolate_snapshot_instructions_); 111 | 112 | return true; 113 | } 114 | 115 | const char *error() { 116 | return error_; 117 | } 118 | 119 | protected: 120 | std::unique_ptr filename; 121 | const uint64_t elf_data_offset; 122 | const char *error_; 123 | void *fd; 124 | void *vm_snapshot_data_, *vm_snapshot_instructions_, *vm_isolate_snapshot_data_, *vm_isolate_snapshot_instructions_; 125 | 126 | private: 127 | FLWAY_DISALLOW_COPY_AND_ASSIGN(LoadedElf); 128 | }; 129 | 130 | Aot_LoadedElf *Aot_LoadELF(const char *filename, const uint64_t file_offset, const char **error, const uint8_t **vm_snapshot_data, const uint8_t **vm_snapshot_instrs, const uint8_t **vm_isolate_snapshot_data, 131 | const uint8_t **vm_isolate_snapshot_instructions) { 132 | 133 | auto elf = std::make_unique(filename, file_offset); 134 | 135 | if (!elf->Load() || !elf->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, vm_isolate_snapshot_data, vm_isolate_snapshot_instructions)) { 136 | *error = elf->error(); 137 | return nullptr; 138 | } 139 | 140 | return reinterpret_cast(elf.release()); 141 | } 142 | 143 | } // namespace flutter 144 | -------------------------------------------------------------------------------- /src/elf.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | // 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | 23 | namespace flutter { 24 | 25 | typedef struct { 26 | } Aot_LoadedElf; 27 | 28 | // Modeled after Dart_LoadELF() however, it is based on the dlopen()/dlsym() instead of relying on any piece of the code from the Dart Engine. 29 | Aot_LoadedElf *Aot_LoadELF(const char *filename, const uint64_t file_offset, const char **error, const uint8_t **vm_snapshot_data, const uint8_t **vm_snapshot_instrs, const uint8_t **vm_isolate_data, const uint8_t **vm_isolate_instrs); 30 | 31 | void Aot_UnloadELF(Aot_LoadedElf *loaded); 32 | 33 | } // namespace flutter 34 | -------------------------------------------------------------------------------- /src/event_loop.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "event_loop.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #include "event_loop.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace flutter { 17 | 18 | PlatformEventLoop::PlatformEventLoop(std::thread::id main_thread_id, const TaskExpiredCallback &on_task_expired, int notify_fd) 19 | : main_thread_id_(main_thread_id) 20 | , on_task_expired_(std::move(on_task_expired)) 21 | , notify_fd_(notify_fd) { 22 | } 23 | 24 | PlatformEventLoop::~PlatformEventLoop() = default; 25 | 26 | bool PlatformEventLoop::RunsTasksOnCurrentThread() const { 27 | return std::this_thread::get_id() == main_thread_id_; 28 | } 29 | 30 | uint64_t PlatformEventLoop::ProcessEvents() { 31 | std::vector expired_tasks; 32 | 33 | // Process expired tasks. 34 | { 35 | std::lock_guard lock(task_queue_mutex_); 36 | const uint64_t start_processing_time = FlutterEngineGetCurrentTime(); 37 | while (!task_queue_.empty()) { 38 | const auto &top = task_queue_.top(); 39 | // If this task (and all tasks after this) has not yet expired, there is 40 | // nothing more to do. Quit iterating. 41 | if (top.fire_time > start_processing_time) { 42 | break; 43 | } 44 | 45 | // Make a record of the expired task. Do NOT service the task here 46 | // because we are still holding onto the task queue mutex. We don't want 47 | // other threads to block on posting tasks onto this thread till we are 48 | // done processing expired tasks. 49 | expired_tasks.push_back(task_queue_.top().task); 50 | 51 | // Remove the tasks from the delayed tasks queue. 52 | task_queue_.pop(); 53 | } 54 | } 55 | 56 | // Fire expired tasks. 57 | { 58 | // Flushing tasks here without holing onto the task queue mutex. 59 | for (const auto &task : expired_tasks) { 60 | on_task_expired_(&task); 61 | } 62 | } 63 | 64 | // return timestamp of next event or 0 if none 65 | { 66 | std::lock_guard lock(task_queue_mutex_); 67 | if (task_queue_.empty()) { 68 | return 0; 69 | } else { 70 | return task_queue_.top().fire_time; 71 | } 72 | } 73 | } 74 | 75 | void PlatformEventLoop::PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos) { 76 | static std::atomic sGlobalTaskOrder(0); 77 | 78 | Task task; 79 | task.order = ++sGlobalTaskOrder; 80 | task.fire_time = flutter_target_time_nanos; 81 | task.task = flutter_task; 82 | 83 | { 84 | std::lock_guard lock(task_queue_mutex_); 85 | task_queue_.push(task); 86 | 87 | // Make sure the queue mutex is unlocked before waking up the loop. In case 88 | // the wake causes this thread to be descheduled for the primary thread to 89 | // process tasks, the acquisition of the lock on that thread while holding 90 | // the lock here momentarily till the end of the scope is a pessimization. 91 | } 92 | Wake(); 93 | } 94 | 95 | void PlatformEventLoop::Wake() { 96 | ssize_t ret = 0; 97 | uint64_t val = 1; 98 | do { 99 | ret = write(notify_fd_, &val, sizeof(val)); 100 | } while (ret < 0 && errno == EAGAIN); 101 | } 102 | 103 | } // namespace flutter 104 | -------------------------------------------------------------------------------- /src/event_loop.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef FLUTTER_SHELL_PLATFORM_GLFW_EVENT_LOOP_H_ 6 | #define FLUTTER_SHELL_PLATFORM_GLFW_EVENT_LOOP_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace flutter { 18 | 19 | // platform event loop, to handle flutter platform events 20 | // adapted from flutter engine glfw embedder (flutter/shell/platform/glfw/flutter_glfw.cc) 21 | class PlatformEventLoop { 22 | public: 23 | using TaskExpiredCallback = std::function; 24 | // Creates an event loop running on the given thread, calling 25 | // |on_task_expired| to run tasks. 26 | // notify_fd event descriptor will be used to wake up the main thread when events arrive 27 | PlatformEventLoop(std::thread::id main_thread_id, const TaskExpiredCallback &on_task_expired, int notify_fd); 28 | 29 | virtual ~PlatformEventLoop(); 30 | 31 | // Disallow copy. 32 | PlatformEventLoop(const PlatformEventLoop &) = delete; 33 | PlatformEventLoop &operator=(const PlatformEventLoop &) = delete; 34 | 35 | // Returns if the current thread is the thread used by this event loop. 36 | bool RunsTasksOnCurrentThread() const; 37 | 38 | // processes expired events, and returns next wake up point or 0 if there are no events 39 | uint64_t ProcessEvents(); 40 | 41 | // Posts a Flutter engine task to the event loop for delayed execution. 42 | void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos); 43 | 44 | protected: 45 | 46 | // Wakes the main thread 47 | void Wake(); 48 | 49 | struct Task { 50 | uint64_t order; 51 | uint64_t fire_time; 52 | FlutterTask task; 53 | 54 | struct Comparer { 55 | bool operator()(const Task &a, const Task &b) { 56 | if (a.fire_time == b.fire_time) { 57 | return a.order > b.order; 58 | } 59 | return a.fire_time > b.fire_time; 60 | } 61 | }; 62 | }; 63 | std::thread::id main_thread_id_; 64 | TaskExpiredCallback on_task_expired_; 65 | std::mutex task_queue_mutex_; 66 | std::priority_queue, Task::Comparer> task_queue_; 67 | int notify_fd_; 68 | }; 69 | 70 | } // namespace flutter 71 | 72 | #endif // FLUTTER_SHELL_PLATFORM_GLFW_EVENT_LOOP_H_ 73 | -------------------------------------------------------------------------------- /src/keys.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | // 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | 23 | #include 24 | #include "keys.h" 25 | 26 | namespace flutter { 27 | 28 | GdkModifierType toGDKModifiers(struct xkb_keymap *xkb_keymap, guint32 mods) { 29 | static const struct { 30 | const char *xkb_name; 31 | guint32 gdk_mask; 32 | } table[] = { 33 | {XKB_MOD_NAME_CAPS, GDK_LOCK_MASK}, 34 | {XKB_MOD_NAME_CTRL, GDK_CONTROL_MASK}, 35 | {XKB_MOD_NAME_SHIFT, GDK_SHIFT_MASK}, 36 | {XKB_MOD_NAME_ALT, GDK_MOD1_MASK}, 37 | {XKB_MOD_NAME_NUM, GDK_MOD2_MASK}, 38 | {XKB_MOD_NAME_LOGO, GDK_MOD4_MASK}, 39 | {"Mod3", GDK_MOD3_MASK}, 40 | {"Mod5", GDK_MOD5_MASK}, 41 | {"Super", GDK_SUPER_MASK}, 42 | {"Hyper", GDK_HYPER_MASK}, 43 | }; 44 | 45 | guint state = 0; 46 | 47 | for (size_t i = 0; i < std::size(table); i++) { 48 | if (mods & (1 << xkb_keymap_mod_get_index(xkb_keymap, table[i].xkb_name))) 49 | state |= table[i].gdk_mask; 50 | } 51 | 52 | if (mods & (1 << xkb_keymap_mod_get_index(xkb_keymap, "Meta")) && (state & GDK_MOD1_MASK) == 0) 53 | state |= GDK_META_MASK; 54 | 55 | return static_cast(state); 56 | } 57 | 58 | } // namespace flutter 59 | -------------------------------------------------------------------------------- /src/keys.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Damian Wrobel 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgment in the product documentation would 14 | // be appreciated but is not required. 15 | // 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | namespace flutter { 29 | 30 | GdkModifierType toGDKModifiers(struct xkb_keymap *xkb_keymap, guint32 mods); 31 | 32 | } // namespace flutter 33 | -------------------------------------------------------------------------------- /src/macros.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #define FLWAY_DISALLOW_COPY(TypeName) TypeName(const TypeName &) = delete; 10 | 11 | #define FLWAY_DISALLOW_ASSIGN(TypeName) void operator=(const TypeName &) = delete; 12 | 13 | #define FLWAY_DISALLOW_COPY_AND_ASSIGN(TypeName) \ 14 | FLWAY_DISALLOW_COPY(TypeName) \ 15 | FLWAY_DISALLOW_ASSIGN(TypeName) 16 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "utils.h" 11 | #include "debug.h" 12 | #include "wayland_display.h" 13 | 14 | static_assert(FLUTTER_ENGINE_VERSION == 1, ""); 15 | 16 | namespace flutter { 17 | 18 | static void PrintUsage() { 19 | std::cerr << "Flutter Wayland Embedder" << std::endl << std::endl; 20 | std::cerr << "========================" << std::endl; 21 | std::cerr << "Usage: `" << GetExecutableName() << " `" << std::endl << std::endl; 22 | std::cerr << R"~( 23 | This utility runs an instance of a Flutter application and renders using 24 | Wayland core protocols. 25 | 26 | The Flutter tools can be obtained at https://flutter.io/ 27 | 28 | asset_bundle_path: The Flutter application code needs to be snapshotted using 29 | the Flutter tools and the assets packaged in the appropriate 30 | location. This can be done for any Flutter application by 31 | running `flutter build bundle` while in the directory of a 32 | valid Flutter project. This should package all the code and 33 | assets in the "build/flutter_assets" directory. Specify this 34 | directory as the first argument to this utility. 35 | 36 | flutter_flags: Typically empty. These extra flags are passed directly to the 37 | Flutter engine. To see all supported flags, run 38 | `flutter_tester --help` using the test binary included in the 39 | Flutter tools. 40 | 41 | Supported environment variables: 42 | LANG= 43 | Value encoded as per setlocale(3) (e.g. "szl_PL.utf8") is 44 | passed to the engine via UpdateLocales(). 45 | 46 | See also: https://man7.org/linux/man-pages/man3/setlocale.3.html 47 | 48 | FLUTTER_WAYLAND_PIXEL_RATIO= 49 | Overwrites the pixel aspect ratio reported 50 | to the engine by FlutterEngineSendWindowMetricsEvent(). 51 | 52 | See also: https://api.flutter.dev/flutter/dart-ui/Window/devicePixelRatio.html 53 | 54 | FLUTTER_WAYLAND_MAIN_UI= 55 | Non-zero value enables grabbing all keys (even without having 56 | a focus) using Xwayland keyboard grabbing protocol (assuming 57 | server implement this extension). 58 | 59 | See also: https://github.com/wayland-project/wayland-protocols/tree/0a61d3516b10da4e65607a6dd97937ebedf6bcfa/unstable/xwayland-keyboard-grab 60 | 61 | FLUTTER_LAUNCHER_WAYLAND_DEBUG= 62 | where can be any of syslog(3) prioritynames or its 63 | unique abbreviation e.g. "err", "warning", "info" or "debug". 64 | 65 | FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH= 66 | if the app is run inside lxc container - path to where cgroup/memory memory.usage_in_bytes & cgroup.event_control are located 67 | necessary for memory watcher to work 68 | 69 | FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES= 70 | if FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH is defined, this specifies container memory usage levels at which the application will get 71 | FlutterEngineNotifyLowMemoryWarning notifications; the format is comma-separated memory (in bytes) values, like: 72 | "1000000,70000000,148478361,167038156,176318054" 73 | At least one memory level needs to be defined for memory watcher to work. After each notification, there is 20sec cooldown period, 74 | when additional FlutterEngineNotifyLowMemoryWarning notifications will not be called. 75 | )~" << std::endl; 76 | } 77 | 78 | static bool Main(std::vector args) { 79 | dbg_init(); 80 | 81 | if (args.size() == 1) { 82 | dbgE("Invalid list of arguments\n"); 83 | PrintUsage(); 84 | return false; 85 | } 86 | 87 | const auto asset_bundle_path = args[1]; 88 | 89 | if (!FlutterAssetBundleIsValid(asset_bundle_path)) { 90 | dbgE("Invalid Flutter Asset Bundle\n"); 91 | PrintUsage(); 92 | return false; 93 | } 94 | 95 | const size_t kWidth = 1920; 96 | const size_t kHeight = 1080; 97 | 98 | const std::vector flutter_args(args.begin() + 1, args.end()); 99 | 100 | for (const auto &arg : flutter_args) { 101 | dbgI("Flutter arg: %s\n", arg.c_str()); 102 | } 103 | 104 | WaylandDisplay display(kWidth, kHeight, asset_bundle_path, flutter_args); 105 | 106 | if (!display.IsValid()) { 107 | dbgI("Wayland display was not valid\n"); 108 | return false; 109 | } 110 | 111 | return display.Run(); 112 | } 113 | 114 | } // namespace flutter 115 | 116 | int main(int argc, char *argv[]) { 117 | std::vector args; 118 | for (int i = 0; i < argc; ++i) { 119 | args.push_back(argv[i]); 120 | } 121 | return flutter::Main(std::move(args)) ? EXIT_SUCCESS : EXIT_FAILURE; 122 | } 123 | -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "debug.h" 12 | #include "utils.h" 13 | 14 | namespace flutter { 15 | 16 | template <> std::string getEnv(const char *variable, std::string default_value) { 17 | if (variable == nullptr) { 18 | return default_value; 19 | } 20 | 21 | const char *str = getenv(variable); 22 | 23 | if (str == nullptr) { 24 | return default_value; 25 | } 26 | 27 | return std::string(str); 28 | } 29 | 30 | template <> double getEnv(char const *variable, double default_value) { 31 | std::string val = getEnv(variable, std::string("")); 32 | 33 | if (val.empty()) { 34 | return default_value; 35 | } 36 | 37 | return atof(val.c_str()); 38 | } 39 | 40 | std::string GetICUDataPath() { 41 | auto base_directory = GetExecutableDirectory(); 42 | if (base_directory == "") { 43 | base_directory = "."; 44 | } 45 | 46 | std::string data_directory = base_directory + "/data"; 47 | std::string icu_data_path = data_directory + "/icudtl.dat"; 48 | 49 | do { 50 | if (std::ifstream(icu_data_path)) { 51 | dbgI("Using %s\n", icu_data_path.c_str()); 52 | break; 53 | } 54 | 55 | icu_data_path = "/usr/share/flutter/icudtl.dat"; 56 | 57 | if (std::ifstream(icu_data_path)) { 58 | dbgI("Using: %s\n", icu_data_path.c_str()); 59 | break; 60 | } 61 | 62 | dbgE("Unnable to locate icudtl.dat file\n"); 63 | icu_data_path = ""; 64 | } while (0); 65 | 66 | return icu_data_path; 67 | } 68 | 69 | static std::string GetExecutablePath() { 70 | char executable_path[1024] = {0}; 71 | std::stringstream stream; 72 | stream << "/proc/" << getpid() << "/exe"; 73 | auto path = stream.str(); 74 | auto executable_path_size = ::readlink(path.c_str(), executable_path, sizeof(executable_path)); 75 | if (executable_path_size <= 0) { 76 | return ""; 77 | } 78 | return std::string{executable_path, static_cast(executable_path_size)}; 79 | } 80 | 81 | std::string GetExecutableName() { 82 | auto path_string = GetExecutablePath(); 83 | auto found = path_string.find_last_of('/'); 84 | if (found == std::string::npos) { 85 | return ""; 86 | } 87 | return path_string.substr(found + 1); 88 | } 89 | 90 | std::string GetExecutableDirectory() { 91 | auto path_string = GetExecutablePath(); 92 | auto found = path_string.find_last_of('/'); 93 | if (found == std::string::npos) { 94 | return ""; 95 | } 96 | return path_string.substr(0, found + 1); 97 | } 98 | 99 | bool FileExistsAtPath(const std::string &path) { 100 | return ::access(path.c_str(), R_OK) == 0; 101 | } 102 | 103 | bool FlutterAssetBundleIsValid(const std::string &bundle_path) { 104 | if (!FileExistsAtPath(bundle_path)) { 105 | dbgE("Bundle directory: %s does not exist\n", bundle_path.c_str()); 106 | return false; 107 | } 108 | 109 | auto kernel_path = bundle_path + std::string{"/kernel_blob.bin"}; 110 | auto aotelf_path = bundle_path + std::string{"/"} + FlutterGetAppAotElfName(); 111 | auto kernel = FileExistsAtPath(kernel_path); 112 | auto aotelf = FileExistsAtPath(aotelf_path); 113 | 114 | if (!(kernel || aotelf)) { 115 | dbgE("Could not found neither %s nor %s\n", kernel_path.c_str(), aotelf_path.c_str()); 116 | return false; 117 | } 118 | 119 | return true; 120 | } 121 | 122 | std::string FlutterGetAppAotElfName() { 123 | return std::string("../../lib/libapp.so"); // assumes 'flutter build' directory layout 124 | } 125 | 126 | bool FlutterSendMessage(FlutterEngine engine, const char *channel, const uint8_t *message, const size_t message_size) { 127 | FlutterPlatformMessageResponseHandle *response_handle = nullptr; 128 | 129 | FlutterPlatformMessage platform_message = { 130 | .struct_size = sizeof(FlutterPlatformMessage), 131 | .channel = channel, 132 | .message = message, 133 | .message_size = message_size, 134 | .response_handle = response_handle, 135 | }; 136 | 137 | FlutterEngineResult message_result = FlutterEngineSendPlatformMessage(engine, &platform_message); 138 | 139 | if (response_handle != nullptr) { 140 | FlutterPlatformMessageReleaseResponseHandle(engine, response_handle); 141 | } 142 | 143 | return message_result == kSuccess; 144 | } 145 | 146 | void FlutterParseLocale(const std::string &locale, MyFlutterLocale *fl) { 147 | if (locale.empty() || fl == nullptr) { 148 | return; 149 | } 150 | 151 | char *l = strdup(locale.c_str()); 152 | 153 | char *match = strrchr(l, '@'); 154 | if (match != nullptr) { 155 | fl->variant_code = strdup(match + 1); 156 | *match = '\0'; 157 | } 158 | 159 | match = strrchr(l, '.'); 160 | if (match != nullptr) { 161 | fl->script_code = strdup(match + 1); 162 | *match = '\0'; 163 | } 164 | 165 | match = strrchr(l, '_'); 166 | if (match != nullptr) { 167 | fl->country_code = strdup(match + 1); 168 | *match = '\0'; 169 | } 170 | 171 | fl->language_code = l; 172 | } 173 | 174 | } // namespace flutter 175 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Copyright 2019 Damian Wrobel 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include "macros.h" 11 | 12 | namespace flutter { 13 | 14 | constexpr auto NSEC_PER_SEC = 1'000'000'000; 15 | 16 | std::string GetICUDataPath(); 17 | 18 | std::string GetExecutableName(); 19 | 20 | std::string GetExecutableDirectory(); 21 | 22 | bool FileExistsAtPath(const std::string &path); 23 | 24 | bool FlutterAssetBundleIsValid(const std::string &bundle_path); 25 | 26 | std::string FlutterGetAppAotElfName(); 27 | 28 | bool FlutterSendMessage(FlutterEngine engine, const char *channel, const uint8_t *message, const size_t message_size); 29 | 30 | struct MyFlutterLocale : public FlutterLocale { 31 | MyFlutterLocale() { 32 | struct_size = sizeof(FlutterLocale); 33 | language_code = script_code = country_code = variant_code = nullptr; 34 | } 35 | 36 | ~MyFlutterLocale() { 37 | free((void *)language_code), language_code = nullptr; 38 | free((void *)country_code), country_code = nullptr; 39 | free((void *)script_code), script_code = nullptr; 40 | free((void *)variant_code), variant_code = nullptr; 41 | } 42 | FLWAY_DISALLOW_COPY_AND_ASSIGN(MyFlutterLocale) 43 | }; 44 | 45 | void FlutterParseLocale(const std::string &locale, MyFlutterLocale *fl); 46 | 47 | template T getEnv(const char *variable, T default_value); 48 | 49 | static inline void timespec_from_nsec(struct timespec *a, int64_t b) { 50 | a->tv_sec = b / NSEC_PER_SEC; 51 | a->tv_nsec = b % NSEC_PER_SEC; 52 | } 53 | 54 | static inline void timespec_from_msec(struct timespec *a, int64_t b) { 55 | timespec_from_nsec(a, b * 1'000'000); 56 | } 57 | 58 | } // namespace flutter 59 | -------------------------------------------------------------------------------- /src/wayland_display.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Copyright 2019 Damian Wrobel 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | #ifndef WL_EGL_PLATFORM 7 | #define WL_EGL_PLATFORM 1 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "debug.h" 32 | #include "elf.h" 33 | #include "keys.h" 34 | #include "utils.h" 35 | #include "egl_utils.h" 36 | #include "wayland_display.h" 37 | 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #define DBG_TIMING(x) 47 | 48 | #define MEMWATCHTAG "memwatcher: " 49 | 50 | DBG_TIMING( 51 | #define gettid() syscall(SYS_gettid) 52 | static uint64_t t00;) 53 | 54 | namespace flutter { 55 | 56 | static double get_pixel_ratio(int32_t physical_width, int32_t physical_height, int32_t pixels_width, int32_t pixels_height) { 57 | 58 | if (pixels_width == 0 || physical_height == 0 || pixels_width == 0 || pixels_height == 0) { 59 | return 1.0; 60 | } 61 | 62 | return getEnv("FLUTTER_WAYLAND_PIXEL_RATIO", 1.0); 63 | } 64 | 65 | static inline WaylandDisplay *get_wayland_display(void *data, const bool check_non_null = true) { 66 | WaylandDisplay *const wd = static_cast(data); 67 | 68 | if (check_non_null) { 69 | assert(wd); 70 | if (wd == nullptr) 71 | abort(); 72 | } 73 | 74 | return wd; 75 | } 76 | 77 | const wl_registry_listener WaylandDisplay::kRegistryListener = { 78 | .global = [](void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) -> void { 79 | WaylandDisplay *const wd = get_wayland_display(data); 80 | 81 | dbgI("registry: name:%2u, interface:%s, version:%u\n", name, interface, version); 82 | 83 | if (strcmp(interface, "wl_compositor") == 0) { 84 | wd->compositor_ = static_cast(wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); 85 | return; 86 | } 87 | 88 | if (strcmp(interface, "wl_shell") == 0) { 89 | wd->shell_ = static_cast(wl_registry_bind(wl_registry, name, &wl_shell_interface, 1)); 90 | return; 91 | } 92 | 93 | if (strcmp(interface, "wl_seat") == 0) { 94 | wd->seat_ = static_cast(wl_registry_bind(wl_registry, name, &wl_seat_interface, 4)); 95 | wl_seat_add_listener(wd->seat_, &kSeatListener, wd); 96 | return; 97 | } 98 | 99 | if (strcmp(interface, "wl_output") == 0) { 100 | wd->output_ = static_cast(wl_registry_bind(wl_registry, name, &wl_output_interface, 1)); 101 | wl_output_add_listener(wd->output_, &kOutputListener, wd); 102 | return; 103 | } 104 | 105 | if (strcmp(interface, wp_presentation_interface.name) == 0) { 106 | wd->presentation_ = static_cast(wl_registry_bind(wl_registry, name, &wp_presentation_interface, 1)); 107 | wp_presentation_add_listener(wd->presentation_, &kPresentationListener, wd); 108 | return; 109 | } 110 | 111 | if (strcmp(interface, zwp_xwayland_keyboard_grab_manager_v1_interface.name) == 0) { 112 | wd->kbd_grab_manager_ = static_cast(wl_registry_bind(wl_registry, name, &zwp_xwayland_keyboard_grab_manager_v1_interface, 1)); 113 | return; 114 | } 115 | }, 116 | 117 | .global_remove = [](void *data, struct wl_registry *wl_registry, uint32_t name) -> void { 118 | 119 | }, 120 | }; 121 | 122 | const wl_shell_surface_listener WaylandDisplay::kShellSurfaceListener = { 123 | .ping = [](void *data, struct wl_shell_surface *wl_shell_surface, uint32_t serial) -> void { 124 | WaylandDisplay *const wd = get_wayland_display(data); 125 | 126 | wl_shell_surface_pong(wd->shell_surface_, serial); 127 | }, 128 | 129 | .configure = [](void *data, struct wl_shell_surface *wl_shell_surface, uint32_t edges, int32_t width, int32_t height) -> void { 130 | WaylandDisplay *const wd = get_wayland_display(data, false); 131 | 132 | if (wd == nullptr) 133 | return; 134 | 135 | if (wd->window_ == nullptr) 136 | return; 137 | 138 | wl_egl_window_resize(wd->window_, wd->screen_width_ = width, wd->screen_height_ = height, 0, 0); 139 | 140 | FlutterWindowMetricsEvent event = {}; 141 | event.struct_size = sizeof(event); 142 | event.width = wd->screen_width_; 143 | event.height = wd->screen_height_; 144 | event.pixel_ratio = get_pixel_ratio(wd->physical_width_, wd->physical_height_, wd->screen_width_, wd->screen_height_); 145 | 146 | auto success = FlutterEngineSendWindowMetricsEvent(wd->engine_, &event) == kSuccess; 147 | 148 | dbgI("shell.configure: %zdx%zd par: %.3g status: %s\n", event.width, event.height, event.pixel_ratio, (success ? "success" : "failed")); 149 | }, 150 | 151 | .popup_done = [](void *data, struct wl_shell_surface *wl_shell_surface) -> void { 152 | // Nothing to do. 153 | }, 154 | }; 155 | 156 | const wl_pointer_listener WaylandDisplay::kPointerListener = { 157 | .enter = [](void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {}, 158 | 159 | .leave = 160 | [](void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { 161 | WaylandDisplay *const wd = get_wayland_display(data); 162 | 163 | wd->key_modifiers = static_cast(0); 164 | }, 165 | 166 | .motion = 167 | [](void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { 168 | WaylandDisplay *const wd = get_wayland_display(data); 169 | 170 | // just store raw values 171 | wd->surface_x = surface_x; 172 | wd->surface_y = surface_y; 173 | }, 174 | 175 | .button = 176 | [](void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { 177 | WaylandDisplay *const wd = get_wayland_display(data); 178 | 179 | uint32_t button_number = button - BTN_LEFT; 180 | button_number = button_number == 1 ? 2 : button_number == 2 ? 1 : button_number; 181 | 182 | FlutterPointerEvent event = { 183 | .struct_size = sizeof(event), 184 | .phase = state == WL_POINTER_BUTTON_STATE_PRESSED ? FlutterPointerPhase::kDown : FlutterPointerPhase::kUp, 185 | .timestamp = time * 1'000, 186 | .x = wl_fixed_to_double(wd->surface_x), 187 | .y = wl_fixed_to_double(wd->surface_y), 188 | .device = 0, 189 | .signal_kind = kFlutterPointerSignalKindNone, 190 | .scroll_delta_x = 0, 191 | .scroll_delta_y = 0, 192 | .device_kind = static_cast(0), // dw: TODO: Why kFlutterPointerDeviceKindMouse does not work? 193 | .buttons = 0, 194 | }; 195 | 196 | FlutterEngineSendPointerEvent(wd->engine_, &event, 1); 197 | }, 198 | 199 | .axis = [](void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {}, 200 | 201 | .frame = [](void *data, struct wl_pointer *wl_pointer) {}, 202 | 203 | .axis_source = [](void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {}, 204 | 205 | .axis_stop = [](void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {}, 206 | 207 | .axis_discrete = [](void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {}, 208 | 209 | }; 210 | 211 | const wl_keyboard_listener WaylandDisplay::kKeyboardListener = { 212 | .keymap = 213 | [](void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { 214 | WaylandDisplay *const wd = get_wayland_display(data); 215 | 216 | wd->keymap_format = static_cast(format); 217 | char *const keymap_string = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0)); 218 | xkb_keymap_unref(wd->keymap); 219 | wd->keymap = xkb_keymap_new_from_string(wd->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); 220 | munmap(keymap_string, size); 221 | close(fd); 222 | xkb_state_unref(wd->xkb_state); 223 | wd->xkb_state = xkb_state_new(wd->keymap); 224 | }, 225 | 226 | .enter = [](void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { dbgI("key: keyboard enter\n"); }, 227 | 228 | .leave = [](void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { dbgI("key: keyboard leave\n"); }, 229 | 230 | .key = 231 | [](void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { 232 | WaylandDisplay *const wd = get_wayland_display(data); 233 | 234 | uint32_t repeat_delay_ms_ = 0; 235 | uint32_t repeat_interval_ms = 0; 236 | 237 | if (wd->key_handler(wd->key.last_ = key, wd->key.state_ = state_w)) { 238 | repeat_delay_ms_ = wd->key.repeat_delay_ms_; 239 | repeat_interval_ms = wd->key.repeat_interval_ms_; 240 | } 241 | 242 | struct itimerspec ts; 243 | timespec_from_msec(&ts.it_value, repeat_delay_ms_); 244 | timespec_from_msec(&ts.it_interval, repeat_interval_ms); 245 | const int rv = timerfd_settime(wd->key.timer_fd_, 0, &ts, nullptr); 246 | 247 | if (rv == -1) { 248 | dbgE("timerfd_settime returned: %d (errno: %d)\n", rv, errno); 249 | } 250 | }, 251 | 252 | .modifiers = 253 | [](void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { 254 | WaylandDisplay *const wd = get_wayland_display(data); 255 | 256 | xkb_state_update_mask(wd->xkb_state, mods_depressed, mods_latched, mods_locked, group, 0, 0); 257 | }, 258 | 259 | .repeat_info = 260 | [](void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { 261 | WaylandDisplay *const wd = get_wayland_display(data); 262 | 263 | if (rate > 0) { 264 | wd->key.repeat_interval_ms_ = 1'000 / rate; 265 | } 266 | 267 | if (delay > 0) { 268 | wd->key.repeat_delay_ms_ = delay; 269 | } 270 | 271 | dbgI("key: repeat_info delay:%d, rate:%d -> delay:%d[ms], repeat-interval:%d[ms]\n", delay, rate, wd->key.repeat_delay_ms_, wd->key.repeat_interval_ms_); 272 | }, 273 | }; 274 | 275 | const wl_seat_listener WaylandDisplay::kSeatListener = { 276 | .capabilities = 277 | [](void *data, struct wl_seat *seat, uint32_t capabilities) { 278 | WaylandDisplay *const wd = get_wayland_display(data); 279 | assert(seat == wd->seat_); 280 | 281 | dbgI("seat.capabilities(data:%p, seat:%p, capabilities:0x%x)\n", data, static_cast(seat), capabilities); 282 | 283 | if (capabilities & WL_SEAT_CAPABILITY_POINTER) { 284 | dbgI("seat.capabilities: pointer\n"); 285 | struct wl_pointer *pointer = wl_seat_get_pointer(seat); 286 | wl_pointer_add_listener(pointer, &kPointerListener, wd); 287 | } 288 | 289 | if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { 290 | dbgI("seat.capabilities: keyboard\n"); 291 | struct wl_keyboard *keyboard = wl_seat_get_keyboard(seat); 292 | wl_keyboard_add_listener(keyboard, &kKeyboardListener, wd); 293 | } 294 | 295 | if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { 296 | dbgI("seat.capabilities: touch\n"); 297 | } 298 | }, 299 | 300 | .name = 301 | [](void *data, struct wl_seat *wl_seat, const char *name) { 302 | // Nothing to do. 303 | }, 304 | }; 305 | 306 | const wl_output_listener WaylandDisplay::kOutputListener = { 307 | .geometry = 308 | [](void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { 309 | WaylandDisplay *const wd = get_wayland_display(data); 310 | 311 | wd->physical_width_ = physical_width; 312 | wd->physical_height_ = physical_height; 313 | 314 | dbgI("output.geometry(data:%p, wl_output:%p, x:%d, y:%d, physical_width:%d, physical_height:%d, subpixel:%d, make:%s, model:%s, transform:%d)\n", data, static_cast(wl_output), x, y, physical_width, physical_height, 315 | subpixel, make, model, transform); 316 | }, 317 | .mode = 318 | [](void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { 319 | WaylandDisplay *const wd = get_wayland_display(data); 320 | 321 | wd->vsync.vblank_time_ns_ = 1'000'000'000'000 / refresh; 322 | 323 | dbgI("output.mode(data:%p, wl_output:%p, flags:%d, width:%d->%d, height:%d->%d, refresh:%d)\n", data, static_cast(wl_output), flags, wd->screen_width_, width, wd->screen_height_, height, refresh); 324 | 325 | if (wd->engine_) { 326 | FlutterWindowMetricsEvent event = {}; 327 | event.struct_size = sizeof(event); 328 | event.width = (wd->screen_width_ = width); 329 | event.height = (wd->screen_height_ = height); 330 | event.pixel_ratio = get_pixel_ratio(wd->physical_width_, wd->physical_height_, wd->screen_width_, wd->screen_height_); 331 | 332 | auto success = FlutterEngineSendWindowMetricsEvent(wd->engine_, &event) == kSuccess; 333 | 334 | wl_egl_window_resize(wd->window_, wd->screen_width_, wd->screen_height_, 0, 0); 335 | 336 | dbgI("Window resized: %zdx%zd par: %.3g status: %s.\n", event.width, event.height, event.pixel_ratio, (success ? "success" : "failed")); 337 | } else { 338 | wd->window_metrix_skipped_ = true; 339 | dbgI("Window resized: %dx%d status: %s.\n", wd->screen_width_, wd->screen_width_, "skipped"); 340 | } 341 | }, 342 | .done = [](void *data, struct wl_output *wl_output) { printf("output.done(data:%p, wl_output:%p)\n", data, static_cast(wl_output)); }, 343 | .scale = [](void *data, struct wl_output *wl_output, int32_t factor) { printf("output.scale(data:%p, wl_output:%p, factor:%d)\n", data, static_cast(wl_output), factor); }, 344 | }; 345 | 346 | const struct wp_presentation_feedback_listener WaylandDisplay::kPresentationFeedbackListener = { 347 | .sync_output = [](void *data, struct wp_presentation_feedback *wp_presentation_feedback, struct wl_output *output) {}, 348 | .presented = 349 | [](void *data, struct wp_presentation_feedback *wp_presentation_feedback, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { 350 | WaylandDisplay *const wd = get_wayland_display(data); 351 | 352 | const uint64_t new_last_frame_ns = (((static_cast(tv_sec_hi) << 32) + tv_sec_lo) * 1'000'000'000) + tv_nsec; 353 | 354 | if (refresh != wd->vsync.vblank_time_ns_) { 355 | static auto displayed = false; 356 | 357 | if (!displayed) { 358 | dbgW("Variable display rate output: vblank_time_ns: %ju refresh: %u\n", wd->vsync.vblank_time_ns_, refresh); 359 | displayed = true; 360 | } 361 | } 362 | 363 | DBG_TIMING({ 364 | static auto t0 = FlutterEngineGetCurrentTime(); 365 | const auto t1 = FlutterEngineGetCurrentTime(); 366 | // dbgI("[%09.4f][%ld] presented: %09lldns flags:%08x p2p-diff:%5lldus rerfresh:%u\n", (t1-t00)/1e9, gettid(), (new_last_frame_ns-t00), flags, (new_last_frame_ns - wd->vsync.last_frame_)/1000, refresh); 367 | dbgI("[%09.4f][%ld] presented %09.4f f:%08x dc:%.5f df:%.5f r:%u\n", (t1 - t00) / 1e9, gettid(), (new_last_frame_ns - t00) / 1e9, flags, (t1 - t0) / 1e9, (new_last_frame_ns - wd->vsync.last_frame_) / 1e9, refresh); 368 | t0 = t1; 369 | }) 370 | 371 | wd->vsync.last_frame_ = new_last_frame_ns; 372 | }, 373 | .discarded = 374 | [](void *data, struct wp_presentation_feedback *wp_presentation_feedback) { 375 | // TODO: remove it 376 | dbgW("presentation.frame dropped\n"); 377 | }, 378 | }; // namespace flutter 379 | 380 | const struct wp_presentation_listener WaylandDisplay::kPresentationListener = { 381 | .clock_id = 382 | [](void *data, struct wp_presentation *wp_presentation, uint32_t clk_id) { 383 | WaylandDisplay *const wd = get_wayland_display(data); 384 | 385 | wd->vsync.presentation_clk_id_ = clk_id; 386 | 387 | dbgI("presentation.clk_id: %u\n", clk_id); 388 | }, 389 | }; 390 | 391 | bool WaylandDisplay::key_handler(const uint32_t key, const uint32_t state_w, const bool is_repeat) { 392 | if (keymap_format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { 393 | dbgW("key: Hmm - no keymap, no key event\n"); 394 | return false; 395 | } 396 | 397 | const xkb_keycode_t hardware_keycode = key + (keymap_format * 8); 398 | const xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, hardware_keycode); 399 | 400 | if (keysym == XKB_KEY_NoSymbol) { 401 | dbgW("key: Hmm - no key symbol, no key event\n"); 402 | return false; 403 | } 404 | 405 | xkb_mod_mask_t mods = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_EFFECTIVE); 406 | key_modifiers = toGDKModifiers(keymap, mods); 407 | 408 | // Remove lock states from state mask. 409 | guint state = key_modifiers & ~(GDK_LOCK_MASK | GDK_MOD2_MASK); 410 | 411 | static bool shift_lock_pressed = false; 412 | static bool caps_lock_pressed = false; 413 | static bool num_lock_pressed = false; 414 | 415 | const GdkEventType type = state_w == WL_KEYBOARD_KEY_STATE_PRESSED ? GDK_KEY_PRESS : GDK_KEY_RELEASE; 416 | 417 | switch (keysym) { 418 | case GDK_KEY_Num_Lock: 419 | num_lock_pressed = type == GDK_KEY_PRESS; 420 | break; 421 | case GDK_KEY_Caps_Lock: 422 | caps_lock_pressed = type == GDK_KEY_PRESS; 423 | break; 424 | case GDK_KEY_Shift_Lock: 425 | shift_lock_pressed = type == GDK_KEY_PRESS; 426 | break; 427 | } 428 | 429 | // Add back in the state matching the actual pressed state of the lock keys, 430 | // not the lock states. 431 | state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0; 432 | state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0; 433 | 434 | const uint32_t utf32 = xkb_keysym_to_utf32(keysym); // TODO: double check if it fully mimics gdk_keyval_to_unicode() 435 | 436 | if (utf32) { 437 | if (utf32 >= 0x21 && utf32 <= 0x7E) { 438 | dbgT("key: %c %s%s\n", (char)utf32, type == GDK_KEY_PRESS ? "pressed" : "released", is_repeat ? " [r]" : ""); 439 | } else { 440 | dbgT("key: U+%04X %s%s\n", utf32, type == GDK_KEY_PRESS ? "pressed" : "released", is_repeat ? " [r]" : ""); 441 | } 442 | } else { 443 | char name[64]; 444 | xkb_keysym_get_name(keysym, name, sizeof(name)); 445 | 446 | dbgT("key: %s %s%s\n", name, type == GDK_KEY_PRESS ? "pressed" : "released", is_repeat ? " [r]" : ""); 447 | } 448 | 449 | std::string message; 450 | 451 | // dw: if you do not like so many backslashes, 452 | // please consider to rerwite it using RapidJson. 453 | message += "{"; 454 | message += " \"type\":" + std::string(type == GDK_KEY_PRESS ? "\"keydown\"" : "\"keyup\""); 455 | message += ",\"keymap\":" + std::string("\"linux\""); 456 | message += ",\"scanCode\":" + std::to_string(hardware_keycode); 457 | message += ",\"toolkit\":" + std::string("\"gtk\""); 458 | message += ",\"keyCode\":" + std::to_string(keysym); 459 | message += ",\"modifiers\":" + std::to_string(state); 460 | if (utf32) { 461 | message += ",\"unicodeScalarValues\":" + std::to_string(utf32); 462 | } 463 | 464 | message += "}"; 465 | 466 | if (!message.empty()) { 467 | bool success = FlutterSendMessage(engine_, "flutter/keyevent", reinterpret_cast(message.c_str()), message.size()); 468 | 469 | if (!success) { 470 | dbgE("Error sending PlatformMessage: %s\n", message.c_str()); 471 | } 472 | } 473 | 474 | return xkb_keymap_key_repeats(keymap, hardware_keycode) && type == GDK_KEY_PRESS; 475 | } 476 | 477 | WaylandDisplay::WaylandDisplay(size_t width, size_t height, const std::string &bundle_path, const std::vector &command_line_args) 478 | : xkb_context(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) 479 | , screen_width_(width) 480 | , screen_height_(height) { 481 | if (screen_width_ == 0 || screen_height_ == 0) { 482 | dbgE("Invalid screen dimensions\n"); 483 | return; 484 | } 485 | 486 | if (socketpair(AF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0, &vsync.sv_[0]) == -1) { 487 | dbgE("socketpair() failed, errno: %d\n", errno); 488 | return; 489 | } 490 | 491 | key.timer_fd_ = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); 492 | 493 | if (key.timer_fd_ < 0) { 494 | dbgE("Could not create timer\n"); 495 | return; 496 | } 497 | 498 | display_ = wl_display_connect(nullptr); 499 | 500 | if (!display_) { 501 | dbgE("Could not connect to the wayland display\n"); 502 | return; 503 | } 504 | 505 | registry_ = wl_display_get_registry(display_); 506 | 507 | if (!registry_) { 508 | dbgE("Could not get the wayland registry\n"); 509 | return; 510 | } 511 | 512 | wl_registry_add_listener(registry_, &kRegistryListener, this); 513 | 514 | wl_display_roundtrip(display_); 515 | 516 | if (!SetupEGL()) { 517 | dbgE("Could not setup EGL.\n"); 518 | return; 519 | } 520 | 521 | if (!SetupEngine(bundle_path, command_line_args)) { 522 | dbgE("Could not setup Flutter Engine\n"); 523 | return; 524 | } 525 | 526 | DBG_TIMING({ t00 = FlutterEngineGetCurrentTime(); }); 527 | valid_ = true; 528 | } 529 | 530 | bool WaylandDisplay::SetupEngine(const std::string &bundle_path, const std::vector &command_line_args) { 531 | FlutterRendererConfig config = {}; 532 | config.type = kOpenGL; 533 | config.open_gl.struct_size = sizeof(config.open_gl); 534 | config.open_gl.make_current = [](void *data) -> bool { 535 | WaylandDisplay *const wd = get_wayland_display(data); 536 | 537 | if (eglMakeCurrent(wd->egl_display_, wd->egl_surface_, wd->egl_surface_, wd->egl_context_) != EGL_TRUE) { 538 | LogLastEGLError(); 539 | dbgE("Could not make the onscreen context current\n"); 540 | return false; 541 | } 542 | 543 | return true; 544 | }; 545 | config.open_gl.clear_current = [](void *data) -> bool { 546 | WaylandDisplay *const wd = get_wayland_display(data); 547 | 548 | if (eglMakeCurrent(wd->egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { 549 | LogLastEGLError(); 550 | dbgE("Could not clear the context\n"); 551 | return false; 552 | } 553 | 554 | return true; 555 | }; 556 | config.open_gl.present = [](void *data) -> bool { 557 | WaylandDisplay *const wd = get_wayland_display(data); 558 | 559 | DBG_TIMING(static auto tprev = FlutterEngineGetCurrentTime(); auto tb = FlutterEngineGetCurrentTime(); dbgI("[%09.4f][%ld] >>> swap buffer [%09.4f]\n", (tb - t00) / 1e9, gettid(), (tb - tprev) / 1e9);); 560 | 561 | if (eglSwapBuffers(wd->egl_display_, wd->egl_surface_) != EGL_TRUE) { 562 | LogLastEGLError(); 563 | dbgE("Could not swap the EGL buffer\n"); 564 | return false; 565 | } 566 | 567 | DBG_TIMING(auto ta = FlutterEngineGetCurrentTime(); dbgI("[%09.4f][%ld] <<< swap buffer [dur:%09.4f]\n", (ta - t00) / 1e9, gettid(), (ta - tb) / 1e9); tprev = tb;); 568 | 569 | return true; 570 | }; 571 | config.open_gl.fbo_callback = [](void *data) -> uint32_t { return 0; }; 572 | config.open_gl.make_resource_current = [](void *data) -> bool { 573 | WaylandDisplay *const wd = get_wayland_display(data); 574 | 575 | if (eglMakeCurrent(wd->egl_display_, wd->resource_egl_surface_, wd->resource_egl_surface_, wd->resource_egl_context_) != EGL_TRUE) { 576 | LogLastEGLError(); 577 | dbgE("Could not make the RESOURCE context current\n"); 578 | return false; 579 | } 580 | 581 | return true; 582 | }; 583 | 584 | config.open_gl.gl_proc_resolver = [](void *data, const char *name) -> void * { 585 | auto address = eglGetProcAddress(name); 586 | if (address != nullptr) { 587 | return reinterpret_cast(address); 588 | } 589 | 590 | dbgW("Using dlsym fallback to resolve: %s\n.", name ? name : ""); 591 | 592 | address = reinterpret_cast(dlsym(RTLD_DEFAULT, name)); 593 | 594 | if (address != nullptr) { 595 | return reinterpret_cast(address); 596 | } 597 | 598 | dbgW("Tried unsuccessfully to resolve: %s\n.", name ? name : ""); 599 | return nullptr; 600 | }; 601 | 602 | auto icu_data_path = GetICUDataPath(); 603 | 604 | if (icu_data_path == "") { 605 | return false; 606 | } 607 | 608 | std::vector command_line_args_c; 609 | 610 | for (const auto &arg : command_line_args) { 611 | command_line_args_c.push_back(arg.c_str()); 612 | } 613 | 614 | FlutterProjectArgs args = { 615 | .struct_size = sizeof(FlutterProjectArgs), 616 | .assets_path = bundle_path.c_str(), 617 | .icu_data_path = icu_data_path.c_str(), 618 | .command_line_argc = static_cast(command_line_args_c.size()), 619 | .command_line_argv = command_line_args_c.data(), 620 | .platform_message_callback = [](const FlutterPlatformMessage *message, void *data) -> void { 621 | if (std::string(message->channel) == "flutter/platform" && std::string((char *)message->message, message->message_size).find("\"method\":\"freeMemory\"") != std::string::npos) { 622 | WaylandDisplay *const wd = get_wayland_display(data); 623 | if (wd->engine_) { 624 | auto ret = FlutterEngineNotifyLowMemoryWarning(wd->engine_); 625 | if (ret != kSuccess) { 626 | dbgE("Could not handle platform message request\n"); 627 | } 628 | FlutterEngineSendPlatformMessageResponse(wd->engine_, message->response_handle, nullptr, 0); 629 | } 630 | } 631 | }, 632 | .vsync_callback = [](void *data, intptr_t baton) -> void { 633 | WaylandDisplay *const wd = get_wayland_display(data); 634 | 635 | DBG_TIMING({ 636 | auto tx = FlutterEngineGetCurrentTime(); 637 | dbgI("[%09.4f][%ld] vsync callback [%jx]\n", (tx - t00) / 1e9, gettid(), static_cast(baton)); 638 | }); 639 | 640 | if (wd->vsync.baton_ != 0) { 641 | dbgE("vsync.wait: New baton arrived, but old was not sent\n"); 642 | exit(1); 643 | } 644 | 645 | wd->vsync.baton_ = baton; 646 | 647 | if (wd->vSyncSendNotifyData() != 1) { 648 | exit(1); 649 | } 650 | }, 651 | .compute_platform_resolved_locale_callback = [](const FlutterLocale **supported_locales, size_t number_of_locales) -> const FlutterLocale * { 652 | dbgI("locale.resolved.callback: number_of_locales: %zu\n", number_of_locales); 653 | 654 | if (number_of_locales > 0) { 655 | const FlutterLocale *fl = supported_locales[0]; 656 | dbgI("locale.resolved: %s_%s.%s@%s\n", fl->language_code ? fl->language_code : "", fl->country_code ? fl->country_code : "", fl->script_code ? fl->script_code : "", fl->variant_code ? fl->variant_code : ""); 657 | return fl; 658 | } 659 | 660 | return nullptr; 661 | }, 662 | }; 663 | 664 | // configure platform task runner; render tasks runner will still be provided by flutter 665 | FlutterTaskRunnerDescription platform_task_runner = {}; 666 | FlutterCustomTaskRunners task_runners = {}; 667 | task_runners.struct_size = sizeof(FlutterCustomTaskRunners); 668 | task_runners.platform_task_runner = &platform_task_runner; 669 | if (ConfigurePlatformTaskRunner(&platform_task_runner)) { 670 | args.custom_task_runners = &task_runners; 671 | } else { 672 | dbgE("Couldn't configure platform task runner\n"); 673 | exit(1); 674 | } 675 | 676 | std::string libapp_aot_path = bundle_path + "/" + FlutterGetAppAotElfName(); // dw: TODO: There seems to be no convention name we could use, so let's temporary hardcode the path. 677 | 678 | if (FlutterEngineRunsAOTCompiledDartCode()) { 679 | dbgI("Using AOT precompiled runtime\n"); 680 | 681 | if (std::ifstream(libapp_aot_path)) { 682 | dbgI("Loading AOT snapshot: %s\n", libapp_aot_path.c_str()); 683 | 684 | const char *error; 685 | auto handle = Aot_LoadELF(libapp_aot_path.c_str(), 0, &error, &args.vm_snapshot_data, &args.vm_snapshot_instructions, &args.isolate_snapshot_data, &args.isolate_snapshot_instructions); 686 | 687 | if (!handle) { 688 | dbgE("Could not load AOT library: %s (error: %s).\n", libapp_aot_path.c_str(), error ? error : ""); 689 | return false; 690 | } 691 | } 692 | } 693 | 694 | auto result = FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &config, &args, this /* userdata */, &engine_); 695 | 696 | if (result != kSuccess) { 697 | dbgE("Could not initialize the Flutter engine.\n"); 698 | return false; 699 | } 700 | 701 | result = FlutterEngineRunInitialized(engine_); 702 | 703 | if (result != kSuccess) { 704 | dbgE("Could not run the Flutter engine.\n"); 705 | return false; 706 | } 707 | 708 | { 709 | MyFlutterLocale fl; 710 | std::string lang = getEnv("LANG", std::string("")); 711 | FlutterParseLocale(lang, &fl); 712 | 713 | if (fl.language_code != nullptr) { 714 | dbgI("locale.parsed: %s_%s.%s@%s\n", fl.language_code ? fl.language_code : "", fl.country_code ? fl.country_code : "", fl.script_code ? fl.script_code : "", fl.variant_code ? fl.variant_code : ""); 715 | const FlutterLocale *flutter_locales[] = {&fl}; 716 | 717 | const auto rv = FlutterEngineUpdateLocales(engine_, flutter_locales, std::size(flutter_locales)); 718 | 719 | if (rv != kSuccess) { 720 | dbgE("Could not update locales for the Flutter engine\n"); 721 | return false; 722 | } 723 | } 724 | } 725 | 726 | SetupMemoryWatcher(); 727 | 728 | if (window_metrix_skipped_) { 729 | FlutterWindowMetricsEvent event = {}; 730 | 731 | event.struct_size = sizeof(event); 732 | event.width = screen_width_; 733 | event.height = screen_height_; 734 | event.pixel_ratio = get_pixel_ratio(physical_width_, physical_height_, screen_width_, screen_height_); 735 | 736 | const auto success = FlutterEngineSendWindowMetricsEvent(engine_, &event) == kSuccess; 737 | 738 | dbgI("Window metric: %zdx%zd par: %.3g status: %s\n", event.width, event.height, event.pixel_ratio, (success ? "success" : "failed")); 739 | 740 | return success; 741 | } 742 | 743 | return true; 744 | } 745 | 746 | void WaylandDisplay::HandleMemoryWatcherEvent() { 747 | // minimum 20 sec between FlutterEngineNotifyLowMemoryWarning calls 748 | constexpr auto min_time_between_warnings_ns_ = uint64_t(2e10); 749 | const uint64_t now_ns = FlutterEngineGetCurrentTime(); 750 | constexpr int readsz = 32; 751 | 752 | if (now_ns < memory_watcher_.last_warning_sent_ns + min_time_between_warnings_ns_) { 753 | return; 754 | } 755 | 756 | lseek(memory_watcher_.memory_file_fd, 0, SEEK_SET); 757 | 758 | char memstr[readsz]; 759 | int pos = 0; 760 | int rv = 0; 761 | do { 762 | rv = read(memory_watcher_.memory_file_fd, memstr + pos, readsz - 1 - pos); 763 | if (rv != -1) 764 | pos += rv; 765 | } while ((rv == -1 && errno == EINTR) || rv > 0); 766 | memstr[pos] = '\0'; 767 | if (rv == -1) { 768 | dbgW(MEMWATCHTAG "problem reading memory_usage, errno:%d\n", errno); 769 | return; 770 | } 771 | 772 | dbgT(MEMWATCHTAG "current mem: %s\n", memstr); 773 | const long mem = atol(memstr); 774 | if (mem > 0) { 775 | long level = 0; 776 | for (auto memwatchlevel : memory_watcher_.levels) { 777 | if (memwatchlevel > mem) 778 | break; 779 | else 780 | ++level; 781 | } 782 | dbgT(MEMWATCHTAG "current memory level: %ld\n", level); 783 | if (level > memory_watcher_.current_level) { 784 | dbgW(MEMWATCHTAG "sending FlutterEngineNotifyLowMemoryWarning\n"); 785 | auto ret = FlutterEngineNotifyLowMemoryWarning(engine_); 786 | if (ret != kSuccess) { 787 | dbgE(MEMWATCHTAG "FlutterEngineNotifyLowMemoryWarning failed with %d\n", int(ret)); 788 | return; 789 | } 790 | memory_watcher_.last_warning_sent_ns = now_ns; 791 | } 792 | memory_watcher_.current_level = level; 793 | } 794 | } 795 | 796 | void WaylandDisplay::CleanupMemoryWatcher() { 797 | if (memory_watcher_.memory_file_fd != -1) { 798 | close(memory_watcher_.memory_file_fd); 799 | memory_watcher_.memory_file_fd = -1; 800 | } 801 | if (memory_watcher_.event_fd != -1) { 802 | close(memory_watcher_.event_fd); 803 | memory_watcher_.event_fd = -1; 804 | } 805 | } 806 | 807 | void WaylandDisplay::SetupMemoryWatcher() { 808 | const std::string cgroup_memory_path = getEnv("FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH", std::string()); 809 | if (cgroup_memory_path.empty()) { 810 | dbgI(MEMWATCHTAG "Memory watcher will not run - no FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH env var defined\n"); 811 | } else { 812 | dbgT("SetupMemoryWatcher start, cgroup_memory_path: %s\n", cgroup_memory_path.c_str()); 813 | const std::string memory_usage_path = cgroup_memory_path + "/memory.usage_in_bytes"; 814 | const std::string cgroup_event_control_path = cgroup_memory_path + "/cgroup.event_control"; 815 | const std::string memoryWarningLevels = getEnv("FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES", std::string()); 816 | if (memoryWarningLevels.empty()) { 817 | dbgI(MEMWATCHTAG "Memory watcher will not run - no FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES env var defined\n"); 818 | } else { 819 | std::stringstream ss{memoryWarningLevels}; 820 | std::string level; 821 | while (std::getline(ss, level, ',')) { 822 | if (const long l = atol(level.c_str())) { 823 | memory_watcher_.levels.push_back(l); 824 | } else { 825 | dbgE(MEMWATCHTAG "Memory watcher will not run - could not perform conversion to long for %s; FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES: %s\n", level.c_str(), memoryWarningLevels.c_str()); 826 | return; 827 | } 828 | } 829 | if (memory_watcher_.levels.size() < 1) { 830 | dbgW(MEMWATCHTAG "Memory watcher will not run - needs at least 1 memory level; FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES: %s\n", memoryWarningLevels.c_str()); 831 | } else { 832 | int event_control = -1; 833 | 834 | bool all_succeeded = false; 835 | auto cleanup = [&](bool *) { 836 | if (!all_succeeded) { 837 | CleanupMemoryWatcher(); 838 | } 839 | if (event_control != -1) { 840 | close(event_control); 841 | event_control = -1; 842 | } 843 | }; 844 | const auto cleanup_watcher = std::unique_ptr(&all_succeeded, cleanup); 845 | 846 | memory_watcher_.memory_file_fd = open(memory_usage_path.c_str(), O_RDONLY); 847 | if (memory_watcher_.memory_file_fd == -1) { 848 | dbgE(MEMWATCHTAG "Cannot open %s, errno: %d\n", memory_usage_path.c_str(), errno); 849 | return; 850 | } 851 | 852 | event_control = open(cgroup_event_control_path.c_str(), O_WRONLY); 853 | if (event_control == -1) { 854 | dbgE(MEMWATCHTAG "Cannot open %s, errno: %d\n", cgroup_event_control_path.c_str(), errno); 855 | return; 856 | } 857 | 858 | memory_watcher_.event_fd = eventfd(0, 0); 859 | if (memory_watcher_.event_fd == -1) { 860 | dbgE(MEMWATCHTAG "eventfd() failed\n"); 861 | return; 862 | } 863 | 864 | for (auto level : memory_watcher_.levels) { 865 | char line[LINE_MAX]; 866 | snprintf(line, LINE_MAX, "%d %d %ld", memory_watcher_.event_fd, memory_watcher_.memory_file_fd, level); 867 | dbgT(MEMWATCHTAG "event_control: writing line '%s'\n", line); 868 | const int ret = write(event_control, line, strlen(line) + 1); 869 | if (ret == -1) { 870 | dbgE(MEMWATCHTAG "Cannot write to cgroup.event_control, errno: %d\n", errno); 871 | return; 872 | } 873 | } 874 | dbgI(MEMWATCHTAG "starting memory watcher\n"); 875 | all_succeeded = true; 876 | } 877 | } 878 | } 879 | } 880 | 881 | WaylandDisplay::~WaylandDisplay() { 882 | CleanupMemoryWatcher(); 883 | 884 | if (engine_) { 885 | auto result = FlutterEngineShutdown(engine_); 886 | if (result == kSuccess) { 887 | engine_ = nullptr; 888 | } else { 889 | dbgE("Could not shutdown the Flutter engine.\n"); 890 | } 891 | } 892 | 893 | if (shell_surface_) { 894 | wl_shell_surface_destroy(shell_surface_); 895 | shell_surface_ = nullptr; 896 | } 897 | 898 | if (shell_) { 899 | wl_shell_destroy(shell_); 900 | shell_ = nullptr; 901 | } 902 | 903 | if (output_) { 904 | wl_output_destroy(output_); 905 | output_ = nullptr; 906 | } 907 | 908 | if (seat_) { 909 | wl_seat_destroy(seat_); 910 | seat_ = nullptr; 911 | } 912 | 913 | xkb_keymap_unref(keymap); 914 | keymap = nullptr; 915 | 916 | xkb_context_unref(xkb_context); 917 | xkb_context = nullptr; 918 | 919 | if (egl_surface_) { 920 | eglDestroySurface(egl_display_, egl_surface_); 921 | egl_surface_ = nullptr; 922 | } 923 | 924 | if (egl_display_) { 925 | eglTerminate(egl_display_); 926 | egl_display_ = nullptr; 927 | } 928 | 929 | if (window_) { 930 | wl_egl_window_destroy(window_); 931 | window_ = nullptr; 932 | } 933 | 934 | if (surface_) { 935 | wl_surface_destroy(surface_); 936 | surface_ = nullptr; 937 | } 938 | 939 | if (compositor_) { 940 | wl_compositor_destroy(compositor_); 941 | compositor_ = nullptr; 942 | } 943 | 944 | if (registry_) { 945 | wl_registry_destroy(registry_); 946 | registry_ = nullptr; 947 | } 948 | 949 | if (display_) { 950 | wl_display_flush(display_); 951 | wl_display_disconnect(display_); 952 | display_ = nullptr; 953 | } 954 | } 955 | 956 | bool WaylandDisplay::IsValid() const { 957 | return valid_; 958 | } 959 | 960 | ssize_t WaylandDisplay::vSyncHandler() { 961 | if (vsync.baton_ == 0) { 962 | return 0; 963 | } 964 | 965 | const auto t_now_ns = FlutterEngineGetCurrentTime(); 966 | const uint64_t after_vsync_time_ns = (t_now_ns - vsync.last_frame_) % vsync.vblank_time_ns_; 967 | const uint64_t before_next_vsync_time_ns = vsync.vblank_time_ns_ - after_vsync_time_ns; 968 | const uint64_t current_ns = t_now_ns + before_next_vsync_time_ns; 969 | const uint64_t finish_time_ns = current_ns + vsync.vblank_time_ns_; 970 | intptr_t baton = std::atomic_exchange(&vsync.baton_, 0); 971 | 972 | DBG_TIMING({ 973 | auto tx = FlutterEngineGetCurrentTime(); 974 | dbgI("[%09.4f][%ld] flutterEngineOnVsync [%jx] (t:%09.4f d:%09.4f vb:%09.4f c:%09.4f f:%09.4f)\n", (tx - t00) / 1e9, gettid(), static_cast(baton), (t_now_ns - t00) / 1e9, (t_now_ns - vsync.last_frame_) / 1e9, 975 | vsync.vblank_time_ns_ / 1e9, (current_ns - t00) / 1e9, (finish_time_ns - t00) / 1e9); 976 | }); 977 | 978 | const auto status = FlutterEngineOnVsync(engine_, baton, current_ns, finish_time_ns); 979 | 980 | if (status != kSuccess) { 981 | printf("[%ju]: ERROR: vsync.ntfy: FlutterEngineOnVsync failed(%d): baton: %p\n", t_now_ns, status, reinterpret_cast(baton)); 982 | return -1; 983 | } 984 | 985 | return 1; 986 | } 987 | 988 | const struct wl_callback_listener WaylandDisplay::kFrameListener = { 989 | 990 | .done = 991 | [](void *data, struct wl_callback *cb, uint32_t callback_data) { 992 | WaylandDisplay *const wd = get_wayland_display(data); 993 | 994 | /* check if we have presentation time extension interface working */ 995 | if (wd->vsync.presentation_clk_id_ != UINT32_MAX) { 996 | return; 997 | } 998 | 999 | wd->vsync.last_frame_ = FlutterEngineGetCurrentTime(); 1000 | wl_callback_destroy(cb); 1001 | wl_callback_add_listener(wl_surface_frame(wd->surface_), &kFrameListener, data); 1002 | } 1003 | 1004 | }; 1005 | 1006 | ssize_t WaylandDisplay::vSyncReadNotifyData() { 1007 | ssize_t rv; 1008 | 1009 | do { 1010 | char c; 1011 | rv = read(vsync.sv_[vsync.SOCKET_READER], &c, sizeof c); 1012 | } while (rv == -1 && errno == EINTR); 1013 | 1014 | if (rv != 1) { 1015 | printf("ERROR: Read error from vsync socket (rv: %zd, errno: %d)\n", rv, errno); 1016 | } 1017 | 1018 | return rv; 1019 | } 1020 | 1021 | ssize_t WaylandDisplay::vSyncSendNotifyData() { 1022 | static unsigned char c = 0; 1023 | ssize_t rv; 1024 | 1025 | c++; 1026 | 1027 | do { 1028 | rv = write(vsync.sv_[vsync.SOCKET_WRITER], &c, sizeof c); 1029 | } while (rv == -1 && errno == EINTR); 1030 | 1031 | if (rv != 1) { 1032 | printf("ERROR: Write error to vsync socket (rv: %zd, errno: %d)\n", rv, errno); 1033 | } 1034 | 1035 | return rv; 1036 | } 1037 | 1038 | static void set_sleep_to_next_platform_event(const uint64_t timestamp_of_next_platform_event, struct timespec &ts) { 1039 | if (timestamp_of_next_platform_event == 0) { 1040 | ts = {.tv_sec = LONG_MAX, .tv_nsec = 0}; 1041 | } else { 1042 | const uint64_t now = FlutterEngineGetCurrentTime(); 1043 | const uint64_t time_to_sleep = now <= timestamp_of_next_platform_event ? timestamp_of_next_platform_event - now : 0; 1044 | timespec_from_nsec(&ts, time_to_sleep); 1045 | } 1046 | } 1047 | 1048 | bool WaylandDisplay::Run() { 1049 | if (!valid_) { 1050 | dbgE("Could not run an invalid display.\n"); 1051 | return false; 1052 | } 1053 | 1054 | const int fd = wl_display_get_fd(display_); 1055 | 1056 | wl_callback_add_listener(wl_surface_frame(surface_), &kFrameListener, this); 1057 | 1058 | if (kbd_grab_manager_ && getEnv("FLUTTER_WAYLAND_MAIN_UI", 0.) != 0.) { 1059 | /* It's the main UI application, so check if we can receive all keys */ 1060 | printf("kbd_grab_manager: grabbing keyboard...\n"); 1061 | xwayland_keyboard_grab = zwp_xwayland_keyboard_grab_manager_v1_grab_keyboard(kbd_grab_manager_, surface_, seat_); 1062 | } 1063 | 1064 | while (valid_) { 1065 | while (wl_display_prepare_read(display_) != 0) { 1066 | wl_display_dispatch_pending(display_); 1067 | } 1068 | 1069 | wl_display_flush(display_); 1070 | 1071 | uint64_t timestamp_of_next_platform_event_ns = 0; 1072 | 1073 | do { 1074 | 1075 | struct timespec ts; 1076 | set_sleep_to_next_platform_event(timestamp_of_next_platform_event_ns, ts); 1077 | 1078 | int rv, ppoll_rv; 1079 | 1080 | struct pollfd fds[5] = { 1081 | {.fd = vsync.sv_[vsync.SOCKET_READER], .events = POLLIN, .revents = 0}, 1082 | {.fd = fd, .events = POLLIN | POLLERR, .revents = 0}, 1083 | {.fd = key.timer_fd_, .events = POLLIN | POLLERR, .revents = 0}, 1084 | {.fd = memory_watcher_.event_fd, .events = POLLIN | POLLERR, .revents = 0}, 1085 | {.fd = event_loop_._platform_event_loop_eventfd, .events = POLLIN | POLLERR, .revents = 0} 1086 | }; 1087 | 1088 | do { 1089 | ppoll_rv = ppoll(&fds[0], std::size(fds), &ts, nullptr); 1090 | } while (ppoll_rv == -1 && errno == EINTR); 1091 | 1092 | if (ppoll_rv == -1) { 1093 | printf("ERROR: ppoll returned -1 (errno: %d)\n", errno); 1094 | return false; 1095 | } 1096 | 1097 | if (fds[2].revents & POLLIN) { 1098 | uint64_t count; 1099 | do { 1100 | rv = read(fds[2].fd, &count, sizeof count); 1101 | } while (rv == -1 && errno == EINTR); 1102 | 1103 | if (rv == -1) { 1104 | printf("ERROR: read returned -1 (errno: %d)\n", errno); 1105 | return false; 1106 | } 1107 | 1108 | key_handler(key.last_, key.state_, true); 1109 | } 1110 | 1111 | if (fds[0].revents & POLLIN) { 1112 | auto rv = vSyncReadNotifyData(); 1113 | 1114 | if (rv != 1) { 1115 | return false; 1116 | } 1117 | 1118 | if (vsync.presentation_clk_id_ != UINT32_MAX && presentation_ != nullptr) { 1119 | DBG_TIMING({ 1120 | auto tx = FlutterEngineGetCurrentTime(); 1121 | dbgI("[%09.4f][%ld] add listener\n", (tx - t00) / 1e9, gettid()); 1122 | }); 1123 | wp_presentation_feedback_add_listener(::wp_presentation_feedback(presentation_, surface_), &kPresentationFeedbackListener, this); 1124 | wl_display_dispatch_pending(display_); 1125 | } 1126 | 1127 | rv = vSyncHandler(); 1128 | 1129 | if (rv != 1) { 1130 | return false; 1131 | } 1132 | 1133 | continue; 1134 | } 1135 | 1136 | if (fds[1].revents & POLLIN) { 1137 | wl_display_read_events(display_); 1138 | } else { 1139 | wl_display_cancel_read(display_); 1140 | } 1141 | 1142 | if (fds[3].revents & POLLIN) { 1143 | uint64_t result; 1144 | do { 1145 | rv = read(fds[3].fd, &result, sizeof result); 1146 | } while (rv == -1 && errno == EINTR); 1147 | if (rv == -1) { 1148 | dbgE(MEMWATCHTAG "problems reading event fd, errno=%d\n", errno); 1149 | } else { 1150 | HandleMemoryWatcherEvent(); 1151 | } 1152 | } 1153 | 1154 | const bool event_loop_wakeup = fds[4].revents & POLLIN; 1155 | if (event_loop_wakeup) { 1156 | uint64_t result; 1157 | do { 1158 | rv = read(fds[4].fd, &result, sizeof result); 1159 | } while (rv == -1 && errno == EINTR); 1160 | } 1161 | // in case of event loop wakeup or if timeout of another event passed - flush events queue 1162 | if (event_loop_wakeup || ppoll_rv == 0) { 1163 | timestamp_of_next_platform_event_ns = event_loop_._platform_event_loop->ProcessEvents(); 1164 | } 1165 | 1166 | break; 1167 | } while (true); 1168 | 1169 | wl_display_dispatch_pending(display_); 1170 | } 1171 | 1172 | return true; 1173 | } 1174 | 1175 | bool WaylandDisplay::SetupEGL() { 1176 | 1177 | egl_display_ = eglGetDisplay(display_); 1178 | if (egl_display_ == EGL_NO_DISPLAY) { 1179 | LogLastEGLError(); 1180 | dbgE("Could not access EGL display.\n"); 1181 | return false; 1182 | } 1183 | 1184 | if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) { 1185 | LogLastEGLError(); 1186 | dbgE("Could not initialize EGL display.\n"); 1187 | return false; 1188 | } 1189 | 1190 | if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) { 1191 | LogLastEGLError(); 1192 | dbgE("Could not bind the ES API.\n"); 1193 | return false; 1194 | } 1195 | 1196 | EGLConfig egl_config = nullptr; 1197 | 1198 | // Choose an EGL config to use for the surface and context. 1199 | { 1200 | EGLint attribs[] = { 1201 | // clang-format off 1202 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 1203 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 1204 | EGL_RED_SIZE, 8, 1205 | EGL_GREEN_SIZE, 8, 1206 | EGL_BLUE_SIZE, 8, 1207 | EGL_ALPHA_SIZE, 8, 1208 | EGL_DEPTH_SIZE, 0, 1209 | EGL_STENCIL_SIZE, 0, 1210 | EGL_NONE, // termination sentinel 1211 | // clang-format on 1212 | }; 1213 | 1214 | EGLint config_count = 0; 1215 | 1216 | if (eglChooseConfig(egl_display_, attribs, &egl_config, 1, &config_count) != EGL_TRUE) { 1217 | LogLastEGLError(); 1218 | dbgE("Error when attempting to choose an EGL surface config.\n"); 1219 | return false; 1220 | } 1221 | 1222 | if (config_count == 0 || egl_config == nullptr) { 1223 | LogLastEGLError(); 1224 | dbgE("No matching configs.\n"); 1225 | return false; 1226 | } 1227 | } 1228 | 1229 | const EGLint ctx_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; 1230 | 1231 | // Create an EGL context with the match config. 1232 | { 1233 | egl_context_ = eglCreateContext(egl_display_, egl_config, nullptr /* share group */, ctx_attribs); 1234 | 1235 | if (egl_context_ == EGL_NO_CONTEXT) { 1236 | LogLastEGLError(); 1237 | dbgE("Could not create an onscreen context.\n"); 1238 | return false; 1239 | } 1240 | } 1241 | 1242 | if (!compositor_ || !shell_) { 1243 | dbgE("EGL setup needs missing compositor and shell connection.\n"); 1244 | return false; 1245 | } 1246 | 1247 | surface_ = wl_compositor_create_surface(compositor_); 1248 | 1249 | if (!surface_) { 1250 | dbgE("Could not create compositor surface.\n"); 1251 | return false; 1252 | } 1253 | 1254 | shell_surface_ = wl_shell_get_shell_surface(shell_, surface_); 1255 | 1256 | if (!shell_surface_) { 1257 | dbgE("Could not shell surface.\n"); 1258 | return false; 1259 | } 1260 | 1261 | wl_shell_surface_add_listener(shell_surface_, &kShellSurfaceListener, this); 1262 | 1263 | wl_shell_surface_set_title(shell_surface_, "Flutter"); 1264 | 1265 | wl_shell_surface_set_toplevel(shell_surface_); 1266 | 1267 | window_ = wl_egl_window_create(surface_, screen_width_, screen_height_); 1268 | 1269 | if (!window_) { 1270 | dbgE("Could not create EGL window.\n"); 1271 | return false; 1272 | } 1273 | 1274 | const EGLint pbuffer_config_attribs[] = {EGL_HEIGHT, 64, EGL_WIDTH, 64, EGL_NONE}; 1275 | 1276 | resource_egl_context_ = eglCreateContext(egl_display_, egl_config, egl_context_ /* share group */, ctx_attribs); 1277 | resource_egl_surface_ = eglCreatePbufferSurface(egl_display_, egl_config, pbuffer_config_attribs); 1278 | 1279 | // Create an EGL window surface with the matched config. 1280 | { 1281 | const EGLint attribs[] = {EGL_NONE}; 1282 | 1283 | egl_surface_ = eglCreateWindowSurface(egl_display_, egl_config, window_, attribs); 1284 | 1285 | if (egl_surface_ == EGL_NO_SURFACE) { 1286 | LogLastEGLError(); 1287 | dbgE("EGL surface was null during surface selection.\n"); 1288 | return false; 1289 | } 1290 | } 1291 | 1292 | return true; 1293 | } 1294 | 1295 | void WaylandDisplay::RunFlutterTask(const FlutterTask *task) { 1296 | if (!engine_) { 1297 | dbgE("FlutterEngineRunTask called before engine was initialized\n"); 1298 | } else if (FlutterEngineRunTask(engine_, task) != kSuccess) { 1299 | dbgW("FlutterEngineRunTask failed\n"); 1300 | } 1301 | } 1302 | 1303 | bool WaylandDisplay::ConfigurePlatformTaskRunner(FlutterTaskRunnerDescription *task_runner) { 1304 | 1305 | event_loop_._platform_event_loop_eventfd = eventfd(0, 0); 1306 | if (event_loop_._platform_event_loop_eventfd == -1) { 1307 | dbgE("Couldn't create platform event loop event.\n"); 1308 | return false; 1309 | } 1310 | 1311 | event_loop_._platform_event_loop = std::make_unique( 1312 | std::this_thread::get_id(), 1313 | std::bind(&WaylandDisplay::RunFlutterTask,this,std::placeholders::_1), 1314 | event_loop_._platform_event_loop_eventfd); 1315 | 1316 | task_runner->struct_size = sizeof(FlutterTaskRunnerDescription); 1317 | task_runner->user_data = event_loop_._platform_event_loop.get(); 1318 | 1319 | task_runner->runs_task_on_current_thread_callback = [](void *state) -> bool { 1320 | return reinterpret_cast(state)->RunsTasksOnCurrentThread(); 1321 | }; 1322 | task_runner->post_task_callback = [](FlutterTask task, uint64_t target_time_nanos, void *state) -> void { 1323 | reinterpret_cast(state)->PostTask(task, target_time_nanos); 1324 | }; 1325 | return true; 1326 | } 1327 | } // namespace flutter 1328 | -------------------------------------------------------------------------------- /src/wayland_display.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Authors. All rights reserved. 2 | // Copyright 2019 Damian Wrobel 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "event_loop.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "macros.h" 27 | 28 | namespace flutter { 29 | 30 | class WaylandDisplay { 31 | public: 32 | WaylandDisplay(size_t width, size_t height, const std::string &bundle_path, const std::vector &args); 33 | 34 | ~WaylandDisplay(); 35 | 36 | bool IsValid() const; 37 | 38 | bool Run(); 39 | 40 | private: 41 | static const wl_registry_listener kRegistryListener; 42 | static const wl_shell_surface_listener kShellSurfaceListener; 43 | static const wl_seat_listener kSeatListener; 44 | static const wl_output_listener kOutputListener; 45 | static const wl_pointer_listener kPointerListener; 46 | static const wl_callback_listener kFrameListener; 47 | static const wp_presentation_listener kPresentationListener; 48 | static const wp_presentation_feedback_listener kPresentationFeedbackListener; 49 | 50 | double surface_x = 0; 51 | double surface_y = 0; 52 | 53 | struct zwp_xwayland_keyboard_grab_v1 *xwayland_keyboard_grab = nullptr; 54 | 55 | bool key_handler(const uint32_t key, const uint32_t state_w, const bool is_repeat = false); 56 | static const wl_keyboard_listener kKeyboardListener; 57 | wl_keyboard_keymap_format keymap_format = WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP; 58 | struct xkb_state *xkb_state = nullptr; 59 | struct xkb_keymap *keymap = nullptr; 60 | struct xkb_context *xkb_context = nullptr; 61 | GdkModifierType key_modifiers = static_cast(0); 62 | 63 | bool valid_ = false; 64 | int screen_width_; 65 | int screen_height_; 66 | int physical_width_ = 0; 67 | int physical_height_ = 0; 68 | bool window_metrix_skipped_ = false; 69 | wl_display *display_ = nullptr; 70 | wl_registry *registry_ = nullptr; 71 | wl_compositor *compositor_ = nullptr; 72 | wl_shell *shell_ = nullptr; 73 | wl_seat *seat_ = nullptr; 74 | wl_output *output_ = nullptr; 75 | wp_presentation *presentation_ = nullptr; 76 | zwp_xwayland_keyboard_grab_manager_v1 *kbd_grab_manager_ = nullptr; 77 | wl_shell_surface *shell_surface_ = nullptr; 78 | wl_surface *surface_ = nullptr; 79 | wl_egl_window *window_ = nullptr; 80 | EGLDisplay egl_display_ = EGL_NO_DISPLAY; 81 | EGLSurface egl_surface_ = nullptr; 82 | EGLContext egl_context_ = EGL_NO_CONTEXT; 83 | 84 | EGLSurface resource_egl_surface_ = nullptr; 85 | EGLContext resource_egl_context_ = EGL_NO_CONTEXT; 86 | 87 | FlutterEngine engine_ = nullptr; 88 | 89 | struct { 90 | std::vector levels; 91 | long current_level = 0; 92 | int memory_file_fd = -1; 93 | uint64_t last_warning_sent_ns = 0; 94 | int event_fd = -1; 95 | } memory_watcher_; 96 | 97 | bool SetupEGL(); 98 | 99 | bool SetupEngine(const std::string &bundle_path, const std::vector &command_line_args); 100 | 101 | bool StopRunning(); 102 | 103 | void SetupMemoryWatcher(); 104 | 105 | void HandleMemoryWatcherEvent(); 106 | 107 | void CleanupMemoryWatcher(); 108 | 109 | bool ConfigurePlatformTaskRunner(FlutterTaskRunnerDescription *task_runner); 110 | 111 | void RunFlutterTask(const FlutterTask *task); 112 | 113 | // key repeat related 114 | struct { 115 | int32_t repeat_delay_ms_ = 400; 116 | int32_t repeat_interval_ms_ = 40; 117 | uint32_t last_ = 0; 118 | uint32_t state_ = WL_KEYBOARD_KEY_STATE_RELEASED; 119 | int timer_fd_ = -1; 120 | } key; 121 | 122 | // vsync related { 123 | struct { 124 | uint32_t presentation_clk_id_ = UINT32_MAX; 125 | std::atomic baton_ = 0; 126 | std::atomic last_frame_ = 0; 127 | uint64_t vblank_time_ns_ = 1'000'000'000'000 / 60'000; 128 | enum { SOCKET_WRITER = 0, SOCKET_READER }; 129 | int sv_[2] = {-1, -1}; // 0-index is for sending, 1-index is for reading 130 | } vsync; 131 | ssize_t vSyncHandler(); 132 | ssize_t vSyncSendNotifyData(); 133 | ssize_t vSyncReadNotifyData(); 134 | // } 135 | 136 | struct { 137 | std::unique_ptr _platform_event_loop; 138 | int _platform_event_loop_eventfd = -1; 139 | } event_loop_; 140 | 141 | FLWAY_DISALLOW_COPY_AND_ASSIGN(WaylandDisplay) 142 | }; 143 | 144 | } // namespace flutter 145 | --------------------------------------------------------------------------------