├── .clang-format ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── FUNDING.yml └── workflows │ ├── cmake.yml │ ├── codeql-buildscript.sh │ ├── codeql.yml │ ├── fail_on_error.py │ ├── no-response.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── CMakePresets.json ├── GETTING_STARTED.md ├── LICENSE ├── README.md ├── config.h.in ├── src ├── compositor_ng.c ├── compositor_ng.h ├── crashpad_handler_trampoline.cc ├── cursor.c ├── cursor.h ├── dmabuf_surface.c ├── dmabuf_surface.h ├── dummy_render_surface.c ├── dummy_render_surface.h ├── egl.h ├── egl_gbm_render_surface.c ├── egl_gbm_render_surface.h ├── event_loop.c ├── event_loop.h ├── filesystem_layout.c ├── filesystem_layout.h ├── flutter-pi.c ├── flutter-pi.h ├── frame_scheduler.c ├── frame_scheduler.h ├── gl_renderer.c ├── gl_renderer.h ├── gles.h ├── jsmn.h ├── keyboard.c ├── keyboard.h ├── locales.c ├── locales.h ├── main.c ├── modesetting.c ├── modesetting.h ├── notifier_listener.c ├── notifier_listener.h ├── pixel_format.c ├── pixel_format.h ├── platformchannel.c ├── platformchannel.h ├── pluginregistry.c ├── pluginregistry.h ├── plugins │ ├── audioplayers.h │ ├── audioplayers │ │ ├── README.md │ │ ├── player.c │ │ └── plugin.c │ ├── charset_converter.c │ ├── charset_converter.h │ ├── gstreamer_video_player.h │ ├── gstreamer_video_player │ │ ├── frame.c │ │ ├── player.c │ │ └── plugin.c │ ├── raw_keyboard.c │ ├── raw_keyboard.h │ ├── sentry │ │ ├── sentry.c │ │ └── sentry.h │ ├── services.c │ ├── services.h │ ├── testplugin.c │ ├── testplugin.h │ ├── text_input.c │ └── text_input.h ├── render_surface.c ├── render_surface.h ├── render_surface_private.h ├── surface.c ├── surface.h ├── surface_private.h ├── texture_registry.c ├── texture_registry.h ├── tracer.c ├── tracer.h ├── user_input.c ├── user_input.h ├── util │ ├── asserts.h │ ├── bitscan.c │ ├── bitscan.h │ ├── bitset.h │ ├── collection.c │ ├── collection.h │ ├── dynarray.h │ ├── geometry.h │ ├── list.h │ ├── lock_ops.h │ ├── logging.h │ ├── macros.h │ ├── refcounting.h │ ├── uuid.h │ ├── vector.c │ └── vector.h ├── vk_gbm_render_surface.c ├── vk_gbm_render_surface.h ├── vk_renderer.c ├── vk_renderer.h ├── vulkan.h ├── window.c └── window.h ├── test ├── CMakeLists.txt ├── flutterpi_test.c └── platformchannel_test.c └── third_party ├── CMakeLists.txt └── flutter_embedder_header ├── engine.version └── include └── flutter_embedder.h /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # For more information, see: 4 | # 5 | # Documentation/process/clang-format.rst 6 | # https://clang.llvm.org/docs/ClangFormat.html 7 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 8 | # 9 | --- 10 | AccessModifierOffset: -4 11 | AlignAfterOpenBracket: BlockIndent 12 | #AlignArrayOfStructures: Right 13 | AlignConsecutiveAssignments: false 14 | #AlignConsecutiveBitFields: false 15 | AlignConsecutiveDeclarations: false 16 | AlignConsecutiveMacros: false 17 | AlignEscapedNewlines: Left 18 | AlignOperands: true 19 | AlignTrailingComments: false 20 | AllowAllArgumentsOnNextLine: false 21 | AllowAllParametersOfDeclarationOnNextLine: false 22 | AllowShortBlocksOnASingleLine: false 23 | AllowShortCaseLabelsOnASingleLine: true 24 | #AllowShortEnumsOnASingleLine: false 25 | AllowShortFunctionsOnASingleLine: None 26 | AllowShortIfStatementsOnASingleLine: Never 27 | #AllowShortLambdasOnASingleLine: None 28 | AllowShortLoopsOnASingleLine: false 29 | AlwaysBreakAfterDefinitionReturnType: None 30 | AlwaysBreakAfterReturnType: None 31 | AlwaysBreakTemplateDeclarations: false 32 | AttributeMacros: 33 | - 'MAYBE_UNUSED' 34 | - 'ATTR_MALLOC' 35 | - 'NONNULL' 36 | - 'ATTR_PURE' 37 | - 'ATTR_CONST' 38 | 39 | BinPackArguments: false 40 | BinPackParameters: false 41 | BraceWrapping: 42 | AfterClass: false 43 | AfterControlStatement: false 44 | AfterEnum: false 45 | AfterFunction: true 46 | AfterNamespace: true 47 | AfterObjCDeclaration: false 48 | AfterStruct: true 49 | AfterUnion: true 50 | AfterExternBlock: false 51 | BeforeCatch: false 52 | BeforeElse: false 53 | IndentBraces: false 54 | SplitEmptyFunction: true 55 | SplitEmptyRecord: true 56 | SplitEmptyNamespace: true 57 | BreakBeforeBinaryOperators: None 58 | BreakBeforeBraces: Attach 59 | BreakBeforeInheritanceComma: false 60 | BreakBeforeTernaryOperators: false 61 | BreakConstructorInitializersBeforeComma: false 62 | BreakConstructorInitializers: BeforeComma 63 | BreakAfterJavaFieldAnnotations: false 64 | BreakStringLiterals: true 65 | AlwaysBreakBeforeMultilineStrings: true 66 | ColumnLimit: 140 67 | CommentPragmas: '^ IWYU pragma:' 68 | CompactNamespaces: false 69 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 70 | ConstructorInitializerIndentWidth: 8 71 | ContinuationIndentWidth: 4 72 | Cpp11BracedListStyle: false 73 | DerivePointerAlignment: false 74 | DisableFormat: false 75 | #ExperimentalAutoDetectBinPacking: true 76 | FixNamespaceComments: false 77 | ForEachMacros: 78 | - 'for_each_pointer_in_pset' 79 | - 'for_each_pointer_in_cpset' 80 | - 'for_each_connector_in_drmdev' 81 | - 'for_each_encoder_in_drmdev' 82 | - 'for_each_crtc_in_drmdev' 83 | - 'for_each_plane_in_drmdev' 84 | - 'for_each_mode_in_connector' 85 | - 'for_each_unreserved_plane_in_atomic_req' 86 | 87 | IncludeBlocks: Regroup 88 | IncludeCategories: 89 | # C standard library headers 90 | - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$' 91 | Priority: 1 92 | # POSIX headers 93 | - Regex: '^<(ctype|dlfcn|fcntl|glob|limits|locale|poll|pthread|regex|semaphore|unistd|sys/mman|sys/stat|sys/types|sys/select)\.h>$' 94 | Priority: 2 95 | # all <> includes 96 | - Regex: '^<[^>]*>$' 97 | Priority: 3 98 | # config header should go last 99 | - Regex: '^"config\.h"$' 100 | Priority: 5 101 | # all "" includes 102 | - Regex: '^"[^"]*"$' 103 | Priority: 4 104 | # all other includes 105 | - Regex: '^.*$' 106 | Priority: 4 107 | 108 | IncludeIsMainRegex: '(_test)?$' 109 | IndentCaseLabels: true 110 | IndentGotoLabels: false 111 | IndentPPDirectives: BeforeHash 112 | IndentWidth: 4 113 | IndentWrappedFunctionNames: false 114 | JavaScriptQuotes: Leave 115 | JavaScriptWrapImports: true 116 | KeepEmptyLinesAtTheStartOfBlocks: false 117 | MacroBlockBegin: '' 118 | MacroBlockEnd: '' 119 | MaxEmptyLinesToKeep: 1 120 | NamespaceIndentation: None 121 | ObjCBinPackProtocolList: Auto 122 | ObjCBlockIndentWidth: 8 123 | ObjCSpaceAfterProperty: true 124 | ObjCSpaceBeforeProtocolList: true 125 | 126 | PenaltyBreakAssignment: 60 127 | PenaltyBreakBeforeFirstCallParameter: 0 128 | PenaltyBreakComment: 10 129 | PenaltyBreakFirstLessLess: 0 130 | PenaltyBreakOpenParenthesis: 0 131 | PenaltyBreakString: 0 132 | PenaltyExcessCharacter: 100 133 | PenaltyReturnTypeOnItsOwnLine: 60 134 | 135 | PointerAlignment: Right 136 | ReflowComments: false 137 | SortIncludes: CaseInsensitive 138 | SortUsingDeclarations: false 139 | SpaceAfterCStyleCast: true 140 | SpaceAfterTemplateKeyword: true 141 | SpaceBeforeAssignmentOperators: true 142 | SpaceBeforeCtorInitializerColon: true 143 | SpaceBeforeInheritanceColon: true 144 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 145 | SpaceBeforeRangeBasedForLoopColon: true 146 | SpaceInEmptyParentheses: false 147 | SpacesBeforeTrailingComments: 2 148 | SpacesInAngles: false 149 | SpacesInContainerLiterals: false 150 | SpacesInCStyleCastParentheses: false 151 | SpacesInLineCommentPrefix: 152 | Minimum: 1 153 | Maximum: -1 154 | SpacesInParentheses: false 155 | SpacesInSquareBrackets: false 156 | Standard: Cpp03 157 | StatementMacros: 158 | - 'DECLARE_REF_OPS' 159 | - 'DEFINE_REF_OPS' 160 | - 'DEFINE_STATIC_REF_OPS' 161 | - 'DECLARE_LOCK_OPS' 162 | - 'DEFINE_LOCK_OPS' 163 | - 'DEFINE_STATIC_LOCK_OPS' 164 | - 'DEFINE_INLINE_LOCK_OPS' 165 | - 'UUID' 166 | - 'CONST_UUID' 167 | TabWidth: 4 168 | TypenameMacros: 169 | - 'BMAP_ELEMENT_TYPE' 170 | - 'MAX_ALIGNMENT' 171 | UseTab: Never 172 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:bookworm 2 | 3 | RUN env DEBIAN_FRONTEND=noninteractive apt-get update && \ 4 | env DEBIAN_FRONTEND=noninteractive apt-get install -y \ 5 | gdb git cmake ninja-build pkg-config nano clang clang-format clang-tidy clang-tools \ 6 | libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev libseat-dev \ 7 | libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \ 8 | libcurl4-openssl-dev 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Debian", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | // "image": "./Dockerfile", 7 | "build": { 8 | "dockerfile": "Dockerfile" 9 | }, 10 | 11 | // Features to add to the dev container. More info: https://containers.dev/features. 12 | "features": { 13 | "ghcr.io/devcontainers/features/github-cli:1": {} 14 | }, 15 | "mounts": [ 16 | { 17 | "source": "desktop-linux-gh", 18 | "target": "/root/.config/gh/", 19 | "type": "volume" 20 | } 21 | ], 22 | 23 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 24 | // "forwardPorts": [], 25 | // Configure tool-specific properties. 26 | // "customizations": {}, 27 | "customizations": { 28 | // Configure properties specific to VS Code. 29 | "vscode": { 30 | // Set *default* container specific settings.json values on container create. 31 | "settings": { 32 | "editor.formatOnSave": true, 33 | "files.insertFinalNewline": true 34 | }, 35 | // Add the IDs of extensions you want installed when the container is created. 36 | "extensions": [ 37 | "ms-vscode.cpptools-extension-pack", 38 | "ms-vscode.cmake-tools", 39 | "github.vscode-github-actions", 40 | "ms-azuretools.vscode-docker" 41 | ] 42 | } 43 | } 44 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 45 | // "remoteUser": "root" 46 | } 47 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ardera # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master, feature/gh-actions, feature/compositor-ng ] 6 | pull_request: 7 | branches: [ master, feature/gh-actions, feature/compositor-ng ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | pi4: 12 | name: Build and Test on Raspberry Pi 4 13 | runs-on: ${{ matrix.runner }} 14 | strategy: 15 | matrix: 16 | runner: 17 | # - ARM 18 | - ARM64 19 | graphics-backends: 20 | - vulkan-only 21 | - opengl-only 22 | - vulkan-and-opengl 23 | compiler: 24 | - gcc 25 | - clang 26 | build-type: 27 | - Debug 28 | - Release 29 | include: 30 | - graphics-backends: 'vulkan-only' 31 | enable-vulkan: 'on' 32 | enable-opengl: 'off' 33 | 34 | - graphics-backends: 'opengl-only' 35 | enable-vulkan: 'off' 36 | enable-opengl: 'on' 37 | 38 | - graphics-backends: 'vulkan-and-opengl' 39 | enable-vulkan: 'on' 40 | enable-opengl: 'on' 41 | 42 | - enable-opengl: 'on' 43 | opengl-deps: 'libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev' 44 | 45 | - enable-vulkan: 'on' 46 | vulkan-deps: 'libvulkan-dev' 47 | 48 | - compiler: 'clang' 49 | clang-deps: 'clang' 50 | steps: 51 | # On the self-hosted runners, we assume those are already installed. 52 | # - name: Install dependencies 53 | # run: | 54 | # id 55 | # sudo apt-get install -y \ 56 | # git cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ 57 | # ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev \ 58 | # libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev \ 59 | # ${{ matrix.vulkan-deps }} \ 60 | # ${{ matrix.clang-deps }} 61 | 62 | - uses: actions/checkout@v3 63 | with: 64 | submodules: 'recursive' 65 | 66 | - name: Configure CMake 67 | run: | 68 | cmake \ 69 | -B ./build \ 70 | -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ 71 | -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ 72 | -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ 73 | -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ 74 | -DENABLE_VULKAN=${{ matrix.enable-vulkan }} \ 75 | -DENABLE_OPENGL=${{ matrix.enable-opengl }} \ 76 | -DENABLE_TESTS=On \ 77 | -GNinja 78 | echo test 79 | 80 | - name: Build 81 | run: cmake --build ./build --config ${{ matrix.build-type }} 82 | 83 | - name: Test 84 | working-directory: build 85 | run: ctest -C ${{ matrix.build-type }} --output-on-failure 86 | 87 | container: 88 | name: Build & Test in Container 89 | runs-on: ubuntu-latest 90 | container: 91 | image: ${{ matrix.container }} 92 | env: 93 | DEBIAN_FRONTEND: noninteractive 94 | strategy: 95 | matrix: 96 | container: 97 | - 'debian:bullseye' 98 | - 'debian:buster' 99 | graphics-backends: 100 | - vulkan-only 101 | - opengl-only 102 | - vulkan-and-opengl 103 | compiler: 104 | - gcc 105 | - clang 106 | build-type: 107 | - Debug 108 | - Release 109 | sentry-plugin: 110 | - 'off' 111 | - 'on-inproc' 112 | - 'on-crashpad-bundled' 113 | - 'on-crashped-unbundled' 114 | include: 115 | - graphics-backends: 'vulkan-only' 116 | enable-vulkan: 'on' 117 | enable-opengl: 'off' 118 | 119 | - graphics-backends: 'opengl-only' 120 | enable-vulkan: 'off' 121 | enable-opengl: 'on' 122 | 123 | - graphics-backends: 'vulkan-and-opengl' 124 | enable-vulkan: 'on' 125 | enable-opengl: 'on' 126 | 127 | - enable-opengl: 'on' 128 | opengl-deps: 'libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev' 129 | 130 | - enable-vulkan: 'on' 131 | vulkan-deps: 'libvulkan-dev' 132 | 133 | - compiler: 'gcc' 134 | cxx-compiler: 'g++' 135 | gcc-deps: 'gcc g++' 136 | 137 | - compiler: 'clang' 138 | cxx-compiler: 'clang++' 139 | clang-deps: 'clang' 140 | 141 | - sentry-plugin: 'off' 142 | sentry-plugin-enable: 'off' 143 | sentry-deps: '' 144 | 145 | - sentry-plugin: 'on-inproc' 146 | sentry-plugin-enable: 'on' 147 | sentry-backend: 'inproc' 148 | sentry-deps: 'libcurl4-openssl-dev' 149 | 150 | - sentry-plugin: 'on-crashpad-bundled' 151 | sentry-plugin-enable: 'on' 152 | sentry-backend: 'crashpad' 153 | sentry-bundled-crashpad-handler: 'on' 154 | sentry-deps: 'libcurl4-openssl-dev' 155 | 156 | - senty-plugin: 'on-crashped-unbundled' 157 | sentry-plugin-enable: 'on' 158 | sentry-backend: 'crashpad' 159 | sentry-bundled-crashpad-handler: 'off' 160 | sentry-deps: 'libcurl4-openssl-dev' 161 | steps: 162 | # git needs to be installed before checking out, otherwise the checkout will fallback to the REST API, 163 | # and the submodule download won't work. 164 | - name: Install dependencies 165 | run: | 166 | apt-get update && apt-get install -y \ 167 | git cmake libdrm-dev libgbm-dev \ 168 | fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev \ 169 | libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev \ 170 | ${{ matrix.vulkan-deps }} \ 171 | ${{ matrix.opengl-deps }} \ 172 | ${{ matrix.gcc-deps }} \ 173 | ${{ matrix.clang-deps }} \ 174 | ${{ matrix.sentry-deps }} 175 | 176 | - uses: actions/checkout@v3 177 | with: 178 | submodules: 'recursive' 179 | 180 | - name: Configure CMake 181 | run: | 182 | cmake \ 183 | -B ./build \ 184 | -S . \ 185 | -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ 186 | -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ 187 | -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} \ 188 | -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ 189 | -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ 190 | -DENABLE_VULKAN=${{ matrix.enable-vulkan }} \ 191 | -DENABLE_OPENGL=${{ matrix.enable-opengl }} \ 192 | -DENABLE_TESTS=On \ 193 | -DBUILD_SENTRY_PLUGIN=${{ matrix.sentry-plugin-enable }} \ 194 | -DSENTRY_BACKEND=${{ matrix.sentry-backend }} \ 195 | -DSENTRY_PLUGIN_BUNDLE_CRASHPAD_HANDLER=${{ matrix.sentry-bundled-crashpad-handler }} \ 196 | -GNinja 197 | 198 | - name: Build 199 | run: cmake --build ./build --config ${{ matrix.build-type }} 200 | 201 | - name: Test 202 | working-directory: build 203 | run: ctest -C ${{ matrix.build-type }} --output-on-failure 204 | 205 | required: 206 | name: required checks 207 | needs: [pi4, container] 208 | runs-on: ubuntu-latest 209 | steps: 210 | - run: | 211 | echo 'all required checks passed.' 212 | -------------------------------------------------------------------------------- /.github/workflows/codeql-buildscript.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt install -y cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev 4 | mkdir build && cd build 5 | cmake .. 6 | make -j`nproc` 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main", "master" ] 17 | schedule: 18 | - cron: '0 0 * * *' 19 | pull_request: 20 | branches: '*' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners 29 | # Consider using larger runners for possible analysis time improvements. 30 | runs-on: 'ubuntu-24.04' 31 | timeout-minutes: 360 32 | permissions: 33 | actions: read 34 | contents: read 35 | security-events: write 36 | 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | language: [ 'cpp' ] 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v4 45 | with: 46 | submodules: recursive 47 | 48 | # Initializes the CodeQL tools for scanning. 49 | - name: Initialize CodeQL 50 | uses: github/codeql-action/init@v3 51 | with: 52 | languages: ${{ matrix.language }} 53 | # If you wish to specify custom queries, you can do so here or in a config file. 54 | # By default, queries listed here will override any specified in a config file. 55 | # Prefix the list here with "+" to use these queries and those in the config file. 56 | 57 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 58 | # queries: security-extended,security-and-quality 59 | queries: security-and-quality 60 | 61 | 62 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 63 | # If this step fails, then you should remove it and run the build manually (see below) 64 | #- name: Autobuild 65 | # uses: github/codeql-action/autobuild@v2 66 | 67 | # ℹ️ Command-line programs to run using the OS shell. 68 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 69 | 70 | # If the Autobuild fails above, remove it and uncomment the following three lines. 71 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 72 | 73 | - run: | 74 | ./.github/workflows/codeql-buildscript.sh 75 | 76 | - name: Perform CodeQL Analysis 77 | uses: github/codeql-action/analyze@v3 78 | with: 79 | category: "/language:${{matrix.language}}" 80 | upload: false 81 | id: step1 82 | 83 | # Filter out rules with low severity or high false positve rate 84 | # Also filter out warnings in third-party code 85 | - name: Filter out unwanted errors and warnings 86 | uses: advanced-security/filter-sarif@v1 87 | with: 88 | patterns: | 89 | -**:cpp/path-injection 90 | -**:cpp/world-writable-file-creation 91 | -**:cpp/poorly-documented-function 92 | -**:cpp/potentially-dangerous-function 93 | -**:cpp/use-of-goto 94 | -**:cpp/integer-multiplication-cast-to-long 95 | -**:cpp/comparison-with-wider-type 96 | -**:cpp/leap-year/* 97 | -**:cpp/ambiguously-signed-bit-field 98 | -**:cpp/suspicious-pointer-scaling 99 | -**:cpp/suspicious-pointer-scaling-void 100 | -**:cpp/unsigned-comparison-zero 101 | -**/cmake*/Modules/** 102 | input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 103 | output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 104 | 105 | - name: Upload CodeQL results to code scanning 106 | uses: github/codeql-action/upload-sarif@v3 107 | with: 108 | sarif_file: ${{ steps.step1.outputs.sarif-output }} 109 | category: "/language:${{matrix.language}}" 110 | 111 | - name: Upload CodeQL results as an artifact 112 | if: success() || failure() 113 | uses: actions/upload-artifact@v4 114 | with: 115 | name: codeql-results 116 | path: ${{ steps.step1.outputs.sarif-output }} 117 | retention-days: 5 118 | 119 | - name: Fail if an error is found 120 | run: | 121 | ./.github/workflows/fail_on_error.py \ 122 | ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 123 | -------------------------------------------------------------------------------- /.github/workflows/fail_on_error.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import sys 5 | 6 | # Return whether SARIF file contains error-level results 7 | def codeql_sarif_contain_error(filename): 8 | with open(filename, 'r') as f: 9 | s = json.load(f) 10 | 11 | for run in s.get('runs', []): 12 | rules_metadata = run['tool']['driver']['rules'] 13 | if not rules_metadata: 14 | rules_metadata = run['tool']['extensions'][0]['rules'] 15 | 16 | for res in run.get('results', []): 17 | if 'ruleIndex' in res: 18 | rule_index = res['ruleIndex'] 19 | elif 'rule' in res and 'index' in res['rule']: 20 | rule_index = res['rule']['index'] 21 | else: 22 | continue 23 | try: 24 | rule_level = rules_metadata[rule_index]['defaultConfiguration']['level'] 25 | except IndexError as e: 26 | print(e, rule_index, len(rules_metadata)) 27 | else: 28 | if rule_level == 'error': 29 | return True 30 | return False 31 | 32 | if __name__ == "__main__": 33 | if codeql_sarif_contain_error(sys.argv[1]): 34 | sys.exit(1) 35 | -------------------------------------------------------------------------------- /.github/workflows/no-response.yml: -------------------------------------------------------------------------------- 1 | name: No Response 2 | 3 | # Both `issue_comment` and `scheduled` event types are required for this Action 4 | # to work properly. 5 | on: 6 | issue_comment: 7 | types: [created] 8 | schedule: 9 | # Schedule for five minutes after the hour, every hour 10 | - cron: '0 0 * * *' 11 | 12 | # By specifying the access of one of the scopes, all of those that are not 13 | # specified are set to 'none'. 14 | permissions: 15 | issues: write 16 | 17 | jobs: 18 | noResponse: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09 22 | with: 23 | token: ${{ github.token }} 24 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 25 | closeComment: > 26 | Without additional information, we are unfortunately not sure how to 27 | resolve this issue. We are therefore reluctantly going to close this 28 | bug for now. 29 | If you find this problem please file a new issue with the same description, 30 | what happens and logs. All system setups can be slightly different so it's 31 | always better to open new issues and reference the related ones. 32 | Thanks for your contribution. 33 | # Number of days of inactivity before an issue is closed for lack of response. 34 | daysUntilClose: 21 35 | # Label requiring a response. 36 | responseRequiredLabel: "waiting for response" 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - release/* 9 | pull_request: 10 | branches: 11 | master 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | build: 18 | name: Build 19 | runs-on: ubuntu-latest 20 | container: 21 | image: ${{ matrix.container }} 22 | env: 23 | DEBIAN_FRONTEND: noninteractive 24 | strategy: 25 | matrix: 26 | target: 27 | - arm-linux-gnueabihf 28 | - aarch64-linux-gnu 29 | - x86_64-linux-gnu 30 | - riscv64-linux-gnu 31 | buildtype: 32 | - debug 33 | - release 34 | include: 35 | - target: 'x86_64-linux-gnu' 36 | is-cross: false 37 | debian-arch: 'amd64' 38 | container: 'debian:bullseye' 39 | 40 | - target: 'aarch64-linux-gnu' 41 | is-cross: true 42 | debian-arch: 'arm64' 43 | container: 'debian:bullseye' 44 | 45 | - target: 'arm-linux-gnueabihf' 46 | is-cross: true 47 | debian-arch: 'armhf' 48 | container: 'debian:bullseye' 49 | 50 | - target: 'riscv64-linux-gnu' 51 | is-cross: true 52 | debian-arch: 'riscv64' 53 | container: 'debian:trixie' 54 | 55 | steps: 56 | - name: Add debian multiarch 57 | if: matrix.is-cross 58 | run: dpkg --add-architecture ${{ matrix.debian-arch }} 59 | 60 | # git needs to be installed before checking out, otherwise the checkout will fallback to the REST API, 61 | # and the submodule download won't work. 62 | - name: Install dependencies 63 | env: 64 | ARCH: ${{ matrix.debian-arch }} 65 | run: | 66 | apt-get update && apt-get install -y \ 67 | git cmake ninja-build clang lld xz-utils \ 68 | libdrm-dev:$ARCH libgbm-dev:$ARCH libsystemd-dev:$ARCH libinput-dev:$ARCH libudev-dev:$ARCH libxkbcommon-dev:$ARCH \ 69 | libgstreamer-plugins-base1.0-dev:$ARCH \ 70 | libvulkan-dev:$ARCH \ 71 | libgl1-mesa-dev:$ARCH libgles2-mesa-dev:$ARCH libegl1-mesa-dev:$ARCH \ 72 | ${{ matrix.is-cross && format('gcc-{0} g++-{0}', matrix.target) || '' }} 73 | 74 | - uses: actions/checkout@v4 75 | with: 76 | submodules: 'recursive' 77 | 78 | - name: Configure CMake 79 | env: 80 | CMAKE_SYSTEM_NAME: ${{ matrix.is-cross && '-DCMAKE_SYSTEM_NAME=Linux' || '' }} 81 | CMAKE_C_COMPILER_TARGET: ${{ matrix.is-cross && format('-DCMAKE_C_COMPILER_TARGET={0}', matrix.target) || '' }} 82 | CMAKE_CXX_COMPILER_TARGET: ${{ matrix.is-cross && format('-DCMAKE_CXX_COMPILER_TARGET={0}', matrix.target) || '' }} 83 | PKG_CONFIG_PATH: ${{ format('/usr/lib/{0}/pkgconfig:/usr/share/pkgconfig', matrix.target) }} 84 | PKG_CONFIG_LIBDIR: ${{ format('/usr/lib/{0}', matrix.target) }} 85 | PKG_CONFIG_SYSROOT_DIR: '' 86 | run: | 87 | cmake \ 88 | -B ./build \ 89 | -S . \ 90 | -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} \ 91 | $CMAKE_SYSTEM_NAME \ 92 | $CMAKE_C_COMPILER_TARGET \ 93 | $CMAKE_CXX_COMPILER_TARGET \ 94 | -DCMAKE_C_COMPILER=clang \ 95 | -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ 96 | -DCMAKE_CXX_COMPILER=clang++ \ 97 | -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ 98 | -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ 99 | -DENABLE_VULKAN=ON \ 100 | -DENABLE_OPENGL=ON \ 101 | -DENABLE_TESTS=On \ 102 | -DBUILD_SENTRY_PLUGIN=OFF \ 103 | -DCMAKE_INSTALL_PREFIX=$PWD/dist \ 104 | -GNinja 105 | 106 | - name: Build & Install 107 | run: cmake --build ./build --target install --config ${{ matrix.buildtype }} 108 | 109 | - name: Package 110 | run: | 111 | mkdir -p artifact && cd artifact 112 | cp -r ../dist/bin/flutter-pi . 113 | 114 | tar -cJf ../flutterpi-${{ matrix.target }}-${{ matrix.buildtype }}.tar.xz . 115 | 116 | - name: Upload artifact 117 | uses: actions/upload-artifact@v4 118 | with: 119 | name: flutterpi-${{ matrix.target }}-${{ matrix.buildtype }}-tar-xz 120 | path: flutterpi-${{ matrix.target }}-${{ matrix.buildtype }}.tar.xz 121 | if-no-files-found: error 122 | 123 | publish: 124 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release/') 125 | name: Publish 126 | runs-on: ubuntu-latest 127 | needs: build 128 | steps: 129 | - name: Download artifacts 130 | uses: actions/download-artifact@v4 131 | with: 132 | path: artifacts 133 | 134 | - name: Prepare release 135 | run: | 136 | mkdir -p release-files 137 | mv artifacts/*-tar-xz/* release-files/ 138 | 139 | - name: Publish Release 140 | uses: softprops/action-gh-release@v1 141 | with: 142 | fail_on_unmatched_files: true 143 | files: release-files/* 144 | name: ${{ github.ref_name }} 145 | body: ${{ github.event.head_commit.message }} 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /build 3 | /out 4 | 5 | # CMake docs says it should not be checked in. 6 | CMakeUserPresets.json 7 | 8 | # cmake: https://github.com/github/gitignore/blob/main/CMake.gitignore 9 | CMakeLists.txt.user 10 | CMakeCache.txt 11 | CMakeFiles 12 | CMakeScripts 13 | Testing 14 | Makefile 15 | cmake_install.cmake 16 | install_manifest.txt 17 | compile_commands.json 18 | CTestTestfile.cmake 19 | _deps 20 | 21 | # C: https://github.com/github/gitignore/blob/main/C.gitignore 22 | # Prerequisites 23 | *.d 24 | 25 | # Object files 26 | *.o 27 | *.ko 28 | *.obj 29 | *.elf 30 | 31 | # Linker output 32 | *.ilk 33 | *.map 34 | *.exp 35 | 36 | # Precompiled Headers 37 | *.gch 38 | *.pch 39 | 40 | # Libraries 41 | *.lib 42 | *.a 43 | *.la 44 | *.lo 45 | 46 | # Shared objects (inc. Windows DLLs) 47 | *.dll 48 | *.so 49 | *.so.* 50 | *.dylib 51 | 52 | # Executables 53 | *.exe 54 | *.out 55 | *.app 56 | *.i*86 57 | *.x86_64 58 | *.hex 59 | 60 | # Debug files 61 | *.dSYM/ 62 | *.su 63 | *.idb 64 | *.pdb 65 | 66 | # Kernel Module Compile Results 67 | *.mod* 68 | *.cmd 69 | .tmp_versions/ 70 | modules.order 71 | Module.symvers 72 | Mkfile.old 73 | dkms.conf 74 | 75 | # MacOS gitignores 76 | # General 77 | .DS_Store 78 | .AppleDouble 79 | .LSOverride 80 | 81 | # Icon must end with two \r 82 | Icon 83 | 84 | # Thumbnails 85 | ._* 86 | 87 | # Files that might appear in the root of a volume 88 | .DocumentRevisions-V100 89 | .fseventsd 90 | .Spotlight-V100 91 | .TemporaryItems 92 | .Trashes 93 | .VolumeIcon.icns 94 | .com.apple.timemachine.donotpresent 95 | 96 | # Directories potentially created on remote AFP share 97 | .AppleDB 98 | .AppleDesktop 99 | Network Trash Folder 100 | Temporary Items 101 | .apdisk 102 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/Unity"] 2 | path = third_party/Unity 3 | url = https://github.com/ThrowTheSwitch/Unity 4 | [submodule "third_party/sentry-native"] 5 | path = third_party/sentry-native 6 | url = https://github.com/getsentry/sentry-native 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | exclude_types: ['markdown'] 9 | - id: forbid-new-submodules 10 | - id: mixed-line-ending 11 | args: ['--fix=lf'] 12 | description: Forces to replace line ending by the UNIX 'lf' character. 13 | - repo: https://github.com/pre-commit/mirrors-clang-format 14 | rev: v16.0.0 15 | hooks: 16 | - id: clang-format 17 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "configurePresets": [ 4 | { 5 | "name": "default", 6 | "displayName": "Default OpenGL host build", 7 | "description": "Sets Ninja generator, build and install directory", 8 | "generator": "Ninja", 9 | "binaryDir": "${sourceDir}/out/build/${presetName}", 10 | "cacheVariables": { 11 | "CMAKE_BUILD_TYPE": "Debug", 12 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", 13 | "ENABLE_OPENGL": true, 14 | "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, 15 | "BUILD_SENTRY_PLUGIN": true, 16 | "ENABLE_TESTS": true 17 | } 18 | }, 19 | { 20 | "name": "cross-aarch64-default", 21 | "displayName": "OpenGL AArch64 cross-build", 22 | "description": "Sets Ninja generator, build and install directory", 23 | "generator": "Ninja", 24 | "binaryDir": "${sourceDir}/out/build/${presetName}", 25 | "inherits": "default", 26 | "cacheVariables": { 27 | "CMAKE_C_COMPILER": "clang", 28 | "CMAKE_C_COMPILER_TARGET": "aarch64-linux-gnu" 29 | } 30 | }, 31 | { 32 | "name": "cross-armhf-default", 33 | "displayName": "OpenGL armhf cross-build", 34 | "description": "Sets Ninja generator, build and install directory", 35 | "generator": "Ninja", 36 | "binaryDir": "${sourceDir}/out/build/${presetName}", 37 | "inherits": "default", 38 | "cacheVariables": { 39 | "CMAKE_C_COMPILER": "clang", 40 | "CMAKE_C_COMPILER_TARGET": "arm-linux-gnueabihf" 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started with flutter-pi 2 | 3 | These instructions summarize the information in the [README.md](README.md) file. See that file for more details. 4 | In each step below with Bash commands, the commands start with a set of `export` commands that you should update appropriately. 5 | 6 | 1. Build a Raspberry Pi with a touchscreen. This will be your target. 7 | 8 | 2. Prepare your target for flutter-pi (see "Configuring your Raspberry Pi" in the README for details): 9 | ```bash 10 | export APPNAME=hello_pi # change this to the name of your application 11 | 12 | # one-time setup 13 | sudo usermod -a -G render $USER 14 | sudo apt --yes install libgl1-mesa-dev libgles2-mesa-dev libegl-mesa0 libdrm-dev libgbm-dev 15 | sudo apt --yes install libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev 16 | sudo apt --yes install ttf-mscorefonts-installer fontconfig 17 | sudo fc-cache 18 | if [ `uname -m` == 'armv7l' ]; then export ARM=arm; else export ARM=arm64; fi 19 | mkdir -p ~/dev 20 | pushd ~/dev 21 | git clone --depth 1 https://github.com/ardera/flutter-engine-binaries-for-arm engine-binaries 22 | sudo ./engine-binaries/install.sh 23 | git clone https://github.com/ardera/flutter-pi.git 24 | cd flutter-pi 25 | mkdir build && cd build 26 | cmake .. 27 | make -j`nproc` 28 | # per-application setup 29 | mkdir -p ~/dev/$APPNAME 30 | popd 31 | echo You will need to set ARM to: $ARM 32 | ``` 33 | 34 | Take a note of the last line of output. It should say you need "arm" or "arm64". This is used to set ARM below. 35 | 36 | Take a note of which version of Flutter the binaries were compiled for. This is used to set VERSION below. It should be clear from the commit messages of the latest commit to the repo: https://github.com/ardera/flutter-engine-binaries-for-arm 37 | 38 | 3. Configure your target. Run `sudo raspi-config`, and configure the system as follows: 39 | 1. Select `System Options` -> `Boot / Auto Login` -> `Console` (or `Console (Autologin)`). 40 | 2. Select `Advanced Options` -> `GL Driver` -> `GL (Fake-KMS)`. 41 | 3. Select `Performance Options` -> `GPU Memory` and set it to `64`. 42 | 4. Exit `raspi-config` and reboot when offered. 43 | 44 | 4. Download, install, and configure Flutter on a host machine (not the Raspberry Pi), then create an application, compile it, and run it. 45 | These instructions will put the version of Flutter you will use for the Raspberry Pi into the `~/dev/flutter-for-pi` directory so as to not interfere with your normal Flutter installation. 46 | For the purposes of these instructions we'll assume this is an x64 Linux workstation. 47 | ```bash 48 | export VERSION=... # set this to the version determined above, e.g. 1.22.4 49 | export ARM=... # set this to "arm" or "arm64" as determined above 50 | export TARGET=... # set this to your Raspberry Pi's hostname 51 | export APPNAME=hello_pi # same as what you used earlier 52 | export TARGETUSER=pi # set this to your username on the raspberry pi, e.g. "pi" or $USER if it's the same as on the host 53 | 54 | mkdir -p ~/dev 55 | pushd ~/dev 56 | # one-time setup 57 | git clone --branch $VERSION https://github.com/flutter/flutter.git flutter-for-pi 58 | ~/dev/flutter-for-pi/bin/flutter precache 59 | git clone --depth 1 https://github.com/ardera/flutter-engine-binaries-for-arm engine-binaries 60 | chmod +x engine-binaries/$ARM/gen_snapshot_linux_x64_release 61 | # create the application 62 | flutter-for-pi/bin/flutter create $APPNAME 63 | # compile the application 64 | cd $APPNAME 65 | ../flutter-for-pi/bin/flutter packages get # this might not be necessary 66 | ../flutter-for-pi/bin/flutter build bundle --no-tree-shake-icons --precompiled 67 | ../flutter-for-pi/bin/cache/dart-sdk/bin/dart \ 68 | ../flutter-for-pi/bin/cache/dart-sdk/bin/snapshots/frontend_server.dart.snapshot \ 69 | --sdk-root ~/dev/flutter-for-pi/bin/cache/artifacts/engine/common/flutter_patched_sdk_product \ 70 | --target=flutter \ 71 | --aot --tfa -Ddart.vm.product=true \ 72 | --packages .dart_tool\package_config.json --output-dill build/kernel_snapshot.dill --depfile build/kernel_snapshot.d \ 73 | package:$APPNAME/main.dart 74 | ../engine-binaries/$ARM/gen_snapshot_linux_x64_release \ 75 | --deterministic --snapshot_kind=app-aot-elf \ 76 | --strip --sim-use-hardfp \ 77 | --elf=build/flutter_assets/app.so build/kernel_snapshot.dill 78 | # upload the application 79 | rsync --recursive ~/dev/$APPNAME/build/flutter_assets/ $TARGETUSER@$TARGET:dev/$APPNAME 80 | # run the application 81 | ssh $TARGETUSER@$TARGET "killall" "flutter-pi" 82 | ssh $TARGETUSER@$TARGET "dev/flutter-pi/build/flutter-pi" "--release" "~/dev/$APPNAME" 83 | popd 84 | ``` 85 | 86 | That's it! 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hannes Winkler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef _FLUTTERPI_CONFIG_H 2 | #define _FLUTTERPI_CONFIG_H 3 | 4 | #define LIBINPUT_VERSION_MAJOR @LIBINPUT_VERSION_MAJOR@ 5 | #define LIBINPUT_VERSION_MINOR @LIBINPUT_VERSION_MINOR@ 6 | #define LIBINPUT_VERSION_PATCH @LIBINPUT_VERSION_PATCH@ 7 | #cmakedefine HAVE_KMS 8 | #cmakedefine HAVE_GBM 9 | #cmakedefine HAVE_FBDEV 10 | #cmakedefine HAVE_EGL 11 | #cmakedefine HAVE_GLES2 12 | #cmakedefine HAVE_EGL_GLES2 13 | #cmakedefine LINT_EGL_HEADERS 14 | #cmakedefine HAVE_VULKAN 15 | #cmakedefine FILESYSTEM_LAYOUT_DEFAULT 16 | #cmakedefine FILESYSTEM_LAYOUT_METAFLUTTER 17 | #cmakedefine HAVE_LIBSEAT 18 | #cmakedefine DEBUG_DRM_PLANE_ALLOCATIONS 19 | #cmakedefine USE_LEGACY_KMS 20 | #cmakedefine BUILD_TEXT_INPUT_PLUGIN 21 | #cmakedefine BUILD_RAW_KEYBOARD_PLUGIN 22 | #define LIBGSTREAMER_VERSION_MAJOR @LIBGSTREAMER_VERSION_MAJOR@ 23 | #define LIBGSTREAMER_VERSION_MINOR @LIBGSTREAMER_VERSION_MINOR@ 24 | #define LIBGSTREAMER_VERSION_PATCH @LIBGSTREAMER_VERSION_PATCH@ 25 | #cmakedefine VULKAN_DEBUG 26 | #cmakedefine ENABLE_MTRACE 27 | #cmakedefine ENABLE_ASAN 28 | #cmakedefine HAVE_BUNDLED_CRASHPAD_HANDLER 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/compositor_ng.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Compositor NG 4 | * 5 | * - newer flutter compositor 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_COMPOSITOR_NG_H 11 | #define _FLUTTERPI_SRC_COMPOSITOR_NG_H 12 | 13 | #include 14 | 15 | #include "cursor.h" 16 | #include "flutter-pi.h" 17 | #include "frame_scheduler.h" 18 | #include "modesetting.h" 19 | #include "pixel_format.h" 20 | #include "util/collection.h" 21 | #include "util/refcounting.h" 22 | 23 | #include "config.h" 24 | 25 | #ifdef HAVE_EGL_GLES2 26 | #include "egl.h" 27 | #endif 28 | 29 | struct compositor; 30 | 31 | struct drm_connector_config { 32 | uint32_t connector_type; 33 | uint32_t connector_type_id; 34 | 35 | bool disable, primary; 36 | 37 | bool has_mode_size; 38 | int mode_width, mode_height; 39 | 40 | bool has_mode_refreshrate; 41 | int mode_refreshrate_n, mode_refreshrate_d; 42 | 43 | bool has_framebuffer_size; 44 | int framebuffer_width, framebuffer_height; 45 | 46 | bool has_physical_dimensions; 47 | int physical_width_mm, physical_height_mm; 48 | }; 49 | 50 | struct drm_device_config { 51 | bool has_path; 52 | const char *path; 53 | 54 | size_t n_connector_configs; 55 | struct drm_connector_config *connector_configs; 56 | }; 57 | 58 | struct fbdev_device_config { 59 | const char *path; 60 | 61 | bool has_physical_dimensions; 62 | int physical_width_mm, physical_height_mm; 63 | }; 64 | 65 | struct device_config { 66 | bool is_drm, is_fbdev; 67 | union { 68 | struct drm_device_config drm_config; 69 | struct fbdev_device_config fbdev_config; 70 | }; 71 | }; 72 | 73 | struct compositor_config { 74 | bool has_use_hardware_cursor, use_hardware_cursor; 75 | 76 | bool has_forced_pixel_format; 77 | enum pixfmt forced_pixel_format; 78 | 79 | size_t n_device_configs; 80 | struct device_config *device_configs; 81 | }; 82 | 83 | struct clip_rect { 84 | struct quad rect; 85 | bool is_aa; 86 | 87 | struct aa_rect aa_rect; 88 | 89 | bool is_rounded; 90 | struct vec2f upper_left_corner_radius; 91 | struct vec2f upper_right_corner_radius; 92 | struct vec2f lower_right_corner_radius; 93 | struct vec2f lower_left_corner_radius; 94 | }; 95 | 96 | struct fl_layer_props { 97 | /** 98 | * @brief True if the presentation quadrangle (the quadrangle on the target window into which the 99 | * layer should be rendered) is an axis-aligned rectangle. For example, allows us to use a plain 100 | * hardware overlay layer for this layer. 101 | * 102 | * This should always be true for backing stores, but might be false for platform views. 103 | */ 104 | bool is_aa_rect; 105 | 106 | /** 107 | * @brief The coords of the axis aligned rectangle if @ref is_aa_rect is true. 108 | */ 109 | struct aa_rect aa_rect; 110 | 111 | /** 112 | * @brief The quadrangle on the target window into which the layer should be rendered. 113 | */ 114 | struct quad quad; 115 | 116 | /** 117 | * @brief Opacity as a normalized float from 0 (transparent) to 1 (opqaue). 118 | */ 119 | double opacity; 120 | 121 | /** 122 | * @brief Rotation of the buffer in degrees clockwise, normalized to a range 0 - 360. 123 | */ 124 | double rotation; 125 | 126 | /** 127 | * @brief The number of clip rectangles in the @ref clip_rects array. 128 | */ 129 | size_t n_clip_rects; 130 | 131 | /** 132 | * @brief The (possibly rounded) rectangles that the surface should be clipped to. 133 | */ 134 | struct clip_rect *clip_rects; 135 | }; 136 | 137 | struct fl_layer { 138 | struct fl_layer_props props; 139 | struct surface *surface; 140 | }; 141 | 142 | struct fl_layer_composition { 143 | refcount_t n_refs; 144 | size_t n_layers; 145 | struct fl_layer layers[]; 146 | }; 147 | 148 | struct drmdev; 149 | struct compositor; 150 | struct frame_scheduler; 151 | struct view_geometry; 152 | struct window; 153 | struct tracer; 154 | 155 | typedef void (*compositor_frame_begin_cb_t)(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns); 156 | 157 | struct compositor *compositor_new(struct tracer *tracer, struct window *main_window); 158 | 159 | void compositor_destroy(struct compositor *compositor); 160 | 161 | DECLARE_REF_OPS(compositor) 162 | 163 | void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out); 164 | 165 | ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); 166 | 167 | int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out); 168 | 169 | int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface); 170 | 171 | struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id); 172 | 173 | const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); 174 | 175 | int compositor_request_frame(struct compositor *compositor, compositor_frame_begin_cb_t cb, void *userdata); 176 | 177 | #ifdef HAVE_EGL_GLES2 178 | bool compositor_has_egl_surface(struct compositor *compositor); 179 | 180 | EGLSurface compositor_get_egl_surface(struct compositor *compositor); 181 | #endif 182 | 183 | int compositor_get_event_fd(struct compositor *compositor); 184 | 185 | int compositor_on_event_fd_ready(struct compositor *compositor); 186 | 187 | void compositor_set_cursor( 188 | struct compositor *compositor, 189 | bool has_enabled, 190 | bool enabled, 191 | bool has_kind, 192 | enum pointer_kind kind, 193 | bool has_delta, 194 | struct vec2f delta 195 | ); 196 | 197 | struct fl_layer_composition; 198 | 199 | struct fl_layer_composition *fl_layer_composition_new(size_t n_layers); 200 | void fl_layer_composition_destroy(struct fl_layer_composition *composition); 201 | DECLARE_REF_OPS(fl_layer_composition) 202 | 203 | size_t fl_layer_composition_get_n_layers(struct fl_layer_composition *composition); 204 | struct fl_layer *fl_layer_composition_peek_layer(struct fl_layer_composition *composition, int layer); 205 | 206 | #endif // _FLUTTERPI_SRC_COMPOSITOR_NG_H 207 | -------------------------------------------------------------------------------- /src/crashpad_handler_trampoline.cc: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #ifndef HAVE_BUNDLED_CRASHPAD_HANDLER 4 | #error "This file should only be built when using the bundled crashpad handler" 5 | #endif 6 | 7 | #include "handler/handler_main.h" 8 | 9 | extern "C" { 10 | 11 | int crashpad_handler_main(int argc, char **argv) { 12 | return crashpad::HandlerMain(argc, argv, nullptr); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/cursor.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Cursor Images 4 | * 5 | * Contains all the mouse cursor images in compressed form, 6 | * and some utilities for using them. 7 | * 8 | * Copyright (c) 2023, Hannes Winkler 9 | */ 10 | 11 | #ifndef _FLUTTERPI_SRC_CURSOR_H 12 | #define _FLUTTERPI_SRC_CURSOR_H 13 | 14 | #include 15 | 16 | #include "util/geometry.h" 17 | 18 | enum pointer_kind { 19 | POINTER_KIND_NONE, 20 | POINTER_KIND_BASIC, 21 | POINTER_KIND_CLICK, 22 | POINTER_KIND_FORBIDDEN, 23 | POINTER_KIND_WAIT, 24 | POINTER_KIND_PROGRESS, 25 | POINTER_KIND_CONTEXT_MENU, 26 | POINTER_KIND_HELP, 27 | POINTER_KIND_TEXT, 28 | POINTER_KIND_VERTICAL_TEXT, 29 | POINTER_KIND_CELL, 30 | POINTER_KIND_PRECISE, 31 | POINTER_KIND_MOVE, 32 | POINTER_KIND_GRAB, 33 | POINTER_KIND_GRABBING, 34 | POINTER_KIND_NO_DROP, 35 | POINTER_KIND_ALIAS, 36 | POINTER_KIND_COPY, 37 | POINTER_KIND_DISAPPEARING, 38 | POINTER_KIND_ALL_SCROLL, 39 | POINTER_KIND_RESIZE_LEFT_RIGHT, 40 | POINTER_KIND_RESIZE_UP_DOWN, 41 | POINTER_KIND_RESIZE_UP_LEFT_DOWN_RIGHT, 42 | POINTER_KIND_RESIZE_UP_RIGHT_DOWN_LEFT, 43 | POINTER_KIND_RESIZE_UP, 44 | POINTER_KIND_RESIZE_DOWN, 45 | POINTER_KIND_RESIZE_LEFT, 46 | POINTER_KIND_RESIZE_RIGHT, 47 | POINTER_KIND_RESIZE_UP_LEFT, 48 | POINTER_KIND_RESIZE_UP_RIGHT, 49 | POINTER_KIND_RESIZE_DOWN_LEFT, 50 | POINTER_KIND_RESIZE_DOWN_RIGHT, 51 | POINTER_KIND_RESIZE_COLUMN, 52 | POINTER_KIND_RESIZE_ROW, 53 | POINTER_KIND_ZOOM_IN, 54 | POINTER_KIND_ZOOM_OUT 55 | }; 56 | 57 | struct pointer_icon; 58 | 59 | const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, double pixel_ratio); 60 | 61 | enum pointer_kind pointer_icon_get_kind(const struct pointer_icon *icon); 62 | 63 | float pointer_icon_get_pixel_ratio(const struct pointer_icon *icon); 64 | 65 | struct vec2i pointer_icon_get_size(const struct pointer_icon *icon); 66 | 67 | struct vec2i pointer_icon_get_hotspot(const struct pointer_icon *icon); 68 | 69 | void *pointer_icon_dup_pixels(const struct pointer_icon *icon); 70 | 71 | #endif // _FLUTTERPI_SRC_CURSOR_H 72 | -------------------------------------------------------------------------------- /src/dmabuf_surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * linux dmabuf surface 4 | * 5 | * - present dmabufs on screen as optimally as possible 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_DMABUF_SURFACE_H 11 | #define _FLUTTERPI_SRC_DMABUF_SURFACE_H 12 | 13 | #include "pixel_format.h" 14 | 15 | struct dmabuf_surface; 16 | 17 | #define CAST_DMABUF_SURFACE_UNCHECKED(ptr) ((struct dmabuf_surface *) (ptr)) 18 | #ifdef DEBUG 19 | #define CAST_DMABUF_SURFACE(ptr) __checked_cast_dmabuf_surface(ptr) 20 | ATTR_PURE struct dmabuf_surface *__checked_cast_dmabuf_surface(void *ptr); 21 | #else 22 | #define CAST_DMABUF_SURFACE(ptr) CAST_DMABUF_SURFACE_UNCHECKED(ptr) 23 | #endif 24 | 25 | struct dmabuf { 26 | enum pixfmt format; 27 | int width, height; 28 | int fds[4]; 29 | int offsets[4]; 30 | int strides[4]; 31 | bool has_modifiers; 32 | uint64_t modifiers[4]; 33 | void *userdata; 34 | }; 35 | 36 | typedef void (*dmabuf_release_cb_t)(struct dmabuf *buf); 37 | 38 | struct texture_registry; 39 | struct tracer; 40 | 41 | MUST_CHECK struct dmabuf_surface *dmabuf_surface_new(struct tracer *tracer, struct texture_registry *texture_registry); 42 | 43 | int dmabuf_surface_push_dmabuf(struct dmabuf_surface *s, const struct dmabuf *buf, dmabuf_release_cb_t release_cb); 44 | 45 | ATTR_PURE int64_t dmabuf_surface_get_texture_id(struct dmabuf_surface *s); 46 | 47 | #endif // _FLUTTERPI_SRC_DMABUF_SURFACE_H 48 | -------------------------------------------------------------------------------- /src/dummy_render_surface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Vulkan GBM Backing Store 4 | * 5 | * - a render surface that can be used for filling flutter vulkan backing stores 6 | * - and for scanout using KMS 7 | * 8 | * Copyright (c) 2022, Hannes Winkler 9 | */ 10 | 11 | #include "dummy_render_surface.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "render_surface.h" 20 | #include "render_surface_private.h" 21 | #include "surface.h" 22 | #include "surface_private.h" 23 | #include "tracer.h" 24 | #include "util/geometry.h" 25 | #include "util/uuid.h" 26 | 27 | struct dummy_render_surface { 28 | union { 29 | struct surface surface; 30 | struct render_surface render_surface; 31 | }; 32 | 33 | #ifdef DEBUG 34 | uuid_t uuid; 35 | #endif 36 | }; 37 | 38 | COMPILE_ASSERT(offsetof(struct dummy_render_surface, surface) == 0); 39 | COMPILE_ASSERT(offsetof(struct dummy_render_surface, render_surface.surface) == 0); 40 | 41 | #ifdef DEBUG 42 | static const uuid_t uuid = CONST_UUID(0x26, 0xfe, 0x91, 0x53, 0x75, 0xf2, 0x41, 0x90, 0xa1, 0xf5, 0xba, 0xe1, 0x1b, 0x28, 0xd5, 0xe5); 43 | #endif 44 | 45 | #define CAST_THIS(ptr) CAST_DUMMY_RENDER_SURFACE(ptr) 46 | #define CAST_THIS_UNCHECKED(ptr) CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) 47 | 48 | #ifdef DEBUG 49 | ATTR_PURE struct dummy_render_surface *__checked_cast_dummy_render_surface(void *ptr) { 50 | struct dummy_render_surface *surface; 51 | 52 | surface = CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr); 53 | ASSERT(uuid_equals(surface->uuid, uuid)); 54 | return surface; 55 | } 56 | #endif 57 | 58 | void dummy_render_surface_deinit(struct surface *s); 59 | static int dummy_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); 60 | static int dummy_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); 61 | static int dummy_render_surface_fill(struct render_surface *surface, FlutterBackingStore *fl_store); 62 | static int dummy_render_surface_queue_present(struct render_surface *surface, const FlutterBackingStore *fl_store); 63 | 64 | int dummy_render_surface_init(struct dummy_render_surface *surface, struct tracer *tracer, struct vec2i size) { 65 | int ok; 66 | 67 | ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(surface), tracer, size); 68 | if (ok != 0) { 69 | return EIO; 70 | } 71 | 72 | surface->surface.present_kms = dummy_render_surface_present_kms; 73 | surface->surface.present_fbdev = dummy_render_surface_present_fbdev; 74 | surface->surface.deinit = dummy_render_surface_deinit; 75 | surface->render_surface.fill = dummy_render_surface_fill; 76 | surface->render_surface.queue_present = dummy_render_surface_queue_present; 77 | 78 | #ifdef DEBUG 79 | uuid_copy(&surface->uuid, uuid); 80 | #endif 81 | return 0; 82 | } 83 | 84 | struct dummy_render_surface *dummy_render_surface_new(struct tracer *tracer, struct vec2i size) { 85 | struct dummy_render_surface *surface; 86 | int ok; 87 | 88 | surface = malloc(sizeof *surface); 89 | if (surface == NULL) { 90 | goto fail_return_null; 91 | } 92 | 93 | ok = dummy_render_surface_init(surface, tracer, size); 94 | if (ok != 0) { 95 | goto fail_free_surface; 96 | } 97 | 98 | return surface; 99 | 100 | fail_free_surface: 101 | free(surface); 102 | 103 | fail_return_null: 104 | return NULL; 105 | } 106 | 107 | void dummy_render_surface_deinit(struct surface *s) { 108 | render_surface_deinit(s); 109 | } 110 | 111 | static int 112 | dummy_render_surface_present_kms(struct surface *s, UNUSED const struct fl_layer_props *props, UNUSED struct kms_req_builder *builder) { 113 | (void) props; 114 | (void) builder; 115 | 116 | TRACER_INSTANT(s->tracer, "dummy_render_surface_present_kms"); 117 | 118 | return 0; 119 | } 120 | 121 | static int dummy_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { 122 | (void) s; 123 | (void) props; 124 | (void) builder; 125 | 126 | TRACER_INSTANT(s->tracer, "dummy_render_surface_present_fbdev"); 127 | 128 | return 0; 129 | } 130 | 131 | static int dummy_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { 132 | (void) fl_store; 133 | 134 | TRACER_INSTANT(s->surface.tracer, "dummy_render_surface_fill"); 135 | 136 | return 0; 137 | } 138 | 139 | static int dummy_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { 140 | (void) fl_store; 141 | 142 | TRACER_INSTANT(s->surface.tracer, "dummy_render_surface_queue_present"); 143 | 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /src/dummy_render_surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Dummy render surface 4 | * 5 | * Just a render surface that does nothing when presenting. 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H 11 | #define _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H 12 | 13 | #include "util/geometry.h" 14 | 15 | struct tracer; 16 | struct dummy_render_surface; 17 | 18 | #define CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) ((struct dummy_render_surface *) (ptr)) 19 | #ifdef DEBUG 20 | #define CAST_DUMMY_RENDER_SURFACE(ptr) __checked_cast_dummy_render_surface(ptr) 21 | ATTR_PURE struct dummy_render_surface *__checked_cast_dummy_render_surface(void *ptr); 22 | #else 23 | #define CAST_DUMMY_RENDER_SURFACE(ptr) CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) 24 | #endif 25 | 26 | struct dummy_render_surface *dummy_render_surface_new(struct tracer *tracer, struct vec2i size); 27 | 28 | #endif // _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H 29 | -------------------------------------------------------------------------------- /src/egl_gbm_render_surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * GBM Surface backing store 4 | * 5 | * - public interface for backing stores which are based on GBM surfaces 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H 11 | #define _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H 12 | 13 | #include "compositor_ng.h" 14 | #include "pixel_format.h" 15 | #include "util/collection.h" 16 | 17 | struct render_surface; 18 | struct gbm_device; 19 | struct egl_gbm_render_surface; 20 | 21 | #define CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr) ((struct egl_gbm_render_surface *) (ptr)) 22 | #ifdef DEBUG 23 | #define CAST_EGL_GBM_RENDER_SURFACE(ptr) __checked_cast_egl_gbm_render_surface(ptr) 24 | ATTR_PURE struct egl_gbm_render_surface *__checked_cast_egl_gbm_render_surface(void *ptr); 25 | #else 26 | #define CAST_EGL_GBM_RENDER_SURFACE(ptr) CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr) 27 | #endif 28 | 29 | struct egl_gbm_render_surface *egl_gbm_render_surface_new_with_egl_config( 30 | struct tracer *tracer, 31 | struct vec2i size, 32 | struct gbm_device *device, 33 | struct gl_renderer *renderer, 34 | enum pixfmt pixel_format, 35 | EGLConfig egl_config, 36 | const uint64_t *allowed_modifiers, 37 | size_t n_allowed_modifiers 38 | ); 39 | 40 | struct egl_gbm_render_surface *egl_gbm_render_surface_new( 41 | struct tracer *tracer, 42 | struct vec2i size, 43 | struct gbm_device *device, 44 | struct gl_renderer *renderer, 45 | enum pixfmt pixel_format 46 | ); 47 | 48 | ATTR_PURE EGLSurface egl_gbm_render_surface_get_egl_surface(struct egl_gbm_render_surface *s); 49 | 50 | ATTR_PURE EGLConfig egl_gbm_render_surface_get_egl_config(struct egl_gbm_render_surface *s); 51 | 52 | #endif // _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H 53 | -------------------------------------------------------------------------------- /src/event_loop.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Event Loop 4 | * 5 | * - multithreaded event loop 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_EVENT_LOOP_H 11 | #define _FLUTTERPI_SRC_EVENT_LOOP_H 12 | 13 | #include "util/refcounting.h" 14 | 15 | struct evloop; 16 | 17 | struct evloop *evloop_new(); 18 | 19 | void evloop_destroy(struct evloop *loop); 20 | 21 | DECLARE_REF_OPS(evloop) 22 | 23 | int evloop_get_fd_locked(struct evloop *loop); 24 | 25 | int evloop_get_fd(struct evloop *loop); 26 | 27 | int evloop_run(struct evloop *loop); 28 | 29 | int evloop_schedule_exit_locked(struct evloop *loop); 30 | 31 | int evloop_schedule_exit(struct evloop *loop); 32 | 33 | int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata); 34 | 35 | int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userdata); 36 | 37 | int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); 38 | 39 | int evloop_post_delayed_task(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); 40 | 41 | struct evsrc; 42 | void evsrc_destroy_locked(struct evsrc *src); 43 | void evsrc_destroy(struct evsrc *src); 44 | 45 | enum event_handler_return { kNoAction_EventHandlerReturn, kRemoveSrc_EventHandlerReturn }; 46 | 47 | typedef enum event_handler_return (*evloop_io_handler_t)(int fd, uint32_t revents, void *userdata); 48 | 49 | struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); 50 | 51 | struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); 52 | 53 | struct evthread; 54 | 55 | struct evthread *evthread_start(); 56 | 57 | struct evloop *evthread_get_evloop(struct evthread *thread); 58 | 59 | void evthread_join(struct evthread *thread); 60 | 61 | #endif // _FLUTTERPI_SRC_EVENT_LOOP_H 62 | -------------------------------------------------------------------------------- /src/filesystem_layout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Filesystem Layout 4 | * 5 | * - implements different filesystem layouts for flutter artifacts 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H 11 | #define _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H 12 | 13 | #include "flutter-pi.h" 14 | 15 | struct flutter_paths { 16 | char *app_bundle_path; 17 | char *asset_bundle_path; 18 | char *app_elf_path; 19 | char *icudtl_path; 20 | char *kernel_blob_path; 21 | char *flutter_engine_path; 22 | char *flutter_engine_dlopen_name; 23 | char *flutter_engine_dlopen_name_fallback; 24 | }; 25 | 26 | typedef struct flutter_paths *(*resolve_paths_t)(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); 27 | 28 | void flutter_paths_free(struct flutter_paths *paths); 29 | 30 | struct flutter_paths *fs_layout_flutterpi_resolve(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); 31 | struct flutter_paths *fs_layout_metaflutter_resolve(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); 32 | 33 | #endif // _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H 34 | -------------------------------------------------------------------------------- /src/flutter-pi.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Flutter-Pi main header 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_FLUTTERPI_H 9 | #define _FLUTTERPI_SRC_FLUTTERPI_H 10 | 11 | #define LOG_FLUTTERPI_ERROR(...) fprintf(stderr, "[flutter-pi] " __VA_ARGS__) 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "cursor.h" 32 | #include "pixel_format.h" 33 | #include "util/collection.h" 34 | 35 | enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight }; 36 | 37 | #define ORIENTATION_IS_LANDSCAPE(orientation) ((orientation) == kLandscapeLeft || (orientation) == kLandscapeRight) 38 | #define ORIENTATION_IS_PORTRAIT(orientation) ((orientation) == kPortraitUp || (orientation) == kPortraitDown) 39 | #define ORIENTATION_IS_VALID(orientation) \ 40 | ((orientation) == kPortraitUp || (orientation) == kLandscapeLeft || (orientation) == kPortraitDown || (orientation) == kLandscapeRight) 41 | 42 | #define ORIENTATION_ROTATE_CW(orientation) \ 43 | ((orientation) == kPortraitUp ? kLandscapeLeft : \ 44 | (orientation) == kLandscapeLeft ? kPortraitDown : \ 45 | (orientation) == kPortraitDown ? kLandscapeRight : \ 46 | (orientation) == kLandscapeRight ? kPortraitUp : \ 47 | (assert(0 && "invalid device orientation"), 0)) 48 | 49 | #define ORIENTATION_ROTATE_CCW(orientation) \ 50 | ((orientation) == kPortraitUp ? kLandscapeRight : \ 51 | (orientation) == kLandscapeLeft ? kPortraitUp : \ 52 | (orientation) == kPortraitDown ? kLandscapeLeft : \ 53 | (orientation) == kLandscapeRight ? kPortraitDown : \ 54 | (assert(0 && "invalid device orientation"), 0)) 55 | 56 | #define ANGLE_FROM_ORIENTATION(o) \ 57 | ((o) == kPortraitUp ? 0 : (o) == kLandscapeLeft ? 90 : (o) == kPortraitDown ? 180 : (o) == kLandscapeRight ? 270 : 0) 58 | 59 | #define ANGLE_BETWEEN_ORIENTATIONS(o_start, o_end) \ 60 | (ANGLE_FROM_ORIENTATION(o_end) - ANGLE_FROM_ORIENTATION(o_start) + \ 61 | (ANGLE_FROM_ORIENTATION(o_start) > ANGLE_FROM_ORIENTATION(o_end) ? 360 : 0)) 62 | 63 | #define FLUTTER_RESULT_TO_STRING(result) \ 64 | ((result) == kSuccess ? "Success." : \ 65 | (result) == kInvalidLibraryVersion ? "Invalid library version." : \ 66 | (result) == kInvalidArguments ? "Invalid arguments." : \ 67 | (result) == kInternalInconsistency ? "Internal inconsistency." : \ 68 | "(?)") 69 | 70 | /// TODO: Move this 71 | #define LIBINPUT_EVENT_IS_TOUCH(event_type) \ 72 | (((event_type) == LIBINPUT_EVENT_TOUCH_DOWN) || ((event_type) == LIBINPUT_EVENT_TOUCH_UP) || \ 73 | ((event_type) == LIBINPUT_EVENT_TOUCH_MOTION) || ((event_type) == LIBINPUT_EVENT_TOUCH_CANCEL) || \ 74 | ((event_type) == LIBINPUT_EVENT_TOUCH_FRAME)) 75 | 76 | #define LIBINPUT_EVENT_IS_POINTER(event_type) \ 77 | (((event_type) == LIBINPUT_EVENT_POINTER_MOTION) || ((event_type) == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) || \ 78 | ((event_type) == LIBINPUT_EVENT_POINTER_BUTTON) || ((event_type) == LIBINPUT_EVENT_POINTER_AXIS)) 79 | 80 | #define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) 81 | 82 | enum flutter_runtime_mode { FLUTTER_RUNTIME_MODE_DEBUG, FLUTTER_RUNTIME_MODE_PROFILE, FLUTTER_RUNTIME_MODE_RELEASE }; 83 | 84 | #define FLUTTER_RUNTIME_MODE_IS_JIT(runtime_mode) ((runtime_mode) == FLUTTER_RUNTIME_MODE_DEBUG) 85 | #define FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode) \ 86 | ((runtime_mode) == FLUTTER_RUNTIME_MODE_PROFILE || (runtime_mode) == FLUTTER_RUNTIME_MODE_RELEASE) 87 | 88 | struct compositor; 89 | struct plugin_registry; 90 | struct texture_registry; 91 | struct drmdev; 92 | struct locales; 93 | struct vk_renderer; 94 | struct flutterpi; 95 | 96 | /// TODO: Remove this 97 | extern struct flutterpi *flutterpi; 98 | 99 | struct platform_task { 100 | int (*callback)(void *userdata); 101 | void *userdata; 102 | }; 103 | 104 | struct platform_message { 105 | bool is_response; 106 | union { 107 | const FlutterPlatformMessageResponseHandle *target_handle; 108 | struct { 109 | char *target_channel; 110 | FlutterPlatformMessageResponseHandle *response_handle; 111 | }; 112 | }; 113 | uint8_t *message; 114 | size_t message_size; 115 | }; 116 | 117 | struct flutterpi_cmdline_args { 118 | bool has_orientation; 119 | enum device_orientation orientation; 120 | 121 | bool has_rotation; 122 | int rotation; 123 | 124 | bool has_physical_dimensions; 125 | struct vec2i physical_dimensions; 126 | 127 | bool has_pixel_format; 128 | enum pixfmt pixel_format; 129 | 130 | bool has_runtime_mode; 131 | enum flutter_runtime_mode runtime_mode; 132 | 133 | char *bundle_path; 134 | 135 | int engine_argc; 136 | char **engine_argv; 137 | 138 | bool use_vulkan; 139 | 140 | char *desired_videomode; 141 | 142 | bool dummy_display; 143 | struct vec2i dummy_display_size; 144 | }; 145 | 146 | int flutterpi_fill_view_properties(bool has_orientation, enum device_orientation orientation, bool has_rotation, int rotation); 147 | 148 | int flutterpi_post_platform_task(int (*callback)(void *userdata), void *userdata); 149 | 150 | int flutterpi_post_platform_task_with_time(int (*callback)(void *userdata), void *userdata, uint64_t target_time_usec); 151 | 152 | int flutterpi_sd_event_add_io(sd_event_source **source_out, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); 153 | 154 | int flutterpi_send_platform_message( 155 | struct flutterpi *flutterpi, 156 | const char *channel, 157 | const uint8_t *restrict message, 158 | size_t message_size, 159 | FlutterPlatformMessageResponseHandle *responsehandle 160 | ); 161 | 162 | int flutterpi_respond_to_platform_message( 163 | const FlutterPlatformMessageResponseHandle *handle, 164 | const uint8_t *restrict message, 165 | size_t message_size 166 | ); 167 | 168 | bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdline_args *result_out); 169 | 170 | struct texture_registry *flutterpi_get_texture_registry(struct flutterpi *flutterpi); 171 | 172 | struct plugin_registry *flutterpi_get_plugin_registry(struct flutterpi *flutterpi); 173 | 174 | FlutterPlatformMessageResponseHandle * 175 | flutterpi_create_platform_message_response_handle(struct flutterpi *flutterpi, FlutterDataCallback data_callback, void *userdata); 176 | 177 | void flutterpi_release_platform_message_response_handle(struct flutterpi *flutterpi, FlutterPlatformMessageResponseHandle *handle); 178 | 179 | struct texture *flutterpi_create_texture(struct flutterpi *flutterpi); 180 | 181 | const char *flutterpi_get_asset_bundle_path(struct flutterpi *flutterpi); 182 | 183 | void flutterpi_schedule_exit(struct flutterpi *flutterpi); 184 | 185 | struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi); 186 | 187 | bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi); 188 | 189 | struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi); 190 | 191 | void flutterpi_set_pointer_kind(struct flutterpi *flutterpi, enum pointer_kind kind); 192 | 193 | void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name); 194 | 195 | void flutterpi_trace_event_begin(struct flutterpi *flutterpi, const char *name); 196 | 197 | void flutterpi_trace_event_end(struct flutterpi *flutterpi, const char *name); 198 | 199 | #endif // _FLUTTERPI_SRC_FLUTTERPI_H 200 | -------------------------------------------------------------------------------- /src/frame_scheduler.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Frame scheduler 4 | * 5 | * Manages scheduling of frames, rendering, flutter vsync requests/replies. 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #include "frame_scheduler.h" 11 | 12 | #include 13 | 14 | #include "compositor_ng.h" 15 | #include "util/collection.h" 16 | #include "util/lock_ops.h" 17 | #include "util/refcounting.h" 18 | 19 | struct frame_scheduler { 20 | refcount_t n_refs; 21 | 22 | bool uses_frame_requests; 23 | enum present_mode present_mode; 24 | fl_vsync_callback_t vsync_cb; 25 | void *userdata; 26 | 27 | pthread_mutex_t mutex; 28 | }; 29 | 30 | DEFINE_REF_OPS(frame_scheduler, n_refs) 31 | DEFINE_STATIC_LOCK_OPS(frame_scheduler, mutex) 32 | 33 | struct frame_scheduler * 34 | frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl_vsync_callback_t vsync_cb, void *userdata) { 35 | struct frame_scheduler *scheduler; 36 | 37 | // uses_frame_requests? => vsync_cb != NULL 38 | assert(!uses_frame_requests || vsync_cb != NULL); 39 | 40 | scheduler = malloc(sizeof *scheduler); 41 | if (scheduler == NULL) { 42 | return NULL; 43 | } 44 | 45 | scheduler->n_refs = REFCOUNT_INIT_1; 46 | scheduler->uses_frame_requests = uses_frame_requests; 47 | scheduler->present_mode = present_mode; 48 | scheduler->vsync_cb = vsync_cb; 49 | scheduler->userdata = userdata; 50 | return scheduler; 51 | } 52 | 53 | void frame_scheduler_destroy(struct frame_scheduler *scheduler) { 54 | free(scheduler); 55 | } 56 | 57 | void frame_scheduler_on_fl_vsync_request(struct frame_scheduler *scheduler, intptr_t vsync_baton) { 58 | ASSERT_NOT_NULL(scheduler); 59 | assert(vsync_baton != 0); 60 | assert(scheduler->uses_frame_requests); 61 | 62 | // flutter called the vsync callback. 63 | // - when do we reply to it? 64 | // - what timestamps do we send as a reply? 65 | // 66 | // Some things to keep in mind: 67 | // - GPU rendering is a big pipeline: 68 | // uploading -> vertex shading -> tesselation -> geometry shading -> rasterization -> fragment shading 69 | // - Some parts of the pipeline might execute on different parts of the GPU, maybe there's specific 70 | // fixed-function hardware for different steps of the pipeline. For example, on PowerVR, there's a tiler (vertex) 71 | // stage and a separate renderer (fragment) stage. 72 | // - So it might not be smart to render just one frame at a time. 73 | // - On PowerVR, it's best to have a 3-frame pipeline: 74 | // 75 | // Frame 0 Frame 1 Frame 2 76 | // Geometry Submission | In Progress | . | . | 77 | // Vertex Processing | | In Progress | . | 78 | // Fragment Processing | | | In Progress | 79 | // 80 | // - That way, occupancy & throughput is optimal, and rendering can be at 60FPS even though maybe fragment processing takes > 16ms. 81 | // - On the other hand, normally a mesa EGL surface only has 4 buffers available, so we could run out of framebuffers for surfaces 82 | // as well if we draw too many frames at once. (Especially considering one framebuffer is probably busy with scanout right now) 83 | // 84 | 85 | /// TODO: Implement 86 | /// For now, just unconditionally reply 87 | if (scheduler->present_mode == kTripleBufferedVsync_PresentMode) { 88 | scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); 89 | } else if (scheduler->present_mode == kDoubleBufferedVsync_PresentMode) { 90 | scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); 91 | } 92 | } 93 | 94 | void frame_scheduler_on_rendering_complete(struct frame_scheduler *scheduler) { 95 | ASSERT_NOT_NULL(scheduler); 96 | (void) scheduler; 97 | 98 | /// TODO: Implement 99 | UNIMPLEMENTED(); 100 | } 101 | 102 | void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns) { 103 | ASSERT_NOT_NULL(scheduler); 104 | (void) scheduler; 105 | (void) has_timestamp; 106 | (void) timestamp_ns; 107 | 108 | /// TODO: Implement 109 | UNIMPLEMENTED(); 110 | } 111 | 112 | void frame_scheduler_request_fb(struct frame_scheduler *scheduler, uint64_t scanout_time_ns) { 113 | ASSERT_NOT_NULL(scheduler); 114 | (void) scheduler; 115 | (void) scanout_time_ns; 116 | 117 | /// TODO: Implement 118 | UNIMPLEMENTED(); 119 | } 120 | 121 | void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb) { 122 | ASSERT_NOT_NULL(scheduler); 123 | ASSERT_NOT_NULL(present_cb); 124 | (void) scheduler; 125 | (void) cancel_cb; 126 | 127 | /// TODO: Implement 128 | present_cb(userdata); 129 | } 130 | 131 | void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns) { 132 | ASSERT_NOT_NULL(scheduler); 133 | assert(!has_timestamp || timestamp_ns != 0); 134 | (void) scheduler; 135 | (void) has_timestamp; 136 | (void) timestamp_ns; 137 | 138 | /// TODO: Implement 139 | UNIMPLEMENTED(); 140 | } 141 | -------------------------------------------------------------------------------- /src/frame_scheduler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Vsync Waiter 4 | * 5 | * Manages scheduling of frames, rendering, flutter vsync requests/replies. 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_FRAME_SCHEDULER_H 11 | #define _FLUTTERPI_SRC_FRAME_SCHEDULER_H 12 | 13 | #include "util/collection.h" 14 | #include "util/refcounting.h" 15 | 16 | struct frame_scheduler; 17 | 18 | // clang-format off 19 | typedef void (*fl_vsync_callback_t)( 20 | void *userdata, 21 | intptr_t vsync_baton, 22 | uint64_t frame_start_time_nanos, 23 | uint64_t next_frame_start_time_nanos 24 | ); 25 | // clang-format on 26 | 27 | enum present_mode { kDoubleBufferedVsync_PresentMode, kTripleBufferedVsync_PresentMode }; 28 | 29 | /** 30 | * @brief Creates a new frame scheduler. 31 | * 32 | * A frame scheduler manages the task of scheduling rendering, frames, and handling & responding to 33 | * flutter vsync requests, depending on the chosen present mode. 34 | * 35 | * The vsync callback is the function that will be called when a flutter vsync request should be responded to. 36 | * (In practice, that callback should rethread to the platform task thread and then call FlutterEngineOnVsync with the argument 37 | * vsync baton.) 38 | * 39 | * @param uses_frame_requests Whether @ref frame_scheduler_on_fl_vsync_request will be called at all. For example, this might be false 40 | * when there was no `vsync_callback` specified in the FlutterProjectArgs. 41 | * @param present_mode Which present mode to use. (Always wait for the next vsync before starting a frame? Always start 42 | * the next frame when rendering is complete) 43 | * @param vsync_cb The function that will be called when a flutter vsync request should be responded to. 44 | * (In practice, that callback should rethread to the platform task thread and then call 45 | * @ref FlutterEngineOnVsync with the argument vsync baton.) 46 | * @param userdata userdata that will be passed to vsync_cb. 47 | * @return struct frame_scheduler* The new frame scheduler. 48 | */ 49 | struct frame_scheduler * 50 | frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl_vsync_callback_t vsync_cb, void *userdata); 51 | 52 | void frame_scheduler_destroy(struct frame_scheduler *scheduler); 53 | 54 | DECLARE_REF_OPS(frame_scheduler) 55 | 56 | /** 57 | * @brief Called when flutter calls the embedder supplied vsync_callback. 58 | * Embedder should reply on the platform task thread with the timestamp 59 | * of the next vsync request. Engine will wait till that time and then begin 60 | * rendering the next frame. 61 | * 62 | * @param scheduler The frame scheduler instance. 63 | * @param vsync_baton The vsync baton that the flutter engine specified. 64 | */ 65 | void frame_scheduler_on_fl_vsync_request(struct frame_scheduler *scheduler, intptr_t vsync_baton); 66 | 67 | void frame_scheduler_on_rendering_complete(struct frame_scheduler *scheduler); 68 | 69 | void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns); 70 | 71 | /** 72 | * @brief Will call present_cb when the next frame is ready to be presented. 73 | * 74 | * If the frame_scheduler is destroyed before the present_cb is called, or if the frame is displaced by another frame, cancel_cb will be called. 75 | * 76 | * @param scheduler The frame scheduler instance. 77 | * @param present_cb Called when the frame should be presented. 78 | * @param userdata Userdata that's passed to the present and cancel callback. 79 | * @param cancel_cb Called when the frame is not going to be presented, and all associated resources should be freed. 80 | */ 81 | void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb); 82 | 83 | #endif // _FLUTTERPI_SRC_FRAME_SCHEDULER_H 84 | -------------------------------------------------------------------------------- /src/gl_renderer.h: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | /* 4 | * EGL/OpenGL renderer 5 | * 6 | * Utilities for rendering with EGL/OpenGL. 7 | * - creating/binding EGL contexts 8 | * - using EGL extensions 9 | * - setting up / cleaning up render threads 10 | * 11 | * Copyright (c) 2022, Hannes Winkler 12 | */ 13 | 14 | #ifndef _FLUTTERPI_SRC_GL_RENDERER_H 15 | #define _FLUTTERPI_SRC_GL_RENDERER_H 16 | 17 | #include "pixel_format.h" 18 | #include "util/collection.h" 19 | #include "util/refcounting.h" 20 | 21 | #include "config.h" 22 | 23 | #if !defined(HAVE_EGL_GLES2) 24 | #error "gl_renderer requires EGL and OpenGL ES support." 25 | #endif 26 | 27 | #include "egl.h" 28 | 29 | struct tracer; 30 | 31 | struct gl_renderer *gl_renderer_new_from_gbm_device( 32 | struct tracer *tracer, 33 | struct gbm_device *gbm_device, 34 | bool has_forced_pixel_format, 35 | enum pixfmt pixel_format 36 | ); 37 | 38 | void gl_renderer_destroy(struct gl_renderer *renderer); 39 | 40 | DECLARE_REF_OPS(gl_renderer) 41 | 42 | bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer); 43 | 44 | enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer); 45 | 46 | bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer); 47 | 48 | EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer); 49 | 50 | struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer); 51 | 52 | int gl_renderer_make_flutter_rendering_context_current(struct gl_renderer *renderer, EGLSurface surface); 53 | 54 | int gl_renderer_make_flutter_resource_uploading_context_current(struct gl_renderer *renderer); 55 | 56 | int gl_renderer_make_flutter_setup_context_current(struct gl_renderer *renderer); 57 | 58 | int gl_renderer_clear_current(struct gl_renderer *renderer); 59 | 60 | EGLContext gl_renderer_create_context(struct gl_renderer *renderer); 61 | 62 | void *gl_renderer_get_proc_address(struct gl_renderer *renderer, const char *name); 63 | 64 | EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer); 65 | 66 | bool gl_renderer_supports_egl_extension(struct gl_renderer *renderer, const char *name); 67 | 68 | bool gl_renderer_supports_gl_extension(struct gl_renderer *renderer, const char *name); 69 | 70 | bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer); 71 | 72 | int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer); 73 | 74 | void gl_renderer_cleanup_this_render_thread(); 75 | 76 | ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); 77 | 78 | ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format); 79 | 80 | EGLSurface gl_renderer_create_gbm_window_surface( 81 | struct gl_renderer *renderer, 82 | EGLConfig config, 83 | struct gbm_surface *gbm_surface, 84 | const EGLAttribKHR *attrib_list, 85 | const EGLint *int_attrib_list 86 | ); 87 | 88 | #endif // _FLUTTERPI_SRC_GL_RENDERER_H 89 | -------------------------------------------------------------------------------- /src/gles.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Just a shim for including EGL headers, and disabling EGL function prototypes if EGL is not present 4 | * 5 | * Copyright (c) 2022, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_GLES_H 9 | #define _FLUTTERPI_SRC_GLES_H 10 | 11 | #include "config.h" 12 | 13 | #if !defined(HAVE_GLES2) 14 | #error "gles.h was included but OpenGLES2 support is disabled." 15 | #endif 16 | 17 | #include 18 | #include 19 | 20 | #endif // _FLUTTERPI_SRC_GLES_H 21 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Keyboard / Text Input support 4 | * 5 | * Converts key events to text events using the system keyboard config. 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_KEYBOARD_H 11 | #define _FLUTTERPI_SRC_KEYBOARD_H 12 | 13 | #include 14 | 15 | #include 16 | 17 | struct keyboard_config { 18 | struct xkb_context *context; 19 | struct xkb_keymap *default_keymap; 20 | struct xkb_compose_table *default_compose_table; 21 | }; 22 | 23 | struct keyboard_state { 24 | struct keyboard_config *config; 25 | struct xkb_state *state; 26 | struct xkb_state *plain_state; 27 | struct xkb_compose_state *compose_state; 28 | int n_iso_level2; 29 | int n_iso_level3; 30 | int n_iso_level5; 31 | }; 32 | 33 | struct keyboard_modifier_state { 34 | bool ctrl : 1; 35 | bool shift : 1; 36 | bool alt : 1; 37 | bool meta : 1; 38 | bool capslock : 1; 39 | bool numlock : 1; 40 | bool scrolllock : 1; 41 | }; 42 | 43 | #define KEY_RELEASE 0 44 | #define KEY_PRESS 1 45 | #define KEY_REPEAT 2 46 | 47 | struct keyboard_config *keyboard_config_new(void); 48 | 49 | void keyboard_config_destroy(struct keyboard_config *config); 50 | 51 | struct keyboard_state * 52 | keyboard_state_new(struct keyboard_config *config, struct xkb_keymap *keymap_override, struct xkb_compose_table *compose_table_override); 53 | 54 | void keyboard_state_destroy(struct keyboard_state *state); 55 | 56 | int keyboard_state_process_key_event( 57 | struct keyboard_state *state, 58 | uint16_t evdev_keycode, 59 | int32_t evdev_value, 60 | xkb_keysym_t *keysym_out, 61 | uint32_t *codepoint_out 62 | ); 63 | 64 | uint32_t keyboard_state_get_plain_codepoint(struct keyboard_state *state, uint16_t evdev_keycode, int32_t evdev_value); 65 | 66 | static inline bool keyboard_state_is_ctrl_active(struct keyboard_state *state) { 67 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); 68 | } 69 | 70 | static inline bool keyboard_state_is_shift_active(struct keyboard_state *state) { 71 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE); 72 | } 73 | 74 | static inline bool keyboard_state_is_alt_active(struct keyboard_state *state) { 75 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE); 76 | } 77 | 78 | static inline bool keyboard_state_is_meta_active(struct keyboard_state *state) { 79 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE); 80 | } 81 | 82 | static inline bool keyboard_state_is_capslock_active(struct keyboard_state *state) { 83 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE); 84 | } 85 | 86 | static inline bool keyboard_state_is_numlock_active(struct keyboard_state *state) { 87 | return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_NUM, XKB_STATE_MODS_EFFECTIVE); 88 | } 89 | 90 | static inline bool keyboard_state_is_scrolllock_active(struct keyboard_state *state) { 91 | return xkb_state_mod_name_is_active(state->state, "Mod3", XKB_STATE_MODS_EFFECTIVE); 92 | } 93 | 94 | static inline struct keyboard_modifier_state keyboard_state_get_meta_state(struct keyboard_state *state) { 95 | return (struct keyboard_modifier_state){ 96 | .ctrl = keyboard_state_is_ctrl_active(state), 97 | .shift = keyboard_state_is_shift_active(state), 98 | .alt = keyboard_state_is_alt_active(state), 99 | .meta = keyboard_state_is_meta_active(state), 100 | .capslock = keyboard_state_is_capslock_active(state), 101 | .numlock = keyboard_state_is_numlock_active(state), 102 | .scrolllock = keyboard_state_is_scrolllock_active(state), 103 | }; 104 | } 105 | 106 | #endif // _FLUTTERPI_SRC_KEYBOARD_H 107 | -------------------------------------------------------------------------------- /src/locales.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Locales 4 | * 5 | * Provides the configured system locales in a flutter-friendly form. 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_LOCALES_H 11 | #define _FLUTTERPI_SRC_LOCALES_H 12 | 13 | #include 14 | 15 | struct locale; 16 | struct locales; 17 | struct concurrent_pointer_set; 18 | 19 | struct locale *locale_new(const char *language, const char *territory, const char *codeset, const char *modifier); 20 | 21 | void locale_destroy(struct locale *locale); 22 | 23 | const FlutterLocale *locale_get_fl_locale(struct locale *locale); 24 | 25 | const char *locale_get_language(struct locale *locale); 26 | 27 | const char *locale_get_territory(struct locale *locale); 28 | 29 | const char *locale_get_codeset(struct locale *locale); 30 | 31 | const char *locale_get_modifier(struct locale *locale); 32 | 33 | struct locales *locales_new(void); 34 | 35 | void locales_destroy(struct locales *locales); 36 | 37 | int locales_get_flutter_locales(struct locales *locales, const FlutterLocale ***fl_locales_out, size_t *n_fl_locales_out); 38 | 39 | const FlutterLocale *locales_get_default_flutter_locale(struct locales *locales); 40 | 41 | int locales_add_to_fl_engine(struct locales *locales, FlutterEngine engine, FlutterEngineUpdateLocalesFnPtr update_locales); 42 | 43 | const FlutterLocale * 44 | locales_on_compute_platform_resolved_locale(struct locales *locales, const FlutterLocale **fl_locales, size_t n_fl_locales); 45 | 46 | void locales_print(const struct locales *locales); 47 | 48 | #endif // _FLUTTERPI_SRC_LOCALES_H 49 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "config.h" 6 | 7 | int flutterpi_app_main(int argc, char **argv); 8 | int crashpad_handler_main(int argc, char **argv); 9 | 10 | #ifdef HAVE_BUNDLED_CRASHPAD_HANDLER 11 | static bool running_in_crashpad_mode(int argc, char **argv) { 12 | for (int i = 0; i < argc; i++) { 13 | if (strcmp(argv[i], "--attachment=FlutterpiCrashpadHandlerMode") == 0) { 14 | return true; 15 | } 16 | } 17 | 18 | return false; 19 | } 20 | #endif 21 | 22 | int main(int argc, char **argv) { 23 | #ifdef HAVE_BUNDLED_CRASHPAD_HANDLER 24 | if (running_in_crashpad_mode(argc, argv)) { 25 | return crashpad_handler_main(argc, argv); 26 | } 27 | #endif 28 | 29 | return flutterpi_app_main(argc, argv); 30 | } 31 | -------------------------------------------------------------------------------- /src/notifier_listener.c: -------------------------------------------------------------------------------- 1 | #include "notifier_listener.h" 2 | 3 | #include 4 | #include 5 | 6 | struct listener { 7 | struct list_head entry; 8 | listener_cb_t notify; 9 | void_callback_t destroy; 10 | void *userdata; 11 | }; 12 | 13 | #define for_each_listener_in_notifier(_notifier, _listener) \ 14 | list_for_each_entry_safe(struct listener, _listener, &(_notifier).listeners, entry) 15 | 16 | static struct listener *listener_new(listener_cb_t notify, void_callback_t destroy, void *userdata); 17 | static void listener_destroy(struct listener *listener); 18 | static enum listener_return listener_notify(struct listener *listener, void *arg); 19 | 20 | int change_notifier_init(struct notifier *notifier) { 21 | int ok; 22 | 23 | ok = pthread_mutex_init(¬ifier->mutex, NULL); 24 | if (ok != 0) { 25 | return ok; 26 | } 27 | 28 | list_inithead(¬ifier->listeners); 29 | notifier->is_value_notifier = false; 30 | notifier->state = NULL; 31 | notifier->value_destroy_callback = NULL; 32 | return 0; 33 | } 34 | 35 | int value_notifier_init(struct notifier *notifier, void *initial_value, void_callback_t value_destroy_callback) { 36 | int ok; 37 | 38 | ok = pthread_mutex_init(¬ifier->mutex, NULL); 39 | if (ok != 0) { 40 | return ok; 41 | } 42 | 43 | list_inithead(¬ifier->listeners); 44 | notifier->is_value_notifier = true; 45 | notifier->state = initial_value; 46 | notifier->value_destroy_callback = value_destroy_callback; 47 | 48 | return 0; 49 | } 50 | 51 | struct notifier *change_notifier_new() { 52 | struct notifier *n; 53 | int ok; 54 | 55 | n = malloc(sizeof *n); 56 | if (n == NULL) { 57 | return NULL; 58 | } 59 | 60 | ok = change_notifier_init(n); 61 | if (ok != 0) { 62 | free(n); 63 | return NULL; 64 | } 65 | 66 | return n; 67 | } 68 | 69 | struct notifier *value_notifier_new(void *initial_value, void_callback_t value_destroy_callback) { 70 | struct notifier *n; 71 | int ok; 72 | 73 | n = malloc(sizeof *n); 74 | if (n == NULL) { 75 | return NULL; 76 | } 77 | 78 | ok = value_notifier_init(n, initial_value, value_destroy_callback); 79 | if (ok != 0) { 80 | free(n); 81 | return NULL; 82 | } 83 | 84 | return n; 85 | } 86 | 87 | void notifier_deinit(struct notifier *notifier) { 88 | for_each_listener_in_notifier(*notifier, l) { 89 | listener_destroy(l); 90 | } 91 | 92 | pthread_mutex_destroy(¬ifier->mutex); 93 | ASSERT_MSG(list_is_empty(¬ifier->listeners), "Listener list was not empty after removing all listeners"); 94 | if (notifier->value_destroy_callback != NULL) { 95 | notifier->value_destroy_callback(notifier->state); 96 | } 97 | } 98 | 99 | void notifier_destroy(struct notifier *notifier) { 100 | notifier_deinit(notifier); 101 | free(notifier); 102 | } 103 | 104 | DEFINE_LOCK_OPS(notifier, mutex) 105 | 106 | struct listener *notifier_listen(struct notifier *notifier, listener_cb_t notify, void_callback_t destroy, void *userdata) { 107 | enum listener_return r; 108 | struct listener *l; 109 | 110 | l = listener_new(notify, destroy, userdata); 111 | if (l == NULL) { 112 | return NULL; 113 | } 114 | 115 | r = listener_notify(l, notifier->state); 116 | if (r == kUnlisten) { 117 | listener_destroy(l); 118 | return NULL; 119 | } 120 | 121 | notifier_lock(notifier); 122 | 123 | list_add(&l->entry, ¬ifier->listeners); 124 | 125 | notifier_unlock(notifier); 126 | 127 | return l; 128 | } 129 | 130 | int notifier_unlisten(struct notifier *notifier, struct listener *listener) { 131 | notifier_lock(notifier); 132 | 133 | listener_destroy(listener); 134 | 135 | notifier_unlock(notifier); 136 | 137 | return 0; 138 | } 139 | 140 | void notifier_notify(struct notifier *notifier, void *arg) { 141 | enum listener_return r; 142 | 143 | notifier_lock(notifier); 144 | 145 | if (notifier->value_destroy_callback != NULL) { 146 | notifier->value_destroy_callback(notifier->state); 147 | } 148 | notifier->state = arg; 149 | 150 | for_each_listener_in_notifier(*notifier, l) { 151 | r = listener_notify(l, arg); 152 | if (r == kUnlisten) { 153 | listener_destroy(l); 154 | } 155 | } 156 | 157 | notifier_unlock(notifier); 158 | } 159 | 160 | static struct listener *listener_new(listener_cb_t notify, void_callback_t destroy, void *userdata) { 161 | struct listener *listener; 162 | 163 | listener = malloc(sizeof *listener); 164 | if (listener == NULL) { 165 | return NULL; 166 | } 167 | 168 | listener->entry = (struct list_head){ NULL, NULL }; 169 | listener->notify = notify; 170 | listener->destroy = destroy; 171 | listener->userdata = userdata; 172 | 173 | return listener; 174 | } 175 | 176 | static void listener_destroy(struct listener *listener) { 177 | if (listener->destroy != NULL) { 178 | listener->destroy(listener->userdata); 179 | } 180 | 181 | if (list_is_linked(&listener->entry)) { 182 | list_del(&listener->entry); 183 | } 184 | 185 | free(listener); 186 | } 187 | 188 | static enum listener_return listener_notify(struct listener *listener, void *arg) { 189 | return listener->notify(arg, listener->userdata); 190 | } 191 | -------------------------------------------------------------------------------- /src/notifier_listener.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Notifier/Listener 4 | * 5 | * Basically a nicer wrapper around callbacks. 6 | * 7 | * Notifiers are event sources that can be listened to. A listener 8 | * is a callbacks that's registered for listening to a notifier. 9 | * 10 | * Copyright (c) 2023, Hannes Winkler 11 | */ 12 | 13 | #ifndef _FLUTTERPI_SRC_NOTIFIER_LISTENER_H 14 | #define _FLUTTERPI_SRC_NOTIFIER_LISTENER_H 15 | 16 | #include "util/collection.h" 17 | #include "util/list.h" 18 | #include "util/lock_ops.h" 19 | 20 | enum listener_return { kNoAction, kUnlisten }; 21 | 22 | typedef enum listener_return (*listener_cb_t)(void *arg, void *userdata); 23 | 24 | struct listener; 25 | 26 | struct notifier { 27 | pthread_mutex_t mutex; 28 | 29 | struct list_head listeners; 30 | 31 | bool is_value_notifier; 32 | void *state; 33 | void_callback_t value_destroy_callback; 34 | }; 35 | 36 | /** 37 | * @brief Initialize this pre-allocated notifier object as a change notifier. 38 | * 39 | * Change notifiers will only notify their listeners when @ref notifier_notify 40 | * is called. They don't call any new listeners with the last notified value, as 41 | * value notifiers do. 42 | * 43 | */ 44 | int change_notifier_init(struct notifier *notifier); 45 | 46 | /** 47 | * @brief Initialize this pre-allocated notifier object as a value notifier. 48 | * 49 | * Value notifiers will remember the last notified value and immediately call 50 | * any new listeners with the last notified value (or the one given to this 51 | * initializer, if @ref notifier_notify was never called). 52 | * 53 | */ 54 | int value_notifier_init(struct notifier *notifier, void *initial_value, void_callback_t value_destroy_callback); 55 | 56 | /** 57 | * @brief Create a new heap allocated change notifier. 58 | * 59 | * For the behaviour of change notifiers, see @ref change_notifier_init. 60 | * 61 | */ 62 | struct notifier *change_notifier_new(); 63 | 64 | /** 65 | * @brief Create a new heap allocated value notifier. 66 | * 67 | * For the behaviour of value notifiers, see @ref value_notifier_init. 68 | * 69 | */ 70 | struct notifier *value_notifier_new(void *initial_value, void_callback_t value_destroy_callback); 71 | 72 | /** 73 | * @brief De-initialize this notifier, destroying all listeners and freeing all 74 | * allocated resources. (But not the memory @arg notifier points to itself). 75 | * 76 | * Use this if you use @ref change_notifier_init or @ref value_notifier_init to 77 | * setup your notifier. 78 | * 79 | * If value_destroy_callback is not NULL, will invoke the value_destroy_callback on 80 | * the last value (either initial_value or the last value given to notifier_notify). 81 | * 82 | * Note that this does not wait for any currently executing callbacks to complete. 83 | * So if this is a value notifier and you call @ref notifier_deinit while any other thread 84 | * is currently inside @ref notifier_notify, this could destroy the void* value passed to 85 | * any listener callbacks while the listener callback is running. Other stuff could be racy 86 | * too. So the lifetime needs to be managed externally too. (as always basically) 87 | */ 88 | void notifier_deinit(struct notifier *notifier); 89 | 90 | /** 91 | * @brief De-initialize this notifier AND free the memory @arg notifier points to. 92 | * 93 | * Use this if you used @ref change_notifier_new or @ref value_notifier_new to 94 | * setup your notifier. 95 | * 96 | * Note that this does not wait for any currently executing listener callbacks to finish. 97 | * 98 | * 99 | */ 100 | void notifier_destroy(struct notifier *notifier); 101 | 102 | DECLARE_LOCK_OPS(notifier) 103 | 104 | /** 105 | * @brief Add a listener that should listen to this notifier. 106 | * 107 | * @param notifier The notifier to listen to. 108 | * @param notify will be called when the event-producing object calls notifier_notify, and additionally 109 | * (if the notifier is a value notifier) one time with the current value immediately inside 110 | * the notifier_listen function. 111 | * @param destroy will be called when the listener is destroyed for some reason. Either when @ref notifier_unlisten 112 | * was called or when the notify callback returned kUnlisten, or when the notifier is destroyed. 113 | * @param userdata The userdata to be passed to the @param notify and @param destroy callbacks. 114 | * 115 | * @returns On success: A new listener object, only really useful for calling @ref notifier_unlisten, 116 | * or NULL when the @arg notifier is a value notifier, and the @arg notify function returned 117 | * kUnlisten when it was called synchronously inside the @ref notifier_listen function. 118 | * On failure: NULL (shouldn't happen though) 119 | */ 120 | struct listener *notifier_listen(struct notifier *notifier, listener_cb_t notify, void_callback_t destroy, void *userdata); 121 | 122 | /** 123 | * @brief If @param listener is currently registered as a listener to @param notifier, de-register 124 | * it and destroy it. Otherwise, do nothing and return an error code. 125 | * 126 | * This is only one way to de-register the listener. The other way is to return `kUnlisten` from 127 | * the listener callback. 128 | * 129 | * @param notifier The notifier from which the listener should be removed. 130 | * @param listener The listener that should no longer receive events and be destroyed 131 | * (will only be destroyed when it's currently registered as a listener to notifier) 132 | * @returns 0 on success, a positive errno error code otherwise. 133 | */ 134 | int notifier_unlisten(struct notifier *notifier, struct listener *listener); 135 | 136 | /** 137 | * @brief Notify all listeners about a new value. For any listeners registered 138 | * to @arg notifier, call the listener callback with @arg arg as the value. 139 | * 140 | * @param notifier The notifier for which all the listener callbacks should be called. 141 | * @param arg The value that should be send to the listeners. 142 | */ 143 | void notifier_notify(struct notifier *notifier, void *arg); 144 | 145 | #endif // _FLUTTERPI_SRC_NOTIFIER_LISTENER_H 146 | -------------------------------------------------------------------------------- /src/pixel_format.c: -------------------------------------------------------------------------------- 1 | #include "pixel_format.h" 2 | 3 | #include "config.h" 4 | 5 | #ifdef HAVE_FBDEV 6 | #define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ 7 | .fbdev_format = { \ 8 | .r = { .length = r_length, .offset = r_offset, .msb_right = 0 }, \ 9 | .g = { .length = g_length, .offset = g_offset, .msb_right = 0 }, \ 10 | .b = { .length = b_length, .offset = b_offset, .msb_right = 0 }, \ 11 | .a = { .length = a_length, .offset = a_offset, .msb_right = 0 }, \ 12 | }, 13 | #else 14 | #define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) 15 | #endif 16 | 17 | #ifdef HAVE_GBM 18 | #include 19 | #define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) .gbm_format = _gbm_format, 20 | #else 21 | #define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) 22 | #endif 23 | 24 | #ifdef HAVE_KMS 25 | #include 26 | #define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) .drm_format = _drm_format, 27 | #else 28 | #define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) 29 | #endif 30 | 31 | #ifdef HAVE_VULKAN 32 | #include "vulkan.h" 33 | #define VK_FORMAT_FIELD_INITIALIZER(_vk_format) .vk_format = _vk_format, 34 | #else 35 | #define VK_FORMAT_FIELD_INITIALIZER(_vk_format) 36 | #endif 37 | 38 | // clang-format off 39 | #define PIXFMT_MAPPING( \ 40 | _name, \ 41 | _arg_name, \ 42 | _format, \ 43 | _bpp, \ 44 | _bit_depth, \ 45 | _is_opaque, \ 46 | _vk_format, \ 47 | r_length, \ 48 | r_offset, \ 49 | g_length, \ 50 | g_offset, \ 51 | b_length, \ 52 | b_offset, \ 53 | a_length, \ 54 | a_offset, \ 55 | _gbm_format, \ 56 | _drm_format \ 57 | ) { \ 58 | .name = _name, \ 59 | .arg_name = _arg_name, \ 60 | .format = _format, \ 61 | .bits_per_pixel = _bpp, \ 62 | .bit_depth = _bit_depth, \ 63 | .is_opaque = _is_opaque, \ 64 | VK_FORMAT_FIELD_INITIALIZER(_vk_format) \ 65 | FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ 66 | GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) DRM_FORMAT_FIELD_INITIALIZER(_drm_format) \ 67 | }, 68 | // clang-format on 69 | 70 | const struct pixfmt_info pixfmt_infos[] = { PIXFMT_LIST(PIXFMT_MAPPING) }; 71 | 72 | /// hack so we can use COMPILE_ASSERT. 73 | enum { n_pixfmt_infos_constexpr = sizeof(pixfmt_infos) / sizeof(*pixfmt_infos) }; 74 | 75 | const size_t n_pixfmt_infos = n_pixfmt_infos_constexpr; 76 | 77 | COMPILE_ASSERT(n_pixfmt_infos_constexpr == PIXFMT_MAX + 1); 78 | 79 | #ifdef DEBUG 80 | void assert_pixfmt_list_valid() { 81 | for (enum pixfmt format = 0; format < PIXFMT_COUNT; format++) { 82 | assert(pixfmt_infos[format].format == format); 83 | } 84 | } 85 | #endif 86 | -------------------------------------------------------------------------------- /src/pluginregistry.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Plugin Registry 4 | * 5 | * Initializes & deinitializes plugins, manages registration of plugins. 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_PLUGINREGISTRY_H 11 | #define _FLUTTERPI_SRC_PLUGINREGISTRY_H 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "platformchannel.h" 19 | 20 | struct flutterpi; 21 | struct plugin_registry; 22 | 23 | typedef enum plugin_init_result (*plugin_init_t)(struct flutterpi *flutterpi, void **userdata_out); 24 | 25 | typedef void (*plugin_deinit_t)(struct flutterpi *flutterpi, void *userdata); 26 | 27 | struct flutterpi_plugin_v2 { 28 | const char *name; 29 | plugin_init_t init; 30 | plugin_deinit_t deinit; 31 | }; 32 | 33 | /// The return value of a plugin initializer function. 34 | enum plugin_init_result { 35 | PLUGIN_INIT_RESULT_INITIALIZED, ///< The plugin was successfully initialized. 36 | PLUGIN_INIT_RESULT_NOT_APPLICABLE, ///< The plugin couldn't be initialized, because it's not compatible with the flutter-pi instance. 37 | /// For example, the plugin requires OpenGL but flutter-pi is using software rendering. 38 | /// This is not an error, and flutter-pi will continue initializing the other plugins. 39 | PLUGIN_INIT_RESULT_ERROR ///< The plugin couldn't be initialized because an unexpected error ocurred. 40 | /// Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. 41 | }; 42 | 43 | struct _FlutterPlatformMessageResponseHandle; 44 | typedef struct _FlutterPlatformMessageResponseHandle FlutterPlatformMessageResponseHandle; 45 | 46 | /// A Callback that gets called when a platform message 47 | /// arrives on a channel you registered it with. 48 | /// channel is the method channel that received a platform message, 49 | /// object is the object that is the result of automatically decoding 50 | /// the platform message using the codec given to plugin_registry_set_receiver. 51 | /// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec 52 | /// passed to plugin_registry_set_receiver. 53 | typedef int (*platch_obj_recv_callback)(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle); 54 | 55 | typedef void (*platform_message_callback_v2_t)(void *userdata, const FlutterPlatformMessage *message); 56 | 57 | /** 58 | * @brief Create a new plugin registry instance and add the hardcoded plugins, but don't initialize them yet. 59 | */ 60 | struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi); 61 | 62 | void plugin_registry_destroy(struct plugin_registry *registry); 63 | 64 | void plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin); 65 | 66 | int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry); 67 | 68 | /** 69 | * @brief Initialize all not-yet initialized plugins. 70 | */ 71 | int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry); 72 | 73 | /** 74 | * @brief Deinitialize all initialized plugins. 75 | */ 76 | void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry); 77 | 78 | /** 79 | * @brief Called by flutter-pi when a platform message arrives. 80 | */ 81 | int plugin_registry_on_platform_message(struct plugin_registry *registry, const FlutterPlatformMessage *message); 82 | 83 | /** 84 | * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. 85 | * 86 | * The platform message will be automatically decoded using the codec `codec`. 87 | */ 88 | int plugin_registry_set_receiver_v2_locked( 89 | struct plugin_registry *registry, 90 | const char *channel, 91 | platform_message_callback_v2_t callback, 92 | void *userdata 93 | ); 94 | 95 | /** 96 | * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. 97 | * 98 | * The platform message will be automatically decoded using the codec `codec`. 99 | */ 100 | int plugin_registry_set_receiver_v2( 101 | struct plugin_registry *registry, 102 | const char *channel, 103 | platform_message_callback_v2_t callback, 104 | void *userdata 105 | ); 106 | 107 | /** 108 | * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. 109 | * 110 | * The platform message will be automatically decoded using the codec `codec`. 111 | */ 112 | int plugin_registry_set_receiver_locked(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); 113 | 114 | /** 115 | * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. 116 | * 117 | * The platform message will be automatically decoded using the codec `codec`. 118 | */ 119 | int plugin_registry_set_receiver(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); 120 | 121 | /** 122 | * @brief Removes the callback for platform channel `channel`. 123 | * 124 | */ 125 | int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, const char *channel); 126 | 127 | /** 128 | * @brief Removes the callback for platform channel `channel`. 129 | * 130 | */ 131 | int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel); 132 | 133 | /** 134 | * @brief Removes the callback for platform channel `channel`. 135 | * 136 | */ 137 | int plugin_registry_remove_receiver_locked(const char *channel); 138 | 139 | /** 140 | * @brief Removes the callback for platform channel `channel`. 141 | * 142 | */ 143 | int plugin_registry_remove_receiver(const char *channel); 144 | 145 | void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name); 146 | 147 | void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registry, const char *plugin_name); 148 | 149 | /** 150 | * @brief Returns true @ref registry has a plugin with name @ref plugin_name. 151 | */ 152 | bool plugin_registry_is_plugin_present(struct plugin_registry *registry, const char *plugin_name); 153 | 154 | /** 155 | * @brief Returns true @ref registry has a plugin with name @ref plugin_name. 156 | */ 157 | bool plugin_registry_is_plugin_present_locked(struct plugin_registry *registry, const char *plugin_name); 158 | 159 | int plugin_registry_deinit(void); 160 | 161 | void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin); 162 | 163 | void static_plugin_registry_remove_plugin(const char *plugin_name); 164 | 165 | #define FLUTTERPI_PLUGIN(_name, _identifier_name, _init, _deinit) \ 166 | __attribute__((constructor)) static void __reg_plugin_##_identifier_name() { \ 167 | static struct flutterpi_plugin_v2 plugin = { \ 168 | .name = (_name), \ 169 | .init = (_init), \ 170 | .deinit = (_deinit), \ 171 | }; \ 172 | static_plugin_registry_add_plugin(&plugin); \ 173 | } \ 174 | \ 175 | __attribute__((destructor)) static void __unreg_plugin_##_identifier_name() { static_plugin_registry_remove_plugin(_name); } 176 | 177 | #endif // _FLUTTERPI_SRC_PLUGINREGISTRY_H 178 | -------------------------------------------------------------------------------- /src/plugins/audioplayers.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIOPLAYERS_H_ 2 | #define AUDIOPLAYERS_H_ 3 | 4 | #include 5 | #include 6 | 7 | struct audio_player; 8 | 9 | struct audio_player *audio_player_new(char *playerId, char *channel); 10 | 11 | // Instance function 12 | 13 | int64_t audio_player_get_position(struct audio_player *self); 14 | 15 | int64_t audio_player_get_duration(struct audio_player *self); 16 | 17 | bool audio_player_get_looping(struct audio_player *self); 18 | 19 | void audio_player_play(struct audio_player *self); 20 | 21 | void audio_player_pause(struct audio_player *self); 22 | 23 | void audio_player_resume(struct audio_player *self); 24 | 25 | void audio_player_destroy(struct audio_player *self); 26 | 27 | void audio_player_set_looping(struct audio_player *self, bool isLooping); 28 | 29 | void audio_player_set_volume(struct audio_player *self, double volume); 30 | 31 | void audio_player_set_playback_rate(struct audio_player *self, double rate); 32 | 33 | void audio_player_set_balance(struct audio_player *self, double balance); 34 | 35 | void audio_player_set_position(struct audio_player *self, int64_t position); 36 | 37 | void audio_player_set_source_url(struct audio_player *self, char *url); 38 | 39 | bool audio_player_is_id(struct audio_player *self, char *id); 40 | 41 | const char* audio_player_subscribe_channel_name(const struct audio_player *self); 42 | 43 | ///Asks to subscribe to channel events 44 | /// 45 | ///`value` - Indicates whether to subscribe or unsubscribe 46 | /// 47 | ///Returns `true` if player uses `channel`, otherwise returns `false 48 | bool audio_player_set_subscription_status(struct audio_player *self, const char *channel, bool value); 49 | 50 | void audio_player_release(struct audio_player *self); 51 | 52 | #endif // AUDIOPLAYERS_H_ 53 | -------------------------------------------------------------------------------- /src/plugins/audioplayers/README.md: -------------------------------------------------------------------------------- 1 | ## audioplayers plugin 2 | 3 | ### Requirements 4 | 5 | - `audioplayers` version `^4.0.0` 6 | - Working gstreamer installation, including corresponding audio plugin (e.g. `gstreamer1.0-alsa`) 7 | 8 | ### Troubleshooting 9 | 10 | - Check that you can list ALSA devices via command `aplay -L`; 11 | - Check that you can launch `playbin` on any audio file via `gst-launch`; 12 | - Make sure `pulseaudio` is deleted 13 | 14 | ### pulseaudio 15 | 16 | Please note that plugin was not tested with `pulseaudio` and it is up to you to make gstreamer work via it. 17 | As `pulseaudio` takes full control over audio devices, `ALSA` will no longer function correctly with `pulseaudio` installed 18 | -------------------------------------------------------------------------------- /src/plugins/charset_converter.c: -------------------------------------------------------------------------------- 1 | #include "plugins/charset_converter.h" 2 | #include "flutter-pi.h" 3 | #include "pluginregistry.h" 4 | #include "util/logging.h" 5 | #include 6 | 7 | static bool convert(char *inbuf, char *outbuf, size_t len, const char *from, const char *to) 8 | { 9 | iconv_t iconv_cd = iconv_open(to, from); 10 | if (iconv_cd == (iconv_t) -1) { 11 | LOG_ERROR("Conversion from charset \"%s\" to charset \"%s\" is not supported. iconv_open: %s\n", from, to, strerror(errno)); 12 | return false; 13 | } 14 | 15 | size_t inlen = len; 16 | size_t outlen = len; 17 | size_t res = 0; 18 | 19 | while (inlen > 0 && outlen > 0) { 20 | res = iconv(iconv_cd, &inbuf, &inlen, &outbuf, &outlen); 21 | if (res == 0) { 22 | break; 23 | } 24 | 25 | if (res == (size_t) (-1)) { 26 | iconv_close(iconv_cd); 27 | *outbuf = '\0'; 28 | 29 | return false; 30 | } 31 | } 32 | 33 | iconv_close(iconv_cd); 34 | *outbuf = '\0'; 35 | 36 | return true; 37 | } 38 | 39 | static int on_encode(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) { 40 | struct std_value *args, *tmp; 41 | char *charset, *input, *output; 42 | 43 | args = &object->std_arg; 44 | 45 | if (args == NULL || !STDVALUE_IS_MAP(*args)) { 46 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg` to be a map."); 47 | } 48 | 49 | tmp = stdmap_get_str(&object->std_arg, "charset"); 50 | if (tmp == NULL || !STDVALUE_IS_STRING(*tmp)) { 51 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg['charset'] to be a string."); 52 | } 53 | 54 | charset = STDVALUE_AS_STRING(*tmp); 55 | 56 | tmp = stdmap_get_str(&object->std_arg, "data"); 57 | if (tmp == NULL || !STDVALUE_IS_STRING(*tmp)) { 58 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg['data'] to be a string."); 59 | } 60 | 61 | input = STDVALUE_AS_STRING(*tmp); 62 | output = malloc(strlen(input) + 1); 63 | 64 | bool res = convert(input, output, strlen(input) + 1, "UTF-8", charset); 65 | if(!res) { 66 | free(output); 67 | return platch_respond_error_std(response_handle, "error_id", "charset_name_unrecognized", NULL); 68 | } 69 | 70 | int ok = platch_respond_success_std( 71 | response_handle, 72 | &(struct std_value) { 73 | .type = kStdUInt8Array, 74 | .size = strlen(output), 75 | .uint8array = (uint8_t*) output, 76 | } 77 | ); 78 | 79 | free(output); 80 | 81 | return ok; 82 | } 83 | 84 | static int on_decode(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) { 85 | struct std_value *args, *tmp; 86 | char *charset, *output; 87 | const uint8_t *input; 88 | 89 | args = &object->std_arg; 90 | 91 | if (args == NULL || !STDVALUE_IS_MAP(*args)) { 92 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg` to be a map."); 93 | } 94 | 95 | tmp = stdmap_get_str(&object->std_arg, "charset"); 96 | if (tmp == NULL || !STDVALUE_IS_STRING(*tmp)) { 97 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg['charset'] to be a string."); 98 | } 99 | 100 | charset = STDVALUE_AS_STRING(*tmp); 101 | 102 | tmp = stdmap_get_str(&object->std_arg, "data"); 103 | if (tmp == NULL || (*tmp).type != kStdUInt8Array ) { 104 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg['data'] to be a uint8_t list."); 105 | } 106 | 107 | input = tmp->uint8array; 108 | output = malloc(strlen((char*) input) + 1); 109 | 110 | bool res = convert((char*) input, output, strlen((char*) input) + 1, "UTF-8", charset); 111 | if(!res) { 112 | free(output); 113 | return platch_respond_error_std(response_handle, "error_id", "charset_name_unrecognized", NULL); 114 | } 115 | 116 | int ok = platch_respond_success_std( 117 | response_handle, 118 | &(struct std_value) { 119 | .type = kStdUInt8Array, 120 | .size = strlen(output), 121 | .uint8array = (uint8_t*) output, 122 | } 123 | ); 124 | 125 | free(output); 126 | 127 | return ok; 128 | } 129 | 130 | static int on_available_charsets(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) { 131 | (void) object; 132 | 133 | char* output; 134 | size_t length, count; 135 | FILE *fp; 136 | 137 | // Count the available charsets 138 | fp = popen("iconv --list | wc -w", "r"); 139 | if(!fp) { 140 | return platch_respond_error_std(response_handle, "error_id", "charsets_not_available", NULL); 141 | } 142 | 143 | while (getline(&output, &length, fp) >= 0) { 144 | count = atoi(output); 145 | } 146 | 147 | pclose(fp); 148 | 149 | fp = popen("iconv --list", "r"); 150 | if(!fp) { 151 | return platch_respond_error_std(response_handle, "error_id", "charsets_not_available", NULL); 152 | } 153 | 154 | struct std_value values; 155 | 156 | values.type = kStdList; 157 | values.size = count; 158 | values.list = alloca(sizeof(struct std_value) * count); 159 | 160 | for (int index = 0; index < count; index++) { 161 | if(getline(&output, &length, fp) < 0) { 162 | break; 163 | } 164 | 165 | strtok(output, "/"); 166 | 167 | values.list[index].type = kStdString; 168 | values.list[index].string_value = strdup(output); 169 | } 170 | 171 | pclose(fp); 172 | 173 | return platch_respond_success_std(response_handle, &values); 174 | } 175 | 176 | static int on_check(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) { 177 | struct std_value *args, *tmp; 178 | char *charset; 179 | 180 | args = &object->std_arg; 181 | 182 | if (args == NULL || !STDVALUE_IS_MAP(*args)) { 183 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg` to be a map."); 184 | } 185 | 186 | tmp = stdmap_get_str(&object->std_arg, "charset"); 187 | if (tmp == NULL || !STDVALUE_IS_STRING(*tmp)) { 188 | return platch_respond_illegal_arg_std(response_handle, "Expected `arg['charset'] to be a string."); 189 | } 190 | 191 | charset = STDVALUE_AS_STRING(*tmp); 192 | 193 | iconv_t iconv_cd = iconv_open("UTF-8", charset); 194 | if (iconv_cd == (iconv_t) -1) { 195 | return platch_respond( 196 | response_handle, 197 | &(struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = { .type = kStdFalse } } 198 | ); 199 | } 200 | 201 | iconv_close(iconv_cd); 202 | 203 | return platch_respond( 204 | response_handle, 205 | &(struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = { .type = kStdTrue } } 206 | ); 207 | } 208 | 209 | static int on_receive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) { 210 | (void) channel; 211 | 212 | const char *method; 213 | method = object->method; 214 | 215 | if (streq(method, "encode")) { 216 | return on_encode(object, response_handle); 217 | } else if (streq(method, "decode")) { 218 | return on_decode(object, response_handle); 219 | } else if (streq(method, "availableCharsets")) { 220 | return on_available_charsets(object, response_handle); 221 | } else if (streq(method, "check")) { 222 | return on_check(object, response_handle); 223 | } 224 | 225 | return platch_respond_not_implemented(response_handle); 226 | } 227 | 228 | enum plugin_init_result charset_converter_init(struct flutterpi *flutterpi, void **userdata_out) { 229 | (void) flutterpi; 230 | 231 | int ok; 232 | 233 | ok = plugin_registry_set_receiver_locked(CHARSET_CONVERTER_CHANNEL, kStandardMethodCall, on_receive); 234 | if (ok != 0) { 235 | return PLUGIN_INIT_RESULT_ERROR; 236 | } 237 | 238 | *userdata_out = NULL; 239 | 240 | return PLUGIN_INIT_RESULT_INITIALIZED; 241 | } 242 | 243 | void charset_converter_deinit(struct flutterpi *flutterpi, void *userdata) { 244 | (void) userdata; 245 | 246 | plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), CHARSET_CONVERTER_CHANNEL); 247 | } 248 | 249 | FLUTTERPI_PLUGIN("charset converter plugin", charset_converter_plugin, charset_converter_init, charset_converter_deinit) -------------------------------------------------------------------------------- /src/plugins/charset_converter.h: -------------------------------------------------------------------------------- 1 | #ifndef _CHARSET_CONVERTER_PLUGIN_H 2 | #define _CHARSET_CONVERTER_PLUGIN_H 3 | 4 | #include 5 | #include 6 | 7 | #define CHARSET_CONVERTER_CHANNEL "charset_converter" 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/plugins/raw_keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef _KEY_EVENT_H 2 | #define _KEY_EVENT_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | union { 13 | struct { 14 | bool shift : 1; 15 | bool capslock : 1; 16 | bool ctrl : 1; 17 | bool alt : 1; 18 | bool numlock : 1; 19 | int __pad : 23; 20 | bool meta : 1; 21 | }; 22 | uint32_t u32; 23 | }; 24 | } key_modifiers_t; 25 | 26 | struct key_event_interface { 27 | void (*send_key_event)(void *userdata, const FlutterKeyEvent *event); 28 | }; 29 | 30 | struct rawkb; 31 | 32 | #define KEY_EVENT_CHANNEL "flutter/keyevent" 33 | 34 | int rawkb_send_android_keyevent( 35 | uint32_t flags, 36 | uint32_t code_point, 37 | unsigned int key_code, 38 | uint32_t plain_code_point, 39 | uint32_t scan_code, 40 | uint32_t meta_state, 41 | uint32_t source, 42 | uint16_t vendor_id, 43 | uint16_t product_id, 44 | uint16_t device_id, 45 | int repeat_count, 46 | bool is_down, 47 | char *character 48 | ); 49 | 50 | int rawkb_send_gtk_keyevent(uint32_t unicode_scalar_values, uint32_t key_code, uint32_t scan_code, uint32_t modifiers, bool is_down); 51 | 52 | int rawkb_on_key_event( 53 | struct rawkb *rawkb, 54 | uint64_t timestamp_us, 55 | xkb_keycode_t xkb_keycode, 56 | xkb_keysym_t xkb_keysym, 57 | uint32_t plain_codepoint, 58 | key_modifiers_t modifiers, 59 | const char *text, 60 | bool is_down, 61 | bool is_repeat 62 | ); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /src/plugins/sentry/sentry.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardera/flutter-pi/0d1f85aadc851e127215dca19b1fe4cc23030f27/src/plugins/sentry/sentry.h -------------------------------------------------------------------------------- /src/plugins/services.h: -------------------------------------------------------------------------------- 1 | #ifndef _SERVICES_PLUGIN_H 2 | #define _SERVICES_PLUGIN_H 3 | 4 | #include 5 | #include 6 | 7 | #define ORIENTATION_FROM_STRING(str) \ 8 | (streq(str, "DeviceOrientation.portraitUp") ? kPortraitUp : \ 9 | streq(str, "DeviceOrientation.landscapeLeft") ? kLandscapeLeft : \ 10 | streq(str, "DeviceOrientation.portraitDown") ? kPortraitDown : \ 11 | streq(str, "DeviceOrientation.landscapeRight") ? kLandscapeRight : \ 12 | -1) 13 | 14 | #define FLUTTER_NAVIGATION_CHANNEL "flutter/navigation" 15 | #define FLUTTER_ISOLATE_CHANNEL "flutter/isolate" 16 | #define FLUTTER_PLATFORM_CHANNEL "flutter/platform" 17 | #define FLUTTER_ACCESSIBILITY_CHANNEL "flutter/accessibility" 18 | #define FLUTTER_PLATFORM_VIEWS_CHANNEL "flutter/platform_views" 19 | #define FLUTTER_MOUSECURSOR_CHANNEL "flutter/mousecursor" 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/plugins/testplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEST_PLUGIN_H 2 | #define _TEST_PLUGIN_H 3 | 4 | #include 5 | #include 6 | 7 | #define TESTPLUGIN_CHANNEL_JSON "flutter-pi/testjson" 8 | #define TESTPLUGIN_CHANNEL_STD "flutter-pi/teststd" 9 | #define TESTPLUGIN_CHANNEL_PING "flutter-pi/ping" 10 | 11 | extern int testp_init(void); 12 | extern int testp_deinit(void); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/plugins/text_input.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEXT_INPUT_H 2 | #define _TEXT_INPUT_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define TEXT_INPUT_CHANNEL "flutter/textinput" 9 | 10 | #define TEXT_INPUT_MAX_CHARS 8192 11 | 12 | enum text_input_type { 13 | kInputTypeText, 14 | kInputTypeMultiline, 15 | kInputTypeNumber, 16 | kInputTypePhone, 17 | kInputTypeDatetime, 18 | kInputTypeEmailAddress, 19 | kInputTypeUrl, 20 | kInputTypeVisiblePassword, 21 | kInputTypeName, 22 | kInputTypeAddress, 23 | kInputTypeNone 24 | }; 25 | 26 | enum text_input_action { 27 | kTextInputActionNone, 28 | kTextInputActionUnspecified, 29 | kTextInputActionDone, 30 | kTextInputActionGo, 31 | kTextInputActionSearch, 32 | kTextInputActionSend, 33 | kTextInputActionNext, 34 | kTextInputActionPrevious, 35 | kTextInputActionContinueAction, 36 | kTextInputActionJoin, 37 | kTextInputActionRoute, 38 | kTextInputActionEmergencyCall, 39 | kTextInputActionNewline 40 | }; 41 | 42 | // while text input configuration has more values, we only care about these two. 43 | struct text_input_configuration { 44 | bool autocorrect; 45 | enum text_input_action input_action; 46 | }; 47 | 48 | enum floating_cursor_drag_state { kFloatingCursorDragStateStart, kFloatingCursorDragStateUpdate, kFloatingCursorDragStateEnd }; 49 | 50 | // parses the input string as linux terminal input and calls the TextInput model functions 51 | // accordingly. 52 | int textin_on_utf8_char(uint8_t *c); 53 | int textin_on_xkb_keysym(xkb_keysym_t keysym); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/render_surface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * render surface 4 | * 5 | * - A surface that can be scanned out, and that flutter can render into. 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #include "render_surface.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "compositor_ng.h" 17 | #include "render_surface_private.h" 18 | #include "surface.h" 19 | #include "tracer.h" 20 | #include "util/collection.h" 21 | 22 | // just so we can be sure &render_surface->surface is the same as (struct surface*) render_surface 23 | COMPILE_ASSERT(offsetof(struct render_surface, surface) == 0); 24 | 25 | #ifdef DEBUG 26 | static const uuid_t uuid = CONST_UUID(0x78, 0x70, 0x45, 0x13, 0xa8, 0xf3, 0x43, 0x34, 0xa0, 0xa3, 0xae, 0x90, 0xf1, 0x11, 0x41, 0xe0); 27 | #endif 28 | 29 | void render_surface_deinit(struct surface *s); 30 | 31 | int render_surface_init(struct render_surface *surface, struct tracer *tracer, struct vec2i size) { 32 | int ok; 33 | 34 | ok = surface_init(&surface->surface, tracer); 35 | if (ok != 0) { 36 | return ok; 37 | } 38 | 39 | surface->surface.deinit = render_surface_deinit; 40 | surface->surface.present_kms = NULL; 41 | surface->surface.present_fbdev = NULL; 42 | 43 | #ifdef DEBUG 44 | uuid_copy(&surface->uuid, uuid); 45 | #endif 46 | 47 | surface->size = size; 48 | surface->fill = NULL; 49 | surface->queue_present = NULL; 50 | return 0; 51 | } 52 | 53 | void render_surface_deinit(struct surface *s) { 54 | surface_deinit(s); 55 | } 56 | 57 | int render_surface_fill(struct render_surface *surface, FlutterBackingStore *fl_store) { 58 | int ok; 59 | 60 | ASSERT_NOT_NULL(surface); 61 | ASSERT_NOT_NULL(fl_store); 62 | ASSERT_NOT_NULL(surface->fill); 63 | 64 | ASSERT_EQUALS(fl_store->user_data, NULL); 65 | ASSERT_EQUALS(fl_store->did_update, false); 66 | 67 | TRACER_BEGIN(surface->surface.tracer, "render_surface_fill"); 68 | ok = surface->fill(surface, fl_store); 69 | TRACER_END(surface->surface.tracer, "render_surface_fill"); 70 | 71 | ASSERT_EQUALS(fl_store->user_data, NULL); 72 | ASSERT_EQUALS(fl_store->did_update, false); 73 | 74 | return ok; 75 | } 76 | 77 | int render_surface_queue_present(struct render_surface *surface, const FlutterBackingStore *fl_store) { 78 | int ok; 79 | 80 | ASSERT_NOT_NULL(surface); 81 | ASSERT_NOT_NULL(fl_store); 82 | ASSERT_NOT_NULL(surface->queue_present); 83 | 84 | TRACER_BEGIN(surface->surface.tracer, "render_surface_queue_present"); 85 | ok = surface->queue_present(surface, fl_store); 86 | TRACER_END(surface->surface.tracer, "render_surface_queue_present"); 87 | 88 | return ok; 89 | } 90 | 91 | #ifdef DEBUG 92 | ATTR_PURE struct render_surface *__checked_cast_render_surface(void *ptr) { 93 | struct render_surface *surface; 94 | 95 | surface = CAST_RENDER_SURFACE_UNCHECKED(ptr); 96 | assert(uuid_equals(surface->uuid, uuid)); 97 | return surface; 98 | } 99 | #endif 100 | -------------------------------------------------------------------------------- /src/render_surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Render surface 4 | * - are special kinds of surfaces that flutter can render into 5 | * - usually a render surface will have multiple framebuffers internally 6 | * - the compositor or window will request a framebuffer for flutter to render into 7 | * in form of a framebuffer using render_surface_fill. 8 | * - Once flutter has rendered into that backing store (whatever it's backed by), 9 | * the compositor will call render_surface_queue_present on the render surface, 10 | * and the argument backing store is the one that was provided using render_surface_fill. 11 | * - That framebuffer is the one that should be committed when the compositor/window calls surface_present_... 12 | * 13 | * Copyright (c) 2022, Hannes Winkler 14 | */ 15 | 16 | #ifndef _FLUTTERPI_SRC_RENDER_SURFACE_H 17 | #define _FLUTTERPI_SRC_RENDER_SURFACE_H 18 | 19 | #include 20 | 21 | #include "util/collection.h" 22 | 23 | struct surface; 24 | struct render_surface; 25 | 26 | #define CAST_RENDER_SURFACE_UNCHECKED(ptr) ((struct render_surface *) (ptr)) 27 | #ifdef DEBUG 28 | #define CAST_RENDER_SURFACE(ptr) __checked_cast_render_surface(ptr) 29 | ATTR_PURE struct render_surface *__checked_cast_render_surface(void *ptr); 30 | #else 31 | #define CAST_RENDER_SURFACE(ptr) CAST_RENDER_SURFACE_UNCHECKED(ptr) 32 | #endif 33 | 34 | int render_surface_fill(struct render_surface *store, FlutterBackingStore *fl_store); 35 | 36 | int render_surface_queue_present(struct render_surface *store, const FlutterBackingStore *fl_store); 37 | 38 | #endif // _FLUTTERPI_SRC_BACKING_STORE_H 39 | -------------------------------------------------------------------------------- /src/render_surface_private.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Render surface implementation 4 | * 5 | * - private implementation for render surfaces 6 | * - needed for implementing specific kinds of render surfaces 7 | * 8 | * Copyright (c) 2022, Hannes Winkler 9 | */ 10 | 11 | #ifndef _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H 12 | #define _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H 13 | 14 | #include 15 | 16 | #include "compositor_ng.h" 17 | #include "surface_private.h" 18 | #include "util/collection.h" 19 | 20 | struct render_surface { 21 | struct surface surface; 22 | 23 | #ifdef DEBUG 24 | uuid_t uuid; 25 | #endif 26 | 27 | struct vec2i size; 28 | int (*fill)(struct render_surface *surface, FlutterBackingStore *fl_store); 29 | int (*queue_present)(struct render_surface *surface, const FlutterBackingStore *fl_store); 30 | }; 31 | 32 | int render_surface_init(struct render_surface *surface, struct tracer *tracer, struct vec2i size); 33 | 34 | void render_surface_deinit(struct surface *s); 35 | 36 | #endif // _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H 37 | -------------------------------------------------------------------------------- /src/surface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Window Surface 4 | * 5 | * - provides an object that can be composited by flutter-pi 6 | * - (by calling present_kms or present_fbdev on it) 7 | * - == basically the thing that stores the graphics of a FlutterLayer 8 | * - render surfaces are special kinds of scanout surfaces that flutter can render into 9 | * - every scanout surface can be registered as a platform view to display it 10 | * 11 | * Copyright (c) 2022, Hannes Winkler 12 | */ 13 | 14 | #include "surface.h" 15 | 16 | #include 17 | #include 18 | 19 | #include "compositor_ng.h" 20 | #include "surface_private.h" 21 | #include "tracer.h" 22 | #include "util/collection.h" 23 | 24 | void surface_deinit(struct surface *s); 25 | 26 | #ifdef DEBUG 27 | static const uuid_t uuid = CONST_UUID(0xce, 0x35, 0x87, 0x0c, 0x82, 0x08, 0x46, 0x09, 0xbd, 0xab, 0x80, 0x67, 0x28, 0x15, 0x45, 0xb5); 28 | 29 | ATTR_PURE struct surface *__checked_cast_surface(void *ptr) { 30 | struct surface *s; 31 | 32 | s = CAST_SURFACE_UNCHECKED(ptr); 33 | assert(uuid_equals(s->uuid, uuid)); 34 | return s; 35 | } 36 | #endif 37 | 38 | int surface_init(struct surface *s, struct tracer *tracer) { 39 | #ifdef DEBUG 40 | uuid_copy(&s->uuid, uuid); 41 | #endif 42 | s->n_refs = REFCOUNT_INIT_1; 43 | pthread_mutex_init(&s->lock, NULL); 44 | s->tracer = tracer_ref(tracer); 45 | s->revision = 1; 46 | s->present_kms = NULL; 47 | s->present_fbdev = NULL; 48 | s->deinit = surface_deinit; 49 | return 0; 50 | } 51 | 52 | void surface_deinit(struct surface *s) { 53 | pthread_mutex_destroy(&s->lock); 54 | tracer_unref(s->tracer); 55 | } 56 | 57 | struct surface *surface_new(struct tracer *tracer) { 58 | struct surface *s; 59 | int ok; 60 | 61 | s = malloc(sizeof *s); 62 | if (s == NULL) { 63 | return NULL; 64 | } 65 | 66 | ok = surface_init(s, tracer); 67 | if (ok != 0) { 68 | free(s); 69 | return NULL; 70 | } 71 | 72 | return s; 73 | } 74 | 75 | void surface_destroy(struct surface *s) { 76 | ASSERT_NOT_NULL(s->deinit); 77 | s->deinit(s); 78 | free(s); 79 | } 80 | 81 | DEFINE_LOCK_OPS(surface, lock) 82 | 83 | DEFINE_REF_OPS(surface, n_refs) 84 | 85 | int64_t surface_get_revision(struct surface *s) { 86 | ASSERT_NOT_NULL(s); 87 | return s->revision; 88 | } 89 | 90 | int surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { 91 | int ok; 92 | 93 | ASSERT_NOT_NULL(s); 94 | ASSERT_NOT_NULL(props); 95 | ASSERT_NOT_NULL(builder); 96 | ASSERT_NOT_NULL(s->present_kms); 97 | 98 | TRACER_BEGIN(s->tracer, "surface_present_kms"); 99 | ok = s->present_kms(s, props, builder); 100 | TRACER_END(s->tracer, "surface_present_kms"); 101 | 102 | return ok; 103 | } 104 | 105 | int surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { 106 | int ok; 107 | 108 | ASSERT_NOT_NULL(s); 109 | ASSERT_NOT_NULL(props); 110 | ASSERT_NOT_NULL(builder); 111 | ASSERT_NOT_NULL(s->present_fbdev); 112 | 113 | TRACER_BEGIN(s->tracer, "surface_present_fbdev"); 114 | ok = s->present_fbdev(s, props, builder); 115 | TRACER_END(s->tracer, "surface_present_fbdev"); 116 | 117 | return ok; 118 | } 119 | -------------------------------------------------------------------------------- /src/surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Surface 4 | * 5 | * - rendering / scanout surface interface 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_SURFACE_H 11 | #define _FLUTTERPI_SRC_SURFACE_H 12 | 13 | #include "util/collection.h" 14 | #include "util/lock_ops.h" 15 | #include "util/refcounting.h" 16 | 17 | struct surface; 18 | struct compositor; 19 | struct fl_layer_props; 20 | struct kms_req_builder; 21 | struct fbdev_commit_builder; 22 | 23 | #define CAST_SURFACE_UNCHECKED(ptr) ((struct surface *) (ptr)) 24 | #ifdef DEBUG 25 | #define CAST_SURFACE(ptr) __checked_cast_surface(ptr) 26 | ATTR_PURE struct surface *__checked_cast_surface(void *ptr); 27 | #else 28 | #define CAST_SURFACE(ptr) CAST_SURFACE_UNCHECKED(ptr) 29 | #endif 30 | 31 | void surface_destroy(struct surface *s); 32 | 33 | DECLARE_LOCK_OPS(surface) 34 | 35 | DECLARE_REF_OPS(surface) 36 | 37 | ATTR_PURE static inline struct surface *surface_from_id(int64_t id) { 38 | return CAST_SURFACE(int64_to_ptr(id)); 39 | } 40 | 41 | ATTR_PURE int64_t surface_get_revision(struct surface *s); 42 | 43 | int surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); 44 | 45 | int surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); 46 | 47 | #endif // _FLUTTERPI_SRC_SURFACE_H 48 | -------------------------------------------------------------------------------- /src/surface_private.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Surface Implementation details 4 | * 5 | * - should be included for expanding the surface (i.e. for a backing store or platform view surface type) 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_SURFACE_PRIVATE_H 11 | #define _FLUTTERPI_SRC_SURFACE_PRIVATE_H 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include "util/collection.h" 18 | #include "util/refcounting.h" 19 | #include "util/uuid.h" 20 | 21 | struct fl_layer_props; 22 | struct kms_req_builder; 23 | struct fbdev_commit_builder; 24 | struct tracer; 25 | 26 | struct surface { 27 | #ifdef DEBUG 28 | uuid_t uuid; 29 | #endif 30 | refcount_t n_refs; 31 | pthread_mutex_t lock; 32 | struct tracer *tracer; 33 | int64_t revision; 34 | 35 | int (*present_kms)(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); 36 | int (*present_fbdev)(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); 37 | void (*deinit)(struct surface *s); 38 | }; 39 | 40 | int surface_init(struct surface *s, struct tracer *tracer); 41 | 42 | void surface_deinit(struct surface *s); 43 | 44 | #endif // _FLUTTERPI_SRC_SURFACE_PRIVATE_H 45 | -------------------------------------------------------------------------------- /src/texture_registry.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Texture Registry 4 | * 5 | * Manages flutter external texture registration, texture frame callbacks. 6 | * 7 | * Copyright (c) 2023, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_TEXTURE_REGISTRY_H 11 | #define _FLUTTERPI_SRC_TEXTURE_REGISTRY_H 12 | 13 | #include 14 | 15 | #include "config.h" 16 | 17 | #ifdef HAVE_EGL_GLES2 18 | #include "gles.h" 19 | #endif 20 | 21 | struct texture_registry_interface { 22 | int (*register_texture)(void *userdata, int64_t texture_identifier); 23 | int (*unregister_texture)(void *userdata, int64_t texture_identifier); 24 | int (*mark_frame_available)(void *userdata, int64_t texture_identifier); 25 | }; 26 | 27 | struct texture_registry; 28 | struct texture; 29 | 30 | #ifdef HAVE_EGL_GLES2 31 | struct gl_texture_frame { 32 | GLenum target; 33 | GLuint name; 34 | GLuint format; 35 | size_t width; 36 | size_t height; 37 | }; 38 | #endif 39 | 40 | struct texture_frame; 41 | struct texture_frame { 42 | union { 43 | #ifdef HAVE_EGL_GLES2 44 | struct gl_texture_frame gl; 45 | #endif 46 | }; 47 | void (*destroy)(const struct texture_frame *frame, void *userdata); 48 | void *userdata; 49 | }; 50 | 51 | struct unresolved_texture_frame { 52 | int (*resolve)(size_t width, size_t height, void *userdata, struct texture_frame *frame_out); 53 | void (*destroy)(void *userdata); 54 | void *userdata; 55 | }; 56 | 57 | struct texture_registry *texture_registry_new(const struct texture_registry_interface *interface, void *userdata); 58 | 59 | void texture_registry_destroy(struct texture_registry *reg); 60 | 61 | #ifdef HAVE_EGL_GLES2 62 | bool texture_registry_gl_external_texture_frame_callback( 63 | struct texture_registry *reg, 64 | int64_t texture_id, 65 | size_t width, 66 | size_t height, 67 | FlutterOpenGLTexture *texture_out 68 | ); 69 | #endif 70 | 71 | struct texture *texture_new(struct texture_registry *reg); 72 | 73 | int64_t texture_get_id(struct texture *texture); 74 | 75 | int texture_push_frame(struct texture *texture, const struct texture_frame *frame); 76 | 77 | int texture_push_unresolved_frame(struct texture *texture, const struct unresolved_texture_frame *frame); 78 | 79 | void texture_destroy(struct texture *texture); 80 | 81 | #endif // _FLUTTERPI_SRC_TEXTURE_REGISTRY_H 82 | -------------------------------------------------------------------------------- /src/tracer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Tracer - simple event tracing based on flutter event tracing interface 4 | * 5 | * Copyright (c) 2022, Hannes Winkler 6 | */ 7 | 8 | #include "tracer.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | #include "util/asserts.h" 15 | #include "util/collection.h" 16 | #include "util/logging.h" 17 | #include "util/refcounting.h" 18 | 19 | struct tracer { 20 | refcount_t n_refs; 21 | atomic_bool has_cbs; 22 | FlutterEngineTraceEventDurationBeginFnPtr trace_begin; 23 | FlutterEngineTraceEventDurationEndFnPtr trace_end; 24 | FlutterEngineTraceEventInstantFnPtr trace_instant; 25 | 26 | atomic_bool logged_discarded_events; 27 | }; 28 | 29 | struct tracer *tracer_new_with_cbs( 30 | FlutterEngineTraceEventDurationBeginFnPtr trace_begin, 31 | FlutterEngineTraceEventDurationEndFnPtr trace_end, 32 | FlutterEngineTraceEventInstantFnPtr trace_instant 33 | ) { 34 | struct tracer *tracer; 35 | 36 | tracer = malloc(sizeof *tracer); 37 | if (tracer == NULL) { 38 | goto fail_return_null; 39 | } 40 | 41 | tracer->n_refs = REFCOUNT_INIT_1; 42 | tracer->has_cbs = true; 43 | tracer->trace_begin = trace_begin; 44 | tracer->trace_end = trace_end; 45 | tracer->trace_instant = trace_instant; 46 | tracer->logged_discarded_events = false; 47 | return tracer; 48 | 49 | fail_return_null: 50 | return NULL; 51 | } 52 | 53 | struct tracer *tracer_new_with_stubs() { 54 | struct tracer *tracer; 55 | 56 | tracer = malloc(sizeof *tracer); 57 | if (tracer == NULL) { 58 | goto fail_return_null; 59 | } 60 | 61 | tracer->n_refs = REFCOUNT_INIT_1; 62 | tracer->has_cbs = false; 63 | tracer->trace_begin = NULL; 64 | tracer->trace_end = NULL; 65 | tracer->trace_instant = NULL; 66 | tracer->logged_discarded_events = false; 67 | return tracer; 68 | 69 | fail_return_null: 70 | return NULL; 71 | } 72 | 73 | void tracer_set_cbs( 74 | struct tracer *tracer, 75 | FlutterEngineTraceEventDurationBeginFnPtr trace_begin, 76 | FlutterEngineTraceEventDurationEndFnPtr trace_end, 77 | FlutterEngineTraceEventInstantFnPtr trace_instant 78 | ) { 79 | tracer->trace_begin = trace_begin; 80 | tracer->trace_end = trace_end; 81 | tracer->trace_instant = trace_instant; 82 | 83 | bool already_set_before = atomic_exchange(&tracer->has_cbs, true); 84 | ASSERT_MSG(!already_set_before, "tracing callbacks can only be set once."); 85 | (void) already_set_before; 86 | } 87 | 88 | void tracer_destroy(struct tracer *tracer) { 89 | free(tracer); 90 | } 91 | 92 | DEFINE_REF_OPS(tracer, n_refs) 93 | 94 | static void log_discarded_event(struct tracer *tracer, const char *name) { 95 | (void) name; 96 | if (atomic_exchange(&tracer->logged_discarded_events, true) == false) { 97 | LOG_DEBUG("Tracing event was discarded because tracer not initialized yet: %s. This message will only be logged once.\n", name); 98 | } 99 | } 100 | 101 | void __tracer_begin(struct tracer *tracer, const char *name) { 102 | ASSERT_NOT_NULL(tracer); 103 | ASSERT_NOT_NULL(name); 104 | if (atomic_load(&tracer->has_cbs)) { 105 | tracer->trace_begin(name); 106 | } else { 107 | log_discarded_event(tracer, name); 108 | } 109 | } 110 | 111 | void __tracer_end(struct tracer *tracer, const char *name) { 112 | ASSERT_NOT_NULL(tracer); 113 | ASSERT_NOT_NULL(name); 114 | if (atomic_load(&tracer->has_cbs)) { 115 | tracer->trace_end(name); 116 | } else { 117 | log_discarded_event(tracer, name); 118 | } 119 | } 120 | 121 | void __tracer_instant(struct tracer *tracer, const char *name) { 122 | ASSERT_NOT_NULL(tracer); 123 | ASSERT_NOT_NULL(name); 124 | if (atomic_load(&tracer->has_cbs)) { 125 | tracer->trace_instant(name); 126 | } else { 127 | log_discarded_event(tracer, name); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/tracer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Tracer - simple object for tracing using flutter event tracing interface. 4 | * 5 | * Copyright (c) 2022, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_TRACER_H 9 | #define _FLUTTERPI_SRC_TRACER_H 10 | 11 | #include 12 | 13 | #include "util/refcounting.h" 14 | 15 | struct tracer; 16 | 17 | struct tracer *tracer_new_with_cbs( 18 | FlutterEngineTraceEventDurationBeginFnPtr trace_begin, 19 | FlutterEngineTraceEventDurationEndFnPtr trace_end, 20 | FlutterEngineTraceEventInstantFnPtr trace_instant 21 | ); 22 | 23 | struct tracer *tracer_new_with_stubs(); 24 | 25 | DECLARE_REF_OPS(tracer); 26 | 27 | void __tracer_begin(struct tracer *tracer, const char *name); 28 | 29 | void __tracer_end(struct tracer *tracer, const char *name); 30 | 31 | void __tracer_instant(struct tracer *tracer, const char *name); 32 | 33 | void tracer_set_cbs( 34 | struct tracer *tracer, 35 | FlutterEngineTraceEventDurationBeginFnPtr trace_begin, 36 | FlutterEngineTraceEventDurationEndFnPtr trace_end, 37 | FlutterEngineTraceEventInstantFnPtr trace_instant 38 | ); 39 | 40 | #ifdef DEBUG 41 | #define TRACER_BEGIN(tracer, name) __tracer_begin(tracer, name) 42 | #define TRACER_END(tracer, name) __tracer_end(tracer, name) 43 | #define TRACER_INSTANT(tracer, name) __tracer_instant(tracer, name) 44 | #else 45 | // Cast tracer & name to void so we don't get unused variable warnings 46 | // in release mode. 47 | #define TRACER_BEGIN(tracer, name) \ 48 | do { \ 49 | (void) tracer; \ 50 | (void) name; \ 51 | } while (0) 52 | #define TRACER_END(tracer, name) \ 53 | do { \ 54 | (void) tracer; \ 55 | (void) name; \ 56 | } while (0) 57 | #define TRACER_INSTANT(tracer, name) \ 58 | do { \ 59 | (void) tracer; \ 60 | (void) name; \ 61 | } while (0) 62 | #endif 63 | 64 | #define DECLARE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name) \ 65 | static void trace_begin(struct obj_type_name *obj_var_name, const char *name); \ 66 | static void trace_end(struct obj_type_name *obj_var_name, const char *name); \ 67 | static void trace_instant(struct obj_type_name *obj_var_name, const char *name); 68 | 69 | #define DEFINE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name, tracer_member_name) \ 70 | static void trace_begin(struct obj_type_name *obj_var_name, const char *name) { \ 71 | return tracer_begin(obj_var_name->tracer_member_name, name); \ 72 | } \ 73 | static void trace_end(struct obj_type_name *obj_var_name, const char *name) { \ 74 | return tracer_end(obj_var_name->tracer_member_name, name); \ 75 | } \ 76 | static void trace_instant(struct obj_type_name *obj_var_name, const char *name) { \ 77 | return tracer_instant(obj_var_name->tracer_member_name, name); \ 78 | } 79 | 80 | #endif // _FLUTTERPI_SRC_TRACER_H 81 | -------------------------------------------------------------------------------- /src/user_input.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * User Input 4 | * 5 | * Collects user input from libinput, transforms it (partially into 6 | * more flutter compatible forms), and calls configured handler callbacks. 7 | * 8 | * Copyright (c) 2023, Hannes Winkler 9 | */ 10 | 11 | #ifndef _FLUTTERPI_SRC_USER_INPUT_H 12 | #define _FLUTTERPI_SRC_USER_INPUT_H 13 | 14 | #include 15 | #include 16 | 17 | #include "plugins/raw_keyboard.h" 18 | #include "util/asserts.h" 19 | #include "util/collection.h" 20 | #include "util/geometry.h" 21 | 22 | #define MAX_COLLECTED_FLUTTER_POINTER_EVENTS 64 23 | 24 | typedef void (*flutter_pointer_event_callback_t)(void *userdata, const FlutterPointerEvent *events, size_t n_events); 25 | 26 | typedef void (*utf8_character_callback_t)(void *userdata, uint8_t *character); 27 | 28 | typedef void (*xkb_keysym_callback_t)(void *userdata, xkb_keysym_t keysym); 29 | 30 | // clang-format off 31 | typedef void (*gtk_keyevent_callback_t)( 32 | void *userdata, 33 | uint32_t unicode_scalar_values, 34 | uint32_t key_code, 35 | uint32_t scan_code, 36 | uint32_t modifiers, 37 | bool is_down 38 | ); 39 | // clang-format on 40 | 41 | typedef void (*set_cursor_enabled_callback_t)(void *userdata, bool enabled); 42 | 43 | typedef void (*move_cursor_callback_t)(void *userdata, struct vec2f delta); 44 | 45 | // clang-format off 46 | typedef void (*keyevent_callback_t)( 47 | void *userdata, 48 | uint64_t timestamp_us, 49 | xkb_keycode_t xkb_keycode, 50 | xkb_keysym_t xkb_keysym, 51 | uint32_t plain_codepoint, 52 | key_modifiers_t modifiers, 53 | const char *text, 54 | bool is_down, 55 | bool is_repeat 56 | ); 57 | // clang-format on 58 | 59 | struct user_input_interface { 60 | flutter_pointer_event_callback_t on_flutter_pointer_event; 61 | utf8_character_callback_t on_utf8_character; 62 | xkb_keysym_callback_t on_xkb_keysym; 63 | gtk_keyevent_callback_t on_gtk_keyevent; 64 | set_cursor_enabled_callback_t on_set_cursor_enabled; 65 | move_cursor_callback_t on_move_cursor; 66 | int (*open)(const char *path, int flags, void *userdata); 67 | void (*close)(int fd, void *userdata); 68 | void (*on_switch_vt)(void *userdata, int vt); 69 | keyevent_callback_t on_key_event; 70 | }; 71 | 72 | struct user_input; 73 | 74 | /** 75 | * @brief Create a new user input instance. Will try to load the default keyboard config from /etc/default/keyboard 76 | * and create a udev-backed libinput instance. 77 | */ 78 | struct user_input *user_input_new( 79 | const struct user_input_interface *interface, 80 | void *userdata, 81 | const struct mat3f *display_to_view_transform, 82 | const struct mat3f *view_to_display_transform, 83 | unsigned int display_width, 84 | unsigned int display_height 85 | ); 86 | 87 | /** 88 | * @brief Destroy this user input instance and free all allocated memory. This will not remove any input devices 89 | * added to flutter and won't invoke any callbacks in the user input interface at all. 90 | */ 91 | void user_input_destroy(struct user_input *input); 92 | 93 | /** 94 | * @brief Set a 3x3 matrix and display width / height so user_input can transform any device coordinates into 95 | * proper flutter view coordinates. (For example to account for a rotated display) 96 | * Will also transform absolute & relative mouse movements. 97 | * 98 | * @param display_to_view_transform will be copied internally. 99 | */ 100 | void user_input_set_transform( 101 | struct user_input *input, 102 | const struct mat3f *display_to_view_transform, 103 | const struct mat3f *view_to_display_transform, 104 | unsigned int display_width, 105 | unsigned int display_height 106 | ); 107 | 108 | /** 109 | * @brief Returns a filedescriptor used for input event notification. The returned 110 | * filedescriptor should be listened to with EPOLLIN | EPOLLRDHUP | EPOLLPRI or equivalent. 111 | * When the fd becomes ready, @ref user_input_on_fd_ready should be called not long after it 112 | * became ready. (libinput somehow relies on that) 113 | */ 114 | int user_input_get_fd(struct user_input *input); 115 | 116 | /** 117 | * @brief Should be called when the fd returned by @ref user_input_get_fd becomes ready. 118 | * The user_input_interface callbacks will be called inside this function. 119 | */ 120 | int user_input_on_fd_ready(struct user_input *input); 121 | 122 | void user_input_suspend(struct user_input *input); 123 | 124 | int user_input_resume(struct user_input *input); 125 | 126 | #endif // _FLUTTERPI_SRC_USER_INPUT_H 127 | -------------------------------------------------------------------------------- /src/util/asserts.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Common assert macros 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_UTIL_ASSERTS_H 9 | #define _FLUTTERPI_SRC_UTIL_ASSERTS_H 10 | 11 | #include 12 | 13 | #define ASSERT assert 14 | #define ASSERT_MSG(__cond, __msg) assert((__cond) && (__msg)) 15 | #define ASSERT_NOT_NULL(__var) assert(__var != NULL) 16 | #define ASSERT_NOT_NULL_MSG(__var, __msg) ASSERT_MSG(__var != NULL, __msg) 17 | #define ASSERT_EQUALS(__a, __b) assert((__a) == (__b)) 18 | #define ASSERT_EQUALS_MSG(__a, __b, __msg) ASSERT_MSG((__a) == (__b), __msg) 19 | #define ASSERT_EGL_TRUE(__var) assert((__var) == EGL_TRUE) 20 | #define ASSERT_EGL_TRUE_MSG(__var, __msg) ASSERT_MSG((__var) == EGL_TRUE, __msg) 21 | #define ASSERT_MUTEX_LOCKED(__mutex) \ 22 | assert(({ \ 23 | bool result; \ 24 | int r = pthread_mutex_trylock(&(__mutex)); \ 25 | if (r == 0) { \ 26 | pthread_mutex_unlock(&(__mutex)); \ 27 | result = false; \ 28 | } else { \ 29 | result = true; \ 30 | } \ 31 | result; \ 32 | })) 33 | #define ASSERT_ZERO(__var) assert((__var) == 0) 34 | #define ASSERT_ZERO_MSG(__var, __msg) ASSERT_MSG((__var) == 0, __msg) 35 | 36 | #define ASSERT_IMPLIES(__cond1, __cond2) assert(!(__cond1) || (__cond2)) 37 | #define ASSERT_IMPLIES_MSG(__cond1, __cond2, __msg) ASSERT_MSG(!(__cond1) || (__cond2), __msg) 38 | 39 | #if !(201112L <= __STDC_VERSION__ || (!defined __STRICT_ANSI__ && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR >= 6)))) 40 | #error "Needs C11 or later or GCC (not in pedantic mode) 4.6.0 or later for compile time asserts." 41 | #endif 42 | 43 | #define COMPILE_ASSERT_MSG(expression, msg) _Static_assert(expression, msg) 44 | #define COMPILE_ASSERT(expression) COMPILE_ASSERT_MSG(expression, "Expression evaluates to false") 45 | 46 | #endif // _FLUTTERPI_SRC_UTIL_ASSERTS_H 47 | -------------------------------------------------------------------------------- /src/util/bitscan.c: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2008 VMware, Inc. 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sub license, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice (including the 15 | * next paragraph) shall be included in all copies or substantial portions 16 | * of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 21 | * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 22 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | **************************************************************************/ 27 | 28 | #include "bitscan.h" 29 | 30 | #ifdef HAVE___BUILTIN_FFS 31 | #elif defined(_MSC_VER) && (_M_IX86 || _M_ARM || _M_AMD64 || _M_IA64) 32 | #else 33 | int ffs(int i) { 34 | int bit = 0; 35 | if (!i) 36 | return bit; 37 | if (!(i & 0xffff)) { 38 | bit += 16; 39 | i >>= 16; 40 | } 41 | if (!(i & 0xff)) { 42 | bit += 8; 43 | i >>= 8; 44 | } 45 | if (!(i & 0xf)) { 46 | bit += 4; 47 | i >>= 4; 48 | } 49 | if (!(i & 0x3)) { 50 | bit += 2; 51 | i >>= 2; 52 | } 53 | if (!(i & 0x1)) 54 | bit += 1; 55 | return bit + 1; 56 | } 57 | #endif 58 | 59 | #ifdef HAVE___BUILTIN_FFSLL 60 | #elif defined(_MSC_VER) && (_M_AMD64 || _M_ARM64 || _M_IA64) 61 | #else 62 | int ffsll(long long int val) { 63 | int bit; 64 | 65 | bit = ffs((unsigned) (val & 0xffffffff)); 66 | if (bit != 0) 67 | return bit; 68 | 69 | bit = ffs((unsigned) (val >> 32)); 70 | if (bit != 0) 71 | return 32 + bit; 72 | 73 | return 0; 74 | } 75 | #endif 76 | -------------------------------------------------------------------------------- /src/util/collection.c: -------------------------------------------------------------------------------- 1 | #include "util/collection.h" 2 | 3 | static pthread_mutexattr_t default_mutex_attrs; 4 | 5 | static void init_default_mutex_attrs() { 6 | pthread_mutexattr_init(&default_mutex_attrs); 7 | #ifdef DEBUG 8 | pthread_mutexattr_settype(&default_mutex_attrs, PTHREAD_MUTEX_ERRORCHECK); 9 | #endif 10 | } 11 | 12 | const pthread_mutexattr_t *get_default_mutex_attrs() { 13 | static pthread_once_t init_once_ctl = PTHREAD_ONCE_INIT; 14 | 15 | pthread_once(&init_once_ctl, init_default_mutex_attrs); 16 | 17 | return &default_mutex_attrs; 18 | } 19 | -------------------------------------------------------------------------------- /src/util/collection.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Collection - common useful functions & macros 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_UTIL_COLLECTION_H 9 | #define _FLUTTERPI_SRC_UTIL_COLLECTION_H 10 | 11 | #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500L 12 | #define _XOPEN_SOURCE 500L 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include "macros.h" 23 | 24 | static inline void *memdup(const void *restrict src, const size_t n) { 25 | void *__restrict__ dest; 26 | 27 | if ((src == NULL) || (n == 0)) 28 | return NULL; 29 | 30 | dest = malloc(n); 31 | if (dest == NULL) 32 | return NULL; 33 | 34 | return memcpy(dest, src, n); 35 | } 36 | 37 | /** 38 | * @brief Get the current time of the system monotonic clock. 39 | * @returns time in nanoseconds. 40 | */ 41 | static inline uint64_t get_monotonic_time(void) { 42 | struct timespec time; 43 | clock_gettime(CLOCK_MONOTONIC, &time); 44 | return time.tv_nsec + time.tv_sec * 1000000000ull; 45 | } 46 | 47 | #define BITCAST(to_type, value) (*((const to_type *) (&(value)))) 48 | 49 | static inline int32_t uint32_to_int32(const uint32_t v) { 50 | return BITCAST(int32_t, v); 51 | } 52 | 53 | static inline uint32_t int32_to_uint32(const int32_t v) { 54 | return BITCAST(uint32_t, v); 55 | } 56 | 57 | static inline uint64_t int64_to_uint64(const int64_t v) { 58 | return BITCAST(uint64_t, v); 59 | } 60 | 61 | static inline int64_t uint64_to_int64(const uint64_t v) { 62 | return BITCAST(int64_t, v); 63 | } 64 | 65 | static inline int64_t ptr_to_int64(const void *const ptr) { 66 | union { 67 | const void *ptr; 68 | int64_t int64; 69 | } u; 70 | 71 | u.int64 = 0; 72 | u.ptr = ptr; 73 | return u.int64; 74 | } 75 | 76 | static inline void *int64_to_ptr(const int64_t v) { 77 | union { 78 | void *ptr; 79 | int64_t int64; 80 | } u; 81 | 82 | u.int64 = v; 83 | return u.ptr; 84 | } 85 | 86 | static inline int64_t ptr_to_uint32(const void *const ptr) { 87 | union { 88 | const void *ptr; 89 | uint32_t u32; 90 | } u; 91 | 92 | u.u32 = 0; 93 | u.ptr = ptr; 94 | return u.u32; 95 | } 96 | 97 | static inline void *uint32_to_ptr(const uint32_t v) { 98 | union { 99 | void *ptr; 100 | uint32_t u32; 101 | } u; 102 | 103 | u.ptr = NULL; 104 | u.u32 = v; 105 | return u.ptr; 106 | } 107 | 108 | #define MAX_ALIGNMENT (__alignof__(max_align_t)) 109 | #define IS_MAX_ALIGNED(num) ((num) % MAX_ALIGNMENT == 0) 110 | 111 | #define DOUBLE_TO_FP1616(v) ((uint32_t) ((v) *65536)) 112 | #define DOUBLE_TO_FP1616_ROUNDED(v) (((uint32_t) (v)) << 16) 113 | 114 | typedef void (*void_callback_t)(void *userdata); 115 | 116 | ATTR_PURE static inline bool streq(const char *a, const char *b) { 117 | return strcmp(a, b) == 0; 118 | } 119 | 120 | const pthread_mutexattr_t *get_default_mutex_attrs(); 121 | 122 | #endif // _FLUTTERPI_SRC_UTIL_COLLECTION_H 123 | -------------------------------------------------------------------------------- /src/util/lock_ops.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Lock Ops - Macros for defining locking operations for a struct with 4 | * sane defaults. 5 | * 6 | * Copyright (c) 2023, Hannes Winkler 7 | */ 8 | 9 | #ifndef _FLUTTERPI_SRC_UTIL_LOCK_OPS_H 10 | #define _FLUTTERPI_SRC_UTIL_LOCK_OPS_H 11 | 12 | #include 13 | 14 | #include "asserts.h" 15 | 16 | #define DECLARE_LOCK_OPS(obj_name) \ 17 | UNUSED void obj_name##_lock(struct obj_name *obj); \ 18 | UNUSED void obj_name##_unlock(struct obj_name *obj); 19 | 20 | #define DEFINE_LOCK_OPS(obj_name, mutex_member_name) \ 21 | UNUSED void obj_name##_lock(struct obj_name *obj) { \ 22 | int ok; \ 23 | ok = pthread_mutex_lock(&obj->mutex_member_name); \ 24 | ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ 25 | (void) ok; \ 26 | } \ 27 | UNUSED void obj_name##_unlock(struct obj_name *obj) { \ 28 | int ok; \ 29 | ok = pthread_mutex_unlock(&obj->mutex_member_name); \ 30 | ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ 31 | (void) ok; \ 32 | } 33 | 34 | #define DEFINE_STATIC_LOCK_OPS(obj_name, mutex_member_name) \ 35 | UNUSED static void obj_name##_lock(struct obj_name *obj) { \ 36 | int ok; \ 37 | ok = pthread_mutex_lock(&obj->mutex_member_name); \ 38 | ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ 39 | (void) ok; \ 40 | } \ 41 | UNUSED static void obj_name##_unlock(struct obj_name *obj) { \ 42 | int ok; \ 43 | ok = pthread_mutex_unlock(&obj->mutex_member_name); \ 44 | ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ 45 | (void) ok; \ 46 | } 47 | 48 | #define DEFINE_INLINE_LOCK_OPS(obj_name, mutex_member_name) \ 49 | UNUSED static inline void obj_name##_lock(struct obj_name *obj) { \ 50 | int ok; \ 51 | ok = pthread_mutex_lock(&obj->mutex_member_name); \ 52 | ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ 53 | (void) ok; \ 54 | } \ 55 | UNUSED static inline void obj_name##_unlock(struct obj_name *obj) { \ 56 | int ok; \ 57 | ok = pthread_mutex_unlock(&obj->mutex_member_name); \ 58 | ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ 59 | (void) ok; \ 60 | } 61 | 62 | #endif // _FLUTTERPI_SRC_UTIL_LOCK_OPS_H 63 | -------------------------------------------------------------------------------- /src/util/logging.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Logging - Provides debug & error logging macros. 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_UTIL_LOGGING_H 9 | #define _FLUTTERPI_SRC_UTIL_LOGGING_H 10 | 11 | #define LOG_ERROR(fmtstring, ...) fprintf(stderr, "%s: " fmtstring, __FILE__, ##__VA_ARGS__) 12 | #define LOG_ERROR_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) 13 | 14 | #ifdef DEBUG 15 | #define LOG_DEBUG(fmtstring, ...) fprintf(stderr, "%s: " fmtstring, __FILE__, ##__VA_ARGS__) 16 | #define LOG_DEBUG_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) 17 | #else 18 | #define LOG_DEBUG(fmtstring, ...) \ 19 | do { \ 20 | } while (0) 21 | #define LOG_DEBUG_UNPREFIXED(fmtstring, ...) \ 22 | do { \ 23 | } while (0) 24 | #endif 25 | 26 | #endif // _FLUTTERPI_SRC_UTIL_LOGGING_H 27 | -------------------------------------------------------------------------------- /src/util/refcounting.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Refcounting - Defines functions and macros for reference keeping. 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_UTIL_REFCOUNTING_H 9 | #define _FLUTTERPI_SRC_UTIL_REFCOUNTING_H 10 | 11 | #include 12 | #include 13 | 14 | #include "macros.h" 15 | 16 | typedef _Atomic(int) refcount_t; 17 | 18 | static inline int refcount_inc_n(refcount_t *refcount, int n) { 19 | return atomic_fetch_add_explicit(refcount, n, memory_order_relaxed); 20 | } 21 | 22 | /// Increments the reference count and returns the previous value. 23 | static inline int refcount_inc(refcount_t *refcount) { 24 | return refcount_inc_n(refcount, 1); 25 | } 26 | 27 | /// Decrement the reference count, return true if the refcount afterwards 28 | /// is still non-zero. 29 | static inline bool refcount_dec(refcount_t *refcount) { 30 | return atomic_fetch_sub_explicit(refcount, 1, memory_order_acq_rel) != 1; 31 | } 32 | 33 | /// Returns true if the reference count is one. 34 | /// If this is the case you that means this thread has exclusive access 35 | /// to the object. 36 | static inline bool refcount_is_one(refcount_t *refcount) { 37 | return atomic_load_explicit(refcount, memory_order_acquire) == 1; 38 | } 39 | 40 | /// Returns true if the reference count is zero. Should never be true 41 | /// in practice because that'd only be the case for a destroyed object. 42 | /// So this is only really useful for debugging. 43 | static inline bool refcount_is_zero(refcount_t *refcount) { 44 | return atomic_load_explicit(refcount, memory_order_acquire) == 0; 45 | } 46 | 47 | /// Get the current reference count, without any memory ordering restrictions. 48 | /// Not strictly correct, should only be used for debugging. 49 | static inline int refcount_get_for_debug(refcount_t *refcount) { 50 | return atomic_load_explicit(refcount, memory_order_relaxed); 51 | } 52 | 53 | #define REFCOUNT_INIT_0 (0) 54 | #define REFCOUNT_INIT_1 (1) 55 | #define REFCOUNT_INIT_N(n) (n) 56 | 57 | #define DECLARE_REF_OPS(obj_name) \ 58 | UNUSED struct obj_name *obj_name##_ref(struct obj_name *obj); \ 59 | UNUSED void obj_name##_unref(struct obj_name *obj); \ 60 | UNUSED void obj_name##_unrefp(struct obj_name **obj); \ 61 | UNUSED void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj); \ 62 | UNUSED void obj_name##_unref_void(void *obj); 63 | 64 | #define DEFINE_REF_OPS(obj_name, refcount_member_name) \ 65 | UNUSED struct obj_name *obj_name##_ref(struct obj_name *obj) { \ 66 | refcount_inc(&obj->refcount_member_name); \ 67 | return obj; \ 68 | } \ 69 | UNUSED void obj_name##_unref(struct obj_name *obj) { \ 70 | if (refcount_dec(&obj->refcount_member_name) == false) { \ 71 | obj_name##_destroy(obj); \ 72 | } \ 73 | } \ 74 | UNUSED void obj_name##_unrefp(struct obj_name **obj) { \ 75 | obj_name##_unref(*obj); \ 76 | *obj = NULL; \ 77 | } \ 78 | UNUSED void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ 79 | if (obj != NULL) { \ 80 | obj_name##_ref(obj); \ 81 | } \ 82 | if (*objp != NULL) { \ 83 | obj_name##_unrefp(objp); \ 84 | } \ 85 | *objp = obj; \ 86 | } \ 87 | UNUSED void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } 88 | 89 | #define DEFINE_STATIC_REF_OPS(obj_name, refcount_member_name) \ 90 | UNUSED static struct obj_name *obj_name##_ref(struct obj_name *obj) { \ 91 | refcount_inc(&obj->refcount_member_name); \ 92 | return obj; \ 93 | } \ 94 | UNUSED static void obj_name##_unref(struct obj_name *obj) { \ 95 | if (refcount_dec(&obj->refcount_member_name) == false) { \ 96 | obj_name##_destroy(obj); \ 97 | } \ 98 | } \ 99 | UNUSED static void obj_name##_unrefp(struct obj_name **obj) { \ 100 | obj_name##_unref(*obj); \ 101 | *obj = NULL; \ 102 | } \ 103 | UNUSED static void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ 104 | if (obj != NULL) { \ 105 | obj_name##_ref(obj); \ 106 | } \ 107 | if (*objp != NULL) { \ 108 | obj_name##_unrefp(objp); \ 109 | } \ 110 | *objp = obj; \ 111 | } \ 112 | UNUSED static void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } 113 | 114 | #endif // _FLUTTERPI_SRC_UTIL_REFCOUNTING_H 115 | -------------------------------------------------------------------------------- /src/util/uuid.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * UUID - Defines an uuid struct and macros & functions for working with it. 4 | * 5 | * Copyright (c) 2023, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_UTIL_UUID_H 9 | #define _FLUTTERPI_SRC_UTIL_UUID_H 10 | 11 | #include 12 | #include 13 | 14 | typedef struct { 15 | uint8_t bytes[16]; 16 | } uuid_t; 17 | 18 | #define UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ 19 | ((uuid_t){ \ 20 | .bytes = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 }, \ 21 | }) 22 | 23 | #define CONST_UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ 24 | ((const uuid_t){ \ 25 | .bytes = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 }, \ 26 | }) 27 | 28 | static inline bool uuid_equals(const uuid_t a, const uuid_t b) { 29 | return memcmp(&a, &b, sizeof(uuid_t)) == 0; 30 | } 31 | 32 | static inline void uuid_copy(uuid_t *dst, const uuid_t src) { 33 | memcpy(dst, &src, sizeof(uuid_t)); 34 | } 35 | 36 | #endif // _FLUTTERPI_SRC_UTIL_UUID_H 37 | -------------------------------------------------------------------------------- /src/util/vector.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Intel Corporation 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the next 12 | * paragraph) shall be included in all copies or substantial portions of the 13 | * Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | * IN THE SOFTWARE. 22 | */ 23 | 24 | #include "util/vector.h" 25 | 26 | #include 27 | 28 | /** @file vector.c 29 | * 30 | * A dynamically growable, circular buffer. Elements are added at head and 31 | * removed from tail. head and tail are free-running uint32_t indices and we 32 | * only compute the modulo with size when accessing the array. This way, 33 | * number of bytes in the queue is always head - tail, even in case of 34 | * wraparound. 35 | */ 36 | 37 | /** 38 | * initial_element_count and element_size must be power-of-two. 39 | */ 40 | int u_vector_init_pow2(struct u_vector *vector, uint32_t initial_element_count, uint32_t element_size) { 41 | assert(util_is_power_of_two_nonzero(initial_element_count)); 42 | assert(util_is_power_of_two_nonzero(element_size)); 43 | 44 | vector->head = 0; 45 | vector->tail = 0; 46 | vector->element_size = element_size; 47 | vector->size = element_size * initial_element_count; 48 | vector->data = malloc(vector->size); 49 | 50 | return vector->data != NULL; 51 | } 52 | 53 | void *u_vector_add(struct u_vector *vector) { 54 | uint32_t offset, size, split, src_tail, dst_tail; 55 | void *data; 56 | 57 | if (vector->head - vector->tail == vector->size) { 58 | size = vector->size * 2; 59 | data = malloc(size); 60 | if (data == NULL) 61 | return NULL; 62 | src_tail = vector->tail & (vector->size - 1); 63 | dst_tail = vector->tail & (size - 1); 64 | if (src_tail == 0) { 65 | /* Since we know that the vector is full, this means that it's 66 | * linear from start to end so we can do one copy. 67 | */ 68 | memcpy((char *) data + dst_tail, vector->data, vector->size); 69 | } else { 70 | /* In this case, the vector is split into two pieces and we have 71 | * to do two copies. We have to be careful to make sure each 72 | * piece goes to the right locations. Thanks to the change in 73 | * size, it may or may not still wrap around. 74 | */ 75 | split = u_align_u32(vector->tail, vector->size); 76 | assert(vector->tail <= split && split < vector->head); 77 | memcpy((char *) data + dst_tail, (char *) vector->data + src_tail, split - vector->tail); 78 | memcpy((char *) data + (split & (size - 1)), vector->data, vector->head - split); 79 | } 80 | free(vector->data); 81 | vector->data = data; 82 | vector->size = size; 83 | } 84 | 85 | assert(vector->head - vector->tail < vector->size); 86 | 87 | offset = vector->head & (vector->size - 1); 88 | vector->head += vector->element_size; 89 | 90 | return (char *) vector->data + offset; 91 | } 92 | 93 | void *u_vector_remove(struct u_vector *vector) { 94 | uint32_t offset; 95 | 96 | if (vector->head == vector->tail) 97 | return NULL; 98 | 99 | assert(vector->head - vector->tail <= vector->size); 100 | 101 | offset = vector->tail & (vector->size - 1); 102 | vector->tail += vector->element_size; 103 | 104 | return (char *) vector->data + offset; 105 | } 106 | -------------------------------------------------------------------------------- /src/util/vector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Intel Corporation 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the next 12 | * paragraph) shall be included in all copies or substantial portions of the 13 | * Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | * IN THE SOFTWARE. 22 | */ 23 | 24 | /* 25 | * u_vector is a vector based queue for storing arbitrary 26 | * sized arrays of objects without using a linked list. 27 | */ 28 | 29 | #ifndef U_VECTOR_H 30 | #define U_VECTOR_H 31 | 32 | #include 33 | #include 34 | 35 | #include "util/bitscan.h" 36 | #include "util/macros.h" 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | /** 43 | * Returns the smallest power of two >= x 44 | */ 45 | static inline unsigned util_next_power_of_two(unsigned x) { 46 | #if defined(HAVE___BUILTIN_CLZ) 47 | if (x <= 1) 48 | return 1; 49 | 50 | return (1 << ((sizeof(unsigned) * 8) - __builtin_clz(x - 1))); 51 | #else 52 | unsigned val = x; 53 | 54 | if (x <= 1) 55 | return 1; 56 | 57 | if (util_is_power_of_two_or_zero(x)) 58 | return x; 59 | 60 | val--; 61 | val = (val >> 1) | val; 62 | val = (val >> 2) | val; 63 | val = (val >> 4) | val; 64 | val = (val >> 8) | val; 65 | val = (val >> 16) | val; 66 | val++; 67 | return val; 68 | #endif 69 | } 70 | 71 | /* TODO - move to u_math.h - name it better etc */ 72 | static inline uint32_t u_align_u32(uint32_t v, uint32_t a) { 73 | assert(a != 0 && a == (a & -((int32_t) a))); 74 | return (v + a - 1) & ~(a - 1); 75 | } 76 | 77 | struct u_vector { 78 | uint32_t head; 79 | uint32_t tail; 80 | uint32_t element_size; 81 | uint32_t size; 82 | void *data; 83 | }; 84 | 85 | int u_vector_init_pow2(struct u_vector *queue, uint32_t initial_element_count, uint32_t element_size); 86 | 87 | void *u_vector_add(struct u_vector *queue); 88 | void *u_vector_remove(struct u_vector *queue); 89 | 90 | static inline int u_vector_init(struct u_vector *queue, uint32_t initial_element_count, uint32_t element_size) { 91 | initial_element_count = util_next_power_of_two(initial_element_count); 92 | element_size = util_next_power_of_two(element_size); 93 | return u_vector_init_pow2(queue, initial_element_count, element_size); 94 | } 95 | 96 | static inline int u_vector_length(struct u_vector *queue) { 97 | return (queue->head - queue->tail) / queue->element_size; 98 | } 99 | 100 | static inline void *u_vector_head(struct u_vector *vector) { 101 | assert(vector->tail < vector->head); 102 | return (void *) ((char *) vector->data + ((vector->head - vector->element_size) & (vector->size - 1))); 103 | } 104 | 105 | static inline void *u_vector_tail(struct u_vector *vector) { 106 | return (void *) ((char *) vector->data + (vector->tail & (vector->size - 1))); 107 | } 108 | 109 | static inline void u_vector_finish(struct u_vector *queue) { 110 | free(queue->data); 111 | } 112 | 113 | #ifdef __cplusplus 114 | #define u_vector_element_cast(elem) (decltype(elem)) 115 | #else 116 | #define u_vector_element_cast(elem) (void *) 117 | #endif 118 | 119 | #define u_vector_foreach(elem, queue) \ 120 | STATIC_ASSERT(__builtin_types_compatible_p(__typeof__(queue), struct u_vector *)); \ 121 | for (uint32_t __u_vector_offset = (queue)->tail; \ 122 | elem = u_vector_element_cast(elem)((char *) (queue)->data + (__u_vector_offset & ((queue)->size - 1))), \ 123 | __u_vector_offset != (queue)->head; \ 124 | __u_vector_offset += (queue)->element_size) 125 | 126 | #ifdef __cplusplus 127 | } 128 | #endif 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /src/vk_gbm_render_surface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Vulkan GBM render surface 4 | * 5 | * - used as a render target for flutter vulkan rendering 6 | * - can be scanned out using KMS 7 | * 8 | * Copyright (c) 2022, Hannes Winkler 9 | */ 10 | 11 | #ifndef _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H 12 | #define _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H 13 | 14 | #include "compositor_ng.h" 15 | #include "pixel_format.h" 16 | #include "util/collection.h" 17 | 18 | struct tracer; 19 | struct gbm_device; 20 | struct vk_gbm_render_surface; 21 | 22 | #define CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr) ((struct vk_gbm_render_surface *) (ptr)) 23 | #ifdef DEBUG 24 | #define CAST_VK_GBM_RENDER_SURFACE(ptr) __checked_cast_vk_gbm_render_surface(ptr) 25 | ATTR_PURE struct vk_gbm_render_surface *__checked_cast_vk_gbm_render_surface(void *ptr); 26 | #else 27 | #define CAST_VK_GBM_RENDER_SURFACE(ptr) CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr) 28 | #endif 29 | 30 | struct vk_gbm_render_surface *vk_gbm_render_surface_new( 31 | struct tracer *tracer, 32 | struct vec2i size, 33 | struct gbm_device *device, 34 | struct vk_renderer *renderer, 35 | enum pixfmt pixel_format 36 | ); 37 | 38 | #endif // _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H 39 | -------------------------------------------------------------------------------- /src/vk_renderer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Vulkan Renderer 4 | * 5 | * - provides a vulkan renderer object 6 | * - a vulkan renderer object is basically a combination of: 7 | * - a vulkan instance (VkInstance) 8 | * - a vulkan physical device (VkPhysicalDevice) 9 | * - a vulkan logical device (VkDevice) 10 | * - a vulkan graphics queue (VkQueue) 11 | * - a vulkan command buffer pool (VkCommandPool or something) 12 | * - and utilities for using those 13 | * 14 | * Copyright (c) 2022, Hannes Winkler 15 | */ 16 | 17 | #ifndef _FLUTTERPI_SRC_VK_RENDERER_H 18 | #define _FLUTTERPI_SRC_VK_RENDERER_H 19 | 20 | #include "util/collection.h" 21 | #include "util/refcounting.h" 22 | 23 | #include "config.h" 24 | 25 | #ifndef HAVE_VULKAN 26 | #error "vk_renderer.h was included but Vulkan support is disabled." 27 | #endif 28 | 29 | #include "vulkan.h" 30 | 31 | struct vk_renderer; 32 | 33 | /** 34 | * @brief Create a new vulkan renderer with some reasonable defaults. 35 | * 36 | * Creates a vulkan instance with: 37 | * - app name `flutter-pi`, version 1.0.0 38 | * - engine `flutter-pi`, version 1.0.0 39 | * - vulkan version 1.1.0 40 | * - khronos validation layers and debug utils enabled, if supported and VULKAN_DEBUG is defined 41 | * 42 | * Selects a good physical device (dedicated GPU > integrated GPU > software) that has a 43 | * graphics queue family and supports the following device extensions: 44 | * - `VK_KHR_external_memory` 45 | * - `VK_KHR_external_memory_fd` 46 | * - `VK_KHR_external_semaphore` 47 | * - `VK_KHR_external_semaphore_fd` 48 | * - `VK_EXT_external_memory_dma_buf` 49 | * - `VK_KHR_image_format_list` 50 | * - `VK_EXT_image_drm_format_modifier` 51 | * 52 | * Those extensions will also be enabled when create the logical device of course. 53 | * 54 | * Will also create a graphics queue. 55 | * 56 | * @return New vulkan renderer instance. 57 | */ 58 | struct vk_renderer *vk_renderer_new(); 59 | 60 | void vk_renderer_destroy(struct vk_renderer *renderer); 61 | 62 | DECLARE_REF_OPS(vk_renderer) 63 | 64 | /** 65 | * @brief Get the vulkan version of this renderer. This is unconditionally VK_MAKE_VERSION(1, 1, 0) for now. 66 | * 67 | * @param renderer renderer instance 68 | * @return VK_MAKE_VERSION(1, 1, 0) 69 | */ 70 | ATTR_CONST uint32_t vk_renderer_get_vk_version(struct vk_renderer *renderer); 71 | 72 | /** 73 | * @brief Get the vulkan instance of this renderer. See @ref vk_renderer_new for details on this instance. 74 | * 75 | * @param renderer renderer instance 76 | * @return vulkan instance 77 | */ 78 | ATTR_PURE VkInstance vk_renderer_get_instance(struct vk_renderer *renderer); 79 | 80 | /** 81 | * @brief Get the physical device that's used by this renderer. See @ref vk_renderer_new for details. 82 | * 83 | * @param renderer renderer instance 84 | * @return vulkan physical device 85 | */ 86 | ATTR_PURE VkPhysicalDevice vk_renderer_get_physical_device(struct vk_renderer *renderer); 87 | 88 | /** 89 | * @brief Get the logical device that's used by this renderer. See @ref vk_renderer_new for details. 90 | * 91 | * @param renderer renderer instance 92 | * @return vulkan logical device 93 | */ 94 | ATTR_PURE VkDevice vk_renderer_get_device(struct vk_renderer *renderer); 95 | 96 | /** 97 | * @brief Get the index of the graphics queue family. 98 | * 99 | * @param renderer renderer instance 100 | * @return instance of the graphics queue family. 101 | */ 102 | ATTR_PURE uint32_t vk_renderer_get_queue_family_index(struct vk_renderer *renderer); 103 | 104 | /** 105 | * @brief Get the graphics queue of this renderer. 106 | * 107 | * @param renderer renderer instance 108 | * @return graphics queue 109 | */ 110 | ATTR_PURE VkQueue vk_renderer_get_queue(struct vk_renderer *renderer); 111 | 112 | ATTR_PURE int vk_renderer_get_enabled_instance_extension_count(struct vk_renderer *renderer); 113 | 114 | ATTR_PURE const char **vk_renderer_get_enabled_instance_extensions(struct vk_renderer *renderer); 115 | 116 | ATTR_PURE int vk_renderer_get_enabled_device_extension_count(struct vk_renderer *renderer); 117 | 118 | ATTR_PURE const char **vk_renderer_get_enabled_device_extensions(struct vk_renderer *renderer); 119 | 120 | /** 121 | * @brief Find the index of a memory type for which the following conditions are true: 122 | * - (1 < 32) & @param req_bits is not 0 123 | * - the memory types property flags support the flags that are given in @param flags 124 | * 125 | * @param renderer renderer instance 126 | * @param flags Which property flags the memory type should support. 127 | * @param req_bits Which memory types are allowed to choose from. 128 | * @return index of the found memory type or -1 if none was found. 129 | */ 130 | ATTR_PURE int vk_renderer_find_mem_type(struct vk_renderer *renderer, VkMemoryPropertyFlags flags, uint32_t req_bits); 131 | 132 | #endif // _FLUTTERPI_SRC_VK_RENDERER_H 133 | -------------------------------------------------------------------------------- /src/vulkan.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Just a shim for including vulkan headers, and disabling vulkan function prototypes if vulkan is not present 4 | * 5 | * Copyright (c) 2022, Hannes Winkler 6 | */ 7 | 8 | #ifndef _FLUTTERPI_SRC_VULKAN_H 9 | #define _FLUTTERPI_SRC_VULKAN_H 10 | 11 | #include "config.h" 12 | 13 | #ifndef HAVE_VULKAN 14 | #error "vulkan.h was included but Vulkan support is disabled." 15 | #endif 16 | 17 | #include 18 | 19 | static inline const char *vk_strerror(VkResult result) { 20 | switch (result) { 21 | case VK_SUCCESS: return "VK_SUCCESS"; 22 | case VK_NOT_READY: return "VK_NOT_READY"; 23 | case VK_TIMEOUT: return "VK_TIMEOUT"; 24 | case VK_EVENT_SET: return "VK_EVENT_SET"; 25 | case VK_EVENT_RESET: return "VK_EVENT_RESET"; 26 | case VK_INCOMPLETE: return "VK_INCOMPLETE"; 27 | case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; 28 | case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; 29 | case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; 30 | case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; 31 | case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; 32 | case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; 33 | case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; 34 | case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; 35 | case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; 36 | case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; 37 | case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; 38 | case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; 39 | #if VK_HEADER_VERSION >= 131 40 | case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; 41 | #endif 42 | case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY"; 43 | case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; 44 | #if VK_HEADER_VERSION >= 131 45 | case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; 46 | case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; 47 | #endif 48 | #if VK_HEADER_VERSION >= 204 49 | case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; 50 | #endif 51 | case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR"; 52 | case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; 53 | case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR"; 54 | case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; 55 | case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; 56 | case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT"; 57 | case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV"; 58 | #if VK_HEADER_VERSION >= 218 && VK_ENABLE_BETA_EXTENSIONS 59 | case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; 60 | case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; 61 | case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; 62 | case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; 63 | case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; 64 | case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; 65 | #endif 66 | #if VK_HEADER_VERSION >= 89 67 | case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; 68 | #endif 69 | #if VK_HEADER_VERSION >= 204 70 | case VK_ERROR_NOT_PERMITTED_KHR: return "VK_ERROR_NOT_PERMITTED_KHR"; 71 | #endif 72 | #if VK_HEADER_VERSION >= 105 73 | case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; 74 | #endif 75 | #if VK_HEADER_VERSION >= 135 76 | case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR"; 77 | case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR"; 78 | case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR"; 79 | case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR"; 80 | #endif 81 | #if VK_HEADER_VERSION >= 213 82 | case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; 83 | #endif 84 | default: return ""; 85 | } 86 | } 87 | 88 | #define LOG_VK_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ vk_strerror(result)) 89 | 90 | #endif // _FLUTTERPI_SRC_VULKAN_H 91 | -------------------------------------------------------------------------------- /src/window.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Window 4 | * 5 | * - a window is a 2D rect inside a real, physical display where flutter content can be displayed, and input events can be routed. 6 | * 7 | * Copyright (c) 2022, Hannes Winkler 8 | */ 9 | 10 | #ifndef _FLUTTERPI_SRC_WINDOW_H 11 | #define _FLUTTERPI_SRC_WINDOW_H 12 | 13 | #include "compositor_ng.h" 14 | #include "modesetting.h" 15 | #include "pixel_format.h" 16 | #include "util/refcounting.h" 17 | 18 | #include "config.h" 19 | 20 | struct surface; 21 | struct window; 22 | struct tracer; 23 | struct frame_scheduler; 24 | struct fl_layer_composition; 25 | 26 | struct view_geometry { 27 | struct vec2f view_size, display_size; 28 | struct mat3f display_to_view_transform; 29 | struct mat3f view_to_display_transform; 30 | double device_pixel_ratio; 31 | }; 32 | 33 | enum renderer_type { kOpenGL_RendererType, kVulkan_RendererType }; 34 | 35 | DECLARE_REF_OPS(window) 36 | 37 | /** 38 | * @brief Creates a new KMS window. 39 | * 40 | * @param tracer 41 | * @param scheduler 42 | * @param render_surface_interface 43 | * @param has_rotation 44 | * @param rotation 45 | * @param has_orientation 46 | * @param orientation 47 | * @param has_explicit_dimensions 48 | * @param width_mm 49 | * @param height_mm 50 | * @param has_forced_pixel_format 51 | * @param forced_pixel_format 52 | * @param drmdev 53 | * @param desired_videomode 54 | * @return struct window* The new KMS window. 55 | */ 56 | struct window *kms_window_new( 57 | // clang-format off 58 | struct tracer *tracer, 59 | struct frame_scheduler *scheduler, 60 | enum renderer_type renderer_type, 61 | struct gl_renderer *gl_renderer, 62 | struct vk_renderer *vk_renderer, 63 | bool has_rotation, drm_plane_transform_t rotation, 64 | bool has_orientation, enum device_orientation orientation, 65 | bool has_explicit_dimensions, int width_mm, int height_mm, 66 | bool has_forced_pixel_format, enum pixfmt forced_pixel_format, 67 | struct drmdev *drmdev, 68 | const char *desired_videomode 69 | // clang-format on 70 | ); 71 | 72 | /** 73 | * Creates a new dummy window. 74 | * 75 | * @param tracer The tracer object. 76 | * @param scheduler The frame scheduler object. 77 | * @param renderer_type The type of renderer. 78 | * @param gl_renderer The GL renderer object. 79 | * @param vk_renderer The Vulkan renderer object. 80 | * @param size The size of the window. 81 | * @param has_explicit_dimensions Indicates if the window has explicit dimensions. 82 | * @param width_mm The width of the window in millimeters. 83 | * @param height_mm The height of the window in millimeters. 84 | * @param refresh_rate The refresh rate of the window. 85 | * @return A pointer to the newly created window. 86 | */ 87 | MUST_CHECK struct window *dummy_window_new( 88 | struct tracer *tracer, 89 | struct frame_scheduler *scheduler, 90 | enum renderer_type renderer_type, 91 | struct gl_renderer *gl_renderer, 92 | struct vk_renderer *vk_renderer, 93 | struct vec2i size, 94 | bool has_explicit_dimensions, 95 | int width_mm, 96 | int height_mm, 97 | double refresh_rate 98 | ); 99 | 100 | /** 101 | * @brief Push a new flutter composition to the window, outputting a new frame. 102 | * 103 | * @param window The window instance. 104 | * @param composition The composition that should be presented. 105 | * @return int Zero if successful, errno-code otherwise. 106 | */ 107 | int window_push_composition(struct window *window, struct fl_layer_composition *composition); 108 | 109 | /** 110 | * @brief Get the current view geometry of this window. 111 | * 112 | * @param window The window instance. 113 | * @return struct view_geometry 114 | */ 115 | struct view_geometry window_get_view_geometry(struct window *window); 116 | 117 | /** 118 | * @brief Returns the vertical refresh rate of the chosen mode & display. 119 | * 120 | * @param window The window instance. 121 | * @return double The refresh rate. 122 | */ 123 | ATTR_PURE double window_get_refresh_rate(struct window *window); 124 | 125 | /** 126 | * @brief Returns the timestamp of the next vblank signal in @param next_vblank_ns_out. 127 | * 128 | * @param window The window instance. 129 | * @param next_vblank_ns_out Next vblank timestamp will be stored here. Must be non-null. 130 | * @return int Zero if successful, errno-code otherwise. 131 | */ 132 | int window_get_next_vblank(struct window *window, uint64_t *next_vblank_ns_out); 133 | 134 | #ifdef HAVE_EGL_GLES2 135 | bool window_has_egl_surface(struct window *window); 136 | 137 | EGLSurface window_get_egl_surface(struct window *window); 138 | #endif 139 | 140 | /** 141 | * @brief Gets a render surface, used as the backing store for an engine layer. 142 | * 143 | * This only makes sense if there's a single UI (engine) layer. If there's multiple ones, lifetimes become weird. 144 | * 145 | */ 146 | struct render_surface *window_get_render_surface(struct window *window, struct vec2i size); 147 | 148 | bool window_is_cursor_enabled(struct window *window); 149 | 150 | int window_set_cursor( 151 | // clang-format off 152 | struct window *window, 153 | bool has_enabled, bool enabled, 154 | bool has_kind, enum pointer_kind kind, 155 | bool has_pos, struct vec2i pos 156 | // clang-format on 157 | ); 158 | 159 | #endif // _FLUTTERPI_SRC_WINDOW_H 160 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(platformchannel_test 2 | platformchannel_test.c 3 | ) 4 | 5 | target_link_libraries( 6 | platformchannel_test 7 | flutterpi_module 8 | Unity 9 | ) 10 | 11 | add_test(platformchannel_test platformchannel_test) 12 | 13 | add_executable(flutterpi_test 14 | flutterpi_test.c 15 | ) 16 | 17 | target_link_libraries( 18 | flutterpi_test 19 | flutterpi_module 20 | Unity 21 | ) 22 | 23 | add_test(flutterpi_test flutterpi_test) -------------------------------------------------------------------------------- /test/flutterpi_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void setUp() { 5 | } 6 | 7 | void tearDown() { 8 | } 9 | 10 | #define TEST_ASSERT_EQUAL_BOOL(expected, actual) \ 11 | do { \ 12 | if (expected) { \ 13 | UNITY_TEST_ASSERT((actual), __LINE__, " Expected TRUE Was FALSE"); \ 14 | } else { \ 15 | UNITY_TEST_ASSERT(!(actual), __LINE__, " Expected FALSE Was TRUE"); \ 16 | } \ 17 | } while (0) 18 | 19 | #define BUNDLE_PATH "/path/to/bundle" 20 | 21 | void expect_parsed_cmdline_args_matches(int argc, char **argv, bool expected_result, const struct flutterpi_cmdline_args expected) { 22 | struct flutterpi_cmdline_args actual; 23 | bool result; 24 | 25 | result = flutterpi_parse_cmdline_args(argc, argv, &actual); 26 | TEST_ASSERT_EQUAL_BOOL(expected_result, result); 27 | TEST_ASSERT_EQUAL_BOOL(expected.has_orientation, actual.has_orientation); 28 | TEST_ASSERT_EQUAL(expected.orientation, actual.orientation); 29 | TEST_ASSERT_EQUAL_BOOL(expected.has_rotation, actual.has_rotation); 30 | TEST_ASSERT_EQUAL_INT(expected.rotation, actual.rotation); 31 | TEST_ASSERT_EQUAL_BOOL(expected.has_physical_dimensions, actual.has_physical_dimensions); 32 | TEST_ASSERT_EQUAL_INT(expected.physical_dimensions.x, actual.physical_dimensions.x); 33 | TEST_ASSERT_EQUAL_INT(expected.physical_dimensions.y, actual.physical_dimensions.y); 34 | TEST_ASSERT_EQUAL_BOOL(expected.has_pixel_format, actual.has_pixel_format); 35 | TEST_ASSERT_EQUAL(expected.pixel_format, actual.pixel_format); 36 | TEST_ASSERT_EQUAL_BOOL(expected.has_runtime_mode, actual.has_runtime_mode); 37 | TEST_ASSERT_EQUAL(expected.runtime_mode, actual.runtime_mode); 38 | TEST_ASSERT_EQUAL_STRING(expected.bundle_path, actual.bundle_path); 39 | TEST_ASSERT_EQUAL_INT(expected.engine_argc, actual.engine_argc); 40 | if (expected.engine_argc != 0) { 41 | TEST_ASSERT_NOT_NULL(actual.engine_argv); 42 | TEST_ASSERT_EQUAL_STRING_ARRAY(expected.engine_argv, actual.engine_argv, expected.engine_argc); 43 | } else { 44 | TEST_ASSERT_NULL(actual.engine_argv); 45 | } 46 | TEST_ASSERT_EQUAL_BOOL(expected.use_vulkan, actual.use_vulkan); 47 | TEST_ASSERT_EQUAL_STRING(expected.desired_videomode, actual.desired_videomode); 48 | TEST_ASSERT_EQUAL_BOOL(expected.dummy_display, actual.dummy_display); 49 | TEST_ASSERT_EQUAL_INT(expected.dummy_display_size.x, actual.dummy_display_size.x); 50 | TEST_ASSERT_EQUAL_INT(expected.dummy_display_size.y, actual.dummy_display_size.y); 51 | } 52 | 53 | static struct flutterpi_cmdline_args get_default_args() { 54 | static char *engine_argv[1] = { "flutter-pi" }; 55 | 56 | return (struct flutterpi_cmdline_args){ 57 | .has_orientation = false, 58 | .orientation = kPortraitUp, 59 | .has_rotation = false, 60 | .rotation = 0, 61 | .has_physical_dimensions = false, 62 | .physical_dimensions = { .x = 0, .y = 0 }, 63 | .has_pixel_format = false, 64 | .pixel_format = PIXFMT_RGB565, 65 | .has_runtime_mode = false, 66 | .runtime_mode = FLUTTER_RUNTIME_MODE_DEBUG, 67 | .bundle_path = BUNDLE_PATH, 68 | .engine_argc = 1, 69 | .engine_argv = engine_argv, 70 | .use_vulkan = false, 71 | .desired_videomode = NULL, 72 | .dummy_display = false, 73 | .dummy_display_size = { .x = 0, .y = 0 }, 74 | }; 75 | } 76 | 77 | void test_parse_orientation_arg() { 78 | struct flutterpi_cmdline_args expected = get_default_args(); 79 | 80 | // test --orientation 81 | expected.has_orientation = true; 82 | expected.orientation = kPortraitUp; 83 | expect_parsed_cmdline_args_matches( 84 | 4, 85 | (char *[]){ 86 | "flutter-pi", 87 | "--orientation", 88 | "portrait_up", 89 | BUNDLE_PATH, 90 | }, 91 | true, 92 | expected 93 | ); 94 | 95 | expected.orientation = kLandscapeLeft; 96 | expect_parsed_cmdline_args_matches( 97 | 4, 98 | (char *[]){ 99 | "flutter-pi", 100 | "--orientation", 101 | "landscape_left", 102 | BUNDLE_PATH, 103 | }, 104 | true, 105 | expected 106 | ); 107 | 108 | expected.orientation = kPortraitDown; 109 | expect_parsed_cmdline_args_matches( 110 | 4, 111 | (char *[]){ 112 | "flutter-pi", 113 | "--orientation", 114 | "portrait_down", 115 | BUNDLE_PATH, 116 | }, 117 | true, 118 | expected 119 | ); 120 | 121 | expected.orientation = kLandscapeRight; 122 | expect_parsed_cmdline_args_matches( 123 | 4, 124 | (char *[]){ 125 | "flutter-pi", 126 | "--orientation", 127 | "landscape_right", 128 | BUNDLE_PATH, 129 | }, 130 | true, 131 | expected 132 | ); 133 | } 134 | 135 | void test_parse_rotation_arg() { 136 | struct flutterpi_cmdline_args expected = get_default_args(); 137 | 138 | expected.has_rotation = true; 139 | expected.rotation = 0; 140 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "0", BUNDLE_PATH }, true, expected); 141 | 142 | expected.rotation = 90; 143 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "90", BUNDLE_PATH }, true, expected); 144 | 145 | expected.rotation = 180; 146 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "180", BUNDLE_PATH }, true, expected); 147 | 148 | expected.rotation = 270; 149 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "270", BUNDLE_PATH }, true, expected); 150 | } 151 | 152 | void test_parse_physical_dimensions_arg() { 153 | struct flutterpi_cmdline_args expected = get_default_args(); 154 | 155 | expected.bundle_path = NULL; 156 | expected.engine_argc = 0; 157 | expected.engine_argv = NULL; 158 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--dimensions", "-10,-10", BUNDLE_PATH }, false, expected); 159 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--dimensions", "xyz", BUNDLE_PATH }, false, expected); 160 | 161 | expected = get_default_args(); 162 | expected.has_physical_dimensions = true; 163 | expected.physical_dimensions = (struct vec2i){ .x = 10, .y = 10 }; 164 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--dimensions", "10,10", BUNDLE_PATH }, true, expected); 165 | } 166 | 167 | void test_parse_pixel_format_arg() { 168 | struct flutterpi_cmdline_args expected = get_default_args(); 169 | 170 | expected.has_pixel_format = true; 171 | expected.pixel_format = PIXFMT_RGB565; 172 | 173 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--pixelformat", "RGB565", BUNDLE_PATH }, true, expected); 174 | 175 | expected.pixel_format = PIXFMT_RGBA8888; 176 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--pixelformat", "RGBA8888", BUNDLE_PATH }, true, expected); 177 | } 178 | 179 | void test_parse_runtime_mode_arg() { 180 | struct flutterpi_cmdline_args expected = get_default_args(); 181 | 182 | // test --debug, --profile, --release 183 | expected.bundle_path = NULL; 184 | expected.engine_argc = 0; 185 | expected.engine_argv = NULL; 186 | expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--debug", BUNDLE_PATH }, false, expected); 187 | 188 | expected = get_default_args(); 189 | expected.has_runtime_mode = true; 190 | expected.runtime_mode = FLUTTER_RUNTIME_MODE_PROFILE; 191 | expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--profile", BUNDLE_PATH }, true, expected); 192 | 193 | expected.runtime_mode = FLUTTER_RUNTIME_MODE_RELEASE; 194 | expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--release", BUNDLE_PATH }, true, expected); 195 | } 196 | 197 | void test_parse_bundle_path_arg() { 198 | struct flutterpi_cmdline_args expected = get_default_args(); 199 | 200 | expected.bundle_path = "/path/to/bundle/test"; 201 | expect_parsed_cmdline_args_matches(2, (char *[]){ "flutter-pi", "/path/to/bundle/test" }, true, expected); 202 | } 203 | 204 | void test_parse_engine_arg() { 205 | struct flutterpi_cmdline_args expected = get_default_args(); 206 | 207 | expected.engine_argc = 2; 208 | expected.engine_argv = (char *[]){ "flutter-pi", "engine-arg" }; 209 | 210 | expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", BUNDLE_PATH, "engine-arg" }, true, expected); 211 | } 212 | 213 | void test_parse_vulkan_arg() { 214 | struct flutterpi_cmdline_args expected = get_default_args(); 215 | 216 | expected.use_vulkan = true; 217 | expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--vulkan", BUNDLE_PATH }, true, expected); 218 | } 219 | 220 | void test_parse_desired_videomode_arg() { 221 | struct flutterpi_cmdline_args expected = get_default_args(); 222 | 223 | expected.desired_videomode = "1920x1080"; 224 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--videomode", "1920x1080", BUNDLE_PATH }, true, expected); 225 | 226 | expected.desired_videomode = "1920x1080@60"; 227 | expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--videomode", "1920x1080@60", BUNDLE_PATH }, true, expected); 228 | } 229 | 230 | int main() { 231 | UNITY_BEGIN(); 232 | 233 | RUN_TEST(test_parse_runtime_mode_arg); 234 | RUN_TEST(test_parse_orientation_arg); 235 | RUN_TEST(test_parse_rotation_arg); 236 | RUN_TEST(test_parse_physical_dimensions_arg); 237 | RUN_TEST(test_parse_pixel_format_arg); 238 | RUN_TEST(test_parse_bundle_path_arg); 239 | RUN_TEST(test_parse_engine_arg); 240 | RUN_TEST(test_parse_vulkan_arg); 241 | RUN_TEST(test_parse_desired_videomode_arg); 242 | 243 | UNITY_END(); 244 | } 245 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(Unity STATIC 2 | Unity/src/unity.c 3 | ) 4 | 5 | target_include_directories(Unity PUBLIC 6 | Unity/src 7 | ) 8 | 9 | target_compile_definitions(Unity PUBLIC 10 | UNITY_SUPPORT_64 11 | UNITY_INCLUDE_DOUBLE 12 | ) 13 | -------------------------------------------------------------------------------- /third_party/flutter_embedder_header/engine.version: -------------------------------------------------------------------------------- 1 | d44b5a94c976fbb65815374f61ab5392a220b084 --------------------------------------------------------------------------------