diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..87646806 --- /dev/null +++ b/.clang-format @@ -0,0 +1,171 @@ +# SPDX-License-Identifier: MIT +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: BlockIndent +#AlignArrayOfStructures: Right +AlignConsecutiveAssignments: false +#AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +#AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +#AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: false +AttributeMacros: + - 'MAYBE_UNUSED' + - 'ATTR_MALLOC' + - 'NONNULL' + - 'ATTR_PURE' + - 'ATTR_CONST' + +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +AlwaysBreakBeforeMultilineStrings: true +ColumnLimit: 140 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +#ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: false +ForEachMacros: + - 'for_each_pointer_in_pset' + - 'for_each_pointer_in_cpset' + - 'for_each_connector_in_drmdev' + - 'for_each_encoder_in_drmdev' + - 'for_each_crtc_in_drmdev' + - 'for_each_plane_in_drmdev' + - 'for_each_mode_in_connector' + - 'for_each_unreserved_plane_in_atomic_req' + +IncludeBlocks: Regroup +IncludeCategories: + # C standard library headers + - 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>$' + Priority: 1 + # POSIX headers + - Regex: '^<(ctype|dlfcn|fcntl|glob|limits|locale|poll|pthread|regex|semaphore|unistd|sys/mman|sys/stat|sys/types|sys/select)\.h>$' + Priority: 2 + # all <> includes + - Regex: '^<[^>]*>$' + Priority: 3 + # config header should go last + - Regex: '^"config\.h"$' + Priority: 5 + # all "" includes + - Regex: '^"[^"]*"$' + Priority: 4 + # all other includes + - Regex: '^.*$' + Priority: 4 + +IncludeIsMainRegex: '(_test)?$' +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +PenaltyBreakAssignment: 60 +PenaltyBreakBeforeFirstCallParameter: 0 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 0 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: CaseInsensitive +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +StatementMacros: + - 'DECLARE_REF_OPS' + - 'DEFINE_REF_OPS' + - 'DEFINE_STATIC_REF_OPS' + - 'DECLARE_LOCK_OPS' + - 'DEFINE_LOCK_OPS' + - 'DEFINE_STATIC_LOCK_OPS' + - 'DEFINE_INLINE_LOCK_OPS' + - 'UUID' + - 'CONST_UUID' +TabWidth: 4 +TypenameMacros: + - 'BMAP_ELEMENT_TYPE' + - 'MAX_ALIGNMENT' +UseTab: Never diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 1c20a901..74e20b75 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -2,88 +2,173 @@ name: CMake on: push: - branches: [ master, feature/gh-actions ] + branches: [ master, feature/gh-actions, feature/compositor-ng ] pull_request: - branches: [ master, feature/gh-actions ] + branches: [ master, feature/gh-actions, feature/compositor-ng ] workflow_dispatch: -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Debug - jobs: - build: - name: build (raspbian bullseye, pi4) + pi4: + name: Build and Test on Raspberry Pi 4 + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: + - ARM + - ARM64 + graphics-backends: + - vulkan-only + - opengl-only + - vulkan-and-opengl + compiler: + - gcc + - clang + build-type: + - Debug + - Release + include: + - graphics-backends: 'vulkan-only' + enable-vulkan: 'on' + enable-opengl: 'off' + + - graphics-backends: 'opengl-only' + enable-vulkan: 'off' + enable-opengl: 'on' + + - graphics-backends: 'vulkan-and-opengl' + enable-vulkan: 'on' + enable-opengl: 'on' + + - enable-opengl: 'on' + opengl-deps: 'libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev' - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: [linux, bullseye] + - enable-vulkan: 'on' + vulkan-deps: 'libvulkan-dev' + - compiler: 'clang' + clang-deps: 'clang' steps: - - uses: actions/checkout@v2 - - # On the self-hosted runners, we assume those are already installed. - # - name: Install dependencies - # run: | - # sudo apt-get 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 ninja-build libgstreamer-plugins-base1.0-dev - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -GNinja - - - name: Build - # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - - name: Test - if: false - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} - - build-buster: - name: build (debian buster, x64) + # On the self-hosted runners, we assume those are already installed. + # - name: Install dependencies + # run: | + # id + # sudo apt-get install -y \ + # git 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 ninja-build libgstreamer-plugins-base1.0-dev \ + # ${{ matrix.vulkan-deps }} \ + # ${{ matrix.clang-deps }} + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Configure CMake + run: | + cmake \ + -B ./build \ + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ + -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ + -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ + -DENABLE_VULKAN=${{ matrix.enable-vulkan }} \ + -DENABLE_OPENGL=${{ matrix.enable-opengl }} \ + -DENABLE_TESTS=On \ + -GNinja + echo test + + - name: Build + run: cmake --build ./build --config ${{ matrix.build-type }} + + - name: Test + working-directory: build + run: ctest -C ${{ matrix.build-type }} --output-on-failure + + container: + name: Build & Test in Container runs-on: ubuntu-latest container: - image: debian:buster + image: ${{ matrix.container }} env: DEBIAN_FRONTEND: noninteractive - steps: - - uses: actions/checkout@v2 + strategy: + matrix: + container: + - 'debian:bullseye' + - 'debian:buster' + graphics-backends: + - vulkan-only + - opengl-only + - vulkan-and-opengl + compiler: + - gcc + - clang + build-type: + - Debug + - Release + include: + - graphics-backends: 'vulkan-only' + enable-vulkan: 'on' + enable-opengl: 'off' + + - graphics-backends: 'opengl-only' + enable-vulkan: 'off' + enable-opengl: 'on' + + - graphics-backends: 'vulkan-and-opengl' + enable-vulkan: 'on' + enable-opengl: 'on' + + - enable-opengl: 'on' + opengl-deps: 'libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev' + + - enable-vulkan: 'on' + vulkan-deps: 'libvulkan-dev' + - compiler: 'clang' + clang-deps: 'clang' + steps: + # git needs to be installed before checking out, otherwise the checkout will fallback to the REST API, + # and the submodule download won't work. - name: Install dependencies run: | apt-get update && apt-get install -y \ - cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev \ + git cmake libdrm-dev libgbm-dev \ fonts-liberation fontconfig libsystemd-dev libinput-dev libudev-dev \ - libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev + libxkbcommon-dev ninja-build libgstreamer-plugins-base1.0-dev \ + ${{ matrix.vulkan-deps }} \ + ${{ matrix.opengl-deps }} \ + ${{ matrix.clang-deps }} + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: | cmake \ - -B ${{ github.workspace }}/build \ - -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN=On \ + -B ./build \ + -S . \ + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_C_COMPILER=${{ matrix.compiler }} \ -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=On \ -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=On \ - -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -DENABLE_VULKAN=${{ matrix.enable-vulkan }} \ + -DENABLE_OPENGL=${{ matrix.enable-opengl }} \ + -DENABLE_TESTS=On \ -GNinja - name: Build - # Build your program with the given configuration - run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} + run: cmake --build ./build --config ${{ matrix.build-type }} - name: Test - if: false - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + working-directory: build + run: ctest -C ${{ matrix.build-type }} --output-on-failure + + required: + name: required checks + needs: [pi4, container] + runs-on: ubuntu-latest + steps: + - run: | + echo 'all required checks passed.' diff --git a/.gitignore b/.gitignore index a6af02d6..d1e6b119 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -.vscode -.clang-format -build \ No newline at end of file +/.vscode +/.clang-format +/build +/out +/CMakePresets.json +/cross-pi4-toolchain.cmake +/cross-pi4-64-toolchain.cmake +/cross-pi3-toolchain.cmake diff --git a/.gitmodules b/.gitmodules index fd6e8e06..4bb04965 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "third_party/backward-cpp"] - path = third_party/backward-cpp - url = https://github.com/bombela/backward-cpp.git +[submodule "third_party/Unity"] + path = third_party/Unity + url = https://github.com/ThrowTheSwitch/Unity diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..90e6b8c5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + exclude_types: ['markdown'] + - id: forbid-new-submodules + - id: mixed-line-ending + args: ['--fix=lf'] + description: Forces to replace line ending by the UNIX 'lf' character. + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.0 + hooks: + - id: clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index aa420a83..4a22e514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,20 +35,34 @@ project(flutter-pi LANGUAGES C VERSION "1.0.0") message(STATUS "Generator .............. ${CMAKE_GENERATOR}") message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") -option(OMXPLAYER_SUPPORTS_RUNTIME_ROTATION "Whether omxplayer supports runtime rotation." OFF) +# configure options option(BUILD_TEXT_INPUT_PLUGIN "Include the text input plugin in the finished binary. Enables text input (to flutter text fields, for example) via attached keyboards." ON) option(BUILD_RAW_KEYBOARD_PLUGIN "Include the raw keyboard plugin in the finished binary. Enables raw keycode listening in flutter via the flutter RawKeyboard interface." ON) option(BUILD_TEST_PLUGIN "Include the test plugin in the finished binary. Allows testing platform channel communication." OFF) -option(BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN "Include the omxplayer_video_player plugin in the finished binary. Allows for hardware accelerated video playback in flutter using omxplayer." ON) option(BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Include the gstreamer based video plugins in the finished binary. Allows for more stable, hardware accelerated video playback in flutter using gstreamer." ON) option(BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN "Include the gstreamer based audio plugins in the finished binary." ON) option(TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Don't throw an error if the gstreamer libs aren't found, instead just don't build the gstreamer video player plugin in that case." ON) option(TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN "Don't throw an error if the gstreamer libs aren't found, instead just don't build gstreamer audio plugin." ON) +option(ENABLE_OPENGL "Build with EGL/OpenGL rendering support." ON) +option(TRY_ENABLE_OPENGL "Don't throw an error if EGL/OpenGL aren't found, instead just build without EGL/OpenGL support in that case." ON) +option(ENABLE_VULKAN "Build with Vulkan rendering support." OFF) +option(TRY_ENABLE_VULKAN "Don't throw an error if vulkan isn't found, instead just build without vulkan support in that case." OFF) +set(VULKAN_DEBUG "AUTO" CACHE STRING "Enable vulkan validation layers and verbose vulkan logging. (ON/OFF/AUTO)") +set_property(CACHE VULKAN_DEBUG PROPERTY STRINGS ON OFF AUTO) +option(ENABLE_SOFTWARE "Build with software rendering support." ON) option(DUMP_ENGINE_LAYERS "True if flutter-pi should dump the list of rendering layers that the flutter engine sends to flutter-pi on each draw." OFF) +option(WARN_MISSING_FIELD_INITIALIZERS "True of the compiler should be instructed to warn about missing field initializers. This needs some hacky workarounds in the code so gcc won't report spurious warnings, so this should only be enabled if the warnings are explicitly required." OFF) option(ENABLE_TSAN "True to build & link with -fsanitize=thread" OFF) option(ENABLE_ASAN "True to build & link with -fsanitize=address" OFF) option(ENABLE_UBSAN "True to build & link with -fsanitize=undefined" OFF) option(ENABLE_MTRACE "True if flutter-pi should call GNU mtrace() on startup." OFF) +option(ENABLE_TESTS "True if tests should be built. Requires Unity to be checked out at third_party/Unity." OFF) +option(ENABLE_SESSION_SWITCHING "True if flutter-pi should be built with session switching support. Requires libseat-dev to be installed." ON) +option(TRY_ENABLE_SESSION_SWITCHING "Don't throw an error if libseat isn't found, instead just build without session switching support in that case." ON) +option(LTO "Check for IPO/LTO support and enable, if supported. May require gold/lld when building with clang. (Either using `-fuse-ld` in CMAKE_C_FLAGS or by setting as the default system linker.) Only applies to Release or RelWithDebInfo build types." ON) +option(LINT_EGL_HEADERS "Set an define that'll make the egl.h only export the extension definitions, prototypes that are explicitly marked as required." OFF) +option(DEBUG_DRM_PLANE_ALLOCATIONS "Add logging in modesetting.c for debugging the process of choosing a fitting DRM plane for a framebuffer layer." OFF) +option(USE_LEGACY_KMS "Force the use of legacy KMS." OFF) set(FILESYSTEM_LAYOUTS default meta-flutter) set(FILESYSTEM_LAYOUT "default" CACHE STRING "Where to look for the icudtl.dat, app.so/libapp.so, flutter asset bundle.") @@ -56,73 +70,33 @@ set_property(CACHE FILESYSTEM_LAYOUT PROPERTY STRINGS ${FILESYSTEM_LAYOUTS}) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if(NOT FLUTTER_EMBEDDER_HEADER) - - include(FetchContent) - - if(NOT FLUTTER_ENGINE_SHA) - - if(NOT CHANNEL) - set(CHANNEL "stable" CACHE STRING "The flutter channel to be used for downloading the flutter_embedder.h header file. Choose: master, dev, beta, stable" FORCE) - message(STATUS "Flutter Channel not set, defaulting to stable") - endif() - - message(STATUS "Flutter Channel ........ ${CHANNEL}") - - FetchContent_Declare(engine-version - URL https://raw.githubusercontent.com/flutter/flutter/${CHANNEL}/bin/internal/engine.version - DOWNLOAD_NAME engine.version - DOWNLOAD_NO_EXTRACT TRUE - DOWNLOAD_DIR ${CMAKE_BINARY_DIR} - ) - - FetchContent_GetProperties(engine-version) - if(NOT engine-version_POPULATED) - FetchContent_Populate(engine-version) - file(READ ${CMAKE_BINARY_DIR}/engine.version FLUTTER_ENGINE_SHA) - string(REPLACE "\n" "" FLUTTER_ENGINE_SHA ${FLUTTER_ENGINE_SHA}) - else() - MESSAGE(FATAL "Unable to determine engine-version, please override FLUTTER_ENGINE_SHA") - endif() - - endif() - - message(STATUS "Engine SHA1 ............ ${FLUTTER_ENGINE_SHA}") - - set(FLUTTER_EMBEDDER_HEADER ${CMAKE_BINARY_DIR}/flutter_embedder.h) - - # Download and setup the flutter engine library header. - if(NOT EXISTS ${FLUTTER_EMBEDDER_HEADER}) - file(DOWNLOAD - https://raw.githubusercontent.com/flutter/engine/${FLUTTER_ENGINE_SHA}/shell/platform/embedder/embedder.h - ${FLUTTER_EMBEDDER_HEADER} - ) - endif() -else() - message(STATUS "Flutter Header ......... ${FLUTTER_EMBEDDER_HEADER}") -endif() - include(ExternalProject) include(CheckCCompilerFlag) +include(FetchContent) +# Those libraries we definitely need. include(FindPkgConfig) -pkg_check_modules(DRM REQUIRED libdrm) -pkg_check_modules(GBM REQUIRED gbm) -pkg_check_modules(EGL REQUIRED egl) -pkg_check_modules(GLESV2 REQUIRED glesv2) -pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) -pkg_check_modules(LIBINPUT REQUIRED libinput) -pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) -pkg_check_modules(LIBUDEV REQUIRED libudev) - -add_executable(flutter-pi +pkg_check_modules(DRM REQUIRED IMPORTED_TARGET libdrm) +pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm) +pkg_check_modules(LIBSYSTEMD REQUIRED IMPORTED_TARGET libsystemd) +pkg_check_modules(LIBINPUT REQUIRED IMPORTED_TARGET libinput) +pkg_check_modules(LIBXKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) +pkg_check_modules(LIBUDEV REQUIRED IMPORTED_TARGET libudev) + +# find pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +add_library( + flutterpi_module OBJECT src/flutter-pi.c src/platformchannel.c src/pluginregistry.c src/texture_registry.c - src/compositor.c src/modesetting.c - src/collection.c + src/util/collection.c + src/util/bitscan.c + src/util/vector.c src/cursor.c src/keyboard.c src/user_input.c @@ -130,50 +104,49 @@ add_executable(flutter-pi src/notifier_listener.c src/pixel_format.c src/filesystem_layout.c + src/compositor_ng.c + src/surface.c + src/render_surface.c + src/tracer.c + src/dmabuf_surface.c + src/frame_scheduler.c + src/window.c + src/dummy_render_surface.c src/plugins/services.c ) -target_link_libraries(flutter-pi - ${DRM_LDFLAGS} - ${GBM_LDFLAGS} - ${EGL_LDFLAGS} - ${GLESV2_LDFLAGS} - EGL - systemd #${LIBSYSTEMD_LDFLAGS} - input #${LIBINPUT_LDFLAGS} - xkbcommon #${LIBUDEV_LDFLAGS} - udev #${LIBXKBCOMMON_LDFLAGS} - pthread dl rt m atomic +target_link_libraries(flutterpi_module PUBLIC + PkgConfig::DRM + PkgConfig::GBM + PkgConfig::LIBSYSTEMD + PkgConfig::LIBINPUT + PkgConfig::LIBXKBCOMMON + PkgConfig::LIBUDEV + Threads::Threads + ${CMAKE_DL_LIBS} + rt m atomic ) -target_include_directories(flutter-pi PRIVATE +target_include_directories(flutterpi_module PUBLIC + ${CMAKE_SOURCE_DIR}/third_party/flutter_embedder_header/include + ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/include/plugins - ${DRM_INCLUDE_DIRS} - ${GBM_INCLUDE_DIRS} - ${EGL_INCLUDE_DIRS} - ${GLESV2_INCLUDE_DIRS} - ${LIBSYSTEMD_INCLUDE_DIRS} - ${LIBINPUT_INCLUDE_DIRS} - ${LIBUDEV_INCLUDE_DIRS} - ${LIBXKBCOMMON_INCLUDE_DIRS} ) -target_compile_options(flutter-pi PRIVATE - ${DRM_CFLAGS} - ${GBM_CFLAGS} - ${EGL_CFLAGS} - ${GLESV2_CFLAGS} - ${LIBSYSTEMD_CFLAGS} - ${LIBINPUT_CFLAGS} - ${LIBUDEV_CFLAGS} - ${LIBXKBCOMMON_CFLAGS} - $<$:-O0 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> - $<$:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> - $<$:-O2 -Wall -Wextra -Wno-unused-function -Wno-sign-compare -Wno-missing-field-initializers -ggdb> +target_compile_options(flutterpi_module PUBLIC + $<$:-O0 -Wall -Wextra -Wno-sign-compare -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> + $<$:-O3 -Wall -Wextra -Wno-sign-compare -ggdb -DNDEBUG> + $<$:-O3 -Wall -Wextra -Wno-sign-compare -DNDEBUG> ) +# GCC prior to 11.3 reports false positives for missing-field-initializers warning. +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + if (CMAKE_C_COMPILER_VERSION VERSION_LESS "11.3") + target_compile_options(flutterpi_module PUBLIC -Wno-missing-field-initializers) + endif() +endif() + +# libinput stuff # There's no other way to query the libinput version (in code) somehow. # So we need to roll our own libinput version macro string(REPLACE "." ";" LIBINPUT_VERSION_AS_LIST ${LIBINPUT_VERSION}) @@ -181,131 +154,184 @@ list(GET LIBINPUT_VERSION_AS_LIST 0 LIBINPUT_VERSION_MAJOR) list(GET LIBINPUT_VERSION_AS_LIST 1 LIBINPUT_VERSION_MINOR) list(GET LIBINPUT_VERSION_AS_LIST 2 LIBINPUT_VERSION_PATCH) -target_compile_definitions(flutter-pi PRIVATE - LIBINPUT_VERSION_MAJOR=${LIBINPUT_VERSION_MAJOR} - LIBINPUT_VERSION_MINOR=${LIBINPUT_VERSION_MINOR} - LIBINPUT_VERSION_PATCH=${LIBINPUT_VERSION_PATCH} -) - # TODO: Just unconditionally define those, make them optional later -target_compile_definitions(flutter-pi PRIVATE HAS_KMS HAS_EGL HAS_GBM HAS_FBDEV) +set(HAVE_KMS ON) +set(HAVE_GBM ON) +set(HAVE_FBDEV ON) + +# OpenGL support +set(HAVE_EGL OFF) +set(HAVE_GLES2 OFF) +set(HAVE_EGL_GLES2 OFF) + +pkg_check_modules(EGL IMPORTED_TARGET egl) +pkg_check_modules(GLES2 IMPORTED_TARGET glesv2) +if (ENABLE_OPENGL) + if (EGL_FOUND AND GLES2_FOUND) + target_sources(flutterpi_module PRIVATE + src/egl_gbm_render_surface.c + src/gl_renderer.c + ) + target_link_libraries(flutterpi_module PUBLIC + PkgConfig::EGL + PkgConfig::GLES2 + ) + + set(HAVE_EGL ON) + set(HAVE_GLES2 ON) + set(HAVE_EGL_GLES2 ON) + elseif (TRY_ENABLE_OPENGL) + message("EGL and/or OpenGL was not found. Flutter-pi will build without EGL/OpenGL rendering support.") + else() + message(SEND_ERROR "EGL and/or OpenGL was not found. Try building with `-DTRY_ENABLE_OPENGL=On` if you want to just disable EGL/OpenGL support in that case.") + endif() +endif() + +message(STATUS "EGL/GLES support ....... ${HAVE_EGL_GLES2}") + +message(STATUS "Lint EGL headers ....... ${LINT_EGL_HEADERS}") + +# Vulkan support +set(HAVE_VULKAN OFF) + +pkg_check_modules(VULKAN IMPORTED_TARGET vulkan) +if (ENABLE_VULKAN) + if (VULKAN_FOUND) + target_sources(flutterpi_module PRIVATE + src/vk_gbm_render_surface.c + src/vk_renderer.c + ) + target_link_libraries(flutterpi_module PUBLIC + PkgConfig::VULKAN + ) + + set(HAVE_VULKAN ON) + elseif (TRY_ENABLE_VULKAN) + message("Vulkan was not found. Flutter-pi will build without vulkan rendering support.") + else() + message(SEND_ERROR "Vulkan was not found. Try building with `-DTRY_ENABLE_VULKAN=On` if you want to just disable vulkan support in that case.") + endif() +endif() +message(STATUS "Vulkan support ......... ${HAVE_VULKAN}") + +# We need at least one renderer +if (NOT HAVE_VULKAN AND NOT HAVE_EGL_GLES2) + message(SEND_ERROR "At least one of the EGL/GLES2 and Vulkan backends must be enabled.") +endif() + +# Filesystem Layout +# meta-flutter or normal flutter-pi. +# see src/filesystem_layout.c for details. if(NOT FILESYSTEM_LAYOUT IN_LIST FILESYSTEM_LAYOUTS) message(FATAL_ERROR "FILESYSTEM_LAYOUT must be one of ${FILESYSTEM_LAYOUTS}") endif() message(STATUS "Filesystem Layout ...... ${FILESYSTEM_LAYOUT}") +# config.h takes the defines in form of #cmakedefine FILESYSTEM_LAYOUT_DEFAULT, ... if(FILESYSTEM_LAYOUT STREQUAL default) - target_compile_definitions(flutter-pi PRIVATE "FILESYSTEM_LAYOUT_DEFAULT") + set(FILESYSTEM_LAYOUT_DEFAULT ON) elseif(FILESYSTEM_LAYOUT STREQUAL meta-flutter) - target_compile_definitions(flutter-pi PRIVATE "FILESYSTEM_LAYOUT_METAFLUTTER") + set(FILESYSTEM_LAYOUT_METAFLUTTER ON) +endif() + +# Session switching support (using libseat) +set(HAVE_LIBSEAT OFF) + +if (ENABLE_SESSION_SWITCHING) + if (TRY_ENABLE_SESSION_SWITCHING) + pkg_check_modules(LIBSEAT IMPORTED_TARGET libseat) + else() + pkg_check_modules(LIBSEAT REQUIRED IMPORTED_TARGET libseat) + endif() + + if (LIBSEAT_FOUND) + target_link_libraries(flutterpi_module PUBLIC PkgConfig::LIBSEAT) + set(HAVE_LIBSEAT ON) + else() + message("libseat was not found. flutter-pi will be built without session switching support.") + endif() endif() +message(STATUS "Session switching ...... ${HAVE_LIBSEAT}") + # TODO: We actually don't need the compile definitions anymore, except for # text input and raw keyboard plugin (because those have special treatment # in flutter-pi.c) if (BUILD_TEXT_INPUT_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/text_input.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_TEXT_INPUT_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/text_input.c) endif() if (BUILD_RAW_KEYBOARD_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/raw_keyboard.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_RAW_KEYBOARD_PLUGIN") + target_sources(flutterpi_module PRIVATE src/plugins/raw_keyboard.c) endif() if (BUILD_TEST_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/testplugin.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_TEST_PLUGIN") -endif() -if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) - target_sources(flutter-pi PRIVATE src/plugins/omxplayer_video_player.c) - target_compile_definitions(flutter-pi PRIVATE "BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN") -endif() -if (OMXPLAYER_SUPPORTS_RUNTIME_ROTATION) - target_compile_definitions(flutter-pi PRIVATE "OMXPLAYER_SUPPORTS_RUNTIME_ROTATION") + target_sources(flutterpi_module PRIVATE src/plugins/testplugin.c) endif() if (BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) - if (TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) - pkg_check_modules(LIBGSTREAMER gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE gstreamer-plugins-base-1.0) - pkg_check_modules(LIBGSTREAMER_APP gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_ALLOCATORS gstreamer-allocators-1.0) - pkg_check_modules(LIBGSTREAMER_VIDEO gstreamer-video-1.0) + if (NOT HAVE_EGL_GLES2) + message(NOTICE "EGL and OpenGL ES2 are required for gstreamer video player. Gstreamer video player plugin won't be build.") else() - pkg_check_modules(LIBGSTREAMER REQUIRED gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0) - pkg_check_modules(LIBGSTREAMER_APP REQUIRED gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_ALLOCATORS REQUIRED gstreamer-allocators-1.0) - pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED gstreamer-video-1.0) - endif() + if (TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN) + pkg_check_modules(LIBGSTREAMER IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE IMPORTED_TARGET gstreamer-plugins-base-1.0) + pkg_check_modules(LIBGSTREAMER_APP IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_ALLOCATORS IMPORTED_TARGET gstreamer-allocators-1.0) + pkg_check_modules(LIBGSTREAMER_VIDEO IMPORTED_TARGET gstreamer-video-1.0) + else() + pkg_check_modules(LIBGSTREAMER REQUIRED IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_PLUGINS_BASE REQUIRED IMPORTED_TARGET gstreamer-plugins-base-1.0) + pkg_check_modules(LIBGSTREAMER_APP REQUIRED IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_ALLOCATORS REQUIRED IMPORTED_TARGET gstreamer-allocators-1.0) + pkg_check_modules(LIBGSTREAMER_VIDEO REQUIRED IMPORTED_TARGET gstreamer-video-1.0) + endif() - if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) - target_sources(flutter-pi PRIVATE - src/plugins/gstreamer_video_player/plugin.c - src/plugins/gstreamer_video_player/player.c - src/plugins/gstreamer_video_player/frame.c - ) - target_compile_definitions(flutter-pi PRIVATE "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN") - target_link_libraries(flutter-pi - ${LIBGSTREAMER_LDFLAGS} - ${LIBGSTREAMER_PLUGINS_BASE_LDFLAGS} - ${LIBGSTREAMER_APP_LDFLAGS} - ${LIBGSTREAMER_ALLOCATORS_LDFLAGS} - ${LIBGSTREAMER_VIDEO_LDFLAGS} - ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} - ) - target_include_directories(flutter-pi PRIVATE - ${LIBGSTREAMER_INCLUDE_DIRS} - ${LIBGSTREAMER_PLUGINS_BASE_INCLUDE_DIRS} - ${LIBGSTREAMER_APP_INCLUDE_DIRS} - ${LIBGSTREAMER_ALLOCATORS_INCLUDE_DIRS} - ${LIBGSTREAMER_VIDEO_INCLUDE_DIRS} - ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} - ) - target_compile_options(flutter-pi PRIVATE - ${LIBGSTREAMER_CFLAGS} - ${LIBGSTREAMER_PLUGINS_BASE_CFLAGS} - ${LIBGSTREAMER_APP_CFLAGS} - ${LIBGSTREAMER_ALLOCATORS_CFLAGS} - ${LIBGSTREAMER_VIDEO_CFLAGS} - ${LIBGSTREAMER_AUDIO_CFLAGS} - ) - else() - message(NOTICE "Couldn't find gstreamer libraries. Gstreamer video player plugin won't be build.") + if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_PLUGINS_BASE_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_ALLOCATORS_FOUND AND LIBGSTREAMER_VIDEO_FOUND) + # There's no other way to query the libinput version (in code) somehow. + # So we need to roll our own libinput version macro + string(REPLACE "." ";" LIBGSTREAMER_VERSION_AS_LIST ${LIBGSTREAMER_VERSION}) + list(GET LIBGSTREAMER_VERSION_AS_LIST 0 LIBGSTREAMER_VERSION_MAJOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 1 LIBGSTREAMER_VERSION_MINOR) + list(GET LIBGSTREAMER_VERSION_AS_LIST 2 LIBGSTREAMER_VERSION_PATCH) + + target_sources(flutterpi_module PRIVATE + src/plugins/gstreamer_video_player/plugin.c + src/plugins/gstreamer_video_player/player.c + src/plugins/gstreamer_video_player/frame.c + ) + target_link_libraries(flutterpi_module PUBLIC + PkgConfig::LIBGSTREAMER + PkgConfig::LIBGSTREAMER_PLUGINS_BASE + PkgConfig::LIBGSTREAMER_APP + PkgConfig::LIBGSTREAMER_ALLOCATORS + PkgConfig::LIBGSTREAMER_VIDEO + ) + else() + message(NOTICE "Couldn't find gstreamer libraries. Gstreamer video player plugin won't be build.") + endif() endif() endif() if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) if (TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) - pkg_check_modules(LIBGSTREAMER gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_APP gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_AUDIO gstreamer-audio-1.0) + pkg_check_modules(LIBGSTREAMER IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_APP IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_AUDIO IMPORTED_TARGET gstreamer-audio-1.0) else() - pkg_check_modules(LIBGSTREAMER REQUIRED gstreamer-1.0) - pkg_check_modules(LIBGSTREAMER_APP REQUIRED gstreamer-app-1.0) - pkg_check_modules(LIBGSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0) + pkg_check_modules(LIBGSTREAMER REQUIRED IMPORTED_TARGET gstreamer-1.0) + pkg_check_modules(LIBGSTREAMER_APP REQUIRED IMPORTED_TARGET gstreamer-app-1.0) + pkg_check_modules(LIBGSTREAMER_AUDIO REQUIRED IMPORTED_TARGET gstreamer-audio-1.0) endif() if (LIBGSTREAMER_FOUND AND LIBGSTREAMER_APP_FOUND AND LIBGSTREAMER_AUDIO_FOUND) - target_sources(flutter-pi PRIVATE + target_sources(flutterpi_module PRIVATE src/plugins/audioplayers/plugin.c src/plugins/audioplayers/player.c ) - target_compile_definitions(flutter-pi PRIVATE "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN") - target_link_libraries(flutter-pi - ${LIBGSTREAMER_LDFLAGS} - ${LIBGSTREAMER_APP_LDFLAGS} - ${LIBGSTREAMER_AUDIO_LIBRARY_DIRS} - ) - target_include_directories(flutter-pi PRIVATE - ${LIBGSTREAMER_INCLUDE_DIRS} - ${LIBGSTREAMER_APP_INCLUDE_DIRS} - ${LIBGSTREAMER_AUDIO_INCLUDE_DIRS} - ) - target_compile_options(flutter-pi PRIVATE - ${LIBGSTREAMER_CFLAGS} - ${LIBGSTREAMER_APP_CFLAGS} - ${LIBGSTREAMER_AUDIO_CFLAGS} + target_link_libraries(flutterpi_module PUBLIC + PkgConfig::LIBGSTREAMER + PkgConfig::LIBGSTREAMER_APP + PkgConfig::LIBGSTREAMER_AUDIO ) else() message(NOTICE "Couldn't find gstreamer libraries. Gstreamer audio player plugin won't be build.") @@ -313,14 +339,26 @@ if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN) endif() # Needed so dart VM can actually resolve symbols in the same -# executable. -target_link_options(flutter-pi PRIVATE - -rdynamic -) +# executable. (For dart:ffi DynamicLibrary.executable / DynamicLibrary.process) +target_link_options(flutterpi_module PUBLIC -rdynamic) +# Define VULKAN_DEBUG if it was set to On, or if it was set to AUTO and we're using debug mode. +if (VULKAN_DEBUG MATCHES AUTO) + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + set(VULKAN_DEBUG ON) + else() + set(VULKAN_DEBUG OFF) + endif() +elseif (VULKAN_DEBUG MATCHES "OFF") + set(VULKAN_DEBUG OFF) +elseif (VULKAN_DEBUG MATCHES "ON") + set(VULKAN_DEBUG ON) +endif() + +# Some sanitizer configs. if (ENABLE_TSAN) - target_link_options(flutter-pi PRIVATE -fsanitize=thread) - target_compile_options(flutter-pi PRIVATE -fsanitize=thread) + target_link_options(flutterpi_module PUBLIC -fsanitize=thread) + target_compile_options(flutterpi_module PUBLIC -fsanitize=thread) endif() if (ENABLE_ASAN) # when we use asan, we need to force linking against the C++ stdlib. @@ -328,21 +366,126 @@ if (ENABLE_ASAN) # and something in the dynamically loaded library triggers asan, it'll throw an error because it hasn't # intercepted stdc++ yet. # Also disable --as-needed so we _actually_ link against c++, even though we don't use any symbols from it. - target_link_libraries(flutter-pi stdc++) - target_link_options(flutter-pi PRIVATE -fsanitize=address -fno-omit-frame-pointer -Wl,--no-as-needed) - target_compile_options(flutter-pi PRIVATE -fsanitize=address) + target_link_libraries(flutterpi_module PUBLIC stdc++) + target_link_options(flutterpi_module PUBLIC -fsanitize=address -fno-omit-frame-pointer -Wl,--no-as-needed) + target_compile_options(flutterpi_module PUBLIC -fsanitize=address) - check_c_compiler_flag(-static-libasan HAS_STATIC_LIBASAN) - if (HAS_STATIC_LIBASAN) - target_link_options(flutter-pi PRIVATE -static-libasan) + check_c_compiler_flag(-static-libasan HAVE_STATIC_LIBASAN) + if (HAVE_STATIC_LIBASAN) + target_link_options(flutterpi_module PUBLIC -static-libasan) endif() endif() if (ENABLE_UBSAN) - target_link_options(flutter-pi PRIVATE -fsanitize=undefined) - target_compile_options(flutter-pi PRIVATE -fsanitize=undefined) + target_link_options(flutterpi_module PUBLIC -fsanitize=undefined) + target_compile_options(flutterpi_module PUBLIC -fsanitize=undefined) endif() -if (ENABLE_MTRACE) - target_compile_definitions(flutter-pi PRIVATE "ENABLE_MTRACE") + +configure_file(config.h.in config.h @ONLY) + +# TODO: make mapping portable +# is mapping `../src/` to `` always gonna work? +check_c_compiler_flag(-fmacro-prefix-map=.=. COMPILER_SUPPORTS_MACRO_PREFIX_MAP) +if (COMPILER_SUPPORTS_MACRO_PREFIX_MAP) + target_compile_options(flutterpi_module PRIVATE "-fmacro-prefix-map=../src/=") + target_compile_options(flutterpi_module PRIVATE "-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/src/=") endif() +# Actual flutter-pi executable. +add_executable( + flutter-pi + src/main.c +) +target_link_libraries( + flutter-pi + flutterpi_module +) install(TARGETS flutter-pi RUNTIME DESTINATION bin) + +# Enable lto if supported. +cmake_policy(SET CMP0069 NEW) +include(CheckIPOSupported) +# include(CheckLinkerFlag) + +set(USE_LTO OFF) +# set(NEEDS_GOLD OFF) +# set(NEEDS_LLD OFF) +if(LTO AND (CMAKE_BUILD_TYPE STREQUAL Release OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)) + # So people can specify `-fuse-ld=lld` in the CMAKE_C_FLAGS. + # Otherwise check_ipo_supported will not use CMAKE_C_FLAGS. + if (POLICY CMP0138) + cmake_policy(SET CMP0138 NEW) + endif() + + check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_SUPPORT_OUTPUT) + if (NOT IPO_SUPPORTED) + message(WARNING "IPO/LTO was requested in the configure options, but is not supported by the toolchain. Check CMakeFiles/CMakeError.log for details.") + endif() + + # Try to enable IPO with gold and lld. + # Needs CMP0138. + # (untested because CMP0138 required CMake 3.24, that's why it's commented out) + # if (NOT IPO_SUPPORTED) + # check_linker_flag(C "-fuse-ld=gold" SUPPORTS_GOLD) + # if (SUPPORTS_GOLD) + # set(OLD_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + # + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") + # try_compile() + # check_ipo_supported(RESULT IPO_SUPPORTED_WITH_GOLD OUTPUT IPO_SUPPORT_OUTPUT) + # if (IPO_SUPPORTED_WITH_GOLD) + # set(IPO_SUPPORTED ON) + # set(NEEDS_GOLD ON) + # endif() + # + # set(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}") + # endif() + # + # check_linker_flag(C "-fuse-ld=lld" SUPPORTS_LLD) + # if (SUPPORTS_LLD) + # set(OLD_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + # + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=lld") + # check_ipo_supported(RESULT IPO_SUPPORTED_WITH_LLD OUTPUT IPO_SUPPORT_OUTPUT) + # if (IPO_SUPPORTED_WITH_LLD) + # set(IPO_SUPPORTED ON) + # set(NEEDS_LLD ON) + # endif() + # + # set(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}") + # endif() + # endif() + + # clang doesn't support LTO when using GNU ld. + if(IPO_SUPPORTED AND ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")) + execute_process(COMMAND ${CMAKE_C_COMPILER} -Wl,--version OUTPUT_VARIABLE LINKER_VERSION_OUTPUT ERROR_QUIET) + if("${LINKER_VERSION_OUTPUT}" MATCHES "GNU ld") + message(WARNING "IPO/LTO was requested, but is not supported when using clang with GNU ld as the linker. Try setting gold or lld as the system linker.") + set(IPO_SUPPORTED OFF) + endif() + endif() + + if (IPO_SUPPORTED) + set(USE_LTO ON) + endif() +endif() + +message(STATUS "IPO/LTO ................ ${USE_LTO}") +if (USE_LTO) + set_property(TARGET flutterpi_module PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + set_property(TARGET flutter-pi PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + # if (NEEDS_GOLD) + # Technically specifying only for one would suffice. + # target_link_options(flutterpi_module PUBLIC "-fuse-ld=gold") + # target_link_options(flutter-pi PUBLIC "-fuse-ld=gold") + # elseif (NEEDS_LLD) + # target_link_options(flutterpi_module PUBLIC "-fuse-ld=lld") + # target_link_options(flutter-pi PUBLIC "-fuse-ld=lld") + # endif() +endif() + +if(ENABLE_TESTS) + include(CTest) + + add_subdirectory(third_party) + add_subdirectory(test) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..24bc30e7 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,83 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "cross-pi4", + "displayName": "cross-pi4", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cross-pi4-toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "ENABLE_VULKAN": true, + "DEBUG_DRM_PLANE_ALLOCATIONS": false, + "USE_LEGACY_KMS": false, + "ENABLE_ASAN": false + } + }, + { + "name": "cross-pi4-release", + "displayName": "cross-pi4-release", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cross-pi4-toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "ENABLE_VULKAN": true, + "DEBUG_DRM_PLANE_ALLOCATIONS": false, + "USE_LEGACY_KMS": false, + "ENABLE_ASAN": false + } + }, + { + "name": "cross-pi4-64", + "displayName": "cross-pi4-64", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cross-pi4-64-toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" + } + }, + { + "name": "cross-pi3", + "displayName": "cross-pi3", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cross-pi3-toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "ENABLE_VULKAN": false, + "DEBUG_DRM_PLANE_ALLOCATIONS": true, + "USE_LEGACY_KMS": false + } + }, + { + "name": "cross-pi3-release", + "displayName": "cross-pi3-release", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cross-pi3-toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "ENABLE_VULKAN": false, + "DEBUG_DRM_PLANE_ALLOCATIONS": true, + "USE_LEGACY_KMS": false + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 493acc9e..8918141a 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ - Engine binaries no longer need to be installed on the target system. - See updated [Building flutter-pi on the Raspberry Pi](#-building-flutter-pi-on-the-raspberry-pi) and [Building the App](#building-the-app-new-method-linux-only) sections below. - Added a section for useful dart packages, See [Useful Dart Packages](#-useful-dart-packages) -- There's now a new video player based on gstreamer. See [gstreamer video player](#gstreamer-video-player) section. - The new latest flutter gallery commit for flutter 3.10 is `d77920b4ced4a105ad35659fbe3958800d418fb9` +- The [gstreamer video player](#gstreamer-video-player) now supports creating players from a raw gstreamer pipeline. +- The deprecated `omxplayer`-based video player has been removed. # flutter-pi A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. @@ -81,7 +82,7 @@ If you encounter issues running flutter-pi on any of the supported platforms lis If you want to use the [gstreamer video player](#gstreamer-video-player), install these too: ```shell - $ sudo apt install 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 + $ sudo apt install 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 ```
More Info @@ -407,7 +408,7 @@ of the flutter app you're trying to run. `[flutter engine options...]` will be passed as commandline arguments to the flutter engine. You can find a list of commandline options for the flutter engine [Here](https://github.com/flutter/engine/blob/master/shell/common/switches.h). ### gstreamer video player -Gstreamer video player is a newer video player based on gstreamer. The older video player (omxplayer_video_player) was based on deprecated omxplayer and it was kind of a hack. So I recommend using the gstreamer one instead. +Gstreamer video player is a newer video player based on gstreamer. To use the gstreamer video player, just rebuild flutter-pi (delete your build folder and reconfigure) and make sure the necessary gstreamer packages are installed. (See [dependencies](#dependencies)) diff --git a/config.h.in b/config.h.in new file mode 100644 index 00000000..2b3f76c9 --- /dev/null +++ b/config.h.in @@ -0,0 +1,29 @@ +#ifndef _FLUTTERPI_CONFIG_H +#define _FLUTTERPI_CONFIG_H + +#define LIBINPUT_VERSION_MAJOR @LIBINPUT_VERSION_MAJOR@ +#define LIBINPUT_VERSION_MINOR @LIBINPUT_VERSION_MINOR@ +#define LIBINPUT_VERSION_PATCH @LIBINPUT_VERSION_PATCH@ +#cmakedefine HAVE_KMS +#cmakedefine HAVE_GBM +#cmakedefine HAVE_FBDEV +#cmakedefine HAVE_EGL +#cmakedefine HAVE_GLES2 +#cmakedefine HAVE_EGL_GLES2 +#cmakedefine LINT_EGL_HEADERS +#cmakedefine HAVE_VULKAN +#cmakedefine FILESYSTEM_LAYOUT_DEFAULT +#cmakedefine FILESYSTEM_LAYOUT_METAFLUTTER +#cmakedefine HAVE_LIBSEAT +#cmakedefine DEBUG_DRM_PLANE_ALLOCATIONS +#cmakedefine USE_LEGACY_KMS +#cmakedefine BUILD_TEXT_INPUT_PLUGIN +#cmakedefine BUILD_RAW_KEYBOARD_PLUGIN +#define LIBGSTREAMER_VERSION_MAJOR @LIBGSTREAMER_VERSION_MAJOR@ +#define LIBGSTREAMER_VERSION_MINOR @LIBGSTREAMER_VERSION_MINOR@ +#define LIBGSTREAMER_VERSION_PATCH @LIBGSTREAMER_VERSION_PATCH@ +#cmakedefine VULKAN_DEBUG +#cmakedefine ENABLE_MTRACE +#cmakedefine ENABLE_ASAN + +#endif diff --git a/cross-pi3-toolchain.cmake b/cross-pi3-toolchain.cmake new file mode 100644 index 00000000..e4377661 --- /dev/null +++ b/cross-pi3-toolchain.cmake @@ -0,0 +1,21 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(CMAKE_SYSROOT /home/hannes/devel/hpi3-sysroot) +set(CMAKE_STAGING_PREFIX /home/hannes/devel/hpi3-sysroot/home/pi/devel/flutterpi-install) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf) + +set(CMAKE_FIND_ROOT_PATH /home/hannes/devel/hpi3-sysroot) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +add_compile_options("-march=armv7" "-mcpu=cortex-a53" "-mtune=cortex-a53") +add_link_options("-fuse-ld=lld") + +set(ENV{PKG_CONFIG_PATH} "") +set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig:${CMAKE_SYSROOT}/usr/lib/arm-linux-gnueabihf/pkgconfig") +set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT}) diff --git a/cross-pi4-64-toolchain.cmake b/cross-pi4-64-toolchain.cmake new file mode 100644 index 00000000..69bbd68a --- /dev/null +++ b/cross-pi4-64-toolchain.cmake @@ -0,0 +1,21 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(CMAKE_SYSROOT /home/hannes/devel/hpi8-sysroot) +set(CMAKE_STAGING_PREFIX /home/hannes/devel/hpi8-sysroot/home/pi/devel/flutterpi-install) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu) + +set(CMAKE_FIND_ROOT_PATH /home/hannes/devel/hpi8-sysroot) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +add_compile_options("-march=armv8" "-mcpu=cortex-a72+nocrypto" "-mtune=cortex-a72") +add_link_options("-fuse-ld=lld") + +set(ENV{PKG_CONFIG_PATH} "") +set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig:${CMAKE_SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig") +set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT}) diff --git a/cross-pi4-toolchain.cmake b/cross-pi4-toolchain.cmake new file mode 100644 index 00000000..45be96fc --- /dev/null +++ b/cross-pi4-toolchain.cmake @@ -0,0 +1,23 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(CMAKE_SYSROOT /home/hannes/devel/pi-sysroot) +set(CMAKE_STAGING_PREFIX /home/hannes/devel/pi-sysroot/home/pi/devel/flutterpi-install) + +#set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) +#set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf) +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf) + +set(CMAKE_FIND_ROOT_PATH /home/hannes/devel/pi-sysroot) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +add_compile_options("-mcpu=cortex-a72" "-mtune=cortex-a72") +add_link_options("-fuse-ld=lld") + +set(ENV{PKG_CONFIG_PATH} "") +set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig:${CMAKE_SYSROOT}/usr/lib/arm-linux-gnueabihf/pkgconfig") +set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT}) diff --git a/include/collection.h b/include/collection.h deleted file mode 100644 index 3035f010..00000000 --- a/include/collection.h +++ /dev/null @@ -1,555 +0,0 @@ -#ifndef _COLLECTION_H -#define _COLLECTION_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -struct queue { - size_t start_index; - size_t length; - size_t size; - void *elements; - - size_t max_queue_size; - size_t element_size; -}; - -struct concurrent_queue { - pthread_mutex_t mutex; - pthread_cond_t is_dequeueable; - pthread_cond_t is_enqueueable; - struct queue queue; -}; - -struct pointer_set { - /** - * @brief The number of non-NULL pointers currently stored in @ref pointers. - */ - size_t count_pointers; - - /** - * @brief The current size of the @ref pointers memory block, in pointers. - */ - size_t size; - - /** - * @brief The actual memory where the pointers are stored. - */ - void **pointers; - - /** - * @brief The maximum size of the @ref pointers memory block, in pointers. - */ - size_t max_size; - - /** - * @brief Whether this pointer_set is using static memory. - */ - bool is_static; -}; - -struct concurrent_pointer_set { - pthread_mutex_t mutex; - struct pointer_set set; -}; - -typedef _Atomic(int) refcount_t; - -#define QUEUE_DEFAULT_MAX_SIZE 64 - -#define QUEUE_INITIALIZER(element_type, _max_size) \ - ((struct queue) { \ - .start_index = 0, \ - .length = 0, \ - .size = 0, \ - .elements = NULL, \ - .max_queue_size = _max_queue_size, \ - .element_size = sizeof(element_type) \ - }) - -#define CQUEUE_DEFAULT_MAX_SIZE 64 - -#define CQUEUE_INITIALIZER(element_type, _max_size) \ - ((struct concurrent_queue) { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .is_dequeueable = PTHREAD_COND_INITIALIZER, \ - .is_enqueueable = PTHREAD_COND_INITIALIZER, \ - .queue = QUEUE_INITIALIZER(element_type, _max_queue_size) \ - }) - -#define PSET_DEFAULT_MAX_SIZE 64 - -#define PSET_INITIALIZER(_max_size) \ - ((struct pointer_set) { \ - .count_pointers = 0, \ - .size = 0, \ - .pointers = NULL, \ - .max_size = _max_size, \ - .is_static = false \ - }) - -#define PSET_INITIALIZER_STATIC(_storage, _size) \ - ((struct pointer_set) { \ - .count_pointers = 0, \ - .size = _size, \ - .pointers = _storage, \ - .max_size = _size, \ - .is_static = true \ - }) - -#define CPSET_DEFAULT_MAX_SIZE 64 - -#define CPSET_INITIALIZER(_max_size) \ - ((struct concurrent_pointer_set) { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .set = { \ - .count_pointers = 0, \ - .size = 0, \ - .pointers = NULL, \ - .max_size = _max_size, \ - .is_static = false \ - } \ - }) - - -int queue_init( - struct queue *queue, - size_t element_size, - size_t max_queue_size -); - -int queue_deinit( - struct queue *queue -); - -int queue_enqueue( - struct queue *queue, - const void *p_element -); - -int queue_dequeue( - struct queue *queue, - void *element_out -); - -int queue_peek( - struct queue *queue, - void **pelement_out -); - - -int cqueue_init( - struct concurrent_queue *queue, - size_t element_size, - size_t max_queue_size -); - -int cqueue_deinit( - struct concurrent_queue *queue -); - -static inline int cqueue_lock(struct concurrent_queue * const queue) { - return pthread_mutex_lock(&queue->mutex); -} - -static inline int cqueue_unlock(struct concurrent_queue * const queue) { - return pthread_mutex_unlock(&queue->mutex); -} - -int cqueue_try_enqueue_locked( - struct concurrent_queue *queue, - const void *p_element -); - -int cqueue_enqueue_locked( - struct concurrent_queue *queue, - const void *p_element -); - -int cqueue_try_enqueue( - struct concurrent_queue *queue, - const void *p_element -); - -int cqueue_enqueue( - struct concurrent_queue *queue, - const void *p_element -); - -int cqueue_try_dequeue_locked( - struct concurrent_queue *queue, - void *element_out -); - -int cqueue_dequeue_locked( - struct concurrent_queue *queue, - void *element_out -); - -int cqueue_try_dequeue( - struct concurrent_queue *queue, - void *element_out -); - -int cqueue_dequeue( - struct concurrent_queue *queue, - void *element_out -); - -int cqueue_peek_locked( - struct concurrent_queue *queue, - void **pelement_out -); - -/* - * pointer set - */ -int pset_init( - struct pointer_set *set, - size_t max_size -); - -int pset_init_static( - struct pointer_set *set, - void **storage, - size_t size -); - -void pset_deinit( - struct pointer_set *set -); - -int pset_put( - struct pointer_set *set, - void *pointer -); - -bool pset_contains( - const struct pointer_set *set, - const void *pointer -); - -int pset_remove( - struct pointer_set *set, - const void *pointer -); - -static inline int pset_get_count_pointers( - const struct pointer_set *set -) { - return set->count_pointers; -} - -/** - * @brief Returns the size of the internal storage of set, in pointers. - */ -static inline int pset_get_storage_size( - const struct pointer_set *set -) { - return set->size; -} - -int pset_copy( - const struct pointer_set *src, - struct pointer_set *dest -); - -void pset_intersect( - struct pointer_set *src_dest, - const struct pointer_set *b -); - -int pset_union( - struct pointer_set *src_dest, - const struct pointer_set *b -); - -int pset_subtract( - struct pointer_set *minuend_difference, - const struct pointer_set *subtrahend -); - -void *__pset_next_pointer( - struct pointer_set *set, - const void *pointer -); - -#define for_each_pointer_in_pset(set, pointer) for ((pointer) = __pset_next_pointer(set, NULL); (pointer) != NULL; (pointer) = __pset_next_pointer(set, (pointer))) - -/* - * concurrent pointer set - */ -int cpset_init( - struct concurrent_pointer_set *set, - size_t max_size -); - -void cpset_deinit(struct concurrent_pointer_set *set); - -static inline int cpset_lock(struct concurrent_pointer_set *set) { - return pthread_mutex_lock(&set->mutex); -} - -static inline int cpset_unlock(struct concurrent_pointer_set *set) { - return pthread_mutex_unlock(&set->mutex); -} - -static inline int cpset_put_locked( - struct concurrent_pointer_set *set, - void *pointer -) { - return pset_put(&set->set, pointer); -} - -static inline int cpset_put( - struct concurrent_pointer_set *set, - void *pointer -) { - int ok; - - cpset_lock(set); - ok = pset_put(&set->set, pointer); - cpset_unlock(set); - - return ok; -} - -static inline bool cpset_contains_locked( - struct concurrent_pointer_set *set, - const void *pointer -) { - return pset_contains(&set->set, pointer); -} - -static inline bool cpset_contains( - struct concurrent_pointer_set *set, - const void *pointer -) { - bool result; - - cpset_lock(set); - result = pset_contains(&set->set, pointer); - cpset_unlock(set); - - return result; -} - -static inline int cpset_remove_locked( - struct concurrent_pointer_set *set, - const void *pointer -) { - return pset_remove(&set->set, pointer); -} - -static inline int cpset_remove( - struct concurrent_pointer_set *set, - const void *pointer -) { - int ok; - - cpset_lock(set); - ok = cpset_remove_locked(set, pointer); - cpset_unlock(set); - - return ok; -} - -static inline int cpset_get_count_pointers_locked( - const struct concurrent_pointer_set *set -) { - return set->set.count_pointers; -} - -/** - * @brief Returns the size of the internal storage of set, in pointers. - */ -static inline int cpset_get_storage_size_locked( - const struct concurrent_pointer_set *set -) { - return set->set.size; -} - -static inline int cpset_copy_into_pset_locked( - struct concurrent_pointer_set *src, - struct pointer_set *dest -) { - return pset_copy(&src->set, dest); -} - -static inline void *__cpset_next_pointer_locked( - struct concurrent_pointer_set *set, - const void *pointer -) { - return __pset_next_pointer(&set->set, pointer); -} - -#define for_each_pointer_in_cpset(set, pointer) for ((pointer) = __cpset_next_pointer_locked(set, NULL); (pointer) != NULL; (pointer) = __cpset_next_pointer_locked(set, (pointer))) - -static inline void *memdup(const void *restrict src, const size_t n) { - void *__restrict__ dest; - - if ((src == NULL) || (n == 0)) return NULL; - - dest = malloc(n); - if (dest == NULL) return NULL; - - return memcpy(dest, src, n); -} - -#define BMAP_DECLARATION(name, n_bits) uint8_t name[(((n_bits) - 1) / 8) + 1] -#define BMAP_IS_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] & (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] |= (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_CLEAR(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] &= ~(1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) -#define BMAP_ZERO(p_bmap, n_bits) (memset((p_bmap), 0, (((n_bits) - 1) / 8) + 1)) - -#define min(a, b) ((a) < (b) ? (a) : (b)) -#define max(a, b) ((a) > (b) ? (a) : (b)) - -/** - * @brief Get the current time of the system monotonic clock. - * @returns time in nanoseconds. - */ -static inline uint64_t get_monotonic_time(void) { - struct timespec time; - clock_gettime(CLOCK_MONOTONIC, &time); - return time.tv_nsec + time.tv_sec*1000000000ull; -} - -#define FILE_DESCR(_logging_name) \ -static const char *__file_logging_name = _logging_name; - -#ifdef DEBUG -#define DEBUG_ASSERT(__cond) assert(__cond) -#define DEBUG_ASSERT_MSG(__cond, __msg) assert((__cond) && (__msg)) -#define LOG_ERROR(fmtstring, ...) fprintf(stderr, "[%s] " fmtstring, __file_logging_name, ##__VA_ARGS__) -#define LOG_ERROR_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) -#define LOG_DEBUG(fmtstring, ...) fprintf(stderr, "[%s] " fmtstring, __file_logging_name, ##__VA_ARGS__) -#define LOG_DEBUG_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) -#else -#define DEBUG_ASSERT(__cond) do {} while (0) -#define DEBUG_ASSERT_MSG(__cond, __msg) do {} while (0) -#define LOG_ERROR(fmtstring, ...) fprintf(stderr, "[%s] " fmtstring, __file_logging_name, ##__VA_ARGS__) -#define LOG_ERROR_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) -#define LOG_DEBUG(fmtstring, ...) do {} while (0) -#define LOG_DEBUG_UNPREFIXED(fmtstring, ...) do {} while (0) -#endif - -#define DEBUG_ASSERT_NOT_NULL(__var) DEBUG_ASSERT(__var != NULL) -#define DEBUG_ASSERT_EQUALS(__a, __b) DEBUG_ASSERT((__a) == (__b)) -#define DEBUG_ASSERT_EGL_TRUE(__var) DEBUG_ASSERT((__var) == EGL_TRUE) - -#if !(201112L <= __STDC_VERSION__ || (!defined __STRICT_ANSI__ && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR >= 6)))) -# error "Needs C11 or later or GCC (not in pedantic mode) 4.6.0 or later for compile time asserts." -#endif - -#define COMPILE_ASSERT_MSG(expression, msg) _Static_assert(expression, msg) -#define COMPILE_ASSERT(expression) COMPILE_ASSERT_MSG(expression, "Expression evaluates to false") - -#define UNIMPLEMENTED() assert(0 && "Unimplemented") - -static inline int refcount_inc_n(refcount_t *refcount, int n) { - return atomic_fetch_add_explicit(refcount, n, memory_order_relaxed); -} - -/// Increments the reference count and returns the previous value. -static inline int refcount_inc(refcount_t *refcount) { - return refcount_inc_n(refcount, 1); -} - -/// Decrement the reference count, return true if the refcount afterwards -/// is still non-zero. -static inline bool refcount_dec(refcount_t *refcount) { - return atomic_fetch_sub_explicit(refcount, 1, memory_order_acq_rel) != 1; -} - -/// Returns true if the reference count is one. -/// If this is the case you that means this thread has exclusive access -/// to the object. -static inline bool refcount_is_one(refcount_t *refcount) { - return atomic_load_explicit(refcount, memory_order_acquire) == 1; -} - -/// Returns true if the reference count is zero. Should never be true -/// in practice because that'd only be the case for a destroyed object. -/// So this is only really useful for debugging. -static inline bool refcount_is_zero(refcount_t *refcount) { - return atomic_load_explicit(refcount, memory_order_acquire) == 0; -} - -/// Get the current reference count, without any memory ordering restrictions. -/// Not strictly correct, should only be used for debugging. -static inline int refcount_get_for_debug(refcount_t *refcount) { - return atomic_load_explicit(refcount, memory_order_relaxed); -} - -#define REFCOUNT_INIT_0 (0) -#define REFCOUNT_INIT_1 (1) -#define REFCOUNT_INIT_N(n) (n) - -#define DECLARE_REF_OPS(obj_name) \ -struct obj_name *obj_name ## _ref(struct obj_name *obj); \ -void obj_name ## _unref(struct obj_name *obj); \ -void obj_name ## _unrefp(struct obj_name **obj); \ - -#define DEFINE_REF_OPS(obj_name, refcount_member_name) \ -struct obj_name *obj_name ## _ref(struct obj_name *obj) { \ - refcount_inc(&obj->refcount_member_name); \ - return obj; \ -} \ -void obj_name ## _unref(struct obj_name *obj) { \ - if (refcount_dec(&obj->refcount_member_name) == false) { \ - obj_name ## _destroy(obj); \ - } \ -} \ -void obj_name ## _unrefp(struct obj_name **obj) { \ - obj_name ## _unref(*obj); \ - *obj = NULL; \ -} - -#define DECLARE_LOCK_OPS(obj_name) \ -void obj_name ## _lock(struct obj_name *obj); \ -void obj_name ## _unlock(struct obj_name *obj); - -#define DEFINE_LOCK_OPS(obj_name, mutex_member_name) \ -void obj_name ## _lock(struct obj_name *obj) { \ - pthread_mutex_lock(&obj->mutex_member_name); \ -} \ -void obj_name ## _unlock(struct obj_name *obj) { \ - pthread_mutex_unlock(&obj->mutex_member_name); \ -} - -#define DEFINE_STATIC_LOCK_OPS(obj_name, mutex_member_name) \ -static void obj_name ## _lock(struct obj_name *obj) { \ - pthread_mutex_lock(&obj->mutex_member_name); \ -} \ -static void obj_name ## _unlock(struct obj_name *obj) { \ - pthread_mutex_unlock(&obj->mutex_member_name); \ -} - -#define DEFINE_INLINE_LOCK_OPS(obj_name, mutex_member_name) \ -static inline void obj_name ## _lock(struct obj_name *obj) { \ - pthread_mutex_lock(&obj->mutex_member_name); \ -} \ -static inline void obj_name ## _unlock(struct obj_name *obj) { \ - pthread_mutex_unlock(&obj->mutex_member_name); \ -} - -#define BITCAST(to_type, value) (*((const to_type*) (&(value)))) - -static inline int32_t uint32_to_int32(const uint32_t v) { - return BITCAST(int32_t, v); -} - -static inline uint32_t int32_to_uint32(const int32_t v) { - return BITCAST(uint32_t, v); -} - -#endif \ No newline at end of file diff --git a/include/compositor.h b/include/compositor.h deleted file mode 100644 index eb06a719..00000000 --- a/include/compositor.h +++ /dev/null @@ -1,327 +0,0 @@ -#ifndef _COMPOSITOR_H -#define _COMPOSITOR_H - -#include - -#include -#include - -#include -#include - -struct platform_view_params; - -typedef int (*platform_view_mount_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -); - -typedef int (*platform_view_unmount_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - void *userdata -); - -typedef int (*platform_view_update_view_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -); - -typedef int (*platform_view_present_cb)( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -); - -struct point { - double x, y; -}; - -struct quad { - struct point top_left, top_right, bottom_left, bottom_right; -}; - -struct aa_rect { - struct point offset, size; -}; - -static inline struct aa_rect get_aa_bounding_rect(const struct quad _rect) { - double l = min(min(min(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); - double r = max(max(max(_rect.top_left.x, _rect.top_right.x), _rect.bottom_left.x), _rect.bottom_right.x); - double t = min(min(min(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); - double b = max(max(max(_rect.top_left.y, _rect.top_right.y), _rect.bottom_left.y), _rect.bottom_right.y); - - return (struct aa_rect) { - .offset.x = l, - .offset.y = t, - .size.x = r - l, - .size.y = b - t - }; -} - -static inline struct quad get_quad(const struct aa_rect rect) { - return (struct quad) { - .top_left = rect.offset, - .top_right.x = rect.offset.x + rect.size.x, - .top_right.y = rect.offset.y, - .bottom_left.x = rect.offset.x, - .bottom_left.y = rect.offset.y + rect.size.y, - .bottom_right.x = rect.offset.x + rect.size.x, - .bottom_right.y = rect.offset.y + rect.size.y - }; -} - -static inline void apply_transform_to_point(const FlutterTransformation transform, struct point *point) { - apply_flutter_transformation(transform, &point->x, &point->y); -} - -static inline void apply_transform_to_quad(const FlutterTransformation transform, struct quad *rect) { - apply_transform_to_point(transform, &rect->top_left); - apply_transform_to_point(transform, &rect->top_right); - apply_transform_to_point(transform, &rect->bottom_left); - apply_transform_to_point(transform, &rect->bottom_right); -} - -static inline struct quad apply_transform_to_aa_rect(const FlutterTransformation transform, const struct aa_rect rect) { - struct quad _quad = get_quad(rect); - apply_transform_to_quad(transform, &_quad); - return _quad; -} - - -struct platform_view_params { - struct quad rect; - double rotation; - struct clip_rect *clip_rects; - size_t n_clip_rects; - double opacity; -}; - -struct compositor { - struct drmdev *drmdev; - - /** - * @brief Contains a struct for each existing platform view, containing the view id - * and platform view callbacks. - * - * @see compositor_set_view_callbacks compositor_remove_view_callbacks - */ - struct concurrent_pointer_set cbs; - - /** - * @brief Whether the compositor should invoke @ref rendertarget_gbm_new the next time - * flutter creates a backing store. Otherwise @ref rendertarget_nogbm_new is invoked. - * - * It's only possible to have at most one GBM-Surface backed backing store (== @ref rendertarget_gbm). So the first - * time @ref on_create_backing_store is invoked, a GBM-Surface backed backing store is returned and after that, - * only backing stores with @ref rendertarget_nogbm. - */ - bool should_create_window_surface_backing_store; - - /** - * @brief Whether the display mode was already applied. (Resolution, Refresh rate, etc) - * If it wasn't already applied, it will be the first time @ref on_present_layers - * is invoked. - */ - bool has_applied_modeset; - - FlutterCompositor flutter_compositor; - - /** - * @brief A cache of rendertargets that are not currently in use for - * any flutter layers and can be reused. - * - * Make sure to destroy all stale rendertargets before presentation so all the DRM planes - * that are reserved by any stale rendertargets get freed. - */ - struct concurrent_pointer_set stale_rendertargets; - - /** - * @brief Whether the mouse cursor is currently enabled and visible. - */ - - struct { - bool is_enabled; - int cursor_size; - const struct cursor_icon *current_cursor; - int current_rotation; - int hot_x, hot_y; - int x, y; - - bool has_buffer; - int buffer_depth; - int buffer_pitch; - int buffer_width; - int buffer_height; - int buffer_size; - uint32_t drm_fb_id; - uint32_t gem_bo_handle; - uint32_t *buffer; - } cursor; - - /** - * If true, @ref on_present_layers will commit blockingly. - * - * It will also schedule a simulated page flip event on the main thread - * afterwards so the frame queue works. - * - * If false, @ref on_present_layers will commit nonblocking using page flip events, - * like usual. - */ - bool do_blocking_atomic_commits; -}; - -/* -struct window_surface_backing_store { - struct compositor *compositor; - struct gbm_surface *gbm_surface; - struct gbm_bo *current_front_bo; - uint32_t drm_plane_id; -}; -*/ - -struct drm_rbo { - EGLImage egl_image; - GLuint gl_rbo_id; - uint32_t gem_handle; - uint32_t gem_stride; - uint32_t drm_fb_id; -}; - -struct drm_fb { - struct gbm_bo *bo; - uint32_t fb_id; -}; - -/* -struct drm_fb_backing_store { - struct compositor *compositor; - - GLuint gl_fbo_id; - struct drm_rbo rbos[2]; - - // The front FB is the one GL is rendering to right now, similiar - // to libgbm. - int current_front_rbo; - - uint32_t drm_plane_id; -}; - -enum backing_store_type { - kWindowSurface, - kDrmFb -}; - -struct backing_store_metadata { - enum backing_store_type type; - union { - struct window_surface_backing_store window_surface; - struct drm_fb_backing_store drm_fb; - }; -}; -*/ - -struct rendertarget_gbm { - struct gbm_surface *gbm_surface; - struct gbm_bo *current_front_bo; -}; - -/** - * @brief No-GBM Rendertarget. - * A type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. - */ -struct rendertarget_nogbm { - GLuint gl_fbo_id; - struct drm_rbo rbos[2]; - - /** - * @brief The index of the @ref drm_rbo in the @ref rendertarget_nogbm::rbos array that - * OpenGL is currently rendering into. - */ - int current_front_rbo; -}; - -struct rendertarget { - bool is_gbm; - - struct compositor *compositor; - - union { - struct rendertarget_gbm gbm; - struct rendertarget_nogbm nogbm; - }; - - GLuint gl_fbo_id; - - void (*destroy)(struct rendertarget *target); - int (*present)( - struct rendertarget *target, - struct drmdev_atomic_req *atomic_req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos - ); - int (*present_legacy)( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode - ); -}; - -struct flutterpi_backing_store { - struct rendertarget *target; - FlutterBackingStore flutter_backing_store; - bool should_free_on_next_destroy; -}; - -extern const FlutterCompositor flutter_compositor; - -int compositor_on_page_flip( - uint32_t sec, - uint32_t usec -); - -int compositor_set_view_callbacks( - int64_t view_id, - platform_view_mount_cb mount, - platform_view_unmount_cb unmount, - platform_view_update_view_cb update_view, - platform_view_present_cb present, - void *userdata -); - -int compositor_remove_view_callbacks( - int64_t view_id -); - -int compositor_apply_cursor_state( - bool is_enabled, - int rotation, - double device_pixel_ratio -); - -int compositor_set_cursor_pos(int x, int y); - -int compositor_initialize( - struct drmdev *drmdev -); - - -#endif \ No newline at end of file diff --git a/include/cursor.h b/include/cursor.h deleted file mode 100644 index 872c029b..00000000 --- a/include/cursor.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _CURSOR_H -#define _CURSOR_H - -#include - -struct cursor_icon { - int rotation; - int hot_x, hot_y; - int width, height; - uint32_t *data; -}; - -extern const struct cursor_icon cursors[5]; -extern int n_cursors; - -#endif \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h deleted file mode 100644 index a44348b2..00000000 --- a/include/flutter-pi.h +++ /dev/null @@ -1,461 +0,0 @@ -#ifndef _FLUTTERPI_H -#define _FLUTTERPI_H - -#define LOG_FLUTTERPI_ERROR(...) fprintf(stderr, "[flutter-pi] " __VA_ARGS__) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -//#define EGL_EGLEXT_PROTOTYPES -#include -#include -//#define GL_GLEXT_PROTOTYPES -#include -#include - -#include -#include -#include - -#define LOAD_EGL_PROC(flutterpi_struct, name, full_name) \ - do { \ - (flutterpi_struct).egl.name = (void*) eglGetProcAddress(#full_name); \ - if ((flutterpi_struct).egl.name == NULL) { \ - fprintf(stderr, "[flutter-pi] FATAL: Could not resolve EGL procedure " #full_name "\n"); \ - return EINVAL; \ - } \ - } while (false) - -#define LOAD_GL_PROC(flutterpi_struct, name, full_name) \ - do { \ - (flutterpi_struct).gl.name = (void*) eglGetProcAddress(#full_name); \ - if ((flutterpi_struct).gl.name == NULL) { \ - fprintf(stderr, "[flutter-pi] FATAL: Could not resolve GL procedure " #full_name "\n"); \ - return EINVAL; \ - } \ - } while (false) - -enum device_orientation { - kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight -}; - -/// TODO: Use flutter_embedder.h proctable instead -struct libflutter_engine { - FlutterEngineResult (*FlutterEngineCreateAOTData)(const FlutterEngineAOTDataSource* source, FlutterEngineAOTData* data_out); - FlutterEngineResult (*FlutterEngineCollectAOTData)(FlutterEngineAOTData data); - FlutterEngineResult (*FlutterEngineRun)(size_t version, const FlutterRendererConfig* config, const FlutterProjectArgs* args, void* user_data, FlutterEngine *engine_out); - FlutterEngineResult (*FlutterEngineShutdown)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineInitialize)(size_t version, const FlutterRendererConfig* config, const FlutterProjectArgs* args, void* user_data, FlutterEngine *engine_out); - FlutterEngineResult (*FlutterEngineDeinitialize)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineRunInitialized)(FlutterEngine engine); - FlutterEngineResult (*FlutterEngineSendWindowMetricsEvent)(FlutterEngine engine, const FlutterWindowMetricsEvent* event); - FlutterEngineResult (*FlutterEngineSendPointerEvent)(FlutterEngine engine, const FlutterPointerEvent* events, size_t events_count); - FlutterEngineResult (*FlutterEngineSendPlatformMessage)(FlutterEngine engine, const FlutterPlatformMessage* message); - FlutterEngineResult (*FlutterPlatformMessageCreateResponseHandle)(FlutterEngine engine, FlutterDataCallback data_callback, void* user_data, FlutterPlatformMessageResponseHandle** response_out); - FlutterEngineResult (*FlutterPlatformMessageReleaseResponseHandle)(FlutterEngine engine, FlutterPlatformMessageResponseHandle* response); - FlutterEngineResult (*FlutterEngineSendPlatformMessageResponse)(FlutterEngine engine, const FlutterPlatformMessageResponseHandle* handle, const uint8_t* data, size_t data_length); - FlutterEngineResult (*__FlutterEngineFlushPendingTasksNow)(); - FlutterEngineResult (*FlutterEngineRegisterExternalTexture)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineUnregisterExternalTexture)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineMarkExternalTextureFrameAvailable)(FlutterEngine engine, int64_t texture_identifier); - FlutterEngineResult (*FlutterEngineUpdateSemanticsEnabled)(FlutterEngine engine, bool enabled); - FlutterEngineResult (*FlutterEngineUpdateAccessibilityFeatures)(FlutterEngine engine, FlutterAccessibilityFeature features); - FlutterEngineResult (*FlutterEngineDispatchSemanticsAction)(FlutterEngine engine, uint64_t id, FlutterSemanticsAction action, const uint8_t* data, size_t data_length); - FlutterEngineResult (*FlutterEngineOnVsync)(FlutterEngine engine, intptr_t baton, uint64_t frame_start_time_nanos, uint64_t frame_target_time_nanos); - FlutterEngineResult (*FlutterEngineReloadSystemFonts)(FlutterEngine engine); - void (*FlutterEngineTraceEventDurationBegin)(const char* name); - void (*FlutterEngineTraceEventDurationEnd)(const char* name); - void (*FlutterEngineTraceEventInstant)(const char* name); - FlutterEngineResult (*FlutterEnginePostRenderThreadTask)(FlutterEngine engine, VoidCallback callback, void* callback_data); - uint64_t (*FlutterEngineGetCurrentTime)(); - FlutterEngineResult (*FlutterEngineRunTask)(FlutterEngine engine, const FlutterTask* task); - FlutterEngineResult (*FlutterEngineUpdateLocales)(FlutterEngine engine, const FlutterLocale** locales, size_t locales_count); - bool (*FlutterEngineRunsAOTCompiledDartCode)(void); - FlutterEngineResult (*FlutterEnginePostDartObject)(FlutterEngine engine, FlutterEngineDartPort port, const FlutterEngineDartObject* object); - FlutterEngineResult (*FlutterEngineNotifyLowMemoryWarning)(FlutterEngine engine); - FlutterEngineResult (*FlutterEnginePostCallbackOnAllNativeThreads)(FlutterEngine engine, FlutterNativeThreadCallback callback, void* user_data); - FlutterEngineResult (*FlutterEngineNotifyDisplayUpdate)(FlutterEngine engine, FlutterEngineDisplaysUpdateType update_type, const FlutterEngineDisplay* displays, size_t display_count); -}; - -#define ANGLE_FROM_ORIENTATION(o) \ - ((o) == kPortraitUp ? 0 : \ - (o) == kLandscapeLeft ? 90 : \ - (o) == kPortraitDown ? 180 : \ - (o) == kLandscapeRight ? 270 : 0) - -#define ANGLE_BETWEEN_ORIENTATIONS(o_start, o_end) \ - (ANGLE_FROM_ORIENTATION(o_end) \ - - ANGLE_FROM_ORIENTATION(o_start) \ - + (ANGLE_FROM_ORIENTATION(o_start) > ANGLE_FROM_ORIENTATION(o_end) ? 360 : 0)) - -#define FLUTTER_TRANSLATION_TRANSFORMATION(translate_x, translate_y) ((FlutterTransformation) \ - {.scaleX = 1, .skewX = 0, .transX = translate_x, \ - .skewY = 0, .scaleY = 1, .transY = translate_y, \ - .pers0 = 0, .pers1 = 0, .pers2 = 1}) - -#define FLUTTER_ROTX_TRANSFORMATION(deg) ((FlutterTransformation) \ - {.scaleX = 1, .skewX = 0, .transX = 0, \ - .skewY = 0, .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = -sin(((double) (deg))/180.0*M_PI), \ - .pers0 = 0, .pers1 = sin(((double) (deg))/180.0*M_PI), .pers2 = cos(((double) (deg))/180.0*M_PI)}) - -#define FLUTTER_ROTY_TRANSFORMATION(deg) ((FlutterTransformation) \ - {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = 0, .transX = sin(((double) (deg))/180.0*M_PI), \ - .skewY = 0, .scaleY = 1, .transY = 0, \ - .pers0 = -sin(((double) (deg))/180.0*M_PI), .pers1 = 0, .pers2 = cos(((double) (deg))/180.0*M_PI)}) - -/** - * A flutter transformation that rotates any coords around the z-axis, counter-clockwise. - */ -#define FLUTTER_ROTZ_TRANSFORMATION(deg) ((FlutterTransformation) \ - {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = -sin(((double) (deg))/180.0*M_PI), .transX = 0, \ - .skewY = sin(((double) (deg))/180.0*M_PI), .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = 0, \ - .pers0 = 0, .pers1 = 0, .pers2 = 1}) - -/** - * A transformation that is the result of multiplying a with b. - */ -#define FLUTTER_MULTIPLIED_TRANSFORMATIONS(a, b) ((FlutterTransformation) \ - {.scaleX = a.scaleX * b.scaleX + a.skewX * b.skewY + a.transX * b.pers0, \ - .skewX = a.scaleX * b.skewX + a.skewX * b.scaleY + a.transX * b.pers1, \ - .transX = a.scaleX * b.transX + a.skewX * b.transY + a.transX * b.pers2, \ - .skewY = a.skewY * b.scaleX + a.scaleY * b.skewY + a.transY * b.pers0, \ - .scaleY = a.skewY * b.skewX + a.scaleY * b.scaleY + a.transY * b.pers1, \ - .transY = a.skewY * b.transX + a.scaleY * b.transY + a.transY * b.pers2, \ - .pers0 = a.pers0 * b.scaleX + a.pers1 * b.skewY + a.pers2 * b.pers0, \ - .pers1 = a.pers0 * b.skewX + a.pers1 * b.scaleY + a.pers2 * b.pers1, \ - .pers2 = a.pers0 * b.transX + a.pers1 * b.transY + a.pers2 * b.pers2}) - -/** - * A transformation that is the result of adding a with b. - */ -#define FLUTTER_ADDED_TRANSFORMATIONS(a, b) ((FlutterTransformation) \ - {.scaleX = a.scaleX + b.scaleX, .skewX = a.skewX + b.skewX, .transX = a.transX + b.transX, \ - .skewY = a.skewY + b.skewY, .scaleY = a.scaleY + b.scaleY, .transY = a.transY + b.transY, \ - .pers0 = a.pers0 + b.pers0, .pers1 = a.pers1 + b.pers1, .pers2 = a.pers2 + b.pers2 \ - }) - -/** - * A transformation that is the result of transponating a. - */ -#define FLUTTER_TRANSPONATED_TRANSFORMATION(a) ((FlutterTransformation) \ - {.scaleX = a.scaleX, .skewX = a.skewY, .transX = a.pers0, \ - .skewY = a.skewX, .scaleY = a.scaleY, .transY = a.pers1, \ - .pers0 = a.transX, .pers1 = a.transY, .pers2 = a.pers2, \ - }) - -static inline void apply_flutter_transformation( - const FlutterTransformation t, - double *px, - double *py -) { - double x = px != NULL ? *px : 0; - double y = py != NULL ? *py : 0; - - if (px != NULL) { - *px = t.scaleX*x + t.skewX*y + t.transX; - } - - if (py != NULL) { - *py = t.skewY*x + t.scaleY*y + t.transY; - } -} - -#define FLUTTER_RESULT_TO_STRING(result) \ - ((result) == kSuccess ? "Success." : \ - (result) == kInvalidLibraryVersion ? "Invalid library version." : \ - (result) == kInvalidArguments ? "Invalid arguments." : \ - (result) == kInternalInconsistency ? "Internal inconsistency." : "(?)") - -#define LIBINPUT_EVENT_IS_TOUCH(event_type) (\ - ((event_type) == LIBINPUT_EVENT_TOUCH_DOWN) || \ - ((event_type) == LIBINPUT_EVENT_TOUCH_UP) || \ - ((event_type) == LIBINPUT_EVENT_TOUCH_MOTION) || \ - ((event_type) == LIBINPUT_EVENT_TOUCH_CANCEL) || \ - ((event_type) == LIBINPUT_EVENT_TOUCH_FRAME)) - -#define LIBINPUT_EVENT_IS_POINTER(event_type) (\ - ((event_type) == LIBINPUT_EVENT_POINTER_MOTION) || \ - ((event_type) == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) || \ - ((event_type) == LIBINPUT_EVENT_POINTER_BUTTON) || \ - ((event_type) == LIBINPUT_EVENT_POINTER_AXIS)) - -#define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (\ - ((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) - -enum frame_state { - kFramePending, - kFrameRendering, - kFrameRendered -}; - -struct frame { - /// The current state of the frame. - /// - Pending, when the frame was requested using the FlutterProjectArgs' vsync_callback. - /// - Rendering, when the baton was returned to the engine - /// - Rendered, when the frame has been / is visible on the display. - enum frame_state state; - - /// The baton to be returned to the flutter engine when the frame can be rendered. - intptr_t baton; -}; - -struct compositor; - -enum flutter_runtime_mode { - kDebug, kProfile, kRelease -}; - -#define FLUTTER_RUNTIME_MODE_IS_JIT(runtime_mode) ((runtime_mode) == kDebug) -#define FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode) ((runtime_mode) == kProfile || (runtime_mode) == kRelease) - -struct plugin_registry; -struct texture_registry; - -struct flutter_paths { - char *app_bundle_path; - char *asset_bundle_path; - char *app_elf_path; - char *icudtl_path; - char *kernel_blob_path; - char *flutter_engine_path; - char *flutter_engine_dlopen_name; - char *flutter_engine_dlopen_name_fallback; -}; - -struct flutterpi { - /// graphics stuff - struct { - struct drmdev *drmdev; - drmEventContext evctx; - sd_event_source *drm_pageflip_event_source; - bool platform_supports_get_sequence_ioctl; - } drm; - - struct { - struct gbm_device *device; - struct gbm_surface *surface; - uint32_t format; - uint64_t modifier; - } gbm; - - struct { - EGLDisplay display; - EGLConfig config; - EGLContext root_context; - EGLContext flutter_render_context; - EGLContext flutter_resource_uploading_context; - EGLContext compositor_context; - - /// Used to lock the @ref temp_context, to be sure we only try to make it current on - /// one thread. - pthread_mutex_t temp_context_lock; - - /// An EGL context that's only made current to create new contexts, - /// for example when some native code calls @ref flutterpi_create_egl_context - /// to get a new context. - EGLContext temp_context; - - EGLSurface surface; - - char *renderer; - - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface; - PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurface; - PFNEGLCREATEDRMIMAGEMESAPROC createDRMImageMESA; - PFNEGLEXPORTDRMIMAGEMESAPROC exportDRMImageMESA; - } egl; - - struct { - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES; - PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC EGLImageTargetRenderbufferStorageOES; - } gl; - - struct { - /// width & height of the display in pixels. - int width, height; - - /// physical width & height of the display in millimeters - /// the physical size can only be queried for HDMI displays (and even then, most displays will - /// probably return bogus values like 160mm x 90mm). - /// for DSI displays, the physical size of the official 7-inch display will be set in init_display. - /// init_display will only update width_mm and height_mm if they are set to zero, allowing you - /// to hardcode values for you individual display. - int width_mm, height_mm; - - int refresh_rate; - - /// The pixel ratio used by flutter. - /// This is computed inside init_display using width_mm and height_mm. - /// flutter only accepts pixel ratios >= 1.0 - double pixel_ratio; - } display; - - struct { - /// This is false when the value in the "orientation" field is - /// unset. (Since we can't just do orientation = null) - bool has_orientation; - - /// The current device orientation. - /// The initial device orientation is based on the width & height data from drm, - /// or given as command line arguments. - enum device_orientation orientation; - - bool has_rotation; - - /// The angle between the initial device orientation and the current device orientation in degrees. - /// (applied as a rotation to the flutter window in transformation_callback, and also - /// is used to determine if width/height should be swapped when sending a WindowMetrics event to flutter) - int rotation; - - /// width & height of the flutter view. These are the dimensions send to flutter using - /// [FlutterEngineSendWindowMetricsEvent]. (So, for example, with rotation == 90, these - /// dimensions are swapped compared to the display dimensions) - int width, height; - - int width_mm, height_mm; - - /// Used by flutter to transform the flutter view to fill the display. - /// Matrix that transforms flutter view coordinates to display coordinates. - FlutterTransformation view_to_display_transform; - - /// Used by the touch input to transform raw display coordinates into flutter view coordinates. - /// Matrix that transforms display coordinates into flutter view coordinates - FlutterTransformation display_to_view_transform; - } view; - - struct concurrent_queue frame_queue; - - struct compositor *compositor; - - /// IO - sd_event_source *user_input_event_source; - struct user_input *user_input; - - /// Locales - struct locales *locales; - - /// flutter stuff - struct { - char *bundle_path; - struct flutter_paths *paths; - void *app_elf_handle; - - FlutterLocale **locales; - size_t n_locales; - - int engine_argc; - char **engine_argv; - enum flutter_runtime_mode runtime_mode; - struct libflutter_engine libflutter_engine; - FlutterEngine engine; - } flutter; - - /// main event loop - pthread_t event_loop_thread; - pthread_mutex_t event_loop_mutex; - sd_event *event_loop; - int wakeup_event_loop_fd; - - /// flutter-pi internal stuff - struct plugin_registry *plugin_registry; - struct texture_registry *texture_registry; -}; - -struct platform_task { - int (*callback)(void *userdata); - void *userdata; -}; - -struct platform_message { - bool is_response; - union { - FlutterPlatformMessageResponseHandle *target_handle; - struct { - char *target_channel; - FlutterPlatformMessageResponseHandle *response_handle; - }; - }; - uint8_t *message; - size_t message_size; -}; - -extern struct flutterpi flutterpi; - -int flutterpi_fill_view_properties( - bool has_orientation, - enum device_orientation orientation, - bool has_rotation, - int rotation -); - -int flutterpi_post_platform_task( - int (*callback)(void *userdata), - void *userdata -); - -int flutterpi_post_platform_task_with_time( - int (*callback)(void *userdata), - void *userdata, - uint64_t target_time_usec -); - -int flutterpi_sd_event_add_io( - sd_event_source **source_out, - int fd, - uint32_t events, - sd_event_io_handler_t callback, - void *userdata -); - -int flutterpi_send_platform_message( - const char *channel, - const uint8_t *restrict message, - size_t message_size, - FlutterPlatformMessageResponseHandle *responsehandle -); - -int flutterpi_respond_to_platform_message( - FlutterPlatformMessageResponseHandle *handle, - const uint8_t *restrict message, - size_t message_size -); - -struct texture_registry *flutterpi_get_texture_registry( - struct flutterpi *flutterpi -); - -struct texture *flutterpi_create_texture(struct flutterpi *flutterpi); - -const char *flutterpi_get_asset_bundle_path( - struct flutterpi *flutterpi -); - -int flutterpi_schedule_exit(void); - -struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi); - -EGLDisplay flutterpi_get_egl_display(struct flutterpi *flutterpi); - -EGLContext flutterpi_create_egl_context(struct flutterpi *flutterpi); - -void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name); - -void flutterpi_trace_event_begin(struct flutterpi *flutterpi, const char *name); - -void flutterpi_trace_event_end(struct flutterpi *flutterpi, const char *name); - -#endif \ No newline at end of file diff --git a/include/jsmn.h b/include/jsmn.h deleted file mode 100644 index 764bb2f2..00000000 --- a/include/jsmn.h +++ /dev/null @@ -1,471 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2010 Serge Zaitsev - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef JSMN_H -#define JSMN_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef JSMN_STATIC -#define JSMN_API static -#else -#define JSMN_API extern -#endif - -/** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 -} jsmntype_t; - -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; - -/** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ -typedef struct { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif -} jsmntok_t; - -/** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string. - */ -typedef struct { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g. parent object or array */ -} jsmn_parser; - -/** - * Create JSON parser over an array of tokens - */ -JSMN_API void jsmn_init(jsmn_parser *parser); - -/** - * Run JSON parser. It parses a JSON data string into and array of tokens, each - * describing - * a single JSON object. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens); - -#ifndef JSMN_HEADER -/** - * Allocates a fresh unused token from the token pool. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const int start, const int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t': - case '\r': - case '\n': - case ' ': - case ',': - case ']': - case '}': - goto found; - default: - /* to quiet a warning from gcc*/ - break; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - parser->pos++; - - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': - case '/': - case '\\': - case 'b': - case 'f': - case 'r': - case 'n': - case 't': - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; - i++) { - /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': - case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - return JSMN_ERROR_NOMEM; - } - if (parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; -#ifdef JSMN_STRICT - /* In strict mode an object or array can't become a key */ - if (t->type == JSMN_OBJECT) { - return JSMN_ERROR_INVAL; - } -#endif - t->size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': - case ']': - if (tokens == NULL) { - break; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if (token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) { - return JSMN_ERROR_INVAL; - } - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - case '\t': - case '\r': - case '\n': - case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 't': - case 'f': - case 'n': - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - const jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -JSMN_API void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} - -#endif /* JSMN_HEADER */ - -#ifdef __cplusplus -} -#endif - -#endif /* JSMN_H */ \ No newline at end of file diff --git a/include/modesetting.h b/include/modesetting.h deleted file mode 100644 index 8ea1a766..00000000 --- a/include/modesetting.h +++ /dev/null @@ -1,307 +0,0 @@ -#ifndef _MODESETTING_H -#define _MODESETTING_H - -#include -#include - -#include -#include - -#include - -struct drm_connector { - drmModeConnector *connector; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; -}; - -struct drm_encoder { - drmModeEncoder *encoder; -}; - -struct drm_crtc { - drmModeCrtc *crtc; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - uint32_t bitmask; - uint8_t index; -}; - -struct drm_plane { - int type; - drmModePlane *plane; - drmModeObjectProperties *props; - drmModePropertyRes **props_info; -}; - -struct drmdev { - int fd; - - pthread_mutex_t mutex; - bool supports_atomic_modesetting; - - size_t n_connectors; - struct drm_connector *connectors; - - size_t n_encoders; - struct drm_encoder *encoders; - - size_t n_crtcs; - struct drm_crtc *crtcs; - - size_t n_planes; - struct drm_plane *planes; - - drmModeRes *res; - drmModePlaneRes *plane_res; - - bool is_configured; - const struct drm_connector *selected_connector; - const struct drm_encoder *selected_encoder; - const struct drm_crtc *selected_crtc; - const drmModeModeInfo *selected_mode; - uint32_t selected_mode_blob_id; -}; - -struct drmdev_atomic_req { - struct drmdev *drmdev; - drmModeAtomicReq *atomic_req; - - void *available_planes_storage[32]; - struct pointer_set available_planes; -}; - -int drmdev_new_from_fd( - struct drmdev **drmdev_out, - int fd -); - -int drmdev_new_from_path( - struct drmdev **drmdev_out, - const char *path -); - -int drmdev_configure( - struct drmdev *drmdev, - uint32_t connector_id, - uint32_t encoder_id, - uint32_t crtc_id, - const drmModeModeInfo *mode -); - -int drmdev_plane_get_type( - struct drmdev *drmdev, - uint32_t plane_id -); - -int drmdev_plane_supports_setting_rotation_value( - struct drmdev *drmdev, - uint32_t plane_id, - int drm_rotation, - bool *result -); - -int drmdev_plane_get_min_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *min_zpos_out -); - -int drmdev_plane_get_max_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t *max_zpos_out -); - -int drmdev_plane_supports_setting_zpos( - struct drmdev *drmdev, - uint32_t plane_id, - bool *result -); - -int drmdev_plane_supports_setting_zpos_value( - struct drmdev *drmdev, - uint32_t plane_id, - int64_t zpos, - bool *result -); - -int drmdev_new_atomic_req( - struct drmdev *drmdev, - struct drmdev_atomic_req **req_out -); - -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -); - -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); - -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value -); - -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value -); - -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -); - -inline static int drmdev_atomic_req_reserve_plane( - struct drmdev_atomic_req *req, - struct drm_plane *plane -) { - return pset_remove(&req->available_planes, plane); -} - -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, - void *userdata -); - -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -); - -/** - * @brief Do a nonblocking, vblank-synced framebuffer swap. - */ -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, - void *userdata -); - -/** - * @brief Do a blocking, vblank-synced framebuffer swap. - * Using this in combination with @ref drmdev_legacy_primary_plane_pageflip - * is not a good idea, since it will block until the primary plane pageflip is complete, - * and then block even longer till the overlay plane pageflip completes the vblank after. - */ -int drmdev_legacy_overlay_plane_pageflip( - struct drmdev *drmdev, - uint32_t plane_id, - uint32_t fb_id, - int32_t crtc_x, - int32_t crtc_y, - int32_t crtc_w, - int32_t crtc_h, - uint32_t src_x, - uint32_t src_y, - uint32_t src_w, - uint32_t src_h -); - -int drmdev_legacy_set_connector_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -); - -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -); - -int drmdev_legacy_set_plane_property( - struct drmdev *drmdev, - uint32_t plane_id, - const char *name, - uint64_t value -); - -float mode_get_vrefresh(const drmModeModeInfo *mode); - -inline static struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { - bool found = connector == NULL; - for (size_t i = 0; i < drmdev->n_connectors; i++) { - if (drmdev->connectors + i == connector) { - found = true; - } else if (found) { - return drmdev->connectors + i; - } - } - - return NULL; -} - -inline static struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { - bool found = encoder == NULL; - for (size_t i = 0; i < drmdev->n_encoders; i++) { - if (drmdev->encoders + i == encoder) { - found = true; - } else if (found) { - return drmdev->encoders + i; - } - } - - return NULL; -} - -inline static struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { - bool found = crtc == NULL; - for (size_t i = 0; i < drmdev->n_crtcs; i++) { - if (drmdev->crtcs + i == crtc) { - found = true; - } else if (found) { - return drmdev->crtcs + i; - } - } - - return NULL; -} - -inline static struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { - bool found = plane == NULL; - for (size_t i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes + i == plane) { - found = true; - } else if (found) { - return drmdev->planes + i; - } - } - - return NULL; -} - -inline static drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { - bool found = mode == NULL; - for (int i = 0; i < connector->connector->count_modes; i++) { - if (connector->connector->modes + i == mode) { - found = true; - } else if (found) { - return connector->connector->modes + i; - } - } - - return NULL; -} - -#define for_each_connector_in_drmdev(drmdev, connector) for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) - -#define for_each_encoder_in_drmdev(drmdev, encoder) for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) - -#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) - -#define for_each_plane_in_drmdev(drmdev, plane) for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) - -#define for_each_mode_in_connector(connector, mode) for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) - -#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) - -#endif diff --git a/include/pixel_format.h b/include/pixel_format.h deleted file mode 100644 index 809cf5b5..00000000 --- a/include/pixel_format.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef _FLUTTERPI_INCLUDE_PIXEL_FORMAT_H -#define _FLUTTERPI_INCLUDE_PIXEL_FORMAT_H - -#include - -#ifdef HAS_FBDEV -#include - -/** - * @brief Description of a fbdev pixel format. - * - */ -struct fbdev_pixfmt { - struct fb_bitfield r, g, b, a; -}; - -#endif - -#ifdef HAS_GBM -#include -#endif - -#ifdef HAS_KMS -#include -#endif - -/** - * @brief A specific pixel format. Use @ref get_pixfmt_info to get information - * about this pixel format. - * - */ -enum pixfmt { - kRGB565_FpiPixelFormat, - kARGB8888_FpiPixelFormat, - kXRGB8888_FpiPixelFormat, - kBGRA8888_FpiPixelFormat, - kRGBA8888_FpiPixelFormat, - kMax_PixFmt = kRGBA8888_FpiPixelFormat, - kCount_PixFmt = kMax_PixFmt + 1 -}; - -// Just a pedantic check so we don't update the pixfmt enum without changing kMax_PixFmt -COMPILE_ASSERT(kMax_PixFmt == kRGBA8888_FpiPixelFormat); - -#define PIXFMT_LIST(V) \ - V( "RGB 5:6:5", "RGB565", kRGB565_FpiPixelFormat, /*bpp*/ 16, /*opaque*/ true, /*R*/ 5, 11, /*G*/ 6, 5, /*B*/ 5, 0, /*A*/ 0, 0, /*GBM fourcc*/ GBM_FORMAT_RGB565, /*DRM fourcc*/ DRM_FORMAT_RGB565) \ - V("ARGB 8:8:8:8", "ARGB8888", kARGB8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 8, 24, /*GBM fourcc*/ GBM_FORMAT_ARGB8888, /*DRM fourcc*/ DRM_FORMAT_ARGB8888) \ - V("XRGB 8:8:8:8", "XRGB8888", kXRGB8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ true, /*R*/ 8, 16, /*G*/ 8, 8, /*B*/ 8, 0, /*A*/ 0, 24, /*GBM fourcc*/ GBM_FORMAT_XRGB8888, /*DRM fourcc*/ DRM_FORMAT_XRGB8888) \ - V("BGRA 8:8:8:8", "BGRA8888", kBGRA8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 8, /*G*/ 8, 16, /*B*/ 8, 24, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_BGRA8888, /*DRM fourcc*/ DRM_FORMAT_BGRA8888) \ - V("RGBA 8:8:8:8", "RGBA8888", kRGBA8888_FpiPixelFormat, /*bpp*/ 32, /*opaque*/ false, /*R*/ 8, 24, /*G*/ 8, 16, /*B*/ 8, 8, /*A*/ 8, 0, /*GBM fourcc*/ GBM_FORMAT_RGBA8888, /*DRM fourcc*/ DRM_FORMAT_RGBA8888) - -// make sure the macro list we defined has as many elements as the pixfmt enum. -#define __COUNT(...) +1 -COMPILE_ASSERT(0 PIXFMT_LIST(__COUNT) == kMax_PixFmt+1); -#undef __COUNT - -/** - * @brief Information about a pixel format. - * - */ -struct pixfmt_info { - /** - * @brief A descriptive, human-readable name for this pixel format. - * - * Example: RGB 5:6:5 - */ - const char *name; - - /** - * @brief A short, unique name for this pixel format, to use it as a commandline argument for example. - * - * Example: RGB565 - * - */ - const char *arg_name; - - /** - * @brief The pixel format that this struct provides information about. - */ - enum pixfmt format; - - /** - * @brief How many bits per pixel does this pixel format use? - */ - int bits_per_pixel; - - /** - * @brief True if there's no way to specify transparency with this format. - */ - bool is_opaque; - -#ifdef HAS_FBDEV - /** - * @brief The fbdev format equivalent to this pixel format. - */ - struct fbdev_pixfmt fbdev_format; -#endif -#ifdef HAS_GBM - /** - * @brief The GBM format equivalent to this pixel format. - */ - uint32_t gbm_format; -#endif -#ifdef HAS_KMS - /** - * @brief The GBM format equivalent to this pixel format. - */ - uint32_t drm_format; -#endif -}; - -/** - * @brief A list of known pixel-formats, with some details about them. - * - */ -extern const struct pixfmt_info pixfmt_infos[]; -extern const size_t n_pixfmt_infos; - -/** - * @brief Get the pixel format info for a specific pixel format. - * - */ -static inline const struct pixfmt_info *get_pixfmt_info(enum pixfmt format) { - DEBUG_ASSERT(format > 0 && format <= kMax_PixFmt); - return pixfmt_infos + format; -} - -#endif // _FLUTTERPI_INCLUDE_PIXEL_FORMAT_H diff --git a/include/platformchannel.h b/include/platformchannel.h deleted file mode 100644 index acbe3805..00000000 --- a/include/platformchannel.h +++ /dev/null @@ -1,871 +0,0 @@ -#ifndef _METHODCHANNEL_H -#define _METHODCHANNEL_H - -#include -#include -#include -#include - -#define JSON_DECODE_TOKENLIST_SIZE 128 - - -/* - * It may be simpler for plugins if the two message value types were unified. - * But from a performance POV, this doesn't make sense. number arrays in StandardMessageCodec - * are 4 or 8 -byte aligned for faster access. We don't have to copy them, StdMsgCodecValue.int64array (as an example) - * is just a pointer to that portion of the buffer, where the array is located. - * - * However, JSON and thus JSON Message Handlers have no idea what a int64array is, they just know of JSON arrays. - * This means we'd have to implicitly convert the int64array into a JSON array when we want to unify the two message value types, - * and this costs all the performance we (more precisely, the flutter engineers) gained by memory-aligning the arrays in StdMsgCodecValue. - * - * Let's just hope the flutter team doesn't randomly switch codecs of platform channels. Receive Handlers would - * need to be rewritten every time they do. The handlers not needing to be rewritten would probably be the only advantage - * of using a unified message value type. - */ -enum json_value_type { - kJsonNull, - kJsonTrue, - kJsonFalse, - kJsonNumber, - kJsonString, - kJsonArray, - kJsonObject -}; -struct json_value { - enum json_value_type type; - union { - double number_value; - char *string_value; - struct { - size_t size; - union { - struct json_value *array; - struct { - char **keys; - struct json_value *values; - }; - }; - }; - }; -}; - -enum std_value_type { - kStdNull = 0, - kStdTrue, - kStdFalse, - kStdInt32, - kStdInt64, - kStdLargeInt, // treat as kString - kStdFloat64, - kStdString, - kStdUInt8Array, - kStdInt32Array, - kStdInt64Array, - kStdFloat64Array, - kStdList, - kStdMap -}; -struct std_value { - enum std_value_type type; - union { - bool bool_value; - int32_t int32_value; - int64_t int64_value; - double float64_value; - char* string_value; - struct { - size_t size; - union { - uint8_t* uint8array; - int32_t* int32array; - int64_t* int64array; - double* float64array; - struct std_value* list; - struct { - struct std_value* keys; - struct std_value* values; - }; - }; - }; - }; -}; - -struct std_value_v2 { - enum std_value_type type; -}; - -#define STDVALUE_IS_NULL(value) ((value).type == kStdNull) -#define STDNULL ((struct std_value) {.type = kStdNull}) - -#define STDVALUE_IS_BOOL(value) (((value).type == kStdTrue) || ((value).type == kStdFalse)) -#define STDVALUE_AS_BOOL(value) ((value).type == kStdTrue) -#define STDBOOL(bool_value) ((struct std_value) {.type = (bool_value) ? kStdTrue : kStdFalse}) - -#define STDVALUE_IS_INT(value) (((value).type == kStdInt32) || ((value).type == kStdInt64)) -#define STDVALUE_AS_INT(value) ((value).type == kStdInt32 ? (int64_t) (value).int32_value : (value).int64_value) -#define STDINT32(_int32_value) ((struct std_value) {.type = kStdInt32, .int32_value = (_int32_value)}) -#define STDINT64(_int64_value) ((struct std_value) {.type = kStdInt64, .int64_value = (_int64_value)}) - -#define STDVALUE_IS_FLOAT(value) ((value).type == kStdFloat64) -#define STDVALUE_AS_FLOAT(value) ((value).float64_value) -#define STDFLOAT64(double_value) ((struct std_value) {.type = kStdFloat64, .float64_value = (double_value)}) - -#define STDVALUE_IS_NUM(value) (STDVALUE_IS_INT(value) || STDVALUE_IS_FLOAT(value)) -#define STDVALUE_AS_NUM(value) (STDVALUE_IS_INT(value) ? STDVALUE_AS_INT(value) : STDVALUE_AS_FLOAT(value)) - -#define STDVALUE_IS_STRING(value) ((value).type == kStdString) -#define STDVALUE_AS_STRING(value) ((value).string_value) -#define STDSTRING(str) ((struct std_value) {.type = kStdString, .string_value = (str)}) - -#define STDVALUE_IS_LIST(value) ((value).type == kStdList) -#define STDVALUE_IS_SIZE(value, _size) ((value).size == (_size)) -#define STDVALUE_IS_SIZED_LIST(value, _size) (STDVALUE_IS_LIST(value) && STDVALUE_IS_SIZE(value, _size)) - -#define STDVALUE_IS_INT_ARRAY(value) (((value).type == kStdInt32Array) || ((value).type == kStdInt64Array) || ((value).type == kStdUInt8Array)) -#define STDVALUE_IS_FLOAT_ARRAY(value) ((value).type == kStdFloat64Array) -#define STDVALUE_IS_NUM_ARRAY(value) (STDVALUE_IS_INT_ARRAY(value) || STDVALUE_IS_FLOAT_ARRAY(value)) - -#define STDVALUE_IS_MAP(value) ((value).type == kStdMap) -#define STDVALUE_IS_SIZED_MAP(value, _size) ((value).size == (_size)) - -#define STDMAP1(key1, val1) ((struct std_value) { \ - .type = kStdMap, \ - .size = 1, \ - .keys = (struct std_value[1]) { \ - (key1) \ - }, \ - .values = (struct std_value[1]) { \ - (val1) \ - } \ -}) - -#define STDMAP2(key1, val1, key2, val2) ((struct std_value) { \ - .type = kStdMap, \ - .size = 2, \ - .keys = (struct std_value[2]) { \ - (key1), (key2) \ - }, \ - .values = (struct std_value[2]) { \ - (val1), (val2) \ - } \ -}) -#define STDMAP3(key1, val1, key2, val2, key3, val3) ((struct std_value) { \ - .type = kStdMap, \ - .size = 3, \ - .keys = (struct std_value[3]) { \ - (key1), (key2), (key3) \ - }, \ - .values = (struct std_value[3]) { \ - (val1), (val2), (val3) \ - } \ -}) -#define STDMAP4(key1, val1, key2, val2, key3, val3, key4, val4) ((struct std_value) { \ - .type = kStdMap, \ - .size = 4, \ - .keys = (struct std_value[4]) { \ - (key1), (key2), (key3), (key4) \ - }, \ - .values = (struct std_value[4]) { \ - (val1), (val2), (val3), (val4) \ - } \ -}) -#define STDMAP5(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5) ((struct std_value) { \ - .type = kStdMap, \ - .size = 5, \ - .keys = (struct std_value[5]) { \ - (key1), (key2), (key3), (key4), (key5) \ - }, \ - .values = (struct std_value[5]) { \ - (val1), (val2), (val3), (val4), (val5) \ - } \ -}) -#define STDMAP6(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6) ((struct std_value) { \ - .type = kStdMap, \ - .size = 6, \ - .keys = (struct std_value[6]) { \ - (key1), (key2), (key3), (key4), (key5), (key6) \ - }, \ - .values = (struct std_value[6]) { \ - (val1), (val2), (val3), (val4), (val5), (val6) \ - } \ -}) -#define STDMAP7(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7) ((struct std_value) { \ - .type = kStdMap, \ - .size = 7, \ - .keys = (struct std_value[7]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7) \ - }, \ - .values = (struct std_value[7]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7) \ - } \ -}) -#define STDMAP8(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8) ((struct std_value) { \ - .type = kStdMap, \ - .size = 8, \ - .keys = (struct std_value[8]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8) \ - }, \ - .values = (struct std_value[8]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8) \ - } \ -}) -#define STDMAP9(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9) ((struct std_value) { \ - .type = kStdMap, \ - .size = 9, \ - .keys = (struct std_value[9]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9) \ - }, \ - .values = (struct std_value[9]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9) \ - } \ -}) -#define STDMAP10(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10) ((struct std_value) { \ - .type = kStdMap, \ - .size = 10, \ - .keys = (struct std_value[10]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10) \ - }, \ - .values = (struct std_value[10]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10) \ - } \ -}) -#define STDMAP11(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11) ((struct std_value) { \ - .type = kStdMap, \ - .size = 11, \ - .keys = (struct std_value[11]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11) \ - }, \ - .values = (struct std_value[11]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11) \ - } \ -}) -#define STDMAP12(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12) ((struct std_value) { \ - .type = kStdMap, \ - .size = 12, \ - .keys = (struct std_value[12]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12) \ - }, \ - .values = (struct std_value[12]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12) \ - } \ -}) -#define STDMAP13(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13) ((struct std_value) { \ - .type = kStdMap, \ - .size = 13, \ - .keys = (struct std_value[13]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13) \ - }, \ - .values = (struct std_value[13]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13) \ - } \ -}) -#define STDMAP14(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14) ((struct std_value) { \ - .type = kStdMap, \ - .size = 14, \ - .keys = (struct std_value[14]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14) \ - }, \ - .values = (struct std_value[14]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14) \ - } \ -}) -#define STDMAP15(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15) ((struct std_value) { \ - .type = kStdMap, \ - .size = 15, \ - .keys = (struct std_value[15]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15) \ - }, \ - .values = (struct std_value[15]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15) \ - } \ -}) -#define STDMAP16(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16) ((struct std_value) { \ - .type = kStdMap, \ - .size = 16, \ - .keys = (struct std_value[16]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16) \ - }, \ - .values = (struct std_value[16]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16) \ - } \ -}) -#define STDMAP17(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17) ((struct std_value) { \ - .type = kStdMap, \ - .size = 17, \ - .keys = (struct std_value[17]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17) \ - }, \ - .values = (struct std_value[17]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17) \ - } \ -}) -#define STDMAP18(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18) ((struct std_value) { \ - .type = kStdMap, \ - .size = 18, \ - .keys = (struct std_value[18]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18) \ - }, \ - .values = (struct std_value[18]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18) \ - } \ -}) -#define STDMAP19(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18, key19, val19) ((struct std_value) { \ - .type = kStdMap, \ - .size = 19, \ - .keys = (struct std_value[19]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19) \ - }, \ - .values = (struct std_value[19]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19) \ - } \ -}) -#define STDMAP20(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18, key19, val19, key20, val20) ((struct std_value) { \ - .type = kStdMap, \ - .size = 20, \ - .keys = (struct std_value[20]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19), (key20) \ - }, \ - .values = (struct std_value[20]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19), (val20) \ - } \ -}) - -#define STDLIST1(val1) ((struct std_value) { \ - .type = kStdList, \ - .size = 1, \ - .list = (struct std_value[1]) { \ - (val1) \ - } \ -}) - -#define STDLIST2(val1, val2) ((struct std_value) { \ - .type = kStdList, \ - .size = 2, \ - .list = (struct std_value[2]) { \ - (val1), (val2) \ - } \ -}) - -#define JSONVALUE_IS_NULL(value) ((value).type == kJsonNull) -#define JSONNULL ((struct json_value) {.type = kJsonNull}) - -#define JSONVALUE_IS_BOOL(value) (((value).type == kJsonTrue) || ((value).type == kJsonFalse)) -#define JSONVALUE_AS_BOOL(value) ((value).type == kJsonTrue) -#define JSONBOOL(bool_value) ((struct json_value) {.type = (bool_value) ? kJsonTrue : kJsonFalse}) - -#define JSONVALUE_IS_NUM(value) ((value).type == kJsonNumber) -#define JSONVALUE_AS_NUM(value) ((value).number_value) -#define JSONNUM(_number_value) ((struct json_value) {.type = kJsonNumber, .number_value = (_number_value)}) - -#define JSONVALUE_IS_STRING(value) ((value).type == kJsonString) -#define JSONVALUE_AS_STRING(value) ((value).string_value) -#define JSONSTRING(str) ((struct json_value) {.type = kJsonString, .string_value = str}) - -#define JSONVALUE_IS_ARRAY(value) ((value).type == kJsonArray) -#define JSONVALUE_IS_SIZE(value, _size) ((value).size == (_size)) -#define JSONVALUE_IS_SIZED_ARRAY(value, _size) (JSONVALUE_IS_ARRAY(value) && JSONVALUE_IS_SIZE(value, _size)) - -#define JSONVALUE_IS_OBJECT(value) ((value).type == kJsonObject) -#define JSONVALUE_IS_SIZED_OBJECT(value, _size) (JSONVALUE_IS_OBJECT(value) && JSONVALUE_IS_SIZE(value, _size)) - -#define JSONARRAY1(val1) ((struct json_value) { \ - .type = kJsonArray, \ - .size = 1, \ - .array = (struct json_value[1]) { \ - (val1) \ - } \ -}) -#define JSONARRAY2(val1, val2) ((struct json_value) { \ - .type = kJsonArray, \ - .size = 2, \ - .array = (struct json_value[2]) { \ - (val1), (val2) \ - } \ -}) -#define JSONARRAY3(val1, val2, val3) ((struct json_value) { \ - .type = kJsonArray, \ - .size = 3, \ - .array = (struct json_value[3]) { \ - (val1), (val2), (val3) \ - } \ -}) - -#define JSONOBJECT1(key1, val1) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 1, \ - .keys = (char *[1]) { \ - (key1) \ - }, \ - .values = (struct json_value[1]) { \ - (val1) \ - } \ -}) - -#define JSONOBJECT2(key1, val1, key2, val2) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 2, \ - .keys = (char *[2]) { \ - (key1), (key2) \ - }, \ - .values = (struct json_value[2]) { \ - (val1), (val2) \ - } \ -}) -#define JSONOBJECT3(key1, val1, key2, val2, key3, val3) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 3, \ - .keys = (char *[3]) { \ - (key1), (key2), (key3) \ - }, \ - .values = (struct json_value[3]) { \ - (val1), (val2), (val3) \ - } \ -}) -#define JSONOBJECT4(key1, val1, key2, val2, key3, val3, key4, val4) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 4, \ - .keys = (char *[4]) { \ - (key1), (key2), (key3), (key4) \ - }, \ - .values = (struct json_value[4]) { \ - (val1), (val2), (val3), (val4) \ - } \ -}) -#define JSONOBJECT5(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 5, \ - .keys = (char *[5]) { \ - (key1), (key2), (key3), (key4), (key5) \ - }, \ - .values = (struct json_value[5]) { \ - (val1), (val2), (val3), (val4), (val5) \ - } \ -}) -#define JSONOBJECT6(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 6, \ - .keys = (char *[6]) { \ - (key1), (key2), (key3), (key4), (key5), (key6) \ - }, \ - .values = (struct json_value[6]) { \ - (val1), (val2), (val3), (val4), (val5), (val6) \ - } \ -}) -#define JSONOBJECT7(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 7, \ - .keys = (char *[7]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7) \ - }, \ - .values = (struct json_value[7]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7) \ - } \ -}) -#define JSONOBJECT8(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 8, \ - .keys = (char *[8]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8) \ - }, \ - .values = (struct json_value[8]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8) \ - } \ -}) -#define JSONOBJECT9(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 9, \ - .keys = (char *[9]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9) \ - }, \ - .values = (struct json_value[9]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9) \ - } \ -}) -#define JSONOBJECT10(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 10, \ - .keys = (char *[10]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10) \ - }, \ - .values = (struct json_value[10]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10) \ - } \ -}) -#define JSONOBJECT11(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 11, \ - .keys = (char *[11]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11) \ - }, \ - .values = (struct json_value[11]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11) \ - } \ -}) -#define JSONOBJECT12(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 12, \ - .keys = (char *[12]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12) \ - }, \ - .values = (struct json_value[12]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12) \ - } \ -}) -#define JSONOBJECT13(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 13, \ - .keys = (char *[13]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13) \ - }, \ - .values = (struct json_value[13]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13) \ - } \ -}) -#define JSONOBJECT14(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 14, \ - .keys = (char *[14]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14) \ - }, \ - .values = (struct json_value[14]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14) \ - } \ -}) -#define JSONOBJECT15(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 15, \ - .keys = (char *[15]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15) \ - }, \ - .values = (struct json_value[15]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15) \ - } \ -}) -#define JSONOBJECT16(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 16, \ - .keys = (char *[16]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16) \ - }, \ - .values = (struct json_value[16]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16) \ - } \ -}) -#define JSONOBJECT17(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 17, \ - .keys = (char *[17]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17) \ - }, \ - .values = (struct json_value[17]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17) \ - } \ -}) -#define JSONOBJECT18(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 18, \ - .keys = (char *[18]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18) \ - }, \ - .values = (struct json_value[18]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18) \ - } \ -}) -#define JSONOBJECT19(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18, key19, val19) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 19, \ - .keys = (char *[19]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19) \ - }, \ - .values = (struct json_value[19]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19) \ - } \ -}) -#define JSONOBJECT20(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10, key11, val11, key12, val12, key13, val13, key14, val14, key15, val15, key16, val16, key17, val17, key18, val18, key19, val19, key20, val20) ((struct json_value) { \ - .type = kJsonObject, \ - .size = 20, \ - .keys = (char *[20]) { \ - (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19), (key20) \ - }, \ - .values = (struct json_value[20]) { \ - (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19), (val20) \ - } \ -}) - -/// codec of an abstract channel object -/// These tell this API how it should encode ChannelObjects -> platform messages -/// and how to decode platform messages -> ChannelObjects. -enum platch_codec { - kNotImplemented, - kStringCodec, - kBinaryCodec, - kJSONMessageCodec, - kStandardMessageCodec, - kStandardMethodCall, - kStandardMethodCallResponse, - kJSONMethodCall, - kJSONMethodCallResponse -}; - -/// Platform Channel Object. -/// Different properties are "valid" for different codecs: -/// kNotImplemented: -/// no values associated with this "codec". -/// this represents a platform message with no buffer and zero length. (so just an empty response) -/// kStringCodec: -/// - string_value is the raw byte data of a platform message, but with an additional null-byte at the end. -/// kBinaryCodec: -/// - binarydata is an array of the raw byte data of a platform message, -/// - binarydata_size is the size of that byte data in bytes. -/// kJSONMessageCodec: -/// - json_value -/// kStandardMessageCodec: -/// - std_value -/// kStandardMethodCall: -/// - "method" is the method you'd like to call, or the method that was called -/// by flutter. -/// - std_arg contains the argument to that method call. -/// kJSONMethodCall: -/// - "method" is the method you'd like to call, or the method that was called -/// by flutter. -/// - json_arg contains the argument to that method call. -/// kStandardMethodCallResponse or kJSONMethodCallResponse: -/// - "success" is whether the method call (called by flutter or by called by you) -/// if success is false, -/// - error_code must be set to point to a valid null-terminated string, -/// - error_msg is either pointing to a valid null-terminated string or is set to NULL, -/// - if the codec is kStandardMethodCallResponse, -/// std_error_details must be a valid std_value -/// ({.type = kStdNull} is possible, but not NULL). -/// - if the codec is kJSONMethodCallResponse, -/// json_error_details must be a valid json_value -/// ({.type = kJsonNull} is possible, but not NULL) -struct platch_obj { - enum platch_codec codec; - union { - char *string_value; - struct { - size_t binarydata_size; - uint8_t *binarydata; - }; - struct json_value json_value; - struct std_value std_value; - struct { - char *method; - union { - struct std_value std_arg; - struct json_value json_arg; - }; - }; - struct { - bool success; - union { - struct std_value std_result; - struct json_value json_result; - }; - char *error_code; - char *error_msg; - union { - struct std_value std_error_details; - struct json_value json_error_details; - }; - }; - }; -}; - -#define PLATCH_OBJ_NOT_IMPLEMENTED ((struct platch_obj) {.codec = kNotImplemented}) -#define PLATCH_OBJ_STRING(string) ((struct platch_obj) {.codec = kStringCodec, .string_value = (string)}) -#define PLATCH_OBJ_BINARY_DATA(data, data_size) ((struct platch_obj) {.codec = kBinaryCodec, .binarydata_size = (data_size), .binarydata = (data)}) -#define PLATCH_OBJ_JSON_MSG(__json_value) ((struct platch_obj) {.codec = kJSONMessageCodec, .json_value = (__json_value)}) -#define PLATCH_OBJ_STD_MSG(__std_value) ((struct platch_obj) {.codec = kStandardMessageCodec, .std_value = (__std_value)}) -#define PLATCH_OBJ_STD_CALL(method_name, arg) ((struct platch_obj) {.codec = kStandardMethodCall, .method = (char*) (method_name), .std_arg = (arg)}) -#define PLATCH_OBJ_JSON_CALL(method_name, arg) ((struct platch_obj) {.codec = kJSONMethodCall, .method = (char*) (method_name), .json_arg = (arg)}) -#define PLATCH_OBJ_STD_CALL_SUCCESS_RESPONSE(result) ((struct platch_obj) {.codec = kStandardMethodCallResponse, .success = true, .std_result = (result)}) -#define PLATCH_OBJ_STD_CALL_ERROR_RESPONSE(code, msg, details) ((struct platch_obj) {.codec = kStandardMethodCallResponse, .success = false, .error_code = (char*) (code), .error_msg = (char*) (msg), .std_error_details = (details)}) -#define PLATCH_OBJ_JSON_CALL_SUCCESS_RESPONSE(result) ((struct platch_obj) {.codec = kJSONMethodCallResponse, .success = true, .json_result = (result)}) -#define PLATCH_OBJ_JSON_CALL_ERROR_RESPONSE(code, msg, details) ((struct platch_obj) {.codec = kStandardMethodCallResponse, .success = false, .error_code = (char*) (code), .error_msg = (char*) (msg), .json_error_details = (details)}) -#define PLATCH_OBJ_STD_SUCCESS_EVENT(value) PLATCH_OBJ_STD_CALL_SUCCESS_RESPONSE(value) -#define PLATCH_OBJ_STD_ERROR_EVENT(code, msg, details) PLATCH_OBJ_STD_CALL_ERROR_RESPONSE(code, msg, details) -#define PLATCH_OBJ_JSON_SUCCESS_EVENT(value) PLATCH_OBJ_JSON_CALL_SUCCESS_RESPONSE(value) -#define PLATCH_OBJ_JSON_ERROR_EVENT(code, msg, details) PLATCH_OBJ_JSON_CALL_ERROR_RESPONSE(code, msg, details) - -/// A Callback that is called when a response to a platform message you send to flutter -/// arrives. "object" is the platform message decoded using the "codec" you gave to PlatformChannel_send, -/// "userdata" is the userdata you gave to PlatformChannel_send. -typedef int (*platch_msg_resp_callback)(struct platch_obj *object, void *userdata); - - -/// decodes a platform message (represented by `buffer` and `size`) as the given codec, -/// and puts the result into object_out. -/// This method will (in some cases) dynamically allocate memory, -/// so you should always call PlatformChannel_free(object_out) when you don't need it anymore. -/// -/// Additionally, PlatformChannel_decode currently "borrows" from the buffer, so if the buffer -/// is freed by flutter, the contents of object_out will in many cases be bogus. -/// If you'd like object_out to be persistent and not depend on the lifetime of the buffer, -/// you'd have to manually deep-copy it. -int platch_decode(uint8_t *buffer, size_t size, enum platch_codec codec, struct platch_obj *object_out); - -/// Encodes a generic ChannelObject into a buffer (that is, too, allocated by PlatformChannel_encode) -/// A pointer to the buffer is put into buffer_out and the size of that buffer into size_out. -/// The lifetime of the buffer is independent of the ChannelObject, so contents of the ChannelObject -/// can be freed after the object was encoded. -int platch_encode(struct platch_obj *object, uint8_t **buffer_out, size_t *size_out); - -/// Encodes a generic ChannelObject (anything, string/binary codec or Standard/JSON Method Calls and responses) as a platform message -/// and sends it to flutter on channel `channel` -/// If you supply a response callback (i.e. on_response is != NULL): -/// when flutter responds to this message, it is automatically decoded using the codec given in `response_codec`. -/// Then, on_response is called with the decoded ChannelObject and the userdata as an argument. -/// It's possible that flutter won't respond to your platform message, like when sending events via an EventChannel. -/// userdata can be NULL. -int platch_send(char *channel, - struct platch_obj *object, - enum platch_codec response_codec, - platch_msg_resp_callback on_response, - void *userdata); - -/// Encodes a StandardMethodCodec method call as a platform message and sends it to flutter -/// on channel `channel`. This is just a wrapper around PlatformChannel_send -/// that builds the ChannelObject for you. -/// The response_codec is kStandardMethodCallResponse. userdata can be NULL. -int platch_call_std(char *channel, - char *method, - struct std_value *argument, - platch_msg_resp_callback on_response, - void *userdata); - -/// Encodes the arguments as a JSON method call and sends it to flutter -/// on channel [channel]. This is just a wrapper around platch_send -/// that builds the ChannelObject for you. -/// The response is automatically decoded as a JSON method call response and -/// supplied to [on_response] as an argument. userdata can be NULL. -int platch_call_json(char *channel, - char *method, - struct json_value *argument, - platch_msg_resp_callback on_response, - void *userdata); - -/// Responds to a platform message. You can (of course) only respond once to a platform message, -/// i.e. a FlutterPlatformMessageResponseHandle can only be used once. -/// The codec of `response` can be any of the available codecs. -int platch_respond(FlutterPlatformMessageResponseHandle *handle, - struct platch_obj *response); - -/// Tells flutter that the platform message that was sent to you was not handled. -/// (for example, there's no plugin that is using this channel, or there is a plugin -/// but it doesn't want to respond.) -/// You should always use this instead of not replying to a platform message, since not replying could cause a memory leak. -/// When flutter receives this response, it will throw a MissingPluginException. -/// For most channel used by the ServicesPlugin, this is not too bad since it -/// specifies many of the channels used as OptionalMethodChannels. (which will silently catch the MissingPluginException) -int platch_respond_not_implemented(FlutterPlatformMessageResponseHandle *handle); - -int platch_respond_success_std(FlutterPlatformMessageResponseHandle *handle, - struct std_value *return_value); - -int platch_respond_error_std(FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct std_value *error_details); - -int platch_respond_illegal_arg_std(FlutterPlatformMessageResponseHandle *handle, - char *error_msg); - -int platch_respond_native_error_std(FlutterPlatformMessageResponseHandle *handle, - int _errno); - - -int platch_respond_success_json(FlutterPlatformMessageResponseHandle *handle, - struct json_value *return_value); - -int platch_respond_error_json(FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct json_value *error_details); - -int platch_respond_illegal_arg_json(FlutterPlatformMessageResponseHandle *handle, - char *error_msg); - -int platch_respond_native_error_json(FlutterPlatformMessageResponseHandle *handle, - int _errno); - -int platch_respond_success_pigeon( - FlutterPlatformMessageResponseHandle *handle, - struct std_value *return_value -); - -int platch_respond_error_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct std_value *error_details -); - -int platch_respond_illegal_arg_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_msg -); - -int platch_respond_illegal_arg_ext_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_msg, - struct std_value *error_details -); - -int platch_respond_native_error_pigeon( - FlutterPlatformMessageResponseHandle *handle, - int _errno -); - -/// Sends a success event with value `event_value` to an event channel -/// that uses the standard method codec. -int platch_send_success_event_std(char *channel, - struct std_value *event_value); - -/// Sends an error event to an event channel that uses the standard method codec. -int platch_send_error_event_std(char *channel, - char *error_code, - char *error_msg, - struct std_value *error_details); -/// Sends a success event with value `event_value` to an event channel -/// that uses the JSON method codec. -int platch_send_success_event_json(char *channel, - struct json_value *event_value); - -/// Sends an error event to an event channel that uses the JSON method codec. -int platch_send_error_event_json(char *channel, - char *error_code, - char *error_msg, - struct json_value *error_details); - -/// frees a ChannelObject that was decoded using PlatformChannel_decode. -/// not freeing ChannelObjects may result in a memory leak. -int platch_free_obj(struct platch_obj *object); - -int platch_free_json_value(struct json_value *value, bool shallow); - -/// returns true if values a and b are equal. -/// for JS arrays, the order of the values is relevant -/// (so two arrays are only equal if the same values appear in exactly same order) -/// for objects, the order of the entries is irrelevant. -bool jsvalue_equals(struct json_value *a, struct json_value *b); - -/// given a JS object as an argument, it searches for an entry with key "key" -/// and returns the value associated with it. -/// if the key is not found, returns NULL. -struct json_value *jsobject_get(struct json_value *object, char *key); - -/// StdMsgCodecValue equivalent of jsvalue_equals. -/// again, for lists, the order of values is relevant -/// for maps, it's not. -bool stdvalue_equals(struct std_value *a, struct std_value *b); - -/// StdMsgCodecValue equivalent of jsobject_get, just that the key can be -/// any arbitrary StdMsgCodecValue (and must not be a string as for jsobject_get) -struct std_value *stdmap_get(struct std_value *map, struct std_value *key); - -struct std_value *stdmap_get_str(struct std_value *map, char *key); - -#endif \ No newline at end of file diff --git a/include/pluginregistry.h b/include/pluginregistry.h deleted file mode 100644 index 200e28d5..00000000 --- a/include/pluginregistry.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef FLUTTER_PI_REGISTRY_H_ -#define FLUTTER_PI_REGISTRY_H_ - -#include -#include -#include - -#include - -struct flutterpi; -struct plugin_registry; - -typedef enum plugin_init_result (*plugin_init_t)(struct flutterpi *flutterpi, void **userdata_out); - -typedef void (*plugin_deinit_t)(struct flutterpi *flutterpi, void *userdata); - -struct flutterpi_plugin_v2 { - const char *name; - plugin_init_t init; - plugin_deinit_t deinit; -}; - -#define STREQ(a, b) (strcmp((a), (b)) == 0) - -/// The return value of a plugin initializer function. -enum plugin_init_result { - kInitialized_PluginInitResult, ///< The plugin was successfully initialized. - kNotApplicable_PluginInitResult, ///< The plugin couldn't be initialized, because it's not compatible with the flutter-pi instance. - /// For example, the plugin requires OpenGL but flutter-pi is using software rendering. - /// This is not an error, and flutter-pi will continue initializing the other plugins. - kError_PluginInitResult ///< The plugin couldn't be initialized because an unexpected error ocurred. - /// Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. -}; - -struct _FlutterPlatformMessageResponseHandle; -typedef struct _FlutterPlatformMessageResponseHandle - FlutterPlatformMessageResponseHandle; - -/// A Callback that gets called when a platform message -/// arrives on a channel you registered it with. -/// channel is the method channel that received a platform message, -/// object is the object that is the result of automatically decoding -/// the platform message using the codec given to plugin_registry_set_receiver. -/// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec -/// passed to plugin_registry_set_receiver. -typedef int (*platch_obj_recv_callback)( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -); - -/** - * @brief Create a new plugin registry instance and add the hardcoded plugins, but don't initialize them yet. - */ -struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi); - -void plugin_registry_destroy(struct plugin_registry *registry); - -int plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin); - -int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry); - -/** - * @brief Initialize all not-yet initialized plugins. - */ -int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry); - -/** - * @brief Deinitialize all initialized plugins. - */ -void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry); - -/** - * @brief Called by flutter-pi when a platform message arrives. - */ -int plugin_registry_on_platform_message(FlutterPlatformMessage *message); - -/// Sets the callback that should be called when a platform message arrives on channel "channel", -/// and the codec used to automatically decode the platform message. -/// Call this method with NULL as the callback parameter to remove the current listener on that channel. -int plugin_registry_set_receiver( - const char *channel, - enum platch_codec codec, - platch_obj_recv_callback callback -); - -int plugin_registry_remove_receiver( - const char *channel -); - -void *plugin_registry_get_plugin_userdata( - struct plugin_registry *registry, - const char *plugin_name -); - -/** - * @brief Returns true @ref registry has a plugin with name @ref plugin_name. - */ -bool plugin_registry_is_plugin_present( - struct plugin_registry *registry, - const char *plugin_name -); - -int plugin_registry_deinit(void); - -int static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin); - -int static_plugin_registry_remove_plugin(const char *plugin_name); - -#define FLUTTERPI_PLUGIN(_name, _identifier_name, _init, _deinit) \ -__attribute__((constructor)) static void __reg_plugin_##_identifier_name() { \ - static struct flutterpi_plugin_v2 plugin = { \ - .name = (_name), \ - .init = (_init), \ - .deinit = (_deinit) \ - }; \ - int ok; \ - \ - ok = static_plugin_registry_add_plugin(&plugin); \ - if (ok != 0) { \ - fprintf(stderr, "Couldn't register plugin " _name " to plugin registry.\n"); \ - abort(); \ - } \ -} \ -\ -__attribute__((destructor)) static void __unreg_plugin_##_identifier_name() { \ - int ok; \ - ok = static_plugin_registry_remove_plugin(_name); \ - if (ok != 0) { \ - fprintf(stderr, "Couldn't remove plugin " _name " from plugin registry.\n"); \ - } \ -} - -#endif \ No newline at end of file diff --git a/include/plugins/omxplayer_video_player.h b/include/plugins/omxplayer_video_player.h deleted file mode 100644 index 95ad07a0..00000000 --- a/include/plugins/omxplayer_video_player.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef _OMXPLAYER_VIDEO_PLAYER_PLUGIN_H -#define _OMXPLAYER_VIDEO_PLAYER_PLUGIN_H - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define DBUS_OMXPLAYER_OBJECT "/org/mpris/MediaPlayer2" -#define DBUS_OMXPLAYER_PLAYER_FACE "org.mpris.MediaPlayer2.Player" -#define DBUS_OMXPLAYER_ROOT_FACE "org.mpris.MediaPlayer2" -#define DBUS_PROPERTY_FACE "org.freedesktop.DBus.Properties" -#define DBUS_PROPERTY_GET "Get" -#define DBUS_PROPRETY_SET "Set" - -struct omxplayer_mgr; - -struct omxplayer_video_player { - int64_t player_id; - char event_channel_name[256]; - char video_uri[256]; - - bool has_view; - int64_t view_id; - - struct omxplayer_mgr *mgr; -}; - -struct omxplayer_mgr { - pthread_t thread; - struct omxplayer_video_player *player; - struct concurrent_queue task_queue; -}; - -enum omxplayer_mgr_task_type { - kCreate, - kDispose, - kListen, - kUnlisten, - kSetLooping, - kSetVolume, - kPlay, - kPause, - kGetPosition, - kSetPosition, - kUpdateView -}; - -struct omxplayer_mgr_task { - enum omxplayer_mgr_task_type type; - - FlutterPlatformMessageResponseHandle *responsehandle; - - union { - struct { - int orientation; - union { - struct { - char *omxplayer_dbus_name; - bool omxplayer_online; - }; - struct { - bool visible; - int offset_x, offset_y; - int width, height; - int zpos; - }; - }; - }; - bool loop; - float volume; - int64_t position; - }; -}; - -enum data_source_type { - kDataSourceTypeAsset, - kDataSourceTypeNetwork, - kDataSourceTypeFile -}; - -#endif \ No newline at end of file diff --git a/include/plugins/raw_keyboard.h b/include/plugins/raw_keyboard.h deleted file mode 100644 index 576df8e2..00000000 --- a/include/plugins/raw_keyboard.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef _KEY_EVENT_H -#define _KEY_EVENT_H - -#include -#include - -#define KEY_EVENT_CHANNEL "flutter/keyevent" - -int rawkb_send_android_keyevent( - uint32_t flags, - uint32_t code_point, - unsigned int key_code, - uint32_t plain_code_point, - uint32_t scan_code, - uint32_t meta_state, - uint32_t source, - uint16_t vendor_id, - uint16_t product_id, - uint16_t device_id, - int repeat_count, - bool is_down, - char *character -); - -int rawkb_send_gtk_keyevent( - uint32_t unicode_scalar_values, - uint32_t key_code, - uint32_t scan_code, - uint32_t modifiers, - bool is_down -); - -#endif \ No newline at end of file diff --git a/include/plugins/services.h b/include/plugins/services.h deleted file mode 100644 index 0ff0ed2f..00000000 --- a/include/plugins/services.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _SERVICES_PLUGIN_H -#define _SERVICES_PLUGIN_H - -#include -#include - -#define ORIENTATION_FROM_STRING(str) \ - (strcmp(str, "DeviceOrientation.portraitUp") == 0 ? kPortraitUp : \ - strcmp(str, "DeviceOrientation.landscapeLeft") == 0 ? kLandscapeLeft :\ - strcmp(str, "DeviceOrientation.portraitDown") == 0 ? kPortraitDown :\ - strcmp(str, "DeviceOrientation.landscapeRight") == 0 ? kLandscapeRight : -1) - -#define FLUTTER_NAVIGATION_CHANNEL "flutter/navigation" -#define FLUTTER_ISOLATE_CHANNEL "flutter/isolate" -#define FLUTTER_PLATFORM_CHANNEL "flutter/platform" -#define FLUTTER_ACCESSIBILITY_CHANNEL "flutter/accessibility" -#define FLUTTER_PLATFORM_VIEWS_CHANNEL "flutter/platform_views" - -#endif \ No newline at end of file diff --git a/include/texture_registry.h b/include/texture_registry.h deleted file mode 100644 index cf578ae4..00000000 --- a/include/texture_registry.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef _TEXTURE_REGISTRY_H -#define _TEXTURE_REGISTRY_H - -#include -#include - -struct flutter_external_texture_interface { - FlutterEngineRegisterExternalTextureFnPtr register_external_texture; - FlutterEngineUnregisterExternalTextureFnPtr unregister_external_texture; - FlutterEngineMarkExternalTextureFrameAvailableFnPtr mark_external_texture_frame_available; - FlutterEngine engine; -}; - -struct texture_registry; -struct texture; - -struct gl_texture_frame { - GLenum target; - GLuint name; - GLuint format; - size_t width; - size_t height; -}; - -struct texture_frame; -struct texture_frame { - union { - struct gl_texture_frame gl; - }; - void (*destroy)(const struct texture_frame *frame, void *userdata); - void *userdata; -}; - -struct texture_registry *texture_registry_new(const struct flutter_external_texture_interface *texture_interface); - -void texture_registry_destroy(struct texture_registry *reg); - -bool texture_registry_gl_external_texture_frame_callback( - struct texture_registry *reg, - int64_t texture_id, - size_t width, size_t height, - FlutterOpenGLTexture *texture_out -); - -struct texture *texture_new(struct texture_registry *reg); - -int64_t texture_get_id(struct texture *texture); - -int texture_push_frame(struct texture *texture, const struct texture_frame *frame); - -void texture_destroy(struct texture *texture); - -#endif \ No newline at end of file diff --git a/include/user_input.h b/include/user_input.h deleted file mode 100644 index 49e16542..00000000 --- a/include/user_input.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef USER_INPUT_H_ -#define USER_INPUT_H_ - -#include -#include - -#define MAX_COLLECTED_FLUTTER_POINTER_EVENTS 64 - -#define FLUTTER_POINTER_EVENT(_phase, _timestamp, _x, _y, _device, _signal_kind, _scroll_delta_x, _scroll_delta_y, _device_kind, _buttons) \ - (FlutterPointerEvent) { \ - .struct_size = sizeof(FlutterPointerEvent), \ - .phase = (_phase), \ - .timestamp = (_timestamp), \ - .x = (_x), .y = (_y), \ - .device = (_device), \ - .signal_kind = (_signal_kind), \ - .scroll_delta_x = (_scroll_delta_x), \ - .scroll_delta_y = (_scroll_delta_y), \ - .device_kind = (_device_kind), \ - .buttons = (_buttons) \ - } - -#define FLUTTER_POINTER_TOUCH_ADD_EVENT(_timestamp, _x, _y, _device_id) \ - FLUTTER_POINTER_EVENT(kAdd, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindTouch, 0) - -#define FLUTTER_POINTER_TOUCH_REMOVE_EVENT(_timestamp, _x, _y, _device_id) \ - FLUTTER_POINTER_EVENT(kRemove, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindTouch, 0) - -#define FLUTTER_POINTER_TOUCH_MOVE_EVENT(_timestamp, _x, _y, _device_id) \ - FLUTTER_POINTER_EVENT(kMove, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindTouch, 0) - -#define FLUTTER_POINTER_TOUCH_DOWN_EVENT(_timestamp, _x, _y, _device_id) \ - FLUTTER_POINTER_EVENT(kDown, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindTouch, 0) - -#define FLUTTER_POINTER_TOUCH_UP_EVENT(_timestamp, _x, _y, _device_id) \ - FLUTTER_POINTER_EVENT(kUp, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindTouch, 0) - -#define FLUTTER_POINTER_MOUSE_BUTTON_EVENT(_phase, _timestamp, _x, _y, _device_id, _buttons) \ - FLUTTER_POINTER_EVENT(_phase, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindMouse, _buttons) - -#define FLUTTER_POINTER_MOUSE_ADD_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ - FLUTTER_POINTER_EVENT(kAdd, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindMouse, _buttons) - -#define FLUTTER_POINTER_MOUSE_SCROLL_EVENT(_timestamp, _x, _y, _device_id, _scroll_x, _scroll_y, _buttons) \ - FLUTTER_POINTER_EVENT(((_buttons) != 0) ? kMove : kHover, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindScroll, _scroll_x, _scroll_y, kFlutterPointerDeviceKindMouse, _buttons) - -#define FLUTTER_POINTER_MOUSE_REMOVE_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ - FLUTTER_POINTER_EVENT(kRemove, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindMouse, _buttons) - -#define FLUTTER_POINTER_REMOVE_EVENT(_timestamp, _x, _y, _device, _buttons) \ - FLUTTER_POINTER_EVENT(kRemove, _timestamp, _x, _y, _device_id, kFlutterPointerSignalKindNone, 0.0, 0.0, kFlutterPointerDeviceKindMouse, _buttons) - -#define FLUTTER_POINTER_MOUSE_MOVE_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ - FLUTTER_POINTER_EVENT( \ - (_buttons) & kFlutterPointerButtonMousePrimary ? kMove : kHover, \ - _timestamp, \ - _x, _y, \ - _device_id, \ - kFlutterPointerSignalKindNone, \ - 0.0, 0.0, \ - kFlutterPointerDeviceKindMouse, \ - _buttons\ - ) - -typedef void (*flutter_pointer_event_callback_t)(void *userdata, const FlutterPointerEvent *events, size_t n_events); - -typedef void (*utf8_character_callback_t)(void *userdata, uint8_t *character); - -typedef void (*xkb_keysym_callback_t)(void *userdata, xkb_keysym_t keysym); - -typedef void (*gtk_keyevent_callback_t)( - void *userdata, - uint32_t unicode_scalar_values, - uint32_t key_code, - uint32_t scan_code, - uint32_t modifiers, - bool is_down -); - -typedef void (*set_cursor_enabled_callback_t)(void *userdata, bool enabled); - -typedef void (*move_cursor_callback_t)(void *userdata, unsigned int x, unsigned int y); - -struct user_input_interface { - flutter_pointer_event_callback_t on_flutter_pointer_event; - utf8_character_callback_t on_utf8_character; - xkb_keysym_callback_t on_xkb_keysym; - gtk_keyevent_callback_t on_gtk_keyevent; - set_cursor_enabled_callback_t on_set_cursor_enabled; - move_cursor_callback_t on_move_cursor; -}; - -struct user_input; - -/** - * @brief Create a new user input instance. Will try to load the default keyboard config from /etc/default/keyboard - * and create a udev-backed libinput instance. - */ -struct user_input *user_input_new( - const struct user_input_interface *interface, - void *userdata, - const FlutterTransformation *display_to_view_transform, - const FlutterTransformation *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -); - -/** - * @brief Destroy this user input instance and free all allocated memory. This will not remove any input devices - * added to flutter and won't invoke any callbacks in the user input interface at all. - */ -void user_input_destroy(struct user_input *input); - -/** - * @brief Set a 3x3 matrix and display width / height so user_input can transform any device coordinates into - * proper flutter view coordinates. (For example to account for a rotated display) - * Will also transform absolute & relative mouse movements. - * - * @param display_to_view_transform will be copied internally. - */ -void user_input_set_transform( - struct user_input *input, - const FlutterTransformation *display_to_view_transform, - const FlutterTransformation *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -); - -/** - * @brief Returns a filedescriptor used for input event notification. The returned - * filedescriptor should be listened to with EPOLLIN | EPOLLRDHUP | EPOLLPRI or equivalent. - * When the fd becomes ready, @ref user_input_on_fd_ready should be called not long after it - * became ready. (libinput somehow relies on that) - */ -int user_input_get_fd(struct user_input *input); - -/** - * @brief Should be called when the fd returned by @ref user_input_get_fd becomes ready. - * The user_input_interface callbacks will be called inside this function. - */ -int user_input_on_fd_ready(struct user_input *input); - -#endif \ No newline at end of file diff --git a/src/collection.c b/src/collection.c deleted file mode 100644 index 225fccb3..00000000 --- a/src/collection.c +++ /dev/null @@ -1,533 +0,0 @@ -#include - -int queue_init(struct queue *queue, size_t element_size, size_t max_queue_size) { - memset(queue, 0, sizeof(*queue)); - - queue->start_index = 0; - queue->length = 0; - queue->size = 2; - queue->elements = calloc(2, element_size); - - queue->max_queue_size = max_queue_size; - queue->element_size = element_size; - - if (queue->elements == NULL) { - queue->size = 0; - return ENOMEM; - } - - return 0; -} - -int queue_deinit(struct queue *queue) { - if (queue->elements != NULL) { - free(queue->elements); - } - - queue->start_index = 0; - queue->length = 0; - queue->size = 0; - queue->elements = NULL; - - queue->max_queue_size = 0; - queue->element_size = 0; - - return 0; -} - -int queue_enqueue( - struct queue *queue, - const void *p_element -) { - size_t new_size; - - if (queue->size == queue->length) { - // expand the queue or wait for an element to be dequeued. - - new_size = queue->size ? (queue->size << 1) : 1; - - if (new_size < queue->max_queue_size) { - void *new_elements = realloc(queue->elements, new_size * queue->element_size); - - if (new_elements == NULL) { - return ENOMEM; - } - - if (queue->size) { - memcpy(((char*)new_elements) + (queue->element_size*queue->size), new_elements, queue->element_size*queue->size); - } - - queue->elements = new_elements; - queue->size = new_size; - } else { - return ENOSPC; - } - } - - memcpy( - ((char*) queue->elements) + (queue->element_size*((queue->start_index + queue->length) & (queue->size - 1))), - p_element, - queue->element_size - ); - - queue->length++; - - return 0; -} - -int queue_dequeue( - struct queue *queue, - void *element_out -) { - if (queue->length == 0) { - return EAGAIN; - } - - memcpy( - element_out, - ((char*) queue->elements) + (queue->element_size*queue->start_index), - queue->element_size - ); - - queue->start_index = (queue->start_index + 1) & (queue->size - 1); - queue->length--; - - return 0; -} - -int queue_peek( - struct queue *queue, - void **pelement_out -) { - if (queue->length == 0) { - if (pelement_out != NULL) { - *pelement_out = NULL; - } - return EAGAIN; - } - - if (pelement_out != NULL) { - *pelement_out = ((char*) queue->elements) + (queue->element_size*queue->start_index); - } - - return 0; -} - - -int cqueue_init( - struct concurrent_queue *queue, - size_t element_size, - size_t max_queue_size -) { - int ok; - - memset(queue, 0, sizeof(*queue)); - - ok = queue_init(&queue->queue, element_size, max_queue_size); - if (ok != 0) { - return ok; - } - - pthread_mutex_init(&queue->mutex, NULL); - pthread_cond_init(&queue->is_enqueueable, NULL); - pthread_cond_init(&queue->is_dequeueable, NULL); - - return 0; -} - -int cqueue_deinit(struct concurrent_queue *queue) { - queue_deinit(&queue->queue); - pthread_mutex_destroy(&queue->mutex); - pthread_cond_destroy(&queue->is_enqueueable); - pthread_cond_destroy(&queue->is_dequeueable); - return 0; -} - -int cqueue_try_enqueue_locked( - struct concurrent_queue *queue, - const void *p_element -) { - return queue_enqueue(&queue->queue, p_element); -} - -int cqueue_enqueue_locked( - struct concurrent_queue *queue, - const void *p_element -) { - int ok; - - while (ok = queue_enqueue(&queue->queue, p_element), ok == ENOSPC) { - ok = pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); - if (ok != 0) { - return ok; - } - } - - return ok; -} - -int cqueue_try_enqueue( - struct concurrent_queue *queue, - const void *p_element -) { - int ok; - - cqueue_lock(queue); - - ok = cqueue_try_enqueue_locked(queue, p_element); - if (ok != 0) { - cqueue_unlock(queue); - return ok; - } - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -int cqueue_enqueue( - struct concurrent_queue *queue, - const void *p_element -) { - int ok; - - cqueue_lock(queue); - - ok = cqueue_enqueue_locked(queue, p_element); - if (ok != 0) { - cqueue_unlock(queue); - return ok; - } - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -int cqueue_try_dequeue_locked( - struct concurrent_queue *queue, - void *element_out -) { - int ok; - - ok = queue_dequeue(&queue->queue, element_out); - if (ok == 0) { - pthread_cond_signal(&queue->is_enqueueable); - } - - return 0; -} - -int cqueue_dequeue_locked( - struct concurrent_queue *queue, - void *element_out -) { - int ok; - - while (ok = queue_dequeue(&queue->queue, element_out), ok == EAGAIN) { - pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); - } - - if (ok == 0) { - pthread_cond_signal(&queue->is_enqueueable); - } - - return ok; -} - -int cqueue_try_dequeue( - struct concurrent_queue *queue, - void *element_out -) { - int ok; - - cqueue_lock(queue); - - ok = cqueue_try_dequeue_locked(queue, element_out); - - cqueue_unlock(queue); - - return ok; -} - -int cqueue_dequeue( - struct concurrent_queue *queue, - void *element_out -) { - int ok; - - cqueue_lock(queue); - - ok = cqueue_dequeue_locked(queue, element_out); - - cqueue_unlock(queue); - - return ok; -} - -int cqueue_peek_locked( - struct concurrent_queue *queue, - void **pelement_out -) { - return queue_peek(&queue->queue, pelement_out); -} - - -int pset_init( - struct pointer_set *set, - size_t max_size -) { - void **storage = (void**) calloc(2, sizeof(void*)); - if (storage == NULL) { - return ENOMEM; - } - - memset(set, 0, sizeof(*set)); - - set->count_pointers = 0; - set->size = 2; - set->pointers = storage; - set->max_size = max_size; - set->is_static = false; - - return 0; -} - -int pset_init_static( - struct pointer_set *set, - void **storage, - size_t size -) { - if (storage == NULL) { - return EINVAL; - } - - memset(set, 0, sizeof *set); - - set->count_pointers = 0; - set->size = size; - set->pointers = storage; - set->max_size = size; - set->is_static = true; - - return 0; -} - -void pset_deinit( - struct pointer_set *set -) { - if ((set->is_static == false) && (set->pointers != NULL)) { - free(set->pointers); - } - - set->count_pointers = 0; - set->size = 0; - set->pointers = NULL; - set->max_size = 0; - set->is_static = false; -} - -int pset_put( - struct pointer_set *set, - void *pointer -) { - size_t new_size; - int index; - - index = -1; - for (int i = 0; i < set->size; i++) { - if (set->pointers[i] == NULL) { - index = i; - } else if (pointer == set->pointers[i]) { - return 0; - } - } - - if (index != -1) { - set->pointers[index] = pointer; - set->count_pointers++; - return 0; - } - - if (set->is_static) { - return ENOSPC; - } else { - new_size = set->size ? set->size << 1 : 1; - - if (new_size < set->max_size) { - void **new_pointers = (void**) realloc(set->pointers, new_size * sizeof(void*)); - if (new_pointers == NULL) { - return ENOMEM; - } - - memset(new_pointers + set->size, 0, (new_size - set->size) * sizeof(void*)); - - new_pointers[set->size] = pointer; - set->count_pointers++; - - set->pointers = new_pointers; - set->size = new_size; - } else { - return ENOSPC; - } - } - - return 0; -} - -bool pset_contains( - const struct pointer_set *set, - const void *pointer -) { - for (int i = 0; i < set->size; i++) { - if ((set->pointers[i] != NULL) && (set->pointers[i] == pointer)) { - return true; - } - } - - return false; -} - -int pset_remove( - struct pointer_set *set, - const void *pointer -) { - for (int i = 0; i < set->size; i++) { - if ((set->pointers[i] != NULL) && (set->pointers[i] == pointer)) { - set->pointers[i] = NULL; - set->count_pointers--; - - return 0; - } - } - - return EINVAL; -} - -int pset_copy( - const struct pointer_set *src, - struct pointer_set *dest -) { - if (dest->size < src->count_pointers) { - if (dest->max_size < src->count_pointers) { - return ENOSPC; - } else { - void *new_pointers = realloc(dest->pointers, src->count_pointers); - if (new_pointers == NULL) { - return ENOMEM; - } - - dest->pointers = new_pointers; - dest->size = src->count_pointers; - } - } - - if (dest->size >= src->size) { - memcpy(dest->pointers, src->pointers, src->size * sizeof(void*)); - - if (dest->size > src->size) { - memset(dest->pointers + src->size, 0, (dest->size - src->size) * sizeof(void*)); - } - } else { - for (int i = 0, j = 0; i < src->size; i++) { - if (src->pointers[i] != NULL) { - dest->pointers[j] = src->pointers[i]; - j++; - } - } - } - - dest->count_pointers = src->count_pointers; - - return 0; -} - -void pset_intersect( - struct pointer_set *src_dest, - const struct pointer_set *b -) { - for (int i = 0, j = 0; (i < src_dest->size) && (j < src_dest->count_pointers); i++) { - if (src_dest->pointers[i] != NULL) { - if (pset_contains(b, src_dest->pointers[i]) == false) { - src_dest->pointers[i] = NULL; - src_dest->count_pointers--; - } else { - j++; - } - } - } -} - -int pset_union( - struct pointer_set *src_dest, - const struct pointer_set *b -) { - int ok; - - for (int i = 0, j = 0; (i < b->size) && (j < b->size); i++) { - if (b->pointers[i] != NULL) { - ok = pset_put(src_dest, b->pointers[i]); - if (ok != 0) { - return ok; - } - - j++; - } - } - - return 0; -} - -void *__pset_next_pointer( - struct pointer_set *set, - const void *pointer -) { - int i = -1; - - if (pointer != NULL) { - for (i = 0; i < set->size; i++) { - if (set->pointers[i] == pointer) { - break; - } - } - - if (i == set->size) return NULL; - } - - for (i = i+1; i < set->size; i++) { - if (set->pointers[i]) { - return set->pointers[i]; - } - } - - return NULL; -} - - -int cpset_init( - struct concurrent_pointer_set *set, - size_t max_size -) { - int ok; - - ok = pset_init(&set->set, max_size); - if (ok != 0) { - return ok; - } - - ok = pthread_mutex_init(&set->mutex, NULL); - if (ok < 0) { - return errno; - } - - return 0; -} - -void cpset_deinit(struct concurrent_pointer_set *set) { - pthread_mutex_destroy(&set->mutex); - pset_deinit(&set->set); -} \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c deleted file mode 100644 index 2ec0585d..00000000 --- a/src/compositor.c +++ /dev/null @@ -1,1933 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#define EGL_EGLEXT_PROTOTYPES -#include -#include -#define GL_GLEXT_PROTOTYPES -#include -#include - -#include -#include -#include -#include - -FILE_DESCR("compositor") - -struct view_cb_data { - int64_t view_id; - platform_view_mount_cb mount; - platform_view_unmount_cb unmount; - platform_view_update_view_cb update_view; - platform_view_present_cb present; - void *userdata; - - bool was_present_last_frame; - int last_zpos; - FlutterSize last_size; - FlutterPoint last_offset; - int last_num_mutations; - FlutterPlatformViewMutation last_mutations[16]; -}; - -struct compositor compositor = { - .drmdev = NULL, - .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), - .has_applied_modeset = false, - .should_create_window_surface_backing_store = true, - .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), - .do_blocking_atomic_commits = true -}; - -static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { - struct view_cb_data *data; - - for_each_pointer_in_cpset(&compositor.cbs, data) { - if (data->view_id == view_id) { - return data; - } - } - - return NULL; -} - -static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { - struct view_cb_data *data; - - cpset_lock(&compositor.cbs); - data = get_cbs_for_view_id_locked(view_id); - cpset_unlock(&compositor.cbs); - - return data; -} - -/** - * @brief Destroy all the rendertargets in the stale rendertarget cache. - */ -static int destroy_stale_rendertargets(void) { - struct rendertarget *target; - - cpset_lock(&compositor.stale_rendertargets); - - for_each_pointer_in_cpset(&compositor.stale_rendertargets, target) { - target->destroy(target); - target = NULL; - } - - cpset_unlock(&compositor.stale_rendertargets); - - return 0; -} - -static void destroy_gbm_bo( - struct gbm_bo *bo, - void *userdata -) { - (void) bo; - - struct drm_fb *fb = userdata; - - if (fb && fb->fb_id) - drmModeRmFB(flutterpi.drm.drmdev->fd, fb->fb_id); - - free(fb); -} - -/** - * @brief Get a DRM FB id for this GBM BO, so we can display it. - */ -static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { - uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; - int ok = -1; - - // if the buffer object already has some userdata associated with it, - // it's the framebuffer we allocated. - struct drm_fb *fb = gbm_bo_get_user_data(bo); - if (fb) return fb->fb_id; - - // if there's no framebuffer for the bo, we need to create one. - fb = calloc(1, sizeof(struct drm_fb)); - fb->bo = bo; - - width = gbm_bo_get_width(bo); - height = gbm_bo_get_height(bo); - format = gbm_bo_get_format(bo); - - uint64_t modifiers[4] = {0}; - modifiers[0] = gbm_bo_get_modifier(bo); - const int num_planes = gbm_bo_get_plane_count(bo); - - for (int i = 0; i < num_planes; i++) { - strides[i] = gbm_bo_get_stride_for_plane(bo, i); - handles[i] = gbm_bo_get_handle(bo).u32; - offsets[i] = gbm_bo_get_offset(bo, i); - modifiers[i] = modifiers[0]; - } - - if (modifiers[0]) { - flags = DRM_MODE_FB_MODIFIERS; - } - - ok = drmModeAddFB2WithModifiers(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); - - if (ok) { - if (flags) - LOG_DEBUG("drm_fb_get_from_bo: modifiers failed!\n"); - - memcpy(handles, (uint32_t [4]){gbm_bo_get_handle(bo).u32,0,0,0}, 16); - memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); - memset(offsets, 0, 16); - - ok = drmModeAddFB2(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); - } - - if (ok) { - LOG_ERROR("drm_fb_get_from_bo: failed to create fb: %s\n", strerror(errno)); - free(fb); - return 0; - } - - gbm_bo_set_user_data(bo, fb, destroy_gbm_bo); - - return fb->fb_id; -} - - -/** - * @brief Create a GL renderbuffer that is backed by a DRM buffer-object and registered as a DRM framebuffer - */ -static int create_drm_rbo( - size_t width, - size_t height, - struct drm_rbo *out -) { - struct drm_rbo fbo; - EGLint egl_error; - GLenum gl_error; - int ok; - - eglGetError(); - glGetError(); - - fbo.egl_image = flutterpi.egl.createDRMImageMESA(flutterpi.egl.display, (const EGLint[]) { - EGL_WIDTH, width, - EGL_HEIGHT, height, - EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA, - EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA, - EGL_NONE - }); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("error creating DRM EGL Image for flutter backing store, eglCreateDRMImageMESA: %" PRId32 "\n", egl_error); - return EINVAL; - } - - flutterpi.egl.exportDRMImageMESA(flutterpi.egl.display, fbo.egl_image, NULL, (EGLint*) &fbo.gem_handle, (EGLint*) &fbo.gem_stride); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("error getting handle & stride for DRM EGL Image, eglExportDRMImageMESA: %d\n", egl_error); - return EINVAL; - } - - glGenRenderbuffers(1, &fbo.gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error generating renderbuffers for flutter backing store, glGenRenderbuffers: %u\n", gl_error); - return EINVAL; - } - - glBindRenderbuffer(GL_RENDERBUFFER, fbo.gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - flutterpi.gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, fbo.egl_image); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding DRM EGL Image to renderbuffer, glEGLImageTargetRenderbufferStorageOES: %u\n", gl_error); - return EINVAL; - } - - /* - glGenFramebuffers(1, &fbo.gl_fbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - return EINVAL; - } - - glBindFramebuffer(GL_FRAMEBUFFER, fbo.gl_fbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); - return EINVAL; - } - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fbo.gl_rbo_id); - if (gl_error = glGetError()) { - LOG_ERROR("error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - */ - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - // glBindFramebuffer(GL_FRAMEBUFFER, 0); - - ok = drmModeAddFB2( - flutterpi.drm.drmdev->fd, - width, - height, - DRM_FORMAT_ARGB8888, - (const uint32_t*) &(uint32_t[4]) { - fbo.gem_handle, - 0, - 0, - 0 - }, - (const uint32_t*) &(uint32_t[4]) { - fbo.gem_stride, 0, 0, 0 - }, - (const uint32_t*) &(uint32_t[4]) { - 0, 0, 0, 0 - }, - &fbo.drm_fb_id, - 0 - ); - if (ok == -1) { - LOG_ERROR("Could not make DRM fb from EGL Image, drmModeAddFB2: %s", strerror(errno)); - return errno; - } - - *out = fbo; - - return 0; -} - -/** - * @brief Set the color attachment of a GL FBO to this DRM RBO. - */ -static int attach_drm_rbo_to_fbo( - GLuint fbo_id, - struct drm_rbo *rbo -) { - GLenum gl_error; - - eglGetError(); - glGetError(); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); - return EINVAL; - } - - glBindRenderbuffer(GL_RENDERBUFFER, rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); - return EINVAL; - } - - return 0; -} - -/** - * @brief Destroy this GL renderbuffer, and the associated DRM buffer-object and DRM framebuffer - */ -static void destroy_drm_rbo( - struct drm_rbo *rbo -) { - EGLint egl_error; - GLenum gl_error; - int ok; - - eglGetError(); - glGetError(); - - glDeleteRenderbuffers(1, &rbo->gl_rbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); - } - - ok = drmModeRmFB(flutterpi.drm.drmdev->fd, rbo->drm_fb_id); - if (ok < 0) { - LOG_ERROR("error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); - } - - eglDestroyImage(flutterpi.egl.display, rbo->egl_image); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); - } -} - -static void rendertarget_gbm_destroy(struct rendertarget *target) { - free(target); -} - -static int rendertarget_gbm_present( - struct rendertarget *target, - struct drmdev_atomic_req *atomic_req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos -) { - struct rendertarget_gbm *gbm_target; - struct gbm_bo *next_front_bo; - uint32_t next_front_fb_id; - bool supported; - int ok; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - - gbm_target = &target->gbm; - - next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); - next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "FB_ID", next_front_fb_id); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_H", flutterpi.display.height); - - ok = drmdev_plane_supports_setting_rotation_value(atomic_req->drmdev, drm_plane_id, DRM_MODE_ROTATE_0, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(atomic_req->drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - - // TODO: move this to the page flip handler. - // We can only be sure the buffer can be released when the buffer swap - // ocurred. - if (gbm_target->current_front_bo != NULL) { - gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); - } - gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; - - return 0; -} - -static int rendertarget_gbm_present_legacy( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode -) { - struct rendertarget_gbm *gbm_target; - struct gbm_bo *next_front_bo; - uint32_t next_front_fb_id; - bool is_primary; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - (void)zpos; - - gbm_target = &target->gbm; - - is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; - - next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); - next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - - if (is_primary) { - if (set_mode) { - drmdev_legacy_set_mode_and_fb( - drmdev, - next_front_fb_id - ); - } else { - /*drmdev_legacy_primary_plane_pageflip( - drmdev, - next_front_fb_id, - NULL - );*/ - - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - next_front_fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - } else { - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - next_front_fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - - // TODO: move this to the page flip handler. - // We can only be sure the buffer can be released when the buffer swap - // ocurred. - if (gbm_target->current_front_bo != NULL) { - gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); - } - gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; - - return 0; -} - -/** - * @brief Create a type of rendertarget that is backed by a GBM Surface, used for rendering into the DRM primary plane. - * - * @param[out] out A pointer to the pointer of the created rendertarget. - * @param[in] compositor The compositor which this rendertarget should be associated with. - * - * @see rendertarget_gbm - */ -static int rendertarget_gbm_new( - struct rendertarget **out, - struct compositor *compositor -) { - struct rendertarget *target; - - target = calloc(1, sizeof *target); - if (target == NULL) { - *out = NULL; - return ENOMEM; - } - - *target = (struct rendertarget) { - .is_gbm = true, - .compositor = compositor, - .gbm = { - .gbm_surface = flutterpi.gbm.surface, - .current_front_bo = NULL - }, - .gl_fbo_id = 0, - .destroy = rendertarget_gbm_destroy, - .present = rendertarget_gbm_present, - .present_legacy = rendertarget_gbm_present_legacy - }; - - *out = target; - - return 0; -} - -static void rendertarget_nogbm_destroy(struct rendertarget *target) { - glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); - destroy_drm_rbo(target->nogbm.rbos + 1); - destroy_drm_rbo(target->nogbm.rbos + 0); - free(target); -} - -static int rendertarget_nogbm_present( - struct rendertarget *target, - struct drmdev_atomic_req *req, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos -) { - struct rendertarget_nogbm *nogbm_target; - bool supported; - int ok; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - - nogbm_target = &target->nogbm; - - nogbm_target->current_front_rbo ^= 1; - ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); - if (ok != 0) return ok; - - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "FB_ID", nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_H", flutterpi.display.height); - - ok = drmdev_plane_supports_setting_rotation_value(req->drmdev, drm_plane_id, DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_atomic_req_put_plane_property(req, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - - return 0; -} - -static int rendertarget_nogbm_present_legacy( - struct rendertarget *target, - struct drmdev *drmdev, - uint32_t drm_plane_id, - int offset_x, - int offset_y, - int width, - int height, - int zpos, - bool set_mode -) { - struct rendertarget_nogbm *nogbm_target; - uint32_t fb_id; - bool supported, is_primary; - int ok; - - (void)offset_x; - (void)offset_y; - (void)width; - (void)height; - - nogbm_target = &target->nogbm; - - is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; - - nogbm_target->current_front_rbo ^= 1; - ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); - if (ok != 0) return ok; - - fb_id = nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id; - - if (is_primary) { - if (set_mode) { - drmdev_legacy_set_mode_and_fb( - drmdev, - fb_id - ); - } else { - drmdev_legacy_primary_plane_pageflip( - drmdev, - fb_id, - NULL - ); - } - } else { - drmdev_legacy_overlay_plane_pageflip( - drmdev, - drm_plane_id, - fb_id, - 0, - 0, - flutterpi.display.width, - flutterpi.display.height, - 0, - 0, - ((uint16_t) flutterpi.display.width) << 16, - ((uint16_t) flutterpi.display.height) << 16 - ); - } - - ok = drmdev_plane_supports_setting_rotation_value(drmdev, drm_plane_id, DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - LOG_ERROR( - "GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - - return 0; -} - -/** - * @brief Create a type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. - * - * @param[out] out A pointer to the pointer of the created rendertarget. - * @param[in] compositor The compositor which this rendertarget should be associated with. - * - * @see rendertarget_nogbm - */ -static int rendertarget_nogbm_new( - struct rendertarget **out, - struct compositor *compositor -) { - struct rendertarget *target; - GLenum gl_error; - int ok; - - target = calloc(1, sizeof *target); - if (target == NULL) { - return ENOMEM; - } - - target->is_gbm = false; - target->compositor = compositor; - target->destroy = rendertarget_nogbm_destroy; - target->present = rendertarget_nogbm_present; - target->present_legacy = rendertarget_nogbm_present_legacy; - - eglGetError(); - glGetError(); - - glGenFramebuffers(1, &target->nogbm.gl_fbo_id); - if ((gl_error = glGetError())) { - LOG_ERROR("error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - ok = EINVAL; - goto fail_free_target; - } - - ok = create_drm_rbo( - flutterpi.display.width, - flutterpi.display.height, - target->nogbm.rbos + 0 - ); - if (ok != 0) { - goto fail_delete_fb; - } - - ok = create_drm_rbo( - flutterpi.display.width, - flutterpi.display.height, - target->nogbm.rbos + 1 - ); - if (ok != 0) { - goto fail_destroy_drm_rbo_0; - } - - ok = attach_drm_rbo_to_fbo(target->nogbm.gl_fbo_id, target->nogbm.rbos + target->nogbm.current_front_rbo); - if (ok != 0) { - goto fail_destroy_drm_rbo_1; - } - - target->gl_fbo_id = target->nogbm.gl_fbo_id; - - *out = target; - return 0; - - - fail_destroy_drm_rbo_1: - destroy_drm_rbo(target->nogbm.rbos + 1); - - fail_destroy_drm_rbo_0: - destroy_drm_rbo(target->nogbm.rbos + 0); - - fail_delete_fb: - glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); - - fail_free_target: - free(target); - *out = NULL; - return ok; -} - -/** - * @brief Called by flutter when the OpenGL FBO of a backing store should be destroyed. - * Called on an internal engine-managed thread. This is actually called after the engine - * calls @ref on_collect_backing_store. - * - * @param[in] userdata The pointer to the struct flutterpi_backing_store that should be destroyed. - */ -static void on_destroy_backing_store_gl_fb(void *userdata) { - struct flutterpi_backing_store *store; - struct compositor *compositor; - - store = userdata; - compositor = store->target->compositor; - - cpset_put(&compositor->stale_rendertargets, store->target); - - if (store->should_free_on_next_destroy) { - free(store); - } else { - store->should_free_on_next_destroy = true; - } -} - -/** - * @brief A callback invoked by the engine to release the backing store. The embedder may - * collect any resources associated with the backing store. Invoked on an internal - * engine-managed thread. This is actually called before the engine calls @ref on_destroy_backing_store_gl_fb. - * - * @param[in] backing_store The backing store to be collected. - * @param[in] userdata A pointer to the flutterpi compositor. - */ -static bool on_collect_backing_store( - const FlutterBackingStore *backing_store, - void *userdata -) { - struct flutterpi_backing_store *store; - struct compositor *compositor; - - (void) userdata; - - store = backing_store->user_data; - compositor = store->target->compositor; - - cpset_put(&compositor->stale_rendertargets, store->target); - - if (store->should_free_on_next_destroy) { - free(store); - } else { - store->should_free_on_next_destroy = true; - } - - return true; -} - -/** - * @brief A callback invoked by the engine to obtain a FlutterBackingStore for a specific FlutterLayer. - * Called on an internal engine-managed thread. - * - * @param[in] config The dimensions of the backing store to be created, post transform. - * @param[out] backing_store_out The created backing store. - * @param[in] userdata A pointer to the flutterpi compositor. - */ -static bool on_create_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, - void *userdata -) { - struct flutterpi_backing_store *store; - struct rendertarget *target; - struct compositor *compositor; - int ok; - - (void) config; - compositor = userdata; - - store = calloc(1, sizeof *store); - if (store == NULL) { - return false; - } - - // first, try to find a stale GBM rendertarget. - cpset_lock(&compositor->stale_rendertargets); - for_each_pointer_in_cpset(&compositor->stale_rendertargets, target) break; - if (target != NULL) { - cpset_remove_locked(&compositor->stale_rendertargets, target); - } - cpset_unlock(&compositor->stale_rendertargets); - - // if we didn't find one, check if we should create one. If not, - // try to find a stale No-GBM rendertarget. If there is none, - // create one. - if (target == NULL) { - if (compositor->should_create_window_surface_backing_store) { - // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY - // plane. That backing store isn't really a backing store at all, it's - // FBO id is 0, so it's actually rendering to the window surface. - - ok = rendertarget_gbm_new( - &target, - compositor - ); - - if (ok != 0) { - free(store); - return false; - } - - compositor->should_create_window_surface_backing_store = false; - } else { - ok = rendertarget_nogbm_new( - &target, - compositor - ); - - if (ok != 0) { - free(store); - return false; - } - } - } - - store->target = target; - store->flutter_backing_store = (FlutterBackingStore) { - .struct_size = backing_store_out->struct_size, - .type = kFlutterBackingStoreTypeOpenGL, - .open_gl = { - .type = kFlutterOpenGLTargetTypeFramebuffer, - .framebuffer = { - .target = GL_BGRA8_EXT, - .name = target->gl_fbo_id, - .destruction_callback = on_destroy_backing_store_gl_fb, - .user_data = store - } - }, - .user_data = store - }; - - memcpy(backing_store_out, &store->flutter_backing_store, sizeof(FlutterBackingStore)); - - return true; -} - -struct simulated_page_flip_event_data { - unsigned int sec; - unsigned int usec; -}; - -extern void on_pageflip_event( - int fd, - unsigned int frame, - unsigned int sec, - unsigned int usec, - void *userdata -); - -static int execute_simulate_page_flip_event(void *userdata) { - struct simulated_page_flip_event_data *data; - - data = userdata; - - // disabled because vsync is broken - // on_pageflip_event(flutterpi.drm.drmdev->fd, 0, data->sec, data->usec, NULL); - - free(data); - - return 0; -} - -static void fill_platform_view_params( - struct platform_view_params *params_out, - const FlutterPoint *offset, - const FlutterSize *size, - const FlutterPlatformViewMutation **mutations, - size_t n_mutations, - const FlutterTransformation *display_to_view_transform, - const FlutterTransformation *view_to_display_transform, - double device_pixel_ratio -) { - (void) view_to_display_transform; - - /** - * inversion for - * ``` - * const auto transformed_layer_bounds = - * root_surface_transformation_.mapRect(layer_bounds); - * ``` - */ - - struct quad quad = apply_transform_to_aa_rect( - *display_to_view_transform, - (struct aa_rect) { - .offset.x = offset->x, - .offset.y = offset->y, - .size.x = size->width, - .size.y = size->height - } - ); - - struct aa_rect rect = get_aa_bounding_rect(quad); - - /** - * inversion for - * ``` - * const auto layer_bounds = - * SkRect::MakeXYWH(params.finalBoundingRect().x(), - * params.finalBoundingRect().y(), - * params.sizePoints().width() * device_pixel_ratio_, - * params.sizePoints().height() * device_pixel_ratio_ - * ); - * ``` - */ - - rect.size.x /= device_pixel_ratio; - rect.size.y /= device_pixel_ratio; - - // okay, now we have the params.finalBoundingRect().x() in aa_back_transformed.x and - // params.finalBoundingRect().y() in aa_back_transformed.y. - // those are flutter view coordinates, so we still need to transform them to display coordinates. - - // However, there are also calculated as a side-product of calculating the size of the quadrangle. - // So we'll avoid calculating them for now. Calculation of the size may fail when the offset - // given to `SceneBuilder.addPlatformView` (https://api.flutter.dev/flutter/dart-ui/SceneBuilder/addPlatformView.html) - // is not zero. (Don't really know what to do in that case) - - rect.offset.x = 0; - rect.offset.y = 0; - quad = get_quad(rect); - - double rotation = 0, opacity = 1; - for (int i = n_mutations - 1; i >= 0; i--) { - if (mutations[i]->type == kFlutterPlatformViewMutationTypeTransformation) { - apply_transform_to_quad(mutations[i]->transformation, &quad); - - double rotz = atan2(mutations[i]->transformation.skewX, mutations[i]->transformation.scaleX) * 180.0 / M_PI; - if (rotz < 0) { - rotz += 360; - } - - rotation += rotz; - } else if (mutations[i]->type == kFlutterPlatformViewMutationTypeOpacity) { - opacity *= mutations[i]->opacity; - } - } - - rotation = fmod(rotation, 360.0); - - params_out->rect = quad; - params_out->opacity = 0; - params_out->rotation = rotation; - params_out->clip_rects = NULL; - params_out->n_clip_rects = 0; -} - -/// PRESENT FUNCS -static bool on_present_layers( - const FlutterLayer **layers, - size_t layers_count, - void *userdata -) { - struct drmdev_atomic_req *req; - struct view_cb_data *cb_data; - struct pointer_set planes; - struct compositor *compositor; - struct drm_plane *plane; - struct drmdev *drmdev; - uint32_t req_flags; - void *planes_storage[32] = {0}; - bool legacy_rendertarget_set_mode = false; - bool schedule_fake_page_flip_event; - bool use_atomic_modesetting; - int ok; - - // TODO: proper error handling - - compositor = userdata; - drmdev = compositor->drmdev; - schedule_fake_page_flip_event = compositor->do_blocking_atomic_commits; - use_atomic_modesetting = drmdev->supports_atomic_modesetting; - -#ifdef DUMP_ENGINE_LAYERS - LOG_DEBUG("layers:\n"); - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - LOG_DEBUG(" backing store (offset: %f, %f. size: %f, %f)\n", layers[i]->offset.x, layers[i]->offset.y, layers[i]->size.width, layers[i]->size.height); - } else { - DEBUG_ASSERT(layers[i]->type == kFlutterLayerContentTypePlatformView); - - LOG_DEBUG(" platform view (id: %"PRId64", offset: %f, %f, size: %f, %f) mutations:\n", layers[i]->platform_view->identifier, layers[i]->offset.x, layers[i]->offset.y, layers[i]->size.width, layers[i]->size.height); - for (size_t j = 0; j < layers[i]->platform_view->mutations_count; j++) { - const FlutterPlatformViewMutation *mut = layers[i]->platform_view->mutations[j]; - switch (mut->type) { - case kFlutterPlatformViewMutationTypeOpacity: - LOG_DEBUG(" opacity %f\n", mut->opacity); - break; - case kFlutterPlatformViewMutationTypeClipRect: - LOG_DEBUG(" clip rect (ltrb: %f, %f, %f, %f)\n", mut->clip_rect.left, mut->clip_rect.top, mut->clip_rect.right, mut->clip_rect.bottom); - break; - case kFlutterPlatformViewMutationTypeClipRoundedRect: - LOG_DEBUG( - " clip rounded rect (ltrb: %f, %f, %f, %f, corner radii ul, ur, br, bl: %f, %f, %f, %f)\n", - mut->clip_rounded_rect.rect.left, mut->clip_rounded_rect.rect.top, mut->clip_rounded_rect.rect.right, mut->clip_rounded_rect.rect.bottom, - mut->clip_rounded_rect.upper_left_corner_radius, - mut->clip_rounded_rect.upper_right_corner_radius, - mut->clip_rounded_rect.lower_right_corner_radius, - mut->clip_rounded_rect.lower_left_corner_radius - ); - break; - case kFlutterPlatformViewMutationTypeTransformation: - LOG_DEBUG( - " transform (matrix: %f %f %f; %f %f %f; %f %f %f)\n", - mut->transformation.scaleX, - mut->transformation.skewX, - mut->transformation.transX, - mut->transformation.skewY, - mut->transformation.scaleY, - mut->transformation.transY, - mut->transformation.pers0, - mut->transformation.pers1, - mut->transformation.pers2 - ); - break; - default: - DEBUG_ASSERT_MSG(0, "invalid platform view mutation type\n"); - break; - } - } - } - } -#endif - - req = NULL; - if (use_atomic_modesetting) { - ok = drmdev_new_atomic_req(compositor->drmdev, &req); - if (ok != 0) { - return false; - } - } else { - planes = PSET_INITIALIZER_STATIC(planes_storage, 32); - for_each_plane_in_drmdev(drmdev, plane) { - if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - ok = pset_put(&planes, plane); - if (ok != 0) { - return false; - } - } - } - } - - cpset_lock(&compositor->cbs); - - EGLDisplay stored_display = eglGetCurrentDisplay(); - EGLSurface stored_read_surface = eglGetCurrentSurface(EGL_READ); - EGLSurface stored_write_surface = eglGetCurrentSurface(EGL_DRAW); - EGLContext stored_context = eglGetCurrentContext(); - - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); - eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); - - req_flags = 0 /* DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK*/; - if (compositor->has_applied_modeset == false) { - if (use_atomic_modesetting) { - ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); - if (ok != 0) { - return false; - } - } else { - legacy_rendertarget_set_mode = true; - schedule_fake_page_flip_event = true; - } - - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_CURSOR) { - // make sure the cursor is in front of everything - int64_t max_zpos; - bool supported; - - ok = drmdev_plane_get_max_zpos_value(req->drmdev, plane->plane->plane_id, &max_zpos); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); - continue; - } - - ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, plane->plane->plane_id, max_zpos, &supported); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - - if (supported) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "zpos", max_zpos); - } else { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if (plane->type == DRM_PLANE_TYPE_CURSOR) { - // make sure the cursor is in front of everything - int64_t max_zpos; - bool supported; - - ok = drmdev_plane_get_max_zpos_value(drmdev, plane->plane->plane_id, &max_zpos); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); - continue; - } - - ok = drmdev_plane_supports_setting_zpos_value(drmdev, plane->plane->plane_id, max_zpos, &supported); - if (ok != 0) { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, plane->plane->plane_id, "zpos", max_zpos); - } else { - LOG_ERROR("Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; - } - } - } - } - - compositor->has_applied_modeset = true; - } - - // first, the state machine phase. - // go through the layers, update - // all platform views accordingly. - // unmount, update, mount. in that order - { - void *mounted_views_storage[layers_count]; - memset(mounted_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set mounted_views = PSET_INITIALIZER_STATIC(mounted_views_storage, layers_count); - - void *unmounted_views_storage[layers_count]; - memset(unmounted_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set unmounted_views = PSET_INITIALIZER_STATIC(unmounted_views_storage, layers_count); - - void *updated_views_storage[layers_count]; - memset(updated_views_storage, 0, layers_count * sizeof(void*)); - struct pointer_set updated_views = PSET_INITIALIZER_STATIC(updated_views_storage, layers_count); - - for_each_pointer_in_cpset(&compositor->cbs, cb_data) { - const FlutterLayer *layer = NULL; - bool is_present = false; - int zpos; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - is_present = true; - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - if (!is_present && cb_data->was_present_last_frame) { - pset_put(&unmounted_views, cb_data); - } else if (is_present && cb_data->was_present_last_frame) { - if (cb_data->update_view != NULL) { - bool did_update_view = false; - - did_update_view = did_update_view || (zpos != cb_data->last_zpos); - did_update_view = did_update_view || memcmp(&cb_data->last_size, &layer->size, sizeof(FlutterSize)); - did_update_view = did_update_view || memcmp(&cb_data->last_offset, &layer->offset, sizeof(FlutterPoint)); - did_update_view = did_update_view || (cb_data->last_num_mutations != layer->platform_view->mutations_count); - for (int i = 0; (i < layer->platform_view->mutations_count) && !did_update_view; i++) { - did_update_view = did_update_view || memcmp(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - - if (did_update_view) { - pset_put(&updated_views, cb_data); - } - } - } else if (is_present && !cb_data->was_present_last_frame) { - pset_put(&mounted_views, cb_data); - } - } - - for_each_pointer_in_pset(&unmounted_views, cb_data) { - if (cb_data->unmount != NULL) { - ok = cb_data->unmount( - cb_data->view_id, - req, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not unmount platform view. unmount: %s\n", strerror(ok)); - } - - cb_data->was_present_last_frame = false; - } - } - - for_each_pointer_in_pset(&updated_views, cb_data) { - const FlutterLayer *layer = NULL; - int zpos = 0; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - struct platform_view_params params; - fill_platform_view_params( - ¶ms, - &layer->offset, - &layer->size, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.pixel_ratio - ); - - ok = cb_data->update_view( - cb_data->view_id, - req, - ¶ms, - zpos, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not update platform view. update_view: %s\n", strerror(ok)); - } - - cb_data->last_zpos = zpos; - cb_data->last_size = layer->size; - cb_data->last_offset = layer->offset; - cb_data->last_num_mutations = layer->platform_view->mutations_count; - for (int i = 0; i < layer->platform_view->mutations_count; i++) { - memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - } - - for_each_pointer_in_pset(&mounted_views, cb_data) { - const FlutterLayer *layer = NULL; - int zpos = 0; - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - layer = layers[i]; - zpos = i; - break; - } - } - - DEBUG_ASSERT_NOT_NULL(layer); - - struct platform_view_params params; - fill_platform_view_params( - ¶ms, - &layer->offset, - &layer->size, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.pixel_ratio - ); - - if (cb_data->mount != NULL) { - ok = cb_data->mount( - layer->platform_view->identifier, - req, - ¶ms, - zpos, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not mount platform view. %s\n", strerror(ok)); - } - } - - cb_data->was_present_last_frame = true; - cb_data->last_zpos = zpos; - cb_data->last_size = layer->size; - cb_data->last_offset = layer->offset; - cb_data->last_num_mutations = layer->platform_view->mutations_count; - for (int i = 0; i < layer->platform_view->mutations_count; i++) { - memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - } - } - - int64_t min_zpos; - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - ok = drmdev_plane_get_min_zpos_value(req->drmdev, plane->plane->plane_id, &min_zpos); - if (ok != 0) { - min_zpos = 0; - } - break; - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - ok = drmdev_plane_get_min_zpos_value(drmdev, plane->plane->plane_id, &min_zpos); - if (ok != 0) { - min_zpos = 0; - } - break; - } - } - } - - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - // choose a plane which has an "intrinsic" zpos that matches - // the zpos we want the plane to have. - // (Since planes are buggy and we can't rely on the zpos we explicitly - // configure the plane to have to be actually applied to the hardware. - // In short, assigning a different value to the zpos property won't always - // take effect.) - if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { - ok = drmdev_atomic_req_reserve_plane(req, plane); - break; - } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - ok = drmdev_atomic_req_reserve_plane(req, plane); - break; - } - } - } else { - for_each_pointer_in_pset(&planes, plane) { - if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { - break; - } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - break; - } - } - if (plane != NULL) { - pset_remove(&planes, plane); - } - } - if (plane == NULL) { - LOG_ERROR("Could not find a free primary/overlay DRM plane for presenting the backing store. drmdev_atomic_req_reserve_plane: %s\n", strerror(ok)); - continue; - } - - struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; - struct rendertarget *target = store->target; - - if (use_atomic_modesetting) { - ok = target->present( - target, - req, - plane->plane->plane_id, - 0, - 0, - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, - i + min_zpos - ); - if (ok != 0) { - LOG_ERROR("Could not present backing store. rendertarget->present: %s\n", strerror(ok)); - } - } else { - ok = target->present_legacy( - target, - drmdev, - plane->plane->plane_id, - 0, - 0, - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, - i + min_zpos, - legacy_rendertarget_set_mode && (plane->type == DRM_PLANE_TYPE_PRIMARY) - ); - } - } else { - DEBUG_ASSERT(layers[i]->type == kFlutterLayerContentTypePlatformView); - cb_data = get_cbs_for_view_id_locked(layers[i]->platform_view->identifier); - - if ((cb_data != NULL) && (cb_data->present != NULL)) { - struct platform_view_params params; - fill_platform_view_params( - ¶ms, - &layers[i]->offset, - &layers[i]->size, - layers[i]->platform_view->mutations, - layers[i]->platform_view->mutations_count, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.pixel_ratio - ); - - ok = cb_data->present( - cb_data->view_id, - req, - ¶ms, - i + min_zpos, - cb_data->userdata - ); - if (ok != 0) { - LOG_ERROR("Could not present platform view. platform_view->present: %s\n", strerror(ok)); - } - } - } - } - - if (use_atomic_modesetting) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - if ((plane->type == DRM_PLANE_TYPE_PRIMARY) || (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB_ID", 0); - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "CRTC_ID", 0); - } - } - } - - eglMakeCurrent(stored_display, stored_read_surface, stored_write_surface, stored_context); - - if (use_atomic_modesetting) { - do_commit: - if (compositor->do_blocking_atomic_commits) { - req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); - } else { - req_flags |= DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; - } - - ok = drmdev_atomic_req_commit(req, req_flags, NULL); - if ((compositor->do_blocking_atomic_commits == false) && (ok == EBUSY)) { - LOG_ERROR( - "Non-blocking drmModeAtomicCommit failed with EBUSY.\n" - " Future drmModeAtomicCommits will be executed blockingly.\n" - " This may have have an impact on performance.\n" - ); - - compositor->do_blocking_atomic_commits = true; - schedule_fake_page_flip_event = true; - goto do_commit; - } else if (ok != 0) { - LOG_ERROR("Could not present frame. drmModeAtomicCommit: %s\n", strerror(ok)); - drmdev_destroy_atomic_req(req); - cpset_unlock(&compositor->cbs); - return false; - } - - drmdev_destroy_atomic_req(req); - } - - cpset_unlock(&compositor->cbs); - - if (schedule_fake_page_flip_event) { - uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); - - struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); - if (data == NULL) { - return false; - } - - data->sec = time / 1000000000llu; - data->usec = (time % 1000000000llu) / 1000; - - flutterpi_post_platform_task(execute_simulate_page_flip_event, data); - } - - return true; -} - -int compositor_on_page_flip( - uint32_t sec, - uint32_t usec -) { - (void) sec; - (void) usec; - return 0; -} - -/// PLATFORM VIEW CALLBACKS -int compositor_set_view_callbacks( - int64_t view_id, - platform_view_mount_cb mount, - platform_view_unmount_cb unmount, - platform_view_update_view_cb update_view, - platform_view_present_cb present, - void *userdata -) { - struct view_cb_data *entry; - - cpset_lock(&compositor.cbs); - - entry = get_cbs_for_view_id_locked(view_id); - - if (entry == NULL) { - entry = calloc(1, sizeof(*entry)); - if (!entry) { - cpset_unlock(&compositor.cbs); - return ENOMEM; - } - - cpset_put_locked(&compositor.cbs, entry); - } - - entry->view_id = view_id; - entry->mount = mount; - entry->unmount = unmount; - entry->update_view = update_view; - entry->present = present; - entry->userdata = userdata; - - return cpset_unlock(&compositor.cbs); -} - -int compositor_remove_view_callbacks(int64_t view_id) { - struct view_cb_data *entry; - - cpset_lock(&compositor.cbs); - - entry = get_cbs_for_view_id_locked(view_id); - if (entry == NULL) { - cpset_unlock(&compositor.cbs); - return EINVAL; - } - - cpset_remove_locked(&compositor.cbs, entry); - free(entry); - cpset_unlock(&compositor.cbs); - return 0; -} - -/// COMPOSITOR INITIALIZATION -int compositor_initialize(struct drmdev *drmdev) { - compositor.drmdev = drmdev; - return 0; -} - -static void destroy_cursor_buffer(void) { - struct drm_mode_destroy_dumb destroy_req; - - munmap(compositor.cursor.buffer, compositor.cursor.buffer_size); - - drmModeRmFB(compositor.drmdev->fd, compositor.cursor.drm_fb_id); - - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = compositor.cursor.gem_bo_handle; - - ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - - compositor.cursor.has_buffer = false; - compositor.cursor.buffer_depth = 0; - compositor.cursor.gem_bo_handle = 0; - compositor.cursor.buffer_pitch = 0; - compositor.cursor.buffer_width = 0; - compositor.cursor.buffer_height = 0; - compositor.cursor.buffer_size = 0; - compositor.cursor.drm_fb_id = 0; - compositor.cursor.buffer = NULL; -} - -static int create_cursor_buffer(int width, int height, int bpp) { - struct drm_mode_create_dumb create_req; - struct drm_mode_map_dumb map_req; - uint32_t drm_fb_id; - uint32_t *buffer; - uint64_t cap; - uint8_t depth; - int ok; - - ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not query GPU Driver support for dumb buffers. drmGetCap: %s\n", strerror(errno)); - goto fail_return_ok; - } - - if (cap == 0) { - LOG_ERROR("Kernel / GPU Driver does not support dumb DRM buffers. Mouse cursor will not be displayed.\n"); - ok = ENOTSUP; - goto fail_return_ok; - } - - ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_PREFERRED_DEPTH, &cap); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not query dumb buffer preferred depth capability. drmGetCap: %s\n", strerror(errno)); - goto fail_return_ok; - } - - depth = (uint8_t) cap; - - if (depth != 32) { - LOG_ERROR("Preferred framebuffer depth for hardware cursor is not supported by flutter-pi.\n"); - } - - memset(&create_req, 0, sizeof create_req); - create_req.width = width; - create_req.height = height; - create_req.bpp = bpp; - create_req.flags = 0; - - ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not create a dumb buffer for the hardware cursor. ioctl: %s\n", strerror(errno)); - goto fail_return_ok; - } - - ok = drmModeAddFB(compositor.drmdev->fd, create_req.width, create_req.height, 32, create_req.bpp, create_req.pitch, create_req.handle, &drm_fb_id); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not make a DRM FB out of the hardware cursor buffer. drmModeAddFB: %s\n", strerror(errno)); - goto fail_destroy_dumb_buffer; - } - - memset(&map_req, 0, sizeof map_req); - map_req.handle = create_req.handle; - - ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not prepare dumb buffer mmap for uploading the hardware cursor icon. ioctl: %s\n", strerror(errno)); - goto fail_rm_drm_fb; - } - - buffer = mmap(0, create_req.size, PROT_READ | PROT_WRITE, MAP_SHARED, compositor.drmdev->fd, map_req.offset); - if (buffer == MAP_FAILED) { - ok = errno; - LOG_ERROR("Could not mmap dumb buffer for uploading the hardware cursor icon. mmap: %s\n", strerror(errno)); - goto fail_rm_drm_fb; - } - - compositor.cursor.has_buffer = true; - compositor.cursor.buffer_depth = depth; - compositor.cursor.gem_bo_handle = create_req.handle; - compositor.cursor.buffer_pitch = create_req.pitch; - compositor.cursor.buffer_width = width; - compositor.cursor.buffer_height = height; - compositor.cursor.buffer_size = create_req.size; - compositor.cursor.drm_fb_id = drm_fb_id; - compositor.cursor.buffer = buffer; - - return 0; - - - fail_rm_drm_fb: - drmModeRmFB(compositor.drmdev->fd, drm_fb_id); - - fail_destroy_dumb_buffer: ; - struct drm_mode_destroy_dumb destroy_req; - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = create_req.handle; - ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - - fail_return_ok: - return ok; -} - -int compositor_apply_cursor_state( - bool is_enabled, - int rotation, - double device_pixel_ratio -) { - const struct cursor_icon *cursor; - int ok; - - if (is_enabled == true) { - // find the best fitting cursor icon. - { - double last_diff = INFINITY; - - cursor = NULL; - for (int i = 0; i < n_cursors; i++) { - double cursor_dpr = (cursors[i].width * 3 * 10.0) / (25.4 * 38); - double cursor_screen_dpr_diff = device_pixel_ratio - cursor_dpr; - if ((cursor_screen_dpr_diff >= 0) && (cursor_screen_dpr_diff < last_diff)) { - cursor = cursors + i; - } - } - } - - // destroy the old cursor buffer, if necessary - if (compositor.cursor.has_buffer && (compositor.cursor.buffer_width != cursor->width)) { - destroy_cursor_buffer(); - } - - // create a new cursor buffer, if necessary - if (compositor.cursor.has_buffer == false) { - create_cursor_buffer(cursor->width, cursor->width, 32); - } - - if ((compositor.cursor.is_enabled == false) || (compositor.cursor.current_rotation != rotation) || (compositor.cursor.current_cursor != cursor)) { - int rotated_hot_x, rotated_hot_y; - - if (rotation == 0) { - memcpy(compositor.cursor.buffer, cursor->data, compositor.cursor.buffer_size); - rotated_hot_x = cursor->hot_x; - rotated_hot_y = cursor->hot_y; - } else if ((rotation == 90) || (rotation == 180) || (rotation == 270)) { - for (int y = 0; y < cursor->width; y++) { - for (int x = 0; x < cursor->width; x++) { - int buffer_x, buffer_y; - if (rotation == 90) { - buffer_x = cursor->width - y - 1; - buffer_y = x; - } else if (rotation == 180) { - buffer_x = cursor->width - y - 1; - buffer_y = cursor->width - x - 1; - } else { - buffer_x = y; - buffer_y = cursor->width - x - 1; - } - - int buffer_offset = compositor.cursor.buffer_pitch * buffer_y + (compositor.cursor.buffer_depth / 8) * buffer_x; - int cursor_offset = cursor->width * y + x; - - compositor.cursor.buffer[buffer_offset / 4] = cursor->data[cursor_offset]; - } - } - - if (rotation == 90) { - rotated_hot_x = cursor->width - cursor->hot_y - 1; - rotated_hot_y = cursor->hot_x; - } else if (rotation == 180) { - rotated_hot_x = cursor->width - cursor->hot_x - 1; - rotated_hot_y = cursor->width - cursor->hot_y - 1; - } else { - DEBUG_ASSERT(rotation == 270); - rotated_hot_x = cursor->hot_y; - rotated_hot_y = cursor->width - cursor->hot_x - 1; - } - } else { - return EINVAL; - } - - ok = drmModeSetCursor2( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, - compositor.cursor.gem_bo_handle, - compositor.cursor.cursor_size, - compositor.cursor.cursor_size, - rotated_hot_x, - rotated_hot_y - ); - if (ok < 0) { - if (errno == ENXIO) { - LOG_ERROR("Could not configure cursor. Hardware cursor is not supported by device.\n"); - } else { - LOG_ERROR("Could not set the mouse cursor buffer. drmModeSetCursor: %s\n", strerror(errno)); - } - return errno; - } - - ok = drmModeMoveCursor( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, - compositor.cursor.x - compositor.cursor.hot_x, - compositor.cursor.y - compositor.cursor.hot_y - ); - if (ok < 0) { - LOG_ERROR("Could not move cursor. drmModeMoveCursor: %s\n", strerror(errno)); - return errno; - } - - compositor.cursor.current_rotation = rotation; - compositor.cursor.current_cursor = cursor; - compositor.cursor.cursor_size = cursor->width; - compositor.cursor.hot_x = rotated_hot_x; - compositor.cursor.hot_y = rotated_hot_y; - compositor.cursor.is_enabled = true; - } - - return 0; - } else if ((is_enabled == false) && (compositor.cursor.is_enabled == true)) { - drmModeSetCursor( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, - 0, 0, 0 - ); - - destroy_cursor_buffer(); - - compositor.cursor.cursor_size = 0; - compositor.cursor.current_cursor = NULL; - compositor.cursor.current_rotation = 0; - compositor.cursor.hot_x = 0; - compositor.cursor.hot_y = 0; - compositor.cursor.x = 0; - compositor.cursor.y = 0; - compositor.cursor.is_enabled = false; - - return 0; - } - - return 0; -} - -int compositor_set_cursor_pos(int x, int y) { - int ok; - - if (compositor.cursor.is_enabled == false) { - return 0; - } - - ok = drmModeMoveCursor(compositor.drmdev->fd, compositor.drmdev->selected_crtc->crtc->crtc_id, x - compositor.cursor.hot_x, y - compositor.cursor.hot_y); - if (ok < 0) { - LOG_ERROR("Could not move cursor. drmModeMoveCursor: %s", strerror(errno)); - return errno; - } - - compositor.cursor.x = x; - compositor.cursor.y = y; - - return 0; -} - -const FlutterCompositor flutter_compositor = { - .struct_size = sizeof(FlutterCompositor), - .create_backing_store_callback = on_create_backing_store, - .collect_backing_store_callback = on_collect_backing_store, - .present_layers_callback = on_present_layers, - .user_data = &compositor -}; diff --git a/src/compositor_ng.c b/src/compositor_ng.c new file mode 100644 index 00000000..c5bb0941 --- /dev/null +++ b/src/compositor_ng.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: MIT +/* + * compositor-ng + * + * - a reimplementation of the flutter compositor + * - takes flutter layers as input, composits them into multiple hw planes, outputs them to the modesetting interface + * + * Copyright (c) 2022, Hannes Winkler + */ + +#define _GNU_SOURCE +#include "compositor_ng.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "cursor.h" +#include "dummy_render_surface.h" +#include "flutter-pi.h" +#include "frame_scheduler.h" +#include "modesetting.h" +#include "notifier_listener.h" +#include "pixel_format.h" +#include "render_surface.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/dynarray.h" +#include "util/logging.h" +#include "util/refcounting.h" +#include "window.h" + +#include "config.h" + +#ifdef HAVE_GBM + #include +#endif + +#ifdef HAVE_EGL_GLES2 + #include "egl.h" + #include "egl_gbm_render_surface.h" + #include "gl_renderer.h" +#endif + +#ifdef HAVE_VULKAN + #include "vk_gbm_render_surface.h" + #include "vk_renderer.h" +#endif + +/** + * @brief A nicer, ref-counted version of the FlutterLayer's passed by the engine to the present layer callback. + * + * Differences to the FlutterLayer's passed to the present layer callback: + * - for platform views: + * - struct platform_view* object as the platform view instead of int64_t view id + * - position is given as a quadrilateral or axis-aligned rectangle instead of a bunch of (broken) transforms + * - same for clip rects + * - opacity and rotation as individual numbers + * - for backing stores: + * - struct render_surface* object instead of FlutterBackingStore + * - offset & size as as a struct aa_rect + * - refcounted + */ + +struct fl_layer_composition *fl_layer_composition_new(size_t n_layers) { + struct fl_layer_composition *composition; + struct fl_layer *layers; + + composition = malloc((sizeof *composition) + (n_layers * sizeof *layers)); + if (composition == NULL) { + return NULL; + } + + composition->n_refs = REFCOUNT_INIT_1; + composition->n_layers = n_layers; + return composition; +} + +size_t fl_layer_composition_get_n_layers(struct fl_layer_composition *composition) { + ASSERT_NOT_NULL(composition); + return composition->n_layers; +} + +struct fl_layer *fl_layer_composition_peek_layer(struct fl_layer_composition *composition, int layer) { + ASSERT_NOT_NULL(composition); + ASSERT_NOT_NULL(composition->layers); + assert(layer >= 0 && layer < composition->n_layers); + return composition->layers + layer; +} + +void fl_layer_composition_destroy(struct fl_layer_composition *composition) { + ASSERT_NOT_NULL(composition); + + for (int i = 0; i < composition->n_layers; i++) { + surface_unref(composition->layers[i].surface); + if (composition->layers[i].props.clip_rects != NULL) { + free(composition->layers[i].props.clip_rects); + } + } + + free(composition); +} + +DEFINE_REF_OPS(fl_layer_composition, n_refs) + +/** + * @brief The flutter compositor. Responsible for taking the FlutterLayers, processing them into a struct fl_layer_composition*, then passing + * those to the window so it can show it on screen. + * + * Right now this is only supports a single output screen only, but in the future we might add multi-screen support. + * (Possibly one Flutter Engine per view) + */ +struct compositor { + refcount_t n_refs; + pthread_mutex_t mutex; + + struct tracer *tracer; + struct window *main_window; + struct util_dynarray views; + + FlutterCompositor flutter_compositor; + + struct vec2f cursor_pos; +}; + +struct platform_view_with_id { + int64_t id; + struct surface *surface; +}; + +static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); + +static bool +on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, void *userdata); + +static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata); + +MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct window *main_window) { + struct compositor *compositor; + int ok; + + compositor = malloc(sizeof *compositor); + if (compositor == NULL) { + goto fail_return_null; + } + + ok = pthread_mutex_init(&compositor->mutex, NULL); + if (ok != 0) { + goto fail_free_compositor; + } + + util_dynarray_init(&compositor->views); + + compositor->n_refs = REFCOUNT_INIT_1; + // just so we get an error if the FlutterCompositor struct was updated + COMPILE_ASSERT(sizeof(FlutterCompositor) == 24 || sizeof(FlutterCompositor) == 48); + compositor->main_window = window_ref(main_window); + compositor->flutter_compositor = (FlutterCompositor){ + .struct_size = sizeof(FlutterCompositor), + .user_data = compositor, + .create_backing_store_callback = on_flutter_create_backing_store, + .collect_backing_store_callback = on_flutter_collect_backing_store, + .present_layers_callback = on_flutter_present_layers, + .avoid_backing_store_cache = true, + }; + compositor->tracer = tracer_ref(tracer); + compositor->cursor_pos = VEC2F(0, 0); + return compositor; + +fail_free_compositor: + free(compositor); + +fail_return_null: + return NULL; +} + +void compositor_destroy(struct compositor *compositor) { + util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view) { + surface_unref(view->surface); + } + util_dynarray_fini(&compositor->views); + tracer_unref(compositor->tracer); + window_unref(compositor->main_window); + pthread_mutex_destroy(&compositor->mutex); + free(compositor); +} + +DEFINE_REF_OPS(compositor, n_refs) + +DEFINE_STATIC_LOCK_OPS(compositor, mutex) + +void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out) { + *view_geometry_out = window_get_view_geometry(compositor->main_window); +} + +ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor) { + return window_get_refresh_rate(compositor->main_window); +} + +int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out) { + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(next_vblank_ns_out); + return window_get_next_vblank(compositor->main_window, next_vblank_ns_out); +} + +static int compositor_push_composition(struct compositor *compositor, struct fl_layer_composition *composition) { + int ok; + + TRACER_BEGIN(compositor->tracer, "window_push_composition"); + ok = window_push_composition(compositor->main_window, composition); + TRACER_END(compositor->tracer, "window_push_composition"); + + return ok; +} + +static void fill_platform_view_layer_props( + struct fl_layer_props *props_out, + const FlutterPoint *offset, + const FlutterSize *size, + const FlutterPlatformViewMutation **mutations, + size_t n_mutations, + const struct mat3f *display_to_view_transform, + const struct mat3f *view_to_display_transform, + double device_pixel_ratio +) { + (void) view_to_display_transform; + + /** + * inversion for + * ``` + * const auto transformed_layer_bounds = + * root_surface_transformation_.mapRect(layer_bounds); + * ``` + */ + + struct quad quad = transform_aa_rect( + FLUTTER_TRANSFORM_AS_MAT3F(*display_to_view_transform), + (struct aa_rect){ .offset.x = offset->x, .offset.y = offset->y, .size.x = size->width, .size.y = size->height } + ); + + struct aa_rect rect = quad_get_aa_bounding_rect(quad); + + /** + * inversion for + * ``` + * const auto layer_bounds = + * SkRect::MakeXYWH(params.finalBoundingRect().x(), + * params.finalBoundingRect().y(), + * params.sizePoints().width() * device_pixel_ratio_, + * params.sizePoints().height() * device_pixel_ratio_ + * ); + * ``` + */ + + rect.size.x /= device_pixel_ratio; + rect.size.y /= device_pixel_ratio; + + // okay, now we have the params.finalBoundingRect().x() in aa_back_transformed.x and + // params.finalBoundingRect().y() in aa_back_transformed.y. + // those are flutter view coordinates, so we still need to transform them to display coordinates. + + // However, there are also calculated as a side-product of calculating the size of the quadrangle. + // So we'll avoid calculating them for now. Calculation of the size may fail when the offset + // given to `SceneBuilder.addPlatformView` (https://api.flutter.dev/flutter/dart-ui/SceneBuilder/addPlatformView.html) + // is not zero. (Don't really know what to do in that case) + + rect.offset.x = 0; + rect.offset.y = 0; + quad = get_quad(rect); + + double rotation = 0, opacity = 1; + for (int i = n_mutations - 1; i >= 0; i--) { + if (mutations[i]->type == kFlutterPlatformViewMutationTypeTransformation) { + quad = transform_quad(FLUTTER_TRANSFORM_AS_MAT3F(mutations[i]->transformation), quad); + + double rotz = atan2(mutations[i]->transformation.skewX, mutations[i]->transformation.scaleX) * 180.0 / M_PI; + if (rotz < 0) { + rotz += 360; + } + + rotation += rotz; + } else if (mutations[i]->type == kFlutterPlatformViewMutationTypeOpacity) { + opacity *= mutations[i]->opacity; + } + } + + rotation = fmod(rotation, 360.0); + + /// TODO: Implement axis aligned rectangle detection + props_out->is_aa_rect = false; + props_out->aa_rect = AA_RECT_FROM_COORDS(0, 0, 0, 0); + props_out->quad = quad; + props_out->opacity = opacity; + props_out->rotation = rotation; + + /// TODO: Implement clip rects + props_out->n_clip_rects = 0; + props_out->clip_rects = NULL; +} + +static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_layers, const FlutterLayer **fl_layers) { + struct fl_layer_composition *composition; + int ok; + + composition = fl_layer_composition_new(n_fl_layers); + if (composition == NULL) { + return ENOMEM; + } + + compositor_lock(compositor); + + for (int i = 0; i < n_fl_layers; i++) { + const FlutterLayer *fl_layer = fl_layers[i]; + struct fl_layer *layer = fl_layer_composition_peek_layer(composition, i); + + if (fl_layer->type == kFlutterLayerContentTypeBackingStore) { + /// TODO: Implement + layer->surface = surface_ref(CAST_SURFACE(fl_layer->backing_store->user_data)); + + // Tell the surface that flutter has rendered into this framebuffer / texture / image. + // It'll also read the did_update field and not update the surface revision in that case. + render_surface_queue_present(CAST_RENDER_SURFACE(layer->surface), fl_layer->backing_store); + + layer->props.is_aa_rect = true; + layer->props.aa_rect = AA_RECT_FROM_COORDS(fl_layer->offset.y, fl_layer->offset.y, fl_layer->size.width, fl_layer->size.height); + layer->props.quad = get_quad(layer->props.aa_rect); + layer->props.opacity = 1.0; + layer->props.rotation = 0.0; + layer->props.n_clip_rects = 0; + layer->props.clip_rects = NULL; + } else { + ASSERT_EQUALS(fl_layer->type, kFlutterLayerContentTypePlatformView); + + /// TODO: Maybe always check if the ID is valid? +#if DEBUG + // if we're in debug mode, we actually check if the ID is a valid, + // registered ID. + /// TODO: Implement + layer->surface = compositor_get_view_by_id_locked(compositor, fl_layer->platform_view->identifier); + if (layer->surface == NULL) { + layer->surface = + CAST_SURFACE(dummy_render_surface_new(compositor->tracer, VEC2I(fl_layer->size.width, fl_layer->size.height))); + } +#else + // in release mode, we just assume the id is valid. + // Since the surface constructs the ID by just casting the surface pointer to an int64_t, + // we can easily cast it back without too much trouble. + // Only problem is if the id is garbage, we won't notice and the returned surface is garbage too. + layer->surface = surface_ref(surface_from_id(fl_layer->platform_view->identifier)); +#endif + + struct view_geometry geometry = window_get_view_geometry(compositor->main_window); + + // The coordinates flutter gives us are a bit buggy, so calculating the right geometry is really a problem on its own + /// TODO: Don't unconditionally take the geometry from the main window. + fill_platform_view_layer_props( + &layer->props, + &fl_layer->offset, + &fl_layer->size, + fl_layer->platform_view->mutations, + fl_layer->platform_view->mutations_count, + &geometry.display_to_view_transform, + &geometry.view_to_display_transform, + geometry.device_pixel_ratio + ); + } + } + + compositor_unlock(compositor); + + TRACER_BEGIN(compositor->tracer, "compositor_push_composition"); + ok = compositor_push_composition(compositor, composition); + TRACER_END(compositor->tracer, "compositor_push_composition"); + + fl_layer_composition_unref(composition); + + return 0; + + //fail_free_composition: + //fl_layer_composition_unref(composition); + return ok; +} + +static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata) { + struct compositor *compositor; + int ok; + + ASSERT_NOT_NULL(layers); + assert(layers_count > 0); + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); + ok = compositor_push_fl_layers(compositor, layers_count, layers); + TRACER_END(compositor->tracer, "compositor_push_fl_layers"); + + if (ok != 0) { + return false; + } + + return true; +} + +ATTR_PURE static bool platform_view_with_id_equal(const struct platform_view_with_id lhs, const struct platform_view_with_id rhs) { + return lhs.id == rhs.id; +} + +int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface) { + struct platform_view_with_id *view; + + ASSERT_NOT_NULL(compositor); + assert(id != 0); + ASSERT_NOT_NULL(surface); + + compositor_lock(compositor); + + view = NULL; + util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view_iter) { + if (view_iter->id == id) { + view = view_iter; + break; + } + } + + if (view == NULL) { + if (surface != NULL) { + struct platform_view_with_id v = { + .id = id, + .surface = surface_ref(surface), + }; + + util_dynarray_append(&compositor->views, struct platform_view_with_id, v); + } + } else { + ASSERT_NOT_NULL(view->surface); + if (surface == NULL) { + util_dynarray_delete_unordered_ext(&compositor->views, struct platform_view_with_id, *view, platform_view_with_id_equal); + surface_unref(view->surface); + free(view); + } else { + surface_swap_ptrs(&view->surface, surface); + } + } + + compositor_unlock(compositor); + return 0; +} + +struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id) { + util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view) { + if (view->id == view_id) { + return view->surface; + } + } + + return NULL; +} + +#ifdef HAVE_EGL_GLES2 +bool compositor_has_egl_surface(struct compositor *compositor) { + return window_has_egl_surface(compositor->main_window); +} + +EGLSurface compositor_get_egl_surface(struct compositor *compositor) { + return window_get_egl_surface(compositor->main_window); +} +#endif + +static bool +on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, void *userdata) { + struct render_surface *s; + struct compositor *compositor; + int ok; + + ASSERT_NOT_NULL(config); + ASSERT_NOT_NULL(backing_store_out); + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + // this will not increase the refcount on the surface. + s = window_get_render_surface(compositor->main_window, VEC2I((int) config->size.width, (int) config->size.height)); + if (s == NULL) { + LOG_ERROR("Couldn't create render surface for flutter to render into.\n"); + return false; + } + + COMPILE_ASSERT(sizeof(FlutterBackingStore) == 56 || sizeof(FlutterBackingStore) == 80); + memset(backing_store_out, 0, sizeof *backing_store_out); + backing_store_out->struct_size = sizeof(FlutterBackingStore); + + /// TODO: Make this better + // compositor_on_event_fd_ready(compositor); + + // render_surface_fill asserts that the user_data is null so it can make sure + // any concrete render_surface_fill implementation doesn't try to set the user_data. + // so we set the user_data after the fill + ok = render_surface_fill(s, backing_store_out); + if (ok != 0) { + LOG_ERROR("Couldn't fill flutter backing store with concrete OpenGL framebuffer/texture or Vulkan image.\n"); + return false; + } + + // now we can set the user_data. + backing_store_out->user_data = s; + + return true; +} + +static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata) { + struct compositor *compositor; + + ASSERT_NOT_NULL(fl_store); + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + /// TODO: What should we do here? + (void) fl_store; + (void) compositor; + + return true; +} + +const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor) { + ASSERT_NOT_NULL(compositor); + return &compositor->flutter_compositor; +} + +void compositor_set_cursor( + struct compositor *compositor, + bool has_enabled, + bool enabled, + bool has_kind, + enum pointer_kind kind, + bool has_delta, + struct vec2f delta +) { + if (!has_enabled && !has_kind && !has_delta) { + return; + } + + compositor_lock(compositor); + + if (has_delta) { + // move cursor + compositor->cursor_pos = vec2f_add(compositor->cursor_pos, delta); + + struct view_geometry viewgeo = window_get_view_geometry(compositor->main_window); + + if (compositor->cursor_pos.x < 0.0f) { + compositor->cursor_pos.x = 0.0f; + } else if (compositor->cursor_pos.x > viewgeo.view_size.x) { + compositor->cursor_pos.x = viewgeo.view_size.x; + } + + if (compositor->cursor_pos.y < 0.0f) { + compositor->cursor_pos.y = 0.0f; + } else if (compositor->cursor_pos.y > viewgeo.view_size.y) { + compositor->cursor_pos.y = viewgeo.view_size.y; + } + } + + window_set_cursor( + compositor->main_window, + has_enabled, + enabled, + has_kind, + kind, + has_delta, + VEC2I((int) round(compositor->cursor_pos.x), (int) round(compositor->cursor_pos.y)) + ); + + compositor_unlock(compositor); +} diff --git a/src/compositor_ng.h b/src/compositor_ng.h new file mode 100644 index 00000000..d7360964 --- /dev/null +++ b/src/compositor_ng.h @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: MIT +/* + * Compositor NG + * + * - newer flutter compositor + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_COMPOSITOR_NG_H +#define _FLUTTERPI_SRC_COMPOSITOR_NG_H + +#include + +#include "cursor.h" +#include "flutter-pi.h" +#include "frame_scheduler.h" +#include "modesetting.h" +#include "pixel_format.h" +#include "util/collection.h" +#include "util/refcounting.h" + +#include "config.h" + +#ifdef HAVE_EGL_GLES2 + #include "egl.h" +#endif + +struct compositor; + +struct drm_connector_config { + uint32_t connector_type; + uint32_t connector_type_id; + + bool disable, primary; + + bool has_mode_size; + int mode_width, mode_height; + + bool has_mode_refreshrate; + int mode_refreshrate_n, mode_refreshrate_d; + + bool has_framebuffer_size; + int framebuffer_width, framebuffer_height; + + bool has_physical_dimensions; + int physical_width_mm, physical_height_mm; +}; + +struct drm_device_config { + bool has_path; + const char *path; + + size_t n_connector_configs; + struct drm_connector_config *connector_configs; +}; + +struct fbdev_device_config { + const char *path; + + bool has_physical_dimensions; + int physical_width_mm, physical_height_mm; +}; + +struct device_config { + bool is_drm, is_fbdev; + union { + struct drm_device_config drm_config; + struct fbdev_device_config fbdev_config; + }; +}; + +struct compositor_config { + bool has_use_hardware_cursor, use_hardware_cursor; + + bool has_forced_pixel_format; + enum pixfmt forced_pixel_format; + + size_t n_device_configs; + struct device_config *device_configs; +}; + +struct clip_rect { + struct quad rect; + bool is_aa; + + struct aa_rect aa_rect; + + bool is_rounded; + struct vec2f upper_left_corner_radius; + struct vec2f upper_right_corner_radius; + struct vec2f lower_right_corner_radius; + struct vec2f lower_left_corner_radius; +}; + +struct fl_layer_props { + /** + * @brief True if the presentation quadrangle (the quadrangle on the target window into which the + * layer should be rendered) is an axis-aligned rectangle. For example, allows us to use a plain + * hardware overlay layer for this layer. + * + * This should always be true for backing stores, but might be false for platform views. + */ + bool is_aa_rect; + + /** + * @brief The coords of the axis aligned rectangle if @ref is_aa_rect is true. + */ + struct aa_rect aa_rect; + + /** + * @brief The quadrangle on the target window into which the layer should be rendered. + */ + struct quad quad; + + /** + * @brief Opacity as a normalized float from 0 (transparent) to 1 (opqaue). + */ + double opacity; + + /** + * @brief Rotation of the buffer in degrees clockwise, normalized to a range 0 - 360. + */ + double rotation; + + /** + * @brief The number of clip rectangles in the @ref clip_rects array. + */ + size_t n_clip_rects; + + /** + * @brief The (possibly rounded) rectangles that the surface should be clipped to. + */ + struct clip_rect *clip_rects; +}; + +struct fl_layer { + struct fl_layer_props props; + struct surface *surface; +}; + +struct fl_layer_composition { + refcount_t n_refs; + size_t n_layers; + struct fl_layer layers[]; +}; + +struct drmdev; +struct compositor; +struct frame_scheduler; +struct view_geometry; +struct window; +struct tracer; + +typedef void (*compositor_frame_begin_cb_t)(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns); + +struct compositor *compositor_new(struct tracer *tracer, struct window *main_window); + +void compositor_destroy(struct compositor *compositor); + +DECLARE_REF_OPS(compositor) + +void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out); + +ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); + +int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out); + +int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface); + +struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id); + +const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); + +int compositor_request_frame(struct compositor *compositor, compositor_frame_begin_cb_t cb, void *userdata); + +#ifdef HAVE_EGL_GLES2 +bool compositor_has_egl_surface(struct compositor *compositor); + +EGLSurface compositor_get_egl_surface(struct compositor *compositor); +#endif + +int compositor_get_event_fd(struct compositor *compositor); + +int compositor_on_event_fd_ready(struct compositor *compositor); + +void compositor_set_cursor( + struct compositor *compositor, + bool has_enabled, + bool enabled, + bool has_kind, + enum pointer_kind kind, + bool has_delta, + struct vec2f delta +); + +struct fl_layer_composition; + +struct fl_layer_composition *fl_layer_composition_new(size_t n_layers); +void fl_layer_composition_destroy(struct fl_layer_composition *composition); +DECLARE_REF_OPS(fl_layer_composition) + +size_t fl_layer_composition_get_n_layers(struct fl_layer_composition *composition); +struct fl_layer *fl_layer_composition_peek_layer(struct fl_layer_composition *composition, int layer); + +#endif // _FLUTTERPI_SRC_COMPOSITOR_NG_H diff --git a/src/cursor.c b/src/cursor.c index a59b1dd3..823d5346 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -1,3948 +1,1508 @@ -#include +#include "cursor.h" -const unsigned char cursor_32x32_data[32*32*4 + 1] = - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\217" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\233\000\000\000\001\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\377\000\000\000\377\000\000\000\252\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\273\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\305\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\317\000\000\000\031" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\335\000\000\000\036\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\342\000\000\000(\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\352\000\000\000\065\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\363\000\000\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\365\000\000\000J\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\372\000\000\000]\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\376\000\000\000h\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000x\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\220\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\232\000\000\000\002\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\252\000\000\000\006\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\273\000" - "\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\252\000\000" - "\000\231\000\000\000~\000\000\000h\000\000\000[\000\000\000\067\000\000\000,\000\000\000\020\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000E\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\365\000\000\000" - "\210\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\360\000\000\000:\000\000\000\000\000\000\000\333\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000.\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\351\000\000\000\060\000\000\000\000\000\000\000\000\000\000\000n\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\240\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\000\000\000\343\000\000\000'\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016\000\000\000\362\000\000\000\377\000\000\000\377\000\000\000\372\000" - "\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\377\000\000\000\337\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\221\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\211\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\332\000\000\000\034\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\027\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\262\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000n\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000F\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\340\000\000\000" - "\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\325\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000h\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\313\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\013\000\000\000\356\000\000\000\377\000\000\000\377\000\000\000\277\000\000" - "\000\016\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\210\000\000\000\245\000\000\000\063\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; +#include +#include -const unsigned char cursor_48x48_data[48*48*4 + 1] = - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\213\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\235\000" - "\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\004\000\000\000\375\000\000\000\377\000\000\000\255\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000" - "\272\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000" - "\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\306\000\000\000\020\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\321\000\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000" - "\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000!\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000)\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000" - "\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\353\000\000\000\064\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\363\000\000\000" - "@\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000N\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\372\000\000\000\\\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\375\000\000\000l\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000}\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\215\000\000" - "\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\235\000\000\000\003\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\255\000\000\000\007\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\013\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000" - "\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\305\000\000\000\021\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\321\000\000\000\031\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000!\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000)\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\353\000\000\000" - "\064\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\362\000\000\000A\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\367\000\000\000N\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\372\000\000\000\\\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000\000[\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000" - "\000\366\000\000\000\330\000\000\000\303\000\000\000\262\000\000\000\231\000\000\000\202\000\000\000p\000\000\000Y\000" - "\000\000>\000\000\000\063\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\036\000\000\000\001\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\206\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\233\000\000\000\367\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\355\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\204\000\000\000\000\000\000\000\237\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000m\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000y\000\000\000\000\000\000\000\000\000\000\000\061" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\335\000\000\000\003\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\375\000\000\000m\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\302\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000U\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000a\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000R\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\307\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\371" - "\000\000\000V\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\341" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000;\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004" - "\000\000\000\375\000\000\000\377\000\000\000\367\000\000\000K\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000v\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\260\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\375\000\000\000\364\000\000\000B\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\022\000" - "\000\000\364\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000&\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004" - "\000\000\000\356\000\000\000\071\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\227\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\060\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000+\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\365\000\000\000\024" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\272\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\177\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000L\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\351" - "\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\334\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000f\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000n\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\327\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016\000\000\000\361\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000N\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\222\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\224\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$\000\000\000\375\000\000\000" - "\377\000\000\000\375\000\000\000\257\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\252\000\000\000\230\000\000\000&\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000"; +#include "util/asserts.h" +#include "util/collection.h" +#include "util/geometry.h" -const unsigned char cursor_64x64_data[64*64*4 + 1] = - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\013\000\000\000\210\000\000\000" - "\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000" - "\371\000\000\000\236\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\014\000\000\000\371\000\000\000\377\000\000\000\255\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\273\000\000\000\014" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\307\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\322\000\000\000\032\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\334\000\000\000\"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000,\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\354\000\000" - "\000\067\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\361\000\000\000C\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000" - "O\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\372\000\000\000^\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000" - "\000\000n\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\177\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\217\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\237" - "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\255\000\000\000\010\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\307\000\000\000\022\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\321\000" - "\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000!\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000,\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\354\000\000\000\067\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000" - "\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\361\000\000\000C\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\367\000\000\000O\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\372\000\000\000_\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\375\000\000\000n\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\376\000\000\000}\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000" - "\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\217\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000" - "\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\237\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\256\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\307\000\000\000\022\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000" - "\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\322\000\000\000\032" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014" - "\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\334\000\000\000\"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\370\000\000\000\317\000\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\367\000\000\000\340\000" - "\000\000\307\000\000\000\260\000\000\000\233\000\000\000\207\000\000\000s\000\000\000_\000\000\000F\000\000\000-\000\000\000" - "\026\000\000\000\010\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000" - "\000e\000\000\000K\000\000\000\066\000\000\000\"\000\000\000\021\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000S\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\304\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000" - "\000\324\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\376\000\000\000\071\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\314\000\000\000\025\000\000\000a\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\255\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\305" - "\000\000\000\020\000\000\000\000\000\000\000\012\000\000\000\351\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000%\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\273\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\204\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\224\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\262\000" - "\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\033\000\000\000\370\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\364\000\000\000\023\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\250\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\247\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000}\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\235\000\000\000\004\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\071\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\347\000\000\000\011" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\223\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\310\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\206\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000Z\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000\000\001" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\377\000\000\000\376\000\000\000z\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\007\000\000\000\345\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000L\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\371\000\000\000\375\000\000\000o\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000}\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\277\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000" - "\367\000\000\000c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\026\000\000\000" - "\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376" - "\000\000\000\063\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\011\000\000\000S\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\240\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\245\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\062\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\037\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\301\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\216\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000S\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\360\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\005\000\000\000\340\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000t\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000v\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\343\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\022\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000]\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\231\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\316\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000,\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000:\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\272\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\375\000\000\000\270\000\000\000D\000\000\000\001\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000L\000\000\000" - "\377\000\000\000\377\000\000\000\371\000\000\000\237\000\000\000.\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\004\000\000\000\312\000\000\000\212\000\000\000\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; +#define PIXEL_RATIO_LDPI 1.25 +#define PIXEL_RATIO_MDPI 1.6666 +#define PIXEL_RATIO_HDPI 2.5 +#define PIXEL_RATIO_XHDPI 3.3333 +#define PIXEL_RATIO_XXHDPI 5 +#define PIXEL_RATIO_XXXHDPI 6.6666 -const unsigned char cursor_96x96_data[96*96*4 + 1] = - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\207\000\000\000\004\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\241\000\000\000\006\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\023\000\000\000\366\000\000\000\377\000\000\000\256\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366" - "\000\000\000\377\000\000\000\377\000\000\000\274\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\307\000\000\000\026\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\322\000\000\000\035\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\334\000\000\000&\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\344\000\000\000\060\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023" - "\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\353\000\000\000:\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\362\000\000\000G\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\365\000\000\000T\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\371\000\000\000c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000r\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\202\000\000\000\001\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\221\000\000" - "\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\240\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000" - "\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\256\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\274\000\000\000\017\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\307\000\000\000\026\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\323\000\000\000\035\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000&\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000" - "\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\345\000\000\000/\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\353\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\361\000\000" - "\000F\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\366\000\000\000U\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000\000b\000" - "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\374\000\000\000r\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000" - "\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000" - "\202\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\221\000\000\000\003\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\241\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\256\000\000" - "\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000" - "\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\274\000\000\000\017\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\307\000\000\000\026\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\322\000\000\000\034\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000&\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000/\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\353\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\362\000\000\000G\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\365\000\000\000T\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\372\000" - "\000\000b\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000" - "\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000s\000" - "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\201\000\000" - "\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\222\000\000" - "\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\240\000" - "\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\257\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\273\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\307\000\000\000\025\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\322\000\000\000\035\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000%\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000\060\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\353\000" - "\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\361\000\000\000F\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\366\000\000\000U\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000\353\000\000\000\327\000\000\000\070\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\373" - "\000\000\000\367\000\000\000\344\000\000\000\316\000\000\000\271\000\000\000\243\000\000\000\215\000\000\000w\000\000\000" - "a\000\000\000L\000\000\000\066\000\000\000\040\000\000\000\022\000\000\000\011\000\000\000\002\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\375\000\000\000\364\000\000\000\351\000\000\000\331\000\000\000\304\000\000\000\256" - "\000\000\000\230\000\000\000\203\000\000\000m\000\000\000W\000\000\000A\000\000\000,\000\000\000\026\000\000\000\003\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\355\000\000\000'\000\000\000\021" - "\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000]\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\316\000\000\000\001\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\376\000\000\000H\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\317\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\271\000\000" - "\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\231\000\000\000\015\000\000\000\336\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\061\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\217" - "\000\000\000\003\000\000\000\000\000\000\000u\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\236\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000" - "\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\376\000\000\000\202\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\025\000\000\000\361\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000y\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\231\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\211\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000l\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000.\000\000\000\375\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\353\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000" - "\000b\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000" - "\271\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000n\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\370\000\000\000W\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000L\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\335\000\000\000" - "\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\363\000\000\000M\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\000\000\000\330\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000" - "\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\361\000\000\000D\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000q\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\310\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\354\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\021\000\000\000\357\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000?\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\347\000\000\000\063\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\220\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\263\000" - "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\342\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000)\000\000\000\372\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\373\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\265\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\231\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000\000\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000G\000\000\000\376\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\364\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000" - "\366\000\000\000\377\000\000\000\377\000\000\000\315\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000" - "\323\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\203\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\023\000\000\000\366\000\000\000\377\000\000\000\306\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000h\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\347\000\000\000\016\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\023\000\000\000\366\000\000\000\274\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\017\000\000\000\353\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000h\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\023\000\000\000\252\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\214\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\327\000" - "\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\003\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\"\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000R\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\255\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\302\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000@\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\375\000\000\000\071\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000\000\000\316\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\252\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\372\000\000\000%\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\346\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\223\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\205\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\360\000\000\000\025\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\036\000\000\000\370\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\244\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\344\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\071\000\000\000\375\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000b\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\307\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\321\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000]\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000J\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\342\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\375\000\000\000\306\000\000\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000|\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\261" - "\000\000\000=\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\000\000\000\364\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\364\000\000\000\230\000\000\000*\000\000\000\001\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\241\000\000\000\377\000\000\000\377\000\000\000\350" - "\000\000\000\203\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\063\000\000\000\326\000\000\000j\000\000\000\016\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; +struct pointer_icon { + enum pointer_kind kind; + unsigned int width; + unsigned int height; + unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ + float pixel_ratio; + unsigned int hot_x, hot_y; + char *rle_pixel_data; +}; + +// These were all generated by GIMP. +static const struct pointer_icon pointer_arrow_mdpi = { + .kind = POINTER_KIND_BASIC, + .width = 22, + .height = 28, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_MDPI, + .hot_x = 5, + .hot_y = 5, + .rle_pixel_data = + "\204\000\000\000\000\001)))\000\222\000\000\000\000\010\012\012\012\001\000\000\000\001\"\"\"\002\202\202\202" + "\006\\\\\\\005\000\000\000\002\005\005\005\002\000\000\000\001\216\000\000\000\000\011\000\000\000\001\000\000\000\004\001\001\001" + "\005" + "^^^\000###\005***\013\000\000\000\006\002\002\002\004\000\000\000\001\215\000\000\000\000\012<<<\003\000\000\000\004rrr\031" + "\352\352\352\227\271\271\271@\000\000\000\012+++\025\000\000\000\013\000\000\000\005\001\001\001\002\214" + "\000\000\000\000\013PPP\005\000\000\000\006\233\233\233\062\347\347\347\364\356\356\356\340\245" + "\245\245R\000\000\000\032###\034\002\002\002\015\000\000\000\005\001\001\001\002\213\000\000\000\000\014;;;\006\000\000" + "\000\014\237\237\237;\303\303\303\342PPP\376\352\352\352\346\240\240\240l\000" + "\000\000%###\036\004\004\004\016\000\000\000\005\001\001\001\002\212\000\000\000\000\015\066\066\066\007\000\000\000\020\204" + "\204\204C\317\317\317\347\000\000\000\377\065\065\065\374\350\350\350\355\255\255" + "\255\202\000\000\000*\040\040\040\037\007\007\007\017\000\000\000\006\002\002\002\002\211\000\000\000\000\017\063\063" + "\063\007\000\000\000\023zzzI\314\314\314\347\014\014\014\377\000\000\000\376---\374\341\341" + "\341\361\273\273\273\215\000\000\000,\035\035\035\040\013\013\013\020\000\000\000\006\002\002\002\002\000" + "\000\000\001\207\000\000\000\000\020\061\061\061\010\000\000\000\024vvvL\314\314\314\347\011\011\011\377" + "\001\001\001\376\000\000\000\377!!!\374\326\326\326\364\306\306\306\226\000\000\000\060\031\031" + "\031!\016\016\016\021\000\000\000\007\002\002\002\003\000\000\000\001\206\000\000\000\000\021\060\060\060\010\000\000\000" + "\024tttM\314\314\314\350\011\011\011\377\000\000\000\377\004\004\004\377\000\000\000\377\023\023" + "\023\374\307\307\307\367\322\322\322\241\000\000\000\064\023\023\023!\021\021\021\023\000" + "\000\000\007\003\003\003\003\000\000\000\001\205\000\000\000\000\022\061\061\061\010\000\000\000\024tttM\314\314\314" + "\350\011\011\011\377\000\000\000\377\001\001\001\377\002\002\002\377\000\000\000\377\012\012\012\374\272" + "\272\272\370\341\341\341\257\000\000\000;\007\007\007\"\025\025\025\024\000\000\000\010\003\003\003\003" + "\000\000\000\001\204\000\000\000\000\023\061\061\061\010\000\000\000\024tttM\314\314\314\350\011\011\011" + "\377\000\000\000\377\001\001\001\377\000\000\000\377\002\002\002\377\000\000\000\377\004\004\004\374\256\256" + "\256\372\351\351\351\272\021\021\021@\000\000\000#\030\030\030\026\000\000\000\011\003\003\003\003\000" + "\000\000\001\203\000\000\000\000\007\061\061\061\010\000\000\000\024tttM\314\314\314\350\011\011\011\377" + "\000\000\000\377\001\001\001\377\202\000\000\000\377\013\002\002\002\377\001\001\001\377\000\000\000\374\235\235" + "\235\373\354\354\354\302###E\000\000\000\"\033\033\033\027\000\000\000\011\002\002\002\003\000\000\000\001" + "\202\000\000\000\000\007\061\061\061\010\000\000\000\024tttM\314\314\314\350\011\011\011\377\000\000" + "\000\377\001\001\001\377\203\000\000\000\377\012\001\001\001\377\003\003\003\377\000\000\000\375\212\212\212" + "\374\366\366\366\312JJJE\000\000\000\032\025\025\025\022\000\000\000\006\001\001\001\002\202\000\000\000\000" + "\007\061\061\061\010\000\000\000\024tttM\314\314\314\350\011\011\011\377\000\000\000\377\001\001\001" + "\377\203\000\000\000\377\012\001\001\001\377\000\000\000\377\002\002\002\377\000\000\000\375ggg\374\345" + "\345\345\320sssA\000\000\000\020$$$\012\000\000\000\002\202\000\000\000\000\011\061\061\061\010\000\000\000" + "\024tttM\314\314\314\350\011\011\011\377\000\000\000\377\001\001\001\377\003\003\003\377\002\002\002" + "\377\202\000\000\000\377\011\007\007\007\377\033\033\033\377\067\067\067\377BBB\375\300\300" + "\300\377\367\367\367\325&&&\040\000\000\000\011\063\063\063\004\202\000\000\000\000\024\060\060\060" + "\010\000\000\000\024tttL\314\314\314\350\010\010\010\377\000\000\000\376\003\003\003\377\000\000\000\377" + "\003\003\003\377\000\000\000\377EEE\370\325\325\325\350\323\323\323\340\331\331\331\320" + "\344\344\344\271\317\317\317\236\300\300\300r(((\031\000\000\000\011\033\033\033\003\202" + "\000\000\000\000\024\062\062\062\010\000\000\000\024wwwK\314\314\314\347\013\013\013\377\000\000\000\377" + "\000\000\000\376kkk\370```\366\000\000\000\377DDD\373\350\350\350\333\035\035\035\207\000" + "\000\000a\001\001\001>\001\001\001\"\000\000\000\020\000\000\000\016\000\000\000\007\000\000\000\002\202\000\000\000\000\024\065" + "\065\065\007\000\000\000\021}}}F\316\316\316\347\005\005\005\377\030\030\030\374\256\256\256" + "\370\347\347\347\322\323\323\323\343\025\025\025\376\000\000\000\376\261\261\261\365" + "\215\215\215\222\000\000\000J\030\030\030\067\033\033\033\040'''\023\005\005\005\011\000\000\000\004\002" + "\002\002\002\202\000\000\000\000\024:::\007\000\000\000\016\223\223\223A\306\306\306\345\\\\\\\377" + "\340\340\340\354\276\276\276\244\000\000\000l\313\313\313\273sss\374\000\000\000\376" + "QQQ\377\337\337\337\306\000\000\000I\006\006\006,\003\003\003\025\000\000\000\010\000\000\000\003\000\000\000\002\000" + "\000\000\001\202\000\000\000\000\022@@@\005\000\000\000\011{{{\062\351\351\351\354\357\357\357\314" + "\204\204\204c\000\000\000?\000\000\000Fwwwv\313\313\313\356\005\005\005\376\000\000\000\376\306\306" + "\306\361\217\217\217q\000\000\000'\032\032\032\026\001\001\001\006\000\000\000\001\204\000\000\000\000\022\023" + "\023\023\003\000\000\000\011\036\036\036\024\271\271\271PNNN-\000\000\000\035\025\025\025(\013\013\013" + ".\000\000\000\071\346\346\346\275\\\\\\\377\000\000\000\375kkk\377\347\347\347\264\000\000" + "\000-\023\023\023\033\006\006\006\010\000\000\000\001\204\000\000\000\000\022\001\001\001\002\000\000\000\005\007\007\007\011" + "\000\000\000\004\022\022\022\017\036\036\036\022\000\000\000\020\032\032\032\031\000\000\000\040\256\256\256" + "m\266\266\266\365\000\000\000\375\006\006\006\375\304\304\304\350\204\204\204R\000\000\000" + "\027'''\013\000\000\000\002\204\000\000\000\000\022\003\003\003\001\000\000\000\002\011\011\011\004YYY\007\040\040\040" + "\007\000\000\000\005\002\002\002\005\011\011\011\013\000\000\000\031\000\000\000/\350\350\350\311TTT\377SSS" + "\375\331\331\331\375\303\303\303v\000\000\000\020AAA\014\000\000\000\002\206\000\000\000\000\205" + "\000\000\000\001\013\000\000\000\005)))\021\000\000\000\024\273\273\273q\354\354\354\341\347\347\347" + "\246\245\245\245e\030\030\030+\000\000\000\024\013\013\013\010\000\000\000\002\213\000\000\000\000\013\002" + "\002\002\002\000\000\000\010\002\002\002\021\017\017\017\033:::.\002\002\002\037\000\000\000\030\000\000\000\026\000\000\000" + "\015\000\000\000\005\000\000\000\001\213\000\000\000\000\013\000\000\000\001\000\000\000\003\001\001\001\010\017\017\017\015\000" + "\000\000\017\037\037\037\025&&&\022\007\007\007\014\000\000\000\006\002\002\002\003\000\000\000\001\214\000\000\000\000\011" + "\002\002\002\001\000\000\000\003\014\014\014\005###" + "\007\004\004\004\006\000\000\000\006\000\000\000\004\000\000\000\002\000\000\000\001" + "\204\000\000\000\000", +}; -const unsigned char cursor_128x128_data[128*128*4 + 1] = - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\206\000\000\000\007\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000" - "\000\242\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\031\000\000\000\363\000\000\000\376\000\000\000\260\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\273\000" - "\000\000\024\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\310\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\322\000\000\000" - "!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000" - "\000\063\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\352\000\000\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\361\000\000\000L\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\365\000\000\000Y\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\367\000\000\000h\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000" - "\000\000u\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\205\000\000\000\004\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\225\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" - "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\376\000\000\000\241\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\260\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\275\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\307\000\000" - "\000\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000" - "\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\323\000\000\000\040\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000*\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\343\000\000\000\065\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\353\000\000\000>\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\360\000\000\000L\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\365\000\000\000Y\000\000\000\001\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000" - "\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000f\000\000\000\002\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000v\000\000\000\001\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000\205\000" - "\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376" - "\000\000\000\223\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000" - "\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\376\000\000\000\242\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" - "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\257\000\000\000\016\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\274\000\000\000\023\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\310\000\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\322\000\000\000!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000*\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\344" - "\000\000\000\064\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" - "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\351\000\000\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\361\000\000\000J\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\365\000\000\000Z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000h\000\000\000\001\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\373\000\000\000t\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\206\000\000\000\003\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000" - "\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\375\000\000\000\224\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\240\000\000" - "\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000" - "\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\260\000\000\000\014\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\275\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" - "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\307\000\000\000" - "\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\323\000\000\000\040\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\333\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\344\000\000\000\064\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\352\000\000\000?\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\360\000\000\000K\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\366\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\370\000\000\000h\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\372\000\000\000u\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000" - "\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\376\000\000\000\203\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\376\000\000\000\224\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\376\000\000\000\242\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\257\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\275\000\000\000\022\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\307\000\000\000\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\321\000\000\000!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000)\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\343\000\000\000\065\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\352\000\000\000?\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\360\000\000\000K\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\365\000\000\000Y\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000g\000\000\000\002\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000u\000\000\000\002" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\374\000\000\000\206\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\224\000\000\000\007\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000\353\000\000\000\336\000\000\000\317\000\000\000`\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000" - "\375\000\000\000\372\000\000\000\350\000\000\000\322\000\000\000\275\000\000\000\250\000\000\000\222\000\000\000}\000" - "\000\000h\000\000\000R\000\000\000=\000\000\000'\000\000\000\031\000\000\000\020\000\000\000\010\000\000\000\001\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\376\000\000\000\371\000\000\000\362\000\000\000\353\000\000\000\336\000\000\000\311\000\000\000" - "\264\000\000\000\237\000\000\000\211\000\000\000t\000\000\000^\000\000\000I\000\000\000\063\000\000\000\036\000\000\000\013" - "\000\000\000\006\000\000\000\004\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\366\000\000\000\324\000" - "\000\000\277\000\000\000\252\000\000\000\224\000\000\000}\000\000\000e\000\000\000M\000\000\000\066\000\000\000&\000\000\000\027" - "\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\350\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000" - "\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000h\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\326\000\000\000" - "\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000T\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\302\000\000\000\002\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000\000\353\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373" - "\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000" - "\000\000_\000\000\000h\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\256\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\364\000\000\000V\000\000\000\001\000\000\000\022\000\000\000\350\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000)\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\361\000\000\000M\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\214\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\221" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\357\000\000\000C\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$" - "\000\000\000\365\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\361\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\350\000\000\000;\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\255\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000}\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\344\000\000\000\063\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000C\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\341\000\000\000\014\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\337\000\000\000,\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\313\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000d\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" - "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000%\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000a\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\321\000" - "\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\321\000\000\000\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\017\000\000\000\345" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\376\000\000\000J\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\311\000\000\000\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\204\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\272\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\301\000\000\000\025\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\040\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\375\000\000\000\067\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\271\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\247\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\245\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\256\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000<\000\000\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\364\000\000\000\"\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000" - "\000\246\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\003\000\000\000\304\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\217\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\233\000" - "\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000Z\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\353\000\000\000\026\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\217\000\000\000\006\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\015\000\000\000\341\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000r\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\374\000\000\000\207\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000}\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\336\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\374\000\000\000|\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\034\000\000\000\360\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000^\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000" - "\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\372\000\000\000o\000\000\000\002" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\241" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\313\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000f\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\066\000\000\000\374\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000" - "\000\000F\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000" - "\366\000\000\000\\\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\277\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\270\000\000\000\002\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\031\000\000\000\363\000\000\000\377\000\000\000\363\000\000\000P\000\000\000\001\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000T\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000\000\061\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000" - "\000\000\363\000\000\000\357\000\000\000I\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\013" - "\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\234\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\337\000\000\000@\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000v\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\365" - "\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\016\000\000\000-\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\356\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\207\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\232\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\347\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\061\000\000" - "\000\372\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000o\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\270\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\331\000\000\000" - "\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000M\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000T\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\011\000\000" - "\000\330\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\304\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000n\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000" - "@\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\025\000\000\000\352\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\257\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\224\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\370\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000*\000\000\000\371\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\232\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\262\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\034\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000G\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000}\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\322\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" - "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\345\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000g\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000h\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\021\000\000\000\347\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\323\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\215" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\375\000\000\000Q\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000%\000\000\000\367\000\000\000\377\000\000\000\377" - "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\237\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\253\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" - "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\372\000\000\000\277\000\000\000Q\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000A\000" - "\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000\252\000\000\000\070\000\000\000\002\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\000\000\000\315\000\000\000\377\000\000\000\377\000\000" - "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\220\000\000\000" - "'\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000`\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\341\000\000\000" - "}\000\000\000\027\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016\000\000\000\344\000\000\000\377\000\000\000\376\000\000\000\322" - "\000\000\000c\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\205\000\000\000" - "\274\000\000\000M\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; +static const struct pointer_icon pointer_arrow_hdpi = { + .kind = POINTER_KIND_BASIC, + .width = 33, + .height = 42, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_HDPI, + .hot_x = 7, + .hot_y = 7, + .rle_pixel_data = + "\245\000\000\000\000\210\000\000\000\001\227\000\000\000\000\001\000\000\000\001\202\000\000\000\002\006\000\000\000\003\070\070" + "\070\005\036\036\036\005\000\000\000\004\001\001\001\004\000\000\000\003\202\000\000\000\002\001\000\000\000\001\225\000\000\000" + "\000\015\000\000\000\002\001\001\001\003\000\000\000\004\021\021\021\006\000\000\000\004\"\"\"\010\035\035\035\012\000\000" + "\000\010\001\001\001\007\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\224\000\000\000\000\016\000\000\000\002" + "\001\001" + "\001\004\012\012\012\010\000\000\000\007\213\213\213\036RRR\021\000\000\000\014\030\030\030\022\000\000\000" + "\015\001\001\001\012\000\000\000\007\000\000\000\004\000\000\000\002\000\000\000\001\222\000\000\000\000\020\000\000\000\001\000" + "\000\000" + "\003\040\040\040\010\000\000\000\010\201\201\201\040\360\360\360\321\350\350\350\265oo" + "o)\000\000\000\022\031\031\031\032\000\000\000\023\000\000\000\016\000\000\000\011\000\000\000\005\000\000\000\003\000\000\000" + "\001\221\000\000\000\000\021\000\000\000\001\000\000\000\004&&&\013\000\000\000\012\247\247\247<\356\356\356" + "\347\364\364\364\367\356\356\356\303ccc=\000\000\000\035\026\026\026\"\000\000\000\030\000\000" + "\000\020\000\000\000\011\000\000\000\005\000\000\000\003\000\000\000\001\220\000\000\000\000\022\000\000\000\001\000\000\000\006" + "\036" + "\036\036\016\000\000\000\017\217\217\217@\363\363\363\344\222\222\222\356\331\331\331" + "\364\367\367\367\320\\\\\\O\000\000\000'\024\024\024)\001\001\001\033\000\000\000\021\000\000\000\012\000" + "\000\000\005\000\000\000\003\000\000\000\001\217\000\000\000\000\023\000\000\000\002\000\000\000\007\031\031\031\021\000\000\000" + "\024" + "zzzG\377\377\377\344SSS\370\031\031\031\370\342\342\342\360\366\366\366\334" + "gggb\000\000\000.\025\025\025,\001\001\001\034\000\000\000\022\000\000\000\012\000\000\000\006\000\000\000\003\000\000\000\001" + "\216\000\000\000\000\024\000\000\000\002\000\000\000\010\026\026\026\023\000\000\000\031qqqM\375\375\375\344" + "]]]\367\000\000\000\377\035\035\035\371\320\320\320\360\371\371\371\341\205\205\205" + "z\000\000\000\062\023\023\023-\003\003\003\035\000\000\000\023\000\000\000\013\000\000\000\006\000\000\000\003\000\000\000\001\215" + "\000\000\000\000\007\000\000\000\002\000\000\000\010\025\025\025\024\000\000\000\035jjjR\375\375\375\344ZZZ\367" + "\202\000\000\000\377\013\030\030\030\372\303\303\303\357\376\376\376\350\213\213\213" + "\201\000\000\000\065\022\022\022.\004\004\004\037\000\000\000\023\000\000\000\014\000\000\000\006\000\000\000\003\202\000" + "\000\000\001\213\000\000\000\000\027\000\000\000\002\000\000\000\011\023\023\023\026\000\000\000\037fffU\375\375\375" + "\344ZZZ\367\000\000\000\377\006\006\006\376\000\000\000\377\021\021\021\374\273\273\273\356\377" + "\377\377\355\233\233\233\215\000\000\000\065\021\021\021/\006\006\006\040\000\000\000\024\000\000\000\014" + "\000\000\000\007\000\000\000\003\000\000\000\002\000\000\000\001\212\000\000\000\000\030\000\000\000\002\000\000\000\011\023\023" + "\023" + "\026\000\000\000!dddW\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\002\002\002\377\000\000\000" + "\377\010\010\010\375\251\251\251\357\377\377\377\355\245\245\245\225\000\000\000\071" + "\016\016\016/\007\007\007\"\000\000\000\025\000\000\000\015\000\000\000\007\000\000\000\003\000\000\000\002\000\000\000\001\211" + "\000\000\000\000\031\000\000\000\002\000\000\000\011\023\023\023\026\000\000\000!bbbY\375\375\375\345ZZZ\367" + "\000\000\000\377\003\003\003\377\000\000\000\377\002\002\002\377\000\000\000\377\006\006\006\375\223\223\223" + "\360\377\377\377\357\256\256\256\235\000\000\000<\014\014\014\060\011\011\011\"\000\000\000" + "\025\000\000\000\015\000\000\000\007\000\000\000\004\000\000\000\002\000\000\000\001\210\000\000\000\000\011\000\000\000\002\000" + "\000\000" + "\011\023\023\023\027\000\000\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\202" + "\000\000\000\377\017\001\001\001\377\002\002\002\377\000\000\000\377\204\204\204\360\377\377\377\360" + "\276\276\276\253\000\000\000>\013\013\013\060\013\013\013$\000\000\000\026\001\001\001\016\000\000\000\010" + "\000\000\000\004\000\000\000\002\000\000\000\001\207\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000\000" + "\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\203\000\000\000\377\017\001\001\001" + "\377\003\003\003\377\000\000\000\377nnn\362\377\377\377\357\313\313\313\270\037\037\037" + "I\000\000\000/\015\015\015%\000\000\000\027\000\000\000\017\000\000\000\010\000\000\000\004\000\000\000\002\000\000\000\001\206" + "\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000\000\000!bbbY\375\375\375\345ZZZ\367" + "\000\000\000\377\003\003\003\377\204\000\000\000\377\017\001\001\001\377\002\002\002\377\000\000\000\377ccc\362" + "\376\376\376\362\326\326\326\300---O\000\000\000\060\017\017\017'\000\000\000\030\000\000\000\017" + "\000\000\000\011\000\000\000\004\000\000\000\002\000\000\000\001\205\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023" + "\023" + "\027\000\000\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\205\000\000\000\377\017" + "\001\001\001\377\003\003\003\377\000\000\000\377XXX\363\370\370\370\361\337\337\337\313\060" + "\060\060S\000\000\000\061\021\021\021)\000\000\000\031\000\000\000\020\000\000\000\011\000\000\000\005\000\000\000\003\000\000" + "\000\001\204\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000\000\000!bbbY\375\375\375\345" + "ZZZ\367\000\000\000\377\003\003\003\377\207\000\000\000\377\016\004\004\004\377\000\000\000\377EEE\365\365" + "\365\365\360\346\346\346\321FFF[\000\000\000/\023\023\023*\000\000\000\032\000\000\000\020\000\000\000" + "\011\000\000\000\005\000\000\000\002\000\000\000\001\203\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000" + "\000\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\210\000\000\000\377\015\003\003" + "\003\377\000\000\000\377\065\065\065\367\345\345\345\361\357\357\357\325TTT`\000\000\000-" + "\025\025\025(\001\001\001\030\000\000\000\016\000\000\000\010\000\000\000\004\000\000\000\002\203\000\000\000\000\011\000\000" + "\000\002\000\000\000\011\023\023\023\027\000\000\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003" + "\003\377\211\000\000\000\377\015\003\003\003\377\000\000\000\377(((\370\334\334\334\360\366\366" + "\366\336jjjb\000\000\000$\033\033\033\"\001\001\001\023\000\000\000\013\000\000\000\005\000\000\000\002\000\000\000\001" + "\202\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000\000\000!bbbY\375\375\375\345Z" + "ZZ\367\000\000\000\377\003\003\003\377\210\000\000\000\377\202\001\001\001\377\014\005\005\005\376\000\000\000" + "\377\034\034\034\371\323\323\323\357\376\376\376\341\235\235\235l\000\000\000\032\024" + "\024\024\031\001\001\001\016\000\000\000\007\000\000\000\003\000\000\000\001\202\000\000\000\000\011\000\000\000\002\000\000\000" + "\011" + "\023\023\023\027\000\000\000!bbbY\375\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\207\000" + "\000\000\377\001\002\002\002\377\203\000\000\000\377\013\001\001\001\377\000\000\000\377\015\015\015\373\257" + "\257\257\360\370\370\370\346\256\256\256f\000\000\000\025\025\025\025\021\003\003\003\010\000" + "\000\000\004\000\000\000\001\202\000\000\000\000\011\000\000\000\002\000\000\000\011\023\023\023\027\000\000\000!bbbY\375" + "\375\375\345ZZZ\367\000\000\000\377\003\003\003\377\203\000\000\000\377\001\001\001\001\377\204\000\000" + "\000\377\016\002\002\002\377\007\007\007\375\033\033\033\372\070\070\070\370ZZZ\367mmm\367\226" + "\226\226\363\364\364\364\367\362\362\362\346]]]+\000\000\000\015\033\033\033\012\000\000" + "\000\004\000\000\000\001\202\000\000\000\000\037\000\000\000\002\000\000\000\011\023\023\023\026\000\000\000!bbbY\375\375" + "\375\345ZZZ\367\000\000\000\377\003\003\003\377\000\000\000\377\001\001\001\377\002\002\002\377\000\000\000\377" + "\001\001\001\377\002\002\002\377\000\000\000\377JJJ\370\303\303\303\354\347\347\347\360\363" + "\363\363\357\365\365\365\353\374\374\374\346\377\377\377\337\366\366\366" + "\313\340\340\340\271\321\321\321\206\"\"\"\035\000\000\000\017\014\014\014\011\000\000\000" + "\004\000\000\000\001\202\000\000\000\000\037\000\000\000\002\000\000\000\011\023\023\023\026\000\000\000!cccX\375\375" + "\375\345ZZZ\367\000\000\000\377\003\003\003\377\002\002\002\377\001\001\001\377\000\000\000\377\004\004\004\376" + "\001\001\001\377\002\002\002\377\000\000\000\377\225\225\225\361\377\377\377\345\243\243\243" + "\303\211\211\211\254ttt\224PPPv\"\"\"V\006\006\006>\002\002\002*\000\000\000\024\000\000\000\023\000" + "\000\000\015\000\000\000\007\000\000\000\003\000\000\000\001\202\000\000\000\000\016\000\000\000\002\000\000\000\011\023\023\023" + "\026\000\000\000\040eeeV\375\375\375\344ZZZ\367\000\000\000\377\006\006\006\376\000\000\000\377\000\000" + "\000\376eee\365\336\336\336\353VVV\366\202\000\000\000\377\017\060\060\060\371\365\365" + "\365\355xxx\253\000\000\000k\010\010\010^\000\000\000J\000\000\000:\000\000\000-\003\003\003!\031\031\031\032" + "\006\006\006\020\000\000\000\012\001\001\001\005\000\000\000\002\000\000\000\001\202\000\000\000\000\007\000\000\000\002\000\000" + "\000\010" + "\025\025\025\024\000\000\000\035iiiS\375\375\375\344\\\\\\\367\202\000\000\000\377\026\030\030" + "\030\372\263\263\263\361\374\374\374\350\366\366\366\340\307\307\307\355\002" + "\002\002\376\000\000\000\377\000\000\000\376\256\256\256\363\352\352\352\325\011\011\011r\005" + "\005\005Y\010\010\010E\005\005\005\063\004\004\004$\004\004\004\031\002\002\002\021\000\000\000\013\000\000\000\007\000\000\000" + "\004\000\000\000\002\000\000\000\001\202\000\000\000\000\017\000\000\000\002\000\000\000\010\026\026\026\023\000\000\000\032n" + "nnO\376\376\376\344\\\\\\\367\000\000\000\377ZZZ\363\352\352\352\360\370\370\370" + "\342iii\243WWW\241\377\377\377\352HHH\367\202\000\000\000\377\015>>>\370\373\373" + "\373\355|||\217\000\000\000L\013\013\013>\000\000\000+\000\000\000\033\000\000\000\021\000\000\000\013\000\000\000" + "\007\000\000\000\004\000\000\000\003\000\000\000\001\203\000\000\000\000\034\000\000\000\002\000\000\000\007\030\030\030\021\000" + "\000" + "\000\026{{{I\371\371\371\343kkk\363\216\216\216\362\377\377\377\360\330\330" + "\330\303\064\064\064u\000\000\000d\000\000\000o\345\345\345\316\263\263\263\363\000\000\000\376" + "\001\001\001\377\000\000\000\375\302\302\302\364\337\337\337\304\000\000\000P\003\003\003;\002\002\002" + "'\000\000\000\027\000\000\000\015\000\000\000\006\000\000\000\003\000\000\000\002\202\000\000\000\001\203\000\000\000\000\020\000" + "\000\000\002\000\000\000\006\035\035\035\017\000\000\000\020\217\217\217B\355\355\355\351\346\346" + "\346\365\375\375\375\344\254\254\254\215\000\000\000H\002\002\002I\020\020\020T\000\000\000P\224" + "\224\224\226\372\372\372\357\066\066\066\370\202\000\000\000\377\011[[[\365\377\377" + "\377\352rrrw\000\000\000\067\015\015\015*\000\000\000\030\000\000\000\014\000\000\000\004\000\000\000\002\206\000\000" + "\000\000\020\000\000\000\001\000\000\000\004\020\020\020\013\000\000\000\017\067\067\067!\354\354\354\305\350" + "\350\350\271vvvR\000\000\000)\014\014\014\064\010\010\010\067\003\003\003;\000\000\000@\034\034\034X\366" + "\366\366\326\236\236\236\364\202\000\000\000\377\011\012\012\012\374\326\326\326\363" + "\330\330\330\267\000\000\000>\010\010\010-\003\003\003\033\000\000\000\015\000\000\000\005\000\000\000\001\206\000" + "\000\000\000\021\000\000\000\001\000\000\000\003\000\000\000\007\004\004\004\015\000\000\000\020\060\060\060\036\025\025\025" + "\034\000\000\000\031\021\021\021$\004\004\004!\000\000\000\"\001\001\001'\017\017\017\062\000\000\000\065\267\267" + "\267\230\360\360\360\363$$$\371\202\000\000\000\377\010ooo\365\377\377\377\345^" + "^^a\000\000\000+\015\015\015\037\000\000\000\020\000\000\000\007\000\000\000\002\207\000\000\000\000\007\000\000\000\002\000\000" + "\000\005\000\000\000\010\003\003\003\014\000\000\000\015\021\021\021\023\027\027\027\026\202\000\000\000\023\022\000" + "\000\000\024\000\000\000\030\010\010\010\"\000\000\000,:::P\374\374\374\332}}}\366\000\000\000\377\001" + "\001\001\377\023\023\023\372\337\337\337\364\310\310\310\231\000\000\000)\025\025\025\"\002" + "\002\002\022\000\000\000\007\000\000\000\002\000\000\000\001\206\000\000\000\000\032\000\000\000\002\000\000\000\003\000\000\000" + "\004\000" + "\000\000\007\022\022\022\011\012\012\012\013\000\000\000\013\000\000\000\012\000\000\000\011\000\000\000\012\000\000\000" + "\016\001\001\001\026\022\022\022%\000\000\000,\320\320\320\240\336\336\336\365\021\021\021\372" + "\000\000\000\377\000\000\000\376\213\213\213\363\374\374\374\330;;;A\000\000\000\034\016\016\016" + "\023\000\000\000\010\000\000\000\002\207\000\000\000\000\001\000\000\000\001\202\000\000\000\002\001\000\000\000\003\206\000\000" + "\000\004\020\000\000\000\010\000\000\000\017\020\020\020\034\000\000\000#rrrY\377\377\377\345nnn\361\065" + "\065\065\370\246\246\246\365\346\346\346\365\361\361\361\343xxxK\000\000\000\030\026" + "\026\026\022\000\000\000\010\000\000\000\002\210\000\000\000\000\210\000\000\000\001\022\000\000\000\002\000\000\000\004\000\000" + "\000\012\004\004\004\023\014\014\014\040\000\000\000%\335\335\335\257\365\365\365\377\366\366" + "\366\351\370\370\370\312\305\305\305\220lllM\000\000\000$\003\003\003\031\002\002\002\016\000\000" + "\000\007\000\000\000\003\000\000\000\001\217\000\000\000\000\021\000\000\000\001\000\000\000\002\000\000\000\006\000\000\000\015" + "\016\016" + "\016\026\000\000\000\031~~~B\317\317\317\216\225\225\225c\002\002\002\065\000\000\000$\002\002\002\040" + "\001\001\001\034\000\000\000\023\000\000\000\013\000\000\000\005\000\000\000\002\221\000\000\000\000\020\000\000\000\001\000\000" + "\000" + "\004\000\000\000\010\000\000\000\015\003\003\003\025\006\006\006\026\000\000\000\022\000\000\000\033\001\001\001\"\026\026\026" + "\"\020\020\020\034\000\000\000\024\000\000\000\015\000\000\000\010\000\000\000\004\000\000\000\001\221\000\000\000\000\020" + "\000\000\000\001\000\000\000\002\000\000\000\004\001\001\001\010\000\000\000\014\025\025\025\021---\027\031\031\031\030" + "\006\006\006\026\001\001\001\024\000\000\000\021\000\000\000\015\000\000\000\011\000\000\000\005\000\000\000\003\000\000\000\001" + "\222" + "\000\000\000\000\006\000\000\000\001\000\000\000\003\000\000\000\004\000\000\000\006\000\000\000\010\001\001\001\013\203\000\000" + "\000\013" + "\005\000\000\000\011\000\000\000\007\000\000\000\005\000\000\000\003\000\000\000\001\224\000\000\000\000\003\000\000\000\001\000" + "\000\000\002" + "\000\000\000\003\206\000\000\000\004\003\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000", +}; + +static const struct pointer_icon pointer_arrow_xhdpi = { + .kind = POINTER_KIND_BASIC, + .width = 44, + .height = 56, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_XHDPI, + .hot_x = 10, + .hot_y = 10, + .rle_pixel_data = + "\262\000\000\000\000\210\000\000\000\001\241\000\000\000\000\204\000\000\000\001\207\000\000\000\002\203\000\000\000\001" + "\236\000\000\000\000\001\000\000\000\001\202\000\000\000\002\202\000\000\000\003\003\003\003\003\004\002\002\002\004\000\000\000" + "\005" + "\202\000\000\000\004\203\000\000\000\003\002\000\000\000\002\000\000\000\001\234\000\000\000\000\002\000\000\000\001\000\000\000" + "\002" + "\202\000\000\000\003\015\000\000\000\004\002\002\002\005DDD\011BBB\012\023\023\023\011\000\000\000\010\001\001\001\007" + "\000\000\000\007\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\233\000\000\000\000\022\000\000" + "\000\001\000" + "\000\000\002\000\000\000\003\000\000\000\005\003\003\003\007\017\017\017\011\000\000\000\000\000\000\000\002\000\000\000\015\010" + "\010" + "\010\016\000\000\000\014\000\000\000\013\000\000\000\011\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\002\000\000" + "\000" + "\001\232\000\000\000\000\021\000\000\000\001\000\000\000\003\000\000\000\005\002\002\002\010\012\012\012\013\000\000\000\011\323" + "\323\323R\316\316\316V\020\020\020\024\006\006\006\023\025\025\025\025\000\000\000\021\001\001\001\020" + "\000\000\000\014\000\000\000\012\000\000\000\007\000\000\000\004\202\000\000\000\002\001\000\000\000\001\230\000\000\000\000\025" + "\000" + "\000\000\002\000\000\000\004\000\000\000\007\026\026\026\013\000\000\000\011\230\230\230*\361\361\361\346" + "\364\364\364\364\341\341\341\233\005\005\005\034\000\000\000\030\016\016\016\034\000\000\000\027\000" + "\000\000\024\000\000\000\020\000\000\000\014\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\226" + "\000" + "\000\000\000\027\000\000\000\001\000\000\000\002\000\000\000\005\000\000\000\011\031\031\031\017\000\000\000\014\260\260\260" + "B\362\362\362\344\357\357\357\347\366\366\366\366\333\333\333\245JJJ.\000\000" + "\000!\023\023\023$\000\000\000\035\000\000\000\030\000\000\000\022\000\000\000\014\000\000\000\010\000\000\000\005\000\000\000" + "\003\000\000\000\002\000\000\000\001\225\000\000\000\000\030\000\000\000\001\000\000\000\002\000\000\000\006\000\000\000\013\024" + "\024" + "\024\022\000\000\000\021\234\234\234F\366\366\366\345\314\314\314\354\335\335\335" + "\346\375\375\375\364\334\334\334\270???;\000\000\000'\015\015\015*\000\000\000!\000\000\000\033" + "\000\000\000\023\000\000\000\015\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\224\000\000\000" + "\000\031" + "\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\015\021\021\021\025\000\000\000\026\220\220\220K\373\373" + "\373\345\271\271\271\356---\372\310\310\310\351\377\377\377\360\332\332\332" + "\302TTTN\000\000\000.\021\021\021\061\000\000\000%\000\000\000\033\000\000\000\023\000\000\000\016\000\000\000\011\000" + "\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\223\000\000\000\000\032\000\000\000\001\000\000\000\003\000\000\000" + "\010\000" + "\000\000\017\017\017\017\030\000\000\000\032\210\210\210P\371\371\371\345\302\302\302\355" + "\000\000\000\377\032\032\032\374\301\301\301\351\377\377\377\360\342\342\342\322\\" + "\\\\X\000\000\000\063\021\021\021\063\001\001\001&\000\000\000\034\000\000\000\024\000\000\000\016\000\000\000\011\000\000" + "\000\006\000\000\000\003\000\000\000\002\000\000\000\001\222\000\000\000\000\033\000\000\000\001\000\000\000\004\000\000\000\010" + "\000\000" + "\000\020\015\015\015\033\000\000\000\037\200\200\200U\371\371\371\345\277\277\277\356\005" + "\005\005\376\000\000\000\377\021\021\021\375\252\252\252\354\377\377\377\354\340\340\340" + "\327}}}q\000\000\000\070\015\015\015\065\001\001\001'\000\000\000\035\000\000\000\025\000\000\000\017\000\000\000\012" + "\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\221\000\000\000\000\034\000\000\000\001\000\000\000\004\000\000" + "\000\011" + "\000\000\000\021\015\015\015\034\000\000\000\"{{{X\371\371\371\346\277\277\277\355\002\002\002\376" + "\002\002\002\377\000\000\000\377\016\016\016\376\250\250\250\354\377\377\377\354\352\352" + "\352\344~~~v\000\000\000\071\020\020\020\066\002\002\002(\000\000\000\036\000\000\000\025\000\000\000\017\000\000\000" + "\012\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\220\000\000\000\000\033\000\000\000\001\000\000\000\004\000" + "\000\000" + "\012\000\000\000\022\014\014\014\036\000\000\000%www[\371\371\371\346\277\277\277\355\003\003\003" + "\376\000\000\000\377\002\002\002\377\000\000\000\377\004\004\004\377\223\223\223\357\377\377\377" + "\351\353\353\353\345\220\220\220\202\000\000\000<\013\013\013\066\004\004\004*\000\000\000\036\000" + "\000\000\026\000\000\000\017\000\000\000\012\000\000\000\006\000\000\000\003\202\000\000\000\002\001\000\000\000\001\216\000\000" + "\000" + "\000\012\000\000\000\001\000\000\000\004\000\000\000\012\000\000\000\023\013\013\013\037\000\000\000'sss^\370\370\370" + "\346\277\277\277\355\003\003\003\376\202\000\000\000\377\023\002\002\002\377\000\000\000\377\005\005\005" + "\377\220\220\220\357\377\377\377\351\362\362\362\355\226\226\226\210\000\000" + "\000<\012\012\012\067\003\003\003+\000\000\000\040\000\000\000\027\000\000\000\017\000\000\000\012\000\000\000\007\000\000\000" + "\004\000\000\000\003\000\000\000\002\000\000\000\001\215\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\012\000" + "\000\000" + "\023\013\013\013\040\000\000\000(rrr_\370\370\370\346\277\277\277\355\003\003\003\376\203" + "\000\000\000\377\001\001\001\001\377\202\000\000\000\377\020www\362\377\377\377\350\363\363\363" + "\354\243\243\243\221\000\000\000@\012\012\012\067\010\010\010,\000\000\000\040\000\000\000\030\000\000\000" + "\020\000\000\000\013\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\214\000\000\000\000\012\000" + "\000\000" + "\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013\040\000\000\000)pppa\370\370\370\346\277\277" + "\277\355\003\003\003\376\204\000\000\000\377\023\001\001\001\377\002\002\002\377\000\000\000\377ttt\362" + "\371\371\371\350\367\367\367\356\252\252\252\233\000\000\000@\001\001\001\067\004\004\004-\000" + "\000\000!" + "\000\000\000\030\000\000\000\021\000\000\000\013\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001" + "\213\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013\040\000\000\000*pppa" + "\370\370\370\346\277\277\277\355\003\003\003\376\205\000\000\000\377\023\001\001\001\377\002\002" + "\002\377\000\000\000\377QQQ\366\363\363\363\350\370\370\370\355\266\266\266\245\016" + "\016\016E\007\007\007\067\015\015\015.\000\000\000!\000\000\000\031\000\000\000\021\000\000\000\014\000\000\000\010\000\000" + "\000\004\000\000\000\003\000\000\000\002\000\000\000\001\212\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013" + "\000\000" + "\000\024\013\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\207" + "\000\000\000\377\022\002\002\002\377\000\000\000\377MMM\366\352\352\352\347\375\375\375\361" + "\301\301\301\261\000\000\000E\000\000\000\070\006\006\006/\000\000\000#\000\000\000\031\000\000\000\022\000\000\000\014" + "\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\211\000\000\000\000\012\000\000\000\001\000\000" + "\000\004" + "\000\000\000\013\000\000\000\024\013\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003" + "\003\003\376\210\000\000\000\377\022\004\004\004\377\000\000\000\377\066\066\066\371\336\336\336\350" + "\376\376\376\356\312\312\312\273\063\063\063U\000\000\000\067\017\017\017\061\000\000\000#\000" + "\000\000\032\000\000\000\023\000\000\000\015\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\210" + "\000" + "\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000\000\000*ooob\370\370" + "\370\346\277\277\277\355\003\003\003\376\211\000\000\000\377\022\003\003\003\377\000\000\000\377\064" + "\064\064\371\333\333\333\350\377\377\377\360\326\326\326\310\070\070\070U\000\000" + "\000\070\012\012\012\061\000\000\000$\000\000\000\033\000\000\000\023\000\000\000\015\000\000\000\011\000\000\000\005\000\000" + "\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024" + "\013" + "\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\212\000\000\000\377" + "\022\003\003\003\377\000\000\000\377$$$\374\320\320\320\350\377\377\377\355\332\332\332" + "\317RRRb\000\000\000\070\017\017\017\063\000\000\000&\000\000\000\034\000\000\000\024\000\000\000\015\000\000\000\011" + "\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\206\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000" + "\000\013" + "\000\000\000\024\013\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376" + "\213\000\000\000\377\022\003\003\003\377\000\000\000\377\040\040\040\374\301\301\301\351\377\377" + "\377\357\341\341\341\327XXXc\000\000\000\071\015\015\015\064\001\001\001&\000\000\000\035\000\000\000\024" + "\000\000\000\016\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\205\000\000\000\000\012\000\000" + "\000\001" + "\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000\000\000*ooob\370\370\370\346\277\277\277" + "\355\003\003\003\376\214\000\000\000\377\022\002\002\002\377\000\000\000\377\023\023\023\375\266\266" + "\266\352\377\377\377\355\344\344\344\333nnno\000\000\000\071\016\016\016\065\000\000\000'" + "\000\000\000\035\000\000\000\025\000\000\000\016\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001" + "\204" + "\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000\000\000*ooob\370\370" + "\370\346\277\277\277\355\003\003\003\376\215\000\000\000\377\022\003\003\003\377\000\000\000\377\021" + "\021\021\376\246\246\246\354\377\377\377\354\350\350\350\337vvvr\000\000\000\067\022" + "\022\022\064\003\003\003'\000\000\000\034\000\000\000\024\000\000\000\015\000\000\000\011\000\000\000\005\000\000\000\003\000\000" + "\000\002\000\000\000\001\203\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000" + "\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\216\000\000\000\377\001\001\001\001" + "\377\202\000\000\000\377\016\217\217\217\357\377\377\377\352\353\353\353\343\221" + "\221\221~\000\000\000\063\015\015\015\061\003\003\003$\000\000\000\031\000\000\000\022\000\000\000\013\000\000\000\007" + "\000\000\000\004\000\000\000\002\000\000\000\001\203\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000" + "\000\024" + "\013\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\217\000\000\000" + "\377\202\002\002\002\377\016\004\004\004\377\203\203\203\360\376\376\376\351\361\361\361" + "\352\232\232\232~\000\000\000,\017\017\017+\003\003\003\037\000\000\000\025\000\000\000\016\000\000\000\011\000" + "\000\000\005\000\000\000\003\000\000\000\001\203\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000" + "\024\013" + "\013\013!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\220\000\000\000\377" + "\001\001\001\001\377\202\000\000\000\377\013lll\362\375\375\375\350\364\364\364\351\275" + "\275\275\220\000\000\000+\011\011\011\"\005\005\005\031\000\000\000\020\000\000\000\012\000\000\000\006\000\000\000\003" + "\202\000\000\000\001\202\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000" + "\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\217\000\000\000\377\202\001\001" + "\001\377\017\003\003\003\377\005\005\005\376\001\001\001\377jjj\363\373\373\373\347\372\372\372" + "\361\306\306\306\221\000\000\000#\013\013\013\033\007\007\007\023\000\000\000\014\000\000\000\007\000\000\000\004" + "\000\000\000\002\000\000\000\001\202\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013" + "\013" + "!\000\000\000*ooob\370\370\370\346\277\277\277\355\003\003\003\376\214\000\000\000\377\001\002" + "\002\002\377\210\000\000\000\377\013OOO\366\341\341\341\347\366\366\366\357\320\320" + "\320\216\000\000\000\030\014\014\014\025\003\003\003\015\000\000\000\010\000\000\000\004\000\000\000\002\000\000\000\001\202" + "\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\013\000\000\000\024\013\013\013!\000\000\000*ooob\370\370" + "\370\346\277\277\277\355\003\003\003\376\216\000\000\000\377\022\006\006\006\376\015\015\015\375" + "'''\372AAA\367ccc\363\200\200\200\360\240\240\240\354\247\247\247\357\331" + "\331\331\354\371\371\371\360\361\361\361\352vvv\064\000\000\000\021\021\021\021\016\000" + "\000\000\010\000\000\000\005\000\000\000\002\000\000\000\001\202\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000" + "\013\000" + "\000\000\024\013\013\013\040\000\000\000*pppa\370\370\370\346\277\277\277\355\003\003\003\376" + "\206\000\000\000\377\002\002\002\002\377\003\003\003\377\202\000\000\000\377\026\001\001\001\377\000\000\000\377" + "@@@\371\253\253\253\356\321\321\321\354\352\352\352\352\361\361\361\353\370" + "\370\370\354\377\377\377\356\377\377\377\361\377\377\377\362\377\377\377" + "\346\362\362\362\331\351\351\351\317\334\334\334\227///\037\006\006\006\022\007\007\007" + "\015\000\000\000\010\000\000\000\004\000\000\000\002\000\000\000\001\202\000\000\000\000\012\000\000\000\001\000\000\000\004\000" + "\000\000" + "\013\000\000\000\024\013\013\013\040\000\000\000*pppa\370\370\370\346\277\277\277\355\003\003\003" + "\376\205\000\000\000\377\001\002\002\002\377\203\000\000\000\377\027\001\001\001\377\000\000\000\377\012\012" + "\012\376\313\313\313\353\377\377\377\346\357\357\357\345\354\354\354\343\331" + "\331\331\327\307\307\307\307\260\260\260\261\236\236\236\232\210\210\210" + "\200wwwikkkRVVV;\000\000\000\030\000\000\000\025\006\006\006\021\000\000\000\013\000\000\000\007\000\000\000\004\000\000" + "\000\002\000\000\000\001\202\000\000\000\000\012\000\000\000\001\000\000\000\004\000\000\000\012\000\000\000\023\013\013\013\040" + "\000\000\000)qqq`\370\370\370\346\277\277\277\355\003\003\003\376\203\000\000\000\377\001\002\002" + "\002\377\202\000\000\000\377\004$$$\373\065\065\065\371\003\003\003\377\001\001\001\377\202\002\002\002" + "\377\024\221\221\221\357\375\375\375\354\225\225\225\302\013\013\013\214\011\011" + "\011\200\000\000\000n\000\000\000[\000\000\000J\000\000\000\071\000\000\000/\000\000\000'\000\000\000\037\022\022\022\035" + "\002\002\002\024\000\000\000\016\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\202\000\000\000" + "\000\015" + "\000\000\000\001\000\000\000\004\000\000\000\012\000\000\000\023\013\013\013\037\000\000\000'sss^\371\371\371\346" + "\277\277\277\355\003\003\003\376\000\000\000\377\001\001\001\377\003\003\003\377\202\000\000\000\377\031" + "nnn\363\343\343\343\351\373\373\373\347EEE\370\000\000\000\377\004\004\004\377\000\000\000" + "\377...\372\364\364\364\352\343\343\343\337\033\033\033\211\000\000\000r\002\002\002d\004" + "\004\004U\011\011\011H\017\017\017<\017\017\017\060\016\016\016&\015\015\015\035\003\003\003\026\000\000" + "\000\020\000\000\000\013\000\000\000\010\000\000\000\005\000\000\000\003\202\000\000\000\001\202\000\000\000\000\024\000\000\000" + "\001\000\000\000\004\000\000\000\011\000\000\000\022\014\014\014\036\000\000\000%vvv\\\371\371\371\346\277" + "\277\277\355\002\002\002\376\002\002\002\377\001\001\001\377\000\000\000\377###\373\262\262\262\354" + "\377\377\377\352\361\361\361\350\376\376\376\347\247\247\247\356\002\002\002\377" + "\202\001\001\001\377\020\000\000\000\377\237\237\237\357\377\377\377\353\210\210\210\251" + "\000\000\000g\007\007\007]\001\001\001L\000\000\000>\000\000\000\061\000\000\000&" + "\000\000\000\035\000\000\000\026\000\000\000\020\000" + "\000\000\014\000\000\000\011\000\000\000\006\202\000\000\000\003\001\000\000\000\001\203\000\000\000\000\012\000\000\000\001\000" + "\000" + "\000\004\000\000\000\011\000\000\000\021\015\015\015\034\000\000\000\"zzzY\371\371\371\346\277\277\277" + "\356\005\005\005\376\202\000\000\000\377\035ccc\364\336\336\336\351\377\377\377\356\326" + "\326\326\332ZZZ\247\326\326\326\331\363\363\363\353---\372\000\000\000\377\004\004" + "\004\377\000\000\000\377;;;\367\363\363\363\355\326\326\326\317\021\021\021k\003\003\003V" + "\004\004\004G\000\000\000\070\000\000\000+\000\000\000\040\000\000\000\027\000\000\000\020\000\000\000\014\000\000\000\011\000\000" + "\000\006\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\203\000\000\000\000'" + "\000\000\000\001\000\000\000\004\000\000\000\010" + "\000\000\000\020\016\016\016\032\000\000\000\037\177\177\177U\371\371\371\345\300\300\300\355" + "\000\000\000\377\032\032\032\374\235\235\235\355\377\377\377\352\367\367\367\353\263" + "\263\263\272&&&\204\000\000\000t\212\212\212\256\377\377\377\355\220\220\220\357" + "\000\000\000\377\003\003\003\377\001\001\001\377\002\002\002\377\260\260\260\355\376\376\376\353" + "vvv\216\000\000\000N\012\012\012E\000\000\000\064\000\000\000'\000\000\000\033\000\000\000\022\000\000\000\014\000\000\000" + "\010\000\000\000\005\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\203\000\000\000\000%" + "\000\000\000\001\000\000" + "\000\003\000\000\000\010\000\000\000\017\017\017\017\030\000\000\000\033\206\206\206P\373\373\373\345" + "\267\267\267\356QQQ\366\331\331\331\351\377\377\377\357\346\346\346\335\207" + "\207\207\217\000\000\000]\000\000\000f\006\006\006h///|\353\353\353\334\342\342\342\355\040" + "\040\040\373\000\000\000\377\005\005\005\376\000\000\000\377PPP\365\375\375\375\355\315\315\315" + "\302\004\004\004T\002\002\002C\001\001\001\063\000\000\000%" + "\000\000\000\031\000\000\000\020\000\000\000\012\000\000\000\006\000\000" + "\000\003\000\000\000\002\203\000\000\000\001\204\000\000\000\000%" + "\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\015\022" + "\022\022\025\000\000\000\025\226\226\226N\364\364\364\344\324\324\324\351\362\362\362" + "\352\376\376\376\360\316\316\316\264EEE]\000\000\000G\012\012\012Q\004\004\004T\005\005\005Y\000" + "\000\000Y\266\266\266\263\377\377\377\354zzz\361\000\000\000\377\002\002\002\377\000\000\000\377" + "\020\020\020\375\315\315\315\353\370\370\370\351mmmy\002\002\002@\011\011\011\066\000\000" + "\000&\000\000\000\031\000\000\000\020\000\000\000\010\000\000\000\004\000\000\000\002\000\000\000\001\207\000\000\000\000$" + "\000\000\000" + "\001\000\000\000\002\000\000\000\005\000\000\000\012\015\015\015\021\000\000\000\023ooo\061\361\361\361\352\370" + "\370\370\366\356\356\356\332\262\262\262\201\000\000\000\067\000\000\000\070\013\013\013>" + "\001\001\001>\000\000\000B\006\006\006H\001\001\001IZZZr\365\365\365\346\332\332\332\353\026\026\026" + "\375\000\000\000\377\003\003\003\377\000\000\000\377lll\362\377\377\377\355\307\307\307\267" + "\000\000\000E\005\005\005\070\003\003\003(\000\000\000\033\000\000\000\021\000\000\000\011\000\000\000\004\000\000\000\002\211\000" + "\000\000\000#\000\000\000\002\000\000\000\004\000\000\000\010\003\003\003\015\000\000\000\022\000\000\000\024\330\330\330\201" + "\331\331\331\224sssC\000\000\000\"\010\010\010,\006\006\006-\000\000\000-\000\000\000.\000\000\000\062\001\001" + "\001\070\002\002\002?\002\002\002H\312\312\312\267\377\377\377\357eee\363\000\000\000\377\004\004" + "\004\377\000\000\000\377\030\030\030\374\326\326\326\354\364\364\364\344fffm\002\002\002\066" + "\015\015\015-\000\000\000\036\000\000\000\023\000\000\000\013\000\000\000\005\000\000\000\002\211\000\000\000\000\013\000\000" + "\000\002\000\000\000\003\000\000\000\007\000\000\000\012\000\000\000\016\006\006\006\022\000\000\000\012\000\000\000\015\000\000" + "\000\031" + "\020\020\020!\004\004\004\040\202\000\000\000\037\027\000\000\000!\000\000\000$\000\000\000*\014\014\014\064\000\000" + "\000\067}}}w\372\372\372\351\306\306\306\354\012\012\012\375\001\001\001\377\003\003\003\377" + "\000\000\000\377\201\201\201\360\377\377\377\361\272\272\272\242\000\000\000\070\010\010" + "\010\060\001\001\001!\000\000\000\025\000\000\000\014\000\000\000\006\000\000\000\003\000\000\000\001\210\000\000\000\000\013\000" + "\000\000\001\000\000\000\003\000\000\000\004\000\000\000\007\000\000\000\012\001\001\001\015***\022)))\026\015\015\015\026" + "\001\001\001\025\000\000\000\025\202\000\000\000\024\027\000\000\000\026\000\000\000\031\000\000\000\037\005\005\005(\006\006" + "\006\063\007\007\007?\325\325\325\275\372\372\372\357FFF\366\000\000\000\377\004\004\004\377\001" + "\001\001\377!!!\373\353\353\353\353\353\353\353\327\061\061\061K\001\001\001.\005\005\005\"" + "\000\000\000\027\000\000\000\015\000\000\000\006\000\000\000\003\000\000\000\001\210\000\000\000\000\010\000\000\000\001\000\000" + "\000\002" + "\000\000\000\003\000\000\000\005\000\000\000\007\000\000\000\011\000\000\000\012\000\000\000\014\202\000\000\000\016\001\000\000" + "\000" + "\015\202\000\000\000\014\027\000\000\000\016\000\000\000\021\000\000\000\026\000\000\000\036\017\017\017+\001\001\001/" + "\220\220\220y\375\375\375\353\247\247\247\356\000\000\000\377\002\002\002\377\004\004\004\377" + "\000\000\000\377\202\202\202\361\377\377\377\355\254\254\254\205\000\000\000&\023\023\023" + "$\000\000\000\027\000\000\000\016\000\000\000\007\000\000\000\003\000\000\000\001\210\000\000\000\000\202\000\000\000\001\005\000" + "\000" + "\000\002\000\000\000\003\000\000\000\004\000\000\000\005\000\000\000\006\204\000\000\000\010\202\000\000\000\007\012\000\000\000" + "\010" + "\000\000\000\013\000\000\000\020\000\000\000\030\003\003\003\"\001\001\001-&&&B\345\345\345\314\366\366\366" + "\354\071\071\071\370\202\000\000\000\377\013\027\027\027\373rrr\357\374\374\374\360\341" + "\341\341\303!!!\061\007\007\007\040\011\011\011\027\000\000\000\016\000\000\000\007\000\000\000\003\000\000\000\001" + "\211\000\000\000\000\001\000\000\000\001\203\000\000\000\002\203\000\000\000\003\202\000\000\000\004\203\000\000\000\003\027" + "\000\000\000\004\000\000\000\007\000\000\000\014\000\000\000\022\001\001\001\033\015\015\015'\000\000\000)\262\262\262" + "\211\377\377\377\360\233\233\233\354+++\371\211\211\211\357\324\324\324\355" + "\373\373\373\357\364\364\364\363\340\340\340\270###.\006\006\006\036\012\012\012\026" + "\000\000\000\015\000\000\000\007\000\000\000\003\000\000\000\001\212\000\000\000\000\203\000\000\000\001\211\000\000\000\002\026" + "\000\000\000\004\000\000\000\010\000\000\000\016\000\000\000\025\017\017\017\037\002\002\002$PPPE\354\354\354\327" + "\363\363\363\360\355\355\355\354\377\377\377\362\366\366\366\340\327\327" + "\327\265\250\250\250tDDD<\000\000\000\"\006\006\006\034\000\000\000\022\000\000\000\013\000\000\000\006\000\000" + "\000\003\000\000\000\001\225\000\000\000\000\016\000\000\000\001\000\000\000\002\000\000\000\006\000\000\000\012\000\000\000\020" + "\001\001" + "\001\027\014\014\014!\000\000\000\040\305\305\305\203\361\361\361\346\347\347\347\306" + "\262\262\262\205pppX\000\000\000\062\202\000\000\000%\007\002\002\002\036\000\000\000\026\000\000\000\017\000\000" + "\000\011\000\000\000\005\000\000\000\003\000\000\000\001\226\000\000\000\000\026\000\000\000\002\000\000\000\003\000\000\000\007" + "\000\000" + "\000\013\000\000\000\021\003\003\003\027\010\010\010\035\000\000\000!;;;\063!!!\062\000\000\000$\000\000\000%\011\011" + "\011*\017\017\017&\005\005\005\037\000\000\000\030\000\000\000\022\000\000\000\014\000\000\000\010\000\000\000\004\000\000\000" + "\002\000\000\000\001\226\000\000\000\000\025\000\000\000\001\000\000\000\003\000\000\000\005\000\000\000\010\000\000\000\014\000" + "\000\000" + "\021\000\000\000\025\005\005\005\032\000\000\000\033\000\000\000\037\021\021\021$\021\021\021#\004\004\004\036\000\000" + "\000\033\000\000\000\026\000\000\000\022\000\000\000\015\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\230\000" + "\000" + "\000\000\024\000\000\000\002\000\000\000\003\000\000\000\005\000\000\000\010\000\000\000\013\000\000\000\016\002\002\002\022\007" + "\007\007" + "\025\005\005\005\027\001\001\001\027\000\000\000\027\000\000\000\025\000\000\000\023\000\000\000\020\000\000\000\015\000\000\000" + "\012\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\230\000\000\000\000\010\000\000\000\001\000\000\000\002\000" + "\000\000" + "\003\000\000\000\005\000\000\000\007\000\000\000\011\000\000\000\013\000\000\000\015\203\000\000\000\017\011\000\000\000\016\000" + "\000\000\014\000\000\000\013\000\000\000\011\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\231" + "\000\000" + "\000\000\006\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\005\000\000\000\007\205\000\000\000\010" + "\003\000\000" + "\000\007\000\000\000\006\000\000\000\005\202\000\000\000\003\002\000\000\000\002\000\000\000\001\233\000\000\000\000\001\000\000" + "\000\001" + "\203\000\000\000\002\001\000\000\000\003\205\000\000\000\004\202\000\000\000\003\202\000\000\000\002\202\000\000\000\001\211" + "\000\000\000\000", +}; -const struct cursor_icon cursors[5] = { - { - .hot_x = 8, - .hot_y = 0, - .width = 32, - .height = 32, - .data = (uint32_t*) cursor_32x32_data - }, - { +static const struct pointer_icon pointer_arrow_xxhdpi = { + .kind = POINTER_KIND_BASIC, + .width = 66, + .height = 84, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_XXHDPI, + .hot_x = 15, + .hot_y = 15, + .rle_pixel_data = + "\377\377\377\377\000\207\377\377\377\000\202\000\000\000\000\204\362\362\362\000\216\000" + "\000\000\001\236\362\362\362\000\214\000\000\000\000\204\377\377\377\000\202\000\000\000\000\001\362" + "\362\362\000\206\000\000\000\001\211\000\000\000\002\206\000\000\000\001\232\362\362\362\000\214\000\000" + "\000\000\204\377\377\377\000\202\000\000\000\000\204\000\000\000\001\203\000\000\000\002\212\000\000\000\003\203" + "\000\000\000\002\203\000\000\000\001\231\362\362\362\000\214\000\000\000\000\204\377\377\377\000\202" + "\000\000\000\000\202\000\000\000\001\202\000\000\000\002\204\000\000\000\003\202\000\000\000\004\204\000\000\000\005\202" + "\000\000\000\004\204\000\000\000\003\202\000\000\000\002\202\000\000\000\001\230\362\362\362\000\214\000\000\000" + "\000\204\377\377\377\000\001\000\000\000\000\202\000\000\000\001\202\000\000\000\002\202\000\000\000\003\202\000" + "\000\000\004\002\000\000\000\005\000\000\000\006\202\000\000\000\007\202\000\000\000\010\202\000\000\000\007\202\000\000\000\006" + "\002\000\000\000\005\000\000\000\004\203\000\000\000\003\001\000\000\000\002\202\000\000\000\001\227\362\362\362\000\214" + "\000\000\000\000\204\377\377\377\000\001\000\000\000\000\202\000\000\000\001\001\000\000\000\002\202\000\000\000\003\004" + "\000\000\000\004\000\000\000\005\000\000\000\006\000\000\000\007\202\000\000\000\011\204\000\000\000\012\203\000\000\000\011\004" + "\000\000\000\010\000\000\000\007\000\000\000\006\000\000\000\004\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\226" + "\362" + "\362\362\000\214\000\000\000\000\204\377\377\377\000\003\000\000\000\000\000\000\000\001\000\000\000\002\202\000" + "\000\000\003\003\000\000\000\004\000\000\000\006\000\000\000\007\202\000\000\000\011\003\000\000\000\013\000\000\000\014\000\000" + "\000\016" + "\203\000\000\000\017\013\000\000\000\016\000\000\000\015\000\000\000\014\000\000\000\012\000\000\000\011\000\000\000\010\000" + "\000\000\007\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\225\362\362\362\000\214" + "\000\000\000\000\204\377\377\377\000\015\000\000\000\000\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000" + "\000\006\000\000\000\010\000\000\000\011\000\000\000\013\000\000\000\015\000\000\000\017\273\273\273\071\000\000\000\022" + "\203\000\000\000\023\013\000\000\000\022\000\000\000\021\000\000\000\020\000\000\000\017\000\000\000\015\000\000\000\013\000" + "\000\000\011\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\202\000\000\000\002\202\000\000\000\001\223\362" + "\362\362\000\214\000\000\000\000\204\377\377\377\000\002\000\000\000\000\000\000\000\001\202\000\000\000\003\012" + "\000\000\000\005\000\000\000\010\000\000\000\011\000\000\000\013\000\000\000\016\000\000\000\017\354\354\354\267\362" + "\362\362\350\353\353\353\270\277\277\277Q\202\000\000\000\031\013\000\000\000\030\000\000\000" + "\027\000\000\000\025\000\000\000\024\000\000\000\021\000\000\000\017\000\000\000\015\000\000\000\013\000\000\000\011\000\000\000" + "\007\000\000\000\004\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\222\362\362\362\000\214\000\000\000" + "\000\204\377\377\377\000\012\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\007\000\000\000\011" + "\000\000\000\013\000\000\000\016\000\000\000\020\261\261\261<\204\362\362\362\350\016\264\264\264" + "V\000\000\000\037\000\000\000\036\000\000\000\035\000\000\000\033\000\000\000\031\000\000\000\027\000\000\000\025\000\000\000\022" + "\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000" + "\000" + "\000\001\221\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\002\000" + "\000\000\003\000\000\000\005\000\000\000\010\000\000\000\012\000\000\000\016\000\000\000\021\000\000\000\024\273\273\273K" + "\205\362\362\362\350\016\250\250\250\\\000\000\000&\000\000\000$\000\000\000\"\000\000\000\040\000\000" + "\000\035\000\000\000\033\000\000\000\027\000\000\000\024\000\000\000\020\000\000\000\014\000\000\000\011\000\000\000\007\000\000" + "\000" + "\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\220\362\362\362\000\214\000\000\000\000\204\377" + "\377\377\000\012\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\006\000\000\000\011\000\000\000\014\000\000\000\020" + "\000\000\000\024\000\000\000\030\264\264\264N\202\362\362\362\350\001\327\327\327\352\203" + "\362\362\362\350\016\304\304\304\204\000\000\000-\000\000\000+\000\000\000(\000\000\000%\000\000\000!\000" + "\000\000\035\000\000\000\031\000\000\000\025\000\000\000\020\000\000\000\015\000\000\000\011\000\000\000\007\000\000\000\005\202" + "\000\000\000\003\001\000\000\000\002\202\000\000\000\001\217\362\362\362\000\214\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\002\000\000\000\004\000\000\000\007\000\000\000\012\000\000\000\016\000\000\000\022\000" + "\000" + "\000\026\000\000\000\033\251\251\251S\202\362\362\362\350\002\262\262\262\356\327\327" + "\327\352\203\362\362\362\350\016\277\277\277\210\000\000\000\064\000\000\000\061\000\000\000-" + "\000\000\000)\000\000\000$\000\000\000\040\000\000\000\033\000\000\000\025\000\000\000\021\000\000\000\015\000\000\000\011\000\000" + "\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\216\362\362\362\000\214\000\000" + "\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\004\000\000\000\010\000\000\000\013\000\000\000" + "\017\000\000\000\025\000\000\000\032\000\000\000\037\243\243\243V\202\362\362\362\350\003\262\262" + "\262\356\021\021\021\375\247\247\247\357\203\362\362\362\350\016\303\303\303" + "\227\000\000\000:\000\000\000\066\000\000\000\062\000\000\000-\000\000\000(\000\000\000!\000\000\000\033\000\000\000\026\000\000" + "\000\021\000\000\000\016\000\000\000\012\000\000\000\010\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000" + "\001\215\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000" + "\000\005\000\000\000\011\000\000\000\015\000\000\000\021\000\000\000\026\000\000\000\034\000\000\000\"\236\236\236Y\202" + "\362\362\362\350\001\262\262\262\356\202\000\000\000\377\001\247\247\247\357\203\362" + "\362\362\350\021\327\327\327\271\000\000\000@\000\000\000;\000\000\000\066\000\000\000/\000\000\000)\000\000" + "\000\"\000\000\000\033\000\000\000\026\000\000\000\022\000\000\000\016\000\000\000\012\000\000\000\010\000\000\000\005\000\000\000" + "\004\000\000\000\003\000\000\000\002\202\000\000\000\001\214\362\362\362\000\214\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\005\000\000\000\011\000\000\000\016\000\000\000\023\000\000\000\031\000" + "\000" + "\000\037\000\000\000&\231\231\231\\\202\362\362\362\350\001\262\262\262\356\203\000\000" + "\000\377\001\227\227\227\360\203\362\362\362\350\021\326\326\326\273\000\000\000F\000" + "\000\000@\000\000\000\071\000\000\000\062\000\000\000*\000\000\000#" + "\000\000\000\034\000\000\000\027\000\000\000\022\000\000\000\016" + "\000\000\000\012\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\213\362\362" + "\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\006\000\000\000\011" + "\000\000\000\017\000\000\000\024\000\000\000\033\000\000\000!\000\000\000)\224\224\224_\202\362\362\362\350" + "\001\262\262\262\356\204\000\000\000\377\001jjj\364\203\362\362\362\350\021\324\324" + "\324\275VVVb\000\000\000C\000\000\000;\000\000\000\063\000\000\000+\000\000\000#\000\000\000\035\000\000\000\027\000\000\000" + "\022\000\000\000\017\000\000\000\013\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000" + "\000" + "\001\212\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000" + "\000\006\000\000\000\012\000\000\000\017\000\000\000\025\000\000\000\034\000\000\000$\000\000\000,\217\217\217b\202\362" + "\362\362\350\001\262\262\262\356\205\000\000\000\377\001jjj\364\203\362\362\362\350" + "\021\350\350\350\331TTTe\000\000\000E\000\000\000<\000\000\000\064\000\000\000+\000\000\000$\000\000\000\035\000\000" + "\000\030\000\000\000\023\000\000\000\017\000\000\000\013\000\000\000\011\000\000\000\006\000\000\000\004\000\000\000\003\000\000" + "\000\002" + "\202\000\000\000\001\211\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000" + "\000\000\003\000\000\000\006\000\000\000\012\000\000\000\020\000\000\000\026\000\000\000\035\000\000\000&\000\000\000.\214\214" + "\214d\202\362\362\362\350\001\262\262\262\356\206\000\000\000\377\001jjj\364\203\362" + "\362\362\350\021\350\350\350\331SSSg\000\000\000F\000\000\000=\000\000\000\064\000\000\000,\000\000\000%" + "\000\000\000\036\000\000\000\030\000\000\000\023\000\000\000\017\000\000\000\013\000\000\000\011\000\000\000\006\000\000\000\004" + "\000" + "\000\000\003\000\000\000\002\202\000\000\000\001\210\362\362\362\000\214\000\000\000\000\204\377\377\377" + "\000\012\000\000\000\001\000\000\000\003\000\000\000\006\000\000\000\013\000\000\000\020\000\000\000\027\000\000\000\037\000\000" + "\000(" + "\000\000\000\061\213\213\213e\202\362\362\362\350\001\262\262\262\356\207\000\000\000\377" + "\001???\371\203\362\362\362\350\020\350\350\350\331iiiq\000\000\000G\000\000\000>\000\000\000" + "\064\000\000\000-\000\000\000%\000\000\000\036\000\000\000\031\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000\011\000" + "\000\000\006\000\000\000\004\000\000\000\003\202\000\000\000\002\202\000\000\000\001\206\362\362\362\000\214\000\000" + "\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\013\000\000\000\021\000\000\000" + "\030\000\000\000\040\000\000\000(\000\000\000\063\207\207\207h\202\362\362\362\350\001\262\262\262" + "\356\210\000\000\000\377\001???\371\204\362\362\362\350\016~~~{\000\000\000H\000\000\000?\000\000" + "\000\065\000\000\000.\000\000\000&\000\000\000\037\000\000\000\031\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000\011" + "\000\000\000\007\000\000\000\004\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\205\362\362\362\000\214" + "\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\013\000\000\000\021\000" + "\000\000\030\000\000\000!\000\000\000*\000\000\000\064\205\205\205j\202\362\362\362\350\001\262\262" + "\262\356\211\000\000\000\377\002???\371\350\350\350\351\203\362\362\362\350\016~~" + "~{\000\000\000I\000\000\000@\000\000\000\066\000\000\000.\000\000\000'\000\000\000\040\000\000\000\032\000\000\000\025\000\000\000" + "\017\000\000\000\014\000\000\000\011\000\000\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001" + "\204" + "\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000" + "\000\000\013\000\000\000\021\000\000\000\031\000\000\000!\000\000\000+\000\000\000\065\205\205\205j\202\362\362" + "\362\350\001\262\262\262\356\212\000\000\000\377\002!!!\374\327\327\327\352\203\362" + "\362\362\350\016\216\216\216\205\000\000\000J\000\000\000@\000\000\000\067\000\000\000.\000\000\000'\000\000" + "\000\040\000\000\000\032\000\000\000\025\000\000\000\020\000\000\000\015\000\000\000\011\000\000\000\007\000\000\000\005\202\000" + "\000\000\003\001\000\000\000\002\202\000\000\000\001\203\362\362\362\000\214\000\000\000\000\204\377\377\377" + "\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000!\000\000\000+" + "\000" + "\000\000\066\203\203\203k\202\362\362\362\350\001\262\262\262\356\213\000\000\000\377" + "\002!!!\374\327\327\327\352\203\362\362\362\350\016\253\253\253\230\000\000\000K\000" + "\000\000@\000\000\000\070\000\000\000/" + "\000\000\000(\000\000\000!\000\000\000\033\000\000\000\025\000\000\000\020\000\000\000\015\000" + "\000\000\011\000\000\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\202\362\362\362" + "\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000" + "\000\022\000\000\000\031\000\000\000!\000\000\000,\000\000\000\067\202\202\202l\202\362\362\362\350\001" + "\262\262\262\356\214\000\000\000\377\002!!!\374\307\307\307\354\203\362\362\362\350" + "\016\251\251\251\231\000\000\000L\000\000\000A\000\000\000\071\000\000\000\060\000\000\000(\000\000\000!\000\000\000\033" + "\000\000\000\025\000\000\000\021\000\000\000\015\000\000\000\011\000\000\000\010\000\000\000\005\202\000\000\000\003\001\000\000" + "\000" + "\002\202\000\000\000\001\001\362\362\362\000\214\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000" + "\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000!\000\000\000,\000\000\000\067\202\202" + "\202l\202\362\362\362\350\001\262\262\262\356\215\000\000\000\377\002\004\004\004\377\247" + "\247\247\357\203\362\362\362\350\016\265\265\265\243\000\000\000L\000\000\000B\000\000\000\071" + "\000\000\000\061\000\000\000(\000\000\000!\000\000\000\033\000\000\000\026\000\000\000\021\000\000\000\016\000\000\000\012\000\000" + "\000\010\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\214\000\000\000\000\204\377\377\377" + "\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000" + ",\000" + "\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\217\000\000\000\377" + "\001\247\247\247\357\203\362\362\362\350\021\322\322\322\276\000\000\000L\000\000\000C\000" + "\000\000:\000\000\000\061\000\000\000)" + "\000\000\000\"\000\000\000\034\000\000\000\026\000\000\000\022\000\000\000\016\000\000\000\012" + "\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\213\000\000\000\000\204\377" + "\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"" + "\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\220\000" + "\000\000\377\001\227\227\227\360\203\362\362\362\350\021\321\321\321\277\000\000\000M" + "\000\000\000D\000\000\000:\000\000\000\062\000\000\000*\000\000\000#" + "\000\000\000\034\000\000\000\027\000\000\000\022\000\000\000\016" + "\000\000\000\012\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\212\000\000" + "\000" + "\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031" + "\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356" + "\221\000\000\000\377\001jjj\364\203\362\362\362\350\021\321\321\321\277TTTe\000\000\000" + "E\000\000\000;\000\000\000\063\000\000\000+\000\000\000#" + "\000\000\000\035\000\000\000\027\000\000\000\022\000\000\000\017\000\000\000" + "\013\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\211\000\000\000\000\204" + "\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000" + "\000\"\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\222" + "\000\000\000\377\001jjj\364\203\362\362\362\350\021\350\350\350\331TTTf\000\000\000F\000\000" + "\000<\000\000\000\064\000\000\000+\000\000\000$" + "\000\000\000\035\000\000\000\030\000\000\000\023\000\000\000\017\000\000\000\013\000" + "\000\000\011\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\210\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000" + "\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\223\000\000\000" + "\377\001jjj\364\203\362\362\362\350\020\350\350\350\331SSSg\000\000\000F\000\000\000=\000" + "\000\000\064\000\000\000,\000\000\000%\000\000\000\036\000\000\000\030\000\000\000\023\000\000\000\017\000\000\000\013\000\000\000" + "\011\000\000\000\006\000\000\000\004\000\000\000\003\202\000\000\000\002\001\000\000\000\001\207\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000" + "\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\224\000\000\000" + "\377\001???\371\203\362\362\362\350\020\350\350\350\331~~~{\000\000\000G\000\000\000>\000" + "\000\000\065\000\000\000-\000\000\000%\000\000\000\037\000\000\000\031\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000" + "\011\000\000\000\006\000\000\000\004\000\000\000\003\202\000\000\000\002\202\000\000\000\001\205\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000" + "\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\225\000\000\000" + "\377\001???\371\204\362\362\362\350\016~~~{\000\000\000H\000\000\000?\000\000\000\065\000\000\000.\000" + "\000\000&" + "\000\000\000\037\000\000\000\031\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000\007\000\000\000" + "\004\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\204\000\000\000\000\204\377\377\377\000\012\000\000" + "\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000," + "\000\000\000\067\201" + "\201\201m\202\362\362\362\350\001\262\262\262\356\226\000\000\000\377\002???\371\327" + "\327\327\352\203\362\362\362\350\016}}}|\000\000\000I\000\000\000@\000\000\000\066\000\000\000.\000\000" + "\000'" + "\000\000\000\040\000\000\000\032\000\000\000\025\000\000\000\020\000\000\000\014\000\000\000\011\000\000\000\007\000\000\000\005" + "\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\203\000\000\000\000\204\377\377\377\000\012\000\000\000" + "\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000," + "\000\000\000\067\201" + "\201\201m\202\362\362\362\350\001\262\262\262\356\227\000\000\000\377\002!!!\374\327" + "\327\327\352\203\362\362\362\350\016\236\236\236\217\000\000\000J\000\000\000@\000\000\000\067" + "\000\000\000.\000\000\000'\000\000\000\040\000\000\000\032\000\000\000\025\000\000\000\020\000\000\000\015\000\000\000\011\000\000" + "\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\202\000\000\000\000\204\377\377\377" + "\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000" + ",\000" + "\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\230\000\000\000\377" + "\002!!!\374\327\327\327\352\203\362\362\362\350\017\253\253\253\230\000\000\000J\000" + "\000\000@\000\000\000\067\000\000\000.\000\000\000'" + "\000\000\000\040\000\000\000\032\000\000\000\025\000\000\000\020\000\000\000\014" + "\000\000\000\011\000\000\000\007\000\000\000\004\000\000\000\003\202\000\000\000\002\001\000\000\000\001\202\000\000\000\000\204" + "\377" + "\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"" + "\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\231\000" + "\000\000\377\002!!!\374\267\267\267\355\203\362\362\362\350\020\253\253\253\230" + "\000\000\000I\000\000\000@\000\000\000\066\000\000\000.\000\000\000&" + "\000\000\000\037\000\000\000\031\000\000\000\024\000\000\000\017" + "\000\000\000\013\000\000\000\011\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\001\000\000" + "\000\000" + "\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031" + "\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356" + "\233\000\000\000\377\001\247\247\247\357\203\362\362\362\350\015\302\302\302\252\000" + "\000\000G\000\000\000>\000\000\000\064\000\000\000,\000\000\000$" + "\000\000\000\035\000\000\000\027\000\000\000\022\000\000\000\016\000" + "\000\000\012\000\000\000\007\000\000\000\005\202\000\000\000\003\202\000\000\000\001\001\000\000\000\000\204\377\377\377" + "\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000" + ",\000" + "\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\234\000\000\000\377" + "\001\247\247\247\357\203\362\362\362\350\021\325\325\325\274\000\000\000D\000\000\000:\000" + "\000\000\061\000\000\000(\000\000\000!\000\000\000\033\000\000\000\025\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000" + "\006\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000" + "\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201" + "\201m\202\362\362\362\350\001\262\262\262\356\235\000\000\000\377\001\210\210\210\362" + "\203\362\362\362\350\016\326\326\326\272\000\000\000@\000\000\000\066\000\000\000-\000\000\000%\000\000" + "\000\035\000\000\000\027\000\000\000\022\000\000\000\016\000\000\000\011\000\000\000\007\000\000\000\004\000\000\000\003\000\000" + "\000\002" + "\202\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000" + "\022\000\000\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262" + "\262\262\356\236\000\000\000\377\001jjj\364\203\362\362\362\350\015\337\337\337\302" + "eeeT\000\000\000\061\000\000\000(\000\000\000!\000\000\000\032\000\000\000\024\000\000\000\017\000\000\000\013\000\000\000\010" + "\000\000\000\005\000\000\000\003\000\000\000\002\202\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003" + "\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m" + "\202\362\362\362\350\001\262\262\262\356\237\000\000\000\377\001jjj\364\203\362\362" + "\362\350\012\353\353\353\326kkkO\000\000\000+\000\000\000\"\000\000\000\033\000\000\000\025\000\000\000\020" + "\000\000\000\014\000\000\000\011\000\000\000\006\202\000\000\000\003\202\000\000\000\001\204\377\377\377\000\012\000" + "\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000," + "\000\000\000\067" + "\201\201\201m\202\362\362\362\350\001\262\262\262\356\240\000\000\000\377\001[[[\366" + "\203\362\362\362\350\015\353\353\353\326tttI\000\000\000%\000\000\000\035\000\000\000\027\000\000" + "\000\022\000\000\000\015\000\000\000\011\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\204\377" + "\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000" + "\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262\262\356\241\000\000\000" + "\377\001???\371\203\362\362\362\350\014\354\354\354\325\250\250\250\\\000\000\000" + "\037\000\000\000\030\000\000\000\023\000\000\000\016\000\000\000\012\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000" + "\002\000" + "\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000\022\000\000" + "\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202\362\362\362\350\001\262\262" + "\262\356\240\000\000\000\377\003\004\004\004\377\070\070\070\371???\371\203\362\362\362\350" + "\013\347\347\347\274\000\000\000\037\000\000\000\031\000\000\000\023\000\000\000\017\000\000\000\012\000\000\000\010" + "\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000" + "\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202" + "\362\362\362\350\001\262\262\262\356\231\000\000\000\377\007\034\034\034\374\070\070\070" + "\371UUU\366ttt\363\242\242\242\357\262\262\262\356\343\343\343\351\207\362" + "\362\362\350\012\233\233\233E\000\000\000\031\000\000\000\023\000\000\000\017\000\000\000\012\000\000\000\010" + "\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000" + "\000\007\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000\"\000\000\000,\000\000\000\067\201\201\201m\202" + "\362\362\362\350\001\262\262\262\356\224\000\000\000\377\004!!!\374\203\203\203\362" + "\262\262\262\356\322\322\322\353\216\362\362\362\350\013\350\350\350\273\000" + "\000\000\035\000\000\000\030\000\000\000\022\000\000\000\016\000\000\000\012\000\000\000\010\000\000\000\005\000\000\000\003\000" + "\000" + "\000\002\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\014\000\000\000" + "\022\000\000\000\031\000\000\000!\000\000\000,\000\000\000\067\202\202\202l\202\362\362\362\350\001\262" + "\262\262\356\223\000\000\000\377\002\037\037\037\374\350\350\350\351\214\362\362\362" + "\350\020\333\333\333\301\336\336\336\277\303\303\303\220\312\312\312\213\245" + "\245\245[\000\000\000!\000\000\000\033\000\000\000\025\000\000\000\021\000\000\000\015\000\000\000\011\000\000\000\007\000\000" + "\000\004\000\000\000\003\000\000\000\002\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007" + "\000\000\000\014\000\000\000\022\000\000\000\031\000\000\000!\000\000\000,\000\000\000\067\202\202\202l\202\362\362" + "\362\350\001\262\262\262\356\223\000\000\000\377\001UUU\366\205\362\362\362\350\030" + "\337\337\337\335\315\315\315\317\271\271\271\277\243\243\243\254zzz\220n" + "nn\200''']\003\003\003L\000\000\000B\000\000\000:\000\000\000\062\000\000\000+\000\000\000$\000\000\000\036\000\000\000\030" + "\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002" + "\000\000" + "\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\013\000\000\000\022\000\000\000" + "\031\000\000\000!\000\000\000+\000\000\000\066\203\203\203k\202\362\362\362\350\001\262\262\262" + "\356\223\000\000\000\377\002\037\037\037\374\350\350\350\351\202\362\362\362\350\026" + "CCC\250\002\002\002\217\000\000\000\206\000\000\000|\000\000\000r\000\000\000h\000\000\000^\000\000\000U\000\000\000L\000\000" + "\000C\000\000\000;\000\000\000\064\000\000\000,\000\000\000&" + "\000\000\000\040\000\000\000\033\000\000\000\025\000\000\000\021\000\000" + "\000\016\000\000\000\012\000\000\000\010\000\000\000\005\202\000\000\000\003\002\000\000\000\002\000\000\000\001\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\013\000\000\000\021\000\000\000\031\000\000\000!" + "\000\000\000" + "*\000\000\000\065\205\205\205j\202\362\362\362\350\001\262\262\262\356\212\000\000\000\377" + "\004???\371\242\242\242\357\322\322\322\353jjj\364\206\000\000\000\377\001\203\203" + "\203\362\202\362\362\362\350\030\250\250\250\304\000\000\000\211\000\000\000\177\000\000\000" + "v\000\000\000k\000\000\000a\000\000\000W\000\000\000M\000\000\000E\000\000\000<\000\000\000\064\000\000\000.\000\000\000'" + "\000\000\000" + "!\000\000\000\033\000\000\000\027\000\000\000\023\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000\007\000\000\000\005" + "\000" + "\000\000\003\000\000\000\002\202\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\007\000" + "\000\000\013\000\000\000\021\000\000\000\030\000\000\000\040\000\000\000)\000\000\000\064\206\206\206i\202\362\362" + "\362\350\001\262\262\262\356\210\000\000\000\377\002\004\004\004\377yyy\363\203\362\362\362" + "\350\002\350\350\350\351\037\037\037\374\205\000\000\000\377\001...\372\203\362\362\362" + "\350\027\067\067\067\222\000\000\000z\000\000\000p\000\000\000e\000\000\000[\000\000\000Q\000\000\000G\000\000\000>\000\000" + "\000\066\000\000\000.\000\000\000(\000\000\000!" + "\000\000\000\034\000\000\000\030\000\000\000\024\000\000\000\020\000\000\000\015\000" + "\000\000\012\000\000\000\010\000\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\202\000\000\000\001\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\003\000\000\000\006\000\000\000\013\000\000\000\020\000\000\000\027\000\000\000\037\000" + "\000" + "\000(\000\000\000\062\207\207\207h\202\362\362\362\350\001\262\262\262\356\207\000\000\000" + "\377\002///\372\307\307\307\354\205\362\362\362\350\001jjj\364\206\000\000\000\377" + "\001\222\222\222\361\202\362\362\362\350\024\224\224\224\257\000\000\000v\000\000\000j\000" + "\000\000`\000\000\000U\000\000\000K\000\000\000A\000\000\000\071\000\000\000\060\000\000\000)" + "\000\000\000\"\000\000\000\035\000\000" + "\000\030\000\000\000\024\000\000\000\020\000\000\000\016\000\000\000\013\000\000\000\011\000\000\000\007\000\000\000\005\202\000" + "\000\000\003\001\000\000\000\002\202\000\000\000\001\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\006" + "\000\000\000\012\000\000\000\020\000\000\000\026\000\000\000\036\000\000\000'\000\000\000\060\213\213\213e\202\362" + "\362\362\350\001\262\262\262\356\206\000\000\000\377\001[[[\366\204\362\362\362\350" + "\001\262\262\262\314\202\362\362\362\350\002\327\327\327\352\021\021\021\375\205" + "\000\000\000\377\001<<<\371\202\362\362\362\350\025\344\344\344\335###{\000\000\000g\000\000" + "\000\\\000\000\000Q\000\000\000F\000\000\000=\000\000\000\064\000\000\000,\000\000\000$" + "\000\000\000\036\000\000\000\031\000\000\000" + "\024\000\000\000\020\000\000\000\016\000\000\000\013\000\000\000\011\000\000\000\007\000\000\000\005\000\000\000\004\000\000\000" + "\003\202" + "\000\000\000\002\002\000\000\000\001\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\006\000" + "\000\000\012\000\000\000\017\000\000\000\025\000\000\000\034\000\000\000%\000\000\000.\215\215\215c\202\362\362" + "\362\350\001\262\262\262\356\204\000\000\000\377\002...\372\267\267\267\355\203\362" + "\362\362\350\004\354\354\354\344ooo\252\000\000\000\215\302\302\302\316\202\362\362" + "\362\350\001MMM\367\206\000\000\000\377\001\247\247\247\357\202\362\362\362\350\022" + "|||\230\000\000\000d\000\000\000Y\000\000\000N\000\000\000C\000\000\000:\000\000\000\060\000\000\000(\000\000\000!\000\000\000\032" + "\000\000\000\025\000\000\000\020\000\000\000\016\000\000\000\012\000\000\000\011\000\000\000\007\000\000\000\005\000\000\000\004" + "\202" + "\000\000\000\003\001\000\000\000\002\202\000\000\000\001\001\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000" + "\000\003\000\000\000\006\000\000\000\011\000\000\000\017\000\000\000\025\000\000\000\033\000\000\000\"\000\000\000+\220\220\220" + "a\202\362\362\362\350\001\262\262\262\356\203\000\000\000\377\001MMM\367\204\362\362" + "\362\350\005\322\322\322\317<<<\215\000\000\000\177\000\000\000\201bbb\237\202\362\362" + "\362\350\002\307\307\307\354\004\004\004\377\205\000\000\000\377\001???\371\202\362\362\362" + "\350\021\335\335\335\324\011\011\011e\000\000\000X\000\000\000L\000\000\000A\000\000\000\067\000\000\000.\000" + "\000\000%" + "\000\000\000\035\000\000\000\027\000\000\000\022\000\000\000\016\000\000\000\012\000\000\000\011\000\000\000\006\000\000\000" + "\005\000\000\000\004\202\000\000\000\003\001\000\000\000\002\203\000\000\000\001\001\000\000\000\000\204\377\377\377\000" + "\012\000\000\000\001\000\000\000\003\000\000\000\005\000\000\000\011\000\000\000\016\000\000\000\023\000\000\000\032\000\000\000!" + "\000" + "\000\000(\225\225\225^\202\362\362\362\350\004\262\262\262\356\000\000\000\377!!!\374" + "\227\227\227\360\204\362\362\362\350\007\217\217\217\235\000\000\000n\000\000\000p\000\000" + "\000r\000\000\000t\010\010\010y\333\333\333\326\202\362\362\362\350\001<<<\371\205\000\000" + "\000\377\002\004\004\004\377\307\307\307\354\202\362\362\362\350\016ooo\207\000\000\000W\000" + "\000\000L\000\000\000@\000\000\000\065\000\000\000,\000\000\000#" + "\000\000\000\033\000\000\000\025\000\000\000\017\000\000\000\014\000" + "\000\000\011\000\000\000\006\000\000\000\004\202\000\000\000\003\202\000\000\000\002\203\000\000\000\001\202\000\000\000\000\204" + "\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\005\000\000\000\011\000\000\000\015\000\000\000\022\000\000" + "\000\027\000\000\000\036\000\000\000%\233\233\233[\202\362\362\362\350\003\262\262\262\356" + "???\371\350\350\350\351\203\362\362\362\350\011\341\341\341\321]]]z\000\000\000" + "^\000\000\000`\000\000\000c\000\000\000e\000\000\000h\000\000\000j\207\207\207\234\202\362\362\362\350" + "\001\247\247\247\357\206\000\000\000\377\001UUU\366\202\362\362\362\350\015\316\316" + "\316\302\000\000\000X\000\000\000L\000\000\000@\000\000\000\065\000\000\000,\000\000\000\"\000\000\000\033\000\000\000\024\000" + "\000\000\017\000\000\000\012\000\000\000\007\000\000\000\005\202\000\000\000\003\001\000\000\000\002\204\000\000\000\001\203\000" + "\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\003\000\000\000\004\000\000\000\010\000\000\000\014\000\000" + "\000\020\000\000\000\025\000\000\000\033\000\000\000!\237\237\237X\202\362\362\362\350\001\262\262" + "\262\356\204\362\362\362\350\013\272\272\272\237\013\013\013S\000\000\000Q\000\000\000R\000" + "\000\000S\000\000\000V\000\000\000X\000\000\000\\\000\000\000_CCCw\356\356\356\343\202\362\362\362\350" + "\001<<<\371\205\000\000\000\377\002\037\037\037\374\350\350\350\351\202\362\362\362\350" + "\013vvv\177\000\000\000M\000\000\000B\000\000\000\067\000\000\000-\000\000\000#\000\000\000\033\000\000\000\024\000\000\000\016" + "\000\000\000\011\000\000\000\006\202\000\000\000\003\001\000\000\000\002\202\000\000\000\001\206\000\000\000\000\204\377\377" + "\377\000\012\000\000\000\001\000\000\000\002\000\000\000\004\000\000\000\007\000\000\000\012\000\000\000\017\000\000\000\023\000" + "\000" + "\000\030\000\000\000\035\247\247\247T\205\362\362\362\350\015\352\352\352\327vvve\000" + "\000\000C\000\000\000D\000\000\000E\000\000\000F\000\000\000G\000\000\000J\000\000\000L\000\000\000P\000\000\000T\000\000\000Y\243" + "\243\243\237\202\362\362\362\350\001\210\210\210\362\206\000\000\000\377\001jjj\364" + "\202\362\362\362\350\016\276\276\276\255\000\000\000P\000\000\000D\000\000\000\071\000\000\000.\000\000" + "\000$" + "\000\000\000\033\000\000\000\025\000\000\000\017\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001" + "\210" + "\000\000\000\000\204\377\377\377\000\012\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\006\000\000\000\011\000" + "\000\000\015\000\000\000\020\000\000\000\025\000\000\000\032\034\034\034\"\204\362\362\362\350\003\320\320" + "\320\236@@@E\000\000\000\070\202\000\000\000\071\202\000\000\000:\007\000\000\000<\000\000\000>\000\000\000A\000\000" + "\000E\000\000\000J\000\000\000Pbbbt\203\362\362\362\350\001...\372\205\000\000\000\377\001...\372" + "\203\362\362\362\350\015fffp\000\000\000F\000\000\000;\000\000\000\060\000\000\000'\000\000\000\035\000\000\000" + "\026\000\000\000\017\000\000\000\012\000\000\000\007\000\000\000\003\000\000\000\002\000\000\000\001\210\000\000\000\000\204\377" + "\377\377\000\017\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\005\000\000\000\010\000\000\000\013\000\000\000\017" + "\000\000\000\022\000\000\000\025\000\000\000\032\336\336\336\224\362\362\362\350\341\341\341\254" + "\245\245\245^\000\000\000,\204\000\000\000.\012\000\000\000/\000\000\000\060\000\000\000\061\000\000\000\064\000\000" + "\000\067\000\000\000;\000\000\000@\000\000\000F\000\000\000N\300\300\300\254\202\362\362\362\350\001j" + "jj\364\206\000\000\000\377\001\210\210\210\362\202\362\362\362\350\015\275\275\275" + "\250\000\000\000I\000\000\000>\000\000\000\063\000\000\000(\000\000\000\040\000\000\000\030\000\000\000\021\000\000\000\014\000" + "\000\000\010\000\000\000\004\000\000\000\003\000\000\000\001\210\000\000\000\000\204\377\377\377\000\020\000\000\000\000" + "\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\007\000\000\000\011\000\000\000\014\000\000\000\017\000\000\000\022" + "\000\000" + "\000\025\000\000\000\031\011\011\011\035\000\000\000\040\000\000\000\"\000\000\000$\000\000\000%\202\000\000\000&\001\000" + "\000\000%\202\000\000\000&\010\000\000\000(\000\000\000*\000\000\000.\000\000\000\062\000\000\000\067\000\000\000>\000\000\000F" + "}}}x\202\362\362\362\350\002\350\350\350\351\037\037\037\374\205\000\000\000\377\001." + "..\372\202\362\362\362\350\015\357\357\357\342RRRb\000\000\000@\000\000\000\065\000\000\000+" + "\000\000\000!" + "\000\000\000\032\000\000\000\022\000\000\000\015\000\000\000\011\000\000\000\005\000\000\000\003\000\000\000\001\210\000" + "\000\000\000\204\377\377\377\000\002\000\000\000\000\000\000\000\001\202\000\000\000\003\012\000\000\000\006\000\000\000\010" + "\000\000\000\012\000\000\000\014\000\000\000\017\000\000\000\021\000\000\000\024\000\000\000\027\000\000\000\032\000\000\000\033" + "\202\000\000\000\035\001\000\000\000\036\203\000\000\000\035\012\000\000\000\036\000\000\000\037\000\000\000!\000\000\000%" + "\000\000\000)\000\000\000/\000\000\000\066\000\000\000>\000\000\000G\324\324\324\275\202\362\362\362\350" + "\001[[[\366\206\000\000\000\377\001\247\247\247\357\202\362\362\362\350\015\242\242" + "\242\213\000\000\000A\000\000\000\066\000\000\000-\000\000\000#\000\000\000\033\000\000\000\024\000\000\000\016\000\000\000\011" + "\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\015\000\000\000\000" + "\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\006\000\000\000\010\000\000\000\012\000\000\000\014" + "\000\000\000" + "\016\000\000\000\020\000\000\000\022\000\000\000\025\202\000\000\000\026\202\000\000\000\027\204\000\000\000\026\011" + "\000\000\000\030\000\000\000\032\000\000\000\035\000\000\000\"\000\000\000(\000\000\000.\000\000\000\067\000\000\000@\201\201" + "\201t\202\362\362\362\350\002\307\307\307\354\004\004\004\377\205\000\000\000\377\001<<<" + "\371\202\362\362\362\350\015\343\343\343\317\016\016\016E\000\000\000\067\000\000\000.\000\000" + "\000$" + "\000\000\000\033\000\000\000\025\000\000\000\017\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001" + "\207" + "\000\000\000\000\204\377\377\377\000\003\000\000\000\000\000\000\000\001\000\000\000\002\202\000\000\000\003\010\000\000\000" + "\005\000\000\000\006\000\000\000\010\000\000\000\011\000\000\000\013\000\000\000\015\000\000\000\016\000\000\000\017\204\000\000" + "\000\021\204\000\000\000\020\012\000\000\000\022\000\000\000\024\000\000\000\027\000\000\000\033\000\000\000!\000\000\000(\000" + "\000\000\060\000\000\000\071\000\000\000C\335\335\335\304\202\362\362\362\350\001???\371\206" + "\000\000\000\377\001\267\267\267\355\202\362\362\362\350\014\213\213\213p\000\000\000\070" + "\000\000\000.\000\000\000$" + "\000\000\000\034\000\000\000\025\000\000\000\017\000\000\000\012\000\000\000\006\000\000\000\003\000\000\000" + "\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\001\000\000\000\000\202\000\000\000\001\001\000\000\000\002" + "\202\000\000\000\003\003\000\000\000\005\000\000\000\006\000\000\000\007\202\000\000\000\011\001\000\000\000\012\202\000\000\000" + "\014\202\000\000\000\015\202\000\000\000\014\202\000\000\000\013\013\000\000\000\014\000\000\000\015\000\000\000\017" + "\000\000\000\022\000\000\000\026\000\000\000\033\000\000\000\"\000\000\000*\000\000\000\063\000\000\000<\230\230\230|\202" + "\362\362\362\350\001\247\247\247\357\206\000\000\000\377\001MMM\367\202\362\362\362" + "\350\014\331\331\331\270\000\000\000\067\000\000\000.\000\000\000$\000\000\000\034\000\000\000\025\000\000\000\017" + "\000\000\000\012\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\001" + "\000\000\000\000\202\000\000\000\001\202\000\000\000\002\202\000\000\000\003\005\000\000\000\004\000\000\000\005\000\000\000\006" + "\000" + "\000\000\007\000\000\000\010\205\000\000\000\011\204\000\000\000\010\013\000\000\000\011\000\000\000\013\000\000\000\016\000" + "\000\000\022\000\000\000\027\000\000\000\035\000\000\000%\000\000\000-\000\000\000\066\071\071\071L\352\352\352\327" + "\202\362\362\362\350\001<<<\371\205\000\000\000\377\002\021\021\021\375\327\327\327\352" + "\202\362\362\362\350\013fffO\000\000\000,\000\000\000#\000\000\000\033\000\000\000\025\000\000\000\017\000\000" + "\000\012\000\000\000\007\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\202\000" + "\000\000\000\202\000\000\000\001\202\000\000\000\002\203\000\000\000\003\202\000\000\000\004\202\000\000\000\005\203\000" + "\000\000\006\205\000\000\000\005\013\000\000\000\006\000\000\000\010\000\000\000\013\000\000\000\017\000\000\000\024\000\000\000" + "\031" + "\000\000\000\040\000\000\000(\000\000\000/\000\000\000\070\270\270\270\215\202\362\362\362\350\001\227" + "\227\227\360\203\000\000\000\377\003...\372\203\203\203\362\322\322\322\353\203\362" + "\362\362\350\013\261\261\261w\000\000\000*\000\000\000\"\000\000\000\033\000\000\000\025\000\000\000\017\000" + "\000\000\012\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\202" + "\000\000\000\000\203\000\000\000\001\203\000\000\000\002\215\000\000\000\003\014\000\000\000\004\000\000\000\006\000\000\000\011" + "\000\000\000\014\000\000\000\020\000\000\000\025\000\000\000\033\000\000\000\"\000\000\000*\000\000\000\062```S\357\357" + "\357\342\202\362\362\362\350\003...\372UUU\366\262\262\262\356\205\362\362" + "\362\350\014\360\360\360\341mmmI\000\000\000(\000\000\000\040\000\000\000\031\000\000\000\023\000\000\000\016" + "\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\202" + "\000\000\000\000\001\362\362\362\000\206\000\000\000\001\214\000\000\000\002\014\000\000\000\003\000\000\000\004\000\000\000" + "\006\000\000\000\011\000\000\000\015\000\000\000\022\000\000\000\027\000\000\000\035\000\000\000$\000\000\000+\000\000\000\062\313" + "\313\313\234\202\362\362\362\350\001\350\350\350\351\205\362\362\362\350\016" + "\336\336\336\277\253\253\253{iiiL\000\000\000+\000\000\000$\000\000\000\035\000\000\000\027\000\000\000\021" + "\000\000\000\015\000\000\000\011\000\000\000\006\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377" + "\377\000\202\000\000\000\000\204\362\362\362\000\217\000\000\000\001\014\000\000\000\002\000\000\000\003\000\000\000" + "\005\000\000\000\010\000\000\000\013\000\000\000\017\000\000\000\024\000\000\000\031\000\000\000\037\000\000\000%\000\000\000+\204" + "\204\204V\205\362\362\362\350\021\351\351\351\324\307\307\307\237xxx_\000\000" + "\000\070\000\000\000\062\000\000\000,\000\000\000&\000\000\000\040\000\000\000\032\000\000\000\025\000\000\000\017\000\000\000\013" + "\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\204\377\377\377\000\202" + "\000\000\000\000\222\362\362\362\000\202\000\000\000\001!\000\000\000\002\000\000\000\003\000\000\000\006\000\000\000\011" + "\000\000\000\014\000\000\000\020\000\000\000\025\000\000\000\032\000\000\000\037\000\000\000$\000\000\000)\315\315\315\220" + "\357\357\357\335\362\362\362\350\326\326\326\252\220\220\220h\020\020\020;\000" + "\000\000\066\000\000\000\064\000\000\000\060\000\000\000+\000\000\000'\000\000\000!" + "\000\000\000\033\000\000\000\026\000\000\000\022" + "\000\000\000\016\000\000\000\012\000\000\000\007\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000" + "\000\204" + "\377\377\377\000\202\000\000\000\000\223\362\362\362\000\020\000\000\000\001\000\000\000\002\000\000\000\003\000" + "\000\000\004\000\000\000\007\000\000\000\011\000\000\000\015\000\000\000\021\000\000\000\025\000\000\000\031\000\000\000\036\000" + "\000" + "\000\"\000\000\000&\000\000\000*@@@\071\000\000\000.\202\000\000\000/\016\000\000\000.\000\000\000,\000\000\000(\000\000\000" + "%\000\000\000!\000\000\000\034\000\000\000\030\000\000\000\024\000\000\000\017\000\000\000\014\000\000\000\011\000\000\000\006\000" + "\000\000\004\000\000\000\003\202\000\000\000\001\207\000\000\000\000\204\377\377\377\000\202\000\000\000\000\224" + "\362\362\362\000\040\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\005\000\000\000\010\000\000\000\012\000\000\000" + "\016\000\000\000\021\000\000\000\025\000\000\000\030\000\000\000\033\000\000\000\037\000\000\000\"\000\000\000%\000\000\000'\000" + "\000\000(\000\000\000'\000\000\000&\000\000\000$\000\000\000!" + "\000\000\000\037\000\000\000\033\000\000\000\027\000\000\000\024\000\000" + "\000\020\000\000\000\015\000\000\000\011\000\000\000\007\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000\001\210\000" + "\000\000" + "\000\204\377\377\377\000\202\000\000\000\000\224\362\362\362\000\017\000\000\000\001\000\000\000\002\000\000" + "\000\003\000\000\000\004\000\000\000\006\000\000\000\011\000\000\000\012\000\000\000\016\000\000\000\020\000\000\000\023\000\000" + "\000\026" + "\000\000\000\031\000\000\000\033\000\000\000\036\000\000\000\037\202\000\000\000\040\017\000\000\000\037\000\000\000\035\000" + "\000\000\033\000\000\000\031\000\000\000\026\000\000\000\023\000\000\000\020\000\000\000\016\000\000\000\012\000\000\000\010\000" + "\000\000\006\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001\210\000\000\000\000\204\377\377\377\000\202" + "\000\000\000\000\225\362\362\362\000\016\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\006\000\000" + "\000\011\000\000\000\012\000\000\000\015\000\000\000\017\000\000\000\021\000\000\000\024\000\000\000\025\000\000\000\027\000\000" + "\000\030\202\000\000\000\031\013\000\000\000\030\000\000\000\027\000\000\000\025\000\000\000\024\000\000\000\022\000\000\000" + "\017\000\000\000\015\000\000\000\012\000\000\000\011\000\000\000\007\000\000\000\005\202\000\000\000\003\002\000\000\000\002\000" + "\000" + "\000\001\210\000\000\000\000\204\377\377\377\000\202\000\000\000\000\225\362\362\362\000\002\000\000\000" + "\001\000\000\000\002\202\000\000\000\003\011\000\000\000\004\000\000\000\006\000\000\000\010\000\000\000\011\000\000\000\013\000" + "\000" + "\000\015\000\000\000\017\000\000\000\020\000\000\000\022\204\000\000\000\023\011\000\000\000\022\000\000\000\020\000\000\000" + "\017\000\000\000\016\000\000\000\014\000\000\000\012\000\000\000\011\000\000\000\007\000\000\000\005\202\000\000\000\003\001\000" + "\000" + "\000\002\202\000\000\000\001\210\000\000\000\000\204\377\377\377\000\202\000\000\000\000\225\362\362\362" + "\000\202\000\000\000\001\001\000\000\000\002\202\000\000\000\003\010\000\000\000\004\000\000\000\006\000\000\000\007\000\000\000" + "\010" + "\000\000\000\011\000\000\000\012\000\000\000\014\000\000\000\015\204\000\000\000\016\010\000\000\000\015\000\000\000\014\000" + "\000\000\013\000\000\000\012\000\000\000\011\000\000\000\010\000\000\000\006\000\000\000\005\202\000\000\000\003\001\000\000\000" + "\002" + "\202\000\000\000\001\211\000\000\000\000\204\377\377\377\000\202\000\000\000\000\226\362\362\362\000" + "\202\000\000\000\001\001\000\000\000\002\202\000\000\000\003\005\000\000\000\004\000\000\000\005\000\000\000\006\000\000\000\007" + "\000\000" + "\000\010\210\000\000\000\011\005\000\000\000\010\000\000\000\007\000\000\000\006\000\000\000\005\000\000\000\004\202\000\000\000" + "\003" + "\001\000\000\000\002\203\000\000\000\001\211\000\000\000\000\204\377\377\377\000\202\000\000\000\000\227\362" + "\362\362\000\202\000\000\000\001\202\000\000\000\002\203\000\000\000\003\001\000\000\000\004\202\000\000\000\005\206" + "\000\000\000\006\202\000\000\000\005\001\000\000\000\004\203\000\000\000\003\202\000\000\000\002\203\000\000\000\001\212\000" + "\000\000\000\204\377\377\377\000\202\000\000\000\000\230\362\362\362\000\203\000\000\000\001\203\000" + "\000\000\002\214\000\000\000\003\203\000\000\000\002\203\000\000\000\001\214\000\000\000\000\202\377\377\377\000", +}; + +static const struct pointer_icon pointer_hand_mdpi = { + .kind = POINTER_KIND_CLICK, + .width = 25, + .height = 25, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_MDPI, + .hot_x = 9, + .hot_y = 4, + .rle_pixel_data = + "\263\377\377\377\000\205\000\000\000\000\007\000\000\000\001\000\000\000\002\000\000\000\005\000\000\000\020\000\000\000\016" + "\000\000\000\004\000\000\000\002\212\000\000\000\000\203\377\377\377\000\205\000\000\000\000\010\000\000\000\002\000\000" + "\000\007\000\000\000\240\010\010\010\375\003\003\003\371\000\000\000y\000\000\000\007\000\000\000\002\211\000\000\000\000" + "\203\377\377\377\000\205\000\000\000\000\010\000\000\000\005\000\000\000\036\014\014\014\377\366\366\366" + "\377\350\350\350\377\006\006\006\373\000\000\000\024\000\000\000\004\211\000\000\000\000\203\377\377\377" + "\000\205\000\000\000\000\003\000\000\000\010\000\000\000$\017\017\017\377\202\377\377\377\377\003\017\017" + "\017\377\000\000\000#\000\000\000\007\211\000\000\000\000\203\377\377\377\000\204\000\000\000\000\004\000\000\000" + "\001\000\000\000\011\000\000\000'\017\017\017\377\202\377\377\377\377\004\017\017\017\377\000\000\000" + "'\000\000\000\011\000\000\000\001\210\000\000\000\000\203\377\377\377\000\204\000\000\000\000\004\000\000\000\001\000" + "\000\000\012\000\000\000(\017\017\017\377\202\377\377\377\377\006\017\017\017\377\000\000\000F\000\000" + "\000'\000\000\000\006\000\000\000\003\000\000\000\001\206\000\000\000\000\203\377\377\377\000\203\000\000\000\000\005\000" + "\000\000\001\000\000\000\002\000\000\000\013\000\000\000)\017\017\017\377\202\377\377\377\377\011\017\017" + "\017\377\031\031\031\377\034\034\034\377\000\000\000\312\000\000\000&\000\000\000!\000\000\000\005\000\000\000\002" + "\000\000\000\001\203\000\000\000\000\203\377\377\377\000\010\000\000\000\000\000\000\000\001\000\000\000\003\000\000\000\011" + "\000\000\000W\000\000\000x\000\000\000B\017\017\017\377\202\377\377\377\377\001\066\066\066\377\202" + "\377\377\377\377\011\"\"\"\377\025\025\025\377\026\026\026\377\000\000\000\277\000\000\000\015" + "\000\000\000\005\000\000\000\002\000\000\000\001\000\000\000\000\203\377\377\377\000\010\000\000\000\000\000\000\000\003\000\000" + "\000\013\000\000\000\253\065\065\065\377___\377\002\002\002\356\017\017\017\377\202\377\377\377" + "\377\001<<<\377\202\377\377\377\377\011NNN\377\375\375\375\377\376\376\376\377" + "!!!\377\000\000\000\347\000\000\000\203\000\000\000\007\000\000\000\002\000\000\000\000\203\377\377\377\000\010" + "\000\000\000\001\000\000\000\006\000\000\000N---\377\371\371\371\377\377\377\377\377\232\232\232" + "\377\017\017\017\377\202\377\377\377\377\001\212\212\212\377\202\377\377\377\377" + "\001ppp\377\202\377\377\377\377\006]]]\377\325\325\325\377###\377\000\000\000F\000\000" + "\000\006\000\000\000\001\203\377\377\377\000\005\000\000\000\001\000\000\000\011\000\000\000A\032\032\032\376\355" + "\355\355\377\202\377\377\377\377\001fff\377\210\377\377\377\377\006\245\245\245" + "\377\377\377\377\377===\377\000\000\000U\000\000\000\011\000\000\000\001\203\377\377\377\000\010" + "\000\000\000\001\000\000\000\010\000\000\000\030\000\000\000\242HHH\377\374\374\374\377\377\377\377\377" + "\354\354\354\377\211\377\377\377\377\005\372\372\372\377\012\012\012\375\000\000\000" + ")\000\000\000\013\000\000\000\001\203\377\377\377\000\006\000\000\000\001\000\000\000\006\000\000\000\022\000\000\000(\000" + "\000\000\315}}}\377\213\377\377\377\377\005\276\276\276\377\000\000\000\324\000\000\000\033" + "\000\000\000\011\000\000\000\001\203\377\377\377\000\007\000\000\000\000\000\000\000\002\000\000\000\012\000\000\000\031\000" + "\000\000>\002\002\002\356\321\321\321\377\212\377\377\377\377\005sss\377\000\000\000\232\000" + "\000\000\026\000\000\000\007\000\000\000\000\203\377\377\377\000\007\000\000\000\000\000\000\000\001\000\000\000\004\000\000\000" + "\015\000\000\000\036\000\000\000\223bbb\377\212\377\377\377\377\005$$$\377\000\000\000[\000\000\000\022" + "\000\000\000\005\000\000\000\000\203\377\377\377\000\202\000\000\000\000\006\000\000\000\001\000\000\000\006\000\000\000\023" + "\000\000\000\070\007\007\007\370\344\344\344\377\210\377\377\377\377\006\317\317\317\377" + "\000\000\000\355\000\000\000$\000\000\000\015\000\000\000\002\000\000\000\000\203\377\377\377\000\203\000\000\000\000" + "\005\000\000\000\002\000\000\000\013\000\000\000\034\000\000\000\243hhh\377\210\377\377\377\377\006uuu\377" + "\000\000\000\237\000\000\000\031\000\000\000\011\000\000\000\001\000\000\000\000\203\377\377\377\000\203\000\000\000" + "\000\006\000\000\000\001\000\000\000\006\000\000\000\022\000\000\000\062\003\003\003\351\303\303\303\377\206\377" + "\377\377\377\005\361\361\361\377\026\026\026\375\000\000\000G\000\000\000\022\000\000\000\005\202\000" + "\000\000\000\203\377\377\377\000\204\000\000\000\000\005\000\000\000\002\000\000\000\012\000\000\000\032\000\000\000^\000" + "\000\000\352\207\000\000\000\377\004\000\000\000\224\000\000\000\034\000\000\000\013\000\000\000\002\202\000\000\000\000" + "\203\377\377\377\000\204\000\000\000\000\006\000\000\000\001\000\000\000\005\000\000\000\016\000\000\000\034\000\000\000" + "(\000\000\000\060\204\000\000\000\063\006\000\000\000\062\000\000\000+\000\000\000\037\000\000\000\021\000\000\000\006\000\000\000" + "\001\202\000\000\000\000\203\377\377\377\000\205\000\000\000\000\006\000\000\000\002\000\000\000\005\000\000\000\014\000" + "\000\000\023\000\000\000\030\000\000\000\031\203\000\000\000\032\005\000\000\000\031\000\000\000\025\000\000\000\016\000\000\000" + "\007\000\000\000\002\203\000\000\000\000\203\377\377\377\000\206\000\000\000\000\003\000\000\000\001\000\000\000\003\000" + "\000\000\006\206\000\000\000\010\004\000\000\000\007\000\000\000\004\000\000\000\002\000\000\000\001\203\000\000\000\000\233\377" + "\377\377\000", +}; + +static const struct pointer_icon pointer_hand_xhdpi = { + .kind = POINTER_KIND_CLICK, + .width = 50, + .height = 50, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_XHDPI, + .hot_x = 18, + .hot_y = 8, + .rle_pixel_data = + "\377\377\377\377\000\314\377\377\377\000\214\000\000\000\000\203\000\000\000\001\204\000\000\000\002" + "\202\000\000\000\001\225\000\000\000\000\210\377\377\377\000\213\000\000\000\000\004\000\000\000\001\000\000\000\002" + "\000\000\000\003\000\000\000\005\203\000\000\000\006\005\000\000\000\005\000\000\000\004\000\000\000\003\000\000\000\002\000\000" + "\000\001\213" + "\377\377\377\000\210\000\000\000\000\210\377\377\377\000\212\000\000\000\000\016\000\000\000\001\000\000\000" + "\002\000\000\000\005\000\000\000\010\000\000\000l\000\000\000\313\000\000\000\354\000\000\000\346\000\000\000\263\000\000\000" + "<\000\000\000\006\000\000\000\004\000\000\000\002\000\000\000\001\212\377\377\377\000\210\000\000\000\000\210\377\377" + "\377\000\211\000\000\000\000\017\000\000\000\001\000\000\000\002\000\000\000\005\000\000\000\010\000\000\000~\000\000\000\377\003" + "\003\003\377\034\034\034\377\027\027\027\377\000\000\000\377\000\000\000\370\000\000\000@\000\000\000\007\000\000" + "\000\004\000\000\000\002\212\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000" + "\007\000\000\000\001\000\000\000\003\000\000\000\007\000\000\000\015\000\000\000\347\016\016\016\377\334\334\334\377" + "\202\377\377\377\377\007\241\241\241\377\000\000\000\377\000\000\000\255\000\000\000\014\000\000\000" + "\006\000\000\000\003\000\000\000\001\211\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000" + "\000\000\000\006\000\000\000\002\000\000\000\005\000\000\000\013\000\000\000\022\000\000\000\362\034\034\034\377\203\377" + "\377\377\377\007\374\374\374\377\000\000\000\377\000\000\000\311\000\000\000\021\000\000\000\011\000\000" + "\000\004\000\000\000\001\211\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000" + "\006\000\000\000\002\000\000\000\006\000\000\000\015\000\000\000\027\000\000\000\345\017\017\017\377\204\377\377\377" + "\377\006\000\000\000\377\000\000\000\313\000\000\000\025\000\000\000\014\000\000\000\005\000\000\000\002\211\377\377" + "\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000\006\000\000\000\002\000\000\000\007\000\000\000" + "\017\000\000\000\032\000\000\000\346\017\017\017\377\204\377\377\377\377\006\000\000\000\377\000\000\000" + "\314\000\000\000\030\000\000\000\016\000\000\000\006\000\000\000\002\211\377\377\377\000\210\000\000\000\000\210" + "\377\377\377\000\211\000\000\000\000\006\000\000\000\002\000\000\000\010\000\000\000\021\000\000\000\034\000\000\000\346" + "\017\017\017\377\204\377\377\377\377\006\000\000\000\377\000\000\000\315\000\000\000\032\000\000\000\017" + "\000\000\000\007\000\000\000\002\211\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000" + "\000\000\006\000\000\000\003\000\000\000\010\000\000\000\021\000\000\000\035\000\000\000\346\017\017\017\377\204\377" + "\377\377\377\006\000\000\000\377\000\000\000\315\000\000\000\033\000\000\000\020\000\000\000\010\000\000\000\002\211" + "\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000\006\000\000\000\003\000\000\000" + "\010\000\000\000\022\000\000\000\035\000\000\000\346\017\017\017\377\204\377\377\377\377\006\000\000\000" + "\377\000\000\000\315\000\000\000\035\000\000\000\022\000\000\000\011\000\000\000\004\202\000\000\000\001\207\377\377" + "\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000\006\000\000\000\003\000\000\000\010\000\000\000" + "\022\000\000\000\035\000\000\000\346\017\017\017\377\204\377\377\377\377\012\000\000\000\377\000\000" + "\000\316\000\000\000!" + "\000\000\000\026\000\000\000\016\000\000\000\010\000\000\000\004\000\000\000\003\000\000\000\002\000\000\000\001" + "\205\377\377\377\000\210\000\000\000\000\210\377\377\377\000\211\000\000\000\000\006\000\000\000\003\000" + "\000\000\010\000\000\000\022\000\000\000\035\000\000\000\346\017\017\017\377\204\377\377\377\377\012\000" + "\000\000\377\000\000\000\355\000\000\000\310\000\000\000\330\000\000\000\326\000\000\000\271\000\000\000T\000\000\000\010" + "\000\000\000\005\000\000\000\004\202\000\000\000\002\202\000\000\000\001\001\377\377\377\000\210\000\000\000\000\210" + "\377\377\377\000\206\000\000\000\000\203\000\000\000\001\006\000\000\000\004\000\000\000\012\000\000\000\022\000\000\000" + "\036\000\000\000\346\017\017\017\377\204\377\377\377\377\203\000\000\000\377\202\006\006\006\377" + "\013\000\000\000\377\000\000\000\376\000\000\000P\000\000\000\014\000\000\000\012\000\000\000\010\000\000\000\006\000\000\000\005" + "\000\000\000\003\000\000\000\002\000\000\000\001\207\000\000\000\000\210\377\377\377\000\204\000\000\000\000\013\000\000" + "\000\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\005\000\000\000\010\000\000\000\015\000\000\000\025\000\000" + "\000\040" + "\000\000\000\346\017\017\017\377\204\377\377\377\377\003\000\000\000\377\015\015\015\377\346" + "\346\346\377\202\377\377\377\377\015\261\261\261\377\000\000\000\377\000\000\000\323\000" + "\000\000\277\000\000\000\334\000\000\000\336\000\000\000\300\000\000\000e\000\000\000\010\000\000\000\005\000\000\000\003\000" + "\000\000\002\000\000\000\001\205\000\000\000\000\210\377\377\377\000\203\000\000\000\000\014\000\000\000\001\000\000\000" + "\003\000\000\000\005\000\000\000\007\000\000\000<\000\000\000\244\000\000\000\275\000\000\000\230\000\000\000<\000\000\000$" + "\000\000" + "\000\347\017\017\017\377\204\377\377\377\377\002\000\000\000\377\020\020\020\377\203\377" + "\377\377\377\001\366\366\366\377\203\000\000\000\377\002\013\013\013\377\014\014\014\377" + "\202\000\000\000\377\007\000\000\000r\000\000\000\013\000\000\000\010\000\000\000\005\000\000\000\003\000\000\000\002\000\000\000" + "\001" + "\203\000\000\000\000\210\377\377\377\000\202\000\000\000\000\006\000\000\000\001\000\000\000\003\000\000\000\006\000\000" + "\000\020\000\000\000\241\000\000\000\376\203\000\000\000\377\004\000\000\000\370\000\000\000u\000\000\000\347\017\017" + "\017\377\204\377\377\377\377\002\000\000\000\377\017\017\017\377\204\377\377\377\377" + "\003\000\000\000\377\004\004\004\377\321\321\321\377\202\377\377\377\377\012\326\326\326" + "\377\000\000\000\377\000\000\000\340\000\000\000\236\000\000\000\207\000\000\000,\000\000\000\006\000\000\000\004\000\000\000" + "\002\000\000\000\001\202\000\000\000\000\210\377\377\377\000\017\000\000\000\000\000\000\000\001\000\000\000\002\000\000\000" + "\006\000\000\000\012\000\000\000\245\000\000\000\377(((\377\264\264\264\377\352\352\352\377\235" + "\235\235\377\014\014\014\377\000\000\000\376\000\000\000\373\017\017\017\377\204\377\377\377" + "\377\002\000\000\000\377\017\017\017\377\204\377\377\377\377\002\000\000\000\377\027\027\027\377" + "\204\377\377\377\377\001\002\002\002\377\203\000\000\000\377\007\000\000\000\370\000\000\000d\000\000\000\010" + "\000\000\000\005\000\000\000\002\000\000\000\001\000\000\000\000\210\377\377\377\000\010\000\000\000\000\000\000\000\002\000\000" + "\000\004\000\000\000\011\000\000\000=\000\000\000\377***\377\357\357\357\377\203\377\377\377\377" + "\004\302\302\302\377\006\006\006\377\000\000\000\377\017\017\017\377\204\377\377\377\377\002" + "\000\000\000\377\017\017\017\377\204\377\377\377\377\002\000\000\000\377\036\036\036\377\204" + "\377\377\377\377\013\000\000\000\377\061\061\061\377\305\305\305\377\232\232\232\377" + "\016\016\016\377\000\000\000\371\000\000\000)\000\000\000\010\000\000\000\004\000\000\000\001\000\000\000\000\210\377\377" + "\377\000\007\000\000\000\000\000\000\000\002\000\000\000\006\000\000\000\015\000\000\000z\000\000\000\377\232\232\232\377" + "\205\377\377\377\377\003\243\243\243\377\000\000\000\377\017\017\017\377\204\377\377" + "\377\377\002\311\311\311\377]]]\377\204\377\377\377\377\002\231\231\231\377u" + "uu\377\204\377\377\377\377\002\000\000\000\377\215\215\215\377\202\377\377\377\377" + "\007\213\213\213\377\000\000\000\377\000\000\000v\000\000\000\013\000\000\000\006\000\000\000\002\000\000\000\000\210" + "\377\377\377\000\007\000\000\000\001\000\000\000\003\000\000\000\010\000\000\000\020\000\000\000f\000\000\000\377ppp\377" + "\206\377\377\377\377\002\\\\\\\377\017\017\017\377\220\377\377\377\377\002\000\000\000" + "\377\237\237\237\377\202\377\377\377\377\007\231\231\231\377\000\000\000\377\000\000" + "\000z\000\000\000\017\000\000\000\010\000\000\000\003\000\000\000\001\210\377\377\377\000\010\000\000\000\001\000\000\000" + "\003\000\000\000\011\000\000\000\022\000\000\000(\000\000\000\353\003\003\003\377\277\277\277\377\205\377\377" + "\377\377\002\356\356\356\377$$$\377\220\377\377\377\377\002eee\377\331\331\331" + "\377\202\377\377\377\377\007ccc\377\000\000\000\377\000\000\000Q\000\000\000\022\000\000\000\011\000\000" + "\000\004\000\000\000\001\210\377\377\377\000\011\000\000\000\001\000\000\000\003\000\000\000\011\000\000\000\021\000\000\000" + "\035\000\000\000e\000\000\000\375\030\030\030\377\344\344\344\377\205\377\377\377\377\001\242" + "\242\242\377\224\377\377\377\377\007---\377\000\000\000\374\000\000\000)\000\000\000\024\000\000\000" + "\012\000\000\000\004\000\000\000\001\210\377\377\377\000\012\000\000\000\000\000\000\000\003\000\000\000\010\000\000\000\017" + "\000\000\000\032\000\000\000'\000\000\000\231\000\000\000\377\067\067\067\377\367\367\367\377\204\377" + "\377\377\377\001\376\376\376\377\223\377\377\377\377\010\354\354\354\377\002\002" + "\002\377\000\000\000\324\000\000\000\040\000\000\000\024\000\000\000\012\000\000\000\004\000\000\000\001\210\377\377\377" + "\000\012\000\000\000\000\000\000\000\002\000\000\000\006\000\000\000\014\000\000\000\025\000\000\000!\000\000\000." + "\000\000\000\300\000" + "\000\000\377```\377\230\377\377\377\377\010\246\246\246\377\000\000\000\377\000\000\000\233" + "\000\000\000\036\000\000\000\022\000\000\000\011\000\000\000\003\000\000\000\000\210\377\377\377\000\013\000\000\000\000" + "\000\000\000\001\000\000\000\004\000\000\000\010\000\000\000\020\000\000\000\032\000\000\000%\000\000\000<" + "\000\000\000\336\000\000\000" + "\377\230\230\230\377\227\377\377\377\377\010___\377\000\000\000\377\000\000\000^\000\000\000" + "\034\000\000\000\020\000\000\000\010\000\000\000\003\000\000\000\000\210\377\377\377\000\014\000\000\000\000\000\000\000" + "\001\000\000\000\002\000\000\000\005\000\000\000\013\000\000\000\023\000\000\000\035\000\000\000)\000\000\000Q\000\000\000\366\010" + "\010\010\377\331\331\331\377\225\377\377\377\377\011\374\374\374\377\026\026\026" + "\377\000\000\000\363\000\000\000*\000\000\000\031\000\000\000\016\000\000\000\006\000\000\000\002\000\000\000\000\210\377\377" + "\377\000\202\000\000\000\000\012\000\000\000\001\000\000\000\003\000\000\000\006\000\000\000\015\000\000\000\025\000\000\000\040" + "\000\000\000,\000\000\000\213\000\000\000\377aaa\377\225\377\377\377\377\011\303\303\303\377" + "\000\000\000\377\000\000\000\266\000\000\000\"\000\000\000\026\000\000\000\014\000\000\000\005\000\000\000\001\000\000\000\000\210" + "\377\377\377\000\203\000\000\000\000\012\000\000\000\001\000\000\000\003\000\000\000\010\000\000\000\016\000\000\000\030" + "\000\000\000#\000\000\000\062\000\000\000\354\011\011\011\377\357\357\357\377\224\377\377\377\377" + "\011sss\377\000\000\000\377\000\000\000t\000\000\000\037\000\000\000\022\000\000\000\012\000\000\000\004\000\000\000\001\000" + "\000\000\000\210\377\377\377\000\204\000\000\000\000\011\000\000\000\002\000\000\000\004\000\000\000\011\000\000\000\021" + "\000\000\000\034\000\000\000(\000\000\000\237\000\000\000\377\221\221\221\377\224\377\377\377\377" + "\007$$$\377\000\000\000\371\000\000\000\064\000\000\000\033\000\000\000\020\000\000\000\010\000\000\000\002\202\000\000\000" + "\000\210\377\377\377\000\204\000\000\000\000\012\000\000\000\001\000\000\000\002\000\000\000\005\000\000\000\014\000\000" + "\000\025\000\000\000!\000\000\000B\000\000\000\372\035\035\035\377\371\371\371\377\222\377\377\377" + "\377\010\317\317\317\377\000\000\000\377\000\000\000\306\000\000\000$\000\000\000\027\000\000\000\015\000\000" + "\000\006\000\000\000\002\202\000\000\000\000\210\377\377\377\000\205\000\000\000\000\011\000\000\000\001\000\000\000\003" + "\000\000\000\010\000\000\000\020\000\000\000\032\000\000\000'\000\000\000\262\000\000\000\377\232\232\232\377\222" + "\377\377\377\377\010uuu\377\000\000\000\377\000\000\000}\000\000\000\040\000\000\000\024\000\000\000\012\000" + "\000\000\004\000\000\000\001\202\000\000\000\000\210\377\377\377\000\206\000\000\000\000\011\000\000\000\002\000\000\000" + "\005\000\000\000\013\000\000\000\025\000\000\000!\000\000\000P\000\000\000\376\"\"\"\377\370\370\370\377\220" + "\377\377\377\377\010\376\376\376\377\037\037\037\377\000\000\000\371\000\000\000\066\000\000\000" + "\033\000\000\000\020\000\000\000\010\000\000\000\003\203\000\000\000\000\210\377\377\377\000\206\000\000\000\000\011" + "\000\000\000\001\000\000\000\003\000\000\000\010\000\000\000\020\000\000\000\033\000\000\000(\000\000\000\266\000\000\000\377\204" + "\204\204\377\220\377\377\377\377\010\303\303\303\377\000\000\000\377\000\000\000\277\000" + "\000\000%\000\000\000\030\000\000\000\015\000\000\000\006\000\000\000\002\203\000\000\000\000\210\377\377\377\000\207" + "\000\000\000\000\011\000\000\000\002\000\000\000\005\000\000\000\014\000\000\000\025\000\000\000!\000\000\000G\000\000\000\366\012" + "\012\012\377\341\341\341\377\217\377\377\377\377\010___\377\000\000\000\377\000\000\000" + "s\000\000\000\040\000\000\000\024\000\000\000\012\000\000\000\004\000\000\000\001\203\000\000\000\000\210\377\377\377" + "\000\207\000\000\000\000\012\000\000\000\001\000\000\000\003\000\000\000\010\000\000\000\020\000\000\000\033\000\000\000'" + "\000\000\000" + "\212\000\000\000\377:::\376\357\357\357\377\215\377\377\377\377\011\336\336\336" + "\377\011\011\011\377\000\000\000\347\000\000\000+\000\000\000\033\000\000\000\020\000\000\000\010\000\000\000\003\000\000" + "\000\001\203\000\000\000\000\210\377\377\377\000\210\000\000\000\000\007\000\000\000\002\000\000\000\005\000\000\000\013" + "\000\000\000\024\000\000\000\037\000\000\000,\000\000\000\266\221\000\000\000\377\006\000\000\000\207\000\000\000\"\000\000" + "\000\026\000\000\000\014\000\000\000\006\000\000\000\002\204\000\000\000\000\210\377\377\377\000\210\000\000\000\000" + "\011\000\000\000\001\000\000\000\003\000\000\000\010\000\000\000\016\000\000\000\030\000\000\000\"\000\000\000/" + "\000\000\000\224\000" + "\000\000\324\215\000\000\000\331\010\000\000\000\326\000\000\000\234\000\000\000*\000\000\000\034\000\000\000\021\000" + "\000\000\011\000\000\000\004\000\000\000\001\204\000\000\000\000\210\377\377\377\000\211\000\000\000\000\012\000\000" + "\000\002\000\000\000\005\000\000\000\012\000\000\000\021\000\000\000\031\000\000\000#\000\000\000+" + "\000\000\000\061\000\000\000\065\000" + "\000\000\067\211\000\000\000\070\011\000\000\000\067\000\000\000\064\000\000\000/\000\000\000(\000\000\000\037\000\000\000\025" + "\000\000\000\014\000\000\000\006\000\000\000\002\205\000\000\000\000\210\377\377\377\000\211\000\000\000\000\012\000" + "\000\000\001\000\000\000\002\000\000\000\006\000\000\000\013\000\000\000\021\000\000\000\030\000\000\000\037\000\000\000$" + "\000\000\000(" + "\000\000\000*\211\000\000\000+\011\000\000\000*\000\000\000'\000\000\000#\000\000\000\035\000\000\000\026\000\000\000\016\000\000" + "\000\010\000\000\000\004\000\000\000\001\205\000\000\000\000\210\377\377\377\000\212\000\000\000\000\011\000\000\000" + "\001\000\000\000\003\000\000\000\006\000\000\000\012\000\000\000\017\000\000\000\023\000\000\000\027\000\000\000\032\000\000\000" + "\033" + "\211\000\000\000\034\011\000\000\000\033\000\000\000\032\000\000\000\027\000\000\000\022\000\000\000\016\000\000\000\011\000" + "\000\000\005\000\000\000\002\000\000\000\001\205\000\000\000\000\210\377\377\377\000\213\000\000\000\000\010\000\000\000" + "\001\000\000\000\003\000\000\000\005\000\000\000\010\000\000\000\013\000\000\000\015\000\000\000\017\000\000\000\020\211\000\000" + "\000" + "\021\010\000\000\000\020\000\000\000\017\000\000\000\015\000\000\000\012\000\000\000\007\000\000\000\005\000\000\000\002\000\000" + "\000" + "\001\206\000\000\000\000\210\377\377\377\000\214\000\000\000\000\006\000\000\000\001\000\000\000\002\000\000\000\003\000" + "\000\000\005\000\000\000\006\000\000\000\007\213\000\000\000\010\006\000\000\000\007\000\000\000\006\000\000\000\005\000\000\000" + "\003\000\000" + "\000\002\000\000\000\001\207\000\000\000\000\210\377\377\377\000\215\000\000\000\000\203\000\000\000\001\217\000" + "\000\000\002\203\000\000\000\001\210\000\000\000\000\351\377\377\377\000", +}; + +static const struct pointer_icon pointer_text_mdpi = { + .kind = POINTER_KIND_TEXT, + .width = 25, + .height = 25, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_MDPI, .hot_x = 12, - .hot_y = 0, - .width = 48, - .height = 48, - .data = (uint32_t*) cursor_48x48_data - }, - { - .rotation = 0, - .hot_x = 16, - .hot_y = 0, - .width = 64, - .height = 64, - .data = (uint32_t*) cursor_64x64_data - }, - { + .hot_y = 12, + .rle_pixel_data = + "\272\377\377\377\000\001\377\377\377`\207\377\377\377f\001\377\377\377`\220\377" + "\377\377\000\011\377\377\377f\000\000\000\377\000\000\000\371\000\000\000\272\246\246\246x\000\000" + "\000\272\000\000\000\371\000\000\000\377\377\377\377f\220\377\377\377\000\007\377\377\377`" + "\377\377\377f\364\364\364^\000\000\000z\000\000\000\376\000\000\000z\364\364\364^\202\377\377" + "\377f\222\377\377\377\000\005\377\377\377Z\365\365\365d\000\000\000\377\372\372\372" + "b\377\377\377Z\225\377\377\377\000\003\377\377\377f\000\000\000\377\377\377\377f\226" + "\377\377\377\000\003\377\377\377f\000\000\000\377\377\377\377f\226\377\377\377\000\003" + "\377\377\377f\000\000\000\377\377\377\377f\226\377\377\377\000\003\377\377\377f\000\000" + "\000\377\377\377\377f\226\377\377\377\000\003\377\377\377f\000\000\000\377\377\377\377" + "f\226\377\377\377\000\003\377\377\377f\000\000\000\377\377\377\377f\226\377\377\377" + "\000\003\377\377\377f\000\000\000\377\377\377\377f\226\377\377\377\000\003\377\377\377" + "f\000\000\000\377\377\377\377f\226\377\377\377\000\003\377\377\377f\000\000\000\377\377\377" + "\377f\226\377\377\377\000\003\377\377\377f\000\000\000\377\377\377\377f\226\377\377" + "\377\000\003\377\377\377f\000\000\000\377\377\377\377f\226\377\377\377\000\003\377\377" + "\377f\000\000\000\377\377\377\377f\225\377\377\377\000\005\377\377\377Z\365\365\365" + "d\000\000\000\377\372\372\372b\377\377\377Z\222\377\377\377\000\011\377\377\377`\377" + "\377\377f\367\367\367]\000\000\000z\000\000\000\376\000\000\000z\364\364\364^\377\377\377f" + "\377\377\377`\220\377\377\377\000\011\377\377\377f\000\000\000\377\000\000\000\372\000\000\000" + "\273\246\246\246x\000\000\000\273\000\000\000\371\000\000\000\377\377\377\377f\220\377\377" + "\377\000\001\377\377\377`\207\377\377\377f\001\377\377\377`\323\377\377\377\000", +}; + +static const struct pointer_icon pointer_text_xhdpi = { + .kind = POINTER_KIND_TEXT, + .width = 50, + .height = 50, + .bytes_per_pixel = 4, + .pixel_ratio = PIXEL_RATIO_XHDPI, .hot_x = 24, - .hot_y = 0, - .width = 96, - .height = 96, - .data = (uint32_t*) cursor_96x96_data - }, - { - .hot_x = 32, - .hot_y = 0, - .width = 128, - .height = 128, - .data = (uint32_t*) cursor_128x128_data - } + .hot_y = 24, + .rle_pixel_data = + "\377\377\377\377\000\377\377\377\377\000\214\377\377\377\000\002\377\377\377R\377" + "\377\377S\202\377\377\377T\004\377\377\377U\377\377\377S\377\377\377G\377\377" + "\377-\202\377\377\377\011\005\377\377\377.\377\377\377J\377\377\377X\377\377" + "\377]\377\377\377^\202\377\377\377_\001\377\377\377`\240\377\377\377\000\002\377" + "\377\377R\377\377\377S\202\377\377\377T\013\377\377\377U\377\377\377V\377" + "\377\377W\377\377\377X\377\377\377S\377\377\377T\377\377\377Z\377\377\377" + "[\377\377\377\\\377\377\377]\377\377\377^\202\377\377\377_\001\377\377\377" + "`\240\377\377\377\000\002\377\377\377R\377\377\377S\203\000\000\000\377\010\004\004\004\367" + "\032\032\032\324rrr\211\377\377\377Y\377\377\377Zsss\214\034\034\034\325\004\004\004" + "\367\203\000\000\000\377\002\377\377\377_\377\377\377`\240\377\377\377\000\002\377\377" + "\377R\377\377\377S\206\000\000\000\377\202>>>\256\206\000\000\000\377\002\377\377\377_" + "\377\377\377`\240\377\377\377\000\002\377\377\377R\377\377\377S\202\377\377\377" + "T\004\377\377\377U\322\322\322aOOO\237\002\002\002\373\202\000\000\000\377\005\002\002\002\373" + "RRR\242\323\323\323h\377\377\377]\377\377\377^\202\377\377\377_\001\377\377" + "\377`\240\377\377\377\000\002\377\377\377R\377\377\377S\202\377\377\377T\004\377" + "\377\377U\377\377\377V\377\377\377WHHH\246\202\000\000\000\377\005PPP\242\377\377" + "\377[\377\377\377\\\377\377\377]\377\377\377^\202\377\377\377_\001\377\377" + "\377`\245\377\377\377\000\003\377\377\377\024\377\377\377W\321\321\321d\202\000" + "\000\000\377\003\322\322\322f\377\377\377[\377\377\377\026\205\000\000\000\000\245\377\377" + "\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377" + "\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377" + "X\202\000\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000" + "\003\000\000\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377" + "\377[\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202" + "\000\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000" + "\000\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377" + "[\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\000\000" + "\000\000\377\377\377W\377\377\377X\202\000\000\000\377\002\377\377\377Z\377\377\377[" + "\206\000\000\000\000\245\377\377\377\000\003\000\000\000\000\377\377\377W\377\377\377X\202\000" + "\000\000\377\002\377\377\377Z\377\377\377[\206\000\000\000\000\245\377\377\377\000\003\377" + "\377\377\024\377\377\377W\321\321\321d\202\000\000\000\377\003\322\322\322f\377\377" + "\377[\377\377\377\026\205\000\000\000\000\240\377\377\377\000\002\377\377\377R\377\377" + "\377S\202\377\377\377T\004\377\377\377U\377\377\377V\377\377\377WIII\245\202" + "\000\000\000\377\005PPP\242\377\377\377[\377\377\377\\\377\377\377]\377\377\377^" + "\202\377\377\377_\001\377\377\377`\240\377\377\377\000\002\377\377\377R\377\377" + "\377S\202\377\377\377T\004\377\377\377U\322\322\322aOOO\237\002\002\002\373\202\000" + "\000\000\377\005\002\002\002\373RRR\242\325\325\325g\377\377\377]\377\377\377^\202\377" + "\377\377_\001\377\377\377`\240\377\377\377\000\002\377\377\377R\377\377\377S\206" + "\000\000\000\377\002>>>\256???\257\206\000\000\000\377\002\377\377\377_\377\377\377`\240" + "\377\377\377\000\002\377\377\377R\377\377\377S\203\000\000\000\377\010\004\004\004\370\032" + "\032\032\324ppp\213\377\377\377Y\377\377\377Zppp\215\034\034\034\325\004\004\004\370" + "\203\000\000\000\377\002\377\377\377_\377\377\377`\240\377\377\377\000\002\377\377\377" + "R\377\377\377S\202\377\377\377T\013\377\377\377U\377\377\377V\377\377\377" + "W\377\377\377X\377\377\377S\377\377\377T\377\377\377Z\377\377\377[\377\377" + "\377\\\377\377\377]\377\377\377^\202\377\377\377_\001\377\377\377`\240\377" + "\377\377\000\002\377\377\377R\377\377\377S\202\377\377\377T\004\377\377\377U\377" + "\377\377S\377\377\377G\377\377\377-\202\377\377\377\011\005\377\377\377.\377" + "\377\377J\377\377\377Y\377\377\377]\377\377\377^\202\377\377\377_\001\377\377" + "\377`\377\377\377\377\000\377\377\377\377\000\214\377\377\377\000", +}; + +static const struct pointer_icon *pointer_icons[] = { + &pointer_arrow_mdpi, &pointer_arrow_hdpi, &pointer_arrow_xhdpi, &pointer_arrow_xxhdpi, + &pointer_hand_mdpi, &pointer_hand_xhdpi, &pointer_text_mdpi, &pointer_text_xhdpi, }; -int n_cursors = sizeof(cursors) / sizeof(*cursors); \ No newline at end of file +// Multiplies the r, g, b values of this color with the alpha value. +ATTR_CONST static uint32_t multiply_alpha(uint32_t rgba) { + uint16_t a = rgba >> 24; + if (a == 0xFF) { + // alpha 255 is full opacity, shouldn't change the color values. + return rgba; + } + + uint16_t r = rgba & 0xFF; + uint16_t g = (rgba >> 8) & 0xFF; + uint16_t b = (rgba >> 16) & 0xFF; + + r = (r * a) >> 8; + g = (g * a) >> 8; + b = (b * a) >> 8; + + return (a << 24) | (b << 16) | (g << 8) | r; +} + +// Method version of GIMPs generated RLE_DECODE macro. +// Also converts the pixel values to premultiplied alpha because KMS expects that +// by default. +static void run_length_decode(void *image_buf, const void *rle_data, size_t size) { + unsigned char *image_buf_cursor; + const unsigned char *image_buf_end, *rle_data_cursor; + + image_buf_cursor = image_buf; + image_buf_end = image_buf_cursor + size * 4; + rle_data_cursor = rle_data; + + /* RGBA */ + while (image_buf_cursor < image_buf_end) { + unsigned int length = *(rle_data_cursor++); + if (length & 128) { + length = length - 128; + + uint32_t rgba; + memcpy(&rgba, rle_data_cursor, 4); + + rgba = multiply_alpha(rgba); + + do { + memcpy(image_buf_cursor, &rgba, 4); + image_buf_cursor += 4; + } while (--length); + rle_data_cursor += 4; + } else { + do { + uint32_t rgba; + memcpy(&rgba, rle_data_cursor, 4); + + rgba = multiply_alpha(rgba); + + memcpy(image_buf_cursor, &rgba, 4); + image_buf_cursor += 4; + rle_data_cursor += 4; + } while (--length); + } + } +} + +const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, double pixel_ratio) { + const struct pointer_icon *best; + + best = NULL; + for (int i = 0; i < ARRAY_SIZE(pointer_icons); i++) { + const struct pointer_icon *icon = pointer_icons[i]; + + if (icon->kind == kind) { + if (best == NULL) { + best = icon; + continue; + } else if (fabs(pixel_ratio - icon->pixel_ratio) < fabs(pixel_ratio - best->pixel_ratio)) { + best = icon; + continue; + } + } + } + + if (best == NULL && kind != POINTER_KIND_BASIC) { + best = pointer_icon_for_details(POINTER_KIND_BASIC, pixel_ratio); + } + + ASSERT_NOT_NULL(best); + return best; +} + +enum pointer_kind pointer_icon_get_kind(const struct pointer_icon *icon) { + return icon->kind; +} + +float pointer_icon_get_pixel_ratio(const struct pointer_icon *icon) { + return icon->pixel_ratio; +} + +struct vec2i pointer_icon_get_size(const struct pointer_icon *icon) { + return VEC2I(icon->width, icon->height); +} + +struct vec2i pointer_icon_get_hotspot(const struct pointer_icon *icon) { + return VEC2I(icon->hot_x, icon->hot_y); +} + +void *pointer_icon_dup_pixels(const struct pointer_icon *icon) { + void *buffer; + + ASSERT_EQUALS(icon->bytes_per_pixel, 4); + + buffer = calloc(1, icon->bytes_per_pixel * icon->width * icon->height); + if (buffer == NULL) { + return NULL; + } + + run_length_decode(buffer, icon->rle_pixel_data, icon->width * icon->height); + + return buffer; +} diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 00000000..270404e7 --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +/* + * Cursor Images + * + * Contains all the mouse cursor images in compressed form, + * and some utilities for using them. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_CURSOR_H +#define _FLUTTERPI_SRC_CURSOR_H + +#include + +#include "util/geometry.h" + +enum pointer_kind { + POINTER_KIND_NONE, + POINTER_KIND_BASIC, + POINTER_KIND_CLICK, + POINTER_KIND_FORBIDDEN, + POINTER_KIND_WAIT, + POINTER_KIND_PROGRESS, + POINTER_KIND_CONTEXT_MENU, + POINTER_KIND_HELP, + POINTER_KIND_TEXT, + POINTER_KIND_VERTICAL_TEXT, + POINTER_KIND_CELL, + POINTER_KIND_PRECISE, + POINTER_KIND_MOVE, + POINTER_KIND_GRAB, + POINTER_KIND_GRABBING, + POINTER_KIND_NO_DROP, + POINTER_KIND_ALIAS, + POINTER_KIND_COPY, + POINTER_KIND_DISAPPEARING, + POINTER_KIND_ALL_SCROLL, + POINTER_KIND_RESIZE_LEFT_RIGHT, + POINTER_KIND_RESIZE_UP_DOWN, + POINTER_KIND_RESIZE_UP_LEFT_DOWN_RIGHT, + POINTER_KIND_RESIZE_UP_RIGHT_DOWN_LEFT, + POINTER_KIND_RESIZE_UP, + POINTER_KIND_RESIZE_DOWN, + POINTER_KIND_RESIZE_LEFT, + POINTER_KIND_RESIZE_RIGHT, + POINTER_KIND_RESIZE_UP_LEFT, + POINTER_KIND_RESIZE_UP_RIGHT, + POINTER_KIND_RESIZE_DOWN_LEFT, + POINTER_KIND_RESIZE_DOWN_RIGHT, + POINTER_KIND_RESIZE_COLUMN, + POINTER_KIND_RESIZE_ROW, + POINTER_KIND_ZOOM_IN, + POINTER_KIND_ZOOM_OUT +}; + +struct pointer_icon; + +const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, double pixel_ratio); + +enum pointer_kind pointer_icon_get_kind(const struct pointer_icon *icon); + +float pointer_icon_get_pixel_ratio(const struct pointer_icon *icon); + +struct vec2i pointer_icon_get_size(const struct pointer_icon *icon); + +struct vec2i pointer_icon_get_hotspot(const struct pointer_icon *icon); + +void *pointer_icon_dup_pixels(const struct pointer_icon *icon); + +#endif // _FLUTTERPI_SRC_CURSOR_H diff --git a/src/dmabuf_surface.c b/src/dmabuf_surface.c new file mode 100644 index 00000000..e8e3f310 --- /dev/null +++ b/src/dmabuf_surface.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: MIT +/* + * linux-dmabuf rendering surface + * + * A surface: + * - that plugins can push linux dmabufs into (for example, for video playback) + * - that'll expose both a texture and a platform view + * + * the exposed flutter texture: (cold path) + * - is an imported EGL Image, which is created from the dmabuf using the EGL_EXT_image_dma_buf_import extension (if supported) + * - if that extension is not supported, copy the dmabuf contents into the texture (glTexImage2D) + * - using a texture is slower than a hardware overlay + * - (because with a texture, texture contents must be converted into the right pixel format and composited into a single framebuffer, + * before the frame can be scanned out => additional memory copy, but with hardware overlay that will be done in realtime, on-the-fly, + * while the picture is being scanned out) + * + * the platform view: (hot path) + * - on KMS present, will add a hardware overlay plane to scanout that fb, if that's possible + * (if the rectangle is axis-aligned and pixel format, alpha value etc is supported by KMS) + * - otherwise, fall back to the cold path (textures) + * - this needs integration from the dart side, because only the dart side can decide whether to use + * texture or platform view + * + * The surface should have a specific counterpart on the dart side, which will decide whether to use + * texture or platform view. That decision is hard to make consistent, i.e. when dart-side decides on + * platform view, it's not 100% guaranteed this surface will actually succeed in adding the hw overlay plane. + * + * So best we can do is guess. Not sure how to implement the switching between hot path / cold path though. + * Maybe, if we fail in adding the hw overlay plane, we could signal that somehow to the + * dart-side and make it use a texture for the next frame. But also, it could be adding the overlay plane succeeds, + * and adding a later plane fails. In that case we don't notice the error, but we should still fallback to texture rendering. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "dmabuf_surface.h" + +#include +#include +#include +#include + +#include "compositor_ng.h" +#include "surface.h" +#include "surface_private.h" +#include "texture_registry.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +#include "config.h" + +struct refcounted_dmabuf { + refcount_t n_refs; + struct dmabuf buf; + dmabuf_release_cb_t release_callback; + + struct drmdev *drmdev; + uint32_t drm_fb_id; +}; + +void refcounted_dmabuf_destroy(struct refcounted_dmabuf *dmabuf) { + dmabuf->release_callback(&dmabuf->buf); + if (DRM_ID_IS_VALID(dmabuf->drm_fb_id)) { + drmdev_rm_fb(dmabuf->drmdev, dmabuf->drm_fb_id); + } + drmdev_unref(dmabuf->drmdev); + free(dmabuf); +} + +DEFINE_STATIC_REF_OPS(refcounted_dmabuf, n_refs); + +struct dmabuf_surface { + struct surface surface; + +#ifdef DEBUG + uuid_t uuid; +#endif + +#ifdef HAVE_EGL_GLES2 + EGLDisplay egl_display; +#endif + + struct texture *texture; + struct refcounted_dmabuf *next_buf; +}; + +COMPILE_ASSERT(offsetof(struct dmabuf_surface, surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0x68, 0xed, 0xe5, 0x8a, 0x4a, 0x2b, 0x40, 0x76, 0xa1, 0xb8, 0x89, 0x2e, 0x81, 0xfa, 0xe2, 0xb7); +#endif + +#define CAST_THIS(ptr) CAST_DMABUF_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_DMABUF_SURFACE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct dmabuf_surface *__checked_cast_dmabuf_surface(void *ptr) { + struct dmabuf_surface *s; + + s = CAST_DMABUF_SURFACE_UNCHECKED(ptr); + assert(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +static void dmabuf_surface_deinit(struct surface *s); +static int dmabuf_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int dmabuf_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); + +int dmabuf_surface_init(struct dmabuf_surface *s, struct tracer *tracer, struct texture_registry *texture_registry) { + struct texture *texture; + int ok; + + texture = texture_new(texture_registry); + if (texture == NULL) { + return EIO; + } + + ok = surface_init(&s->surface, tracer); + if (ok != 0) { + return ok; + } + + s->surface.deinit = dmabuf_surface_deinit; + s->surface.present_kms = dmabuf_surface_present_kms; + s->surface.present_fbdev = dmabuf_surface_present_fbdev; + +#ifdef DEBUG + uuid_copy(&s->uuid, uuid); +#endif + +#ifdef HAVE_EGL_GLES2 + s->egl_display = EGL_NO_DISPLAY; +#endif + + s->texture = texture; + s->next_buf = NULL; + return 0; +} + +static void dmabuf_surface_deinit(struct surface *s) { + texture_destroy(CAST_THIS_UNCHECKED(s)->texture); + surface_deinit(s); +} + +/** + * @brief Create a new dmabuf surface. + * + * @return struct dmabuf_surface* + */ +MUST_CHECK struct dmabuf_surface *dmabuf_surface_new(struct tracer *tracer, struct texture_registry *texture_registry) { + struct dmabuf_surface *s; + int ok; + + s = malloc(sizeof *s); + if (s == NULL) { + goto fail_return_null; + } + + ok = dmabuf_surface_init(s, tracer, texture_registry); + if (ok != 0) { + goto fail_free_surface; + } + + return s; + +fail_free_surface: + free(s); + +fail_return_null: + return NULL; +} + +int on_resolve_texture_frame(size_t width, size_t height, void *userdata, struct texture_frame *frame_out) { + /// TODO: Implement + (void) width; + (void) height; + (void) userdata; + +#ifdef HAVE_GLES2 + frame_out->gl.target = GL_TEXTURE_2D; + frame_out->gl.name = 0; + frame_out->gl.format = GL_RGBA8_OES; + frame_out->gl.width = 0; + frame_out->gl.height = 0; +#endif + + frame_out->userdata = NULL; + frame_out->destroy = NULL; + return 0; +} + +int dmabuf_surface_push_dmabuf(struct dmabuf_surface *s, const struct dmabuf *buf, dmabuf_release_cb_t release_cb) { + struct refcounted_dmabuf *b; + int ok; + + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(buf); + ASSERT_NOT_NULL(release_cb); + +#ifdef HAVE_EGL_GLES2 + assert(eglGetCurrentContext() != EGL_NO_CONTEXT); +#endif + + UNIMPLEMENTED(); + + b = malloc(sizeof *b); + if (b == NULL) { + return ENOMEM; + } + + b->n_refs = REFCOUNT_INIT_0; + b->buf = *buf; + b->release_callback = release_cb; + b->drmdev = NULL; + b->drm_fb_id = DRM_ID_NONE; + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + ok = texture_push_unresolved_frame( + s->texture, + &(const struct unresolved_texture_frame){ + .resolve = on_resolve_texture_frame, + .destroy = surface_unref_void, + .userdata = surface_ref(CAST_SURFACE_UNCHECKED(s)), + } + ); + if (ok != 0) { + LOG_ERROR("Couldn't post new frame to texture.\n"); + surface_unref(CAST_SURFACE_UNCHECKED(s)); + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + free(b); + return ok; + } + + refcounted_dmabuf_swap_ptrs(&s->next_buf, b); + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + + return 0; +} + +ATTR_PURE int64_t dmabuf_surface_get_texture_id(struct dmabuf_surface *s) { + ASSERT_NOT_NULL(s); + return texture_get_id(s->texture); +} + +static int dmabuf_surface_present_kms(struct surface *_s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct dmabuf_surface *s; + uint32_t fb_id; + int ok; + + ASSERT_MSG(props->is_aa_rect, "Only axis-aligned rectangles supported right now."); + s = CAST_THIS(_s); + (void) s; + (void) props; + (void) builder; + + surface_lock(_s); + + ASSERT_NOT_NULL_MSG(s->next_buf, "dmabuf_surface_present_kms was called, but no dmabuf is queued to be presented."); + + if (DRM_ID_IS_VALID(s->next_buf->drm_fb_id)) { + ASSERT_EQUALS_MSG(s->next_buf->drmdev, kms_req_builder_get_drmdev(builder), "Only 1 KMS instance per dmabuf supported right now."); + fb_id = s->next_buf->drm_fb_id; + } else { + fb_id = drmdev_add_fb_from_dmabuf( + kms_req_builder_get_drmdev(builder), + s->next_buf->buf.width, + s->next_buf->buf.height, + s->next_buf->buf.format, + s->next_buf->buf.fds[0], + s->next_buf->buf.strides[0], + s->next_buf->buf.offsets[0], + s->next_buf->buf.has_modifiers, + s->next_buf->buf.modifiers[0] + ); + if (!DRM_ID_IS_VALID(fb_id)) { + LOG_ERROR("Couldn't add dmabuf as framebuffer.\n"); + } + + s->next_buf->drm_fb_id = fb_id; + s->next_buf->drmdev = drmdev_ref(kms_req_builder_get_drmdev(builder)); + } + + ok = kms_req_builder_push_fb_layer( + builder, + &(struct kms_fb_layer){ + .drm_fb_id = fb_id, + .format = s->next_buf->buf.format, + + .has_modifier = s->next_buf->buf.has_modifiers, + .modifier = s->next_buf->buf.modifiers[0], + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(s->next_buf->buf.width), + .src_h = DOUBLE_TO_FP1616_ROUNDED(s->next_buf->buf.height), + + .dst_x = props->aa_rect.offset.x, + .dst_y = props->aa_rect.offset.y, + .dst_w = props->aa_rect.size.x, + .dst_h = props->aa_rect.size.y, + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + .has_in_fence_fd = false, + .in_fence_fd = 0, + }, + refcounted_dmabuf_unref_void, + NULL, + refcounted_dmabuf_ref(s->next_buf) + ); + if (ok != 0) { + LOG_ERROR("Couldn't push KMS fb layer. kms_req_builder_push_fb_layer: %s\n", strerror(ok)); + refcounted_dmabuf_unref(s->next_buf); + surface_unlock(_s); + return ok; + } + + surface_unlock(_s); + + return 0; +} + +static int dmabuf_surface_present_fbdev(struct surface *_s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct dmabuf_surface *s; + + s = CAST_THIS(_s); + (void) s; + (void) props; + (void) builder; + UNIMPLEMENTED(); + + return 0; +} diff --git a/src/dmabuf_surface.h b/src/dmabuf_surface.h new file mode 100644 index 00000000..0050e3ac --- /dev/null +++ b/src/dmabuf_surface.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * linux dmabuf surface + * + * - present dmabufs on screen as optimally as possible + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_DMABUF_SURFACE_H +#define _FLUTTERPI_SRC_DMABUF_SURFACE_H + +#include "pixel_format.h" + +struct dmabuf_surface; + +#define CAST_DMABUF_SURFACE_UNCHECKED(ptr) ((struct dmabuf_surface *) (ptr)) +#ifdef DEBUG + #define CAST_DMABUF_SURFACE(ptr) __checked_cast_dmabuf_surface(ptr) +ATTR_PURE struct dmabuf_surface *__checked_cast_dmabuf_surface(void *ptr); +#else + #define CAST_DMABUF_SURFACE(ptr) CAST_DMABUF_SURFACE_UNCHECKED(ptr) +#endif + +struct dmabuf { + enum pixfmt format; + int width, height; + int fds[4]; + int offsets[4]; + int strides[4]; + bool has_modifiers; + uint64_t modifiers[4]; + void *userdata; +}; + +typedef void (*dmabuf_release_cb_t)(struct dmabuf *buf); + +struct texture_registry; +struct tracer; + +MUST_CHECK struct dmabuf_surface *dmabuf_surface_new(struct tracer *tracer, struct texture_registry *texture_registry); + +int dmabuf_surface_push_dmabuf(struct dmabuf_surface *s, const struct dmabuf *buf, dmabuf_release_cb_t release_cb); + +ATTR_PURE int64_t dmabuf_surface_get_texture_id(struct dmabuf_surface *s); + +#endif // _FLUTTERPI_SRC_DMABUF_SURFACE_H diff --git a/src/dummy_render_surface.c b/src/dummy_render_surface.c new file mode 100644 index 00000000..3cc56714 --- /dev/null +++ b/src/dummy_render_surface.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan GBM Backing Store + * + * - a render surface that can be used for filling flutter vulkan backing stores + * - and for scanout using KMS + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "dummy_render_surface.h" + +#include +#include +#include + +#include + +#include "render_surface.h" +#include "render_surface_private.h" +#include "surface.h" +#include "surface_private.h" +#include "tracer.h" +#include "util/geometry.h" +#include "util/uuid.h" + +struct dummy_render_surface { + union { + struct surface surface; + struct render_surface render_surface; + }; + +#ifdef DEBUG + uuid_t uuid; +#endif +}; + +COMPILE_ASSERT(offsetof(struct dummy_render_surface, surface) == 0); +COMPILE_ASSERT(offsetof(struct dummy_render_surface, render_surface.surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0x26, 0xfe, 0x91, 0x53, 0x75, 0xf2, 0x41, 0x90, 0xa1, 0xf5, 0xba, 0xe1, 0x1b, 0x28, 0xd5, 0xe5); +#endif + +#define CAST_THIS(ptr) CAST_DUMMY_RENDER_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct dummy_render_surface *__checked_cast_dummy_render_surface(void *ptr) { + struct dummy_render_surface *surface; + + surface = CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr); + ASSERT(uuid_equals(surface->uuid, uuid)); + return surface; +} +#endif + +void dummy_render_surface_deinit(struct surface *s); +static int dummy_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int dummy_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int dummy_render_surface_fill(struct render_surface *surface, FlutterBackingStore *fl_store); +static int dummy_render_surface_queue_present(struct render_surface *surface, const FlutterBackingStore *fl_store); + +int dummy_render_surface_init(struct dummy_render_surface *surface, struct tracer *tracer, struct vec2i size) { + int ok; + + ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(surface), tracer, size); + if (ok != 0) { + return EIO; + } + + surface->surface.present_kms = dummy_render_surface_present_kms; + surface->surface.present_fbdev = dummy_render_surface_present_fbdev; + surface->surface.deinit = dummy_render_surface_deinit; + surface->render_surface.fill = dummy_render_surface_fill; + surface->render_surface.queue_present = dummy_render_surface_queue_present; + +#ifdef DEBUG + uuid_copy(&surface->uuid, uuid); +#endif + return 0; +} + +struct dummy_render_surface *dummy_render_surface_new(struct tracer *tracer, struct vec2i size) { + struct dummy_render_surface *surface; + int ok; + + surface = malloc(sizeof *surface); + if (surface == NULL) { + goto fail_return_null; + } + + ok = dummy_render_surface_init(surface, tracer, size); + if (ok != 0) { + goto fail_free_surface; + } + + return surface; + +fail_free_surface: + free(surface); + +fail_return_null: + return NULL; +} + +void dummy_render_surface_deinit(struct surface *s) { + render_surface_deinit(s); +} + +static int +dummy_render_surface_present_kms(struct surface *s, UNUSED const struct fl_layer_props *props, UNUSED struct kms_req_builder *builder) { + (void) props; + (void) builder; + + TRACER_INSTANT(s->tracer, "dummy_render_surface_present_kms"); + + return 0; +} + +static int dummy_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + (void) s; + (void) props; + (void) builder; + + TRACER_INSTANT(s->tracer, "dummy_render_surface_present_fbdev"); + + return 0; +} + +static int dummy_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { + (void) fl_store; + + TRACER_INSTANT(s->surface.tracer, "dummy_render_surface_fill"); + + return 0; +} + +static int dummy_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { + (void) fl_store; + + TRACER_INSTANT(s->surface.tracer, "dummy_render_surface_queue_present"); + + return 0; +} diff --git a/src/dummy_render_surface.h b/src/dummy_render_surface.h new file mode 100644 index 00000000..dacabb0e --- /dev/null +++ b/src/dummy_render_surface.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +/* + * Dummy render surface + * + * Just a render surface that does nothing when presenting. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H +#define _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H + +#include "util/geometry.h" + +struct tracer; +struct dummy_render_surface; + +#define CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) ((struct dummy_render_surface *) (ptr)) +#ifdef DEBUG + #define CAST_DUMMY_RENDER_SURFACE(ptr) __checked_cast_dummy_render_surface(ptr) +ATTR_PURE struct dummy_render_surface *__checked_cast_dummy_render_surface(void *ptr); +#else + #define CAST_DUMMY_RENDER_SURFACE(ptr) CAST_DUMMY_RENDER_SURFACE_UNCHECKED(ptr) +#endif + +struct dummy_render_surface *dummy_render_surface_new(struct tracer *tracer, struct vec2i size); + +#endif // _FLUTTERPI_SRC_DUMMY_RENDER_SURFACE_H diff --git a/src/egl.h b/src/egl.h new file mode 100644 index 00000000..9a9a936c --- /dev/null +++ b/src/egl.h @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including EGL headers, and disabling EGL function prototypes if EGL is not present + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_EGL_H +#define _FLUTTERPI_SRC_EGL_H + +#include +#include + +#include "config.h" + +#ifndef HAVE_EGL + #error "egl.h was included but EGL support is disabled." +#endif + +#ifdef LINT_EGL_HEADERS + + // This makes sure we only use EGL 1.4 definitions and prototypes. + #define EGL_VERSION_1_5 1 + #include + #undef EGL_VERSION_1_5 + + // Every extension with a #define 1 here is disabled. + // Once eglext.h is included, only the extensions that didn't have + // such a define have their prototypes, types defined. + + #define EGL_KHR_cl_event 1 + // #define EGL_KHR_cl_event2 1 + #define EGL_KHR_client_get_all_proc_addresses 1 + #define EGL_KHR_config_attribs 1 + #define EGL_KHR_context_flush_control 1 + #define EGL_KHR_create_context 1 + #define EGL_KHR_create_context_no_error 1 + #define EGL_KHR_debug 1 + #define EGL_KHR_display_reference 1 + #define EGL_KHR_fence_sync 1 + #define EGL_KHR_get_all_proc_addresses 1 + #define EGL_KHR_gl_colorspace 1 + #define EGL_KHR_gl_renderbuffer_image 1 + #define EGL_KHR_gl_texture_2D_image 1 + #define EGL_KHR_gl_texture_3D_image 1 + #define EGL_KHR_gl_texture_cubemap_image 1 + // #define EGL_KHR_image 1 + // #define EGL_KHR_image_base 1 + #define EGL_KHR_image_pixmap 1 + #define EGL_KHR_lock_surface 1 + #define EGL_KHR_lock_surface2 1 + #define EGL_KHR_lock_surface3 1 + #define EGL_KHR_mutable_render_buffer 1 + // #define EGL_KHR_no_config_context 1 + #define EGL_KHR_partial_update 1 + #define EGL_KHR_platform_android 1 + // #define EGL_KHR_platform_gbm 1 + #define EGL_KHR_platform_wayland 1 + #define EGL_KHR_platform_x11 1 + #define EGL_KHR_reusable_sync 1 + // #define EGL_KHR_stream 1 + #define EGL_KHR_stream_attrib 1 + #define EGL_KHR_stream_consumer_gltexture 1 + #define EGL_KHR_stream_cross_process_fd 1 + #define EGL_KHR_stream_fifo 1 + #define EGL_KHR_stream_producer_aldatalocator 1 + #define EGL_KHR_stream_producer_eglsurface 1 + #define EGL_KHR_surfaceless_context 1 + #define EGL_KHR_swap_buffers_with_damage 1 + #define EGL_KHR_vg_parent_image 1 + #define EGL_KHR_wait_sync 1 + #define EGL_ANDROID_GLES_layers 1 + #define EGL_ANDROID_blob_cache 1 + #define EGL_ANDROID_create_native_client_buffer 1 + #define EGL_ANDROID_framebuffer_target 1 + #define EGL_ANDROID_front_buffer_auto_refresh 1 + #define EGL_ANDROID_get_frame_timestamps 1 + #define EGL_ANDROID_get_native_client_buffer 1 + #define EGL_ANDROID_image_native_buffer 1 + #define EGL_ANDROID_native_fence_sync 1 + #define EGL_ANDROID_presentation_time 1 + #define EGL_ANDROID_recordable 1 + #define EGL_ANGLE_d3d_share_handle_client_buffer 1 + #define EGL_ANGLE_device_d3d 1 + #define EGL_ANGLE_query_surface_pointer 1 + #define EGL_ANGLE_surface_d3d_texture_2d_share_handle 1 + #define EGL_ANGLE_sync_control_rate 1 + #define EGL_ANGLE_window_fixed_size 1 + #define EGL_ARM_image_format 1 + #define EGL_ARM_implicit_external_sync 1 + #define EGL_ARM_pixmap_multisample_discard 1 + #define EGL_EXT_bind_to_front 1 + #define EGL_EXT_buffer_age 1 + #define EGL_EXT_client_extensions 1 + #define EGL_EXT_client_sync 1 + #define EGL_EXT_compositor 1 + #define EGL_EXT_config_select_group 1 + #define EGL_EXT_create_context_robustness 1 + #define EGL_EXT_device_base 1 + #define EGL_EXT_device_drm 1 + #define EGL_EXT_device_drm_render_node 1 + #define EGL_EXT_device_enumeration 1 + #define EGL_EXT_device_openwf 1 + #define EGL_EXT_device_persistent_id 1 + #define EGL_EXT_device_query 1 + #define EGL_EXT_device_query_name 1 + #define EGL_EXT_gl_colorspace_bt2020_linear 1 + #define EGL_EXT_gl_colorspace_bt2020_pq 1 + #define EGL_EXT_gl_colorspace_display_p3 1 + #define EGL_EXT_gl_colorspace_display_p3_linear 1 + #define EGL_EXT_gl_colorspace_display_p3_passthrough 1 + #define EGL_EXT_gl_colorspace_scrgb 1 + #define EGL_EXT_gl_colorspace_scrgb_linear 1 + // #define EGL_EXT_image_dma_buf_import 1 + #define EGL_EXT_image_dma_buf_import_modifiers 1 + #define EGL_EXT_image_gl_colorspace 1 + #define EGL_EXT_image_implicit_sync_control 1 + #define EGL_EXT_multiview_window 1 + #define EGL_EXT_output_base 1 + #define EGL_EXT_output_drm 1 + #define EGL_EXT_output_openwf 1 + #define EGL_EXT_pixel_format_float 1 + #define EGL_EXT_platform_base 1 + #define EGL_EXT_platform_device 1 + #define EGL_EXT_platform_wayland 1 + #define EGL_EXT_platform_x11 1 + #define EGL_EXT_platform_xcb 1 + #define EGL_EXT_present_opaque 1 + #define EGL_EXT_protected_content 1 + #define EGL_EXT_protected_surface 1 + #define EGL_EXT_stream_consumer_egloutput 1 + #define EGL_EXT_surface_CTA861_3_metadata 1 + #define EGL_EXT_surface_SMPTE2086_metadata 1 + #define EGL_EXT_surface_compression 1 + #define EGL_EXT_swap_buffers_with_damage 1 + #define EGL_EXT_sync_reuse 1 + #define EGL_EXT_yuv_surface 1 + #define EGL_HI_clientpixmap 1 + #define EGL_HI_colorformats 1 + #define EGL_IMG_context_priority 1 + #define EGL_IMG_image_plane_attribs 1 + #define EGL_MESA_drm_image 1 + #define EGL_MESA_image_dma_buf_export 1 + #define EGL_MESA_platform_gbm 1 + #define EGL_MESA_platform_surfaceless 1 + #define EGL_NV_quadruple_buffer 1 + #define EGL_NV_robustness_video_memory_purge 1 + #define EGL_NV_stream_consumer_eglimage 1 + #define EGL_NV_stream_consumer_gltexture_yuv 1 + #define EGL_NV_stream_cross_display 1 + #define EGL_NV_stream_cross_object 1 + #define EGL_NV_stream_cross_partition 1 + #define EGL_NV_stream_cross_process 1 + #define EGL_NV_stream_cross_system 1 + #define EGL_NV_stream_dma 1 + #define EGL_NV_stream_fifo_next 1 + #define EGL_NV_stream_fifo_synchronous 1 + #define EGL_NV_stream_flush 1 + #define EGL_NV_stream_frame_limits 1 + #define EGL_NV_stream_metadata 1 + #define EGL_NV_stream_origin 1 + #define EGL_NV_stream_remote 1 + #define EGL_NV_stream_reset 1 + #define EGL_NV_stream_socket 1 + #define EGL_NV_stream_socket_inet 1 + #define EGL_NV_stream_socket_unix 1 + #define EGL_NV_stream_sync 1 + #define EGL_NV_sync 1 + #define EGL_NV_system_time 1 + #define EGL_NV_triple_buffer 1 + #define EGL_TIZEN_image_native_buffer 1 + #define EGL_TIZEN_image_native_surface 1 + #define EGL_WL_bind_wayland_display 1 + #define EGL_WL_create_wayland_buffer_from_image 1 + #define EGL_NV_native_query 1 + #define EGL_NV_post_convert_rounding 1 + #define EGL_NV_post_sub_buffer 1 + #define EGL_NV_quadruple_buffer 1 + #define EGL_NV_robustness_video_memory_purge 1 + #define EGL_NV_stream_consumer_eglimage 1 + #define EGL_NV_stream_consumer_gltexture_yuv 1 + #define EGL_NV_stream_cross_display 1 + #define EGL_NV_stream_cross_object 1 + #define EGL_NV_stream_cross_partition 1 + #define EGL_NV_stream_cross_process 1 + #define EGL_NV_stream_cross_system 1 + #define EGL_NV_stream_dma 1 + #define EGL_NV_stream_fifo_next 1 + #define EGL_NV_stream_fifo_synchronous 1 + #define EGL_NV_stream_flush 1 + #define EGL_NV_stream_frame_limits 1 + #define EGL_NV_stream_metadata 1 + #define EGL_NV_stream_origin 1 + #define EGL_NV_stream_remote 1 + #define EGL_NV_stream_reset 1 + #define EGL_NV_stream_socket 1 + #define EGL_NV_stream_socket_inet 1 + #define EGL_NV_stream_socket_unix 1 + #define EGL_NV_stream_sync 1 + #define EGL_NV_sync 1 + #define EGL_NV_system_time 1 + #define EGL_NV_triple_buffer 1 + #define EGL_TIZEN_image_native_buffer 1 + #define EGL_TIZEN_image_native_surface 1 + #define EGL_WL_bind_wayland_display 1 + #define EGL_WL_create_wayland_buffer_from_image 1 + + // Actually include eglext.h + #include + + #undef EGL_KHR_cl_event + // #undef EGL_KHR_cl_event2 + #undef EGL_KHR_client_get_all_proc_addresses + #undef EGL_KHR_config_attribs + #undef EGL_KHR_context_flush_control + #undef EGL_KHR_create_context + #undef EGL_KHR_create_context_no_error + #undef EGL_KHR_debug + #undef EGL_KHR_display_reference + #undef EGL_KHR_fence_sync + #undef EGL_KHR_get_all_proc_addresses + #undef EGL_KHR_gl_colorspace + #undef EGL_KHR_gl_renderbuffer_image + #undef EGL_KHR_gl_texture_2D_image + #undef EGL_KHR_gl_texture_3D_image + #undef EGL_KHR_gl_texture_cubemap_image + // #undef EGL_KHR_image + // #undef EGL_KHR_image_base + #undef EGL_KHR_image_pixmap + #undef EGL_KHR_lock_surface + #undef EGL_KHR_lock_surface2 + #undef EGL_KHR_lock_surface3 + #undef EGL_KHR_mutable_render_buffer + // #undef EGL_KHR_no_config_context + #undef EGL_KHR_partial_update + #undef EGL_KHR_platform_android + // #undef EGL_KHR_platform_gbm + #undef EGL_KHR_platform_wayland + #undef EGL_KHR_platform_x11 + #undef EGL_KHR_reusable_sync + // #undef EGL_KHR_stream + #undef EGL_KHR_stream_attrib + #undef EGL_KHR_stream_consumer_gltexture + #undef EGL_KHR_stream_cross_process_fd + #undef EGL_KHR_stream_fifo + #undef EGL_KHR_stream_producer_aldatalocator + #undef EGL_KHR_stream_producer_eglsurface + #undef EGL_KHR_surfaceless_context + #undef EGL_KHR_swap_buffers_with_damage + #undef EGL_KHR_vg_parent_image + #undef EGL_KHR_wait_sync + #undef EGL_ANDROID_GLES_layers + #undef EGL_ANDROID_blob_cache + #undef EGL_ANDROID_create_native_client_buffer + #undef EGL_ANDROID_framebuffer_target + #undef EGL_ANDROID_front_buffer_auto_refresh + #undef EGL_ANDROID_get_frame_timestamps + #undef EGL_ANDROID_get_native_client_buffer + #undef EGL_ANDROID_image_native_buffer + #undef EGL_ANDROID_native_fence_sync + #undef EGL_ANDROID_presentation_time + #undef EGL_ANDROID_recordable + #undef EGL_ANGLE_d3d_share_handle_client_buffer + #undef EGL_ANGLE_device_d3d + #undef EGL_ANGLE_query_surface_pointer + #undef EGL_ANGLE_surface_d3d_texture_2d_share_handle + #undef EGL_ANGLE_sync_control_rate + #undef EGL_ANGLE_window_fixed_size + #undef EGL_ARM_image_format + #undef EGL_ARM_implicit_external_sync + #undef EGL_ARM_pixmap_multisample_discard + #undef EGL_EXT_bind_to_front + #undef EGL_EXT_buffer_age + #undef EGL_EXT_client_extensions + #undef EGL_EXT_client_sync + #undef EGL_EXT_compositor + #undef EGL_EXT_config_select_group + #undef EGL_EXT_create_context_robustness + #undef EGL_EXT_device_base + #undef EGL_EXT_device_drm + #undef EGL_EXT_device_drm_render_node + #undef EGL_EXT_device_enumeration + #undef EGL_EXT_device_openwf + #undef EGL_EXT_device_persistent_id + #undef EGL_EXT_device_query + #undef EGL_EXT_device_query_name + #undef EGL_EXT_gl_colorspace_bt2020_linear + #undef EGL_EXT_gl_colorspace_bt2020_pq + #undef EGL_EXT_gl_colorspace_display_p3 + #undef EGL_EXT_gl_colorspace_display_p3_linear + #undef EGL_EXT_gl_colorspace_display_p3_passthrough + #undef EGL_EXT_gl_colorspace_scrgb + #undef EGL_EXT_gl_colorspace_scrgb_linear + // #undef EGL_EXT_image_dma_buf_import + #undef EGL_EXT_image_dma_buf_import_modifiers + #undef EGL_EXT_image_gl_colorspace + #undef EGL_EXT_image_implicit_sync_control + #undef EGL_EXT_multiview_window + #undef EGL_EXT_output_base + #undef EGL_EXT_output_drm + #undef EGL_EXT_output_openwf + #undef EGL_EXT_pixel_format_float + #undef EGL_EXT_platform_base + #undef EGL_EXT_platform_device + #undef EGL_EXT_platform_wayland + #undef EGL_EXT_platform_x11 + #undef EGL_EXT_platform_xcb + #undef EGL_EXT_present_opaque + #undef EGL_EXT_protected_content + #undef EGL_EXT_protected_surface + #undef EGL_EXT_stream_consumer_egloutput + #undef EGL_EXT_surface_CTA861_3_metadata + #undef EGL_EXT_surface_SMPTE2086_metadata + #undef EGL_EXT_surface_compression + #undef EGL_EXT_swap_buffers_with_damage + #undef EGL_EXT_sync_reuse + #undef EGL_EXT_yuv_surface + #undef EGL_HI_clientpixmap + #undef EGL_HI_colorformats + #undef EGL_IMG_context_priority + #undef EGL_IMG_image_plane_attribs + #undef EGL_MESA_drm_image + #undef EGL_MESA_image_dma_buf_export + #undef EGL_MESA_platform_gbm + #undef EGL_MESA_platform_surfaceless + #undef EGL_NV_quadruple_buffer + #undef EGL_NV_robustness_video_memory_purge + #undef EGL_NV_stream_consumer_eglimage + #undef EGL_NV_stream_consumer_gltexture_yuv + #undef EGL_NV_stream_cross_display + #undef EGL_NV_stream_cross_object + #undef EGL_NV_stream_cross_partition + #undef EGL_NV_stream_cross_process + #undef EGL_NV_stream_cross_system + #undef EGL_NV_stream_dma + #undef EGL_NV_stream_fifo_next + #undef EGL_NV_stream_fifo_synchronous + #undef EGL_NV_stream_flush + #undef EGL_NV_stream_frame_limits + #undef EGL_NV_stream_metadata + #undef EGL_NV_stream_origin + #undef EGL_NV_stream_remote + #undef EGL_NV_stream_reset + #undef EGL_NV_stream_socket + #undef EGL_NV_stream_socket_inet + #undef EGL_NV_stream_socket_unix + #undef EGL_NV_stream_sync + #undef EGL_NV_sync + #undef EGL_NV_system_time + #undef EGL_NV_triple_buffer + #undef EGL_TIZEN_image_native_buffer + #undef EGL_TIZEN_image_native_surface + #undef EGL_WL_bind_wayland_display + #undef EGL_WL_create_wayland_buffer_from_image + #undef EGL_NV_native_query + #undef EGL_NV_post_convert_rounding + #undef EGL_NV_post_sub_buffer + #undef EGL_NV_quadruple_buffer + #undef EGL_NV_robustness_video_memory_purge + #undef EGL_NV_stream_consumer_eglimage + #undef EGL_NV_stream_consumer_gltexture_yuv + #undef EGL_NV_stream_cross_display + #undef EGL_NV_stream_cross_object + #undef EGL_NV_stream_cross_partition + #undef EGL_NV_stream_cross_process + #undef EGL_NV_stream_cross_system + #undef EGL_NV_stream_dma + #undef EGL_NV_stream_fifo_next + #undef EGL_NV_stream_fifo_synchronous + #undef EGL_NV_stream_flush + #undef EGL_NV_stream_frame_limits + #undef EGL_NV_stream_metadata + #undef EGL_NV_stream_origin + #undef EGL_NV_stream_remote + #undef EGL_NV_stream_reset + #undef EGL_NV_stream_socket + #undef EGL_NV_stream_socket_inet + #undef EGL_NV_stream_socket_unix + #undef EGL_NV_stream_sync + #undef EGL_NV_sync + #undef EGL_NV_system_time + #undef EGL_NV_triple_buffer + #undef EGL_TIZEN_image_native_buffer + #undef EGL_TIZEN_image_native_surface + #undef EGL_WL_bind_wayland_display + #undef EGL_WL_create_wayland_buffer_from_image + +#else + + #include + #include + +#endif + +// Older egl.h doesn't define typedefs for standard EGL functions, for example on debian buster. +// Define them ourselves if necessary. +// +// We don't use the function typedefs for functions part of 1.4, since +// those should be present at all times and we just statically use them. +// +// For functions part of EGL 1.5 we dynamically resolve the functions at +// runtime, since we can't be sure they're actually present. +#if defined(EGL_VERSION_1_5) && !defined(EGL_EGL_PROTOTYPES) + +// clang-format off +typedef EGLSync(EGLAPIENTRYP PFNEGLCREATESYNCPROC)(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); +typedef EGLBoolean(EGLAPIENTRYP PFNEGLDESTROYSYNCPROC)(EGLDisplay dpy, EGLSync sync); +typedef EGLint(EGLAPIENTRYP PFNEGLCLIENTWAITSYNCPROC)(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); +typedef EGLBoolean(EGLAPIENTRYP PFNEGLGETSYNCATTRIBPROC)(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); +typedef EGLImage(EGLAPIENTRYP PFNEGLCREATEIMAGEPROC)( + EGLDisplay dpy, + EGLContext ctx, + EGLenum target, + EGLClientBuffer buffer, + const EGLAttrib *attrib_list +); +typedef EGLBoolean(EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC)(EGLDisplay dpy, EGLImage image); +typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC)(EGLenum platform, void *native_display, const EGLAttrib *attrib_list); +typedef EGLSurface(EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEPROC)( + EGLDisplay dpy, + EGLConfig config, + void *native_window, + const EGLAttrib *attrib_list +); +typedef EGLSurface(EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC)( + EGLDisplay dpy, + EGLConfig config, + void *native_pixmap, + const EGLAttrib *attrib_list +); +typedef EGLBoolean(EGLAPIENTRYP PFNEGLWAITSYNCPROC)(EGLDisplay dpy, EGLSync sync, EGLint flags); + // clang-format on + +#endif + +#ifdef HAVE_EGL +static inline bool check_egl_extension(const char *client_ext_string, const char *display_ext_string, const char *extension) { + size_t len = strlen(extension); + + if (client_ext_string != NULL) { + const char *result = strstr(client_ext_string, extension); + if (result != NULL && (result[len] == ' ' || result[len] == '\0')) { + return true; + } + } + + if (display_ext_string != NULL) { + const char *result = strstr(display_ext_string, extension); + if (result != NULL && (result[len] == ' ' || result[len] == '\0')) { + return true; + } + } + + return false; +} + +static inline const char *egl_strerror(EGLenum result) { + switch (result) { + case EGL_SUCCESS: return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; + case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; + case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; + case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; + case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; + case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; + case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; + case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; + default: return ""; + } +} + + #define LOG_EGL_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ egl_strerror(result)) +#endif + +#endif // _FLUTTERPI_SRC_EGL_H diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c new file mode 100644 index 00000000..5cd66195 --- /dev/null +++ b/src/egl_gbm_render_surface.c @@ -0,0 +1,731 @@ +// SPDX-License-Identifier: MIT +/* + * GBM Surface Backing Stores + * + * - implements EGL/GL render surfaces using a gbm surface + * - ideal way to create render surfaces right now + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "egl_gbm_render_surface.h" + +#include +#include + +#include "egl.h" +#include "gl_renderer.h" +#include "gles.h" +#include "modesetting.h" +#include "pixel_format.h" +#include "render_surface.h" +#include "render_surface_private.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +struct egl_gbm_render_surface; + +struct locked_fb { + atomic_flag is_locked; + struct egl_gbm_render_surface *surface; + struct gbm_bo *bo; + refcount_t n_refs; +}; + +struct egl_gbm_render_surface { + union { + struct surface surface; + struct render_surface render_surface; + }; + +#ifdef DEBUG + uuid_t uuid; +#endif + + enum pixfmt pixel_format; + struct gbm_device *gbm_device; + struct gbm_surface *gbm_surface; + struct gbm_bo *front_buffer; + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLConfig egl_config; + struct gl_renderer *renderer; + + // Internally mesa supports 4 GBM BOs per surface, so we don't need + // more than 4 here either. + struct locked_fb locked_fbs[4]; + struct locked_fb *locked_front_fb; +#ifdef DEBUG + atomic_int n_locked_fbs; +#endif +}; + +COMPILE_ASSERT(offsetof(struct egl_gbm_render_surface, surface) == 0); +COMPILE_ASSERT(offsetof(struct egl_gbm_render_surface, render_surface.surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0xf9, 0xc2, 0x5d, 0xad, 0x2e, 0x3b, 0x4e, 0x2c, 0x9d, 0x26, 0x64, 0x70, 0xfa, 0x9a, 0x25, 0xd9); +#endif + +#define CAST_THIS(ptr) CAST_EGL_GBM_RENDER_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr) + +static void locked_fb_destroy(struct locked_fb *fb) { + struct egl_gbm_render_surface *s; + + s = fb->surface; + fb->surface = NULL; + gbm_surface_release_buffer(s->gbm_surface, fb->bo); +#ifdef DEBUG + atomic_fetch_sub(&s->n_locked_fbs, 1); +#endif + atomic_flag_clear(&fb->is_locked); + surface_unref(CAST_SURFACE(s)); +} + +DEFINE_STATIC_REF_OPS(locked_fb, n_refs) + +#ifdef DEBUG +ATTR_PURE struct egl_gbm_render_surface *__checked_cast_egl_gbm_render_surface(void *ptr) { + struct egl_gbm_render_surface *s; + + s = CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr); + assert(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +void egl_gbm_render_surface_deinit(struct surface *s); +static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int +egl_gbm_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int egl_gbm_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store); +static int egl_gbm_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store); + +static int egl_gbm_render_surface_init( + struct egl_gbm_render_surface *s, + struct tracer *tracer, + struct vec2i size, + struct gbm_device *gbm_device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config, + const uint64_t *allowed_modifiers, + size_t n_allowed_modifiers +) { + struct gbm_surface *gbm_surface; + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLBoolean egl_ok; + int ok; + + ASSERT_NOT_NULL(renderer); + ASSUME_PIXFMT_VALID(pixel_format); + egl_display = gl_renderer_get_egl_display(renderer); + ASSERT_NOT_NULL(egl_display); + +#ifdef DEBUG + if (egl_config != EGL_NO_CONFIG_KHR) { + EGLint value = 0; + + egl_ok = eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &value); + if (egl_ok == EGL_FALSE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EIO; + } + + ASSERT_EQUALS_MSG( + value, + get_pixfmt_info(pixel_format)->gbm_format, + "EGL framebuffer config pixel format doesn't match the argument pixel format." + ); + } +#endif + + if (allowed_modifiers != NULL) { + gbm_surface = gbm_surface_create_with_modifiers( + gbm_device, + size.x, + size.y, + get_pixfmt_info(pixel_format)->gbm_format, + allowed_modifiers, + n_allowed_modifiers + ); + if (gbm_surface == NULL) { + ok = errno; + LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s\n", strerror(ok)); + return ok; + } + } else { + gbm_surface = gbm_surface_create( + gbm_device, + size.x, + size.y, + get_pixfmt_info(pixel_format)->gbm_format, + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT + ); + if (gbm_surface == NULL) { + ok = errno; + LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create: %s\n", strerror(ok)); + return ok; + } + } + + /// TODO: Think about allowing different tilings / modifiers here + if (egl_config == EGL_NO_CONFIG_KHR) { + // choose a config + egl_config = gl_renderer_choose_config_direct(renderer, pixel_format); + if (egl_config == EGL_NO_CONFIG_KHR) { + LOG_ERROR( + "EGL doesn't supported the specified pixel format %s. Try a different one (ARGB8888 should always work).\n", + get_pixfmt_info(pixel_format)->name + ); + ok = EINVAL; + goto fail_destroy_gbm_surface; + } + } + +// EGLAttribKHR is defined by EGL_KHR_cl_event2. +#ifndef EGL_KHR_cl_event2 + #error "EGL header definitions for extension EGL_KHR_cl_event2 are required." +#endif + + static const EGLAttribKHR surface_attribs[] = { + /* EGL_GL_COLORSPACE, GL_LINEAR / GL_SRGB */ + /* EGL_RENDER_BUFFER, EGL_BACK_BUFFER / EGL_SINGLE_BUFFER */ + /* EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_NONPRE / EGL_VG_ALPHA_FORMAT_PRE */ + /* EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_sRGB / EGL_VG_COLORSPACE_LINEAR */ + EGL_NONE, + }; + + (void) surface_attribs; + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok == EGL_FALSE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't bind OpenGL ES API to EGL. eglBindAPI"); + ok = EIO; + goto fail_destroy_gbm_surface; + } + + egl_surface = gl_renderer_create_gbm_window_surface(renderer, egl_config, gbm_surface, NULL, NULL); + if (egl_surface == EGL_NO_SURFACE) { + ok = EIO; + goto fail_destroy_gbm_surface; + } + + /// TODO: Implement + ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(s), tracer, size); + if (ok != 0) { + goto fail_destroy_egl_surface; + } + + s->surface.present_kms = egl_gbm_render_surface_present_kms; + s->surface.present_fbdev = egl_gbm_render_surface_present_fbdev; + s->surface.deinit = egl_gbm_render_surface_deinit; + s->render_surface.fill = egl_gbm_render_surface_fill; + s->render_surface.queue_present = egl_gbm_render_surface_queue_present; +#ifdef DEBUG + uuid_copy(&s->uuid, uuid); +#endif + s->pixel_format = pixel_format; + s->gbm_device = gbm_device; + s->gbm_surface = gbm_surface; + s->egl_display = egl_display; + s->egl_surface = egl_surface; + s->egl_config = egl_config; + s->renderer = gl_renderer_ref(renderer); + for (int i = 0; i < ARRAY_SIZE(s->locked_fbs); i++) { + s->locked_fbs->is_locked = (atomic_flag) ATOMIC_FLAG_INIT; + } + s->locked_front_fb = NULL; + return 0; + +fail_destroy_egl_surface: + eglDestroySurface(egl_display, egl_surface); + +fail_destroy_gbm_surface: + gbm_surface_destroy(gbm_surface); + return ok; +} + +/** + * @brief Create a new gbm_surface based render surface, with an explicit EGL Config for the created EGLSurface. + * + * @param compositor The compositor that this surface will be registered to when calling surface_register. + * @param size The size of the surface. + * @param device The GBM device used to allocate the surface. + * @param renderer The EGL/OpenGL used to create any GL surfaces. + * @param pixel_format The pixel format to be used by the framebuffers of the surface. + * @param egl_config The EGLConfig used for creating the EGLSurface. + * @param allowed_modifiers The list of modifiers that gbm_surface_create_with_modifiers can choose from. + * NULL if not specified. (In that case, gbm_surface_create will be used) + * @param n_allowed_modifiers The number of modifiers in @param allowed_modifiers. + * @return struct egl_gbm_render_surface* + */ +struct egl_gbm_render_surface *egl_gbm_render_surface_new_with_egl_config( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config, + const uint64_t *allowed_modifiers, + size_t n_allowed_modifiers +) { + struct egl_gbm_render_surface *surface; + int ok; + + surface = malloc(sizeof *surface); + if (surface == NULL) { + goto fail_return_null; + } + + ok = egl_gbm_render_surface_init( + surface, + tracer, + size, + device, + renderer, + pixel_format, + egl_config, + allowed_modifiers, + n_allowed_modifiers + ); + if (ok != 0) { + goto fail_free_surface; + } + + return surface; + +fail_free_surface: + free(surface); + +fail_return_null: + return NULL; +} + +/** + * @brief Create a new gbm_surface based render surface. + * + * @param compositor The compositor that this surface will be registered to when calling surface_register. + * @param size The size of the surface. + * @param device The GBM device used to allocate the surface. + * @param renderer The EGL/OpenGL used to create any GL surfaces. + * @param pixel_format The pixel format to be used by the framebuffers of the surface. + * @return struct egl_gbm_render_surface* + */ +struct egl_gbm_render_surface *egl_gbm_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format +) { + return egl_gbm_render_surface_new_with_egl_config(tracer, size, device, renderer, pixel_format, EGL_NO_CONFIG_KHR, NULL, 0); +} + +void egl_gbm_render_surface_deinit(struct surface *s) { + struct egl_gbm_render_surface *egl_surface; + + egl_surface = CAST_EGL_GBM_RENDER_SURFACE(s); + + gl_renderer_unref(egl_surface->renderer); + render_surface_deinit(s); +} + +struct gbm_bo_meta { + struct drmdev *drmdev; + uint32_t fb_id; + + bool has_opaque_fb_id; + uint32_t opaque_fb_id; +}; + +static void on_destroy_gbm_bo_meta(struct gbm_bo *bo, void *meta_void) { + struct gbm_bo_meta *meta; + + ASSERT_NOT_NULL(bo); + ASSERT_NOT_NULL(meta_void); + (void) bo; + meta = meta_void; + + drmdev_rm_fb(meta->drmdev, meta->fb_id); + if (meta->has_opaque_fb_id) { + drmdev_rm_fb(meta->drmdev, meta->opaque_fb_id); + } + drmdev_unref(meta->drmdev); + free(meta); +} + +static void on_release_layer(void *userdata) { + struct locked_fb *fb; + + ASSERT_NOT_NULL(userdata); + + fb = userdata; + locked_fb_unref(fb); +} + +static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct egl_gbm_render_surface *egl_surface; + struct gbm_bo_meta *meta; + struct drmdev *drmdev; + struct gbm_bo *bo; + enum pixfmt pixel_format; + uint32_t fb_id, opaque_fb_id; + int ok; + + egl_surface = CAST_THIS(s); + + /// TODO: Implement non axis-aligned fl_layer_props + ASSERT_MSG(props->is_aa_rect, "only axis aligned view geometry is supported right now"); + + surface_lock(s); + + ASSERT_NOT_NULL_MSG( + egl_surface->locked_front_fb, + "There's no framebuffer available for scanout right now. Make sure you called render_surface_queue_present() before presenting." + ); + + bo = egl_surface->locked_front_fb->bo; + meta = gbm_bo_get_user_data(bo); + if (meta == NULL) { + meta = malloc(sizeof *meta); + if (meta == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + drmdev = kms_req_builder_get_drmdev(builder); + ASSERT_NOT_NULL(drmdev); + + TRACER_BEGIN(egl_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); + fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + egl_surface->pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, + gbm_bo_get_modifier(bo) + ); + TRACER_END(egl_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); + + if (fb_id == 0) { + ok = EIO; + LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); + goto fail_free_meta; + } + + // if this EGL surface is non-opaque and has an opaque equivalent + if (!get_pixfmt_info(egl_surface->pixel_format)->is_opaque && + pixfmt_opaque(egl_surface->pixel_format) != egl_surface->pixel_format) { + opaque_fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + pixfmt_opaque(egl_surface->pixel_format), + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, + gbm_bo_get_modifier(bo) + ); + if (opaque_fb_id == 0) { + ok = EIO; + LOG_ERROR("Couldn't add GBM buffer as opaque DRM framebuffer.\n"); + goto fail_remove_fb; + } + + meta->has_opaque_fb_id = true; + meta->opaque_fb_id = opaque_fb_id; + } else { + meta->has_opaque_fb_id = false; + meta->opaque_fb_id = 0; + } + + meta->drmdev = drmdev_ref(drmdev); + meta->fb_id = fb_id; + gbm_bo_set_user_data(bo, meta, on_destroy_gbm_bo_meta); + } else { + // We can only add this GBM BO to a single KMS device as an fb right now. + ASSERT_EQUALS_MSG( + meta->drmdev, + kms_req_builder_get_drmdev(builder), + "Currently GBM BOs can only be scanned out on a single KMS device for their whole lifetime." + ); + } + + /* + LOG_DEBUG( + "egl_gbm_render_surface_present_kms:\n" + " src_x, src_y, src_w, src_h: %f %f %f %f\n" + " dst_x, dst_y, dst_w, dst_h: %f %f %f %f\n", + 0.0, 0.0, + s->render_surface.size.x, + s->render_surface.size.y, + props->aa_rect.offset.x, + props->aa_rect.offset.y, + props->aa_rect.size.x, + props->aa_rect.size.y + ); + */ + + // So we just cast our fb to an XRGB8888 framebuffer and scanout that instead. + fb_id = meta->fb_id; + pixel_format = egl_surface->pixel_format; + + // The bottom-most layer should preferably be an opaque layer. + // We could also try non-opaque first and fallback to opaque if not supported though. + if (kms_req_builder_prefer_next_layer_opaque(builder) && meta->has_opaque_fb_id) { + fb_id = meta->opaque_fb_id; + pixel_format = pixfmt_opaque(egl_surface->pixel_format); + } + + TRACER_BEGIN(egl_surface->surface.tracer, "kms_req_builder_push_fb_layer"); + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer){ + .drm_fb_id = fb_id, + .format = pixel_format, + .has_modifier = gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, + .modifier = gbm_bo_get_modifier(bo), + + .dst_x = (int32_t) props->aa_rect.offset.x, + .dst_y = (int32_t) props->aa_rect.offset.y, + .dst_w = (uint32_t) props->aa_rect.size.x, + .dst_h = (uint32_t) props->aa_rect.size.y, + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(egl_surface->render_surface.size.x), + .src_h = DOUBLE_TO_FP1616_ROUNDED(egl_surface->render_surface.size.y), + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + + .has_in_fence_fd = false, + .in_fence_fd = 0, + }, + on_release_layer, + NULL, + locked_fb_ref(egl_surface->locked_front_fb) + ); + TRACER_END(egl_surface->surface.tracer, "kms_req_builder_push_fb_layer"); + if (ok != 0) { + goto fail_unref_locked_fb; + } + + surface_unlock(s); + return ok; + +fail_unref_locked_fb: + locked_fb_unref(egl_surface->locked_front_fb); + goto fail_unlock; + +fail_remove_fb: + drmdev_rm_fb(drmdev, fb_id); + +fail_free_meta: + free(meta); + +fail_unlock: + surface_unlock(s); + return ok; +} + +static int +egl_gbm_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct egl_gbm_render_surface *egl_surface; + + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev + /// TODO: Print a warning here if we're not using explicit linear tiling and use glReadPixels instead of gbm_bo_map in that case + + egl_surface = CAST_THIS(s); + (void) egl_surface; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + +static int egl_gbm_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { + fl_store->type = kFlutterBackingStoreTypeOpenGL; + fl_store->open_gl = (FlutterOpenGLBackingStore + ){ .type = kFlutterOpenGLTargetTypeFramebuffer, + .framebuffer = { /* for some reason flutter wants this to be GL_BGRA8_EXT, contrary to what the docs say */ + .target = GL_BGRA8_EXT, + + /* 0 refers to the window surface, instead of to an FBO */ + .name = 0, + + /* + * even though the compositor will call surface_ref too to fill the FlutterBackingStore.user_data, + * we need to ref two times because flutter will call both this destruction callback and the + * compositor collect callback + */ + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(s)), + .destruction_callback = surface_unref_void } }; + return 0; +} + +static int egl_gbm_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { + UNUSED struct egl_gbm_render_surface *egl_surface; + struct gbm_bo *bo; + UNUSED EGLBoolean egl_ok; + int i, ok; + + egl_surface = CAST_THIS(s); + (void) fl_store; + + surface_lock(CAST_SURFACE(s)); + + /// TODO: Handle fl_store->did_update == false here + + // Unref the old front fb so potentially one of the locked_fbs entries gets freed + if (egl_surface->locked_front_fb != NULL) { + locked_fb_unrefp(&egl_surface->locked_front_fb); + } + + assert(gbm_surface_has_free_buffers(egl_surface->gbm_surface)); + + // create the in fence here + TRACER_BEGIN(s->surface.tracer, "eglSwapBuffers"); + egl_ok = eglSwapBuffers(egl_surface->egl_display, egl_surface->egl_surface); + TRACER_END(s->surface.tracer, "eglSwapBuffers"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't flush rendering. eglSwapBuffers"); + return EIO; + } + + TRACER_BEGIN(s->surface.tracer, "gbm_surface_lock_front_buffer"); + bo = gbm_surface_lock_front_buffer(egl_surface->gbm_surface); + TRACER_END(s->surface.tracer, "gbm_surface_lock_front_buffer"); + + if (bo == NULL) { + ok = errno; + LOG_ERROR("Couldn't lock GBM front buffer. gbm_surface_lock_front_buffer: %s\n", strerror(ok)); + goto fail_unlock; + } + + // Try to find & lock a locked_fb we can use. + // Note we use atomics here even though we hold the surfaces' mutex because + // releasing a locked_fb is possibly done without the mutex. + for (i = 0; i < ARRAY_SIZE(egl_surface->locked_fbs); i++) { + if (atomic_flag_test_and_set(&egl_surface->locked_fbs[i].is_locked) == false) { + goto locked; + } + } + + // If we reached this point, we couldn't find lock one of the 4 locked_fbs. + // Which shouldn't happen except we have an application bug. + + /// FIXME: This sometimes (rarely) fails with EGL surface backing stores. + /// See below: + /// + /// pi@hpi4:~ $ LD_LIBRARY_PATH=~/mesa-install/lib/arm-linux-gnueabihf/ LIBGL_DRIVERS_PATH=~/mesa-install/lib/arm-linux-gnueabihf/dri/ ~/devel/flutterpi-install/bin/flutter-pi ~/devel/flutterpi_platform_view_test_debug/ --vm-service-host=192.168.178.11 + /// ==============Locale============== + /// Flutter locale: + /// default: de_DE + /// locales: de_DE de.UTF-8 de.UTF-8 de.UTF-8 de_DE de de.UTF-8 + /// =================================== + /// =================================== + /// EGL information: + /// version: 1.4 + /// vendor: Mesa Project + /// client extensions: EGL_EXT_client_extensions EGL_EXT_device_base EGL_EXT_device_enumeration EGL_EXT_device_query EGL_EXT_platform_base EGL_KHR_client_get_all_proc_addresses EGL_KHR_debug EGL_EXT_platform_device EGL_MESA_platform_gbm EGL_KHR_platform_gbm EGL_MESA_platform_surfaceless + /// display extensions: EGL_ANDROID_blob_cache EGL_EXT_buffer_age EGL_EXT_image_dma_buf_import EGL_EXT_image_dma_buf_import_modifiers EGL_KHR_cl_event2 EGL_KHR_config_attribs EGL_KHR_context_flush_control EGL_KHR_create_context EGL_KHR_create_context_no_error EGL_KHR_fence_sync EGL_KHR_get_all_proc_addresses EGL_KHR_gl_colorspace EGL_KHR_gl_renderbuffer_image EGL_KHR_gl_texture_2D_image EGL_KHR_gl_texture_3D_image EGL_KHR_gl_texture_cubemap_image EGL_KHR_image EGL_KHR_image_base EGL_KHR_image_pixmap EGL_KHR_no_config_context EGL_KHR_reusable_sync EGL_KHR_surfaceless_context EGL_EXT_pixel_format_float EGL_KHR_wait_sync EGL_MESA_configless_context EGL_MESA_drm_image EGL_MESA_image_dma_buf_export EGL_MESA_query_driver + /// =================================== + /// =================================== + /// OpenGL ES information: + /// version: "OpenGL ES 3.1 Mesa 23.2.0-devel (git-8bfd18b8c5)" + /// shading language version: "OpenGL ES GLSL ES 3.10" + /// vendor: "Broadcom" + /// renderer: "V3D 4.2" + /// extensions: "GL_EXT_blend_minmax GL_EXT_multi_draw_arrays GL_EXT_texture_filter_anisotropic GL_EXT_texture_compression_s3tc GL_EXT_texture_compression_dxt1 GL_EXT_texture_compression_rgtc GL_EXT_texture_format_BGRA8888 GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth24 GL_OES_element_index_uint GL_OES_fbo_render_mipmap GL_OES_mapbuffer GL_OES_rgb8_rgba8 GL_OES_standard_derivatives GL_OES_stencil8 GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float GL_EXT_draw_instanced GL_EXT_texture_sRGB_decode GL_OES_EGL_image GL_OES_depth_texture GL_AMD_performance_monitor GL_OES_packed_depth_stencil GL_EXT_texture_type_2_10_10_10_REV GL_NV_conditional_render GL_OES_get_program_binary GL_APPLE_texture_max_level GL_EXT_discard_framebuffer GL_EXT_read_format_bgra GL_NV_pack_subimage GL_EXT_frag_depth GL_NV_fbo_color_attachments GL_OES_EGL_image_external GL_OES_EGL_sync GL_OES_vertex_array_object GL_ANGLE_pack_reverse_row_order GL_ANGLE_texture_compression_dxt3 GL_ANGLE_texture_compression_dxt5 GL_EXT_occlusion_query_boolean GL_EXT_texture_rg GL_EXT_unpack_subimage GL_NV_draw_buffers GL_NV_read_buffer GL_NV_read_depth GL_NV_read_depth_stencil GL_NV_read_stencil GL_EXT_draw_buffers GL_EXT_instanced_arrays GL_EXT_map_buffer_range GL_KHR_debug GL_KHR_texture_compression_astc_ldr GL_NV_generate_mipmap_sRGB GL_NV_pixel_buffer_object GL_OES_depth_texture_cube_map GL_OES_required_internalformat GL_OES_surfaceless_context GL_EXT_color_buffer_float GL_EXT_debug_label GL_EXT_sRGB_write_control GL_EXT_separate_shader_objects GL_EXT_shader_implicit_conversions GL_EXT_shader_integer_mix GL_EXT_base_instance GL_EXT_compressed_ETC1_RGB8_sub_texture GL_EXT_copy_image GL_EXT_draw_buffers_indexed GL_EXT_draw_elements_base_vertex GL_EXT_polygon_offset_clamp GL_EXT_primitive_bounding_box GL_EXT_shader_io_blocks GL_EXT_texture_border_clamp GL_EXT_texture_cube_map_array GL_EXT_texture_view GL_KHR_context_flush_control GL_NV_image_formats GL_NV_shader_noperspective_interpolation GL_OES_copy_image GL_OES_draw_buffers_indexed GL_OES_draw_elements_base_vertex GL_OES_primitive_bounding_box GL_OES_shader_io_blocks GL_OES_texture_border_clamp GL_OES_texture_cube_map_array GL_OES_texture_stencil8 GL_OES_texture_storage_multisample_2d_array GL_OES_texture_view GL_EXT_buffer_storage GL_EXT_float_blend GL_EXT_geometry_point_size GL_EXT_geometry_shader GL_KHR_no_error GL_KHR_texture_compression_astc_sliced_3d GL_OES_EGL_image_external_essl3 GL_OES_geometry_point_size GL_OES_geometry_shader GL_OES_shader_image_atomic GL_EXT_texture_compression_s3tc_srgb GL_MESA_shader_integer_functions GL_EXT_texture_mirror_clamp_to_edge GL_KHR_parallel_shader_compile GL_EXT_EGL_image_storage GL_MESA_framebuffer_flip_y GL_EXT_texture_query_lod GL_MESA_bgra " + /// =================================== + /// window.c: INFO: display has non-square pixels. Non-square-pixels are not supported by flutter. + /// display mode: + /// resolution: 800 x 480 + /// refresh rate: 59.928489Hz + /// physical size: 154mm x 86mm + /// flutter device pixel ratio: 1.367054 + /// pixel format: (any) + /// pluginregistry.c: Initialized plugins: services, text input, raw keyboard plugin, gstreamer video_player, audioplayers, + /// flutter: The Dart VM service is listening on http://192.168.178.11:44515/F3wK7cUNFd0=/ + /// gl_renderer.c: Choosing EGL config with pixel format ARGB 8:8:8:8... + /// gl_renderer.c: Choosing EGL config with pixel format ARGB 8:8:8:8... + /// compositor_ng.c: fl_layer_props[1]: + /// is_aa_rect: yes + /// aa_rect: offset: (0,000000, 76,555023), size: (799,999992, 403,444973) + /// quad: top left: (0,000000, 76,555023), top right: (799,999992, 76,555023), bottom left: (0,000000, 479,999996), bottom right: (799,999992, 479,999996) + /// opacity: 1,000000 + /// rotation: 0,000000 + /// n_clip_rects: 0 + /// clip_rects: (nil) + /// egl_gbm_render_surface.c: egl_gbm_render_surface_present_kms: + /// src_x, src_y, src_w, src_h: 0 0 800 480 + /// dst_x, dst_y, dst_w, dst_h: 0,000000 0,000000 800,000000 480,000000 + /// egl_gbm_render_surface.c: egl_gbm_render_surface_present_kms: + /// src_x, src_y, src_w, src_h: 0 0 800 480 + /// dst_x, dst_y, dst_w, dst_h: 0,000000 76,555023 799,999992 403,444973 + /// egl_gbm_render_surface.c: egl_gbm_render_surface_present_kms: + /// src_x, src_y, src_w, src_h: 0 0 800 480 + /// dst_x, dst_y, dst_w, dst_h: 0,000000 0,000000 800,000000 480,000000 + /// flutter-pi: egl_gbm_render_surface.c:641: int egl_gbm_render_surface_queue_present(struct render_surface *, const FlutterBackingStore *): Zusicherung »(0) && ("Couldn't find a free slot to lock the surfaces front framebuffer.")« nicht erfüllt. + + ASSERT_MSG(false, "Couldn't find a free slot to lock the surfaces front framebuffer."); + ok = EIO; + goto fail_release_bo; + +locked: + /// TODO: Remove this once we're using triple buffering + //ASSERT_MSG(atomic_fetch_add(&render_surface->n_locked_fbs, 1) <= 1, "sanity check failed: too many locked fbs for double-buffered vsync"); + egl_surface->locked_fbs[i].bo = bo; + egl_surface->locked_fbs[i].surface = CAST_THIS(surface_ref(CAST_SURFACE(s))); + egl_surface->locked_fbs[i].n_refs = REFCOUNT_INIT_1; + egl_surface->locked_front_fb = egl_surface->locked_fbs + i; + surface_unlock(CAST_SURFACE(s)); + return 0; + +fail_release_bo: + gbm_surface_release_buffer(egl_surface->gbm_surface, bo); + +fail_unlock: + surface_unlock(CAST_SURFACE(s)); + return ok; +} + +/** + * @brief Get the EGL Surface for rendering into this render surface. + * + * Flutter doesn't really support backing stores to be EGL Surfaces, so we have to hack around this, kinda. + * + * @param s + * @return EGLSurface The EGLSurface associated with this render surface. Only valid for the lifetime of this egl_gbm_render_surface. + */ +ATTR_PURE EGLSurface egl_gbm_render_surface_get_egl_surface(struct egl_gbm_render_surface *s) { + return s->egl_surface; +} + +/** + * @brief Get the EGLConfig that was used to create the EGLSurface for this render surface. + * + * If the display doesn't support EGL_KHR_no_config_context, we need to create the EGL rendering context with + * the same EGLConfig as every EGLSurface we want to bind to it. So we can just let egl_gbm_render_surface choose a config + * and let flutter-pi query that config when creating the rendering contexts in that case. + * + * @param s + * @return EGLConfig The chosen EGLConfig. Valid forever. + */ +ATTR_PURE EGLConfig egl_gbm_render_surface_get_egl_config(struct egl_gbm_render_surface *s) { + return s->egl_config; +} diff --git a/src/egl_gbm_render_surface.h b/src/egl_gbm_render_surface.h new file mode 100644 index 00000000..ad7f0284 --- /dev/null +++ b/src/egl_gbm_render_surface.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +/* + * GBM Surface backing store + * + * - public interface for backing stores which are based on GBM surfaces + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H +#define _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H + +#include "compositor_ng.h" +#include "pixel_format.h" +#include "util/collection.h" + +struct render_surface; +struct gbm_device; +struct egl_gbm_render_surface; + +#define CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr) ((struct egl_gbm_render_surface *) (ptr)) +#ifdef DEBUG + #define CAST_EGL_GBM_RENDER_SURFACE(ptr) __checked_cast_egl_gbm_render_surface(ptr) +ATTR_PURE struct egl_gbm_render_surface *__checked_cast_egl_gbm_render_surface(void *ptr); +#else + #define CAST_EGL_GBM_RENDER_SURFACE(ptr) CAST_EGL_GBM_RENDER_SURFACE_UNCHECKED(ptr) +#endif + +struct egl_gbm_render_surface *egl_gbm_render_surface_new_with_egl_config( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format, + EGLConfig egl_config, + const uint64_t *allowed_modifiers, + size_t n_allowed_modifiers +); + +struct egl_gbm_render_surface *egl_gbm_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct gl_renderer *renderer, + enum pixfmt pixel_format +); + +ATTR_PURE EGLSurface egl_gbm_render_surface_get_egl_surface(struct egl_gbm_render_surface *s); + +ATTR_PURE EGLConfig egl_gbm_render_surface_get_egl_config(struct egl_gbm_render_surface *s); + +#endif // _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H diff --git a/src/event_loop.c b/src/event_loop.c new file mode 100644 index 00000000..70213c84 --- /dev/null +++ b/src/event_loop.c @@ -0,0 +1,581 @@ +#include "event_loop.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "util/collection.h" +#include "util/refcounting.h" + +struct evloop { + refcount_t n_refs; + pthread_mutex_t mutex; + sd_event *sdloop; + int wakeup_fd; + pthread_t owning_thread; +}; + +DEFINE_STATIC_LOCK_OPS(evloop, mutex) + +static int on_wakeup_event_loop(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + uint8_t buffer[8]; + int ok; + + (void) s; + (void) revents; + (void) userdata; + + ok = read(fd, buffer, 8); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not read eventloop wakeup userdata. read: %s\n", strerror(ok)); + return ok; + } + + return 0; +} + +struct evloop *evloop_new() { + struct evloop *loop; + sd_event *sdloop; + int ok, wakeup_fd; + + loop = malloc(sizeof *loop); + if (loop == NULL) { + return NULL; + } + + ok = sd_event_new(&sdloop); + if (ok != 0) { + LOG_ERROR("Couldn't create libsystemd event loop. sd_event_new: %s\n", strerror(-ok)); + goto fail_free_loop; + } + + wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (wakeup_fd < 0) { + LOG_ERROR("Could not create fd for waking up the main loop. eventfd: %s\n", strerror(errno)); + goto fail_unref_sdloop; + } + + ok = sd_event_add_io(sdloop, NULL, wakeup_fd, EPOLLIN, on_wakeup_event_loop, NULL); + if (ok < 0) { + LOG_ERROR("Error adding wakeup callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + goto fail_unref_sdloop; + } + + loop->n_refs = REFCOUNT_INIT_1; + pthread_mutex_init(&loop->mutex, get_default_mutex_attrs()); + loop->sdloop = sdloop; + loop->wakeup_fd = wakeup_fd; + loop->owning_thread = pthread_self(); + return loop; + +fail_unref_sdloop: + sd_event_unref(sdloop); + +fail_free_loop: + free(loop); + return NULL; +} + +void evloop_destroy(struct evloop *loop) { + sd_event_unref(loop->sdloop); + close(loop->wakeup_fd); + pthread_mutex_destroy(&loop->mutex); + free(loop); +} + +DEFINE_REF_OPS(evloop, n_refs) + +int evloop_get_fd_locked(struct evloop *loop) { + ASSERT_NOT_NULL(loop); + ASSERT_MUTEX_LOCKED(loop->mutex); + return sd_event_get_fd(loop->sdloop); +} + +int evloop_get_fd(struct evloop *loop) { + int result; + + ASSERT_NOT_NULL(loop); + + evloop_lock(loop); + result = evloop_get_fd_locked(loop); + evloop_unlock(loop); + + return result; +} + +int evloop_run(struct evloop *loop) { + int evloop_fd; + int ok; + + ASSERT_NOT_NULL(loop); + + evloop_fd = evloop_get_fd(loop); + + { + fd_set rfds, wfds, xfds; + int state; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + FD_SET(evloop_fd, &rfds); + FD_SET(evloop_fd, &wfds); + FD_SET(evloop_fd, &xfds); + + const fd_set const_fds = rfds; + + evloop_lock(loop); + + do { + state = sd_event_get_state(loop->sdloop); + switch (state) { + case SD_EVENT_INITIAL: + ok = sd_event_prepare(loop->sdloop); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not prepare event loop. sd_event_prepare: %s\n", strerror(ok)); + goto fail_unlock; + } + + break; + case SD_EVENT_ARMED: + evloop_unlock(loop); + + do { + rfds = const_fds; + wfds = const_fds; + xfds = const_fds; + ok = select(evloop_fd + 1, &rfds, &wfds, &xfds, NULL); + if ((ok < 0) && (errno != EINTR)) { + ok = errno; + LOG_ERROR("Could not wait for event loop events. select: %s\n", strerror(ok)); + goto fail; + } + } while ((ok < 0) && (errno == EINTR)); + + evloop_lock(loop); + + ok = sd_event_wait(loop->sdloop, 0); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not check for event loop events. sd_event_wait: %s\n", strerror(ok)); + goto fail_unlock; + } + + break; + case SD_EVENT_PENDING: + ok = sd_event_dispatch(loop->sdloop); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not dispatch event loop events. sd_event_dispatch: %s\n", strerror(ok)); + goto fail_unlock; + } + + break; + case SD_EVENT_FINISHED: break; + default: UNREACHABLE(); + } + } while (state != SD_EVENT_FINISHED); + + evloop_unlock(loop); + } + + return 0; + +fail_unlock: + evloop_unlock(loop); + +fail: + return ok; +} + +static int wakeup_sdloop(struct evloop *loop) { + int ok; + + ok = write(loop->wakeup_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); + if (ok < 0) { + ok = errno; + LOG_ERROR("Error waking up event loop. write: %s\n", strerror(ok)); + return ok; + } + + return 0; +} + +int evloop_schedule_exit_locked(struct evloop *loop) { + int ok; + + ASSERT_NOT_NULL(loop); + ASSERT_MUTEX_LOCKED(loop->mutex); + + ok = sd_event_exit(loop->sdloop, 0); + if (ok != 0) { + LOG_ERROR("Couldn't schedule event loop exit. sd_event_exit: %s\n", strerror(-ok)); + return -ok; + } + + return 0; +} + +int evloop_schedule_exit(struct evloop *loop) { + int ok; + + ASSERT_NOT_NULL(loop); + + evloop_lock(loop); + ok = evloop_schedule_exit_locked(loop); + evloop_unlock(loop); + + wakeup_sdloop(loop); + + return ok; +} + +struct task { + void_callback_t callback; + void *userdata; +}; + +static int on_execute_task(sd_event_source *s, void *userdata) { + struct task *task; + int ok; + + ASSERT_NOT_NULL(userdata); + task = userdata; + + task->callback(task->userdata); + free(task); + + sd_event_source_set_enabled(s, SD_EVENT_OFF); + sd_event_source_unref(s); + return 0; +} + +int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata) { + sd_event_source *src; + struct task *task; + int ok; + + ASSERT_NOT_NULL(loop); + ASSERT_NOT_NULL(callback); + ASSERT_MUTEX_LOCKED(loop->mutex); + + task = malloc(sizeof *task); + if (task == NULL) { + return ENOMEM; + } + + task->callback = callback; + task->userdata = userdata; + + ok = sd_event_add_defer(loop->sdloop, &src, on_execute_task, task); + if (ok < 0) { + LOG_ERROR("Error adding task to event loop. sd_event_add_defer: %s\n", strerror(-ok)); + ok = -ok; + goto fail_free_task; + } + + return 0; + +fail_remove_src: + sd_event_source_disable_unref(src); + +fail_free_task: + free(task); + return ok; +} + +int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userdata) { + int ok; + + ASSERT_NOT_NULL(loop); + ASSERT_NOT_NULL(callback); + + evloop_lock(loop); + ok = evloop_post_task_locked(loop, callback, userdata); + evloop_unlock(loop); + + wakeup_sdloop(loop); + + return ok; +} + +static int on_execute_delayed_task(sd_event_source *s, uint64_t usec, void *userdata) { + struct task *task; + + ASSERT_NOT_NULL(userdata); + task = userdata; + (void) usec; + + task->callback(task->userdata); + free(task); + + sd_event_source_disable_unref(s); + return 0; +} + +int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec) { + sd_event_source *src; + struct task *task; + int ok; + + ASSERT_NOT_NULL(loop); + ASSERT_NOT_NULL(callback); + ASSERT_MUTEX_LOCKED(loop->mutex); + + task = malloc(sizeof *task); + if (task == NULL) { + return ENOMEM; + } + + task->callback = callback; + task->userdata = userdata; + + ok = sd_event_add_time(loop->sdloop, &src, CLOCK_MONOTONIC, target_time_usec, 1, on_execute_delayed_task, task); + if (ok < 0) { + LOG_ERROR("Error posting platform task to main loop. sd_event_add_time: %s\n", strerror(-ok)); + ok = -ok; + goto fail_free_task; + } + + return 0; + +fail_remove_src: + sd_event_source_disable_unref(src); + +fail_free_task: + free(task); + return ok; +} + +int evloop_post_delayed_task(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec) { + int ok; + + ASSERT_NOT_NULL(loop); + ASSERT_NOT_NULL(callback); + + evloop_lock(loop); + ok = evloop_post_delayed_task_locked(loop, callback, userdata, target_time_usec); + evloop_unlock(loop); + + wakeup_sdloop(loop); + + return ok; +} + +struct evsrc { + struct evloop *loop; + sd_event_source *sdsrc; + + evloop_io_handler_t io_callback; + void *userdata; +}; + +void evsrc_destroy_locked(struct evsrc *src) { + ASSERT_MUTEX_LOCKED(src->loop->mutex); + sd_event_source_disable_unref(src->sdsrc); + evloop_unref(src->loop); + free(src); +} + +void evsrc_destroy(struct evsrc *src) { + evloop_lock(src->loop); + sd_event_source_disable_unref(src->sdsrc); + evloop_unlock(src->loop); + + evloop_unref(src->loop); + free(src); +} + +int on_io_src_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + enum event_handler_return handler_return; + struct evsrc *evsrc; + + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(userdata); + evsrc = userdata; + + handler_return = evsrc->io_callback(fd, revents, evsrc->userdata); + if (handler_return == kRemoveSrc_EventHandlerReturn) { + evsrc_destroy_locked(evsrc); + return -1; + } + + return 0; +} + +struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { + sd_event_source *src; + struct evsrc *evsrc; + int ok; + + evsrc = malloc(sizeof *evsrc); + if (evsrc == NULL) { + return NULL; + } + + ASSERT_NOT_NULL(loop); + ASSERT_MUTEX_LOCKED(loop->mutex); + + evsrc->io_callback = callback; + evsrc->userdata = userdata; + + ok = sd_event_add_io(loop->sdloop, &src, fd, events, on_io_src_ready, evsrc); + if (ok < 0) { + LOG_ERROR("Could not add IO callback to event loop. sd_event_add_io: %s\n", strerror(-ok)); + free(evsrc); + return NULL; + } + + evsrc->loop = evloop_ref(loop); + evsrc->sdsrc = sd_event_source_ref(src); + return evsrc; +} + +struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { + struct evsrc *src; + + ASSERT_NOT_NULL(loop); + ASSERT_NOT_NULL(callback); + + evloop_lock(loop); + src = evloop_add_io_locked(loop, fd, events, callback, userdata); + evloop_unlock(loop); + + wakeup_sdloop(loop); + + return src; +} + +struct evthread { + struct evloop *loop; + pthread_t thread; +}; + +struct evthread_startup_args { + struct evthread *evthread; + sem_t initialization_done; + bool initialization_success; +}; + +static void *evthread_entry(void *userdata) { + struct evthread *evthread; + struct evloop *evloop; + int ok; + + // initialization. + { + struct evthread_startup_args *args; + ASSERT_NOT_NULL(userdata); + args = userdata; + + evthread = malloc(sizeof *evthread); + if (evthread == NULL) { + goto fail_post_semaphore; + } + + evloop = evloop_new(); + if (evloop == NULL) { + goto fail_free_evthread; + } + + evthread->loop = evloop; + evthread->thread = pthread_self(); + + args->initialization_success = true; + sem_post(&args->initialization_done); + goto init_done; + +// error handling +fail_free_evthread: + free(evthread); + +fail_post_semaphore: + args->initialization_success = false; + sem_post(&args->initialization_done); + return NULL; + } + +init_done: + evloop_run(evloop); + sd_event_unrefp(&evthread->loop); + return NULL; +} + +struct evthread *evthread_start() { + struct evthread_startup_args *args; + struct evthread *evthread; + pthread_t tid; + int ok; + + args = malloc(sizeof *args); + if (args == NULL) { + return NULL; + } + + args->evthread = NULL; + args->initialization_success = false; + + ok = sem_init(&args->initialization_done, 0, 0); + if (ok != 0) { + goto fail_free_args; + } + + ok = pthread_create(&tid, NULL, evthread_entry, args); + if (ok != 0) { + LOG_ERROR("Could not create new event thread. pthread_create: %s\n", strerror(-ok)); + goto fail_deinit_semaphore; + } + + ok = sem_wait(&args->initialization_done); + if (ok != 0) { + LOG_ERROR("Couldn't wait for event thread initialization finish. sem_wait: %s\n", strerror(-ok)); + goto fail_cancel_thread; + } + + if (!args->initialization_success) { + ok = pthread_join(tid, NULL); + if (ok != 0) { + LOG_ERROR("Couldn't wait for event thread to finish. pthread_join: %s\n", strerror(-ok)); + goto fail_cancel_thread; + } + + sem_destroy(&args->initialization_done); + free(args); + return NULL; + } + + sem_destroy(&args->initialization_done); + + ASSERT_NOT_NULL(args->evthread); + evthread = args->evthread; + free(args); + + return evthread; + +fail_cancel_thread: + pthread_cancel(tid); + +fail_deinit_semaphore: + sem_destroy(&args->initialization_done); + +fail_free_args: + free(args); + return NULL; +} + +struct evloop *evthread_get_evloop(struct evthread *thread) { + return thread->loop; +} + +void evthread_stop(struct evthread *thread) { + evloop_schedule_exit(thread->loop); + pthread_join(thread->thread, NULL); + free(thread); +} diff --git a/src/event_loop.h b/src/event_loop.h new file mode 100644 index 00000000..62edabe7 --- /dev/null +++ b/src/event_loop.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +/* + * Event Loop + * + * - multithreaded event loop + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_EVENT_LOOP_H +#define _FLUTTERPI_SRC_EVENT_LOOP_H + +#include "util/refcounting.h" + +struct evloop; + +struct evloop *evloop_new(); + +void evloop_destroy(struct evloop *loop); + +DECLARE_REF_OPS(evloop) + +int evloop_get_fd_locked(struct evloop *loop); + +int evloop_get_fd(struct evloop *loop); + +int evloop_run(struct evloop *loop); + +int evloop_schedule_exit_locked(struct evloop *loop); + +int evloop_schedule_exit(struct evloop *loop); + +int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata); + +int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userdata); + +int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); + +int evloop_post_delayed_task(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); + +struct evsrc; +void evsrc_destroy_locked(struct evsrc *src); +void evsrc_destroy(struct evsrc *src); + +enum event_handler_return { kNoAction_EventHandlerReturn, kRemoveSrc_EventHandlerReturn }; + +typedef enum event_handler_return (*evloop_io_handler_t)(int fd, uint32_t revents, void *userdata); + +struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); + +struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); + +struct evthread; + +struct evthread *evthread_start(); + +struct evloop *evthread_get_evloop(struct evthread *thread); + +void evthread_join(struct evthread *thread); + +#endif // _FLUTTERPI_SRC_EVENT_LOOP_H diff --git a/src/filesystem_layout.c b/src/filesystem_layout.c index bb150aa5..39924c30 100644 --- a/src/filesystem_layout.c +++ b/src/filesystem_layout.c @@ -10,13 +10,16 @@ #define _GNU_SOURCE #include -#include + #include +#include -#include -#include +#include -FILE_DESCR("fs layout") +#include "flutter-pi.h" +#include "util/asserts.h" +#include "util/collection.h" +#include "util/logging.h" static bool path_exists(const char *path) { return access(path, R_OK) == 0; @@ -46,14 +49,20 @@ static struct flutter_paths *resolve( char *engine_path; int ok; - DEBUG_ASSERT_NOT_NULL(app_bundle_path); - DEBUG_ASSERT(icudtl_subpath || icudtl_system_path || icudtl_system_path_fallback); - DEBUG_ASSERT_MSG(!icudtl_system_path_fallback || icudtl_system_path, "icudtl.dat fallback system path is given, but no non-fallback system path."); - DEBUG_ASSERT_NOT_NULL(asset_bundle_subpath); - DEBUG_ASSERT_NOT_NULL(kernel_blob_subpath); - DEBUG_ASSERT_NOT_NULL(app_elf_subpath); - DEBUG_ASSERT(app_engine_subpath || engine_dlopen_name || engine_dlopen_name_fallback); - DEBUG_ASSERT_MSG(!engine_dlopen_name_fallback || engine_dlopen_name, "flutter engine fallback dlopen name is given, but no non-fallback dlopen name."); + ASSERT_NOT_NULL(app_bundle_path); + assert(icudtl_subpath || icudtl_system_path || icudtl_system_path_fallback); + ASSERT_MSG( + !icudtl_system_path_fallback || icudtl_system_path, + "icudtl.dat fallback system path is given, but no non-fallback system path." + ); + ASSERT_NOT_NULL(asset_bundle_subpath); + ASSERT_NOT_NULL(kernel_blob_subpath); + ASSERT_NOT_NULL(app_elf_subpath); + assert(app_engine_subpath || engine_dlopen_name || engine_dlopen_name_fallback); + ASSERT_MSG( + !engine_dlopen_name_fallback || engine_dlopen_name, + "flutter engine fallback dlopen name is given, but no non-fallback dlopen name." + ); paths = malloc(sizeof *paths); if (paths == NULL) { @@ -71,7 +80,7 @@ static struct flutter_paths *resolve( goto fail_free_paths; } - DEBUG_ASSERT(path_exists(app_bundle_path_real)); + assert(path_exists(app_bundle_path_real)); // Asset bundle path is the same as the app bundle path in the default filesystem layout, // or /flutter_assets in meta-flutter dunfell / kirkstone layout. @@ -109,7 +118,7 @@ static struct flutter_paths *resolve( } } - DEBUG_ASSERT_NOT_NULL(icudtl_path); + ASSERT_NOT_NULL(icudtl_path); if (icudtl_system_path_fallback != NULL && path_exists(icudtl_path) == false) { LOG_DEBUG("icudtl file not found at %s.\n", icudtl_path); @@ -121,7 +130,7 @@ static struct flutter_paths *resolve( } } - DEBUG_ASSERT_NOT_NULL(icudtl_path); + ASSERT_NOT_NULL(icudtl_path); // We still haven't found it. Fail because we need it to run flutter. if (path_exists(icudtl_path) == false) { @@ -153,7 +162,7 @@ static struct flutter_paths *resolve( LOG_ERROR("app elf file \"%s\" does not exist, but is necessary for release/profile mode.\n", app_elf_path); goto fail_free_app_elf_path; } - + // Try to find the engine inside the asset bundle. If we don't find it, that's not an error because // it could still be inside /usr/lib and we can just dlopen it using the filename. ok = asprintf(&engine_path, "%s/%s", app_bundle_path_real, app_engine_subpath); @@ -189,7 +198,6 @@ static struct flutter_paths *resolve( dlopen_name_fallback_duped = NULL; } - paths->app_bundle_path = app_bundle_path_real; paths->asset_bundle_path = asset_bundle_path; paths->icudtl_path = icudtl_path; @@ -200,28 +208,27 @@ static struct flutter_paths *resolve( paths->flutter_engine_dlopen_name_fallback = dlopen_name_fallback_duped; return paths; - - fail_free_dlopen_name_duped: +fail_free_dlopen_name_duped: free(dlopen_name_duped); - fail_maybe_free_engine_path: +fail_maybe_free_engine_path: if (engine_path != NULL) { free(engine_path); } - fail_free_app_elf_path: +fail_free_app_elf_path: free(app_elf_path); - fail_free_kernel_blob_path: +fail_free_kernel_blob_path: free(kernel_blob_path); - - fail_free_asset_bundle_path: + +fail_free_asset_bundle_path: free(asset_bundle_path); - fail_free_app_bundle_path_real: +fail_free_app_bundle_path_real: free(app_bundle_path_real); - fail_free_paths: +fail_free_paths: free(paths); return NULL; } @@ -255,9 +262,9 @@ struct flutter_paths *fs_layout_flutterpi_resolve(const char *app_bundle_path, e /* kernel blob subpath */ "kernel_blob.bin", /* app elf subpath */ "app.so", /* flutter engine subpath */ "libflutter_engine.so", - /* engine dlopen name */ runtime_mode == kDebug ? "libflutter_engine.so.debug" : - runtime_mode == kProfile ? "libflutter_engine.so.profile" : - "libflutter_engine.so.release", + /* engine dlopen name */ runtime_mode == FLUTTER_RUNTIME_MODE_DEBUG ? "libflutter_engine.so.debug" : + runtime_mode == FLUTTER_RUNTIME_MODE_PROFILE ? "libflutter_engine.so.profile" : + "libflutter_engine.so.release", /* engine dlopen name fallback */ "libflutter_engine.so" ); } diff --git a/include/filesystem_layout.h b/src/filesystem_layout.h similarity index 53% rename from include/filesystem_layout.h rename to src/filesystem_layout.h index 533cd240..8a3b4efa 100644 --- a/include/filesystem_layout.h +++ b/src/filesystem_layout.h @@ -7,15 +7,27 @@ * Copyright (c) 2022, Hannes Winkler */ +#ifndef _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H +#define _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H -#ifndef _FLUTTERPI_INCLUDE_FILESYSTEM_LAYOUT_H -#define _FLUTTERPI_INCLUDE_FILESYSTEM_LAYOUT_H +#include "flutter-pi.h" -#include +struct flutter_paths { + char *app_bundle_path; + char *asset_bundle_path; + char *app_elf_path; + char *icudtl_path; + char *kernel_blob_path; + char *flutter_engine_path; + char *flutter_engine_dlopen_name; + char *flutter_engine_dlopen_name_fallback; +}; typedef struct flutter_paths *(*resolve_paths_t)(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); +void flutter_paths_free(struct flutter_paths *paths); + struct flutter_paths *fs_layout_flutterpi_resolve(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); struct flutter_paths *fs_layout_metaflutter_resolve(const char *app_bundle_path, enum flutter_runtime_mode runtime_mode); -#endif // _FLUTTERPI_INCLUDE_FILESYSTEM_LAYOUT_H +#endif // _FLUTTERPI_SRC_FILESYSTEM_LAYOUT_H diff --git a/src/flutter-pi.c b/src/flutter-pi.c index c5dd0085..82dd20a6 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1,69 +1,87 @@ -#define _GNU_SOURCE +#define _GNU_SOURCE +#include "flutter-pi.h" + +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include -#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include -#include #include -#include +#include +#include -#include -#include #include +#include +#include +#include #include -#include -#define EGL_EGLEXT_PROTOTYPES -#include -#include -#define GL_GLEXT_PROTOTYPES -#include +#include +#include #include #include +#include +#include #include -#include +#include +#include + +#include "compositor_ng.h" +#include "filesystem_layout.h" +#include "frame_scheduler.h" +#include "keyboard.h" +#include "locales.h" +#include "modesetting.h" +#include "pixel_format.h" +#include "platformchannel.h" +#include "pluginregistry.h" +#include "plugins/raw_keyboard.h" +#include "plugins/text_input.h" +#include "texture_registry.h" +#include "tracer.h" +#include "user_input.h" +#include "util/list.h" +#include "util/logging.h" +#include "window.h" + +#include "config.h" + +#ifdef HAVE_LIBSEAT + #include +#endif + +#ifdef HAVE_EGL_GLES2 + #include "egl.h" + #include "gl_renderer.h" + #include "gles.h" +#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifdef HAVE_VULKAN + #include "vk_renderer.h" +#endif #ifdef ENABLE_MTRACE -# include + #include #endif -FILE_DESCR("flutter-pi") +#define PIXFMT_ARG_NAME(_name, _arg_name, ...) _arg_name ", " -const char *const usage ="\ +const char *const usage = + "\ flutter-pi - run flutter apps on your Raspberry Pi.\n\ \n\ USAGE:\n\ @@ -74,12 +92,19 @@ OPTIONS:\n\ of the app must be located inside the bundle directory.\n\ This also requires a libflutter_engine.so that was\n\ built with --runtime-mode=release.\n\ - \n\ +\n\ --profile Run the app in profile mode. The AOT snapshot\n\ of the app must be located inside the bundle directory.\n\ This also requires a libflutter_engine.so that was\n\ built with --runtime-mode=profile.\n\ - \n\ +\n\ + --vulkan Use vulkan for rendering.\n" +#ifndef HAVE_VULKAN + "\ + NOTE: This flutter-pi executable was built without\n\ + vulkan support.\n" +#endif + "\n\ -o, --orientation Start the app in this orientation. Valid\n\ for are: portrait_up, landscape_left,\n\ portrait_down, landscape_right.\n\ @@ -103,26 +128,14 @@ OPTIONS:\n\ in turn basically \"scales\" the UI.\n\ \n\ --pixelformat Selects the pixel format to use for the framebuffers.\n\ - Available pixel formats:\n\ - RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n\ -\n\ - -i, --input Appends all files matching this glob pattern to the\n\ - list of input (touchscreen, mouse, touchpad, \n\ - keyboard) devices. Brace and tilde expansion is \n\ - enabled.\n\ - Every file that matches this pattern, but is not\n\ - a valid touchscreen / -pad, mouse or keyboard is \n\ - silently ignored.\n\ - If no -i options are given, flutter-pi will try to\n\ - use all input devices assigned to udev seat0.\n\ - If that fails, or udev is not installed, flutter-pi\n\ - will fallback to using all devices matching \n\ - \"/dev/input/event*\" as inputs.\n\ - In most cases, there's no need to specify this\n\ - option.\n\ - Note that you need to properly escape each glob \n\ - pattern you use as a parameter so it isn't \n\ - implicitly expanded by your shell.\n\ + If this is not specified, a good pixel format will\n\ + be selected automatically.\n\ + Available pixel formats: " PIXFMT_LIST(PIXFMT_ARG_NAME + ) "\n\ + --videomode widthxheight\n\ + --videomode widthxheight@hz Uses an output videomode that satisfies the argument.\n\ + If no hz value is given, the highest possible refreshrate\n\ + will be used.\n\ \n\ -h, --help Show this help and exit.\n\ \n\ @@ -132,8 +145,8 @@ EXAMPLES:\n\ flutter-pi -o portrait_up ./my_app\n\ flutter-pi -r 90 ./my_app\n\ flutter-pi -d \"155, 86\" ./my_app\n\ - flutter-pi -i \"/dev/input/event{0,1}\" -i \"/dev/input/event{2,3}\" /home/pi/helloworld_flutterassets\n\ - flutter-pi -i \"/dev/input/mouse*\" /home/pi/helloworld_flutterassets\n\ + flutter-pi --videomode 1920x1080 ./my_app\n\ + flutter-pi --videomode 1280x720@60 ./my_app\n\ \n\ SEE ALSO:\n\ Author: Hannes Winkler, a.k.a ardera\n\ @@ -143,57 +156,186 @@ SEE ALSO:\n\ For instructions on how to build an asset bundle or an AOT snapshot\n\ of your app, please see the linked github repository.\n\ For a list of options you can pass to the flutter engine, look here:\n\ - https://github.com/flutter/engine/blob/master/shell/common/switches.h\n\ + https://github.com/flutter/engine/blob/main/shell/common/switches.h\n\ "; -// If this fails, update the accepted value list for --pixelformat above too. -COMPILE_ASSERT(kCount_PixFmt == 5); +struct libseat; + +struct flutterpi { + /** + * @brief The KMS device. + * + */ + struct drmdev *drmdev; + + /** + * @brief The flutter event tracing interface. + * + */ + struct tracer *tracer; + + /** + * @brief The compositor. Manages all the window stuff. + * + */ + struct compositor *compositor; + + /** + * @brief Event source which represents the compositor event fd as registered to the + * event loop. + * + */ + // sd_event_source *compositor_event_source; + + /** + * @brief The user input instance. + * + * Handles touch, mouse and keyboard input and calls the callbacks. + */ + struct user_input *user_input; + + /** + * @brief The user input instance event fd registered to the event loop. + * + */ + // sd_event_source *user_input_event_source; + + /** + * @brief The locales instance. Provides the system locales to flutter. + * + */ + struct locales *locales; + + /** + * @brief flutter stuff. + * + */ + struct { + char *bundle_path; + struct flutter_paths *paths; + void *app_elf_handle; + void *engine_handle; + + FlutterLocale **locales; + size_t n_locales; + + int engine_argc; + char **engine_argv; + enum flutter_runtime_mode runtime_mode; + FlutterEngineProcTable procs; + FlutterEngine engine; + FlutterEngineAOTData aot_data; + + bool next_frame_request_is_secondary; + } flutter; + + /// main event loop + pthread_t event_loop_thread; + pthread_mutex_t event_loop_mutex; + sd_event *event_loop; + int wakeup_event_loop_fd; + + struct evloop *evloop; + + /** + * @brief Manages all plugins. + * + */ + struct plugin_registry *plugin_registry; + + /** + * @brief Manages all external textures registered to the flutter engine. + * + */ + struct texture_registry *texture_registry; + + struct gl_renderer *gl_renderer; + struct vk_renderer *vk_renderer; + + struct libseat *libseat; + struct list_head fd_for_device_id; + bool session_active; + + char *desired_videomode; +}; -struct flutterpi flutterpi; +struct device_id_and_fd { + struct list_head entry; + int device_id; + int fd; +}; -/*static int flutterpi_post_platform_task( - int (*callback)(void *userdata), - void *userdata -);*/ +/// TODO: Remove this +struct flutterpi *flutterpi; static bool runs_platform_tasks_on_current_thread(void *userdata); /********************* * FLUTTER CALLBACKS * *********************/ + +#ifdef HAVE_EGL_GLES2 /// Called on some flutter internal thread when the flutter /// rendering EGLContext should be made current. -static bool on_make_current(void* userdata) { - EGLint egl_error; - - (void) userdata; +static bool on_make_current(void *userdata) { + struct flutterpi *flutterpi; + EGLSurface surface; + int ok; - eglGetError(); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->gl_renderer); + + // Ideally we don't make a surface current here at all. + // But that doesn't work right now. + // if (compositor_has_egl_surface(flutterpi->compositor)) { + // surface = compositor_get_egl_surface(flutterpi->compositor); + // if (surface == EGL_NO_SURFACE) { + // /// TODO: Get a fake EGL Surface just for initialization. + // LOG_ERROR("Couldn't get an EGL surface from the compositor.\n"); + // return false; + // } + // ok = gl_renderer_make_flutter_rendering_context_current(flutterpi->gl_renderer, surface); + // if (ok != 0) { + // return false; + // } + // } else { + // ok = gl_renderer_make_flutter_setup_context_current(flutterpi->gl_renderer); + // if (ok != 0) { + // return false; + // } + // } + + surface = compositor_get_egl_surface(flutterpi->compositor); + if (surface == EGL_NO_SURFACE) { + /// TODO: Get a fake EGL Surface just for initialization. + LOG_ERROR("Couldn't get an EGL surface from the compositor.\n"); + return false; + } - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.flutter_render_context); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not make the flutter rendering EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); + ok = gl_renderer_make_flutter_rendering_context_current(flutterpi->gl_renderer, surface); + if (ok != 0) { return false; } - + return true; } /// Called on some flutter internal thread to /// clear the EGLContext. -static bool on_clear_current(void* userdata) { - EGLint egl_error; - - (void) userdata; +static bool on_clear_current(void *userdata) { + struct flutterpi *flutterpi; + int ok; - eglGetError(); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->gl_renderer); - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not clear the flutter EGL context. eglMakeCurrent: 0x%08X\n", egl_error); + ok = gl_renderer_clear_current(flutterpi->gl_renderer); + if (ok != 0) { return false; } - + return true; } @@ -203,301 +345,245 @@ static bool on_clear_current(void* userdata) { /// still needs to be present) static bool on_present(void *userdata) { (void) userdata; - return true; + UNREACHABLE(); } /// Called on some flutter internal thread to get the /// GL FBO id flutter should render into /// (Won't be called since we're supplying a compositor, /// still needs to be present) -static uint32_t fbo_callback(void* userdata) { - (void) userdata; +static uint32_t fbo_callback(void *userdata) { + struct flutterpi *flutterpi; + + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + (void) flutterpi; + + TRACER_INSTANT(flutterpi->tracer, "fbo_callback"); return 0; } /// Called on some flutter internal thread when the flutter /// resource uploading EGLContext should be made current. static bool on_make_resource_current(void *userdata) { - EGLint egl_error; - - (void) userdata; + struct flutterpi *flutterpi; + int ok; - eglGetError(); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->gl_renderer); - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, flutterpi.egl.flutter_resource_uploading_context); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - LOG_ERROR("Could not make the flutter resource uploading EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); + ok = gl_renderer_make_flutter_resource_uploading_context_current(flutterpi->gl_renderer); + if (ok != 0) { return false; } - + return true; } -/// Cut a word from a string, mutating "string" -static void cut_word_from_string( - char* string, - const char* word -) { - size_t word_length = strlen(word); - char* word_in_str = strstr(string, word); +/// Called by flutter +static void *proc_resolver(void *userdata, const char *name) { + struct flutterpi *flutterpi; - // check if the given word is surrounded by spaces in the string - if (word_in_str - && ((word_in_str == string) || (word_in_str[-1] == ' ')) - && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' ')) - ) { - if (word_in_str[word_length] == ' ') word_length++; + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->gl_renderer); - int i = 0; - do { - word_in_str[i] = word_in_str[i+word_length]; - } while (word_in_str[i++ + word_length] != 0); - } + return gl_renderer_get_proc_address(flutterpi->gl_renderer, name); } +#endif -/// An override for glGetString since the real glGetString -/// won't work. -static const GLubyte *hacked_glGetString(GLenum name) { - static GLubyte *extensions = NULL; +UNUSED static void *on_get_vulkan_proc_address(void *userdata, FlutterVulkanInstanceHandle instance, const char *name) { + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(name); + (void) userdata; - if (name != GL_EXTENSIONS) - return glGetString(name); +#ifdef HAVE_VULKAN + if (streq(name, "GetInstanceProcAddr")) { + name = "vkGetInstanceProcAddr"; + } - if (extensions == NULL) { - GLubyte *orig_extensions = (GLubyte *) glGetString(GL_EXTENSIONS); - - extensions = malloc(strlen((const char*)orig_extensions) + 1); - if (!extensions) { - return NULL; - } + return (void *) vkGetInstanceProcAddr((VkInstance) instance, name); +#else + (void) userdata; + (void) instance; + (void) name; + UNREACHABLE(); +#endif +} - strcpy((char*)extensions, (const char*)orig_extensions); - - /* - * working (apparently) - */ - //cut_word_from_string(extensions, "GL_EXT_blend_minmax"); - //cut_word_from_string(extensions, "GL_EXT_multi_draw_arrays"); - //cut_word_from_string(extensions, "GL_EXT_texture_format_BGRA8888"); - //cut_word_from_string(extensions, "GL_OES_compressed_ETC1_RGB8_texture"); - //cut_word_from_string(extensions, "GL_OES_depth24"); - //cut_word_from_string(extensions, "GL_OES_texture_npot"); - //cut_word_from_string(extensions, "GL_OES_vertex_half_float"); - //cut_word_from_string(extensions, "GL_OES_EGL_image"); - //cut_word_from_string(extensions, "GL_OES_depth_texture"); - //cut_word_from_string(extensions, "GL_AMD_performance_monitor"); - //cut_word_from_string(extensions, "GL_OES_EGL_image_external"); - //cut_word_from_string(extensions, "GL_EXT_occlusion_query_boolean"); - //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_ldr"); - //cut_word_from_string(extensions, "GL_EXT_compressed_ETC1_RGB8_sub_texture"); - //cut_word_from_string(extensions, "GL_EXT_draw_elements_base_vertex"); - //cut_word_from_string(extensions, "GL_EXT_texture_border_clamp"); - //cut_word_from_string(extensions, "GL_OES_draw_elements_base_vertex"); - //cut_word_from_string(extensions, "GL_OES_texture_border_clamp"); - //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_sliced_3d"); - //cut_word_from_string(extensions, "GL_MESA_tile_raster_order"); - - /* - * should be working, but isn't - */ - cut_word_from_string((char*)extensions, "GL_EXT_map_buffer_range"); - - /* - * definitely broken - */ - cut_word_from_string((char*)extensions, "GL_OES_element_index_uint"); - cut_word_from_string((char*)extensions, "GL_OES_fbo_render_mipmap"); - cut_word_from_string((char*)extensions, "GL_OES_mapbuffer"); - cut_word_from_string((char*)extensions, "GL_OES_rgb8_rgba8"); - cut_word_from_string((char*)extensions, "GL_OES_stencil8"); - cut_word_from_string((char*)extensions, "GL_OES_texture_3D"); - cut_word_from_string((char*)extensions, "GL_OES_packed_depth_stencil"); - cut_word_from_string((char*)extensions, "GL_OES_get_program_binary"); - cut_word_from_string((char*)extensions, "GL_APPLE_texture_max_level"); - cut_word_from_string((char*)extensions, "GL_EXT_discard_framebuffer"); - cut_word_from_string((char*)extensions, "GL_EXT_read_format_bgra"); - cut_word_from_string((char*)extensions, "GL_EXT_frag_depth"); - cut_word_from_string((char*)extensions, "GL_NV_fbo_color_attachments"); - cut_word_from_string((char*)extensions, "GL_OES_EGL_sync"); - cut_word_from_string((char*)extensions, "GL_OES_vertex_array_object"); - cut_word_from_string((char*)extensions, "GL_EXT_unpack_subimage"); - cut_word_from_string((char*)extensions, "GL_NV_draw_buffers"); - cut_word_from_string((char*)extensions, "GL_NV_read_buffer"); - cut_word_from_string((char*)extensions, "GL_NV_read_depth"); - cut_word_from_string((char*)extensions, "GL_NV_read_depth_stencil"); - cut_word_from_string((char*)extensions, "GL_NV_read_stencil"); - cut_word_from_string((char*)extensions, "GL_EXT_draw_buffers"); - cut_word_from_string((char*)extensions, "GL_KHR_debug"); - cut_word_from_string((char*)extensions, "GL_OES_required_internalformat"); - cut_word_from_string((char*)extensions, "GL_OES_surfaceless_context"); - cut_word_from_string((char*)extensions, "GL_EXT_separate_shader_objects"); - cut_word_from_string((char*)extensions, "GL_KHR_context_flush_control"); - cut_word_from_string((char*)extensions, "GL_KHR_no_error"); - cut_word_from_string((char*)extensions, "GL_KHR_parallel_shader_compile"); - } - - return extensions; -} - -/// Called by flutter -static void *proc_resolver( - void* userdata, - const char* name -) { - static int is_VC4 = -1; - void *address; +UNUSED static FlutterVulkanImage on_get_next_vulkan_image(void *userdata, const FlutterFrameInfo *frameinfo) { + struct flutterpi *flutterpi; - (void) userdata; + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(frameinfo); + flutterpi = userdata; - /* - * The mesa V3D driver reports some OpenGL ES extensions as supported and working - * even though they aren't. hacked_glGetString is a workaround for this, which will - * cut out the non-working extensions from the list of supported extensions. - */ + (void) flutterpi; + (void) frameinfo; - if (name == NULL) - return NULL; + UNIMPLEMENTED(); + UNREACHABLE(); +} - // first detect if we're running on a VideoCore 4 / using the VC4 driver. - if ((is_VC4 == -1) && (is_VC4 = strcmp(flutterpi.egl.renderer, "VC4 V3D 2.1") == 0)) { - printf( "detected VideoCore IV as underlying graphics chip, and VC4 as the driver.\n" - "Reporting modified GL_EXTENSIONS string that doesn't contain non-working extensions.\n"); - is_VC4 = 0; - } +UNUSED static bool on_present_vulkan_image(void *userdata, const FlutterVulkanImage *image) { + struct flutterpi *flutterpi; - // if we do, and the symbol to resolve is glGetString, we return our hacked_glGetString. - if (is_VC4 && (strcmp(name, "glGetString") == 0)) - return hacked_glGetString; + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(image); + flutterpi = userdata; - if ((address = dlsym(RTLD_DEFAULT, name)) || (address = eglGetProcAddress(name))) - return address; - - LOG_ERROR("proc_resolver: Could not resolve symbol \"%s\"\n", name); + (void) flutterpi; + (void) image; - return NULL; + UNIMPLEMENTED(); + UNREACHABLE(); } -static void on_platform_message( - const FlutterPlatformMessage* message, - void* userdata -) { +static void on_platform_message(const FlutterPlatformMessage *message, void *userdata) { int ok; (void) userdata; - ok = plugin_registry_on_platform_message((FlutterPlatformMessage *) message); + ok = plugin_registry_on_platform_message(flutterpi->plugin_registry, message); if (ok != 0) { LOG_ERROR("Error handling platform message. plugin_registry_on_platform_message: %s\n", strerror(ok)); } } -/// Called on the main thread when a new frame request may have arrived. -/// Uses [drmCrtcGetSequence] or [FlutterEngineGetCurrentTime] to complete -/// the frame request. -static int on_execute_frame_request( - void *userdata -) { - FlutterEngineResult result; - struct frame *peek; +static bool flutterpi_runs_platform_tasks_on_current_thread(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + return pthread_equal(pthread_self(), flutterpi->event_loop_thread) != 0; +} + +struct frame_req { + struct flutterpi *flutterpi; + intptr_t baton; + uint64_t vblank_ns, next_vblank_ns; +}; + +static int on_deferred_begin_frame(void *userdata) { + FlutterEngineResult engine_result; + struct frame_req *req; + + ASSERT_NOT_NULL(userdata); + req = userdata; + + assert(flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)); + + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + engine_result = req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, req->vblank_ns, req->next_vblank_ns); + + free(req); + + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + return EIO; + } + + return 0; +} + +UNUSED static void on_begin_frame(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns) { + FlutterEngineResult engine_result; + struct frame_req *req; int ok; - (void) userdata; - cqueue_lock(&flutterpi.frame_queue); - - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if (ok == 0) { - if (peek->state == kFramePending) { - uint64_t ns; - if (flutterpi.drm.platform_supports_get_sequence_ioctl) { - ns = 0; - ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); - if (ok < 0) { - perror("[flutter-pi] Couldn't get last vblank timestamp. drmCrtcGetSequence"); - cqueue_unlock(&flutterpi.frame_queue); - return errno; - } - } else { - ns = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); - } - - result = flutterpi.flutter.libflutter_engine.FlutterEngineOnVsync( - flutterpi.flutter.engine, - peek->baton, - ns, - ns + (1000000000 / flutterpi.display.refresh_rate) - ); - if (result != kSuccess) { - LOG_ERROR("Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); - cqueue_unlock(&flutterpi.frame_queue); - return EIO; - } + ASSERT_NOT_NULL(userdata); + req = userdata; + + if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + + engine_result = req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, vblank_ns, next_vblank_ns); + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + goto fail_free_req; + } - peek->state = kFrameRendering; + free(req); + } else { + req->vblank_ns = vblank_ns; + req->next_vblank_ns = next_vblank_ns; + ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + if (ok != 0) { + LOG_ERROR("Couldn't defer signalling frame begin.\n"); + goto fail_free_req; } - } else if (ok == EAGAIN) { - // do nothing - } else if (ok != 0) { - LOG_ERROR("Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); - cqueue_unlock(&flutterpi.frame_queue); - return ok; } - cqueue_unlock(&flutterpi.frame_queue); + return; - return 0; +fail_free_req: + free(req); + return; } /// Called on some flutter internal thread to request a frame, /// and also get the vblank timestamp of the pageflip preceding that frame. -static void on_frame_request( - void* userdata, - intptr_t baton -) { - struct frame *peek; +UNUSED static void on_frame_request(void *userdata, intptr_t baton) { + FlutterEngineResult engine_result; + struct flutterpi *flutterpi; + struct frame_req *req; int ok; - (void) userdata; - cqueue_lock(&flutterpi.frame_queue); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if ((ok == 0) || (ok == EAGAIN)) { - bool reply_instantly = ok == EAGAIN; + TRACER_INSTANT(flutterpi->tracer, "on_frame_request"); - ok = cqueue_try_enqueue_locked(&flutterpi.frame_queue, &(struct frame) { - .state = kFramePending, - .baton = baton - }); - if (ok != 0) { - LOG_ERROR("Could not enqueue frame request. cqueue_try_enqueue_locked: %s\n", strerror(ok)); - cqueue_unlock(&flutterpi.frame_queue); - return; + req = malloc(sizeof *req); + if (req == NULL) { + LOG_ERROR("Out of memory\n"); + return; + } + + req->flutterpi = flutterpi; + req->baton = baton; + req->vblank_ns = get_monotonic_time(); + req->next_vblank_ns = req->vblank_ns + (1000000000.0 / compositor_get_refresh_rate(flutterpi->compositor)); + + if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { + TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + + engine_result = + req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, req->vblank_ns, req->next_vblank_ns); + if (engine_result != kSuccess) { + LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + goto fail_free_req; } - if (reply_instantly) { - flutterpi_post_platform_task( - on_execute_frame_request, - NULL - ); + free(req); + } else { + ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + if (ok != 0) { + LOG_ERROR("Couldn't defer signalling frame begin.\n"); + goto fail_free_req; } - } else if (ok != 0) { - LOG_ERROR("Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); } - cqueue_unlock(&flutterpi.frame_queue); + return; + +fail_free_req: + free(req); } -static FlutterTransformation on_get_transformation(void *userdata) { - (void) userdata; - return flutterpi.view.view_to_display_transform; +UNUSED static FlutterTransformation on_get_transformation(void *userdata) { + struct view_geometry geometry; + struct flutterpi *flutterpi; + + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + + compositor_get_view_geometry(flutterpi->compositor, &geometry); + + return MAT3F_AS_FLUTTER_TRANSFORM(geometry.view_to_display_transform); } atomic_int_least64_t platform_task_counter = 0; /// platform tasks -static int on_execute_platform_task( - sd_event_source *s, - void *userdata -) { +static int on_execute_platform_task(sd_event_source *s, void *userdata) { struct platform_task *task; int ok; @@ -515,10 +601,7 @@ static int on_execute_platform_task( return 0; } -int flutterpi_post_platform_task( - int (*callback)(void *userdata), - void *userdata -) { +int flutterpi_post_platform_task(int (*callback)(void *userdata), void *userdata) { struct platform_task *task; sd_event_source *src; int ok; @@ -531,16 +614,11 @@ int flutterpi_post_platform_task( task->callback = callback; task->userdata = userdata; - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_lock(&flutterpi.event_loop_mutex); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_lock(&flutterpi->event_loop_mutex); } - ok = sd_event_add_defer( - flutterpi.event_loop, - &src, - on_execute_platform_task, - task - ); + ok = sd_event_add_defer(flutterpi->event_loop, &src, on_execute_platform_task, task); if (ok < 0) { LOG_ERROR("Error posting platform task to main loop. sd_event_add_defer: %s\n", strerror(-ok)); ok = -ok; @@ -550,36 +628,31 @@ int flutterpi_post_platform_task( // Higher values mean lower priority. So later platform tasks are handled later too. sd_event_source_set_priority(src, atomic_fetch_add(&platform_task_counter, 1)); - if (pthread_self() != flutterpi.event_loop_thread) { - ok = write(flutterpi.wakeup_event_loop_fd, (uint8_t[8]) {0, 0, 0, 0, 0, 0, 0, 1}, 8); + if (pthread_self() != flutterpi->event_loop_thread) { + ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); if (ok < 0) { - perror("[flutter-pi] Error arming main loop for platform task. write"); ok = errno; + LOG_ERROR("Error arming main loop for platform task. write: %s\n", strerror(ok)); goto fail_unlock_event_loop; } } - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } return 0; - - fail_unlock_event_loop: - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); +fail_unlock_event_loop: + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } return ok; } /// timed platform tasks -static int on_execute_platform_task_with_time( - sd_event_source *s, - uint64_t usec, - void *userdata -) { +static int on_execute_platform_task_with_time(sd_event_source *s, uint64_t usec, void *userdata) { struct platform_task *task; int ok; @@ -599,11 +672,7 @@ static int on_execute_platform_task_with_time( return 0; } -int flutterpi_post_platform_task_with_time( - int (*callback)(void *userdata), - void *userdata, - uint64_t target_time_usec -) { +int flutterpi_post_platform_task_with_time(int (*callback)(void *userdata), void *userdata, uint64_t target_time_usec) { struct platform_task *task; //sd_event_source *source; int ok; @@ -615,28 +684,20 @@ int flutterpi_post_platform_task_with_time( task->callback = callback; task->userdata = userdata; - - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_lock(&flutterpi.event_loop_mutex); - } - - ok = sd_event_add_time( - flutterpi.event_loop, - NULL, - CLOCK_MONOTONIC, - target_time_usec, - 1, - on_execute_platform_task_with_time, - task - ); + + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_lock(&flutterpi->event_loop_mutex); + } + + ok = sd_event_add_time(flutterpi->event_loop, NULL, CLOCK_MONOTONIC, target_time_usec, 1, on_execute_platform_task_with_time, task); if (ok < 0) { LOG_ERROR("Error posting platform task to main loop. sd_event_add_time: %s\n", strerror(-ok)); ok = -ok; goto fail_unlock_event_loop; } - if (pthread_self() != flutterpi.event_loop_thread) { - ok = write(flutterpi.wakeup_event_loop_fd, (uint8_t[8]) {0, 0, 0, 0, 0, 0, 0, 1}, 8); + if (pthread_self() != flutterpi->event_loop_thread) { + ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); if (ok < 0) { perror("[flutter-pi] Error arming main loop for platform task. write"); ok = errno; @@ -644,49 +705,35 @@ int flutterpi_post_platform_task_with_time( } } - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } return 0; - - fail_unlock_event_loop: - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); +fail_unlock_event_loop: + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } free(task); return ok; } -int flutterpi_sd_event_add_io( - sd_event_source **source_out, - int fd, - uint32_t events, - sd_event_io_handler_t callback, - void *userdata -) { +int flutterpi_sd_event_add_io(sd_event_source **source_out, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata) { int ok; - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_lock(&flutterpi.event_loop_mutex); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_lock(&flutterpi->event_loop_mutex); } - ok = sd_event_add_io( - flutterpi.event_loop, - source_out, - fd, - events, - callback, - userdata - ); + ok = sd_event_add_io(flutterpi->event_loop, source_out, fd, events, callback, userdata); if (ok < 0) { LOG_ERROR("Could not add IO callback to event loop. sd_event_add_io: %s\n", strerror(-ok)); return -ok; } - if (pthread_self() != flutterpi.event_loop_thread) { - ok = write(flutterpi.wakeup_event_loop_fd, (uint8_t[8]) {0, 0, 0, 0, 0, 0, 0, 1}, 8); + if (pthread_self() != flutterpi->event_loop_thread) { + ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); if (ok < 0) { perror("[flutter-pi] Error arming main loop for io callback. write"); ok = errno; @@ -694,30 +741,27 @@ int flutterpi_sd_event_add_io( } } - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } return 0; - - fail_unlock_event_loop: - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); +fail_unlock_event_loop: + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } return ok; } /// flutter tasks -static int on_execute_flutter_task( - void *userdata -) { +static int on_execute_flutter_task(void *userdata) { FlutterEngineResult result; FlutterTask *task; task = userdata; - result = flutterpi.flutter.libflutter_engine.FlutterEngineRunTask(flutterpi.flutter.engine, task); + result = flutterpi->flutter.procs.RunTask(flutterpi->flutter.engine, task); if (result != kSuccess) { LOG_ERROR("Error running platform task. FlutterEngineRunTask: %d\n", result); free(task); @@ -729,11 +773,7 @@ static int on_execute_flutter_task( return 0; } -static void on_post_flutter_task( - FlutterTask task, - uint64_t target_time, - void *userdata -) { +static void on_post_flutter_task(FlutterTask task, uint64_t target_time, void *userdata) { FlutterTask *dup_task; int ok; @@ -743,39 +783,34 @@ static void on_post_flutter_task( if (dup_task == NULL) { return; } - + *dup_task = task; - ok = flutterpi_post_platform_task_with_time( - on_execute_flutter_task, - dup_task, - target_time / 1000 - ); + ok = flutterpi_post_platform_task_with_time(on_execute_flutter_task, dup_task, target_time / 1000); if (ok != 0) { free(dup_task); } } /// platform messages -static int on_send_platform_message( - void *userdata -) { +static int on_send_platform_message(void *userdata) { struct platform_message *msg; FlutterEngineResult result; msg = userdata; if (msg->is_response) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessageResponse(flutterpi.flutter.engine, msg->target_handle, msg->message, msg->message_size); + result = flutterpi->flutter.procs + .SendPlatformMessageResponse(flutterpi->flutter.engine, msg->target_handle, msg->message, msg->message_size); } else { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessage( - flutterpi.flutter.engine, - &(FlutterPlatformMessage) { + result = flutterpi->flutter.procs.SendPlatformMessage( + flutterpi->flutter.engine, + &(FlutterPlatformMessage){ .struct_size = sizeof(FlutterPlatformMessage), .channel = msg->target_channel, .message = msg->message, .message_size = msg->message_size, - .response_handle = msg->response_handle + .response_handle = msg->response_handle, } ); } @@ -798,6 +833,7 @@ static int on_send_platform_message( } int flutterpi_send_platform_message( + struct flutterpi *flutterpi, const char *channel, const uint8_t *restrict message, size_t message_size, @@ -806,16 +842,16 @@ int flutterpi_send_platform_message( struct platform_message *msg; FlutterEngineResult result; int ok; - - if (runs_platform_tasks_on_current_thread(NULL)) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessage( - flutterpi.flutter.engine, - &(const FlutterPlatformMessage) { + + if (runs_platform_tasks_on_current_thread(flutterpi)) { + result = flutterpi->flutter.procs.SendPlatformMessage( + flutterpi->flutter.engine, + &(const FlutterPlatformMessage){ .struct_size = sizeof(FlutterPlatformMessage), .channel = channel, .message = message, .message_size = message_size, - .response_handle = responsehandle + .response_handle = responsehandle, } ); if (result != kSuccess) { @@ -836,7 +872,7 @@ int flutterpi_send_platform_message( } msg->response_handle = responsehandle; - + if (message && message_size) { msg->message_size = message_size; msg->message = memdup(message, message_size); @@ -850,10 +886,7 @@ int flutterpi_send_platform_message( msg->message_size = 0; } - ok = flutterpi_post_platform_task( - on_send_platform_message, - msg - ); + ok = flutterpi_post_platform_task(on_send_platform_message, msg); if (ok != 0) { if (message && message_size) { free(msg->message); @@ -868,23 +901,21 @@ int flutterpi_send_platform_message( } int flutterpi_respond_to_platform_message( - FlutterPlatformMessageResponseHandle *handle, + const FlutterPlatformMessageResponseHandle *handle, const uint8_t *restrict message, size_t message_size ) { struct platform_message *msg; FlutterEngineResult result; int ok; - - if (runs_platform_tasks_on_current_thread(NULL)) { - result = flutterpi.flutter.libflutter_engine.FlutterEngineSendPlatformMessageResponse( - flutterpi.flutter.engine, - handle, - message, - message_size - ); + + if (flutterpi_runs_platform_tasks_on_current_thread(flutterpi)) { + result = flutterpi->flutter.procs.SendPlatformMessageResponse(flutterpi->flutter.engine, handle, message, message_size); if (result != kSuccess) { - LOG_ERROR("Error sending platform message response. FlutterEngineSendPlatformMessageResponse: %s\n", FLUTTER_RESULT_TO_STRING(result)); + LOG_ERROR( + "Error sending platform message response. FlutterEngineSendPlatformMessageResponse: %s\n", + FLUTTER_RESULT_TO_STRING(result) + ); return EIO; } } else { @@ -907,10 +938,7 @@ int flutterpi_respond_to_platform_message( msg->message = 0; } - ok = flutterpi_post_platform_task( - on_send_platform_message, - msg - ); + ok = flutterpi_post_platform_task(on_send_platform_message, msg); if (ok != 0) { if (msg->message) { free(msg->message); @@ -922,166 +950,113 @@ int flutterpi_respond_to_platform_message( return 0; } -struct texture_registry *flutterpi_get_texture_registry( - struct flutterpi *flutterpi -) { +struct texture_registry *flutterpi_get_texture_registry(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + ASSERT_NOT_NULL(flutterpi->texture_registry); return flutterpi->texture_registry; } +struct plugin_registry *flutterpi_get_plugin_registry(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + ASSERT_NOT_NULL(flutterpi->plugin_registry); + return flutterpi->plugin_registry; +} + +FlutterPlatformMessageResponseHandle * +flutterpi_create_platform_message_response_handle(struct flutterpi *flutterpi, FlutterDataCallback data_callback, void *userdata) { + FlutterPlatformMessageResponseHandle *handle; + FlutterEngineResult engine_result; + + ASSERT_NOT_NULL(flutterpi); + ASSERT_NOT_NULL(data_callback); + + // FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( + // FLUTTER_API_SYMBOL(FlutterEngine) engine, + // FlutterDataCallback data_callback, + // void* user_data, + // FlutterPlatformMessageResponseHandle** response_out + // ); + + engine_result = + flutterpi->flutter.procs.PlatformMessageCreateResponseHandle(flutterpi->flutter.engine, data_callback, userdata, &handle); + if (engine_result != kSuccess) { + LOG_ERROR( + "Couldn't create platform message response handle. FlutterPlatformMessageCreateResponseHandle: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + return NULL; + } + + return handle; +} + +void flutterpi_release_platform_message_response_handle(struct flutterpi *flutterpi, FlutterPlatformMessageResponseHandle *handle) { + FlutterEngineResult engine_result; + + ASSERT_NOT_NULL(flutterpi); + ASSERT_NOT_NULL(handle); + + // FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( + // FLUTTER_API_SYMBOL(FlutterEngine) engine, + // FlutterPlatformMessageResponseHandle* response + // ); + + engine_result = flutterpi->flutter.procs.PlatformMessageReleaseResponseHandle(flutterpi->flutter.engine, handle); + if (engine_result != kSuccess) { + // We can't do anything about it though. + LOG_ERROR( + "Couldn't release platform message response handle. FlutterPlatformMessageReleaseResponseHandle: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + } +} + struct texture *flutterpi_create_texture(struct flutterpi *flutterpi) { return texture_new(flutterpi_get_texture_registry(flutterpi)); } -const char *flutterpi_get_asset_bundle_path( - struct flutterpi *flutterpi -) { +const char *flutterpi_get_asset_bundle_path(struct flutterpi *flutterpi) { return flutterpi->flutter.paths->asset_bundle_path; } /// TODO: Make this refcounted if we're gonna use it from multiple threads. struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi) { - return flutterpi->gbm.device; + return drmdev_get_gbm_device(flutterpi->drmdev); } -EGLDisplay flutterpi_get_egl_display(struct flutterpi *flutterpi) { - return flutterpi->egl.display; +bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + return flutterpi->gl_renderer != NULL; } -EGLContext flutterpi_create_egl_context(struct flutterpi *flutterpi) { - EGLContext context; - - pthread_mutex_lock(&flutterpi->egl.temp_context_lock); - - context = eglCreateContext( - flutterpi->egl.display, - flutterpi->egl.config, - flutterpi->egl.temp_context, - (EGLint[]) { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - } - ); - if (context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create new EGL context from temp context. eglCreateContext: %" PRId32 "\n", eglGetError()); - goto fail_unlock_mutex; - } - - pthread_mutex_unlock(&flutterpi->egl.temp_context_lock); - - return context; +struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + return flutterpi->gl_renderer; +} - fail_unlock_mutex: - pthread_mutex_unlock(&flutterpi->egl.temp_context_lock); - return EGL_NO_CONTEXT; +void flutterpi_set_pointer_kind(struct flutterpi *flutterpi, enum pointer_kind kind) { + return compositor_set_cursor(flutterpi->compositor, false, false, true, kind, false, VEC2F(0, 0)); } void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventInstant(name); + flutterpi->flutter.procs.TraceEventInstant(name); } void flutterpi_trace_event_begin(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventDurationBegin(name); + flutterpi->flutter.procs.TraceEventDurationBegin(name); } void flutterpi_trace_event_end(struct flutterpi *flutterpi, const char *name) { - flutterpi->flutter.libflutter_engine.FlutterEngineTraceEventDurationEnd(name); + flutterpi->flutter.procs.TraceEventDurationEnd(name); } -static bool runs_platform_tasks_on_current_thread(void* userdata) { - (void) userdata; - return pthread_equal(pthread_self(), flutterpi.event_loop_thread) != 0; +static bool runs_platform_tasks_on_current_thread(void *userdata) { + return flutterpi_runs_platform_tasks_on_current_thread(userdata); } -static int run_main_loop(void) { - int ok, evloop_fd; - - pthread_mutex_lock(&flutterpi.event_loop_mutex); - ok = sd_event_get_fd(flutterpi.event_loop); - if (ok < 0) { - LOG_ERROR("Could not get fd for main event loop. sd_event_get_fd: %s\n", strerror(-ok)); - pthread_mutex_unlock(&flutterpi.event_loop_mutex); - return -ok; - } - pthread_mutex_unlock(&flutterpi.event_loop_mutex); - - evloop_fd = ok; - - { - fd_set rfds, wfds, xfds; - int state; - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&xfds); - FD_SET(evloop_fd, &rfds); - FD_SET(evloop_fd, &wfds); - FD_SET(evloop_fd, &xfds); - - const fd_set const_fds = rfds; - - pthread_mutex_lock(&flutterpi.event_loop_mutex); - - do { - state = sd_event_get_state(flutterpi.event_loop); - switch (state) { - case SD_EVENT_INITIAL: - ok = sd_event_prepare(flutterpi.event_loop); - if (ok < 0) { - LOG_ERROR("Could not prepare event loop. sd_event_prepare: %s\n", strerror(-ok)); - return -ok; - } - - break; - case SD_EVENT_ARMED: - pthread_mutex_unlock(&flutterpi.event_loop_mutex); - - do { - rfds = const_fds; - wfds = const_fds; - xfds = const_fds; - ok = select(evloop_fd + 1, &rfds, &wfds, &xfds, NULL); - if ((ok < 0) && (errno != EINTR)) { - perror("[flutter-pi] Could not wait for event loop events. select"); - return errno; - } - } while ((ok < 0) && (errno == EINTR)); - - pthread_mutex_lock(&flutterpi.event_loop_mutex); - - ok = sd_event_wait(flutterpi.event_loop, 0); - if (ok < 0) { - LOG_ERROR("Could not check for event loop events. sd_event_wait: %s\n", strerror(-ok)); - return -ok; - } - - break; - case SD_EVENT_PENDING: - ok = sd_event_dispatch(flutterpi.event_loop); - if (ok < 0) { - LOG_ERROR("Could not dispatch event loop events. sd_event_dispatch: %s\n", strerror(-ok)); - return -ok; - } - - break; - case SD_EVENT_FINISHED: - break; - default: - LOG_ERROR("Unhandled event loop state: %d. Aborting\n", state); - abort(); - } - } while (state != SD_EVENT_FINISHED); - - pthread_mutex_unlock(&flutterpi.event_loop_mutex); - } - - pthread_mutex_destroy(&flutterpi.event_loop_mutex); - sd_event_unrefp(&flutterpi.event_loop); - - return 0; -} - -static int on_wakeup_main_loop(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - uint8_t buffer[8]; - int ok; +static int on_wakeup_main_loop(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + uint8_t buffer[8]; + int ok; (void) s; (void) revents; @@ -1096,1191 +1071,1456 @@ static int on_wakeup_main_loop(sd_event_source *s, int fd, uint32_t revents, voi return 0; } -static int init_main_loop(void) { - int ok, wakeup_fd; +/************************** + * DISPLAY INITIALIZATION * + **************************/ +static int on_drmdev_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct drmdev *drmdev; - flutterpi.event_loop_thread = pthread_self(); + (void) s; + (void) fd; + (void) revents; + (void) userdata; - wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (wakeup_fd < 0) { - perror("[flutter-pi] Could not create fd for waking up the main loop. eventfd"); - return errno; - } + ASSERT_NOT_NULL(userdata); + drmdev = userdata; - ok = sd_event_new(&flutterpi.event_loop); - if (ok < 0) { - LOG_ERROR("Could not create main event loop. sd_event_new: %s\n", strerror(-ok)); - return -ok; - } + return drmdev_on_event_fd_ready(drmdev); +} - ok = sd_event_add_io( - flutterpi.event_loop, - NULL, - wakeup_fd, - EPOLLIN, - on_wakeup_main_loop, - NULL - ); - if (ok < 0) { - LOG_ERROR("Error adding wakeup callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); - sd_event_unrefp(&flutterpi.event_loop); - close(wakeup_fd); - return -ok; - } +static const FlutterLocale *on_compute_platform_resolved_locales(const FlutterLocale **locales, size_t n_locales) { + return locales_on_compute_platform_resolved_locale(flutterpi->locales, locales, n_locales); +} + +#ifdef HAVE_EGL_GLES2 +static bool +on_gl_external_texture_frame_callback(void *userdata, int64_t texture_id, size_t width, size_t height, FlutterOpenGLTexture *texture_out) { + struct flutterpi *flutterpi; - flutterpi.wakeup_event_loop_fd = wakeup_fd; + ASSERT_NOT_NULL(userdata); - return 0; + flutterpi = userdata; + + return texture_registry_gl_external_texture_frame_callback(flutterpi->texture_registry, texture_id, width, height, texture_out); } +#endif /************************** - * DISPLAY INITIALIZATION * + * FLUTTER INITIALIZATION * **************************/ -/// Called on the main thread when a pageflip ocurred. -void on_pageflip_event( - int fd, - unsigned int frame, - unsigned int sec, - unsigned int usec, - void *userdata -) { - FlutterEngineResult result; - struct frame presented_frame, *peek; - int ok; - (void) fd; - (void) frame; - (void) userdata; - - flutterpi.flutter.libflutter_engine.FlutterEngineTraceEventInstant("pageflip"); +static void *load_flutter_engine_lib(struct flutter_paths *paths) { + void *engine_handle = NULL; + int dlopen_mode; + +#ifdef ENABLE_ASAN + // If address sanitizer is enabled, we specify RTLD_NODELETE so + // the library isn't really unloaded on dlclose(). (dlclose will actually + // do nothing) + // + // That enables asan to actually symbolize any backtraces for memory leaks + // it finds. + // + // Without RTLD_NODELETE it can't symbolize the backtraces because the + // library isn't present in memory anymore when asan tries to symbolize. + dlopen_mode = RTLD_LOCAL | RTLD_NOW | RTLD_NODELETE; +#else + dlopen_mode = RTLD_LOCAL | RTLD_NOW; +#endif - cqueue_lock(&flutterpi.frame_queue); - - ok = cqueue_try_dequeue_locked(&flutterpi.frame_queue, &presented_frame); - if (ok != 0) { - LOG_ERROR("Could not dequeue completed frame from frame queue: %s\n", strerror(ok)); - goto fail_unlock_frame_queue; + if (paths->flutter_engine_path != NULL) { + engine_handle = dlopen(paths->flutter_engine_path, dlopen_mode); + if (engine_handle == NULL) { + LOG_DEBUG("Info: Could not load flutter engine from app bundle. dlopen(\"%s\"): %s.\n", paths->flutter_engine_path, dlerror()); + } } - ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); - if (ok == EAGAIN) { - // no frame queued after the one that was completed right now. - // do nothing here. - } else if (ok != 0) { - LOG_ERROR("Could not get frame queue peek. cqueue_peek_locked: %s\n", strerror(ok)); - goto fail_unlock_frame_queue; - } else { - if (peek->state == kFramePending) { - uint64_t ns = (sec * 1000000000ll) + (usec * 1000ll); - - result = flutterpi.flutter.libflutter_engine.FlutterEngineOnVsync( - flutterpi.flutter.engine, - peek->baton, - ns, - ns + (1000000000ll / flutterpi.display.refresh_rate) - ); - if (result != kSuccess) { - LOG_ERROR("Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); - goto fail_unlock_frame_queue; - } - - peek->state = kFrameRendering; - } else { - LOG_ERROR("frame queue in inconsistent state. aborting\n"); - abort(); + if (engine_handle == NULL && paths->flutter_engine_dlopen_name != NULL) { + engine_handle = dlopen(paths->flutter_engine_dlopen_name, dlopen_mode); + if (engine_handle == NULL) { + LOG_DEBUG("Info: Could not load flutter engine. dlopen(\"%s\"): %s.\n", paths->flutter_engine_dlopen_name, dlerror()); } } - cqueue_unlock(&flutterpi.frame_queue); - - ok = compositor_on_page_flip(sec, usec); - if (ok != 0) { - LOG_ERROR("Error notifying compositor about page flip. compositor_on_page_flip: %s\n", strerror(ok)); + if (engine_handle == NULL && paths->flutter_engine_dlopen_name_fallback != NULL) { + engine_handle = dlopen(paths->flutter_engine_dlopen_name_fallback, dlopen_mode); + if (engine_handle == NULL) { + LOG_DEBUG("Info: Could not load flutter engine. dlopen(\"%s\"): %s.\n", paths->flutter_engine_dlopen_name_fallback, dlerror()); + } } - return; + if (engine_handle == NULL) { + LOG_ERROR("Error: Could not load flutter engine from any location. Make sure you have installed the engine binaries.\n"); + return NULL; + } + return engine_handle; +} - fail_unlock_frame_queue: - cqueue_unlock(&flutterpi.frame_queue); +static void unload_flutter_engine_lib(void *handle) { + dlclose(handle); } -static int on_drm_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - int ok; +static int get_flutter_engine_procs(void *engine_handle, FlutterEngineProcTable *procs_out) { + // clang-format off + FlutterEngineResult (*get_proc_addresses)(FlutterEngineProcTable *table); + // clang-format on - (void) s; - (void) revents; - (void) userdata; + FlutterEngineResult engine_result; - ok = drmHandleEvent(fd, &flutterpi.drm.evctx); - if (ok < 0) { - perror("[flutter-pi] Could not handle DRM event. drmHandleEvent"); - return -errno; + get_proc_addresses = dlsym(engine_handle, "FlutterEngineGetProcAddresses"); + if (get_proc_addresses == NULL) { + LOG_ERROR("Could not resolve flutter engine function FlutterEngineGetProcAddresses.\n"); + return EINVAL; + } + + procs_out->struct_size = sizeof(FlutterEngineProcTable); + engine_result = get_proc_addresses(procs_out); + if (engine_result != kSuccess) { + LOG_ERROR( + "Could not resolve flutter engine proc addresses. FlutterEngineGetProcAddresses: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + return EINVAL; } return 0; } -int flutterpi_fill_view_properties( - bool has_orientation, - enum device_orientation orientation, - bool has_rotation, - int rotation -) { - enum device_orientation default_orientation = flutterpi.display.width >= flutterpi.display.height ? kLandscapeLeft : kPortraitUp; - - if (flutterpi.view.has_orientation) { - if (flutterpi.view.has_rotation == false) { - flutterpi.view.rotation = ANGLE_BETWEEN_ORIENTATIONS(default_orientation, flutterpi.view.orientation); - flutterpi.view.has_rotation = true; - } - } else if (flutterpi.view.has_rotation) { - for (int i = kPortraitUp; i <= kLandscapeRight; i++) { - if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == flutterpi.view.rotation) { - flutterpi.view.orientation = i; - flutterpi.view.has_orientation = true; - break; - } - } - } else { - flutterpi.view.orientation = default_orientation; - flutterpi.view.has_orientation = true; - flutterpi.view.rotation = 0; - flutterpi.view.has_rotation = true; - } +static int on_register_texture(void *userdata, int64_t texture_identifier) { + FlutterEngineResult engine_result; + struct flutterpi *flutterpi; - if (has_orientation) { - flutterpi.view.rotation += ANGLE_BETWEEN_ORIENTATIONS(flutterpi.view.orientation, orientation); - if (flutterpi.view.rotation >= 360) { - flutterpi.view.rotation -= 360; - } - - flutterpi.view.orientation = orientation; - } else if (has_rotation) { - for (int i = kPortraitUp; i <= kLandscapeRight; i++) { - if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == rotation) { - flutterpi.view.orientation = i; - flutterpi.view.rotation = rotation; - break; - } - } - } + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->flutter.engine); - if ((flutterpi.view.rotation <= 45) || ((flutterpi.view.rotation >= 135) && (flutterpi.view.rotation <= 225)) || (flutterpi.view.rotation >= 315)) { - flutterpi.view.width = flutterpi.display.width; - flutterpi.view.height = flutterpi.display.height; - flutterpi.view.width_mm = flutterpi.display.width_mm; - flutterpi.view.height_mm = flutterpi.display.height_mm; - } else { - flutterpi.view.width = flutterpi.display.height; - flutterpi.view.height = flutterpi.display.width; - flutterpi.view.width_mm = flutterpi.display.height_mm; - flutterpi.view.height_mm = flutterpi.display.width_mm; - } - - if (flutterpi.view.rotation == 0) { - flutterpi.view.view_to_display_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); - - flutterpi.view.display_to_view_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); - } else if (flutterpi.view.rotation == 90) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(90); - flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-90); - flutterpi.view.display_to_view_transform.transY = flutterpi.display.width; - } else if (flutterpi.view.rotation == 180) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(180); - flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; - flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-180); - flutterpi.view.display_to_view_transform.transX = flutterpi.display.width; - flutterpi.view.display_to_view_transform.transY = flutterpi.display.height; - } else if (flutterpi.view.rotation == 270) { - flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(270); - flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - - flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-270); - flutterpi.view.display_to_view_transform.transX = flutterpi.display.height; - } - - if (flutterpi.user_input != NULL) { - // update the user input with the new transforms - user_input_set_transform( - flutterpi.user_input, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.width, - flutterpi.display.height + engine_result = flutterpi->flutter.procs.RegisterExternalTexture(flutterpi->flutter.engine, texture_identifier); + if (engine_result != kSuccess) { + LOG_ERROR( + "Error registering external texture to flutter engine. FlutterEngineRegisterExternalTexture: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) ); + return EIO; } return 0; } -static const FlutterLocale* on_compute_platform_resolved_locales(const FlutterLocale **locales, size_t n_locales) { - return locales_on_compute_platform_resolved_locale(flutterpi.locales, locales, n_locales); -} - -static bool on_gl_external_texture_frame_callback( - void* userdata, - int64_t texture_id, - size_t width, - size_t height, - FlutterOpenGLTexture *texture_out -) { +static int on_unregister_texture(void *userdata, int64_t texture_identifier) { + FlutterEngineResult engine_result; struct flutterpi *flutterpi; - DEBUG_ASSERT_NOT_NULL(userdata); - + ASSERT_NOT_NULL(userdata); flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->flutter.engine); - return texture_registry_gl_external_texture_frame_callback( - flutterpi->texture_registry, - texture_id, - width, - height, - texture_out - ); + engine_result = flutterpi->flutter.procs.UnregisterExternalTexture(flutterpi->flutter.engine, texture_identifier); + if (engine_result != kSuccess) { + LOG_ERROR( + "Error unregistering external texture from flutter engine. FlutterEngineUnregisterExternalTexture: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + return EIO; + } + + return 0; } -static int load_egl_gl_procs(void) { - LOAD_EGL_PROC(flutterpi, getPlatformDisplay, eglGetPlatformDisplayEXT); - LOAD_EGL_PROC(flutterpi, createPlatformWindowSurface, eglCreatePlatformWindowSurface); - LOAD_EGL_PROC(flutterpi, createPlatformPixmapSurface, eglCreatePlatformPixmapSurface); - flutterpi.egl.createDRMImageMESA = (PFNEGLCREATEDRMIMAGEMESAPROC) eglGetProcAddress("eglCreateDRMImageMESA"); - flutterpi.egl.exportDRMImageMESA = (PFNEGLEXPORTDRMIMAGEMESAPROC) eglGetProcAddress("eglExportDRMImageMESA"); - flutterpi.gl.EGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES"); - flutterpi.gl.EGLImageTargetRenderbufferStorageOES = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES"); - return 0; -} - -static int init_display(void) { - /********************** - * DRM INITIALIZATION * - **********************/ - const struct drm_connector *connector; - const struct drm_encoder *encoder; - const struct drm_crtc *crtc; - const drmModeModeInfo *mode, *mode_iter; - drmDevicePtr devices[64]; - EGLint egl_error; - int ok, num_devices; - - /********************** - * DRM INITIALIZATION * - **********************/ - - num_devices = drmGetDevices2(0, devices, sizeof(devices)/sizeof(*devices)); - if (num_devices < 0) { - LOG_ERROR("Could not query DRM device list: %s\n", strerror(-num_devices)); - return -num_devices; - } - - // find a GPU that has a primary node - flutterpi.drm.drmdev = NULL; - for (int i = 0; i < num_devices; i++) { - drmDevicePtr device; - - device = devices[i]; +static int on_mark_texture_frame_available(void *userdata, int64_t texture_identifier) { + FlutterEngineResult engine_result; + struct flutterpi *flutterpi; - if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { - // We need a primary node. - continue; - } + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + ASSERT_NOT_NULL(flutterpi->flutter.engine); - ok = drmdev_new_from_path(&flutterpi.drm.drmdev, device->nodes[DRM_NODE_PRIMARY]); - if (ok != 0) { - LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); - continue; - } + engine_result = flutterpi->flutter.procs.MarkExternalTextureFrameAvailable(flutterpi->flutter.engine, texture_identifier); + if (engine_result != kSuccess) { + LOG_ERROR( + "Error notifying flutter engine about new external texture frame. FlutterEngineMarkExternalTextureFrameAvailable: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + return EIO; + } - for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { - if (connector->connector->connection == DRM_MODE_CONNECTED) { - goto found_connected_connector; - } - } - LOG_ERROR("Device \"%s\" doesn't have a display connected. Skipping.\n", device->nodes[DRM_NODE_PRIMARY]); - continue; + return 0; +} +static FlutterEngine create_flutter_engine( + struct vk_renderer *vk_renderer, + struct flutter_paths *paths, + int engine_argc, + char **engine_argv, + struct compositor *compositor, + FlutterEngineAOTData aot_data, + const FlutterEngineProcTable *procs +) { + FlutterRendererConfig renderer_config; + FlutterEngineResult engine_result; + FlutterProjectArgs project_args; + FlutterEngine engine; - found_connected_connector: - break; + // configure flutter rendering + if (vk_renderer) { + COMPILE_ASSERT(sizeof(FlutterRendererConfig) == 60 || sizeof(FlutterRendererConfig) == 120); + COMPILE_ASSERT(sizeof(FlutterVulkanRendererConfig) == 56 || sizeof(FlutterVulkanRendererConfig) == 112); +#ifdef HAVE_VULKAN + renderer_config = (FlutterRendererConfig) { + .type = kVulkan, + .vulkan = { + .struct_size = sizeof(FlutterVulkanRendererConfig), + .version = vk_renderer_get_vk_version(vk_renderer), + .instance = vk_renderer_get_instance(vk_renderer), + .physical_device = vk_renderer_get_physical_device(vk_renderer), + .device = vk_renderer_get_device(vk_renderer), + .queue_family_index = vk_renderer_get_queue_family_index(vk_renderer), + .queue = vk_renderer_get_queue(vk_renderer), + .enabled_instance_extension_count = vk_renderer_get_enabled_instance_extension_count(vk_renderer), + .enabled_instance_extensions = vk_renderer_get_enabled_instance_extensions(vk_renderer), + .enabled_device_extension_count = vk_renderer_get_enabled_device_extension_count(vk_renderer), + .enabled_device_extensions = vk_renderer_get_enabled_device_extensions(vk_renderer), + .get_instance_proc_address_callback = on_get_vulkan_proc_address, + .get_next_image_callback = on_get_next_vulkan_image, + .present_image_callback = on_present_vulkan_image, + }, + }; +#else + UNREACHABLE(); +#endif + } else { + COMPILE_ASSERT(sizeof(FlutterRendererConfig) == 60 || sizeof(FlutterRendererConfig) == 120); + COMPILE_ASSERT(sizeof(FlutterOpenGLRendererConfig) == 52 || sizeof(FlutterOpenGLRendererConfig) == 104); +#ifdef HAVE_EGL_GLES2 + renderer_config = (FlutterRendererConfig){ + .type = kOpenGL, + .open_gl = { + .struct_size = sizeof(FlutterOpenGLRendererConfig), + .make_current = on_make_current, + .clear_current = on_clear_current, + .present = on_present, + .fbo_callback = fbo_callback, + .make_resource_current = on_make_resource_current, + .gl_proc_resolver = proc_resolver, + .surface_transformation = on_get_transformation, + .gl_external_texture_frame_callback = on_gl_external_texture_frame_callback, + .fbo_with_frame_info_callback = NULL, + .present_with_info = NULL, + .populate_existing_damage = NULL, + }, + }; +#else + UNREACHABLE(); +#endif } - if (flutterpi.drm.drmdev == NULL) { - LOG_ERROR("flutter-pi couldn't find a usable DRM device.\n" - "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" - "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n"); - return ENOENT; - } - - // find a connected connector - for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { - if (connector->connector->connection == DRM_MODE_CONNECTED) { - // only update the physical size of the display if the values - // are not yet initialized / not set with a commandline option - if ((flutterpi.display.width_mm == 0) || (flutterpi.display.height_mm == 0)) { - if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && - (connector->connector->mmWidth == 0) && - (connector->connector->mmHeight == 0)) - { - // if it's connected via DSI, and the width & height are 0, - // it's probably the official 7 inch touchscreen. - flutterpi.display.width_mm = 155; - flutterpi.display.height_mm = 86; - } else if ((connector->connector->mmHeight % 10 == 0) && - (connector->connector->mmWidth % 10 == 0)) { - // don't change anything. - } else { - flutterpi.display.width_mm = connector->connector->mmWidth; - flutterpi.display.height_mm = connector->connector->mmHeight; - } - } + COMPILE_ASSERT(sizeof(FlutterProjectArgs) == 152 || sizeof(FlutterProjectArgs) == 288); - break; - } - } + // configure the project + project_args = (FlutterProjectArgs){ + .struct_size = sizeof(FlutterProjectArgs), + .assets_path = paths->asset_bundle_path, + .icu_data_path = paths->icudtl_path, + .command_line_argc = engine_argc, + .command_line_argv = (const char *const *) engine_argv, + .platform_message_callback = on_platform_message, + .vm_snapshot_data = NULL, + .vm_snapshot_data_size = 0, + .vm_snapshot_instructions = NULL, + .vm_snapshot_instructions_size = 0, + .isolate_snapshot_data = NULL, + .isolate_snapshot_data_size = 0, + .isolate_snapshot_instructions = NULL, + .isolate_snapshot_instructions_size = 0, + .root_isolate_create_callback = NULL, + .update_semantics_node_callback = NULL, + .update_semantics_custom_action_callback = NULL, + .persistent_cache_path = paths->asset_bundle_path, + .is_persistent_cache_read_only = false, + .vsync_callback = NULL, // on_frame_request, /* broken since 2.2, kinda */ + .custom_dart_entrypoint = NULL, + .custom_task_runners = + &(FlutterCustomTaskRunners){ + .struct_size = sizeof(FlutterCustomTaskRunners), + .platform_task_runner = + &(FlutterTaskRunnerDescription){ + .struct_size = sizeof(FlutterTaskRunnerDescription), + .user_data = flutterpi, + .runs_task_on_current_thread_callback = runs_platform_tasks_on_current_thread, + .post_task_callback = on_post_flutter_task, + }, + .render_task_runner = NULL, + .thread_priority_setter = NULL, + }, + .shutdown_dart_vm_when_done = true, + .compositor = compositor_get_flutter_compositor(compositor), + .dart_old_gen_heap_size = -1, + .aot_data = aot_data, + .compute_platform_resolved_locale_callback = on_compute_platform_resolved_locales, + .dart_entrypoint_argc = 0, + .dart_entrypoint_argv = NULL, + .log_message_callback = NULL, + .log_tag = NULL, + .on_pre_engine_restart_callback = NULL, + .update_semantics_callback = NULL, + .update_semantics_callback2 = NULL, + }; - if (connector == NULL) { - LOG_ERROR("Could not find a connected connector!\n"); - return EINVAL; + // spin up the engine + engine_result = procs->Initialize(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, flutterpi, &engine); + if (engine_result != kSuccess) { + LOG_ERROR("Could not initialize the flutter engine. FlutterEngineInitialize: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + return NULL; } - // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) - // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, - // prefer higher refresh rates. After that, prefer progressive scanout modes. - mode = NULL; - for_each_mode_in_connector(connector, mode_iter) { - if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { - mode = mode_iter; - break; - } else if (mode == NULL) { - mode = mode_iter; - } else { - int area = mode_iter->hdisplay * mode_iter->vdisplay; - int old_area = mode->hdisplay * mode->vdisplay; + return engine; +} - if ((area > old_area) || - ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || - ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { - mode = mode_iter; - } +static int flutterpi_run(struct flutterpi *flutterpi) { + FlutterEngineProcTable *procs; + struct view_geometry geometry; + FlutterEngineResult engine_result; + FlutterEngine engine; + int ok, evloop_fd; + + procs = &flutterpi->flutter.procs; + + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + ok = libseat_dispatch(flutterpi->libseat, 0); + if (ok < 0) { + LOG_ERROR("initial libseat dispatch failed. libseat_dispatch: %s\n", strerror(errno)); } +#else + UNREACHABLE(); +#endif } - if (mode == NULL) { - LOG_ERROR("Could not find a preferred output mode!\n"); + ok = plugin_registry_ensure_plugins_initialized(flutterpi->plugin_registry); + if (ok != 0) { + LOG_ERROR("Could not initialize plugins.\n"); return EINVAL; } - flutterpi.display.width = mode->hdisplay; - flutterpi.display.height = mode->vdisplay; - flutterpi.display.refresh_rate = mode->vrefresh; - - if ((flutterpi.display.width_mm == 0) || (flutterpi.display.height_mm == 0)) { - LOG_ERROR("WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default to 1.0, which may not be the fitting device-pixel ratio for your display.\n"); - flutterpi.display.pixel_ratio = 1.0; - } else { - flutterpi.display.pixel_ratio = (10.0 * flutterpi.display.width) / (flutterpi.display.width_mm * 38.0); - - int horizontal_dpi = (int) (flutterpi.display.width / (flutterpi.display.width_mm / 25.4)); - int vertical_dpi = (int) (flutterpi.display.height / (flutterpi.display.height_mm / 25.4)); - - if (horizontal_dpi != vertical_dpi) { - // See https://github.com/flutter/flutter/issues/71865 for current status of this issue. - LOG_ERROR("WARNING: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); - } + engine = create_flutter_engine( + flutterpi->vk_renderer, + flutterpi->flutter.paths, + flutterpi->flutter.engine_argc, + flutterpi->flutter.engine_argv, + flutterpi->compositor, + flutterpi->flutter.aot_data, + &flutterpi->flutter.procs + ); + if (engine == NULL) { + return EINVAL; } - - for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoder_id) { - break; - } + + flutterpi->flutter.engine = engine; + + engine_result = procs->RunInitialized(engine); + if (engine_result != kSuccess) { + LOG_ERROR("Could not run the flutter engine. FlutterEngineRunInitialized: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + ok = EIO; + goto fail_deinitialize_engine; } - - if (encoder == NULL) { - for (int i = 0; i < connector->connector->count_encoders; i++, encoder = NULL) { - for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { - if (encoder->encoder->encoder_id == connector->connector->encoders[i]) { - break; - } - } - if (encoder->encoder->possible_crtcs) { - // only use this encoder if there's a crtc we can use with it - break; - } - } + ok = locales_add_to_fl_engine(flutterpi->locales, engine, procs->UpdateLocales); + if (ok != 0) { + goto fail_shutdown_engine; } - if (encoder == NULL) { - LOG_ERROR("Could not find a suitable DRM encoder.\n"); - return EINVAL; + COMPILE_ASSERT(sizeof(FlutterEngineDisplay) == 32); + + engine_result = procs->NotifyDisplayUpdate( + engine, + kFlutterEngineDisplaysUpdateTypeStartup, + &(FlutterEngineDisplay){ + .struct_size = sizeof(FlutterEngineDisplay), + .display_id = 0, + .single_display = true, + .refresh_rate = compositor_get_refresh_rate(flutterpi->compositor), + }, + 1 + ); + if (engine_result != kSuccess) { + ok = EINVAL; + LOG_ERROR( + "Could not send display update to flutter engine. FlutterEngineNotifyDisplayUpdate: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + goto fail_shutdown_engine; } - for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { - if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { - break; + compositor_get_view_geometry(flutterpi->compositor, &geometry); + + // just so we get an error if the window metrics event was expanded without us noticing + COMPILE_ASSERT(sizeof(FlutterWindowMetricsEvent) == 64 || sizeof(FlutterWindowMetricsEvent) == 80); + + // update window size + engine_result = procs->SendWindowMetricsEvent( + engine, + &(FlutterWindowMetricsEvent){ + .struct_size = sizeof(FlutterWindowMetricsEvent), + .width = geometry.view_size.x, + .height = geometry.view_size.y, + .pixel_ratio = geometry.device_pixel_ratio, + .left = 0, + .top = 0, + .physical_view_inset_top = 0, + .physical_view_inset_right = 0, + .physical_view_inset_bottom = 0, + .physical_view_inset_left = 0, } + ); + if (engine_result != kSuccess) { + LOG_ERROR( + "Could not send window metrics to flutter engine. FlutterEngineSendWindowMetricsEvent: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + goto fail_shutdown_engine; } - if (crtc == NULL) { - for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { - if (encoder->encoder->possible_crtcs & crtc->bitmask) { - // find a CRTC that is possible to use with this encoder - break; + pthread_mutex_lock(&flutterpi->event_loop_mutex); + + ok = sd_event_get_fd(flutterpi->event_loop); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not get fd for main event loop. sd_event_get_fd: %s\n", strerror(ok)); + pthread_mutex_unlock(&flutterpi->event_loop_mutex); + goto fail_shutdown_engine; + } + + pthread_mutex_unlock(&flutterpi->event_loop_mutex); + + evloop_fd = ok; + + { + fd_set rfds, wfds, xfds; + int state; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + FD_SET(evloop_fd, &rfds); + FD_SET(evloop_fd, &wfds); + FD_SET(evloop_fd, &xfds); + + const fd_set const_fds = rfds; + + pthread_mutex_lock(&flutterpi->event_loop_mutex); + + do { + state = sd_event_get_state(flutterpi->event_loop); + switch (state) { + case SD_EVENT_INITIAL: + ok = sd_event_prepare(flutterpi->event_loop); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not prepare event loop. sd_event_prepare: %s\n", strerror(ok)); + goto fail_shutdown_engine; + } + + break; + case SD_EVENT_ARMED: + pthread_mutex_unlock(&flutterpi->event_loop_mutex); + + do { + rfds = const_fds; + wfds = const_fds; + xfds = const_fds; + ok = select(evloop_fd + 1, &rfds, &wfds, &xfds, NULL); + if ((ok < 0) && (errno != EINTR)) { + ok = errno; + LOG_ERROR("Could not wait for event loop events. select: %s\n", strerror(ok)); + goto fail_shutdown_engine; + } + } while ((ok < 0) && (errno == EINTR)); + + pthread_mutex_lock(&flutterpi->event_loop_mutex); + + ok = sd_event_wait(flutterpi->event_loop, 0); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not check for event loop events. sd_event_wait: %s\n", strerror(ok)); + goto fail_shutdown_engine; + } + + break; + case SD_EVENT_PENDING: + ok = sd_event_dispatch(flutterpi->event_loop); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not dispatch event loop events. sd_event_dispatch: %s\n", strerror(ok)); + goto fail_shutdown_engine; + } + + break; + case SD_EVENT_FINISHED: break; + default: UNREACHABLE(); } + } while (state != SD_EVENT_FINISHED); + + pthread_mutex_unlock(&flutterpi->event_loop_mutex); + } + + // We deinitialize the plugins here so plugins don't attempt to use the + // flutter engine anymore. + // For example, otherwise the gstreamer video player might call + // texture_push_frame in another thread. + plugin_registry_ensure_plugins_deinitialized(flutterpi->plugin_registry); + + flutterpi->flutter.procs.Shutdown(engine); + flutterpi->flutter.engine = NULL; + return 0; + +fail_shutdown_engine: + flutterpi->flutter.procs.Shutdown(engine); + return ok; + +fail_deinitialize_engine: + flutterpi->flutter.procs.Deinitialize(engine); + return ok; +} + +void flutterpi_schedule_exit(struct flutterpi *flutterpi) { + int ok; + + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_lock(&flutterpi->event_loop_mutex); + } + + // There's a race condition here: + // + // Other threads can always call flutterpi_post_platform_task(). We can only + // be sure flutterpi_post_platform_task() will not be called anymore when + // FlutterEngineShutdown() has returned. + // + // However, FlutterEngineShutdown() is blocking and should be called on the + // platform thread. + // + // 1. If we process them all, that's basically just continuing to run the + // application. + // + // 2. If we don't process them and just error, that could result in memory + // leaks. + // + // There's not really a nice solution here, but we use the 2nd option here. + ok = sd_event_exit(flutterpi->event_loop, 0); + if (ok < 0) { + LOG_ERROR("Could not schedule application exit. sd_event_exit: %s\n", strerror(-ok)); + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } + return; } - if (crtc == NULL) { - LOG_ERROR("Could not find a suitable DRM CRTC.\n"); - return EINVAL; + if (pthread_self() != flutterpi->event_loop_thread) { + pthread_mutex_unlock(&flutterpi->event_loop_mutex); } - ok = drmdev_configure(flutterpi.drm.drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); - if (ok != 0) return ok; + return; +} - // only enable vsync if the kernel supplies valid vblank timestamps - { - uint64_t ns = 0; - ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); - int _errno = errno; +/************** + * USER INPUT * + **************/ +static void on_flutter_pointer_event(void *userdata, const FlutterPointerEvent *events, size_t n_events) { + FlutterEngineResult engine_result; + struct flutterpi *flutterpi; - if ((ok == 0) && (ns != 0)) { - flutterpi.drm.platform_supports_get_sequence_ioctl = true; - } else { - flutterpi.drm.platform_supports_get_sequence_ioctl = false; - if (ok != 0) { - LOG_ERROR("WARNING: Error getting last vblank timestamp. drmCrtcGetSequence: %s\n", strerror(_errno)); - } else { - LOG_ERROR("WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n"); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + + /// TODO: make this atomic + flutterpi->flutter.next_frame_request_is_secondary = true; + + engine_result = flutterpi->flutter.procs.SendPointerEvent(flutterpi->flutter.engine, events, n_events); + if (engine_result != kSuccess) { + LOG_ERROR( + "Error sending touchscreen / mouse events to flutter. FlutterEngineSendPointerEvent: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + //flutterpi_schedule_exit(flutterpi); + } +} + +static void on_utf8_character(void *userdata, uint8_t *character) { + struct flutterpi *flutterpi; + int ok; + + flutterpi = userdata; + + (void) flutterpi; + +#ifdef BUILD_TEXT_INPUT_PLUGIN + ok = textin_on_utf8_char(character); + if (ok != 0) { + LOG_ERROR("Error handling keyboard event. textin_on_utf8_char: %s\n", strerror(ok)); + //flutterpi_schedule_exit(flutterpi); + } +#endif +} + +static void on_xkb_keysym(void *userdata, xkb_keysym_t keysym) { + struct flutterpi *flutterpi; + int ok; + + flutterpi = userdata; + (void) flutterpi; + +#ifdef BUILD_TEXT_INPUT_PLUGIN + ok = textin_on_xkb_keysym(keysym); + if (ok != 0) { + LOG_ERROR("Error handling keyboard event. textin_on_xkb_keysym: %s\n", strerror(ok)); + //flutterpi_schedule_exit(flutterpi); + } +#endif +} + +static void +on_gtk_keyevent(void *userdata, uint32_t unicode_scalar_values, uint32_t key_code, uint32_t scan_code, uint32_t modifiers, bool is_down) { + struct flutterpi *flutterpi; + int ok; + + flutterpi = userdata; + (void) flutterpi; + +#ifdef BUILD_RAW_KEYBOARD_PLUGIN + ok = rawkb_send_gtk_keyevent(unicode_scalar_values, key_code, scan_code, modifiers, is_down); + if (ok != 0) { + LOG_ERROR("Error handling keyboard event. rawkb_send_gtk_keyevent: %s\n", strerror(ok)); + //flutterpi_schedule_exit(flutterpi); + } +#endif +} + +static void on_switch_vt(void *userdata, int vt) { + struct flutterpi *flutterpi; + + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + (void) flutterpi; + (void) vt; + + LOG_DEBUG("on_switch_vt(%d)\n", vt); + + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + int ok; + + ok = libseat_switch_session(flutterpi->libseat, vt); + if (ok < 0) { + LOG_ERROR("Could not switch session. libseat_switch_session: %s\n", strerror(errno)); + } +#else + UNREACHABLE(); +#endif + } +} + +static void on_set_cursor_enabled(void *userdata, bool enabled) { + struct flutterpi *flutterpi; + + flutterpi = userdata; + (void) flutterpi; + + compositor_set_cursor(flutterpi->compositor, true, enabled, false, POINTER_KIND_NONE, false, VEC2F(0, 0)); +} + +static void on_move_cursor(void *userdata, struct vec2f delta) { + struct flutterpi *flutterpi; + + flutterpi = userdata; + + compositor_set_cursor(flutterpi->compositor, true, true, false, POINTER_KIND_NONE, true, delta); +} + +static int on_user_input_open(const char *path, int flags, void *userdata) { + struct flutterpi *flutterpi; + int ok, fd; + + ASSERT_NOT_NULL(path); + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + (void) flutterpi; + + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + struct device_id_and_fd *entry; + int device_id; + + ok = libseat_open_device(flutterpi->libseat, path, &fd); + if (ok < 0) { + ok = errno; + LOG_ERROR("Couldn't open evdev device. libseat_open_device: %s\n", strerror(ok)); + return -ok; + } + + device_id = ok; + + entry = malloc(sizeof *entry); + if (entry == NULL) { + libseat_close_device(flutterpi->libseat, device_id); + return -ENOMEM; + } + + entry->entry = (struct list_head){ NULL, NULL }; + entry->fd = fd; + entry->device_id = device_id; + + list_add(&entry->entry, &flutterpi->fd_for_device_id); + return fd; +#else + UNREACHABLE(); +#endif + } else { + ok = open(path, flags); + if (ok < 0) { + ok = errno; + LOG_ERROR("Couldn't open evdev device. open: %s\n", strerror(ok)); + return -ok; + } + + fd = ok; + return fd; + } +} + +static void on_user_input_close(int fd, void *userdata) { + struct flutterpi *flutterpi; + int ok; + + ASSERT_NOT_NULL(userdata); + flutterpi = userdata; + (void) flutterpi; + + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + struct device_id_and_fd *entry = NULL; + + list_for_each_entry_safe(struct device_id_and_fd, entry_iter, &flutterpi->fd_for_device_id, entry) { + if (entry_iter->fd == fd) { + entry = entry_iter; + break; } - LOG_ERROR("VSync will be disabled.\nSee https://github.com/ardera/flutter-pi/issues/38 for more info.\n"); + } + + if (entry == NULL) { + LOG_ERROR("Could not find the device id for the evdev device that should be closed.\n"); + return; + } + + ok = libseat_close_device(flutterpi->libseat, entry->device_id); + if (ok < 0) { + LOG_ERROR("Couldn't close evdev device. libseat_close_device: %s\n", strerror(errno)); + } + + list_del(&entry->entry); + free(entry); + return; +#else + UNREACHABLE(); +#endif + } else { + ok = close(fd); + if (ok < 0) { + LOG_ERROR("Could not close evdev device. close: %s\n", strerror(errno)); } } +} + +static int on_user_input_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct user_input *input; + + (void) s; + (void) fd; + (void) revents; + + input = userdata; + + return user_input_on_fd_ready(input); +} + +struct cmd_args { + bool has_orientation; + enum device_orientation orientation; + + bool has_rotation; + int rotation; + + bool has_physical_dimensions; + int width_mm, height_mm; + + bool has_pixel_format; + enum pixfmt pixel_format; + + bool has_runtime_mode; + enum flutter_runtime_mode runtime_mode; + + char *bundle_path; + + int engine_argc; + char **engine_argv; - memset(&flutterpi.drm.evctx, 0, sizeof(drmEventContext)); - flutterpi.drm.evctx.version = 4; - flutterpi.drm.evctx.page_flip_handler = on_pageflip_event; + bool use_vulkan; + + char *desired_videomode; +}; + +static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { +#if defined(FILESYSTEM_LAYOUT_DEFAULT) + return fs_layout_flutterpi_resolve(app_bundle_path, runtime_mode); +#elif defined(FILESYSTEM_LAYOUT_METAFLUTTER) + return fs_layout_metaflutter_resolve(app_bundle_path, runtime_mode); +#else + #error "Exactly one of FILESYSTEM_LAYOUT_DEFAULT or FILESYSTEM_LAYOUT_METAFLUTTER must be defined." + return NULL; +#endif +} + +static bool parse_cmd_args(int argc, char **argv, struct cmd_args *result_out) { + bool finished_parsing_options; + int runtime_mode_int = FLUTTER_RUNTIME_MODE_DEBUG; + int vulkan_int = false; + int longopt_index = 0; + int opt, ok; + + struct option long_options[] = { + { "release", no_argument, &runtime_mode_int, FLUTTER_RUNTIME_MODE_RELEASE }, + { "profile", no_argument, &runtime_mode_int, FLUTTER_RUNTIME_MODE_PROFILE }, + { "orientation", required_argument, NULL, 'o' }, + { "rotation", required_argument, NULL, 'r' }, + { "dimensions", required_argument, NULL, 'd' }, + { "help", no_argument, 0, 'h' }, + { "pixelformat", required_argument, NULL, 'p' }, + { "vulkan", no_argument, &vulkan_int, true }, + { "videomode", required_argument, NULL, 'v' }, + { 0, 0, 0, 0 }, + }; + + memset(result_out, 0, sizeof *result_out); + + result_out->has_orientation = false; + result_out->has_rotation = false; + result_out->has_physical_dimensions = false; + result_out->has_pixel_format = false; + result_out->has_runtime_mode = false; + result_out->bundle_path = NULL; + result_out->engine_argc = 0; + result_out->engine_argv = NULL; + + finished_parsing_options = false; + while (!finished_parsing_options) { + longopt_index = 0; + opt = getopt_long(argc, argv, "+i:o:r:d:h", long_options, &longopt_index); + + switch (opt) { + case 0: + // flag was encountered. just continue + break; + + case 'o': + if (streq(optarg, "portrait_up")) { + result_out->orientation = kPortraitUp; + result_out->has_orientation = true; + } else if (streq(optarg, "landscape_left")) { + result_out->orientation = kLandscapeLeft; + result_out->has_orientation = true; + } else if (streq(optarg, "portrait_down")) { + result_out->orientation = kPortraitDown; + result_out->has_orientation = true; + } else if (streq(optarg, "landscape_right")) { + result_out->orientation = kLandscapeRight; + result_out->has_orientation = true; + } else { + LOG_ERROR( + "ERROR: Invalid argument for --orientation passed.\n" + "Valid values are \"portrait_up\", \"landscape_left\", \"portrait_down\", \"landscape_right\".\n" + "%s", + usage + ); + return false; + } + break; + + case 'r': + errno = 0; + long rotation = strtol(optarg, NULL, 0); + if ((errno != 0) || ((rotation != 0) && (rotation != 90) && (rotation != 180) && (rotation != 270))) { + LOG_ERROR( + "ERROR: Invalid argument for --rotation passed.\n" + "Valid values are 0, 90, 180, 270.\n" + "%s", + usage + ); + return false; + } + + result_out->rotation = rotation; + result_out->has_rotation = true; + break; + + case 'd':; + unsigned int width_mm, height_mm; + + ok = sscanf(optarg, "%u,%u", &width_mm, &height_mm); + if (ok != 2) { + LOG_ERROR("ERROR: Invalid argument for --dimensions passed.\n%s", usage); + return false; + } + + result_out->width_mm = width_mm; + result_out->height_mm = height_mm; + result_out->has_physical_dimensions = true; + + break; + + case 'p': + for (unsigned i = 0; i < n_pixfmt_infos; i++) { + if (streq(optarg, pixfmt_infos[i].arg_name)) { + result_out->has_pixel_format = true; + result_out->pixel_format = pixfmt_infos[i].format; + goto valid_format; + } + } + + LOG_ERROR( + "ERROR: Invalid argument for --pixelformat passed.\n" + "Valid values are: " PIXFMT_LIST(PIXFMT_ARG_NAME + ) "\n" + "%s", + usage + ); + return false; + +valid_format: + break; + + case 'v':; + char *vmode_dup = strdup(optarg); + if (vmode_dup == NULL) { + return false; + } - ok = sd_event_add_io( - flutterpi.event_loop, - &flutterpi.drm.drm_pageflip_event_source, - flutterpi.drm.drmdev->fd, - EPOLLIN | EPOLLHUP | EPOLLPRI, - on_drm_fd_ready, - NULL - ); - if (ok < 0) { - LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); - return -ok; - } + result_out->desired_videomode = vmode_dup; + break; - locales_print(flutterpi.locales); - printf( - "===================================\n" - "display mode:\n" - " resolution: %u x %u\n" - " refresh rate: %uHz\n" - " physical size: %umm x %umm\n" - " flutter device pixel ratio: %f\n" - "===================================\n", - flutterpi.display.width, flutterpi.display.height, - flutterpi.display.refresh_rate, - flutterpi.display.width_mm, flutterpi.display.height_mm, - flutterpi.display.pixel_ratio - ); + case 'h': printf("%s", usage); return false; - /********************** - * GBM INITIALIZATION * - **********************/ - flutterpi.gbm.device = gbm_create_device(flutterpi.drm.drmdev->fd); - flutterpi.gbm.surface = NULL; - flutterpi.gbm.modifier = DRM_FORMAT_MOD_LINEAR; - if (!flutterpi.gbm.format) - flutterpi.gbm.format = DRM_FORMAT_XRGB8888; + case '?': + case ':': LOG_ERROR("Invalid option specified.\n%s", usage); return false; - flutterpi.gbm.surface = gbm_surface_create_with_modifiers(flutterpi.gbm.device, flutterpi.display.width, flutterpi.display.height, flutterpi.gbm.format, &flutterpi.gbm.modifier, 1); - if (flutterpi.gbm.surface == NULL) { - perror("[flutter-pi] Could not create GBM Surface. gbm_surface_create_with_modifiers. Will attempt with gbm_surface_create"); + case -1: finished_parsing_options = true; break; - flutterpi.gbm.surface = gbm_surface_create(flutterpi.gbm.device, flutterpi.display.width, flutterpi.display.height, flutterpi.gbm.format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + default: break; + } + } - if (flutterpi.gbm.surface == NULL) { - perror("[flutter-pi] Could not create GBM Surface even with gbm_surface_create"); - return errno; - } + if (optind >= argc) { + LOG_ERROR("ERROR: Expected asset bundle path after options.\n"); + printf("%s", usage); + return false; } - /********************** - * EGL INITIALIZATION * - **********************/ - EGLint major, minor; + result_out->bundle_path = realpath(argv[optind], NULL); + result_out->runtime_mode = runtime_mode_int; + result_out->has_runtime_mode = runtime_mode_int != 0; - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; + argv[optind] = argv[0]; + result_out->engine_argc = argc - optind; + result_out->engine_argv = argv + optind; - const EGLint config_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SAMPLES, 0, - EGL_NONE - }; +#ifndef HAVE_VULKAN + if (vulkan_int == true) { + LOG_ERROR("ERROR: --vulkan was specified, but flutter-pi was built without vulkan support.\n"); + printf("%s", usage); + return false; + } +#endif + result_out->use_vulkan = vulkan_int; - const char *egl_exts_client, *egl_exts_dpy, *gl_exts; + return true; +} - egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); +static int on_drmdev_open(const char *path, int flags, void **fd_metadata_out, void *userdata) { + int ok, fd, device_id; - ok = load_egl_gl_procs(); - if (ok != 0) { - LOG_ERROR("Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); - return ok; - } + ASSERT_NOT_NULL(path); + ASSERT_NOT_NULL(fd_metadata_out); + (void) userdata; - eglGetError(); +#ifdef HAVE_LIBSEAT + struct libseat *libseat = userdata; + if (libseat != NULL) { + ok = libseat_open_device(libseat, path, &fd); + if (ok < 0) { + LOG_ERROR("Couldn't open DRM device. libseat_open_device: %s\n", strerror(errno)); + return -1; + } -#ifdef EGL_KHR_platform_gbm - flutterpi.egl.display = flutterpi.egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, flutterpi.gbm.device, NULL); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get EGL display! eglGetPlatformDisplay: 0x%08X\n", egl_error); - return EIO; + device_id = ok; + *(intptr_t *) fd_metadata_out = (intptr_t) device_id; + return fd; } #else - flutterpi.egl.display = eglGetDisplay((void*) flutterpi.gbm.device); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get EGL display! eglGetDisplay: 0x%08X\n", egl_error); - return EIO; - } + ASSERT_EQUALS(userdata, NULL); #endif - - eglInitialize(flutterpi.egl.display, &major, &minor); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Failed to initialize EGL! eglInitialize: 0x%08X\n", egl_error); - return EIO; - } - - egl_exts_dpy = eglQueryString(flutterpi.egl.display, EGL_EXTENSIONS); - - printf("EGL information:\n"); - printf(" version: %s\n", eglQueryString(flutterpi.egl.display, EGL_VERSION)); - printf(" vendor: \"%s\"\n", eglQueryString(flutterpi.egl.display, EGL_VENDOR)); - printf(" client extensions: \"%s\"\n", egl_exts_client); - printf(" display extensions: \"%s\"\n", egl_exts_dpy); - printf("===================================\n"); - eglBindAPI(EGL_OPENGL_ES_API); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Failed to bind OpenGL ES API! eglBindAPI: 0x%08X\n", egl_error); - return EIO; + ok = open(path, flags); + if (ok < 0) { + LOG_ERROR("Couldn't open DRM device. open: %s\n", strerror(errno)); + return -1; } - EGLint count = 0, matched = 0; - EGLConfig *configs; - bool _found_matching_config = false; - - eglGetConfigs(flutterpi.egl.display, NULL, 0, &count); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not get the number of EGL framebuffer configurations. eglGetConfigs: 0x%08X\n", egl_error); - return EIO; - } + fd = ok; + device_id = 0; - configs = malloc(count * sizeof(EGLConfig)); - if (!configs) return ENOMEM; + *(intptr_t *) fd_metadata_out = (intptr_t) device_id; + return fd; +} - eglChooseConfig(flutterpi.egl.display, config_attribs, configs, count, &matched); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", egl_error); - return EIO; - } +static void on_drmdev_close(int fd, void *fd_metadata, void *userdata) { + int ok; - if (matched == 0) { - LOG_ERROR("No fitting EGL framebuffer configuration found.\n"); - return EIO; - } + (void) fd_metadata; + (void) userdata; - for (int i = 0; i < count; i++) { - EGLint native_visual_id; +#ifdef HAVE_LIBSEAT + struct libseat *libseat = userdata; + if (libseat != NULL) { + ASSERT_NOT_NULL(fd_metadata); + int device_id = (intptr_t) fd_metadata; - eglGetConfigAttrib(flutterpi.egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &native_visual_id); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not query native visual ID of EGL config. eglGetConfigAttrib: 0x%08X\n", egl_error); - continue; + ok = libseat_close_device(libseat, device_id); + if (ok < 0) { + LOG_ERROR("Couldn't close DRM device. libseat_close_device: %s\n", strerror(errno)); + return; } - if (native_visual_id == flutterpi.gbm.format) { - flutterpi.egl.config = configs[i]; - _found_matching_config = true; - break; - } + return; } - free(configs); +#else + ASSERT_EQUALS(userdata, NULL); +#endif - if (_found_matching_config == false) { - LOG_ERROR("Could not find EGL framebuffer configuration with appropriate attributes & native visual ID.\n"); - return EIO; + ok = close(fd); + if (ok < 0) { + LOG_ERROR("Couldn't close DRM device. close: %s\n", strerror(errno)); + return; } +} - /**************************** - * OPENGL ES INITIALIZATION * - ****************************/ - flutterpi.egl.root_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, EGL_NO_CONTEXT, context_attribs); - if (flutterpi.egl.root_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES root context. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } +static const struct drmdev_interface drmdev_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; - flutterpi.egl.flutter_render_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.flutter_render_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for flutter rendering. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } +static struct drmdev *find_drmdev(struct libseat *libseat) { + struct drm_connector *connector; + struct drmdev *drmdev; + drmDevicePtr devices[64]; + int ok, n_devices; - flutterpi.egl.flutter_resource_uploading_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.flutter_resource_uploading_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for flutter resource uploads. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } +#ifndef HAVE_LIBSEAT + ASSERT_EQUALS(libseat, NULL); +#endif - flutterpi.egl.compositor_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.compositor_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for compositor. eglCreateContext: 0x%08X\n", egl_error); - return EIO; + ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); + if (ok < 0) { + LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); + return NULL; } - pthread_mutex_init(&flutterpi.egl.temp_context_lock, NULL); + n_devices = ok; - flutterpi.egl.temp_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); - if (flutterpi.egl.temp_context == EGL_NO_CONTEXT) { - LOG_ERROR("Could not create OpenGL ES context for creating new contexts. eglCreateContext: 0x%08X\n", egl_error); - return EIO; - } + // find a GPU that has a primary node + drmdev = NULL; + for (int i = 0; i < n_devices; i++) { + drmDevicePtr device; - flutterpi.egl.surface = eglCreateWindowSurface(flutterpi.egl.display, flutterpi.egl.config, (EGLNativeWindowType) flutterpi.gbm.surface, NULL); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not create EGL window surface. eglCreateWindowSurface: 0x%08X\n", egl_error); - return EIO; - } + device = devices[i]; - eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); - return EIO; - } + if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { + // We need a primary node. + continue; + } - flutterpi.egl.renderer = (char*) glGetString(GL_RENDERER); - - gl_exts = (char*) glGetString(GL_EXTENSIONS); - printf("OpenGL ES information:\n"); - printf(" version: \"%s\"\n", glGetString(GL_VERSION)); - printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); - printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR)); - printf(" renderer: \"%s\"\n", flutterpi.egl.renderer); - printf(" extensions: \"%s\"\n", gl_exts); - printf("===================================\n"); - - // it seems that after some Raspbian update, regular users are sometimes no longer allowed - // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ - // as read-write. flutter-pi must be run as root then. - // sometimes it works fine without root, sometimes it doesn't. - if (strncmp(flutterpi.egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) { - printf("WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" - " Check that flutter-pi has permission to use the 3D graphics hardware,\n" - " or try running it as root.\n" - " This warning will probably result in a \"failed to set mode\" error\n" - " later on in the initialization.\n"); - } - - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - LOG_ERROR("Could not clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); - return EIO; - } + drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], &drmdev_interface, libseat); + if (drmdev == NULL) { + LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); + continue; + } - /// miscellaneous initialization - /// initialize the compositor - ok = compositor_initialize(flutterpi.drm.drmdev); - if (ok != 0) { - return ok; + for_each_connector_in_drmdev(drmdev, connector) { + if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { + goto found_connected_connector; + } + } + LOG_ERROR("Device \"%s\" doesn't have a display connected. Skipping.\n", device->nodes[DRM_NODE_PRIMARY]); + drmdev_unref(drmdev); + continue; + +found_connected_connector: + break; } - /// initialize the frame queue - ok = cqueue_init(&flutterpi.frame_queue, sizeof(struct frame), QUEUE_DEFAULT_MAX_SIZE); - if (ok != 0) { - return ok; + drmFreeDevices(devices, n_devices); + + if (drmdev == NULL) { + LOG_ERROR( + "flutter-pi couldn't find a usable DRM device.\n" + "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" + "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n" + ); + goto fail_free_devices; } - /// We're starting without any rotation by default. - flutterpi_fill_view_properties(false, 0, false, 0); + return drmdev; - return 0; +fail_free_devices: + drmFreeDevices(devices, n_devices); + return NULL; } -/************************** - * FLUTTER INITIALIZATION * - **************************/ -static int init_application(void) { - FlutterEngineAOTDataSource aot_source; - enum flutter_runtime_mode runtime_mode; - struct libflutter_engine *libflutter_engine; - struct texture_registry *texture_registry; - struct plugin_registry *plugin_registry; - FlutterRendererConfig renderer_config = {0}; - FlutterEngineAOTData aot_data; - FlutterEngineResult engine_result; - FlutterProjectArgs project_args = {0}; - void *engine_handle; +#ifdef HAVE_LIBSEAT +static void on_session_enable(struct libseat *seat, void *userdata) { + struct flutterpi *fpi; int ok; - runtime_mode = flutterpi.flutter.runtime_mode; - - engine_handle = NULL; - if (flutterpi.flutter.paths->flutter_engine_path != NULL) { - engine_handle = dlopen(flutterpi.flutter.paths->flutter_engine_path, RTLD_LOCAL | RTLD_NOW); - if (engine_handle == NULL) { - LOG_DEBUG( - "Info: Could not load flutter engine from app bundle. dlopen(\"%s\"): %s.\n", - flutterpi.flutter.paths->flutter_engine_path, - dlerror() - ); - } - } + ASSERT_NOT_NULL(seat); + ASSERT_NOT_NULL(userdata); + fpi = userdata; + (void) fpi; + (void) seat; - if (engine_handle == NULL && flutterpi.flutter.paths->flutter_engine_dlopen_name != NULL) { - engine_handle = dlopen(flutterpi.flutter.paths->flutter_engine_dlopen_name, RTLD_LOCAL | RTLD_NOW); - if (engine_handle == NULL) { - LOG_DEBUG( - "Info: Could not load flutter engine. dlopen(\"%s\"): %s.\n", - flutterpi.flutter.paths->flutter_engine_dlopen_name, - dlerror() - ); - } - } + /// TODO: Implement + LOG_DEBUG("on_session_enable\n"); - if (engine_handle == NULL && flutterpi.flutter.paths->flutter_engine_dlopen_name_fallback != NULL) { - engine_handle = dlopen(flutterpi.flutter.paths->flutter_engine_dlopen_name_fallback, RTLD_LOCAL | RTLD_NOW); - if (engine_handle == NULL) { - LOG_DEBUG( - "Info: Could not load flutter engine. dlopen(\"%s\"): %s.\n", - flutterpi.flutter.paths->flutter_engine_dlopen_name_fallback, - dlerror() - ); + if (fpi->user_input != NULL) { + ok = user_input_resume(fpi->user_input); + if (ok != 0) { + LOG_ERROR("Couldn't resume user input handling.\n"); } } - if (engine_handle == NULL) { - LOG_ERROR("Error: Could not load flutter engine from any location. Make sure you have installed the engine binaries.\n"); - return EINVAL; - } - - libflutter_engine = &flutterpi.flutter.libflutter_engine; - -# define LOAD_LIBFLUTTER_ENGINE_PROC(name) \ - do { \ - libflutter_engine->name = dlsym(engine_handle, #name); \ - if (!libflutter_engine->name) {\ - perror("[flutter-pi] Could not resolve libflutter_engine procedure " #name ". dlsym"); \ - return EINVAL; \ - } \ - } while (false) - - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineCreateAOTData); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineCollectAOTData); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRun); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineShutdown); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineInitialize); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineDeinitialize); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunInitialized); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendWindowMetricsEvent); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPointerEvent); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPlatformMessage); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterPlatformMessageCreateResponseHandle); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterPlatformMessageReleaseResponseHandle); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineSendPlatformMessageResponse); - LOAD_LIBFLUTTER_ENGINE_PROC(__FlutterEngineFlushPendingTasksNow); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRegisterExternalTexture); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUnregisterExternalTexture); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineMarkExternalTextureFrameAvailable); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateSemanticsEnabled); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateAccessibilityFeatures); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineDispatchSemanticsAction); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineOnVsync); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineReloadSystemFonts); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventDurationBegin); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventDurationEnd); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineTraceEventInstant); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostRenderThreadTask); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineGetCurrentTime); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunTask); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineUpdateLocales); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineRunsAOTCompiledDartCode); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostDartObject); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineNotifyLowMemoryWarning); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEnginePostCallbackOnAllNativeThreads); - LOAD_LIBFLUTTER_ENGINE_PROC(FlutterEngineNotifyDisplayUpdate); - -# undef LOAD_LIBFLUTTER_ENGINE_PROC - - plugin_registry = plugin_registry_new(&flutterpi); - if (plugin_registry == NULL) { - LOG_ERROR("Could not create plugin registry.\n"); - return EIO; - } - - flutterpi.plugin_registry = plugin_registry; + // if (fpi->drmdev != NULL) { + // ok = drmdev_resume(fpi->drmdev); + // if (ok != 0) { + // LOG_ERROR("Couldn't resume drmdev.\n"); + // } + // } - ok = plugin_registry_add_plugins_from_static_registry(plugin_registry); - if (ok != 0) { - LOG_ERROR("Could not register plugins to plugin registry.\n"); - return EIO; - } + fpi->session_active = true; +} - ok = plugin_registry_ensure_plugins_initialized(plugin_registry); - if (ok != 0) { - LOG_ERROR("Could not initialize plugins.\n"); - return EIO; - } +static void on_session_disable(struct libseat *seat, void *userdata) { + struct flutterpi *fpi; - // configure flutter rendering - renderer_config = (FlutterRendererConfig) { - .type = kOpenGL, - .open_gl = { - .struct_size = sizeof(FlutterOpenGLRendererConfig), - .make_current = on_make_current, - .clear_current = on_clear_current, - .present = on_present, - .fbo_callback = fbo_callback, - .make_resource_current = on_make_resource_current, - .gl_proc_resolver = proc_resolver, - .surface_transformation = on_get_transformation, - .gl_external_texture_frame_callback = on_gl_external_texture_frame_callback, - } - }; + ASSERT_NOT_NULL(seat); + ASSERT_NOT_NULL(userdata); + fpi = userdata; + (void) fpi; - // configure the project - project_args = (FlutterProjectArgs) { - .struct_size = sizeof(FlutterProjectArgs), - .assets_path = flutterpi.flutter.paths->asset_bundle_path, - .icu_data_path = flutterpi.flutter.paths->icudtl_path, - .command_line_argc = flutterpi.flutter.engine_argc, - .command_line_argv = (const char * const*) flutterpi.flutter.engine_argv, - .platform_message_callback = on_platform_message, - .vm_snapshot_data = NULL, - .vm_snapshot_data_size = 0, - .vm_snapshot_instructions = NULL, - .vm_snapshot_instructions_size = 0, - .isolate_snapshot_data = NULL, - .isolate_snapshot_data_size = 0, - .isolate_snapshot_instructions = NULL, - .isolate_snapshot_instructions_size = 0, - .root_isolate_create_callback = NULL, - .update_semantics_node_callback = NULL, - .update_semantics_custom_action_callback = NULL, - .persistent_cache_path = NULL, - .is_persistent_cache_read_only = false, - .vsync_callback = NULL /* on_frame_request - broken since 2.2 */, - .custom_dart_entrypoint = NULL, - .custom_task_runners = &(FlutterCustomTaskRunners) { - .struct_size = sizeof(FlutterCustomTaskRunners), - .platform_task_runner = &(FlutterTaskRunnerDescription) { - .struct_size = sizeof(FlutterTaskRunnerDescription), - .user_data = NULL, - .runs_task_on_current_thread_callback = runs_platform_tasks_on_current_thread, - .post_task_callback = on_post_flutter_task - }, - .render_task_runner = NULL, - .thread_priority_setter = NULL - }, - .shutdown_dart_vm_when_done = true, - .compositor = &flutter_compositor, - .dart_old_gen_heap_size = -1, - .compute_platform_resolved_locale_callback = NULL, - .dart_entrypoint_argc = 0, - .dart_entrypoint_argv = NULL, - .log_message_callback = NULL, - .log_tag = NULL - }; + /// TODO: Implement + LOG_DEBUG("on_session_disable\n"); - bool engine_is_aot = libflutter_engine->FlutterEngineRunsAOTCompiledDartCode(); - if (engine_is_aot == true && runtime_mode == kDebug) { - LOG_ERROR( - "The flutter engine was built for release or profile (AOT) mode, but flutter-pi was not started up in release or profile mode.\n" - "Either you swap out the libflutter_engine.so with one that was built for debug mode, or you start" - "flutter-pi with the --release or --profile flag and make sure a valid \"app.so\" is located inside the asset bundle directory.\n" - ); - return EINVAL; - } else if (engine_is_aot == false && runtime_mode != kDebug) { - LOG_ERROR( - "The flutter engine was built for debug mode, but flutter-pi was started up in release mode.\n" - "Either you swap out the libflutter_engine.so with one that was built for release mode," - "or you start flutter-pi without the --release flag.\n" - ); - return EINVAL; + if (fpi->user_input != NULL) { + user_input_suspend(fpi->user_input); } - if (flutterpi.flutter.runtime_mode != kDebug) { - aot_source = (FlutterEngineAOTDataSource) { - .elf_path = flutterpi.flutter.paths->app_elf_path, - .type = kFlutterEngineAOTDataSourceTypeElfPath - }; - - engine_result = libflutter_engine->FlutterEngineCreateAOTData(&aot_source, &aot_data); - if (engine_result != kSuccess) { - LOG_ERROR("Could not load AOT data. FlutterEngineCreateAOTData: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EIO; - } - - project_args.aot_data = aot_data; - } + // if (fpi->drmdev != NULL) { + // drmdev_suspend(fpi->drmdev); + // } - // spin up the engine - engine_result = libflutter_engine->FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, &flutterpi, &flutterpi.flutter.engine); - if (engine_result != kSuccess) { - LOG_ERROR("Could not initialize the flutter engine. FlutterEngineInitialize: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; - } - - engine_result = libflutter_engine->FlutterEngineRunInitialized(flutterpi.flutter.engine); - if (engine_result != kSuccess) { - LOG_ERROR("Could not run the flutter engine. FlutterEngineRunInitialized: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; - } + libseat_disable_seat(seat); - ok = locales_add_to_fl_engine(flutterpi.locales, flutterpi.flutter.engine, libflutter_engine->FlutterEngineUpdateLocales); - if (ok != 0) { - return ok; - } + fpi->session_active = false; +} - texture_registry = texture_registry_new( - &(const struct flutter_external_texture_interface) { - .register_external_texture = libflutter_engine->FlutterEngineRegisterExternalTexture, - .unregister_external_texture = libflutter_engine->FlutterEngineUnregisterExternalTexture, - .mark_external_texture_frame_available = libflutter_engine->FlutterEngineMarkExternalTextureFrameAvailable, - .engine = flutterpi.flutter.engine - } - ); - flutterpi.texture_registry = texture_registry; +static int on_libseat_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct flutterpi *fpi; + int ok; - engine_result = libflutter_engine->FlutterEngineNotifyDisplayUpdate( - flutterpi.flutter.engine, - kFlutterEngineDisplaysUpdateTypeStartup, - &(FlutterEngineDisplay) { - .struct_size = sizeof(FlutterEngineDisplay), - .display_id = 0, - .single_display = true, - .refresh_rate = flutterpi.display.refresh_rate - }, - 1 - ); - if (engine_result != kSuccess) { - LOG_ERROR("Could not send display update to flutter engine. FlutterEngineNotifyDisplayUpdate: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; - } + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(userdata); + fpi = userdata; + (void) s; + (void) fd; + (void) revents; - // update window size - engine_result = libflutter_engine->FlutterEngineSendWindowMetricsEvent( - flutterpi.flutter.engine, - &(FlutterWindowMetricsEvent) { - .struct_size = sizeof(FlutterWindowMetricsEvent), - .width = flutterpi.view.width, - .height = flutterpi.view.height, - .pixel_ratio = flutterpi.display.pixel_ratio, - .left = 0, - .top = 0, - .physical_view_inset_top = 0, - .physical_view_inset_right = 0, - .physical_view_inset_bottom = 0, - .physical_view_inset_left = 0 - } - ); - if (engine_result != kSuccess) { - LOG_ERROR("Could not send window metrics to flutter engine. FlutterEngineSendWindowMetricsEvent: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; + ok = libseat_dispatch(fpi->libseat, 0); + if (ok < 0) { + LOG_ERROR("Couldn't dispatch libseat events. libseat_dispatch: %s\n", strerror(errno)); } return 0; } +#endif -int flutterpi_schedule_exit(void) { - int ok; +struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { + enum flutter_runtime_mode runtime_mode; + enum renderer_type renderer_type; + struct texture_registry *texture_registry; + struct plugin_registry *plugin_registry; + struct frame_scheduler *scheduler; + struct flutter_paths *paths; + struct view_geometry geometry; + FlutterEngineAOTData aot_data; + FlutterEngineResult engine_result; + struct gl_renderer *gl_renderer; + struct vk_renderer *vk_renderer; + struct gbm_device *gbm_device; + struct user_input *input; + struct compositor *compositor; + struct flutterpi *fpi; + struct sd_event *event_loop; + struct cmd_args cmd_args; + struct libseat *libseat; + struct locales *locales; + struct drmdev *drmdev; + struct tracer *tracer; + struct window *window; + void *engine_handle; + char *bundle_path, **engine_argv, *desired_videomode; + int ok, engine_argc, wakeup_fd; - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_lock(&flutterpi.event_loop_mutex); - } - - ok = sd_event_exit(flutterpi.event_loop, 0); - if (ok < 0) { - LOG_ERROR("Could not schedule application exit. sd_event_exit: %s\n", strerror(-ok)); - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); - } - return -ok; + fpi = malloc(sizeof *fpi); + if (fpi == NULL) { + return NULL; } - if (pthread_self() != flutterpi.event_loop_thread) { - pthread_mutex_unlock(&flutterpi.event_loop_mutex); + /// TODO: Remove this + flutterpi = fpi; + + ok = parse_cmd_args(argc, argv, &cmd_args); + if (ok == false) { + goto fail_free_fpi; } - return 0; -} + runtime_mode = cmd_args.has_runtime_mode ? cmd_args.runtime_mode : FLUTTER_RUNTIME_MODE_DEBUG; + bundle_path = cmd_args.bundle_path; + engine_argc = cmd_args.engine_argc; + engine_argv = cmd_args.engine_argv; -/************** - * USER INPUT * - **************/ -static void on_flutter_pointer_event(void *userdata, const FlutterPointerEvent *events, size_t n_events) { - FlutterEngineResult engine_result; - struct flutterpi *flutterpi; +#if defined(HAVE_EGL_GLES2) && defined(HAVE_VULKAN) + renderer_type = cmd_args.use_vulkan ? kVulkan_RendererType : kOpenGL_RendererType; +#elif defined(HAVE_EGL_GLES2) && !defined(HAVE_VULKAN) + ASSUME(!cmd_args.use_vulkan); + renderer_type = kOpenGL_RendererType; +#elif !defined(HAVE_EGL_GLES2) && defined(HAVE_VULKAN) + renderer_type = kVulkan_RendererType; +#else + #error "At least one of the Vulkan and OpenGL renderer backends must be built." +#endif - flutterpi = userdata; + desired_videomode = cmd_args.desired_videomode; - engine_result = flutterpi->flutter.libflutter_engine.FlutterEngineSendPointerEvent( - flutterpi->flutter.engine, - events, - n_events - ); + paths = setup_paths(runtime_mode, bundle_path); + if (paths == NULL) { + goto fail_free_cmd_args; + } - if (engine_result != kSuccess) { - LOG_ERROR("Error sending touchscreen / mouse events to flutter. FlutterEngineSendPointerEvent: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - //flutterpi_schedule_exit(flutterpi); + wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (wakeup_fd < 0) { + LOG_ERROR("Could not create fd for waking up the main loop. eventfd: %s\n", strerror(errno)); + goto fail_free_paths; } -} -static void on_utf8_character(void *userdata, uint8_t *character) { - struct flutterpi *flutterpi; - int ok; + ok = sd_event_new(&event_loop); + if (ok < 0) { + LOG_ERROR("Could not create main event loop. sd_event_new: %s\n", strerror(-ok)); + goto fail_close_wakeup_fd; + } - flutterpi = userdata; + ok = sd_event_add_io(event_loop, NULL, wakeup_fd, EPOLLIN, on_wakeup_main_loop, NULL); + if (ok < 0) { + LOG_ERROR("Error adding wakeup callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + goto fail_unref_event_loop; + } - (void) flutterpi; +#ifdef HAVE_LIBSEAT + static const struct libseat_seat_listener libseat_interface = { .enable_seat = on_session_enable, .disable_seat = on_session_disable }; -#ifdef BUILD_TEXT_INPUT_PLUGIN - ok = textin_on_utf8_char(character); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. textin_on_utf8_char: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); + libseat = libseat_open_seat(&libseat_interface, fpi); + if (libseat == NULL) { + LOG_DEBUG("Couldn't open libseat. Flutter-pi will run without session switching support. libseat_open_seat: %s\n", strerror(errno)); } -#endif -} -static void on_xkb_keysym(void *userdata, xkb_keysym_t keysym) { - struct flutterpi *flutterpi; - int ok; + if (libseat != NULL) { + ok = libseat_get_fd(libseat); + if (ok < 0) { + LOG_ERROR( + "Couldn't get an event fd from libseat. Flutter-pi will run without session switching support. libseat_get_fd: %s\n", + strerror(errno) + ); + libseat_close_seat(libseat); + libseat = NULL; + } + } - flutterpi = userdata; - (void) flutterpi; + if (libseat != NULL) { + ok = sd_event_add_io(event_loop, NULL, ok, EPOLLIN, on_libseat_fd_ready, fpi); + if (ok < 0) { + LOG_ERROR( + "Couldn't listen for libseat events. Flutter-pi will run without session switching support. sd_event_add_io: %s\n", + strerror(-ok) + ); + libseat_close_seat(libseat); + libseat = NULL; + } + } -#ifdef BUILD_TEXT_INPUT_PLUGIN - ok = textin_on_xkb_keysym(keysym); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. textin_on_xkb_keysym: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); + if (libseat != NULL) { + libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG); } +#else + libseat = NULL; #endif -} -static void on_gtk_keyevent( - void *userdata, - uint32_t unicode_scalar_values, - uint32_t key_code, - uint32_t scan_code, - uint32_t modifiers, - bool is_down -) { - struct flutterpi *flutterpi; - int ok; + locales = locales_new(); + if (locales == NULL) { + LOG_ERROR("Couldn't setup locales.\n"); + goto fail_destroy_libseat; + } - flutterpi = userdata; - (void) flutterpi; + locales_print(locales); -#ifdef BUILD_RAW_KEYBOARD_PLUGIN - ok = rawkb_send_gtk_keyevent( - unicode_scalar_values, - key_code, - scan_code, - modifiers, - is_down - ); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. rawkb_send_gtk_keyevent: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); + drmdev = find_drmdev(libseat); + if (drmdev == NULL) { + goto fail_destroy_locales; } -#endif -} - -static void on_set_cursor_enabled(void *userdata, bool enabled) { - struct flutterpi *flutterpi; - int ok; - flutterpi = userdata; + gbm_device = drmdev_get_gbm_device(drmdev); + if (gbm_device == NULL) { + LOG_ERROR("Couldn't create GBM device.\n"); + goto fail_destroy_drmdev; + } - ok = compositor_apply_cursor_state( - enabled, - flutterpi->view.rotation, - flutterpi->display.pixel_ratio - ); - if (ok != 0) { - LOG_ERROR("Error enabling / disabling mouse cursor. compositor_apply_cursor_state: %s\n", strerror(ok)); + tracer = tracer_new_with_stubs(); + if (tracer == NULL) { + LOG_ERROR("Couldn't create event tracer.\n"); + goto fail_destroy_drmdev; } -} -static void on_move_cursor(void *userdata, unsigned int x, unsigned int y) { - struct flutterpi *flutterpi; - int ok; + scheduler = frame_scheduler_new(false, kDoubleBufferedVsync_PresentMode, NULL, NULL); + if (scheduler == NULL) { + LOG_ERROR("Couldn't create frame scheduler.\n"); + goto fail_unref_tracer; + } - flutterpi = userdata; - (void) flutterpi; + if (renderer_type == kVulkan_RendererType) { +#ifdef HAVE_VULKAN + gl_renderer = NULL; + vk_renderer = vk_renderer_new(); + if (vk_renderer == NULL) { + LOG_ERROR("Couldn't create vulkan renderer.\n"); + ok = EIO; + goto fail_unref_scheduler; + } +#else + UNREACHABLE(); +#endif + } else if (renderer_type == kOpenGL_RendererType) { +#ifdef HAVE_EGL_GLES2 + vk_renderer = NULL; + gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device, cmd_args.has_pixel_format, cmd_args.pixel_format); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); + ok = EIO; + goto fail_unref_scheduler; + } - ok = compositor_set_cursor_pos(x, y); - if (ok != 0) { - LOG_ERROR("Error moving mouse cursor. compositor_set_cursor_pos: %s\n", strerror(ok)); + // it seems that after some Raspbian update, regular users are sometimes no longer allowed + // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ + // as read-write. flutter-pi must be run as root then. + // sometimes it works fine without root, sometimes it doesn't. + if (gl_renderer_is_llvmpipe(gl_renderer)) { + LOG_ERROR_UNPREFIXED( + "WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" + " Check that flutter-pi has permission to use the 3D graphics hardware,\n" + " or try running it as root.\n" + " This warning will probably result in a \"failed to set mode\" error\n" + " later on in the initialization.\n" + ); + ok = EINVAL; + goto fail_unref_scheduler; + } +#else + UNREACHABLE(); +#endif + } else { + UNREACHABLE(); + goto fail_unref_scheduler; + } + + window = kms_window_new( + // clang-format off + tracer, + scheduler, + renderer_type, + gl_renderer, + vk_renderer, + cmd_args.has_rotation, + cmd_args.rotation == 0 ? PLANE_TRANSFORM_ROTATE_0 : + cmd_args.rotation == 90 ? PLANE_TRANSFORM_ROTATE_90 : + cmd_args.rotation == 180 ? PLANE_TRANSFORM_ROTATE_180 : + cmd_args.rotation == 270 ? PLANE_TRANSFORM_ROTATE_270 : + (assert(0 && "invalid rotation"), PLANE_TRANSFORM_ROTATE_0), + cmd_args.has_orientation, cmd_args.orientation, + cmd_args.has_physical_dimensions, cmd_args.width_mm, cmd_args.height_mm, + cmd_args.has_pixel_format, cmd_args.pixel_format, + drmdev, + desired_videomode + // clang-format on + ); + if (window == NULL) { + LOG_ERROR("Couldn't create KMS window.\n"); + goto fail_unref_renderer; } -} - -static const struct user_input_interface user_input_interface = { - .on_flutter_pointer_event = on_flutter_pointer_event, - .on_utf8_character = on_utf8_character, - .on_xkb_keysym = on_xkb_keysym, - .on_gtk_keyevent = on_gtk_keyevent, - .on_set_cursor_enabled = on_set_cursor_enabled, - .on_move_cursor = on_move_cursor -}; -static int on_user_input_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - struct user_input *input; - - (void) s; - (void) fd; - (void) revents; + compositor = compositor_new(tracer, window); + if (compositor == NULL) { + LOG_ERROR("Couldn't create compositor.\n"); + goto fail_unref_window; + } - input = userdata; + /// TODO: Do we really need the window after this? + ok = sd_event_add_io(event_loop, NULL, drmdev_get_event_fd(drmdev), EPOLLIN | EPOLLHUP | EPOLLPRI, on_drmdev_ready, drmdev); + if (ok < 0) { + LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); + goto fail_unref_compositor; + } + + compositor_get_view_geometry(compositor, &geometry); + + static const struct user_input_interface user_input_interface = { + .on_flutter_pointer_event = on_flutter_pointer_event, + .on_utf8_character = on_utf8_character, + .on_xkb_keysym = on_xkb_keysym, + .on_gtk_keyevent = on_gtk_keyevent, + .on_set_cursor_enabled = on_set_cursor_enabled, + .on_move_cursor = on_move_cursor, + .open = on_user_input_open, + .close = on_user_input_close, + .on_switch_vt = on_switch_vt, + .on_key_event = NULL, + }; - return user_input_on_fd_ready(input); -} + fpi->libseat = libseat; + list_inithead(&fpi->fd_for_device_id); -static int init_user_input(void) { - struct user_input *input; - sd_event_source *event_source; - int ok; - - event_source = NULL; - input = user_input_new( &user_input_interface, - &flutterpi, - &flutterpi.view.display_to_view_transform, - &flutterpi.view.view_to_display_transform, - flutterpi.display.width, - flutterpi.display.height + fpi, + &geometry.display_to_view_transform, + &geometry.view_to_display_transform, + geometry.display_size.x, + geometry.display_size.y ); if (input == NULL) { LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); } else { + sd_event_source *user_input_event_source; + ok = sd_event_add_io( - flutterpi.event_loop, - &event_source, + event_loop, + &user_input_event_source, user_input_get_fd(input), EPOLLIN | EPOLLRDHUP | EPOLLPRI, on_user_input_fd_ready, @@ -2291,245 +2531,246 @@ static int init_user_input(void) { user_input_destroy(input); input = NULL; } - } - - flutterpi.user_input = input; - flutterpi.user_input_event_source = event_source; - - return 0; -} - -static bool path_exists(const char *path) { - return access(path, R_OK) == 0; -} - -static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { -#if defined(FILESYSTEM_LAYOUT_DEFAULT) - return fs_layout_flutterpi_resolve(app_bundle_path, runtime_mode); -#elif defined(FILESYSTEM_LAYOUT_METAFLUTTER) - return fs_layout_metaflutter_resolve(app_bundle_path, runtime_mode); -#else - #error "Exactly one of FILESYSTEM_LAYOUT_DEFAULT or FILESYSTEM_LAYOUT_METAFLUTTER must be defined." - return NULL; -#endif -} -static bool parse_cmd_args(int argc, char **argv) { - bool finished_parsing_options; - int runtime_mode_int = kDebug; - int longopt_index = 0; - int opt, ok; + sd_event_source_set_priority(user_input_event_source, SD_EVENT_PRIORITY_IDLE - 10); - struct option long_options[] = { - {"release", no_argument, &runtime_mode_int, kRelease}, - {"profile", no_argument, &runtime_mode_int, kProfile}, - {"input", required_argument, NULL, 'i'}, - {"orientation", required_argument, NULL, 'o'}, - {"rotation", required_argument, NULL, 'r'}, - {"dimensions", required_argument, NULL, 'd'}, - {"help", no_argument, 0, 'h'}, - {"pixelformat", required_argument, NULL, 'p'}, - {0, 0, 0, 0} - }; + sd_event_source_set_floating(user_input_event_source, true); + sd_event_source_unref(user_input_event_source); + } - finished_parsing_options = false; - while (!finished_parsing_options) { - longopt_index = 0; - opt = getopt_long(argc, argv, "+i:o:r:d:h", long_options, &longopt_index); + engine_handle = load_flutter_engine_lib(paths); + if (engine_handle == NULL) { + goto fail_destroy_user_input; + } - switch (opt) { - case 0: - // flag was encountered. just continue - break; + ok = get_flutter_engine_procs(engine_handle, &fpi->flutter.procs); + if (ok != 0) { + goto fail_unload_engine; + } - case 'o': - if (STREQ(optarg, "portrait_up")) { - flutterpi.view.orientation = kPortraitUp; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "landscape_left")) { - flutterpi.view.orientation = kLandscapeLeft; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "portrait_down")) { - flutterpi.view.orientation = kPortraitDown; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "landscape_right")) { - flutterpi.view.orientation = kLandscapeRight; - flutterpi.view.has_orientation = true; - } else { - LOG_ERROR( - "ERROR: Invalid argument for --orientation passed.\n" - "Valid values are \"portrait_up\", \"landscape_left\", \"portrait_down\", \"landscape_right\".\n" - "%s", - usage - ); - return false; - } - break; - - case 'r': - errno = 0; - long rotation = strtol(optarg, NULL, 0); - if ((errno != 0) || ((rotation != 0) && (rotation != 90) && (rotation != 180) && (rotation != 270))) { - LOG_ERROR( - "ERROR: Invalid argument for --rotation passed.\n" - "Valid values are 0, 90, 180, 270.\n" - "%s", - usage - ); - return false; - } + tracer_set_cbs( + tracer, + fpi->flutter.procs.TraceEventDurationBegin, + fpi->flutter.procs.TraceEventDurationEnd, + fpi->flutter.procs.TraceEventInstant + ); - flutterpi.view.rotation = rotation; - flutterpi.view.has_rotation = true; - break; - - case 'd': ; - unsigned int width_mm, height_mm; + plugin_registry = plugin_registry_new(fpi); + if (plugin_registry == NULL) { + LOG_ERROR("Could not create plugin registry.\n"); + goto fail_unload_engine; + } - ok = sscanf(optarg, "%u,%u", &width_mm, &height_mm); - if (ok != 2) { - LOG_ERROR("ERROR: Invalid argument for --dimensions passed.\n%s", usage); - return false; - } + ok = plugin_registry_add_plugins_from_static_registry(plugin_registry); + if (ok != 0) { + LOG_ERROR("Could not register plugins to plugin registry.\n"); + goto fail_destroy_plugin_registry; + } - flutterpi.display.width_mm = width_mm; - flutterpi.display.height_mm = height_mm; - - break; - - case 'p': - for (int i = 0; i < n_pixfmt_infos; i++) { - if (strcmp(optarg, pixfmt_infos[i].arg_name) == 0) { - flutterpi.gbm.format = pixfmt_infos[i].gbm_format; - goto valid_format; - } - } + const struct texture_registry_interface texture_registry_interface = { + .register_texture = on_register_texture, + .unregister_texture = on_unregister_texture, + .mark_frame_available = on_mark_texture_frame_available, + }; - LOG_ERROR( - "ERROR: Invalid argument for --pixelformat passed.\n" - "Valid values are: RGB565, ARGB8888, XRGB8888, BGRA8888, RGBA8888\n" - "%s", - usage - ); + texture_registry = texture_registry_new(&texture_registry_interface, fpi); + if (texture_registry == NULL) { + LOG_ERROR("Could not create texture registry.\n"); + goto fail_destroy_plugin_registry; + } - // Just so we get a compile error when we update the pixel format list - // but don't update the valid values above. - COMPILE_ASSERT(kCount_PixFmt == 5); + bool engine_is_aot = fpi->flutter.procs.RunsAOTCompiledDartCode(); + if (engine_is_aot == true && !FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode)) { + LOG_ERROR( + "The flutter engine was built for release or profile (AOT) mode, but flutter-pi was not started up in release or profile " + "mode.\n" + "Either you swap out the libflutter_engine.so with one that was built for debug mode, or you start" + "flutter-pi with the --release or --profile flag and make sure a valid \"app.so\" is located inside the asset bundle " + "directory.\n" + ); + goto fail_destroy_texture_registry; + } else if (engine_is_aot == false && FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode)) { + LOG_ERROR( + "The flutter engine was built for debug mode, but flutter-pi was started up in release mode.\n" + "Either you swap out the libflutter_engine.so with one that was built for release mode," + "or you start flutter-pi without the --release flag.\n" + ); + goto fail_destroy_texture_registry; + } - valid_format: - break; - - case 'h': - printf("%s", usage); - return false; + aot_data = NULL; + if (FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode)) { + FlutterEngineAOTDataSource aot_source = { .elf_path = paths->app_elf_path, .type = kFlutterEngineAOTDataSourceTypeElfPath }; - case '?': - case ':': - LOG_ERROR("Invalid option specified.\n%s", usage); - return false; - - case -1: - finished_parsing_options = true; - break; - - default: - break; + engine_result = fpi->flutter.procs.CreateAOTData(&aot_source, &aot_data); + if (engine_result != kSuccess) { + LOG_ERROR("Could not load AOT data. FlutterEngineCreateAOTData: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + goto fail_destroy_texture_registry; } } - - if (optind >= argc) { - LOG_ERROR("ERROR: Expected asset bundle path after options.\n"); - printf("%s", usage); - return false; + // We don't need these anymore. + frame_scheduler_unref(scheduler); + window_unref(window); + + pthread_mutex_init(&fpi->event_loop_mutex, get_default_mutex_attrs()); + fpi->event_loop_thread = pthread_self(); + fpi->wakeup_event_loop_fd = wakeup_fd; + fpi->event_loop = event_loop; + fpi->locales = locales; + fpi->tracer = tracer; + fpi->compositor = compositor; + fpi->gl_renderer = gl_renderer; + fpi->vk_renderer = vk_renderer; + fpi->user_input = input; + fpi->flutter.runtime_mode = runtime_mode; + fpi->flutter.bundle_path = bundle_path; + fpi->flutter.engine_argc = engine_argc; + fpi->flutter.engine_argv = engine_argv; + fpi->flutter.paths = paths; + fpi->flutter.engine_handle = engine_handle; + fpi->flutter.aot_data = aot_data; + fpi->drmdev = drmdev; + fpi->plugin_registry = plugin_registry; + fpi->texture_registry = texture_registry; + fpi->libseat = libseat; + return fpi; + +fail_destroy_texture_registry: + texture_registry_destroy(texture_registry); + +fail_destroy_plugin_registry: + plugin_registry_destroy(plugin_registry); + +fail_unload_engine: + unload_flutter_engine_lib(engine_handle); + +fail_destroy_user_input: + user_input_destroy(input); + +fail_unref_compositor: + compositor_unref(compositor); + +fail_unref_window: + window_unref(window); + +fail_unref_renderer: + if (gl_renderer) { +#ifdef HAVE_EGL_GLES2 + gl_renderer_unref(gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (vk_renderer) { +#ifdef HAVE_VULKAN + vk_renderer_unref(vk_renderer); +#else + UNREACHABLE(); +#endif } - flutterpi.flutter.bundle_path = realpath(argv[optind], NULL); - flutterpi.flutter.runtime_mode = runtime_mode_int; +fail_unref_scheduler: + frame_scheduler_unref(scheduler); - argv[optind] = argv[0]; - flutterpi.flutter.engine_argc = argc - optind; - flutterpi.flutter.engine_argv = argv + optind; +fail_unref_tracer: + tracer_unref(tracer); - return true; -} +fail_destroy_drmdev: + drmdev_unref(drmdev); -int init(int argc, char **argv) { - struct flutter_paths *paths; - int ok; +fail_destroy_locales: + locales_destroy(locales); -#ifdef ENABLE_MTRACE - mtrace(); +fail_destroy_libseat: + if (libseat != NULL) { +#ifdef HAVE_LIBSEAT + libseat_close_seat(libseat); +#else + UNREACHABLE(); #endif - - ok = parse_cmd_args(argc, argv); - if (ok == false) { - return EINVAL; - } - - paths = setup_paths(flutterpi.flutter.runtime_mode, flutterpi.flutter.bundle_path); - if (paths == NULL) { - return EINVAL; - } - - flutterpi.flutter.paths = paths; - - ok = init_main_loop(); - if (ok != 0) { - return ok; } - flutterpi.locales = locales_new(); - if (flutterpi.locales == NULL) { - LOG_ERROR("Couldn't setup locales.\n"); - return EINVAL; - } +fail_unref_event_loop: + sd_event_unrefp(&event_loop); - ok = init_user_input(); - if (ok != 0) { - return ok; - } +fail_close_wakeup_fd: + close(wakeup_fd); - ok = init_display(); - if (ok != 0) { - return ok; - } +fail_free_paths: + flutter_paths_free(paths); - ok = init_application(); - if (ok != 0) { - return ok; - } +fail_free_cmd_args: + free(cmd_args.bundle_path); - return 0; -} +fail_free_fpi: + free(fpi); -int run() { - return run_main_loop(); + return NULL; } -void deinit() { +void flutterpi_destroy(struct flutterpi *flutterpi) { + (void) flutterpi; + LOG_DEBUG("deinit\n"); + + pthread_mutex_destroy(&flutterpi->event_loop_mutex); + texture_registry_destroy(flutterpi->texture_registry); + plugin_registry_destroy(flutterpi->plugin_registry); + unload_flutter_engine_lib(flutterpi->flutter.engine_handle); + user_input_destroy(flutterpi->user_input); + compositor_unref(flutterpi->compositor); + if (flutterpi->gl_renderer) { +#ifdef HAVE_EGL_GLES2 + gl_renderer_unref(flutterpi->gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (flutterpi->vk_renderer) { +#ifdef HAVE_VULKAN + vk_renderer_unref(flutterpi->vk_renderer); +#else + UNREACHABLE(); +#endif + } + tracer_unref(flutterpi->tracer); + drmdev_unref(flutterpi->drmdev); + locales_destroy(flutterpi->locales); + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + libseat_close_seat(flutterpi->libseat); +#else + UNREACHABLE(); +#endif + } + sd_event_unrefp(&flutterpi->event_loop); + close(flutterpi->wakeup_event_loop_fd); + flutter_paths_free(flutterpi->flutter.paths); + free(flutterpi->flutter.bundle_path); + free(flutterpi); return; } - -int main(int argc, char **argv) { +int flutterpi_app_main(int argc, char **argv) { + struct flutterpi *flutterpi; int ok; #ifdef ENABLE_MTRACE mtrace(); #endif - ok = init(argc, argv); - if (ok != 0) { + flutterpi = flutterpi_new_from_args(argc, argv); + if (flutterpi == NULL) { return EXIT_FAILURE; } - ok = run(); + ok = flutterpi_run(flutterpi); if (ok != 0) { + flutterpi_destroy(flutterpi); return EXIT_FAILURE; } - deinit(); + flutterpi_destroy(flutterpi); return EXIT_SUCCESS; } diff --git a/src/flutter-pi.h b/src/flutter-pi.h new file mode 100644 index 00000000..e1203a63 --- /dev/null +++ b/src/flutter-pi.h @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +/* + * Flutter-Pi main header + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_FLUTTERPI_H +#define _FLUTTERPI_SRC_FLUTTERPI_H + +#define LOG_FLUTTERPI_ERROR(...) fprintf(stderr, "[flutter-pi] " __VA_ARGS__) + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "cursor.h" +#include "util/collection.h" + +enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight }; + +#define ORIENTATION_IS_LANDSCAPE(orientation) ((orientation) == kLandscapeLeft || (orientation) == kLandscapeRight) +#define ORIENTATION_IS_PORTRAIT(orientation) ((orientation) == kPortraitUp || (orientation) == kPortraitDown) +#define ORIENTATION_IS_VALID(orientation) \ + ((orientation) == kPortraitUp || (orientation) == kLandscapeLeft || (orientation) == kPortraitDown || (orientation) == kLandscapeRight) + +#define ORIENTATION_ROTATE_CW(orientation) \ + ((orientation) == kPortraitUp ? kLandscapeLeft : \ + (orientation) == kLandscapeLeft ? kPortraitDown : \ + (orientation) == kPortraitDown ? kLandscapeRight : \ + (orientation) == kLandscapeRight ? kPortraitUp : \ + (assert(0 && "invalid device orientation"), 0)) + +#define ORIENTATION_ROTATE_CCW(orientation) \ + ((orientation) == kPortraitUp ? kLandscapeRight : \ + (orientation) == kLandscapeLeft ? kPortraitUp : \ + (orientation) == kPortraitDown ? kLandscapeLeft : \ + (orientation) == kLandscapeRight ? kPortraitDown : \ + (assert(0 && "invalid device orientation"), 0)) + +#define ANGLE_FROM_ORIENTATION(o) \ + ((o) == kPortraitUp ? 0 : (o) == kLandscapeLeft ? 90 : (o) == kPortraitDown ? 180 : (o) == kLandscapeRight ? 270 : 0) + +#define ANGLE_BETWEEN_ORIENTATIONS(o_start, o_end) \ + (ANGLE_FROM_ORIENTATION(o_end) - ANGLE_FROM_ORIENTATION(o_start) + \ + (ANGLE_FROM_ORIENTATION(o_start) > ANGLE_FROM_ORIENTATION(o_end) ? 360 : 0)) + +#define FLUTTER_RESULT_TO_STRING(result) \ + ((result) == kSuccess ? "Success." : \ + (result) == kInvalidLibraryVersion ? "Invalid library version." : \ + (result) == kInvalidArguments ? "Invalid arguments." : \ + (result) == kInternalInconsistency ? "Internal inconsistency." : \ + "(?)") + +/// TODO: Move this +#define LIBINPUT_EVENT_IS_TOUCH(event_type) \ + (((event_type) == LIBINPUT_EVENT_TOUCH_DOWN) || ((event_type) == LIBINPUT_EVENT_TOUCH_UP) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_MOTION) || ((event_type) == LIBINPUT_EVENT_TOUCH_CANCEL) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_FRAME)) + +#define LIBINPUT_EVENT_IS_POINTER(event_type) \ + (((event_type) == LIBINPUT_EVENT_POINTER_MOTION) || ((event_type) == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) || \ + ((event_type) == LIBINPUT_EVENT_POINTER_BUTTON) || ((event_type) == LIBINPUT_EVENT_POINTER_AXIS)) + +#define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) + +enum flutter_runtime_mode { FLUTTER_RUNTIME_MODE_DEBUG, FLUTTER_RUNTIME_MODE_PROFILE, FLUTTER_RUNTIME_MODE_RELEASE }; + +#define FLUTTER_RUNTIME_MODE_IS_JIT(runtime_mode) ((runtime_mode) == FLUTTER_RUNTIME_MODE_DEBUG) +#define FLUTTER_RUNTIME_MODE_IS_AOT(runtime_mode) \ + ((runtime_mode) == FLUTTER_RUNTIME_MODE_PROFILE || (runtime_mode) == FLUTTER_RUNTIME_MODE_RELEASE) + +struct compositor; +struct plugin_registry; +struct texture_registry; +struct drmdev; +struct locales; +struct vk_renderer; +struct flutterpi; + +/// TODO: Remove this +extern struct flutterpi *flutterpi; + +struct platform_task { + int (*callback)(void *userdata); + void *userdata; +}; + +struct platform_message { + bool is_response; + union { + const FlutterPlatformMessageResponseHandle *target_handle; + struct { + char *target_channel; + FlutterPlatformMessageResponseHandle *response_handle; + }; + }; + uint8_t *message; + size_t message_size; +}; + +int flutterpi_fill_view_properties(bool has_orientation, enum device_orientation orientation, bool has_rotation, int rotation); + +int flutterpi_post_platform_task(int (*callback)(void *userdata), void *userdata); + +int flutterpi_post_platform_task_with_time(int (*callback)(void *userdata), void *userdata, uint64_t target_time_usec); + +int flutterpi_sd_event_add_io(sd_event_source **source_out, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); + +int flutterpi_send_platform_message( + struct flutterpi *flutterpi, + const char *channel, + const uint8_t *restrict message, + size_t message_size, + FlutterPlatformMessageResponseHandle *responsehandle +); + +int flutterpi_respond_to_platform_message( + const FlutterPlatformMessageResponseHandle *handle, + const uint8_t *restrict message, + size_t message_size +); + +struct texture_registry *flutterpi_get_texture_registry(struct flutterpi *flutterpi); + +struct plugin_registry *flutterpi_get_plugin_registry(struct flutterpi *flutterpi); + +FlutterPlatformMessageResponseHandle * +flutterpi_create_platform_message_response_handle(struct flutterpi *flutterpi, FlutterDataCallback data_callback, void *userdata); + +void flutterpi_release_platform_message_response_handle(struct flutterpi *flutterpi, FlutterPlatformMessageResponseHandle *handle); + +struct texture *flutterpi_create_texture(struct flutterpi *flutterpi); + +const char *flutterpi_get_asset_bundle_path(struct flutterpi *flutterpi); + +void flutterpi_schedule_exit(struct flutterpi *flutterpi); + +struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi); + +bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi); + +struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi); + +void flutterpi_set_pointer_kind(struct flutterpi *flutterpi, enum pointer_kind kind); + +void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name); + +void flutterpi_trace_event_begin(struct flutterpi *flutterpi, const char *name); + +void flutterpi_trace_event_end(struct flutterpi *flutterpi, const char *name); + +#endif // _FLUTTERPI_SRC_FLUTTERPI_H diff --git a/src/frame_scheduler.c b/src/frame_scheduler.c new file mode 100644 index 00000000..0adf7b9a --- /dev/null +++ b/src/frame_scheduler.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +/* + * Frame scheduler + * + * Manages scheduling of frames, rendering, flutter vsync requests/replies. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "frame_scheduler.h" + +#include + +#include "compositor_ng.h" +#include "util/collection.h" +#include "util/lock_ops.h" +#include "util/refcounting.h" + +struct frame_scheduler { + refcount_t n_refs; + + bool uses_frame_requests; + enum present_mode present_mode; + fl_vsync_callback_t vsync_cb; + void *userdata; + + pthread_mutex_t mutex; +}; + +DEFINE_REF_OPS(frame_scheduler, n_refs) +DEFINE_STATIC_LOCK_OPS(frame_scheduler, mutex) + +struct frame_scheduler * +frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl_vsync_callback_t vsync_cb, void *userdata) { + struct frame_scheduler *scheduler; + + // uses_frame_requests? => vsync_cb != NULL + assert(!uses_frame_requests || vsync_cb != NULL); + + scheduler = malloc(sizeof *scheduler); + if (scheduler == NULL) { + return NULL; + } + + scheduler->n_refs = REFCOUNT_INIT_1; + scheduler->uses_frame_requests = uses_frame_requests; + scheduler->present_mode = present_mode; + scheduler->vsync_cb = vsync_cb; + scheduler->userdata = userdata; + return scheduler; +} + +void frame_scheduler_destroy(struct frame_scheduler *scheduler) { + free(scheduler); +} + +void frame_scheduler_on_fl_vsync_request(struct frame_scheduler *scheduler, intptr_t vsync_baton) { + ASSERT_NOT_NULL(scheduler); + assert(vsync_baton != 0); + assert(scheduler->uses_frame_requests); + + // flutter called the vsync callback. + // - when do we reply to it? + // - what timestamps do we send as a reply? + // + // Some things to keep in mind: + // - GPU rendering is a big pipeline: + // uploading -> vertex shading -> tesselation -> geometry shading -> rasterization -> fragment shading + // - Some parts of the pipeline might execute on different parts of the GPU, maybe there's specific + // fixed-function hardware for different steps of the pipeline. For example, on PowerVR, there's a tiler (vertex) + // stage and a separate renderer (fragment) stage. + // - So it might not be smart to render just one frame at a time. + // - On PowerVR, it's best to have a 3-frame pipeline: + // + // Frame 0 Frame 1 Frame 2 + // Geometry Submission | In Progress | . | . | + // Vertex Processing | | In Progress | . | + // Fragment Processing | | | In Progress | + // + // - That way, occupancy & throughput is optimal, and rendering can be at 60FPS even though maybe fragment processing takes > 16ms. + // - On the other hand, normally a mesa EGL surface only has 4 buffers available, so we could run out of framebuffers for surfaces + // as well if we draw too many frames at once. (Especially considering one framebuffer is probably busy with scanout right now) + // + + /// TODO: Implement + /// For now, just unconditionally reply + if (scheduler->present_mode == kTripleBufferedVsync_PresentMode) { + scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); + } else if (scheduler->present_mode == kDoubleBufferedVsync_PresentMode) { + scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); + } +} + +void frame_scheduler_on_rendering_complete(struct frame_scheduler *scheduler) { + ASSERT_NOT_NULL(scheduler); + (void) scheduler; + + /// TODO: Implement + UNIMPLEMENTED(); +} + +void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns) { + ASSERT_NOT_NULL(scheduler); + (void) scheduler; + (void) has_timestamp; + (void) timestamp_ns; + + /// TODO: Implement + UNIMPLEMENTED(); +} + +void frame_scheduler_request_fb(struct frame_scheduler *scheduler, uint64_t scanout_time_ns) { + ASSERT_NOT_NULL(scheduler); + (void) scheduler; + (void) scanout_time_ns; + + /// TODO: Implement + UNIMPLEMENTED(); +} + +void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb) { + ASSERT_NOT_NULL(scheduler); + ASSERT_NOT_NULL(present_cb); + (void) scheduler; + (void) cancel_cb; + + /// TODO: Implement + present_cb(userdata); +} + +void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns) { + ASSERT_NOT_NULL(scheduler); + assert(!has_timestamp || timestamp_ns != 0); + (void) scheduler; + (void) has_timestamp; + (void) timestamp_ns; + + /// TODO: Implement + UNIMPLEMENTED(); +} diff --git a/src/frame_scheduler.h b/src/frame_scheduler.h new file mode 100644 index 00000000..229d9e59 --- /dev/null +++ b/src/frame_scheduler.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +/* + * Vsync Waiter + * + * Manages scheduling of frames, rendering, flutter vsync requests/replies. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_FRAME_SCHEDULER_H +#define _FLUTTERPI_SRC_FRAME_SCHEDULER_H + +#include "util/collection.h" +#include "util/refcounting.h" + +struct frame_scheduler; + +// clang-format off +typedef void (*fl_vsync_callback_t)( + void *userdata, + intptr_t vsync_baton, + uint64_t frame_start_time_nanos, + uint64_t next_frame_start_time_nanos +); +// clang-format on + +enum present_mode { kDoubleBufferedVsync_PresentMode, kTripleBufferedVsync_PresentMode }; + +/** + * @brief Creates a new frame scheduler. + * + * A frame scheduler manages the task of scheduling rendering, frames, and handling & responding to + * flutter vsync requests, depending on the chosen present mode. + * + * The vsync callback is the function that will be called when a flutter vsync request should be responded to. + * (In practice, that callback should rethread to the platform task thread and then call FlutterEngineOnVsync with the argument + * vsync baton.) + * + * @param uses_frame_requests Whether @ref frame_scheduler_on_fl_vsync_request will be called at all. For example, this might be false + * when there was no `vsync_callback` specified in the FlutterProjectArgs. + * @param present_mode Which present mode to use. (Always wait for the next vsync before starting a frame? Always start + * the next frame when rendering is complete) + * @param vsync_cb The function that will be called when a flutter vsync request should be responded to. + * (In practice, that callback should rethread to the platform task thread and then call + * @ref FlutterEngineOnVsync with the argument vsync baton.) + * @param userdata userdata that will be passed to vsync_cb. + * @return struct frame_scheduler* The new frame scheduler. + */ +struct frame_scheduler * +frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl_vsync_callback_t vsync_cb, void *userdata); + +void frame_scheduler_destroy(struct frame_scheduler *scheduler); + +DECLARE_REF_OPS(frame_scheduler) + +/** + * @brief Called when flutter calls the embedder supplied vsync_callback. + * Embedder should reply on the platform task thread with the timestamp + * of the next vsync request. Engine will wait till that time and then begin + * rendering the next frame. + * + * @param scheduler The frame scheduler instance. + * @param vsync_baton The vsync baton that the flutter engine specified. + */ +void frame_scheduler_on_fl_vsync_request(struct frame_scheduler *scheduler, intptr_t vsync_baton); + +void frame_scheduler_on_rendering_complete(struct frame_scheduler *scheduler); + +void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns); + +/** + * @brief Will call present_cb when the next frame is ready to be presented. + * + * 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. + * + * @param scheduler The frame scheduler instance. + * @param present_cb Called when the frame should be presented. + * @param userdata Userdata that's passed to the present and cancel callback. + * @param cancel_cb Called when the frame is not going to be presented, and all associated resources should be freed. + */ +void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb); + +#endif // _FLUTTERPI_SRC_FRAME_SCHEDULER_H diff --git a/src/gl_renderer.c b/src/gl_renderer.c new file mode 100644 index 00000000..41cf0d3d --- /dev/null +++ b/src/gl_renderer.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: MIT +/* + * Utilities for rendering using OpenGL + * + * Copyright (c) 2022, Hannes Winkler + */ + +#define _GNU_SOURCE +#include "gl_renderer.h" + +#include +#include + +#include +#include + +#include "egl.h" +#include "gles.h" +#include "pixel_format.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +struct gl_renderer { + refcount_t n_refs; + + struct tracer *tracer; + struct gbm_device *gbm_device; + EGLDisplay egl_display; + + EGLContext root_context, flutter_rendering_context, flutter_resource_uploading_context, flutter_setup_context; + pthread_mutex_t root_context_lock; + + bool has_forced_pixel_format; + enum pixfmt pixel_format; + + /** + * @brief If EGL doesn't support EGL_KHR_no_config_context, we need to specify an EGLConfig (basically the framebuffer format) + * for the context. And since all shared contexts need to have the same EGL Config, we basically have to choose a single global config + * for the display. + * + * If this field is not EGL_NO_CONFIG_KHR, all contexts we create need to have exactly this EGL Config. + */ + EGLConfig forced_egl_config; + + EGLint major, minor; + const char *egl_client_exts; + const char *egl_display_exts; + const char *gl_renderer; + const char *gl_exts; + + bool supports_egl_ext_platform_base; +#ifdef EGL_EXT_platform_base + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext; +#endif + +#ifdef EGL_VERSION_1_5 + PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display; + PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface; +#endif +}; + +static void *try_get_proc_address(const char *name) { + void *address; + + address = eglGetProcAddress(name); + if (address) { + return address; + } + + address = dlsym(RTLD_DEFAULT, name); + if (address) { + return address; + } + + return NULL; +} + +static void *get_proc_address(const char *name) { + void *address; + + address = try_get_proc_address(name); + if (address == NULL) { + LOG_ERROR("Could not resolve EGL/GL symbol \"%s\"\n", name); + return NULL; + } + + return address; +} + +static ATTR_PURE EGLConfig choose_config_with_pixel_format(EGLDisplay display, const EGLint *attrib_list, enum pixfmt pixel_format) { + EGLConfig *matching; + EGLBoolean egl_ok; + EGLint value, n_matched; + + assert(display != EGL_NO_DISPLAY); + + LOG_DEBUG("Choosing EGL config with pixel format %s...\n", get_pixfmt_info(pixel_format)->name); + + n_matched = 0; + egl_ok = eglChooseConfig(display, attrib_list, NULL, 0, &n_matched); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query number of EGL framebuffer configurations with fitting attributes. eglChooseConfig"); + return EGL_NO_CONFIG_KHR; + } + + matching = alloca(n_matched * sizeof *matching); + + egl_ok = eglChooseConfig(display, attrib_list, matching, n_matched, &n_matched); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig"); + return EGL_NO_CONFIG_KHR; + } + + for (int i = 0; i < n_matched; i++) { + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_NATIVE_VISUAL_ID, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (int32_to_uint32(value) == get_pixfmt_info(pixel_format)->gbm_format) { + // found a config with matching pixel format. + return matching[i]; + } + } + + return EGL_NO_CONFIG_KHR; +} + +struct gl_renderer *gl_renderer_new_from_gbm_device( + struct tracer *tracer, + struct gbm_device *gbm_device, + bool has_forced_pixel_format, + enum pixfmt pixel_format +) { + struct gl_renderer *renderer; + const char *egl_client_exts, *egl_display_exts; + const char *gl_renderer, *gl_exts; + EGLContext root_context, flutter_render_context, flutter_resource_uploading_context, flutter_setup_context; + EGLDisplay egl_display; + EGLBoolean egl_ok; + EGLConfig forced_egl_config; + EGLint major, minor; + bool supports_egl_ext_platform_base; + int ok; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) { + goto fail_return_null; + } + + egl_client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (egl_client_exts == NULL) { + LOG_EGL_ERROR(eglGetError(), "Couldn't query EGL client extensions. eglQueryString"); + goto fail_free_renderer; + } + + if (check_egl_extension(egl_client_exts, NULL, "EGL_EXT_platform_base")) { +#ifdef EGL_EXT_platform_base + supports_egl_ext_platform_base = true; +#else + LOG_ERROR( + "EGL supports EGL_EXT_platform_base, but EGL headers didn't contain definitions for EGL_EXT_platform_base." + "eglGetPlatformDisplayEXT and eglCreatePlatformWindowSurfaceEXT will not be used to create an EGL display.\n" + ); + supports_egl_ext_platform_base = false; +#endif + } else { + supports_egl_ext_platform_base = false; + } + +// PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC +// are defined by EGL_EXT_platform_base. +#ifdef EGL_EXT_platform_base + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext; +#endif + + if (supports_egl_ext_platform_base) { +#ifdef EGL_EXT_platform_base + egl_get_platform_display_ext = try_get_proc_address("eglGetPlatformDisplayEXT"); + if (egl_get_platform_display_ext == NULL) { + LOG_ERROR("Couldn't resolve \"eglGetPlatformDisplayEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n"); + supports_egl_ext_platform_base = false; + } +#else + UNREACHABLE(); +#endif + } + + if (supports_egl_ext_platform_base) { +#ifdef EGL_EXT_platform_base + egl_create_platform_window_surface_ext = try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); + if (egl_create_platform_window_surface_ext == NULL) { + LOG_ERROR( + "Couldn't resolve \"eglCreatePlatformWindowSurfaceEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n" + ); + egl_get_platform_display_ext = NULL; + supports_egl_ext_platform_base = false; + } +#else + UNREACHABLE(); +#endif + } + +// EGL_PLATFORM_GBM_KHR is defined by EGL_KHR_platform_gbm. +#ifndef EGL_KHR_platform_gbm + #error "EGL extension EGL_KHR_platform_gbm is required." +#endif + + egl_display = EGL_NO_DISPLAY; + bool failed_before = false; + +#ifdef EGL_VERSION_1_5 + PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = try_get_proc_address("eglGetPlatformDisplay"); + PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface = try_get_proc_address("eglCreatePlatformWindowSurface"); + + if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display != NULL) { + egl_display = egl_get_platform_display(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get EGL display from GBM device. eglGetPlatformDisplay"); + failed_before = true; + } + } +#endif + +#ifdef EGL_EXT_platform_base + if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display_ext != NULL) { + if (failed_before) { + LOG_DEBUG("Attempting eglGetPlatformDisplayEXT...\n"); + } + + egl_display = egl_get_platform_display_ext(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get EGL display from GBM device. eglGetPlatformDisplayEXT"); + failed_before = true; + } + } +#endif + + if (egl_display == EGL_NO_DISPLAY) { + if (failed_before) { + LOG_DEBUG("Attempting eglGetDisplay...\n"); + } + + egl_display = eglGetDisplay((void *) gbm_device); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get EGL display from GBM device. eglGetDisplay"); + } + } + + if (egl_display == EGL_NO_DISPLAY) { + LOG_ERROR("Could not get EGL Display from any function.\n"); + goto fail_free_renderer; + } + + egl_ok = eglInitialize(egl_display, &major, &minor); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Failed to initialize EGL! eglInitialize:"); + goto fail_free_renderer; + } + + egl_display_exts = eglQueryString(egl_display, EGL_EXTENSIONS); + if (egl_display_exts == NULL) { + LOG_EGL_ERROR(eglGetError(), "Couldn't query EGL display extensions. eglQueryString"); + goto fail_terminate_display; + } + + if (!check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_surfaceless_context")) { + LOG_ERROR("EGL doesn't support the EGL_KHR_surfaceless_context extension, which is required by flutter-pi.\n"); + goto fail_terminate_display; + } + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't bind OpenGL ES API to EGL. eglBindAPI"); + goto fail_terminate_display; + } + + if (check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_no_config_context")) { + // EGL supports creating contexts without an EGLConfig, which is nice. + // Just create a context without selecting a config and let the backing stores (when they're created) select + // the framebuffer config instead. + forced_egl_config = EGL_NO_CONFIG_KHR; + } else { + // choose a config + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE, + }; + + if (has_forced_pixel_format == false) { + has_forced_pixel_format = true; + pixel_format = PIXFMT_ARGB8888; + } + + forced_egl_config = choose_config_with_pixel_format(egl_display, config_attribs, pixel_format); + if (forced_egl_config == EGL_NO_CONFIG_KHR) { + LOG_ERROR("No fitting EGL framebuffer configuration found.\n"); + goto fail_terminate_display; + } + } + + static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + + root_context = eglCreateContext(egl_display, forced_egl_config, EGL_NO_CONTEXT, context_attribs); + if (root_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL context for OpenGL ES. eglCreateContext"); + goto fail_terminate_display; + } + + flutter_render_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_render_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter rendering. eglCreateContext"); + goto fail_destroy_root_context; + } + + flutter_resource_uploading_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_resource_uploading_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter resource uploads. eglCreateContext"); + goto fail_destroy_flutter_render_context; + } + + flutter_setup_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + if (flutter_setup_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter initialization. eglCreateContext"); + goto fail_destroy_flutter_resource_uploading_context; + } + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, root_context); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not make EGL OpenGL ES root context current to query OpenGL information. eglMakeCurrent"); + goto fail_destroy_flutter_setup_context; + } + + gl_renderer = (const char *) glGetString(GL_RENDERER); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't query OpenGL ES renderer information.\n"); + goto fail_clear_current; + } + + gl_exts = (const char *) glGetString(GL_EXTENSIONS); + if (gl_exts == NULL) { + LOG_ERROR("Couldn't query supported OpenGL ES extensions.\n"); + goto fail_clear_current; + } + + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "EGL information:\n" + " version: %s\n" + " vendor: %s\n" + " client extensions: %s\n" + " display extensions: %s\n" + "===================================\n", + eglQueryString(egl_display, EGL_VERSION), + eglQueryString(egl_display, EGL_VENDOR), + egl_client_exts, + egl_display_exts + ); + + // this needs to be here because the EGL context needs to be current. + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "OpenGL ES information:\n" + " version: \"%s\"\n" + " shading language version: \"%s\"\n" + " vendor: \"%s\"\n" + " renderer: \"%s\"\n" + " extensions: \"%s\"\n" + "===================================\n", + glGetString(GL_VERSION), + glGetString(GL_SHADING_LANGUAGE_VERSION), + glGetString(GL_VENDOR), + gl_renderer, + gl_exts + ); + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not clear EGL OpenGL ES context. eglMakeCurrent"); + goto fail_destroy_flutter_resource_uploading_context; + } + + ok = pthread_mutex_init(&renderer->root_context_lock, NULL); + if (ok < 0) { + goto fail_destroy_flutter_resource_uploading_context; + } + + renderer->n_refs = REFCOUNT_INIT_1; + renderer->tracer = tracer_ref(tracer); + renderer->gbm_device = gbm_device; + renderer->egl_display = egl_display; + renderer->root_context = root_context; + renderer->flutter_rendering_context = flutter_render_context; + renderer->flutter_resource_uploading_context = flutter_resource_uploading_context; + renderer->flutter_setup_context = flutter_setup_context; + renderer->has_forced_pixel_format = has_forced_pixel_format; + renderer->pixel_format = pixel_format; + renderer->forced_egl_config = forced_egl_config; + renderer->major = major; + renderer->minor = minor; + renderer->egl_client_exts = egl_client_exts; + renderer->egl_display_exts = egl_display_exts; + renderer->gl_renderer = gl_renderer; + renderer->gl_exts = gl_exts; + renderer->supports_egl_ext_platform_base = supports_egl_ext_platform_base; +#ifdef EGL_EXT_platform_base + renderer->egl_get_platform_display_ext = egl_get_platform_display_ext; + renderer->egl_create_platform_window_surface_ext = egl_create_platform_window_surface_ext; +#endif +#ifdef EGL_VERSION_1_5 + renderer->egl_get_platform_display = egl_get_platform_display; + renderer->egl_create_platform_window_surface = egl_create_platform_window_surface; +#endif + return renderer; + +fail_clear_current: + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + +fail_destroy_flutter_setup_context: + eglDestroyContext(egl_display, flutter_setup_context); + +fail_destroy_flutter_resource_uploading_context: + eglDestroyContext(egl_display, flutter_resource_uploading_context); + +fail_destroy_flutter_render_context: + eglDestroyContext(egl_display, flutter_render_context); + +fail_destroy_root_context: + eglDestroyContext(egl_display, root_context); + +fail_terminate_display: + eglTerminate(egl_display); + +fail_free_renderer: + free(renderer); + +fail_return_null: + return NULL; +} + +void gl_renderer_destroy(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + tracer_unref(renderer->tracer); + pthread_mutex_destroy(&renderer->root_context_lock); + eglDestroyContext(renderer->egl_display, renderer->flutter_resource_uploading_context); + eglDestroyContext(renderer->egl_display, renderer->flutter_rendering_context); + eglDestroyContext(renderer->egl_display, renderer->root_context); + eglTerminate(renderer->egl_display); + free(renderer); +} + +DEFINE_REF_OPS(gl_renderer, n_refs) + +bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->has_forced_pixel_format; +} + +enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + assert(renderer->has_forced_pixel_format); + return renderer->pixel_format; +} + +bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->forced_egl_config != EGL_NO_CONFIG_KHR; +} + +EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->forced_egl_config; +} + +struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer) { + return renderer->gbm_device; +} + +int gl_renderer_make_flutter_setup_context_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_setup_context_current"); + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, renderer->flutter_setup_context); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_setup_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not make the flutter setup EGL context current. eglMakeCurrent"); + return EIO; + } + + return 0; +} + +int gl_renderer_make_flutter_rendering_context_current(struct gl_renderer *renderer, EGLSurface surface) { + EGLBoolean egl_ok; + + ASSERT_NOT_NULL(renderer); + /// NOTE: Allow this for now + /// assert(surface != EGL_NO_SURFACE); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + egl_ok = eglMakeCurrent(renderer->egl_display, surface, surface, renderer->flutter_rendering_context); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_rendering_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not make the flutter rendering EGL context current. eglMakeCurrent"); + return EIO; + } + + return 0; +} + +int gl_renderer_make_flutter_resource_uploading_context_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_make_flutter_resource_uploading_context_current"); + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, renderer->flutter_resource_uploading_context); + TRACER_END(renderer->tracer, "gl_renderer_make_flutter_resource_uploading_context_current"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not make the flutter resource uploading EGL context current. eglMakeCurrent"); + return EIO; + } + + return 0; +} + +int gl_renderer_clear_current(struct gl_renderer *renderer) { + EGLBoolean egl_ok; + + ASSERT_NOT_NULL(renderer); + + TRACER_BEGIN(renderer->tracer, "gl_renderer_clear_current"); + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + TRACER_END(renderer->tracer, "gl_renderer_clear_current"); + + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not clear the flutter EGL context. eglMakeCurrent"); + return EIO; + } + + return 0; +} + +void *gl_renderer_get_proc_address(ASSERTED struct gl_renderer *renderer, const char *name) { + ASSERT_NOT_NULL(renderer); + ASSERT_NOT_NULL(name); + return get_proc_address(name); +} + +EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->egl_display; +} + +EGLContext gl_renderer_create_context(struct gl_renderer *renderer) { + EGLContext context; + + ASSERT_NOT_NULL(renderer); + + pthread_mutex_lock(&renderer->root_context_lock); + context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, NULL); + pthread_mutex_unlock(&renderer->root_context_lock); + + return context; +} + +bool gl_renderer_supports_egl_extension(struct gl_renderer *renderer, const char *name) { + ASSERT_NOT_NULL(renderer); + ASSERT_NOT_NULL(name); + return check_egl_extension(renderer->egl_client_exts, renderer->egl_display_exts, name); +} + +bool gl_renderer_supports_gl_extension(struct gl_renderer *renderer, const char *name) { + ASSERT_NOT_NULL(renderer); + ASSERT_NOT_NULL(name); + return check_egl_extension(renderer->gl_exts, NULL, name); +} + +bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return strstr(renderer->gl_renderer, "llvmpipe") != NULL; +} + +#ifdef DEBUG +static __thread bool is_render_thread = false; +#endif + +int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer) { + EGLContext context; + EGLBoolean egl_ok; + + ASSERT_NOT_NULL(renderer); + assert(is_render_thread == false); + + pthread_mutex_lock(&renderer->root_context_lock); + context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, NULL); + pthread_mutex_unlock(&renderer->root_context_lock); + + if (context == EGL_NO_CONTEXT) { + LOG_ERROR("Couldn't create a new EGL context.\n"); + return EIO; + } + + egl_ok = eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, context); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't make EGL context current to make this an EGL thread.\n"); + return EIO; + } + +#ifdef DEBUG + is_render_thread = true; +#endif + + return 0; +} + +void gl_renderer_cleanup_this_render_thread() { + EGLDisplay display; + EGLContext context; + EGLBoolean egl_ok; + + assert(is_render_thread); + + context = eglGetCurrentContext(); + assert(context != EGL_NO_CONTEXT); + + display = eglGetCurrentDisplay(); + assert(display != EGL_NO_CONTEXT); + + egl_ok = eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't clear EGL context to cleanup this EGL thread.\n"); + return; + } + + egl_ok = eglDestroyContext(display, context); + if (egl_ok == EGL_FALSE) { + LOG_ERROR("Couldn't destroy EGL context to cleanup this EGL thread.\n"); + return; + } +} + +ATTR_PURE EGLConfig +gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format) { + ASSERT_NOT_NULL(renderer); + + if (renderer->forced_egl_config != EGL_NO_CONFIG_KHR) { + return renderer->forced_egl_config; + } + + const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE }; + + return choose_config_with_pixel_format( + renderer->egl_display, + config_attribs, + renderer->has_forced_pixel_format ? renderer->pixel_format : + has_desired_pixel_format ? desired_pixel_format : + PIXFMT_ARGB8888 + ); +} + +ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format) { + ASSERT_NOT_NULL(renderer); + ASSUME_PIXFMT_VALID(pixel_format); + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE, + }; + + return choose_config_with_pixel_format(renderer->egl_display, config_attribs, pixel_format); +} + +EGLSurface gl_renderer_create_gbm_window_surface( + struct gl_renderer *renderer, + EGLConfig config, + struct gbm_surface *gbm_surface, + const EGLAttribKHR *attrib_list, + const EGLint *int_attrib_list +) { + EGLSurface surface = EGL_NO_SURFACE; + bool failed_before = false; + +#ifdef EGL_VERSION_1_5 + if (renderer->egl_create_platform_window_surface != NULL) { + surface = renderer->egl_create_platform_window_surface(renderer->egl_display, config, gbm_surface, attrib_list); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't create gbm_surface backend window surface. eglCreatePlatformWindowSurface"); + failed_before = true; + } + } +#endif + + if (surface == EGL_NO_SURFACE && renderer->supports_egl_ext_platform_base) { +#ifdef EGL_EXT_platform_base + ASSUME(renderer->egl_create_platform_window_surface_ext); + + if (failed_before) { + LOG_DEBUG("Attempting eglCreatePlatformWindowSurfaceEXT...\n"); + } + + surface = renderer->egl_create_platform_window_surface_ext(renderer->egl_display, config, gbm_surface, int_attrib_list); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't create gbm_surface backend window surface. eglCreatePlatformWindowSurfaceEXT"); + failed_before = true; + } +#else + UNREACHABLE(); +#endif + } + + if (surface == EGL_NO_SURFACE) { + if (failed_before) { + LOG_DEBUG("Attempting eglCreateWindowSurface...\n"); + } + + surface = eglCreateWindowSurface(renderer->egl_display, config, (EGLNativeWindowType) gbm_surface, int_attrib_list); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't create gbm_surface backend window surface. eglCreateWindowSurface"); + } + } + + return surface; +} diff --git a/src/gl_renderer.h b/src/gl_renderer.h new file mode 100644 index 00000000..d6c8160a --- /dev/null +++ b/src/gl_renderer.h @@ -0,0 +1,88 @@ + +// SPDX-License-Identifier: MIT +/* + * EGL/OpenGL renderer + * + * Utilities for rendering with EGL/OpenGL. + * - creating/binding EGL contexts + * - using EGL extensions + * - setting up / cleaning up render threads + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_GL_RENDERER_H +#define _FLUTTERPI_SRC_GL_RENDERER_H + +#include "pixel_format.h" +#include "util/collection.h" +#include "util/refcounting.h" + +#include "config.h" + +#if !defined(HAVE_EGL_GLES2) + #error "gl_renderer requires EGL and OpenGL ES support." +#endif + +#include "egl.h" + +struct tracer; + +struct gl_renderer *gl_renderer_new_from_gbm_device( + struct tracer *tracer, + struct gbm_device *gbm_device, + bool has_forced_pixel_format, + enum pixfmt pixel_format +); + +void gl_renderer_destroy(struct gl_renderer *renderer); + +DECLARE_REF_OPS(gl_renderer) + +bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer); + +enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer); + +bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer); + +EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer); + +struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer); + +int gl_renderer_make_flutter_rendering_context_current(struct gl_renderer *renderer, EGLSurface surface); + +int gl_renderer_make_flutter_resource_uploading_context_current(struct gl_renderer *renderer); + +int gl_renderer_make_flutter_setup_context_current(struct gl_renderer *renderer); + +int gl_renderer_clear_current(struct gl_renderer *renderer); + +EGLContext gl_renderer_create_context(struct gl_renderer *renderer); + +void *gl_renderer_get_proc_address(struct gl_renderer *renderer, const char *name); + +EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer); + +bool gl_renderer_supports_egl_extension(struct gl_renderer *renderer, const char *name); + +bool gl_renderer_supports_gl_extension(struct gl_renderer *renderer, const char *name); + +bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer); + +int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer); + +void gl_renderer_cleanup_this_render_thread(); + +ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); + +ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format); + +EGLSurface gl_renderer_create_gbm_window_surface( + struct gl_renderer *renderer, + EGLConfig config, + struct gbm_surface *gbm_surface, + const EGLAttribKHR *attrib_list, + const EGLint *int_attrib_list +); + +#endif // _FLUTTERPI_SRC_GL_RENDERER_H diff --git a/src/gles.h b/src/gles.h new file mode 100644 index 00000000..00c4edfd --- /dev/null +++ b/src/gles.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including EGL headers, and disabling EGL function prototypes if EGL is not present + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_GLES_H +#define _FLUTTERPI_SRC_GLES_H + +#include "config.h" + +#if !defined(HAVE_GLES2) + #error "gles.h was included but OpenGLES2 support is disabled." +#endif + +#include +#include + +#endif // _FLUTTERPI_SRC_GLES_H diff --git a/src/jsmn.h b/src/jsmn.h new file mode 100644 index 00000000..79ceebf6 --- /dev/null +++ b/src/jsmn.h @@ -0,0 +1,446 @@ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef JSMN_H +#define JSMN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef JSMN_STATIC + #define JSMN_API static +#else + #define JSMN_API extern +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { JSMN_UNDEFINED = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, JSMN_STRING = 3, JSMN_PRIMITIVE = 4 } jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +JSMN_API void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing + * a single JSON object. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens); + +#ifndef JSMN_HEADER +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, const size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; + #ifdef JSMN_PARENT_LINKS + tok->parent = -1; + #endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, const int start, const int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { + #ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': + #endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + #ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; + #endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + #ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; + #endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); + #ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; + #endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: parser->pos = start; return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + #ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) { + return JSMN_ERROR_INVAL; + } + #endif + t->size++; + #ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; + #endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + #ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } + #else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } + #endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': break; + case ':': parser->toksuper = parser->toknext - 1; break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { + #ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; + #else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } + #endif + } + break; + #ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } + #else + /* In non-strict mode every unquoted value is a primitive */ + default: + #endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + + #ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: return JSMN_ERROR_INVAL; + #endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +JSMN_API void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +#endif /* JSMN_HEADER */ + +#ifdef __cplusplus +} +#endif + +#endif /* JSMN_H */ diff --git a/src/keyboard.c b/src/keyboard.c index bff8659c..9c499bcc 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1,27 +1,27 @@ #define _GNU_SOURCE -#include -#include -#include +#include "keyboard.h" + #include -#include -#include #include -#include +#include +#include +#include +#include + #include -#include +#include #include +#include +#include #include -#include #include - -#include +#include #include +#include -#include -#include - -FILE_DESCR("keyboard") +#include "util/collection.h" +#include "util/logging.h" static int find_var_offset_in_string(const char *varname, const char *buffer, regmatch_t *match) { regmatch_t matches[2]; @@ -39,7 +39,7 @@ static int find_var_offset_in_string(const char *varname, const char *buffer, re if (ok != 0) { //pregexerr("regcomp", ok, ®ex); ok = EINVAL; - goto fail_set_match; + goto fail_free_pattern; } ok = regexec(®ex, buffer, 2, matches, 0); @@ -52,12 +52,18 @@ static int find_var_offset_in_string(const char *varname, const char *buffer, re *match = matches[1]; } + free(pattern); + regfree(®ex); + return 0; - fail_free_regex: +fail_free_regex: regfree(®ex); - fail_set_match: +fail_free_pattern: + free(pattern); + +fail_set_match: if (match != NULL) { match->rm_so = -1; match->rm_eo = -1; @@ -88,16 +94,16 @@ static char *get_value_allocated(const char *varname, const char *buffer) { } strncpy(allocated, buffer + match.rm_so, match_length); - + allocated[match_length] = '\0'; - + return allocated; } static char *load_file(const char *path) { struct stat s; int ok, fd; - + ok = open(path, O_RDONLY); if (ok < 0) { goto fail_return_null; @@ -133,21 +139,23 @@ static char *load_file(const char *path) { return buffer; - - fail_close: +fail_close: close(fd); - fail_return_null: +fail_return_null: return NULL; } static struct xkb_keymap *load_default_keymap(struct xkb_context *context) { struct xkb_keymap *keymap; - char *file, *xkbmodel, *xkblayout, *xkbvariant, *xkboptions; + char *file, *xkbmodel, *xkblayout, *xkbvariant, *xkboptions; file = load_file("/etc/default/keyboard"); if (file == NULL) { - LOG_ERROR("Could not load keyboard configuration from \"/etc/default/keyboard\". Default keyboard config will be used. load_file: %s\n", strerror(errno)); + LOG_ERROR( + "Could not load keyboard configuration from \"/etc/default/keyboard\". Default keyboard config will be used. load_file: %s\n", + strerror(errno) + ); xkbmodel = NULL; xkblayout = NULL; xkbvariant = NULL; @@ -177,20 +185,18 @@ static struct xkb_keymap *load_default_keymap(struct xkb_context *context) { free(file); } - struct xkb_rule_names names = { - .rules = NULL, - .model = xkbmodel, - .layout = xkblayout, - .variant = xkbvariant, - .options = xkboptions - }; + struct xkb_rule_names names = { .rules = NULL, .model = xkbmodel, .layout = xkblayout, .variant = xkbvariant, .options = xkboptions }; keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); - if (xkbmodel != NULL) free(xkbmodel); - if (xkblayout != NULL) free(xkblayout); - if (xkbvariant != NULL) free(xkbvariant); - if (xkboptions != NULL) free(xkboptions); + if (xkbmodel != NULL) + free(xkbmodel); + if (xkblayout != NULL) + free(xkblayout); + if (xkbvariant != NULL) + free(xkbvariant); + if (xkboptions != NULL) + free(xkboptions); if (keymap == NULL) { LOG_ERROR("Could not create xkb keymap."); @@ -203,7 +209,7 @@ static struct xkb_compose_table *load_default_compose_table(struct xkb_context * struct xkb_compose_table *tbl; setlocale(LC_ALL, ""); - + tbl = xkb_compose_table_new_from_locale(context, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); if (tbl == NULL) { LOG_ERROR("Could not create compose table from locale.\n"); @@ -212,13 +218,12 @@ static struct xkb_compose_table *load_default_compose_table(struct xkb_context * return tbl; } - struct keyboard_config *keyboard_config_new(void) { struct keyboard_config *cfg; struct xkb_compose_table *compose_table; struct xkb_context *ctx; struct xkb_keymap *keymap; - + cfg = malloc(sizeof *cfg); if (cfg == NULL) { errno = ENOMEM; @@ -247,16 +252,16 @@ struct keyboard_config *keyboard_config_new(void) { return cfg; - fail_free_compose_table: +fail_free_compose_table: xkb_compose_table_unref(compose_table); - fail_free_context: +fail_free_context: xkb_context_unref(ctx); - fail_free_cfg: +fail_free_cfg: free(cfg); - fail_return_null: +fail_return_null: return NULL; } @@ -267,12 +272,8 @@ void keyboard_config_destroy(struct keyboard_config *config) { free(config); } - -struct keyboard_state *keyboard_state_new( - struct keyboard_config *config, - struct xkb_keymap *keymap_override, - struct xkb_compose_table *compose_table_override -) { +struct keyboard_state * +keyboard_state_new(struct keyboard_config *config, struct xkb_keymap *keymap_override, struct xkb_compose_table *compose_table_override) { struct keyboard_state *state; struct xkb_compose_state *compose_state; struct xkb_state *xkb_state, *plain_xkb_state; @@ -295,7 +296,10 @@ struct keyboard_state *keyboard_state_new( goto fail_free_xkb_state; } - compose_state = xkb_compose_state_new(compose_table_override != NULL ? compose_table_override : config->default_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + compose_state = xkb_compose_state_new( + compose_table_override != NULL ? compose_table_override : config->default_compose_table, + XKB_COMPOSE_STATE_NO_FLAGS + ); if (compose_state == NULL) { LOG_ERROR("Could not create new XKB compose state.\n"); goto fail_free_plain_xkb_state; @@ -308,22 +312,20 @@ struct keyboard_state *keyboard_state_new( return state; - fail_free_plain_xkb_state: +fail_free_plain_xkb_state: xkb_state_unref(plain_xkb_state); - fail_free_xkb_state: +fail_free_xkb_state: xkb_state_unref(xkb_state); - fail_free_state: +fail_free_state: free(state); - fail_return_null: +fail_return_null: return NULL; } -void keyboard_state_destroy( - struct keyboard_state *state -) { +void keyboard_state_destroy(struct keyboard_state *state) { xkb_compose_state_unref(state->compose_state); xkb_state_unref(state->plain_state); xkb_state_unref(state->state); @@ -361,7 +363,7 @@ int keyboard_state_process_key_event( if (feed_result == XKB_COMPOSE_FEED_ACCEPTED && compose_status == XKB_COMPOSE_COMPOSING) { keysym = XKB_KEY_NoSymbol; } - + if (compose_status == XKB_COMPOSE_COMPOSED) { keysym = xkb_compose_state_get_one_sym(state->compose_state); xkb_compose_state_reset(state->compose_state); @@ -369,22 +371,20 @@ int keyboard_state_process_key_event( xkb_compose_state_reset(state->compose_state); } - codepoint = xkb_keysym_to_utf32(keysym); + codepoint = xkb_state_key_get_utf32(state->state, xkb_keycode); } xkb_state_update_key(state->state, xkb_keycode, (enum xkb_key_direction) evdev_value); - if (keysym_out) *keysym_out = keysym; - if (codepoint_out) *codepoint_out = codepoint; + if (keysym_out) + *keysym_out = keysym; + if (codepoint_out) + *codepoint_out = codepoint; return 0; } -uint32_t keyboard_state_get_plain_codepoint( - struct keyboard_state *state, - uint16_t evdev_keycode, - int32_t evdev_value -) { +uint32_t keyboard_state_get_plain_codepoint(struct keyboard_state *state, uint16_t evdev_keycode, int32_t evdev_value) { xkb_keycode_t xkb_keycode = evdev_keycode + 8; if (evdev_value) { @@ -392,4 +392,4 @@ uint32_t keyboard_state_get_plain_codepoint( } return 0; -} \ No newline at end of file +} diff --git a/include/keyboard.h b/src/keyboard.h similarity index 57% rename from include/keyboard.h rename to src/keyboard.h index 16eb3a8a..a72ac25b 100644 --- a/include/keyboard.h +++ b/src/keyboard.h @@ -1,5 +1,16 @@ -#ifndef KEYBOARD_H -#define KEYBOARD_H +// SPDX-License-Identifier: MIT +/* + * Keyboard / Text Input support + * + * Converts key events to text events using the system keyboard config. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_KEYBOARD_H +#define _FLUTTERPI_SRC_KEYBOARD_H + +#include #include @@ -20,13 +31,13 @@ struct keyboard_state { }; struct keyboard_modifier_state { - bool ctrl:1; - bool shift:1; - bool alt:1; - bool meta:1; - bool capslock:1; - bool numlock:1; - bool scrolllock:1; + bool ctrl : 1; + bool shift : 1; + bool alt : 1; + bool meta : 1; + bool capslock : 1; + bool numlock : 1; + bool scrolllock : 1; }; #define KEY_RELEASE 0 @@ -37,15 +48,10 @@ struct keyboard_config *keyboard_config_new(void); void keyboard_config_destroy(struct keyboard_config *config); -struct keyboard_state *keyboard_state_new( - struct keyboard_config *config, - struct xkb_keymap *keymap_override, - struct xkb_compose_table *compose_table_override -); +struct keyboard_state * +keyboard_state_new(struct keyboard_config *config, struct xkb_keymap *keymap_override, struct xkb_compose_table *compose_table_override); -void keyboard_state_destroy( - struct keyboard_state *state -); +void keyboard_state_destroy(struct keyboard_state *state); int keyboard_state_process_key_event( struct keyboard_state *state, @@ -55,66 +61,46 @@ int keyboard_state_process_key_event( uint32_t *codepoint_out ); -uint32_t keyboard_state_get_plain_codepoint( - struct keyboard_state *state, - uint16_t evdev_keycode, - int32_t evdev_value -); +uint32_t keyboard_state_get_plain_codepoint(struct keyboard_state *state, uint16_t evdev_keycode, int32_t evdev_value); -static inline bool keyboard_state_is_ctrl_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_ctrl_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_shift_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_shift_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_alt_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_alt_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_meta_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_meta_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_capslock_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_capslock_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_numlock_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_numlock_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_NUM, XKB_STATE_MODS_EFFECTIVE); } -static inline bool keyboard_state_is_scrolllock_active( - struct keyboard_state *state -) { +static inline bool keyboard_state_is_scrolllock_active(struct keyboard_state *state) { return xkb_state_mod_name_is_active(state->state, "Mod3", XKB_STATE_MODS_EFFECTIVE); } -static inline struct keyboard_modifier_state keyboard_state_get_meta_state( - struct keyboard_state *state -) { - return (struct keyboard_modifier_state) { +static inline struct keyboard_modifier_state keyboard_state_get_meta_state(struct keyboard_state *state) { + return (struct keyboard_modifier_state){ .ctrl = keyboard_state_is_ctrl_active(state), .shift = keyboard_state_is_shift_active(state), .alt = keyboard_state_is_alt_active(state), .meta = keyboard_state_is_meta_active(state), .capslock = keyboard_state_is_capslock_active(state), .numlock = keyboard_state_is_numlock_active(state), - .scrolllock = keyboard_state_is_scrolllock_active(state) + .scrolllock = keyboard_state_is_scrolllock_active(state), }; } -#endif \ No newline at end of file +#endif // _FLUTTERPI_SRC_KEYBOARD_H diff --git a/src/locales.c b/src/locales.c index 3d3aabb8..ff66a098 100644 --- a/src/locales.c +++ b/src/locales.c @@ -1,17 +1,25 @@ #define _GNU_SOURCE +#include "locales.h" + +#include #include #include +#include #include -#include -#include -#include + #include -#include -#include + +#include "flutter-pi.h" +#include "util/asserts.h" +#include "util/collection.h" +#include "util/list.h" +#include "util/logging.h" #define LOG_LOCALES_ERROR(...) fprintf(stderr, "[locales] " __VA_ARGS__); struct locale { + struct list_head entry; + char *language; char *territory; char *codeset; @@ -23,12 +31,14 @@ struct locales { const FlutterLocale **flutter_locales; const FlutterLocale *default_flutter_locale; - struct concurrent_pointer_set locales; + struct list_head locales; struct locale *default_locale; - + size_t n_locales; }; +#define for_each_locale_in_locales(_locale, _locales) list_for_each_entry_safe(struct locale, _locale, &(_locales)->locales, entry) + static const char *get_system_locale_string(void) { const char *locale; @@ -60,7 +70,7 @@ struct locale *locale_new(const char *language, const char *territory, const cha struct locale *locale; FlutterLocale *fl_locale; - DEBUG_ASSERT(language != NULL); + assert(language != NULL); locale = malloc(sizeof *locale); if (locale == NULL) { @@ -109,6 +119,7 @@ struct locale *locale_new(const char *language, const char *territory, const cha fl_locale->country_code = territory_dup; fl_locale->script_code = codeset_dup; fl_locale->variant_code = modifier_dup; + locale->entry = (struct list_head){ NULL, NULL }; locale->flutter_locale = fl_locale; locale->language = language_dup; locale->territory = territory_dup; @@ -117,40 +128,42 @@ struct locale *locale_new(const char *language, const char *territory, const cha return locale; - - fail_free_codeset_dup: +fail_free_codeset_dup: free(codeset_dup); - fail_free_territory_dup: +fail_free_territory_dup: free(territory_dup); - fail_free_language_dup: +fail_free_language_dup: free(language_dup); - fail_free_fl_locale: +fail_free_fl_locale: free(fl_locale); - fail_free_locale: +fail_free_locale: free(locale); return NULL; } const FlutterLocale *locale_get_fl_locale(struct locale *locale) { - DEBUG_ASSERT(locale != NULL); + assert(locale != NULL); return locale->flutter_locale; } void locale_destroy(struct locale *locale) { free(locale->language); - if (locale->territory) free(locale->territory); - if (locale->codeset) free(locale->codeset); - if (locale->modifier) free(locale->modifier); + if (locale->territory) + free(locale->territory); + if (locale->codeset) + free(locale->codeset); + if (locale->modifier) + free(locale->modifier); free(locale->flutter_locale); free(locale); } -static int add_locale_variants(struct concurrent_pointer_set *locales, const char *locale_description) { +static int add_locale_variants(struct list_head *locales, const char *locale_description) { char *language = NULL; char *territory = NULL; char *codeset = NULL; @@ -235,57 +248,57 @@ static int add_locale_variants(struct concurrent_pointer_set *locales, const cha goto fail_free_language; } - ok = cpset_put_locked(locales, locale); - if (ok != 0) { - locale_destroy(locale); - goto fail_free_language; - } + list_add(&locale->entry, locales); } + free(language); + if (territory) + free(territory); + if (codeset) + free(codeset); + if (modifier) + free(modifier); return 0; - - fail_free_language: +fail_free_language: free(language); - fail_free_territory: - if (territory) free(territory); +fail_free_territory: + if (territory) + free(territory); - fail_free_codeset: - if (codeset) free(codeset); +fail_free_codeset: + if (codeset) + free(codeset); - fail_free_modifier: - if (modifier) free(modifier); +fail_free_modifier: + if (modifier) + free(modifier); - fail_return_ok: - return ok; +fail_return_ok: + return ok; } struct locales *locales_new(void) { struct locales *locales; - struct locale *locale; const char *system_locales; char *system_locales_modifiable, *syslocale; const FlutterLocale **fl_locales; size_t n_locales; - int ok; locales = malloc(sizeof *locales); if (locales == NULL) { goto fail_return_null; } - ok = cpset_init(&locales->locales, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - goto fail_free_locales; - } - + list_inithead(&locales->locales); + // Add our system locales. system_locales = get_system_locale_string(); system_locales_modifiable = strdup(system_locales); if (system_locales_modifiable == NULL) { - goto fail_deinit_cpset; + goto fail_free_locales; } syslocale = strtok(system_locales_modifiable, ":"); @@ -297,20 +310,21 @@ struct locales *locales_new(void) { free(system_locales_modifiable); // Use those to create our flutter locales. - n_locales = cpset_get_count_pointers_locked(&locales->locales); + n_locales = list_length(&locales->locales); fl_locales = calloc(n_locales, sizeof *fl_locales); if (fl_locales == NULL) { goto fail_free_allocated_locales; } int i = 0; - for_each_pointer_in_cpset(&locales->locales, locale) { + for_each_locale_in_locales(locale, locales) { fl_locales[i] = locale_get_fl_locale(locale); i++; } - if (strcmp(fl_locales[0]->language_code, "C") == 0) { - LOG_LOCALES_ERROR("Warning: The system has no configured locale. The default \"C\" locale may or may not be supported by the app.\n"); + if (streq(fl_locales[0]->language_code, "C")) { + LOG_LOCALES_ERROR("Warning: The system has no configured locale. The default \"C\" locale may or may not be supported by the app.\n" + ); } locales->flutter_locales = fl_locales; @@ -320,45 +334,41 @@ struct locales *locales_new(void) { return locales; - - fail_free_allocated_locales: - for_each_pointer_in_cpset(&locales->locales, locale) { +fail_free_allocated_locales: + for_each_locale_in_locales(locale, locales) { + list_del(&locale->entry); locale_destroy(locale); } - fail_deinit_cpset: - cpset_deinit(&locales->locales); - - fail_free_locales: +fail_free_locales: free(locales); - fail_return_null: +fail_return_null: return NULL; } void locales_destroy(struct locales *locales) { - struct locale *locale; + assert(locales != NULL); - DEBUG_ASSERT(locales != NULL); - - for_each_pointer_in_cpset(&locales->locales, locale) { + for_each_locale_in_locales(locale, locales) { + list_del(&locale->entry); locale_destroy(locale); } - cpset_deinit(&locales->locales); + free(locales->flutter_locales); free(locales); } int locales_get_flutter_locales(struct locales *locales, const FlutterLocale ***fl_locales_out, size_t *n_fl_locales_out) { - DEBUG_ASSERT(locales != NULL); - DEBUG_ASSERT(fl_locales_out != NULL); - DEBUG_ASSERT(n_fl_locales_out != NULL); + assert(locales != NULL); + assert(fl_locales_out != NULL); + assert(n_fl_locales_out != NULL); *fl_locales_out = locales->flutter_locales; *n_fl_locales_out = locales->n_locales; return 0; } const FlutterLocale *locales_get_default_flutter_locale(struct locales *locales) { - DEBUG_ASSERT(locales != NULL); + assert(locales != NULL); return locales->default_flutter_locale; } @@ -382,63 +392,67 @@ const char *locale_get_modifier(struct locale *locale) { int locales_add_to_fl_engine(struct locales *locales, FlutterEngine engine, FlutterEngineUpdateLocalesFnPtr update_locales) { FlutterEngineResult engine_result; - + engine_result = update_locales(engine, locales->flutter_locales, locales->n_locales); if (engine_result != kSuccess) { - LOG_LOCALES_ERROR("Couldn't update flutter engine locales. FlutterEngineUpdateLocales: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + LOG_LOCALES_ERROR( + "Couldn't update flutter engine locales. FlutterEngineUpdateLocales: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); return EINVAL; } return 0; } -const FlutterLocale *locales_on_compute_platform_resolved_locale(struct locales *locales, const FlutterLocale **fl_locales, size_t n_fl_locales) { - DEBUG_ASSERT(locales != NULL); - DEBUG_ASSERT(fl_locales != NULL); - DEBUG_ASSERT(n_fl_locales > 0); +const FlutterLocale * +locales_on_compute_platform_resolved_locale(struct locales *locales, const FlutterLocale **fl_locales, size_t n_fl_locales) { + assert(locales != NULL); + assert(fl_locales != NULL); + assert(n_fl_locales > 0); (void) locales; (void) n_fl_locales; - + return fl_locales[0]; } void locales_print(const struct locales *locales) { - DEBUG_ASSERT(locales != NULL); + ASSERT_NOT_NULL(locales); - printf("==============Locale==============\n"); - printf("Flutter locale:\n"); + LOG_DEBUG_UNPREFIXED("==============Locale==============\n"); + LOG_DEBUG_UNPREFIXED("Flutter locale:\n"); if (locales->default_flutter_locale != NULL) { - printf(" default: %s", locales->default_flutter_locale->language_code); + LOG_DEBUG_UNPREFIXED(" default: %s", locales->default_flutter_locale->language_code); if (locales->default_flutter_locale->country_code != NULL) { - printf("_%s", locales->default_flutter_locale->country_code); + LOG_DEBUG_UNPREFIXED("_%s", locales->default_flutter_locale->country_code); } if (locales->default_flutter_locale->script_code != NULL) { - printf(".%s", locales->default_flutter_locale->script_code); + LOG_DEBUG_UNPREFIXED(".%s", locales->default_flutter_locale->script_code); } if (locales->default_flutter_locale->variant_code != NULL) { - printf("@%s", locales->default_flutter_locale->variant_code); + LOG_DEBUG_UNPREFIXED("@%s", locales->default_flutter_locale->variant_code); } - printf("\n"); + LOG_DEBUG_UNPREFIXED("\n"); } else { - printf(" default: NULL\n"); + LOG_DEBUG_UNPREFIXED(" default: NULL\n"); } - printf(" locales:"); + LOG_DEBUG_UNPREFIXED(" locales:"); for (size_t idx = 0; idx < locales->n_locales; idx++) { const FlutterLocale *locale = locales->flutter_locales[idx]; - printf(" %s", locale->language_code); + LOG_DEBUG_UNPREFIXED(" %s", locale->language_code); if (locale->country_code != NULL) { - printf("_%s", locale->country_code); + LOG_DEBUG_UNPREFIXED("_%s", locale->country_code); } if (locale->script_code != NULL) { - printf(".%s", locale->script_code); + LOG_DEBUG_UNPREFIXED(".%s", locale->script_code); } if (locale->variant_code != NULL) { - printf("@%s", locale->variant_code); + LOG_DEBUG_UNPREFIXED("@%s", locale->variant_code); } } - printf("\n===================================\n"); + LOG_DEBUG_UNPREFIXED("\n===================================\n"); } diff --git a/include/locales.h b/src/locales.h similarity index 69% rename from include/locales.h rename to src/locales.h index babbcab6..04da3788 100644 --- a/include/locales.h +++ b/src/locales.h @@ -1,5 +1,14 @@ -#ifndef _LOCALES_H -#define _LOCALES_H +// SPDX-License-Identifier: MIT +/* + * Locales + * + * Provides the configured system locales in a flutter-friendly form. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_LOCALES_H +#define _FLUTTERPI_SRC_LOCALES_H #include @@ -21,7 +30,6 @@ const char *locale_get_codeset(struct locale *locale); const char *locale_get_modifier(struct locale *locale); - struct locales *locales_new(void); void locales_destroy(struct locales *locales); @@ -32,8 +40,9 @@ const FlutterLocale *locales_get_default_flutter_locale(struct locales *locales) int locales_add_to_fl_engine(struct locales *locales, FlutterEngine engine, FlutterEngineUpdateLocalesFnPtr update_locales); -const FlutterLocale *locales_on_compute_platform_resolved_locale(struct locales *locales, const FlutterLocale **fl_locales, size_t n_fl_locales); +const FlutterLocale * +locales_on_compute_platform_resolved_locale(struct locales *locales, const FlutterLocale **fl_locales, size_t n_fl_locales); void locales_print(const struct locales *locales); -#endif +#endif // _FLUTTERPI_SRC_LOCALES_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..b339272a --- /dev/null +++ b/src/main.c @@ -0,0 +1,5 @@ +int flutterpi_app_main(int argc, char **argv); + +int main(int argc, char **argv) { + return flutterpi_app_main(argc, argv); +} diff --git a/src/modesetting.c b/src/modesetting.c index b22269ba..abee0d86 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -1,99 +1,300 @@ -#include -#include +#include "modesetting.h" + +#include +#include #include -#include +#include #include -#include +#include + +#include +#include #include +#include +#include #include #include -#include +#include "pixel_format.h" +#include "util/bitset.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" +#include "util/refcounting.h" + +struct drm_fb { + struct list_head entry; + + uint32_t id; + + uint32_t width, height; + + enum pixfmt format; + + bool has_modifier; + uint64_t modifier; + + uint32_t flags; + + uint32_t handles[4]; + uint32_t pitches[4]; + uint32_t offsets[4]; +}; + +struct kms_req_layer { + struct kms_fb_layer layer; + + uint32_t plane_id; + struct drm_plane *plane; + + bool set_zpos; + int64_t zpos; + + bool set_rotation; + drm_plane_transform_t rotation; + + kms_fb_release_cb_t release_callback; + kms_deferred_fb_release_cb_t deferred_release_callback; + void *release_callback_userdata; +}; + +struct kms_req_builder { + refcount_t n_refs; + + struct drmdev *drmdev; + bool use_legacy; + bool supports_atomic; + + struct drm_connector *connector; + struct drm_crtc *crtc; + + BITSET_DECLARE(available_planes, 32); + drmModeAtomicReq *req; + int64_t next_zpos; + + int n_layers; + struct kms_req_layer layers[32]; + + bool unset_mode; + bool has_mode; + drmModeModeInfo mode; +}; -static int drmdev_lock(struct drmdev *drmdev) { - return pthread_mutex_lock(&drmdev->mutex); +COMPILE_ASSERT(BITSET_SIZE(((struct kms_req_builder *) 0)->available_planes) == 32); + +struct drmdev { + int fd; + + refcount_t n_refs; + pthread_mutex_t mutex; + bool supports_atomic_modesetting; + bool supports_dumb_buffers; + + size_t n_connectors; + struct drm_connector *connectors; + + size_t n_encoders; + struct drm_encoder *encoders; + + size_t n_crtcs; + struct drm_crtc *crtcs; + + size_t n_planes; + struct drm_plane *planes; + + drmModeRes *res; + drmModePlaneRes *plane_res; + + struct gbm_device *gbm_device; + + int event_fd; + + struct { + kms_scanout_cb_t scanout_callback; + void *userdata; + void_callback_t destroy_callback; + + struct kms_req *last_flipped; + } per_crtc_state[32]; + + int master_fd; + void *master_fd_metadata; + + struct drmdev_interface interface; + void *userdata; + + struct list_head fbs; +}; + +static bool is_drm_master(int fd) { + return drmAuthMagic(fd, 0) != -EACCES; } -static int drmdev_unlock(struct drmdev *drmdev) { - return pthread_mutex_unlock(&drmdev->mutex); +static struct drm_mode_blob *drm_mode_blob_new(int drm_fd, const drmModeModeInfo *mode) { + struct drm_mode_blob *blob; + uint32_t blob_id; + int ok; + + blob = malloc(sizeof *blob); + if (blob == NULL) { + return NULL; + } + + ok = drmModeCreatePropertyBlob(drm_fd, mode, sizeof *mode, &blob_id); + if (ok != 0) { + ok = errno; + LOG_ERROR("Couldn't upload mode to kernel. drmModeCreatePropertyBlob: %s\n", strerror(ok)); + free(blob); + return NULL; + } + + blob->drm_fd = dup(drm_fd); + blob->blob_id = blob_id; + blob->mode = *mode; + return blob; } -static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { - struct drm_connector *connectors; - int n_allocated_connectors; +void drm_mode_blob_destroy(struct drm_mode_blob *blob) { int ok; - connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); - if (connectors == NULL) { - *connectors_out = NULL; - return ENOMEM; + ASSERT_NOT_NULL(blob); + + ok = drmModeDestroyPropertyBlob(blob->drm_fd, blob->blob_id); + if (ok != 0) { + ok = errno; + LOG_ERROR("Couldn't destroy mode property blob. drmModeDestroyPropertyBlob: %s\n", strerror(ok)); } + // we dup()-ed it in drm_mode_blob_new. + close(blob->drm_fd); + free(blob); +} - n_allocated_connectors = 0; - for (int i = 0; i < drmdev->res->count_connectors; i++, n_allocated_connectors++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModeConnector *connector; +DEFINE_STATIC_LOCK_OPS(drmdev, mutex) - connector = drmModeGetConnector(drmdev->fd, drmdev->res->connectors[i]); - if (connector == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device connector. drmModeGetConnector"); - goto fail_free_connectors; - } +static int fetch_connector(int drm_fd, uint32_t connector_id, struct drm_connector *connector_out) { + struct drm_connector_prop_ids ids; + drmModeObjectProperties *props; + drmModePropertyRes *prop_info; + drmModeConnector *connector; + drmModeModeInfo *modes; + uint32_t crtc_id; + int ok; + + drm_connector_prop_ids_init(&ids); + + connector = drmModeGetConnector(drm_fd, connector_id); + if (connector == NULL) { + ok = errno; + LOG_ERROR("Could not get DRM device connector. drmModeGetConnector"); + return ok; + } + + props = drmModeObjectGetProperties(drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); + goto fail_free_connector; + } - props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->connectors[i], DRM_MODE_OBJECT_CONNECTOR); - if (props == NULL) { + crtc_id = DRM_ID_NONE; + for (int i = 0; i < props->count_props; i++) { + prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); - drmModeFreeConnector(connector); - goto fail_free_connectors; + LOG_ERROR("Could not get DRM device connector properties' info. drmModeGetProperty: %s\n", strerror(ok)); + goto fail_free_props; } - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreeConnector(connector); - goto fail_free_connectors; +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, DRM_PROP_NAME_LEN) == 0) { \ + ids._name = prop_info->prop_id; \ + } else + + DRM_CONNECTOR_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // this is the trailing else case + LOG_DEBUG("Unknown DRM connector property: %s\n", prop_info->name); } - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device connector properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreeConnector(connector); - goto fail_free_connectors; - } +#undef CHECK_ASSIGN_PROPERTY_ID + + if (strncmp(prop_info->name, "CRTC_ID", DRM_PROP_NAME_LEN) == 0) { + crtc_id = props->prop_values[i]; } - connectors[i].connector = connector; - connectors[i].props = props; - connectors[i].props_info = props_info; + drmModeFreeProperty(prop_info); + prop_info = NULL; } - *connectors_out = connectors; - *n_connectors_out = drmdev->res->count_connectors; + assert((connector->modes == NULL) == (connector->count_modes == 0)); + if (connector->modes != NULL) { + modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); + if (modes == NULL) { + ok = ENOMEM; + goto fail_free_props; + } + } else { + modes = NULL; + } + + connector_out->id = connector->connector_id; + connector_out->type = connector->connector_type; + connector_out->type_id = connector->connector_type_id; + connector_out->ids = ids; + connector_out->n_encoders = connector->count_encoders; + assert(connector->count_encoders <= 32); + memcpy(connector_out->encoders, connector->encoders, connector->count_encoders * sizeof(uint32_t)); + connector_out->variable_state.connection_state = (enum drm_connection_state) connector->connection; + connector_out->variable_state.subpixel_layout = (enum drm_subpixel_layout) connector->subpixel; + connector_out->variable_state.width_mm = connector->mmWidth; + connector_out->variable_state.height_mm = connector->mmHeight; + connector_out->variable_state.n_modes = connector->count_modes; + connector_out->variable_state.modes = modes; + connector_out->committed_state.crtc_id = crtc_id; + connector_out->committed_state.encoder_id = connector->encoder_id; + drmModeFreeObjectProperties(props); + drmModeFreeConnector(connector); return 0; - fail_free_connectors: - for (int i = 0; i < n_allocated_connectors; i++) { - for (int j = 0; j < connectors[i].props->count_props; j++) - drmModeFreeProperty(connectors[i].props_info[j]); - free(connectors[i].props_info); - drmModeFreeObjectProperties(connectors[i].props); - drmModeFreeConnector(connectors[i].connector); +fail_free_props: + drmModeFreeObjectProperties(props); + +fail_free_connector: + drmModeFreeConnector(connector); + return ok; +} + +static void free_connector(struct drm_connector *connector) { + free(connector->variable_state.modes); +} + +static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { + struct drm_connector *connectors; + int ok; + + connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); + if (connectors == NULL) { + *connectors_out = NULL; + return ENOMEM; } - free(connectors); + for (int i = 0; i < drmdev->res->count_connectors; i++) { + ok = fetch_connector(drmdev->fd, drmdev->res->connectors[i], connectors + i); + if (ok != 0) { + for (int j = 0; j < i; j++) + free_connector(connectors + j); + goto fail_free_connectors; + } + } + *connectors_out = connectors; + *n_connectors_out = drmdev->res->count_connectors; + return 0; + +fail_free_connectors: + free(connectors); *connectors_out = NULL; *n_connectors_out = 0; return ok; @@ -101,18 +302,31 @@ static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connec static int free_connectors(struct drm_connector *connectors, size_t n_connectors) { for (int i = 0; i < n_connectors; i++) { - for (int j = 0; j < connectors[i].props->count_props; j++) - drmModeFreeProperty(connectors[i].props_info[j]); - free(connectors[i].props_info); - drmModeFreeObjectProperties(connectors[i].props); - drmModeFreeConnector(connectors[i].connector); + free_connector(connectors + i); } - free(connectors); + return 0; +} +static int fetch_encoder(int drm_fd, uint32_t encoder_id, struct drm_encoder *encoder_out) { + drmModeEncoder *encoder; + int ok; + + encoder = drmModeGetEncoder(drm_fd, encoder_id); + if (encoder == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); + return ok; + } + + encoder_out->encoder = encoder; return 0; } +static void free_encoder(struct drm_encoder *encoder) { + drmModeFreeEncoder(encoder->encoder); +} + static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_out, size_t *n_encoders_out) { struct drm_encoder *encoders; int n_allocated_encoders; @@ -127,24 +341,17 @@ static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_o n_allocated_encoders = 0; for (int i = 0; i < drmdev->res->count_encoders; i++, n_allocated_encoders++) { - drmModeEncoder *encoder; - - encoder = drmModeGetEncoder(drmdev->fd, drmdev->res->encoders[i]); - if (encoder == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); + ok = fetch_encoder(drmdev->fd, drmdev->res->encoders[i], encoders + i); + if (ok != 0) { goto fail_free_encoders; } - - encoders[i].encoder = encoder; } *encoders_out = encoders; *n_encoders_out = drmdev->res->count_encoders; - return 0; - fail_free_encoders: +fail_free_encoders: for (int i = 0; i < n_allocated_encoders; i++) { drmModeFreeEncoder(encoders[i].encoder); } @@ -156,95 +363,110 @@ static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_o return ok; } -static int free_encoders(struct drm_encoder *encoders, size_t n_encoders) { +static void free_encoders(struct drm_encoder *encoders, size_t n_encoders) { for (int i = 0; i < n_encoders; i++) { - drmModeFreeEncoder(encoders[i].encoder); + free_encoder(encoders + i); } - free(encoders); +} + +static int fetch_crtc(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_crtc *crtc_out) { + struct drm_crtc_prop_ids ids; + drmModeObjectProperties *props; + drmModePropertyRes *prop_info; + drmModeCrtc *crtc; + int ok; + + drm_crtc_prop_ids_init(&ids); + + crtc = drmModeGetCrtc(drm_fd, crtc_id); + if (crtc == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); + return ok; + } + props = drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); + goto fail_free_crtc; + } + + for (int i = 0; i < props->count_props; i++) { + prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); + goto fail_free_props; + } + +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, ARRAY_SIZE(prop_info->name)) == 0) { \ + ids._name = prop_info->prop_id; \ + } else + + DRM_CRTC_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // this is the trailing else case + LOG_DEBUG("Unknown DRM crtc property: %s\n", prop_info->name); + } + +#undef CHECK_ASSIGN_PROPERTY_ID + + drmModeFreeProperty(prop_info); + prop_info = NULL; + } + + crtc_out->id = crtc->crtc_id; + crtc_out->index = crtc_index; + crtc_out->bitmask = 1u << crtc_index; + crtc_out->ids = ids; + crtc_out->committed_state.has_mode = crtc->mode_valid; + crtc_out->committed_state.mode = crtc->mode; + crtc_out->committed_state.mode_blob = NULL; + drmModeFreeObjectProperties(props); + drmModeFreeCrtc(crtc); return 0; + +fail_free_props: + drmModeFreeObjectProperties(props); + +fail_free_crtc: + drmModeFreeCrtc(crtc); + return ok; +} + +static void free_crtc(struct drm_crtc *crtc) { + /// TODO: Implement + (void) crtc; } static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_t *n_crtcs_out) { struct drm_crtc *crtcs; - int n_allocated_crtcs; int ok; crtcs = calloc(drmdev->res->count_crtcs, sizeof *crtcs); if (crtcs == NULL) { *crtcs_out = NULL; + *n_crtcs_out = 0; return ENOMEM; } - n_allocated_crtcs = 0; - for (int i = 0; i < drmdev->res->count_crtcs; i++, n_allocated_crtcs++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModeCrtc *crtc; - - crtc = drmModeGetCrtc(drmdev->fd, drmdev->res->crtcs[i]); - if (crtc == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); - goto fail_free_crtcs; - } - - props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->crtcs[i], DRM_MODE_OBJECT_CRTC); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); - drmModeFreeCrtc(crtc); - goto fail_free_crtcs; - } - - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); + for (int i = 0; i < drmdev->res->count_crtcs; i++) { + ok = fetch_crtc(drmdev->fd, i, drmdev->res->crtcs[i], crtcs + i); + if (ok != 0) { + for (int j = 0; j < i; j++) + free_crtc(crtcs + i); goto fail_free_crtcs; } - - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); - goto fail_free_crtcs; - } - } - - crtcs[i].crtc = crtc; - crtcs[i].props = props; - crtcs[i].props_info = props_info; - - crtcs[i].index = i; - crtcs[i].bitmask = 1 << i; } *crtcs_out = crtcs; *n_crtcs_out = drmdev->res->count_crtcs; - return 0; - - fail_free_crtcs: - for (int i = 0; i < n_allocated_crtcs; i++) { - for (int j = 0; j < crtcs[i].props->count_props; j++) - drmModeFreeProperty(crtcs[i].props_info[j]); - free(crtcs[i].props_info); - drmModeFreeObjectProperties(crtcs[i].props); - drmModeFreeCrtc(crtcs[i].crtc); - } - +fail_free_crtcs: free(crtcs); - *crtcs_out = NULL; *n_crtcs_out = 0; return ok; @@ -252,174 +474,534 @@ static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_ static int free_crtcs(struct drm_crtc *crtcs, size_t n_crtcs) { for (int i = 0; i < n_crtcs; i++) { - for (int j = 0; j < crtcs[i].props->count_props; j++) - drmModeFreeProperty(crtcs[i].props_info[j]); - free(crtcs[i].props_info); - drmModeFreeObjectProperties(crtcs[i].props); - drmModeFreeCrtc(crtcs[i].crtc); + free_crtc(crtcs + i); } - free(crtcs); - return 0; } -static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { - struct drm_plane *planes; - int n_allocated_planes; - int ok; +void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata) { + struct drm_format_modifier_blob *blob; + struct drm_format_modifier *modifiers; + uint32_t *formats; - planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); - if (planes == NULL) { - *planes_out = NULL; - return ENOMEM; - } + ASSERT_NOT_NULL(plane); + ASSERT_NOT_NULL(callback); + ASSERT(plane->supports_modifiers); + ASSERT_EQUALS(plane->supported_modified_formats_blob->version, FORMAT_BLOB_CURRENT); - n_allocated_planes = 0; - for (int i = 0; i < drmdev->plane_res->count_planes; i++, n_allocated_planes++) { - drmModeObjectProperties *props; - drmModePropertyRes **props_info; - drmModePlane *plane; + blob = plane->supported_modified_formats_blob; - plane = drmModeGetPlane(drmdev->fd, drmdev->plane_res->planes[i]); - if (plane == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); - goto fail_free_planes; + modifiers = (void *) (((char *) blob) + blob->modifiers_offset); + formats = (void *) (((char *) blob) + blob->formats_offset); + + int index = 0; + for (int i = 0; i < blob->count_modifiers; i++) { + for (int j = modifiers[i].offset; (j < blob->count_formats) && (j < modifiers[i].offset + 64); j++) { + bool is_format_bit_set = (modifiers[i].formats & (1ull << (j % 64))) != 0; + if (!is_format_bit_set) { + continue; + } + + if (has_pixfmt_for_drm_format(formats[j])) { + enum pixfmt format = get_pixfmt_for_drm_format(formats[j]); + + bool should_continue = callback(plane, index, format, modifiers[i].modifier, userdata); + if (!should_continue) { + goto exit; + } + + index++; + } } + } + +exit: + return; +} + +struct _drmModeFB2; + +struct drm_mode_fb2 { + uint32_t fb_id; + uint32_t width, height; + uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ + uint64_t modifier; /* applies to all buffers */ + uint32_t flags; + + /* per-plane GEM handle; may be duplicate entries for multiple planes */ + uint32_t handles[4]; + uint32_t pitches[4]; /* bytes */ + uint32_t offsets[4]; /* bytes */ +}; + +#ifdef HAVE_FUNC_ATTRIBUTE_WEAK +extern struct _drmModeFB2 *drmModeGetFB2(int fd, uint32_t bufferId) __attribute__((weak)); +extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __attribute__((weak)); + #define HAVE_WEAK_DRM_MODE_GET_FB2 +#endif + +static int fetch_plane(int drm_fd, uint32_t plane_id, struct drm_plane *plane_out) { + struct drm_plane_prop_ids ids; + drmModeObjectProperties *props; + drm_plane_transform_t hardcoded_rotation, supported_rotations, committed_rotation; + enum drm_blend_mode committed_blend_mode; + enum drm_plane_type type; + drmModePropertyRes *info; + drmModePlane *plane; + uint32_t comitted_crtc_x, comitted_crtc_y, comitted_crtc_w, comitted_crtc_h; + uint32_t comitted_src_x, comitted_src_y, comitted_src_w, comitted_src_h; + uint16_t committed_alpha; + int64_t min_zpos, max_zpos, hardcoded_zpos, committed_zpos; + bool supported_blend_modes[kCount_DrmBlendMode] = { 0 }; + bool supported_formats[PIXFMT_COUNT] = { 0 }; + bool has_type, has_rotation, has_zpos, has_hardcoded_zpos, has_hardcoded_rotation, has_alpha, has_blend_mode; + int ok; + + drm_plane_prop_ids_init(&ids); + + plane = drmModeGetPlane(drm_fd, plane_id); + if (plane == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); + return ok; + } - props = drmModeObjectGetProperties(drmdev->fd, drmdev->plane_res->planes[i], DRM_MODE_OBJECT_PLANE); - if (props == NULL) { + props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); + goto fail_free_plane; + } + + // zero-initialize plane_out. + memset(plane_out, 0, sizeof(*plane_out)); + + has_type = false; + has_rotation = false; + has_hardcoded_rotation = false; + has_zpos = false; + has_hardcoded_zpos = false; + has_alpha = false; + has_blend_mode = false; + comitted_crtc_x = comitted_crtc_y = comitted_crtc_w = comitted_crtc_h = 0; + comitted_src_x = comitted_src_y = comitted_src_w = comitted_src_h = 0; + for (int j = 0; j < props->count_props; j++) { + info = drmModeGetProperty(drm_fd, props->props[j]); + if (info == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); - drmModeFreePlane(plane); - goto fail_free_planes; + perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); + goto fail_maybe_free_supported_modified_formats_blob; } - props_info = calloc(props->count_props, sizeof *props_info); - if (props_info == NULL) { - ok = ENOMEM; - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - goto fail_free_planes; - } + if (streq(info->name, "type")) { + assert(has_type == false); + has_type = true; + + type = props->prop_values[j]; + } else if (streq(info->name, "rotation")) { + assert(has_rotation == false); + has_rotation = true; + + supported_rotations = PLANE_TRANSFORM_NONE; + assert(info->flags & DRM_MODE_PROP_BITMASK); + + for (int k = 0; k < info->count_enums; k++) { + supported_rotations.u32 |= 1 << info->enums[k].value; + } + + assert(PLANE_TRANSFORM_IS_VALID(supported_rotations)); + + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + has_hardcoded_rotation = true; + hardcoded_rotation.u64 = props->prop_values[j]; + } - for (int j = 0; j < props->count_props; j++) { - props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); - if (props_info[j] == NULL) { + committed_rotation.u64 = props->prop_values[j]; + } else if (streq(info->name, "zpos")) { + assert(has_zpos == false); + has_zpos = true; + + if (info->flags & DRM_MODE_PROP_SIGNED_RANGE) { + min_zpos = *(int64_t *) (info->values + 0); + max_zpos = *(int64_t *) (info->values + 1); + committed_zpos = *(int64_t *) (props->prop_values + j); + assert(min_zpos <= max_zpos); + assert(min_zpos <= committed_zpos); + assert(committed_zpos <= max_zpos); + } else if (info->flags & DRM_MODE_PROP_RANGE) { + assert(info->values[0] < (uint64_t) INT64_MAX); + assert(info->values[1] < (uint64_t) INT64_MAX); + min_zpos = info->values[0]; + max_zpos = info->values[1]; + committed_zpos = props->prop_values[j]; + assert(min_zpos <= max_zpos); + } else { + ASSERT_MSG(info->flags && false, "Invalid property type for zpos property."); + } + + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + has_hardcoded_zpos = true; + assert(props->prop_values[j] < (uint64_t) INT64_MAX); + hardcoded_zpos = committed_zpos; + if (min_zpos != max_zpos) { + LOG_DEBUG( + "DRM plane minimum supported zpos does not equal maximum supported zpos, even though zpos is " + "immutable.\n" + ); + min_zpos = max_zpos = hardcoded_zpos; + } + } + } else if (streq(info->name, "SRC_X")) { + comitted_src_x = props->prop_values[j]; + } else if (streq(info->name, "SRC_Y")) { + comitted_src_y = props->prop_values[j]; + } else if (streq(info->name, "SRC_W")) { + comitted_src_w = props->prop_values[j]; + } else if (streq(info->name, "SRC_H")) { + comitted_src_h = props->prop_values[j]; + } else if (streq(info->name, "CRTC_X")) { + comitted_crtc_x = props->prop_values[j]; + } else if (streq(info->name, "CRTC_Y")) { + comitted_crtc_y = props->prop_values[j]; + } else if (streq(info->name, "CRTC_W")) { + comitted_crtc_w = props->prop_values[j]; + } else if (streq(info->name, "CRTC_H")) { + comitted_crtc_h = props->prop_values[j]; + } else if (streq(info->name, "IN_FORMATS")) { + drmModePropertyBlobRes *blob; + + blob = drmModeGetPropertyBlob(drm_fd, props->prop_values[j]); + if (blob == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); - for (int k = 0; k < (j-1); k++) - drmModeFreeProperty(props_info[j]); - free(props_info); - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - goto fail_free_planes; + LOG_ERROR( + "Couldn't get list of supported format modifiers for plane %u. drmModeGetPropertyBlob: %s\n", + plane_id, + strerror(ok) + ); + drmModeFreeProperty(info); + goto fail_free_props; } - if (strcmp(props_info[j]->name, "type") == 0) { - planes[i].type = 0; - for (int k = 0; k < props->count_props; k++) { - if (props->props[k] == props_info[j]->prop_id) { - planes[i].type = props->prop_values[k]; - break; - } + plane_out->supports_modifiers = true; + plane_out->supported_modified_formats_blob = memdup(blob->data, blob->length); + ASSERT_NOT_NULL(plane_out->supported_modified_formats_blob); + + drmModeFreePropertyBlob(blob); + } else if (streq(info->name, "alpha")) { + has_alpha = true; + assert(info->flags == DRM_MODE_PROP_RANGE); + assert(info->values[0] == 0); + assert(info->values[1] == 0xFFFF); + assert(props->prop_values[j] <= 0xFFFF); + + committed_alpha = (uint16_t) props->prop_values[j]; + } else if (streq(info->name, "pixel blend mode")) { + has_blend_mode = true; + assert(info->flags == DRM_MODE_PROP_ENUM); + + for (int i = 0; i < info->count_enums; i++) { + if (streq(info->enums[i].name, "None")) { + ASSERT_EQUALS(info->enums[i].value, kNone_DrmBlendMode); + supported_blend_modes[kNone_DrmBlendMode] = true; + } else if (streq(info->enums[i].name, "Pre-multiplied")) { + ASSERT_EQUALS(info->enums[i].value, kPremultiplied_DrmBlendMode); + supported_blend_modes[kPremultiplied_DrmBlendMode] = true; + } else if (streq(info->enums[i].name, "Coverage")) { + ASSERT_EQUALS(info->enums[i].value, kCoverage_DrmBlendMode); + supported_blend_modes[kCoverage_DrmBlendMode] = true; + } else { + LOG_DEBUG( + "Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", + info->enums[i].name, + (uint64_t) info->enums[i].value + ); } } - } - planes[i].plane = plane; - planes[i].props = props; - planes[i].props_info = props_info; - } + committed_blend_mode = props->prop_values[j]; + assert(committed_blend_mode >= 0 && committed_blend_mode <= kMax_DrmBlendMode); + assert(supported_blend_modes[committed_blend_mode]); + } - *planes_out = planes; - *n_planes_out = drmdev->plane_res->count_planes; +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(info->name, _name_str, ARRAY_SIZE(info->name)) == 0) { \ + ids._name = info->prop_id; \ + } else - return 0; + DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // do nothing + } +#undef CHECK_ASSIGN_PROPERTY_ID - fail_free_planes: - for (int i = 0; i < n_allocated_planes; i++) { - for (int j = 0; j < planes[i].props->count_props; j++) - drmModeFreeProperty(planes[i].props_info[j]); - free(planes[i].props_info); - drmModeFreeObjectProperties(planes[i].props); - drmModeFreePlane(planes[i].plane); + drmModeFreeProperty(info); } - free(planes); - - *planes_out = NULL; - *n_planes_out = 0; - return ok; -} + assert(has_type); + (void) has_type; -static int free_planes(struct drm_plane *planes, size_t n_planes) { - for (int i = 0; i < n_planes; i++) { - for (int j = 0; j < planes[i].props->count_props; j++) - drmModeFreeProperty(planes[i].props_info[j]); - free(planes[i].props_info); - drmModeFreeObjectProperties(planes[i].props); - drmModeFreePlane(planes[i].plane); + for (int i = 0; i < plane->count_formats; i++) { + for (int j = 0; j < PIXFMT_COUNT; j++) { + if (get_pixfmt_info(j)->drm_format == plane->formats[i]) { + supported_formats[j] = true; + break; + } + } } - free(planes); + bool has_format = false; + enum pixfmt format = PIXFMT_RGB565; + + // drmModeGetFB2 might not be present. + // If __attribute__((weak)) is supported by the compiler, we redefine it as + // weak above. + // If we don't have weak, we can't check for it here. +#ifdef HAVE_WEAK_DRM_MODE_GET_FB2 + if (drmModeGetFB2 && drmModeFreeFB2) { + struct drm_mode_fb2 *fb = (struct drm_mode_fb2 *) drmModeGetFB2(drm_fd, plane->fb_id); + if (fb != NULL) { + for (int i = 0; i < PIXFMT_COUNT; i++) { + if (get_pixfmt_info(i)->drm_format == fb->pixel_format) { + has_format = true; + format = i; + break; + } + } + drmModeFreeFB2((struct _drmModeFB2 *) fb); + } + } +#endif + + plane_out->id = plane->plane_id; + plane_out->possible_crtcs = plane->possible_crtcs; + plane_out->ids = ids; + plane_out->type = type; + plane_out->has_zpos = has_zpos; + plane_out->min_zpos = min_zpos; + plane_out->max_zpos = max_zpos; + plane_out->has_hardcoded_zpos = has_hardcoded_zpos; + plane_out->hardcoded_zpos = hardcoded_zpos; + plane_out->has_rotation = has_rotation; + plane_out->supported_rotations = supported_rotations; + plane_out->has_hardcoded_rotation = has_hardcoded_rotation; + plane_out->hardcoded_rotation = hardcoded_rotation; + memcpy(plane_out->supported_formats, supported_formats, sizeof supported_formats); + plane_out->has_alpha = has_alpha; + plane_out->has_blend_mode = has_blend_mode; + memcpy(plane_out->supported_blend_modes, supported_blend_modes, sizeof supported_blend_modes); + plane_out->committed_state.crtc_id = plane->crtc_id; + plane_out->committed_state.fb_id = plane->fb_id; + plane_out->committed_state.src_x = comitted_src_x; + plane_out->committed_state.src_y = comitted_src_y; + plane_out->committed_state.src_w = comitted_src_w; + plane_out->committed_state.src_h = comitted_src_h; + plane_out->committed_state.crtc_x = comitted_crtc_x; + plane_out->committed_state.crtc_y = comitted_crtc_y; + plane_out->committed_state.crtc_w = comitted_crtc_w; + plane_out->committed_state.crtc_h = comitted_crtc_h; + plane_out->committed_state.zpos = committed_zpos; + plane_out->committed_state.rotation = committed_rotation; + plane_out->committed_state.alpha = committed_alpha; + plane_out->committed_state.blend_mode = committed_blend_mode; + plane_out->committed_state.has_format = has_format; + plane_out->committed_state.format = format; + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); return 0; -} +fail_maybe_free_supported_modified_formats_blob: + if (plane_out->supported_modified_formats_blob != NULL) + free(plane_out->supported_modified_formats_blob); -float mode_get_vrefresh(const drmModeModeInfo *mode) { - return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); -} +fail_free_props: + drmModeFreeObjectProperties(props); -int drmdev_new_from_fd( - struct drmdev **drmdev_out, - int fd -) { - struct drmdev *drmdev; - int ok; +fail_free_plane: + drmModeFreePlane(plane); + return ok; +} - drmdev = calloc(1, sizeof *drmdev); - if (drmdev == NULL) { - return ENOMEM; +static void free_plane(UNUSED struct drm_plane *plane) { + if (plane->supported_modified_formats_blob != NULL) { + free(plane->supported_modified_formats_blob); } +} - drmdev->fd = fd; - - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); +static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { + struct drm_plane *planes; + int ok; + + planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); + if (planes == NULL) { + *planes_out = NULL; + return ENOMEM; + } + + for (int i = 0; i < drmdev->plane_res->count_planes; i++) { + ok = fetch_plane(drmdev->fd, drmdev->plane_res->planes[i], planes + i); + if (ok != 0) { + for (int j = 0; j < i; j++) { + free_plane(planes + i); + } + free(planes); + return ENOMEM; + } + + ASSERT_MSG(planes[0].has_zpos == planes[i].has_zpos, "If one plane has a zpos property, all planes need to have one."); + } + + *planes_out = planes; + *n_planes_out = drmdev->plane_res->count_planes; + + return 0; +} + +static void free_planes(struct drm_plane *planes, size_t n_planes) { + for (int i = 0; i < n_planes; i++) { + free_plane(planes + i); + } + free(planes); +} + +static void assert_rotations_work() { + assert(PLANE_TRANSFORM_ROTATE_0.rotate_0 == true); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_0.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_0.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_90.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_90 == true); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_90.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_90.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_180.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_180 == true); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_180.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_180.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_270.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_270 == true); + assert(PLANE_TRANSFORM_ROTATE_270.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_270.reflect_y == false); + + assert(PLANE_TRANSFORM_REFLECT_X.rotate_0 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_90 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_180 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_270 == false); + assert(PLANE_TRANSFORM_REFLECT_X.reflect_x == true); + assert(PLANE_TRANSFORM_REFLECT_X.reflect_y == false); + + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_0 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_90 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_180 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_270 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.reflect_x == false); + assert(PLANE_TRANSFORM_REFLECT_Y.reflect_y == true); + + drm_plane_transform_t r = PLANE_TRANSFORM_NONE; + + r.rotate_0 = true; + r.reflect_x = true; + assert(r.u32 == (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); + + r.u32 = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_Y; + assert(r.rotate_0 == false); + assert(r.rotate_90 == true); + assert(r.rotate_180 == false); + assert(r.rotate_270 == false); + assert(r.reflect_x == false); + assert(r.reflect_y == true); + (void) r; +} + +static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { + int ok; + + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); if (ok < 0) { ok = errno; - perror("[modesetting] Could not set DRM client universal planes capable. drmSetClientCap"); - goto fail_free_drmdev; + LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; } - - ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_ATOMIC, 1); + +#ifdef USE_LEGACY_KMS + *supports_atomic_modesetting = false; +#else + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); if ((ok < 0) && (errno == EOPNOTSUPP)) { - drmdev->supports_atomic_modesetting = false; + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = false; + } } else if (ok < 0) { ok = errno; - perror("[modesetting] Could not set DRM client atomic capable. drmSetClientCap"); + LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; + } else { + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = true; + } + } +#endif + + return 0; +} + +struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata) { + struct gbm_device *gbm_device; + struct drmdev *drmdev; + uint64_t cap; + bool supports_atomic_modesetting; + bool supports_dumb_buffers; + int ok, master_fd, event_fd; + + assert_rotations_work(); + + drmdev = malloc(sizeof *drmdev); + if (drmdev == NULL) { + return NULL; + } + + master_fd = fd; + + ok = set_drm_client_caps(fd, &supports_atomic_modesetting); + if (ok != 0) { goto fail_free_drmdev; + } + + cap = 0; + ok = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ok < 0) { + supports_dumb_buffers = false; } else { - drmdev->supports_atomic_modesetting = true; + supports_dumb_buffers = !!cap; } - drmdev->res = drmModeGetResources(drmdev->fd); + drmdev->res = drmModeGetResources(fd); if (drmdev->res == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device resources. drmModeGetResources"); + LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); goto fail_free_drmdev; } - drmdev->plane_res = drmModeGetPlaneResources(drmdev->fd); + drmdev->plane_res = drmModeGetPlaneResources(fd); if (drmdev->plane_res == NULL) { ok = errno; - perror("[modesetting] Could not get DRM device planes resources. drmModeGetPlaneResources"); + LOG_ERROR("Could not get DRM device planes resources. drmModeGetPlaneResources: %s\n", strerror(ok)); goto fail_free_resources; } + drmdev->fd = fd; + ok = fetch_connectors(drmdev, &drmdev->connectors, &drmdev->n_connectors); if (ok != 0) { goto fail_free_plane_resources; @@ -440,829 +1022,1779 @@ int drmdev_new_from_fd( goto fail_free_crtcs; } - *drmdev_out = drmdev; + gbm_device = gbm_create_device(drmdev->fd); + if (gbm_device == NULL) { + LOG_ERROR("Could not create GBM device.\n"); + goto fail_free_planes; + } - return 0; + event_fd = epoll_create1(EPOLL_CLOEXEC); + if (event_fd < 0) { + LOG_ERROR("Could not create modesetting epoll instance.\n"); + goto fail_destroy_gbm_device; + } + ok = epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &(struct epoll_event){ .events = EPOLLIN | EPOLLPRI, .data.ptr = NULL }); + if (ok != 0) { + LOG_ERROR("Could not add DRM file descriptor to epoll instance.\n"); + goto fail_close_event_fd; + } - fail_free_crtcs: + pthread_mutex_init(&drmdev->mutex, get_default_mutex_attrs()); + drmdev->n_refs = REFCOUNT_INIT_1; + drmdev->fd = fd; + drmdev->supports_atomic_modesetting = supports_atomic_modesetting; + drmdev->supports_dumb_buffers = supports_dumb_buffers; + drmdev->gbm_device = gbm_device; + drmdev->event_fd = event_fd; + memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); + drmdev->master_fd = master_fd; + drmdev->master_fd_metadata = fd_metadata; + drmdev->interface = *interface; + drmdev->userdata = userdata; + list_inithead(&drmdev->fbs); + return drmdev; + +fail_close_event_fd: + close(event_fd); + +fail_destroy_gbm_device: + gbm_device_destroy(gbm_device); + +fail_free_planes: + free_planes(drmdev->planes, drmdev->n_planes); + +fail_free_crtcs: free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - fail_free_encoders: +fail_free_encoders: free_encoders(drmdev->encoders, drmdev->n_encoders); - fail_free_connectors: +fail_free_connectors: free_connectors(drmdev->connectors, drmdev->n_connectors); - fail_free_plane_resources: +fail_free_plane_resources: drmModeFreePlaneResources(drmdev->plane_res); - fail_free_resources: +fail_free_resources: drmModeFreeResources(drmdev->res); - fail_free_drmdev: + // fail_close_master_fd: + // interface->close(master_fd, NULL, userdata); + +fail_free_drmdev: free(drmdev); - return ok; + return NULL; } -int drmdev_new_from_path( - struct drmdev **drmdev_out, - const char *path -) { - int ok, fd; +struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata) { + struct drmdev *drmdev; + void *fd_metadata; + int fd; + + ASSERT_NOT_NULL(path); + ASSERT_NOT_NULL(interface); - fd = open(path, O_RDWR); + fd = interface->open(path, O_RDWR, &fd_metadata, userdata); if (fd < 0) { - perror("[modesetting] Could not open DRM device. open"); - return errno; + LOG_ERROR("Could not open DRM device. interface->open: %s\n", strerror(errno)); + return NULL; } - ok = drmdev_new_from_fd(drmdev_out, fd); - if (ok != 0) { + drmdev = drmdev_new_from_interface_fd(fd, fd_metadata, interface, userdata); + if (drmdev == NULL) { close(fd); - return ok; + return NULL; } - return 0; + return drmdev; +} + +static void drmdev_destroy(struct drmdev *drmdev) { + assert(refcount_is_zero(&drmdev->n_refs)); + + drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); + close(drmdev->event_fd); + gbm_device_destroy(drmdev->gbm_device); + free_planes(drmdev->planes, drmdev->n_planes); + free_crtcs(drmdev->crtcs, drmdev->n_crtcs); + free_encoders(drmdev->encoders, drmdev->n_encoders); + free_connectors(drmdev->connectors, drmdev->n_connectors); + drmModeFreePlaneResources(drmdev->plane_res); + drmModeFreeResources(drmdev->res); + free(drmdev); +} + +DEFINE_REF_OPS(drmdev, n_refs) + +int drmdev_get_fd(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + return drmdev->master_fd; +} + +int drmdev_get_event_fd(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + return drmdev->master_fd; } -int drmdev_configure( +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) { + return drmdev->supports_dumb_buffers; +} + +int drmdev_create_dumb_buffer( struct drmdev *drmdev, - uint32_t connector_id, - uint32_t encoder_id, - uint32_t crtc_id, - const drmModeModeInfo *mode + int width, + int height, + int bpp, + uint32_t *gem_handle_out, + uint32_t *pitch_out, + size_t *size_out ) { - struct drm_connector *connector; - struct drm_encoder *encoder; - struct drm_crtc *crtc; - uint32_t mode_id; + struct drm_mode_create_dumb create_req; int ok; - drmdev_lock(drmdev); + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(gem_handle_out); + ASSERT_NOT_NULL(pitch_out); + ASSERT_NOT_NULL(size_out); - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->connector->connector_id == connector_id) { - break; - } + memset(&create_req, 0, sizeof create_req); + create_req.width = width; + create_req.height = height; + create_req.bpp = bpp; + create_req.flags = 0; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not create dumb buffer. ioctl: %s\n", strerror(errno)); + goto fail_return_ok; } - if (connector == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + *gem_handle_out = create_req.handle; + *pitch_out = create_req.pitch; + *size_out = create_req.size; + return 0; + +fail_return_ok: + return ok; +} + +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle) { + struct drm_mode_destroy_dumb destroy_req; + int ok; + + ASSERT_NOT_NULL(drmdev); + + memset(&destroy_req, 0, sizeof destroy_req); + destroy_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + if (ok < 0) { + LOG_ERROR("Could not destroy dumb buffer. ioctl: %s\n", strerror(errno)); } +} - for_each_encoder_in_drmdev(drmdev, encoder) { - if (encoder->encoder->encoder_id == encoder_id) { - break; - } +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size) { + struct drm_mode_map_dumb map_req; + void *map; + int ok; + + ASSERT_NOT_NULL(drmdev); + + memset(&map_req, 0, sizeof map_req); + map_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); + if (ok < 0) { + LOG_ERROR("Could not prepare dumb buffer mmap. ioctl: %s\n", strerror(errno)); + return NULL; } - if (encoder == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev->fd, map_req.offset); + if (map == MAP_FAILED) { + LOG_ERROR("Could not mmap dumb buffer. mmap: %s\n", strerror(errno)); + return NULL; + } + + return map; +} + +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size) { + int ok; + + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(map); + (void) drmdev; + + ok = munmap(map, size); + if (ok < 0) { + LOG_ERROR("Couldn't unmap dumb buffer. munmap: %s\n", strerror(errno)); } +} +static void +drmdev_on_page_flip_locked(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, unsigned int crtc_id, void *userdata) { + struct kms_req_builder *builder; + struct drm_crtc *crtc; + struct kms_req **last_flipped; + struct kms_req *req; + struct drmdev *drmdev; + + ASSERT_NOT_NULL(userdata); + builder = userdata; + req = userdata; + + (void) fd; + (void) sequence; + (void) crtc_id; + + drmdev = builder->drmdev; for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->crtc->crtc_id == crtc_id) { + if (crtc->id == crtc_id) { break; } } - if (crtc == NULL) { - drmdev_unlock(drmdev); - return EINVAL; - } + ASSERT_NOT_NULL_MSG(crtc, "Invalid CRTC id"); - mode_id = 0; - if (drmdev->supports_atomic_modesetting) { - ok = drmModeCreatePropertyBlob(drmdev->fd, mode, sizeof(*mode), &mode_id); - if (ok < 0) { - perror("[modesetting] Could not create property blob for DRM mode. drmModeCreatePropertyBlob"); - drmdev_unlock(drmdev); - return errno; - } + if (drmdev->per_crtc_state[crtc->index].scanout_callback != NULL) { + uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; + drmdev->per_crtc_state[crtc->index].scanout_callback(drmdev, vblank_ns, drmdev->per_crtc_state[crtc->index].userdata); - if (drmdev->selected_mode != NULL) { - ok = drmModeDestroyPropertyBlob(drmdev->fd, drmdev->selected_mode_blob_id); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not destroy old DRM mode property blob. drmModeDestroyPropertyBlob"); - drmModeDestroyPropertyBlob(drmdev->fd, mode_id); - drmdev_unlock(drmdev); - return ok; - } - } + // clear the scanout callback + drmdev->per_crtc_state[crtc->index].scanout_callback = NULL; + drmdev->per_crtc_state[crtc->index].destroy_callback = NULL; + drmdev->per_crtc_state[crtc->index].userdata = NULL; + } + + last_flipped = &drmdev->per_crtc_state[crtc->index].last_flipped; + if (*last_flipped != NULL) { + /// TODO: Remove this if we ever cache KMS reqs. + /// FIXME: This will fail if we're using blocking commits. + // assert(refcount_is_one(&((struct kms_req_builder*) *last_flipped)->n_refs)); } - drmdev->selected_connector = connector; - drmdev->selected_encoder = encoder; - drmdev->selected_crtc = crtc; - drmdev->selected_mode = mode; - drmdev->selected_mode_blob_id = mode_id; + kms_req_swap_ptrs(last_flipped, req); + kms_req_unref(req); +} + +static int drmdev_on_modesetting_fd_ready_locked(struct drmdev *drmdev) { + int ok; - drmdev->is_configured = true; + static drmEventContext ctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .vblank_handler = NULL, + .page_flip_handler = NULL, + .page_flip_handler2 = drmdev_on_page_flip_locked, + .sequence_handler = NULL, + }; - drmdev_unlock(drmdev); + ok = drmHandleEvent(drmdev->master_fd, &ctx); + if (ok != 0) { + return EIO; + } return 0; } -static struct drm_plane *get_plane_by_id( - struct drmdev *drmdev, - uint32_t plane_id -) { - struct drm_plane *plane; +int drmdev_on_event_fd_ready(struct drmdev *drmdev) { + struct epoll_event events[16]; + int ok, n_events; - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].plane->plane_id == plane_id) { - plane = drmdev->planes + i; + ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); + + while (1) { + ok = epoll_wait(drmdev->event_fd, events, ARRAY_SIZE(events), 0); + if ((ok < 0) && (errno == EINTR)) { + // retry + continue; + } else if (ok < 0) { + ok = errno; + LOG_ERROR("Could read kernel modesetting events. epoll_wait: %s\n", strerror(ok)); + goto fail_unlock; + } else { break; } } - return plane; + n_events = ok; + for (int i = 0; i < n_events; i++) { + // currently this could only be the root drmdev fd. + ASSERT_EQUALS(events[i].data.ptr, NULL); + ok = drmdev_on_modesetting_fd_ready_locked(drmdev); + if (ok != 0) { + goto fail_unlock; + } + } + + drmdev_unlock(drmdev); + + return 0; + +fail_unlock: + drmdev_unlock(drmdev); + return ok; } -static int get_plane_property_index_by_name( - struct drm_plane *plane, - const char *property_name -) { - if (plane == NULL) { - return -1; - } +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + return drmdev->gbm_device; +} - int prop_index = -1; - for (int i = 0; i < plane->props->count_props; i++) { - if (strcmp(plane->props_info[i]->name, property_name) == 0) { - prop_index = i; - break; - } +int drmdev_get_last_vblank_locked(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { + int ok; + + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(last_vblank_ns_out); + + ok = drmCrtcGetSequence(drmdev->fd, crtc_id, NULL, last_vblank_ns_out); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not get next vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); + return ok; } - return prop_index; + return 0; } -int drmdev_plane_get_type( - struct drmdev *drmdev, - uint32_t plane_id -) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return -1; - } +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { + int ok; + + drmdev_lock(drmdev); + ok = drmdev_get_last_vblank_locked(drmdev, crtc_id, last_vblank_ns_out); + drmdev_unlock(drmdev); - return plane->type; + return ok; } -int drmdev_plane_supports_setting_rotation_value( +uint32_t drmdev_add_fb_multiplanar_locked( struct drmdev *drmdev, - uint32_t plane_id, - int drm_rotation, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const uint32_t bo_handles[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "rotation"); - if (prop_index == -1) { - *result = false; - return 0; - } + struct drm_fb *fb; + uint32_t fb_id; + int ok; - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; + /// TODO: Code in https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/drm_framebuffer.c#L257 + /// assumes handles, pitches, offsets and modifiers for unused planes are zero. Make sure that's the + /// case here. + ASSERT_NOT_NULL(drmdev); + assert(width > 0 && height > 0); + assert(bo_handles[0] != 0); + assert(pitches[0] != 0); + + fb = malloc(sizeof *fb); + if (fb == NULL) { return 0; } - if (!(plane->props_info[prop_index]->flags & DRM_MODE_PROP_BITMASK)) { - *result = false; - return 0; + list_inithead(&fb->entry); + fb->id = 0; + fb->width = width; + fb->height = height; + fb->format = pixel_format; + fb->has_modifier = has_modifiers; + fb->modifier = modifiers[0]; + fb->flags = 0; + memcpy(fb->handles, bo_handles, sizeof(fb->handles)); + memcpy(fb->pitches, pitches, sizeof(fb->pitches)); + memcpy(fb->offsets, offsets, sizeof(fb->offsets)); + + fb_id = 0; + if (has_modifiers) { + ok = drmModeAddFB2WithModifiers( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + bo_handles, + pitches, + offsets, + modifiers, + &fb_id, + DRM_MODE_FB_MODIFIERS + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); + goto fail_free_fb; + } + } else { + ok = drmModeAddFB2(drmdev->fd, width, height, get_pixfmt_info(pixel_format)->drm_format, bo_handles, pitches, offsets, &fb_id, 0); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); + goto fail_free_fb; + } } - uint64_t value = drm_rotation; + fb->id = fb_id; + list_add(&fb->entry, &drmdev->fbs); - for (int i = 0; i < plane->props_info[prop_index]->count_enums; i++) { - value &= ~(1 << plane->props_info[prop_index]->enums[i].value); - } + assert(fb_id != 0); + return fb_id; - *result = !value; +fail_free_fb: + free(fb); return 0; } -int drmdev_plane_supports_setting_zpos_value( +uint32_t drmdev_add_fb_multiplanar( struct drmdev *drmdev, - uint32_t plane_id, - int64_t zpos, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const uint32_t bo_handles[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - *result = false; - return 0; - } - - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; - } + uint32_t fb; - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; - } + drmdev_lock(drmdev); - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t min = *((int64_t*) (plane->props_info[prop_index]->values + 0)); - int64_t max = *((int64_t*) (plane->props_info[prop_index]->values + 1)); + fb = drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; - } - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t min = plane->props_info[prop_index]->values[0]; - uint64_t max = plane->props_info[prop_index]->values[1]; + drmdev_unlock(drmdev); - if ((min <= zpos) && (max >= zpos)) { - *result = true; - return 0; - } else { - *result = false; - return 0; - } - } else { - *result = false; - return 0; - } - - return 0; + return fb; } -int drmdev_plane_get_min_zpos_value( +uint32_t drmdev_add_fb_locked( struct drmdev *drmdev, - uint32_t plane_id, - int64_t *min_zpos_out + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handle, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - return EINVAL; - } + return drmdev_add_fb_multiplanar_locked( + drmdev, + width, + height, + pixel_format, + (uint32_t[4]){ bo_handle, 0 }, + (uint32_t[4]){ pitch, 0 }, + (uint32_t[4]){ offset, 0 }, + has_modifier, + (const uint64_t[4]){ modifier, 0 } + ); +} - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; - } +uint32_t drmdev_add_fb( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handle, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier +) { + return drmdev_add_fb_multiplanar( + drmdev, + width, + height, + pixel_format, + (uint32_t[4]){ bo_handle, 0 }, + (uint32_t[4]){ pitch, 0 }, + (uint32_t[4]){ offset, 0 }, + has_modifier, + (const uint64_t[4]){ modifier, 0 } + ); +} - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t min = *((int64_t*) (plane->props_info[prop_index]->values + 0)); - - *min_zpos_out = min; - return 0; - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t min = plane->props_info[prop_index]->values[0]; +uint32_t drmdev_add_fb_from_dmabuf_locked( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fd, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier +) { + uint32_t bo_handle; + int ok; - *min_zpos_out = (int64_t) min; + ok = drmPrimeFDToHandle(drmdev->fd, prime_fd, &bo_handle); + if (ok < 0) { + LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); return 0; - } else { - return EINVAL; } - - return EINVAL; + + return drmdev_add_fb_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); } -int drmdev_plane_get_max_zpos_value( +uint32_t drmdev_add_fb_from_dmabuf( struct drmdev *drmdev, - uint32_t plane_id, - int64_t *max_zpos_out + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fd, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; - } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - return EINVAL; - } + uint32_t fb; - if (plane->props_info[prop_index]->count_values != 2) { - return EINVAL; - } + drmdev_lock(drmdev); - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_SIGNED_RANGE) { - int64_t max = *((int64_t*) (plane->props_info[prop_index]->values + 1)); - - *max_zpos_out = max; - return 0; - } else if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_RANGE) { - uint64_t max = plane->props_info[prop_index]->values[1]; + fb = drmdev_add_fb_from_dmabuf_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); - *max_zpos_out = (int64_t) max; - return 0; - } else { - return EINVAL; + drmdev_unlock(drmdev); + + return fb; +} + +uint32_t drmdev_add_fb_from_dmabuf_multiplanar_locked( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const int prime_fds[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +) { + uint32_t bo_handles[4] = { 0 }; + int ok; + + for (int i = 0; (i < 4) && (prime_fds[i] != 0); i++) { + ok = drmPrimeFDToHandle(drmdev->fd, prime_fds[i], bo_handles + i); + if (ok < 0) { + LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); + return 0; + } } - - return EINVAL; + + return drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); } -int drmdev_plane_supports_setting_zpos( +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( struct drmdev *drmdev, - uint32_t plane_id, - bool *result + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const int prime_fds[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] ) { - struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); - if (plane == NULL) { - return EINVAL; + uint32_t fb; + + drmdev_lock(drmdev); + + fb = drmdev_add_fb_from_dmabuf_multiplanar_locked( + drmdev, + width, + height, + pixel_format, + prime_fds, + pitches, + offsets, + has_modifiers, + modifiers + ); + + drmdev_unlock(drmdev); + + return fb; +} + +int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id) { + int ok; + + list_for_each_entry(struct drm_fb, fb, &drmdev->fbs, entry) { + if (fb->id == fb_id) { + list_del(&fb->entry); + free(fb); + break; + } } - - int prop_index = get_plane_property_index_by_name(plane, "zpos"); - if (prop_index == -1) { - *result = false; - return 0; + + ok = drmModeRmFB(drmdev->fd, fb_id); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not remove DRM framebuffer. drmModeRmFB: %s\n", strerror(ok)); + return ok; } - if (plane->props_info[prop_index]->count_values != 2) { - *result = false; - return 0; + return 0; +} + +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id) { + int ok; + + drmdev_lock(drmdev); + + ok = drmdev_rm_fb_locked(drmdev, fb_id); + + drmdev_unlock(drmdev); + + return ok; +} + +bool drmdev_can_modeset(struct drmdev *drmdev) { + bool can_modeset; + + ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); + + can_modeset = drmdev->master_fd > 0; + + drmdev_unlock(drmdev); + + return can_modeset; +} + +void drmdev_suspend(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); + + if (drmdev->master_fd <= 0) { + LOG_ERROR("drmdev_suspend was called, but drmdev is already suspended\n"); + drmdev_unlock(drmdev); + return; } - if (plane->props_info[prop_index]->flags & DRM_MODE_PROP_IMMUTABLE) { - *result = false; - return 0; + drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); + drmdev->master_fd = -1; + drmdev->master_fd_metadata = NULL; + + drmdev_unlock(drmdev); +} + +int drmdev_resume(struct drmdev *drmdev) { + drmDevicePtr device; + void *fd_metadata; + int ok, master_fd; + + ASSERT_NOT_NULL(drmdev); + + drmdev_lock(drmdev); + + if (drmdev->master_fd > 0) { + ok = EINVAL; + LOG_ERROR("drmdev_resume was called, but drmdev is already resumed\n"); + goto fail_unlock; } - if (!(plane->props_info[prop_index]->flags & (DRM_MODE_PROP_RANGE | DRM_MODE_PROP_SIGNED_RANGE))) { - *result = false; - return 0; + ok = drmGetDevice(drmdev->fd, &device); + if (ok < 0) { + ok = errno; + LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); + goto fail_unlock; + } + + ok = drmdev->interface.open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &fd_metadata, drmdev->userdata); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Couldn't open DRM device.\n"); + goto fail_free_device; + } + + master_fd = ok; + + drmFreeDevice(&device); + + ok = set_drm_client_caps(master_fd, NULL); + if (ok != 0) { + goto fail_close_device; + } + + drmdev->master_fd = master_fd; + drmdev->master_fd_metadata = fd_metadata; + drmdev_unlock(drmdev); + return 0; + +fail_close_device: + drmdev->interface.close(master_fd, fd_metadata, drmdev->userdata); + goto fail_unlock; + +fail_free_device: + drmFreeDevice(&device); + +fail_unlock: + drmdev_unlock(drmdev); + return ok; +} + +int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos) { + int ok = drmModeMoveCursor(drmdev->master_fd, crtc_id, pos.x, pos.y); + if (ok < 0) { + LOG_ERROR("Couldn't move mouse cursor. drmModeMoveCursor: %s\n", strerror(-ok)); + return -ok; } - *result = true; return 0; } -int drmdev_new_atomic_req( +static void drmdev_set_scanout_callback_locked( struct drmdev *drmdev, - struct drmdev_atomic_req **req_out + uint32_t crtc_id, + kms_scanout_cb_t scanout_callback, + void *userdata, + void_callback_t destroy_callback ) { - struct drmdev_atomic_req *req; + struct drm_crtc *crtc; + + ASSERT_NOT_NULL(drmdev); + + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { + break; + } + } + + ASSERT_NOT_NULL_MSG(crtc, "Could not find CRTC with given id."); + + // If there's already a scanout callback configured, this is probably a state machine error. + // The scanout callback is configured in kms_req_commit and is cleared after it was called. + // So if this is called again this mean kms_req_commit is called but the previous frame wasn't committed yet. + ASSERT_EQUALS_MSG( + drmdev->per_crtc_state[crtc->index].scanout_callback, + NULL, + "There's already a scanout callback configured for this CRTC." + ); + drmdev->per_crtc_state[crtc->index].scanout_callback = scanout_callback; + drmdev->per_crtc_state[crtc->index].destroy_callback = destroy_callback; + drmdev->per_crtc_state[crtc->index].userdata = userdata; +} + +UNUSED static struct drm_plane *get_plane_by_id(struct drmdev *drmdev, uint32_t plane_id) { struct drm_plane *plane; - if (drmdev->supports_atomic_modesetting == false) { - return EOPNOTSUPP; + plane = NULL; + for (int i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes[i].id == plane_id) { + plane = drmdev->planes + i; + break; + } } - req = calloc(1, sizeof *req); - if (req == NULL) { - return ENOMEM; + return plane; +} + +struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { + bool found = connector == NULL; + for (size_t i = 0; i < drmdev->n_connectors; i++) { + if (drmdev->connectors + i == connector) { + found = true; + } else if (found) { + return drmdev->connectors + i; + } } - req->drmdev = drmdev; + return NULL; +} - req->atomic_req = drmModeAtomicAlloc(); - if (req->atomic_req == NULL) { - free(req); - return ENOMEM; +struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { + bool found = encoder == NULL; + for (size_t i = 0; i < drmdev->n_encoders; i++) { + if (drmdev->encoders + i == encoder) { + found = true; + } else if (found) { + return drmdev->encoders + i; + } } - req->available_planes = PSET_INITIALIZER_STATIC(req->available_planes_storage, 32); + return NULL; +} - for_each_plane_in_drmdev(drmdev, plane) { - if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - pset_put(&req->available_planes, plane); +struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { + bool found = crtc == NULL; + for (size_t i = 0; i < drmdev->n_crtcs; i++) { + if (drmdev->crtcs + i == crtc) { + found = true; + } else if (found) { + return drmdev->crtcs + i; } } - *req_out = req; - - return 0; + return NULL; } -void drmdev_destroy_atomic_req( - struct drmdev_atomic_req *req -) { - drmModeAtomicFree(req->atomic_req); - free(req); +struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { + bool found = plane == NULL; + for (size_t i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes + i == plane) { + found = true; + } else if (found) { + return drmdev->planes + i; + } + } + + return NULL; } -int drmdev_atomic_req_put_connector_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value +drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { + bool found = mode == NULL; + for (int i = 0; i < connector->variable_state.n_modes; i++) { + if (connector->variable_state.modes + i == mode) { + found = true; + } else if (found) { + return connector->variable_state.modes + i; + } + } + + return NULL; +} + +#ifdef DEBUG_DRM_PLANE_ALLOCATIONS + #define LOG_DRM_PLANE_ALLOCATION_DEBUG LOG_DEBUG +#else + #define LOG_DRM_PLANE_ALLOCATION_DEBUG(...) +#endif + +static bool +check_modified_format_supported(UNUSED struct drm_plane *plane, UNUSED int index, enum pixfmt format, uint64_t modifier, void *userdata) { + struct { + enum pixfmt format; + uint64_t modifier; + bool found; + } *context = userdata; + + if (format == context->format && modifier == context->modifier) { + context->found = true; + return false; + } else { + return true; + } +} + +static bool plane_qualifies( + // clang-format off + struct drm_plane *plane, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on ) { - int ok; + LOG_DRM_PLANE_ALLOCATION_DEBUG(" checking if plane with id %" PRIu32 " qualifies...\n", plane->id); + + if (plane->type == kPrimary_DrmPlaneType) { + if (!allow_primary) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is primary but allow_primary is false\n"); + return false; + } + } else if (plane->type == kOverlay_DrmPlaneType) { + if (!allow_overlay) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is overlay but allow_overlay is false\n"); + return false; + } + } else if (plane->type == kCursor_DrmPlaneType) { + if (!allow_cursor) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is cursor but allow_cursor is false\n"); + return false; + } + } else { + ASSERT(false); + } - drmdev_lock(req->drmdev); + if (has_modifier) { + if (!plane->supported_modified_formats_blob) { + // return false if we want a modified format but the plane doesn't support modified formats + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: framebuffer has modifier %" PRIu64 " but plane does not support modified formats\n", + modifier + ); + return false; + } - for (int i = 0; i < req->drmdev->selected_connector->props->count_props; i++) { - drmModePropertyRes *prop = req->drmdev->selected_connector->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - req->drmdev->selected_connector->connector->connector_id, - prop->prop_id, value + struct { + enum pixfmt format; + uint64_t modifier; + bool found; + } context = { + .format = format, + .modifier = modifier, + .found = false, + }; + + // Check if the requested format & modifier is supported. + drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); + + // Otherwise fail. + if (!context.found) { + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: plane does not support the modified format %s, %" PRIu64 ".\n", + get_pixfmt_info(format)->name, + modifier ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add connector property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; - } - drmdev_unlock(req->drmdev); - return 0; + // not found in the supported modified format list + return false; + } + } else { + // we don't want a modified format, return false if the format is not in the list + // of supported (unmodified) formats + if (!plane->supported_formats[format]) { + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: plane does not support the (unmodified) format %s.\n", + get_pixfmt_info(format)->name + ); + return false; } } - drmdev_unlock(req->drmdev); - return EINVAL; + if (has_zpos) { + if (!plane->has_zpos) { + // return false if we want a zpos but the plane doesn't support one + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: zpos constraints specified but plane doesn't have a zpos property.\n"); + return false; + } else if (zpos_lower_limit > plane->max_zpos || zpos_upper_limit < plane->min_zpos) { + // return false if the zpos we want is outside the supported range of the plane + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane limits cannot satisfy the specified zpos constraints.\n"); + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " plane zpos range: %" PRIi64 " <= zpos <= %" PRIi64 ", given zpos constraints: %" PRIi64 " <= zpos <= %" PRIi64 ".\n", + plane->min_zpos, + plane->max_zpos, + zpos_lower_limit, + zpos_upper_limit + ); + return false; + } + } + if (has_id_range && plane->id < id_lower_limit) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane id does not satisfy the given plane id constrains.\n"); + LOG_DRM_PLANE_ALLOCATION_DEBUG(" plane id: %" PRIu32 ", plane id lower limit: %" PRIu32 "\n", plane->id, id_lower_limit); + return false; + } + if (has_rotation) { + if (!plane->has_rotation) { + // return false if the plane doesn't support rotation + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: explicit rotation requested but plane has no rotation property.\n"); + return false; + } else if (plane->has_hardcoded_rotation && plane->hardcoded_rotation.u32 != rotation.u32) { + // return false if the plane has a hardcoded rotation and the rotation we want + // is not the hardcoded one + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane has hardcoded rotation that doesn't match the requested rotation.\n" + ); + return false; + } else if (rotation.u32 & ~plane->supported_rotations.u32) { + // return false if we can't construct the rotation using the rotation + // bits that are supported by the plane + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: requested rotation is not supported by the plane.\n"); + return false; + } + } + + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does qualify.\n"); + return true; } -int drmdev_atomic_req_put_crtc_property( - struct drmdev_atomic_req *req, - const char *name, - uint64_t value +static struct drm_plane *allocate_plane( + // clang-format off + struct kms_req_builder *builder, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on ) { - int ok; - - drmdev_lock(req->drmdev); - - for (int i = 0; i < req->drmdev->selected_crtc->props->count_props; i++) { - drmModePropertyRes *prop = req->drmdev->selected_crtc->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - req->drmdev->selected_crtc->crtc->crtc_id, - prop->prop_id, - value + for (int i = 0; i < BITSET_SIZE(builder->available_planes); i++) { + struct drm_plane *plane = builder->drmdev->planes + i; + + if (BITSET_TEST(builder->available_planes, i)) { + // find out if the plane matches our criteria + bool qualifies = plane_qualifies( + plane, + allow_primary, + allow_overlay, + allow_cursor, + format, + has_modifier, + modifier, + has_zpos, + zpos_lower_limit, + zpos_upper_limit, + has_rotation, + rotation, + has_id_range, + id_lower_limit ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add crtc property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; + + // if it doesn't, look for the next one + if (!qualifies) { + continue; } - - drmdev_unlock(req->drmdev); - return 0; + + // we found one, mark it as used and return it + BITSET_CLEAR(builder->available_planes, i); + return plane; } } - drmdev_unlock(req->drmdev); - return EINVAL; + // we didn't find an available plane matching our criteria + return NULL; } -int drmdev_atomic_req_put_plane_property( - struct drmdev_atomic_req *req, - uint32_t plane_id, - const char *name, - uint64_t value -) { +static void release_plane(struct kms_req_builder *builder, uint32_t plane_id) { struct drm_plane *plane; - int ok; - - drmdev_lock(req->drmdev); + int index; - plane = NULL; - for (int i = 0; i < req->drmdev->n_planes; i++) { - if (req->drmdev->planes[i].plane->plane_id == plane_id) { - plane = req->drmdev->planes + i; + index = 0; + for_each_plane_in_drmdev(builder->drmdev, plane) { + if (plane->id == plane_id) { break; } + index++; } if (plane == NULL) { - drmdev_unlock(req->drmdev); - return EINVAL; - } - - for (int i = 0; i < plane->props->count_props; i++) { - drmModePropertyRes *prop; - - prop = plane->props_info[i]; - - if (strcmp(prop->name, name) == 0) { - ok = drmModeAtomicAddProperty( - req->atomic_req, - plane_id, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not add plane property to atomic request. drmModeAtomicAddProperty"); - drmdev_unlock(req->drmdev); - return ok; - } - - drmdev_unlock(req->drmdev); - return 0; - } + LOG_ERROR("Could not release invalid plane %" PRIu32 ".\n", plane_id); + return; } - drmdev_unlock(req->drmdev); - return EINVAL; + assert(!BITSET_TEST(builder->available_planes, index)); + BITSET_SET(builder->available_planes, index); } -int drmdev_atomic_req_put_modeset_props( - struct drmdev_atomic_req *req, - uint32_t *flags -) { - struct drmdev_atomic_req *augment; - int ok; +struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id) { + struct kms_req_builder *builder; + drmModeAtomicReq *req; + struct drm_crtc *crtc; + int64_t min_zpos; + bool supports_atomic_modesetting; - ok = drmdev_new_atomic_req(req->drmdev, &augment); - if (ok != 0) { - return ok; - } + ASSERT_NOT_NULL(drmdev); + assert(crtc_id != 0 && crtc_id != 0xFFFFFFFF); - ok = drmdev_atomic_req_put_connector_property(req, "CRTC_ID", req->drmdev->selected_crtc->crtc->crtc_id); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + drmdev_lock(drmdev); + + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == crtc_id) { + break; + } } - ok = drmdev_atomic_req_put_crtc_property(req, "MODE_ID", req->drmdev->selected_mode_blob_id); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + if (crtc == NULL) { + LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); + goto fail_unlock; } - ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); - if (ok != 0) { - drmdev_destroy_atomic_req(augment); - return ok; + builder = malloc(sizeof *builder); + if (builder == NULL) { + goto fail_unlock; } - ok = drmModeAtomicMerge(req->atomic_req, augment->atomic_req); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not apply modesetting properties to atomic request. drmModeAtomicMerge"); - drmdev_destroy_atomic_req(augment); - return ok; + supports_atomic_modesetting = drmdev->supports_atomic_modesetting; + + if (supports_atomic_modesetting) { + req = drmModeAtomicAlloc(); + if (req == NULL) { + goto fail_free_builder; + } + + // set the CRTC to active + drmModeAtomicAddProperty(req, crtc->id, crtc->ids.active, 1); + } else { + req = NULL; } - drmdev_destroy_atomic_req(augment); + min_zpos = INT64_MAX; + BITSET_ZERO(builder->available_planes); + for (int i = 0; i < drmdev->n_planes; i++) { + struct drm_plane *plane = drmdev->planes + i; - if (flags != NULL) { - *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + if (plane->possible_crtcs & crtc->bitmask) { + BITSET_SET(builder->available_planes, i); + if (plane->has_zpos && plane->min_zpos < min_zpos) { + min_zpos = plane->min_zpos; + } + } } - return 0; + drmdev_unlock(drmdev); + + builder->n_refs = REFCOUNT_INIT_1; + builder->drmdev = drmdev_ref(drmdev); + // right now they're the same, but they might not be in the future. + builder->use_legacy = !supports_atomic_modesetting; + builder->supports_atomic = supports_atomic_modesetting; + builder->connector = NULL; + builder->crtc = crtc; + builder->req = req; + builder->next_zpos = min_zpos; + builder->n_layers = 0; + builder->has_mode = false; + builder->unset_mode = false; + return builder; + +fail_free_builder: + free(builder); + +fail_unlock: + drmdev_unlock(drmdev); + return NULL; } -int drmdev_atomic_req_commit( - struct drmdev_atomic_req *req, - uint32_t flags, - void *userdata -) { - int ok; +static void kms_req_builder_destroy(struct kms_req_builder *builder) { + /// TODO: Is this complete? + for (int i = 0; i < builder->n_layers; i++) { + if (builder->layers[i].release_callback != NULL) { + builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); + } + } + if (builder->req != NULL) { + drmModeAtomicFree(builder->req); + } + drmdev_unref(builder->drmdev); + free(builder); +} - drmdev_lock(req->drmdev); +DEFINE_REF_OPS(kms_req_builder, n_refs) - ok = drmModeAtomicCommit(req->drmdev->fd, req->atomic_req, flags, userdata); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not commit atomic request. drmModeAtomicCommit"); - drmdev_unlock(req->drmdev); - return ok; - } +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { + return builder->drmdev; +} - drmdev_unlock(req->drmdev); +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return builder->n_layers == 0; +} + +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode) { + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(mode); + builder->has_mode = true; + builder->mode = *mode; return 0; } -int drmdev_legacy_set_mode_and_fb( - struct drmdev *drmdev, - uint32_t fb_id -) { - int ok; +int kms_req_builder_unset_mode(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + assert(!builder->has_mode); + builder->unset_mode = true; + return 0; +} - drmdev_lock(drmdev); +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { + struct drm_connector *conn; - ok = drmModeSetCrtc( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - 0, - 0, - &drmdev->selected_connector->connector->connector_id, - 1, - (drmModeModeInfoPtr) drmdev->selected_mode - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set CRTC mode and framebuffer. drmModeSetCrtc"); - drmdev_unlock(drmdev); - return ok; + ASSERT_NOT_NULL(builder); + assert(DRM_ID_IS_VALID(connector_id)); + + for_each_connector_in_drmdev(builder->drmdev, conn) { + if (conn->id == connector_id) { + break; + } } - drmdev_unlock(drmdev); + if (conn == NULL) { + LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); + return EINVAL; + } + builder->connector = conn; return 0; } -int drmdev_legacy_primary_plane_pageflip( - struct drmdev *drmdev, - uint32_t fb_id, +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + kms_fb_release_cb_t release_callback, + kms_deferred_fb_release_cb_t deferred_release_callback, void *userdata ) { - int ok; + struct drm_plane *plane; + int64_t zpos; + bool has_zpos; + bool close_in_fence_fd_after; + int ok, index; + + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(layer); + ASSERT_NOT_NULL(release_callback); + ASSERT_EQUALS_MSG(deferred_release_callback, NULL, "deferred release callbacks are not supported right now."); + + if (builder->use_legacy && builder->supports_atomic && builder->n_layers > 1) { + // if we already have a first layer and we should use legacy modesetting even though the kernel driver + // supports atomic modesetting, return EINVAL. + // if the driver supports atomic modesetting, drmModeSetPlane will block for vblank, so we can't use it, + // and we can't use drmModeAtomicCommit for non-blocking multi-plane commits of course. + // For the first layer we can use drmModePageFlip though. + LOG_DEBUG("Can't do multi-plane commits when using legacy modesetting (and driver supports atomic modesetting).\n"); + return EINVAL; + } - drmdev_lock(drmdev); + close_in_fence_fd_after = false; + if (builder->use_legacy && layer->has_in_fence_fd) { + LOG_DEBUG("Explicit fencing is not supported for legacy modesetting. Implicit fencing will be used instead.\n"); + close_in_fence_fd_after = true; + } - ok = drmModePageFlip( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - DRM_MODE_PAGE_FLIP_EVENT, - userdata - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not schedule pageflip on primary plane. drmModePageFlip"); - drmdev_unlock(drmdev); - return ok; + // Index of our layer. + index = builder->n_layers; + + // If we should prefer a cursor plane, try to find one first. + plane = NULL; + if (layer->prefer_cursor) { + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ false, + /* allow_cursor */ true, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + if (plane == NULL) { + LOG_DEBUG("Couldn't find a fitting cursor plane.\n"); + } } - drmdev_unlock(drmdev); + /// TODO: Not sure we can use crtc_x, crtc_y, etc with primary planes + if (plane == NULL && index == 0) { + // if this is the first layer, try using a + // primary plane for it. + + /// TODO: Use cursor_plane->max_zpos - 1 as the upper zpos limit, instead of INT64_MAX + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + + if (plane == NULL && !get_pixfmt_info(layer->format)->is_opaque) { + // maybe we can find a plane if we use the opaque version of this pixel format? + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + /* format */ pixfmt_opaque(layer->format), + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + } + } else if (plane == NULL) { + // First try to find an overlay plane with a higher zpos. + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ true, builder->next_zpos, INT64_MAX, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + + // If we can't find one, find an overlay plane with the next highest plane_id. + // (According to some comments in the kernel, that's the fallback KMS uses for the + // occlusion order if no zpos property is supported, i.e. planes with plane id occlude + // planes with lower id) + if (plane == NULL) { + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ true, builder->layers[index - 1].plane_id + 1 + // clang-format on + ); + } + } - return 0; -} + if (plane == NULL) { + LOG_ERROR("Could not find a suitable unused DRM plane for pushing the framebuffer.\n"); + return EIO; + } + + // Now that we have a plane, use the minimum zpos + // that's both higher than the last layers zpos and + // also supported by the plane. + // This will also work for planes with hardcoded zpos. + has_zpos = plane->has_zpos; + if (has_zpos) { + zpos = builder->next_zpos; + if (plane->min_zpos > zpos) { + zpos = plane->min_zpos; + } + } else { + // just to silence an uninitialized use warning below. + zpos = 0; + } -int drmdev_legacy_overlay_plane_pageflip( - struct drmdev *drmdev, - uint32_t plane_id, - uint32_t fb_id, - int32_t crtc_x, - int32_t crtc_y, - int32_t crtc_w, - int32_t crtc_h, - uint32_t src_x, - uint32_t src_y, - uint32_t src_w, - uint32_t src_h -) { - int ok; + if (builder->use_legacy) { + } else { + uint32_t plane_id = plane->id; + + /// TODO: Error checking + /// TODO: Maybe add these in the kms_req_builder_commit instead? + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_id, builder->crtc->id); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.fb_id, layer->drm_fb_id); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_x, layer->dst_x); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_y, layer->dst_y); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_w, layer->dst_w); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.crtc_h, layer->dst_h); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_x, layer->src_x); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_y, layer->src_y); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_w, layer->src_w); + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.src_h, layer->src_h); + + if (plane->has_zpos && !plane->has_hardcoded_zpos) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.zpos, zpos); + } - drmdev_lock(drmdev); + if (layer->has_rotation && plane->has_rotation && !plane->has_hardcoded_rotation) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.rotation, layer->rotation.u64); + } - ok = drmModeSetPlane( - drmdev->fd, - plane_id, - drmdev->selected_crtc->crtc->crtc_id, - fb_id, - 0, - crtc_x, crtc_y, crtc_w, crtc_h, - src_x, src_y, src_w, src_h - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not do blocking pageflip on overlay plane. drmModeSetPlane"); - drmdev_unlock(drmdev); - return ok; + if (index == 0) { + if (plane->has_alpha) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.alpha, DRM_BLEND_ALPHA_OPAQUE); + } + + if (plane->has_blend_mode && plane->supported_blend_modes[kNone_DrmBlendMode]) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, kNone_DrmBlendMode); + } + } } - drmdev_unlock(drmdev); + // This should be done when we're sure we're not failing. + // Because on failure it would be the callers job to close the fd. + if (close_in_fence_fd_after) { + ok = close(layer->in_fence_fd); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not close layer in_fence_fd. close: %s\n", strerror(ok)); + goto fail_release_plane; + } + } + /// TODO: Right now we're adding zpos, rotation to the atomic request unconditionally + /// when specified in the fb layer. Ideally we would check for updates + /// on commit and only add to the atomic request when zpos / rotation changed. + builder->n_layers++; + if (has_zpos) { + builder->next_zpos = zpos + 1; + } + builder->layers[index].layer = *layer; + builder->layers[index].plane_id = plane->id; + builder->layers[index].plane = plane; + builder->layers[index].set_zpos = has_zpos; + builder->layers[index].zpos = zpos; + builder->layers[index].set_rotation = layer->has_rotation; + builder->layers[index].rotation = layer->rotation; + builder->layers[index].release_callback = release_callback; + builder->layers[index].deferred_release_callback = deferred_release_callback; + builder->layers[index].release_callback_userdata = userdata; return 0; + +fail_release_plane: + release_plane(builder, plane->id); + return ok; } -int drmdev_legacy_set_connector_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -) { +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(zpos_out); + *zpos_out = builder->next_zpos++; + return 0; +} + +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { + return (struct kms_req *) kms_req_builder_ref(builder); +} + +UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { + return (struct kms_req *) kms_req_builder_ref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unref(struct kms_req *req) { + return kms_req_builder_unref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unrefp(struct kms_req **req) { + return kms_req_builder_unrefp((struct kms_req_builder **) req); +} + +UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { + return kms_req_builder_swap_ptrs((struct kms_req_builder **) oldp, (struct kms_req_builder *) new); +} + +static int +kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { + struct kms_req_builder *builder; + struct drm_mode_blob *mode_blob; + uint32_t flags; + bool internally_blocking; + bool update_mode; int ok; - drmdev_lock(drmdev); + internally_blocking = false; + update_mode = false; + mode_blob = NULL; - for (int i = 0; i < drmdev->selected_connector->props->count_props; i++) { - drmModePropertyRes *prop = drmdev->selected_connector->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeConnectorSetProperty( - drmdev->fd, - drmdev->selected_connector->connector->connector_id, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set connector property. drmModeConnectorSetProperty"); - drmdev_unlock(drmdev); - return ok; - } + ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; - drmdev_unlock(drmdev); - return 0; + drmdev_lock(builder->drmdev); + + if (builder->drmdev->master_fd < 0) { + LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n"); + ok = EBUSY; + goto fail_unlock; + } + + if (!is_drm_master(builder->drmdev->master_fd)) { + LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); + ok = EBUSY; + goto fail_unlock; + } + + // only change the mode if the new mode differs from the old one + + /// TOOD: If this is not a standard mode reported by connector/CRTC, + /// is there a way to verify if it is valid? (maybe use DRM_MODE_ATOMIC_TEST) + + // this could be a single expression but this way you see a bit better what's going on. + // We need to upload the new mode blob if: + // - we have a new mode + // - and: we don't have an old mode + // - or: the old mode differs from the new mode + bool upload_mode = false; + if (builder->has_mode) { + if (!builder->crtc->committed_state.has_mode) { + upload_mode = true; + } else if (memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0) { + upload_mode = true; } } - drmdev_unlock(drmdev); - return EINVAL; -} + if (upload_mode) { + update_mode = true; + mode_blob = drm_mode_blob_new(builder->drmdev->fd, &builder->mode); + if (mode_blob == NULL) { + ok = EIO; + goto fail_unlock; + } + } else if (builder->unset_mode) { + update_mode = true; + mode_blob = NULL; + } + + if (builder->use_legacy) { + ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0); + ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0); + ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay); + ASSERT_EQUALS(builder->layers[0].layer.dst_h, builder->mode.vdisplay); + + /// TODO: Do we really need to assert this? + ASSERT_NOT_NULL(builder->connector); + + bool needs_set_crtc = update_mode; + + // check if the plane pixel format changed. + // that needs a drmModeSetCrtc for legacy KMS as well. + // get the current, committed fb for the plane, check if we have info + // for it (we can't use drmModeGetFB2 since that's not present on debian buster) + // and if we're not absolutely sure the formats match, set needs_set_crtc + // too. + if (!needs_set_crtc) { + struct kms_req_layer *layer = builder->layers + 0; + struct drm_plane *plane = layer->plane; + ASSERT_NOT_NULL(plane); + + if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { + needs_set_crtc = false; + } else { + needs_set_crtc = true; + } -int drmdev_legacy_set_crtc_property( - struct drmdev *drmdev, - const char *name, - uint64_t value -) { - int ok; +#ifdef DEBUG + drmModeFBPtr committed_fb = drmModeGetFB(builder->drmdev->master_fd, plane->committed_state.fb_id); + if (committed_fb == NULL) { + needs_set_crtc = true; + } else { + needs_set_crtc = true; - drmdev_lock(drmdev); + list_for_each_entry(struct drm_fb, fb, &builder->drmdev->fbs, entry) { + if (fb->id == committed_fb->fb_id) { + ASSERT_EQUALS(fb->format, plane->committed_state.format); + + if (fb->format == layer->layer.format) { + needs_set_crtc = false; + } + } + + if (fb->id == layer->layer.drm_fb_id) { + ASSERT_EQUALS(fb->format, layer->layer.format); + } + } + } + + drmModeFreeFB(committed_fb); +#endif + } - for (int i = 0; i < drmdev->selected_crtc->props->count_props; i++) { - drmModePropertyRes *prop = drmdev->selected_crtc->props_info[i]; - if (strcmp(prop->name, name) == 0) { - ok = drmModeObjectSetProperty( - drmdev->fd, - drmdev->selected_crtc->crtc->crtc_id, - DRM_MODE_OBJECT_CRTC, - prop->prop_id, - value + /// TODO: Handle {src,dst}_{x,y,w,h} here + /// TODO: Handle setting other properties as well + if (needs_set_crtc) { + /// TODO: Fetch new connector or current connector here since we seem to need it for drmModeSetCrtc + ok = drmModeSetCrtc( + builder->drmdev->master_fd, + builder->crtc->id, + builder->layers[0].layer.drm_fb_id, + 0, + 0, + (uint32_t[1]){ builder->connector->id }, + 1, + builder->unset_mode ? NULL : &builder->mode ); - if (ok < 0) { + if (ok != 0) { ok = errno; - perror("[modesetting] Could not set CRTC property. drmModeObjectSetProperty"); - drmdev_unlock(drmdev); - return ok; + LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); + goto fail_maybe_destroy_mode_blob; } - drmdev_unlock(drmdev); - return 0; + internally_blocking = true; + } else { + ok = drmModePageFlip( + builder->drmdev->master_fd, + builder->crtc->id, + builder->layers[0].layer.drm_fb_id, + DRM_MODE_PAGE_FLIP_EVENT, + kms_req_builder_ref(builder) + ); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not commit display update. drmModePageFlip: %s\n", strerror(ok)); + goto fail_unref_builder; + } } - } - drmdev_unlock(drmdev); - return EINVAL; -} + // This should also be ensured by kms_req_builder_push_fb_layer + ASSERT_MSG( + !(builder->supports_atomic && builder->n_layers > 1), + "There can be at most one framebuffer layer when the KMS device supports atomic modesetting but we are " + "using legacy modesetting." + ); -int drmdev_legacy_set_plane_property( - struct drmdev *drmdev, - uint32_t plane_id, - const char *name, - uint64_t value -) { - struct drm_plane *plane; - int ok; + /// TODO: Call drmModeSetPlane for all other layers + /// TODO: Assert here - drmdev_lock(drmdev); + } else { + /// TODO: If we can do explicit fencing, don't use the page flip event. + /// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs? + flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].plane->plane_id == plane_id) { - plane = drmdev->planes + i; - break; + if (builder->connector != NULL) { + // add the CRTC_ID property if that was explicitly set + drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); + } + + if (update_mode) { + if (mode_blob != NULL) { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, mode_blob->blob_id); + } else { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, 0); + } + } + + /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip + /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering + /// with fully decoupled framerate, potentially) + ok = drmModeAtomicCommit(builder->drmdev->master_fd, builder->req, flags, kms_req_builder_ref(builder)); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not commit display update. drmModeAtomicCommit: %s\n", strerror(ok)); + goto fail_unref_builder; } } - if (plane == NULL) { - drmdev_unlock(drmdev); - return EINVAL; + // update struct drm_plane.committed_state for all planes + for (int i = 0; i < builder->n_layers; i++) { + struct drm_plane *plane = builder->layers[i].plane; + struct kms_req_layer *layer = builder->layers + i; + + plane->committed_state.crtc_id = builder->crtc->id; + plane->committed_state.fb_id = layer->layer.drm_fb_id; + plane->committed_state.src_x = layer->layer.src_x; + plane->committed_state.src_y = layer->layer.src_y; + plane->committed_state.src_w = layer->layer.src_w; + plane->committed_state.src_h = layer->layer.src_h; + plane->committed_state.crtc_x = layer->layer.dst_x; + plane->committed_state.crtc_y = layer->layer.dst_y; + plane->committed_state.crtc_w = layer->layer.dst_w; + plane->committed_state.crtc_h = layer->layer.dst_h; + + if (builder->layers[i].set_zpos) { + plane->committed_state.zpos = layer->zpos; + } + if (builder->layers[i].set_rotation) { + plane->committed_state.rotation = layer->rotation; + } + + plane->committed_state.has_format = true; + plane->committed_state.format = layer->layer.format; + + // builder->layers[i].plane->committed_state.alpha = layer->alpha; + // builder->layers[i].plane->committed_state.blend_mode = builder->layers[i].layer.blend_mode; } - for (int i = 0; i < plane->props->count_props; i++) { - drmModePropertyRes *prop; - - prop = plane->props_info[i]; - - if (strcmp(prop->name, name) == 0) { - ok = drmModeObjectSetProperty( - drmdev->fd, - plane_id, - DRM_MODE_OBJECT_PLANE, - prop->prop_id, - value - ); - if (ok < 0) { - ok = errno; - perror("[modesetting] Could not set plane property. drmModeObjectSetProperty"); - drmdev_unlock(drmdev); - return ok; - } - - drmdev_unlock(drmdev); - return 0; + // update struct drm_crtc.committed_state + if (update_mode) { + // destroy the old mode blob + if (builder->crtc->committed_state.mode_blob != NULL) { + /// TODO: Should we defer this to after the pageflip? + drm_mode_blob_destroy(builder->crtc->committed_state.mode_blob); + } + + // store the new mode + if (mode_blob != NULL) { + builder->crtc->committed_state.has_mode = true; + builder->crtc->committed_state.mode = builder->mode; + builder->crtc->committed_state.mode_blob = mode_blob; + } else { + builder->crtc->committed_state.has_mode = false; + builder->crtc->committed_state.mode_blob = NULL; } } - drmdev_unlock(drmdev); - return EINVAL; -} \ No newline at end of file + // update struct drm_connector.committed_state + builder->connector->committed_state.crtc_id = builder->crtc->id; + // builder->connector->committed_state.encoder_id = 0; + + drmdev_set_scanout_callback_locked(builder->drmdev, builder->crtc->id, scanout_cb, userdata, destroy_cb); + + if (internally_blocking) { + uint64_t sequence = 0; + uint64_t ns = 0; + int ok; + + ok = drmCrtcGetSequence(builder->drmdev->fd, builder->crtc->id, &sequence, &ns); + if (ok != 0) { + ok = errno; + LOG_ERROR("Could not get vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); + goto fail_unref_builder; + } + + drmdev_on_page_flip_locked( + builder->drmdev->fd, + (unsigned int) sequence, + ns / 1000000000, + ns / 1000, + builder->crtc->id, + kms_req_ref(req) + ); + } else if (blocking) { + // handle the page-flip event here, rather than via the eventfd + ok = drmdev_on_modesetting_fd_ready_locked(builder->drmdev); + if (ok != 0) { + LOG_ERROR("Couldn't synchronously handle pageflip event.\n"); + goto fail_unset_scanout_callback; + } + } + + drmdev_unlock(builder->drmdev); + + return 0; + +fail_unset_scanout_callback: + builder->drmdev->per_crtc_state[builder->crtc->index].scanout_callback = NULL; + builder->drmdev->per_crtc_state[builder->crtc->index].destroy_callback = NULL; + builder->drmdev->per_crtc_state[builder->crtc->index].userdata = NULL; + goto fail_unlock; + +fail_unref_builder: + kms_req_builder_unref(builder); + +fail_maybe_destroy_mode_blob: + if (mode_blob != NULL) + drm_mode_blob_destroy(mode_blob); + +fail_unlock: + drmdev_unlock(builder->drmdev); + + return ok; +} + +void set_vblank_ns(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { + uint64_t *vblank_ns_out; + + ASSERT_NOT_NULL(userdata); + vblank_ns_out = userdata; + (void) drmdev; + + *vblank_ns_out = vblank_ns; +} + +int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out) { + uint64_t vblank_ns; + int ok; + + vblank_ns = int64_to_uint64(-1); + ok = kms_req_commit_common(req, true, set_vblank_ns, &vblank_ns, NULL); + if (ok != 0) { + return ok; + } + + // make sure the vblank_ns is actually set + assert(vblank_ns != int64_to_uint64(-1)); + if (vblank_ns_out != NULL) { + *vblank_ns_out = vblank_ns; + } + + return 0; +} + +int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { + return kms_req_commit_common(req, false, scanout_cb, userdata, destroy_cb); +} diff --git a/src/modesetting.h b/src/modesetting.h new file mode 100644 index 00000000..91de16d1 --- /dev/null +++ b/src/modesetting.h @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: MIT +/* + * KMS Modesetting + * + * - implements the interface to linux kernel modesetting + * - allows querying connected screens, crtcs, planes, etc + * - allows setting video modes, showing things on screen + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_MODESETTING_H +#define _FLUTTERPI_SRC_MODESETTING_H + +#include + +#include + +#include +#include + +#include "pixel_format.h" +#include "util/collection.h" +#include "util/geometry.h" +#include "util/refcounting.h" + +#define DRM_ID_NONE ((uint32_t) 0xFFFFFFFF) + +#define DRM_ID_IS_VALID(id) ((id) != 0 && (id) != DRM_ID_NONE) + +// All commented out properties are not present on the RPi. +// Some of not-commented out properties we don't make usage of, +// but they could be useful in the future. +// +// keep in sync with: https://drmdb.emersion.fr/properties?object-type=3233857728 +#define DRM_CONNECTOR_PROPERTIES(V) \ + V("Broadcast RGB", broadcast_rgb) \ + /* V("CONNECTOR_ID", connector_id) */ \ + V("CRTC_ID", crtc_id) \ + V("Colorspace", colorspace) \ + /* V("Content Protection", content_protection) */ \ + V("DPMS", dpms) \ + V("EDID", edid) \ + /* V("HDCP Content Type", hdcp_content_type) */ \ + V("HDR_OUTPUT_METADATA", hdr_output_metadata) \ + /* V("HDR_PANEL_METADATA", hdr_panel_metadata) */ \ + /* V("HDR_SOURCE_METADATA", hdr_source_metadata) */ \ + /* V("NEXT_HDR_SINK_DATA", next_hdr_sink_data) */ \ + V("Output format", output_format) \ + /* V("PATH", path) */ \ + V("TILE", tile) \ + /* V("USER_SPLIT_MODE", user_split_mode) */ \ + V("WRITEBACK_FB_ID", writeback_fb_id) \ + V("WRITEBACK_OUT_FENCE_PTR", writeback_out_fence_ptr) \ + V("WRITEBACK_PIXEL_FORMATS", writeback_pixel_formats) \ + /* V("abm level", abm_level) */ \ + /* V("allm_capacity", allm_capacity) */ \ + /* V("allm_enable", allm_enable) */ \ + /* V("aspect ratio", aspect_ratio) */ \ + /* V("audio", audio) */ \ + /* V("backlight", backlight) */ \ + V("bottom margin", bottom_margin) \ + /* V("bpc", bpc) */ \ + /* V("brightness", brightness) */ \ + /* V("coherent", coherent) */ \ + /* V("color vibrance", color_vibrance) */ \ + /* V("color depth", color_depth) */ \ + /* V("color depth caps", color_depth_caps) */ \ + /* V("color format", color_format) */ \ + /* V("color format caps", color_format_caps) */ \ + /* V("content type", content_type) */ \ + /* V("contrast", contrast) */ \ + /* V("dither", dither) */ \ + /* V("dithering depth", dithering_depth) */ \ + /* V("dithering mode", dithering_mode) */ \ + /* V("flicker reduction", flicker_reduction) */ \ + /* V("hdmi_color_depth_capacity", hdmi_color_depth_capacity) */ \ + /* V("hdmi_output_colorimetry", hdmi_output_colorimetry) */ \ + /* V("hdmi_output_depth", hdmi_output_depth) */ \ + /* V("hdmi_output_mode_capacity", hdmi_output_mode_capacity) */ \ + /* V("hotplug_mode_update", hotplug_mode_update) */ \ + /* V("hue", hue) */ \ + V("left margin", left_margin) \ + V("link-status", link_status) \ + /* V("load detection", load_detection) */ \ + V("max bpc", max_bpc) \ + V("mode", mode) \ + V("non-desktop", non_desktop) \ + /* V("output_csc", output_csc) */ \ + /* V("output_hdmi_dvi", output_hdmi_dvi) */ \ + /* V("output_type_capacity", output_type_capacity) */ \ + /* V("overscan", overscan) */ \ + /* V("panel orientation", panel_orientation) */ \ + /* V("privacy-screen hw-state", privacy_screen_hw_state) */ \ + /* V("privacy-screen sw-state", privacy_screen_sw_state) */ \ + V("right margin", right_margin) \ + /* V("saturation", saturation) */ \ + /* V("scaling mode", scaling_mode) */ \ + /* V("select subconnector", select_subconnector) */ \ + /* V("subconnector", subconnector) */ \ + /* V("suggested X", suggested_x) */ \ + /* V("suggested Y", suggested_y) */ \ + /* V("sync", sync) */ \ + V("top margin", top_margin) \ + /* V("tv standard", tv_standard) */ \ + /* V("underscan", underscan) */ \ + /* V("underscan hborder", underscan_hborder) */ \ + /* V("underscan vborder", underscan_vborder) */ \ + /* V("vibrant hue", vibrant_hue) */ \ + /* V("vrr_capable", vrr_capable) */ + +// again, crtc properties that are not available on pi 4 +// are commented out. +// +// keep in sync with: +// https://drmdb.emersion.fr/properties?object-type=3435973836 +#define DRM_CRTC_PROPERTIES(V) \ + /* V("ACLK", aclk) */ \ + V("ACTIVE", active) \ + /* V("ALPHA_SCALE", alpha_scale) */ \ + /* V("BACKGROUND", background) */ \ + /* V("BG_COLOR", bg_color) */ \ + /* V("CABC_CALC_PIXEL_NUM", cabc_calc_pixel_num) */ \ + /* V("CABC_GLOBAL_DN", cabc_global_dn) */ \ + /* V("CABC_LUT", cabc_lut) */ \ + /* V("CABC_MODE", cabc_mode) */ \ + /* V("CABC_STAGE_DOWN", cabc_stage_down) */ \ + /* V("CABC_STAGE_UP", cabc_stage_up) */ \ + V("CTM", ctm) \ + /* V("DEGAMMA_LUT", degamma_lut) */ \ + /* V("DEGAMMA_LUT_SIZE", degamma_lut_size) */ \ + /* V("DITHER_ENABLED", dither_enabled) */ \ + /* V("FEATURE", feature) */ \ + V("GAMMA_LUT", gamma_lut) \ + V("GAMMA_LUT_SIZE", gamma_lut_size) \ + /* V("LINE_FLAG1", line_flag1) */ \ + V("MODE_ID", mode_id) \ + V("OUT_FENCE_PTR", out_fence_ptr) \ + /* V("PLANE_MASK", plane_mask) */ \ + /* V("PORT_ID", port_id) */ \ + /* V("SCALING_FILTER", scaling_filter) */ \ + /* V("SOC_ID", soc_id) */ \ + /* V("SYNC_ENABLED", sync_enabled) */ \ + V("VRR_ENABLED", vrr_enabled) \ + /* V("bg_c0", bg_c0) */ \ + /* V("bg_c1", bg_c1) */ \ + /* V("bg_c2", bg_c2) */ \ + /* V("bottom margin", bottom_margin) */ \ + /* V("left margin", left_margin) */ \ + /* V("max refresh rate", max_refresh_rate) */ \ + /* V("min refresh rate", min_refresh_rate) */ \ + /* V("output_color", output_color) */ \ + /* V("right margin", right_margin) */ \ + V("rotation", rotation) \ + /* V("top margin", top_margin) */ \ + /* V("variable refresh rate", variable_refresh_rate) */ \ + V("zorder", zorder) + +// again, plane properties that are not present on pi 4 are commented out. +// +// keep in sync with: +// https://drmdb.emersion.fr/properties?object-type=4008636142 +#define DRM_PLANE_PROPERTIES(V) \ + /* V("ASYNC_COMMIT", async_commit) */ \ + /* V("BLEND_MODE", blend_mode) */ \ + /* V("CHROMA_SITING_H", chroma_siting_h) */ \ + /* V("CHROMA_SITING_V", chroma_siting_v) */ \ + /* V("COLOR_CONFIG", color_config) */ \ + V("COLOR_ENCODING", color_encoding) \ + V("COLOR_RANGE", color_range) \ + /* V("COLOR_SPACE", color_space) */ \ + V("CRTC_H", crtc_h) \ + V("CRTC_ID", crtc_id) \ + V("CRTC_W", crtc_w) \ + V("CRTC_X", crtc_x) \ + V("CRTC_Y", crtc_y) \ + /* V("DEGAMMA_MODE", degamma_mode) */ \ + /* V("EOTF", eotf) */ \ + /* V("FB_DAMAGE_CLIPS", fb_damage_clips) */ \ + V("FB_ID", fb_id) \ + /* V("FEATURE", feature) */ \ + /* V("GLOBAL_ALPHA", global_alpha) */ \ + /* V("INPUT_HEIGHT", input_height) */ \ + /* V("INPUT_WIDTH", input_width) */ \ + V("IN_FENCE_FD", in_fence_fd) \ + V("IN_FORMATS", in_formats) \ + /* V("NAME", name) */ \ + /* V("NV_HDR_STATIC_METADATA", nv_hdr_static_metadata) */ \ + /* V("NV_INPUT_COLORSPACE", nv_input_colorspace) */ \ + /* V("OUTPUT_HEIGHT", output_height) */ \ + /* V("OUTPUT_WIDTH", output_width) */ \ + /* V("ROI", roi) */ \ + /* V("SCALE_RATE", scale_rate) */ \ + /* V("SCALING_FILTER", scaling_filter) */ \ + /* V("SHARE_ID", share_id) */ \ + V("SRC_H", src_h) \ + V("SRC_W", src_w) \ + V("SRC_X", src_x) \ + V("SRC_Y", src_y) \ + /* V("WATERMARK", watermark) */ \ + /* V("ZPOS", zpos) */ \ + V("alpha", alpha) \ + /* V("brightness", brightness) */ \ + /* V("colorkey", colorkey) */ \ + /* V("contrast", contrast) */ \ + /* V("g_alpha_en", g_alpha_en) */ \ + /* V("hue", hue) */ \ + V("pixel blend mode", pixel_blend_mode) \ + V("rotation", rotation) \ + /* V("saturation", saturation) */ \ + /* V("tpg", tpg) */ \ + V("type", type) \ + /* V("zorder", zorder) */ \ + V("zpos", zpos) + +#define DECLARE_PROP_ID_AS_UINT32(prop_name, prop_var_name) uint32_t prop_var_name; + +#define DRM_BLEND_ALPHA_OPAQUE 0xFFFF + +enum drm_blend_mode { + kPremultiplied_DrmBlendMode, + kCoverage_DrmBlendMode, + kNone_DrmBlendMode, + + kMax_DrmBlendMode = kNone_DrmBlendMode, + kCount_DrmBlendMode = kMax_DrmBlendMode + 1 +}; + +struct drm_connector_prop_ids { + DRM_CONNECTOR_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) +}; + +static inline void drm_connector_prop_ids_init(struct drm_connector_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +struct drm_crtc_prop_ids { + DRM_CRTC_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) +}; + +static inline void drm_crtc_prop_ids_init(struct drm_crtc_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +struct drm_plane_prop_ids { + DRM_PLANE_PROPERTIES(DECLARE_PROP_ID_AS_UINT32) +}; + +static inline void drm_plane_prop_ids_init(struct drm_plane_prop_ids *ids) { + memset(ids, 0xFF, sizeof(*ids)); +} + +#undef DECLARE_PROP_ID_AS_UINT32 + +// This is quite hacky, but if it works it pretty nice. +// There's asserts in modesetting.c (fn assert_rotations_work()) that make sure these rotations all work as expected. +// If any of these fail we gotta use a more conservative approach instead. +typedef struct { + union { + struct { + bool rotate_0 : 1; + bool rotate_90 : 1; + bool rotate_180 : 1; + bool rotate_270 : 1; + bool reflect_x : 1; + bool reflect_y : 1; + }; + uint32_t u32; + uint64_t u64; + }; +} drm_plane_transform_t; + +#define PLANE_TRANSFORM_NONE ((const drm_plane_transform_t){ .u64 = 0 }) +#define PLANE_TRANSFORM_ROTATE_0 ((const drm_plane_transform_t){ .u64 = DRM_MODE_ROTATE_0 }) +#define PLANE_TRANSFORM_ROTATE_90 ((const drm_plane_transform_t){ .u64 = DRM_MODE_ROTATE_90 }) +#define PLANE_TRANSFORM_ROTATE_180 ((const drm_plane_transform_t){ .u64 = DRM_MODE_ROTATE_180 }) +#define PLANE_TRANSFORM_ROTATE_270 ((const drm_plane_transform_t){ .u64 = DRM_MODE_ROTATE_270 }) +#define PLANE_TRANSFORM_REFLECT_X ((const drm_plane_transform_t){ .u64 = DRM_MODE_REFLECT_X }) +#define PLANE_TRANSFORM_REFLECT_Y ((const drm_plane_transform_t){ .u64 = DRM_MODE_REFLECT_Y }) + +#define PLANE_TRANSFORM_IS_VALID(t) (((t).u64 & ~(DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK)) == 0) +#define PLANE_TRANSFORM_IS_ONLY_ROTATION(t) (((t).u64 & ~DRM_MODE_ROTATE_MASK) == 0 && (HWEIGHT((t).u64) == 1)) +#define PLANE_TRANSFORM_IS_ONLY_REFLECTION(t) (((t).u64 & ~DRM_MODE_REFLECT_MASK) == 0 && (HWEIGHT((t).u64) == 1)) + +#define PLANE_TRANSFORM_ROTATE_CW(t) \ + (assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(t)), \ + (t).u64 == DRM_MODE_ROTATE_0 ? PLANE_TRANSFORM_ROTATE_90 : \ + (t).u64 == DRM_MODE_ROTATE_90 ? PLANE_TRANSFORM_ROTATE_180 : \ + (t).u64 == DRM_MODE_ROTATE_180 ? PLANE_TRANSFORM_ROTATE_270 : \ + PLANE_TRANSFORM_ROTATE_0) + +#define PLANE_TRANSFORM_ROTATE_CCW(t) \ + (assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(t)), \ + (t).u64 == DRM_MODE_ROTATE_0 ? PLANE_TRANSFORM_ROTATE_270 : \ + (t).u64 == DRM_MODE_ROTATE_90 ? PLANE_TRANSFORM_ROTATE_0 : \ + (t).u64 == DRM_MODE_ROTATE_180 ? PLANE_TRANSFORM_ROTATE_90 : \ + PLANE_TRANSFORM_ROTATE_180) + +/* +enum drm_plane_rotation { + kRotate0_DrmPlaneRotation = DRM_MODE_ROTATE_0, + kRotate90_DrmPlaneRotation = DRM_MODE_ROTATE_90, + kRotate180_DrmPlaneRotation = DRM_MODE_ROTATE_180, + kRotate270_DrmPlaneRotation = DRM_MODE_ROTATE_270, + kReflectX_DrmPlaneRotation = DRM_MODE_REFLECT_X, + kReflectY_DrmPlaneRotation = DRM_MODE_REFLECT_Y +}; +*/ + +enum drm_plane_type { + kPrimary_DrmPlaneType = DRM_PLANE_TYPE_PRIMARY, + kOverlay_DrmPlaneType = DRM_PLANE_TYPE_OVERLAY, + kCursor_DrmPlaneType = DRM_PLANE_TYPE_CURSOR +}; + +struct drm_mode_blob { + int drm_fd; + uint32_t blob_id; + drmModeModeInfo mode; +}; + +enum drm_connector_type { + kUnknown_DrmConnectorType = DRM_MODE_CONNECTOR_Unknown, + kVGA_DrmConnectorType = DRM_MODE_CONNECTOR_VGA, + kDVII_DrmConnectorType = DRM_MODE_CONNECTOR_DVII, + kDVID_DrmConnectorType = DRM_MODE_CONNECTOR_DVID, + kDVIA_DrmConnectorType = DRM_MODE_CONNECTOR_DVIA, + kComposite_DrmConnectorType = DRM_MODE_CONNECTOR_Composite, + kSVIDEO_DrmConnectorType = DRM_MODE_CONNECTOR_SVIDEO, + kLVDS_DrmConnectorType = DRM_MODE_CONNECTOR_LVDS, + kComponent_DrmConnectorType = DRM_MODE_CONNECTOR_Component, + k9PinDIN_DrmConnectorType = DRM_MODE_CONNECTOR_9PinDIN, + kDisplayPort_DrmConnectorType = DRM_MODE_CONNECTOR_DisplayPort, + kHDMIA_DrmConnectorType = DRM_MODE_CONNECTOR_HDMIA, + kHDMIB_DrmConnectorType = DRM_MODE_CONNECTOR_HDMIB, + kTV_DrmConnectorType = DRM_MODE_CONNECTOR_TV, + keDP_DrmConnectorType = DRM_MODE_CONNECTOR_eDP, + kVIRTUAL_DrmConnectorType = DRM_MODE_CONNECTOR_VIRTUAL, + kDSI_DrmConnectorType = DRM_MODE_CONNECTOR_DSI, + kDPI_DrmConnectorType = DRM_MODE_CONNECTOR_DPI, + kWRITEBACK_DrmConnectorType = DRM_MODE_CONNECTOR_WRITEBACK, +#ifdef DRM_MODE_CONNECTOR_SPI + kSPI_DrmConnectorType = DRM_MODE_CONNECTOR_SPI +#endif +}; + +enum drm_connection_state { + kConnected_DrmConnectionState = DRM_MODE_CONNECTED, + kDisconnected_DrmConnectionState = DRM_MODE_DISCONNECTED, + kUnknown_DrmConnectionState = DRM_MODE_UNKNOWNCONNECTION +}; + +enum drm_subpixel_layout { + kUnknown_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_UNKNOWN, + kHorizontalRRB_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB, + kHorizontalBGR_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_HORIZONTAL_BGR, + kVerticalRGB_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_VERTICAL_RGB, + kVerticalBGR_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_VERTICAL_BGR, + kNone_DrmSubpixelLayout = DRM_MODE_SUBPIXEL_NONE +}; + +struct drm_connector { + uint32_t id; + + enum drm_connector_type type; + uint32_t type_id; + + struct drm_connector_prop_ids ids; + + int n_encoders; + uint32_t encoders[32]; + + struct { + enum drm_connection_state connection_state; + enum drm_subpixel_layout subpixel_layout; + uint32_t width_mm, height_mm; + uint32_t n_modes; + drmModeModeInfo *modes; + } variable_state; + + struct { + uint32_t crtc_id; + uint32_t encoder_id; + } committed_state; +}; + +struct drm_encoder { + drmModeEncoder *encoder; +}; + +struct drm_crtc { + uint32_t id; + uint32_t bitmask; + uint8_t index; + + struct drm_crtc_prop_ids ids; + + struct { + bool has_mode; + drmModeModeInfo mode; + struct drm_mode_blob *mode_blob; + } committed_state; +}; + +struct modified_format { + enum pixfmt format; + uint64_t modifier; +}; + +struct drm_plane { + /// @brief The DRM id of this plane. + uint32_t id; + + /// @brief Bitmap of the indexes of the CRTCs that this plane can be scanned out on. + /// + /// I.e. if bit 0 is set, this plane can be scanned out on the CRTC with index 0. + /// if bit 0 is not set, this plane can not be scanned out on that CRTC. + uint32_t possible_crtcs; + + /// @brief The ids of all properties associated with this plane. + /// + /// Any property that is not supported has the value @ref DRM_PROP_ID_NONE. + struct drm_plane_prop_ids ids; + + /// @brief The type of this plane (primary, overlay, cursor) + /// + /// The type has some influence on what you can do with the plane. + /// For example, it's possible the driver enforces the primary plane to be + /// the bottom-most plane or have an opaque pixel format. + enum drm_plane_type type; + + /// @brief True if this plane has a zpos property. + /// + /// This does not mean it is changeable by userspace. Check + /// @ref has_hardcoded_zpos for that. + /// + /// The docs say if one plane has a zpos property, all planes should have one. + bool has_zpos; + + /// @brief The minimum and maximum possible zpos + /// + /// Only valid if @ref has_zpos is true. + /// + /// If @ref has_hardcoded_zpos is true, min_zpos should equal max_zpos. + int64_t min_zpos, max_zpos; + + /// @brief True if this plane has a hardcoded zpos that can't + /// be changed by userspace. + bool has_hardcoded_zpos; + + /// @brief The specific hardcoded zpos of this plane. + /// + /// Only valid if @ref has_hardcoded_zpos is true. + int64_t hardcoded_zpos; + + /// @brief True if this plane has a rotation property. + /// + /// This does not mean that it is mutable. Check @ref has_hardcoded_rotation + /// and @ref supported_rotations for that. + bool has_rotation; + + /// @brief The set of rotations that are supported by this plane. + /// + /// Query the set booleans of the supported_rotations struct + /// to find out of a given rotation is supported. + /// + /// It is assumed if both a and b are listed as supported in this struct, + /// a rotation value of a | b is supported as well. + /// Only valid if @ref has_rotation is supported as well. + drm_plane_transform_t supported_rotations; + + /// @brief True if this plane has a hardcoded rotation. + bool has_hardcoded_rotation; + + /// @brief The specific hardcoded rotation. + /// + /// Only valid if @ref has_hardcoded_rotation is true. + drm_plane_transform_t hardcoded_rotation; + + /// @brief The framebuffer formats this plane supports, assuming no + /// (implicit) modifier. + /// + /// For example, @ref PIXFMT_ARGB8888 is supported if + /// supported_formats[PIXFMT_ARGB8888] is true. + bool supported_formats[PIXFMT_COUNT]; + + /// @brief True if this plane has an IN_FORMATS property attached and + /// supports scanning out buffers with explicit format modifiers. + bool supports_modifiers; + + /// @brief A pair of pixel format / modifier that is definitely supported. + /// + /// DRM_FORMAT_MOD_LINEAR is supported for most (but not all pixel formats). + /// There are some format & modifier pairs that may be faster to scanout by the GPU. + /// + /// Is NULL if the plane didn't specify an IN_FORMATS property. + /// + /// Use @ref drm_plane_for_each_modified_format to iterate over the supported modified + /// formats. + struct drm_format_modifier_blob *supported_modified_formats_blob; + + /// @brief Whether this plane has a mutable alpha property we can set. + bool has_alpha; + + /// @brief Whether this plane has a mutable pixel blend mode we can set. + bool has_blend_mode; + + /// @brief The supported blend modes. + /// + /// Only valid if @ref has_blend_mode is true. + bool supported_blend_modes[kCount_DrmBlendMode]; + + struct { + /// @brief The committed CRTC id. + /// + /// The id of the CRTC this plane is associated with, right now. + uint32_t crtc_id; + + /// @brief The committed framebuffer id. + /// + /// The id of the framebuffer this plane is scanning out, right now. + uint32_t fb_id; + + /// @brief The committed source rect from the framebuffer. + /// + /// Only valid when using atomic modesetting. + uint32_t src_x, src_y, src_w, src_h; + + /// @brief The committed destination rect, on the CRTC. + /// + /// Only valid when using atomic modesetting. + uint32_t crtc_x, crtc_y, crtc_w, crtc_h; + + /// @brief The committed plane zpos. + /// + /// Only valid if @ref drm_plane.has_zpos is true. + int64_t zpos; + + /// @brief The committed plane rotation. + /// + /// Only valid if @ref drm_plane.has_rotation is true. + drm_plane_transform_t rotation; + + /// @brief The committed alpha property. + /// + /// Only valid if @ref drm_plane.has_alpha is true. + uint16_t alpha; + + /// @brief The committed blend mode. + /// + /// Only valid if @ref drm_plane.has_blend_mode is true. + enum drm_blend_mode blend_mode; + + /// @brief If false, we don't know about the committed format. + /// + /// This can be false on debian buster for example, because we don't + /// have drmModeGetFB2 here, which is required for querying the pixel + /// format of a framebuffer. When a plane is associated with our own + /// framebuffer (created via @ref drmdev_add_fb, for example), we can + /// still determine the pixel format because we track the pixel formats + /// of each added drm fb. + /// + /// But for foreign framebuffers, i.e. the ones that are set on + /// the plane by fbcon when flutter-pi is starting up, we simply can't + /// tell. + /// + /// We need to know though because we need to call @ref drmModeSetCrtc + /// if the pixel format of a drm plane has changed. + bool has_format; + + /// @brief The pixel format of the currently committed framebuffer. + /// + /// Only valid if @ref has_format is true. + enum pixfmt format; + } committed_state; +}; + +/** + * @brief Callback that will be called on each iteration of + * @ref drm_plane_for_each_modified_format. + * + * Should return true if looping should continue. False if iterating should be + * stopped. + * + * @param plane The plane that was passed to @ref drm_plane_for_each_modified_format. + * @param index The index of the pixel format. Is incremented for each call of the callback. + * @param pixel_format The pixel format. + * @param modifier The modifier of this pixel format. + * @param userdata Userdata that was passed to @ref drm_plane_for_each_modified_format. + */ +typedef bool (*drm_plane_modified_format_callback_t +)(struct drm_plane *plane, int index, enum pixfmt pixel_format, uint64_t modifier, void *userdata); + +/** + * @brief Iterates over every supported pixel-format & modifier pair. + * + * See @ref drm_plane_modified_format_callback_t for documentation on the callback. + */ +void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata); + +struct drmdev; +struct _drmModeModeInfo; + +struct drmdev_interface { + int (*open)(const char *path, int flags, void **fd_metadata_out, void *userdata); + void (*close)(int fd, void *fd_metadata, void *userdata); +}; + +struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata); + +struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata); + +DECLARE_REF_OPS(drmdev) + +struct drmdev; +struct _drmModeModeInfo; + +int drmdev_get_fd(struct drmdev *drmdev); +int drmdev_get_event_fd(struct drmdev *drmdev); +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev); +int drmdev_create_dumb_buffer( + struct drmdev *drmdev, + int width, + int height, + int bpp, + uint32_t *gem_handle_out, + uint32_t *pitch_out, + size_t *size_out +); +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle); +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size); +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size); +int drmdev_on_event_fd_ready(struct drmdev *drmdev); +const struct drm_connector *drmdev_get_selected_connector(struct drmdev *drmdev); +const struct drm_encoder *drmdev_get_selected_encoder(struct drmdev *drmdev); +const struct drm_crtc *drmdev_get_selected_crtc(struct drmdev *drmdev); +const struct _drmModeModeInfo *drmdev_get_selected_mode(struct drmdev *drmdev); + +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev); + +uint32_t drmdev_add_fb( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + uint32_t bo_handle, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier +); + +uint32_t drmdev_add_fb_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const uint32_t bo_handles[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +); + +uint32_t drmdev_add_fb_from_dmabuf( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + int prime_fd, + uint32_t pitch, + uint32_t offset, + bool has_modifier, + uint64_t modifier +); + +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const int prime_fds[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +); + +int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id); + +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); + +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); + +bool drmdev_can_modeset(struct drmdev *drmdev); + +void drmdev_suspend(struct drmdev *drmdev); + +int drmdev_resume(struct drmdev *drmdev); + +int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos); + +static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +} + +typedef void (*kms_scanout_cb_t)(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata); + +struct kms_fb_layer { + uint32_t drm_fb_id; + enum pixfmt format; + bool has_modifier; + uint64_t modifier; + + int32_t src_x, src_y, src_w, src_h; + int32_t dst_x, dst_y, dst_w, dst_h; + + bool has_rotation; + drm_plane_transform_t rotation; + + bool has_in_fence_fd; + int in_fence_fd; + + bool prefer_cursor; +}; + +typedef void (*kms_fb_release_cb_t)(void *userdata); + +typedef void (*kms_deferred_fb_release_cb_t)(void *userdata, int syncfile_fd); + +struct kms_req_builder; + +struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id); + +DECLARE_REF_OPS(kms_req_builder); + +/** + * @brief Gets the @ref drmdev associated with this KMS request builder. + * + * @param builder The KMS request builder. + * @returns The drmdev associated with this KMS request builder. + */ +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder); + +/** + * @brief Adds a property to the KMS request that will set the given video mode + * on this CRTC on commit, regardless of whether the currently committed output + * mode is the same. + * + * @param builder The KMS request builder. + * @param mode The output mode to set (on @ref kms_req_commit) + * @returns Zero if successful, positive errno-style error on failure. + */ +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode); + +/** + * @brief Adds a property to the KMS request that will unset the configured + * output mode for this CRTC on commit, regardless of whether the currently + * committed output mdoe is already unset. + * + * @param builder The KMS request builder. + * @returns Zero if successful, positive errno-style error on failure. + */ +int kms_req_builder_unset_mode(struct kms_req_builder *builder); + +/** + * @brief Adds a property to the KMS request that will change the connector + * that this CRTC is displaying content on to @param connector_id. + * + * @param builder The KMS request builder. + * @param connector_id The connector that this CRTC should display contents on. + * @returns Zero if successful, EINVAL if the @param connector_id is invalid. + */ +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id); + +/** + * @brief True if the next layer pushed using @ref kms_req_builder_push_fb_layer + * should be opaque, i.e. use a framebuffer which has a pixel format that has no + * alpha channel. + * + * This is true for the bottom-most layer. There are some display controllers + * that don't support non-opaque pixel formats for the bottom-most (primary) + * plane. So ignoring this might lead to an EINVAL on commit. + * + * @param builder The KMS request builder. + * @returns True if the next layer should preferably be opaque, false if there's + * no preference. + */ +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder); + +/** + * @brief Adds a new framebuffer (display) layer on top of the last layer. + * + * If this is the first layer, the framebuffer should cover the entire screen + * (CRTC). + * + * To allow the use of explicit fencing, specify an in_fence_fd in @param layer + * and a @param deferred_release_callback. + * + * If explicit fencing is supported: + * - the in_fence_fd should be a DRM syncobj fd that signals + * when the GPU has finished rendering to the framebuffer and is ready + * to be scanned out. + * - @param deferred_release_callback will be called + * with a DRM syncobj fd that is signaled once the framebuffer is no longer + * being displayed on screen (and can be rendered into again) + * + * If explicit fencing is not supported: + * - the in_fence_fd in @param layer will be closed by this procedure. + * - @param deferred_release_callback will NOT be called and + * @param release_callback will be called instead. + * + * Explicit fencing is supported: When atomic modesetting is being used and + * the driver supports it. (Driver has IN_FENCE_FD plane and OUT_FENCE_PTR crtc + * properties) + * + * @param builder The KMS request builder. + * @param layer The exact details (src pos, output pos, rotation, + * framebuffer) of the layer that should be shown on + * screen. + * @param release_callback Called when the framebuffer of this layer is no + * longer being shown on screen. This is called with the + * drmdev locked, so make sure to use _locked variants + * of any drmdev calls. + * @param deferred_release_callback (Unimplemented right now) If this is present, + * this callback might be called instead of + * @param release_callback. + * This is called with a DRM syncobj fd that is + * signaled when the framebuffer is no longer + * shown on screen. + * Legacy DRM modesetting does not support + * explicit fencing, in which case + * @param release_callback will be called + * instead. + * @param userdata Userdata pointer that's passed to the release_callback or + * deferred_release_callback as-is. + * @returns Zero on success, otherwise: + * - EINVAL: if attempting to push a second framebuffer layer, if + * driver supports atomic modesetting but legacy modesetting is + * being used. + * - EIO: if no DRM plane could be found that supports displaying + * this framebuffer layer. Either the pixel format is not + * supported, the modifier, the rotation or the drm device + * doesn't have enough planes. + * - The error returned by @ref close if closing the in_fence_fd + * fails. + */ +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + kms_fb_release_cb_t release_callback, + kms_deferred_fb_release_cb_t deferred_release_callback, + void *userdata +); + +/** + * @brief Push a "fake" layer that just keeps one zpos free, incase something + * other than KMS wants to display contents there. (e.g. omxplayer) + * + * @param builder The KMS request builder. + * @param zpos_out Filled with the zpos that won't be occupied by the request + * builder. + * @returns Zero. + */ +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out); + +/** + * @brief A KMS request (atomic or legacy modesetting) that can be committed to + * change the state of a single CRTC. + * + * Only way to construct this is by building a KMS request using + * @ref kms_req_builder and then calling @ref kms_req_builder_build. + */ +struct kms_req; + +DECLARE_REF_OPS(kms_req); + +/** + * @brief Build the KMS request builder into an actual, immutable KMS request + * that can be committed. Internally this doesn't do much at all. + * + * @param builder The KMS request builder that should be built. + * @returns KMS request that can be committed using @ref kms_req_commit_blocking + * or @ref kms_req_commit_nonblocking. + * The returned KMS request has refcount 1. Unref using + * @ref kms_req_unref after usage. + */ +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder); + +int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out); + +int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb); + +struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector); + +struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder); + +struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc); + +struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane); + +drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode); + +#define for_each_connector_in_drmdev(drmdev, connector) \ + for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) + +#define for_each_encoder_in_drmdev(drmdev, encoder) \ + for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) + +#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) + +#define for_each_plane_in_drmdev(drmdev, plane) for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) + +#define for_each_mode_in_connector(connector, mode) \ + for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) + +#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) + +#endif // _FLUTTERPI_SRC_MODESETTING_H diff --git a/src/notifier_listener.c b/src/notifier_listener.c index 8652df91..d8156538 100644 --- a/src/notifier_listener.c +++ b/src/notifier_listener.c @@ -1,15 +1,17 @@ -#include -#include +#include "notifier_listener.h" -#include +#include +#include struct listener { + struct list_head entry; listener_cb_t notify; void_callback_t destroy; void *userdata; }; -#define for_each_listener_in_notifier(notifier, listener) for_each_pointer_in_pset(¬ifier->listeners, listener) +#define for_each_listener_in_notifier(_notifier, _listener) \ + list_for_each_entry_safe(struct listener, _listener, &(_notifier).listeners, entry) static struct listener *listener_new(listener_cb_t notify, void_callback_t destroy, void *userdata); static void listener_destroy(struct listener *listener); @@ -23,11 +25,7 @@ int change_notifier_init(struct notifier *notifier) { return ok; } - ok = pset_init(¬ifier->listeners, PSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - return ok; - } - + list_inithead(¬ifier->listeners); notifier->is_value_notifier = false; notifier->state = NULL; notifier->value_destroy_callback = NULL; @@ -42,11 +40,7 @@ int value_notifier_init(struct notifier *notifier, void *initial_value, void_cal return ok; } - ok = pset_init(¬ifier->listeners, PSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - return ok; - } - + list_inithead(¬ifier->listeners); notifier->is_value_notifier = true; notifier->state = initial_value; notifier->value_destroy_callback = value_destroy_callback; @@ -91,13 +85,12 @@ struct notifier *value_notifier_new(void *initial_value, void_callback_t value_d } void notifier_deinit(struct notifier *notifier) { - struct listener *l; - - for_each_listener_in_notifier(notifier, l) { + for_each_listener_in_notifier(*notifier, l) { listener_destroy(l); } + pthread_mutex_destroy(¬ifier->mutex); - pset_deinit(¬ifier->listeners); + ASSERT_MSG(list_is_empty(¬ifier->listeners), "Listener list was not empty after removing all listeners"); if (notifier->value_destroy_callback != NULL) { notifier->value_destroy_callback(notifier->state); } @@ -113,8 +106,7 @@ DEFINE_LOCK_OPS(notifier, mutex) struct listener *notifier_listen(struct notifier *notifier, listener_cb_t notify, void_callback_t destroy, void *userdata) { enum listener_return r; struct listener *l; - int ok; - + l = listener_new(notify, destroy, userdata); if (l == NULL) { return NULL; @@ -128,38 +120,25 @@ struct listener *notifier_listen(struct notifier *notifier, listener_cb_t notify notifier_lock(notifier); - ok = pset_put(¬ifier->listeners, l); + list_add(&l->entry, ¬ifier->listeners); notifier_unlock(notifier); - if (ok != 0) { - listener_destroy(l); - return NULL; - } - return l; } int notifier_unlisten(struct notifier *notifier, struct listener *listener) { - int ok; - notifier_lock(notifier); - ok = pset_remove(¬ifier->listeners, listener); + listener_destroy(listener); notifier_unlock(notifier); - if (ok == 0) { - listener_destroy(listener); - } - - return ok; + return 0; } void notifier_notify(struct notifier *notifier, void *arg) { enum listener_return r; - struct listener *l, *last_kept; - int ok; notifier_lock(notifier); @@ -168,16 +147,10 @@ void notifier_notify(struct notifier *notifier, void *arg) { } notifier->state = arg; - last_kept = NULL; - for_each_listener_in_notifier(notifier, l) { + for_each_listener_in_notifier(*notifier, l) { r = listener_notify(l, arg); if (r == kUnlisten) { - ok = pset_remove(¬ifier->listeners, l); - DEBUG_ASSERT(ok == 0); (void) ok; - listener_destroy(l); - - l = last_kept; } } @@ -192,6 +165,7 @@ static struct listener *listener_new(listener_cb_t notify, void_callback_t destr return NULL; } + listener->entry = (struct list_head){ NULL, NULL }; listener->notify = notify; listener->destroy = destroy; listener->userdata = userdata; @@ -203,10 +177,14 @@ static void listener_destroy(struct listener *listener) { if (listener->destroy != NULL) { listener->destroy(listener->userdata); } + + if (list_is_linked(&listener->entry)) { + list_del(&listener->entry); + } + free(listener); } static enum listener_return listener_notify(struct listener *listener, void *arg) { return listener->notify(arg, listener->userdata); } - diff --git a/include/notifier_listener.h b/src/notifier_listener.h similarity index 88% rename from include/notifier_listener.h rename to src/notifier_listener.h index ac32397d..3861182e 100644 --- a/include/notifier_listener.h +++ b/src/notifier_listener.h @@ -1,23 +1,32 @@ -#ifndef _FLUTTERPI_INCLUDE_NOTIFIER_LISTENER_H -#define _FLUTTERPI_INCLUDE_NOTIFIER_LISTENER_H +// SPDX-License-Identifier: MIT +/* + * Notifier/Listener + * + * Basically a nicer wrapper around callbacks. + * + * Notifiers are event sources that can be listened to. A listener + * is a callbacks that's registered for listening to a notifier. + * + * Copyright (c) 2023, Hannes Winkler + */ -#include +#ifndef _FLUTTERPI_SRC_NOTIFIER_LISTENER_H +#define _FLUTTERPI_SRC_NOTIFIER_LISTENER_H -enum listener_return { - kNoAction, - kUnlisten -}; +#include "util/collection.h" +#include "util/list.h" +#include "util/lock_ops.h" -typedef enum listener_return (*listener_cb_t)(void *arg, void *userdata); +enum listener_return { kNoAction, kUnlisten }; -typedef void (*void_callback_t)(void *arg); +typedef enum listener_return (*listener_cb_t)(void *arg, void *userdata); struct listener; struct notifier { pthread_mutex_t mutex; - struct pointer_set listeners; + struct list_head listeners; bool is_value_notifier; void *state; @@ -26,50 +35,50 @@ struct notifier { /** * @brief Initialize this pre-allocated notifier object as a change notifier. - * + * * Change notifiers will only notify their listeners when @ref notifier_notify * is called. They don't call any new listeners with the last notified value, as * value notifiers do. - * + * */ int change_notifier_init(struct notifier *notifier); /** * @brief Initialize this pre-allocated notifier object as a value notifier. - * + * * Value notifiers will remember the last notified value and immediately call * any new listeners with the last notified value (or the one given to this * initializer, if @ref notifier_notify was never called). - * + * */ int value_notifier_init(struct notifier *notifier, void *initial_value, void_callback_t value_destroy_callback); /** * @brief Create a new heap allocated change notifier. - * + * * For the behaviour of change notifiers, see @ref change_notifier_init. - * + * */ struct notifier *change_notifier_new(); /** * @brief Create a new heap allocated value notifier. - * + * * For the behaviour of value notifiers, see @ref value_notifier_init. - * + * */ struct notifier *value_notifier_new(void *initial_value, void_callback_t value_destroy_callback); /** * @brief De-initialize this notifier, destroying all listeners and freeing all * allocated resources. (But not the memory @arg notifier points to itself). - * + * * Use this if you use @ref change_notifier_init or @ref value_notifier_init to * setup your notifier. - * + * * If value_destroy_callback is not NULL, will invoke the value_destroy_callback on * the last value (either initial_value or the last value given to notifier_notify). - * + * * Note that this does not wait for any currently executing callbacks to complete. * So if this is a value notifier and you call @ref notifier_deinit while any other thread * is currently inside @ref notifier_notify, this could destroy the void* value passed to @@ -80,13 +89,13 @@ void notifier_deinit(struct notifier *notifier); /** * @brief De-initialize this notifier AND free the memory @arg notifier points to. - * - * Use this if you used @ref change_notifier_new or @ref value_notifier_new to + * + * Use this if you used @ref change_notifier_new or @ref value_notifier_new to * setup your notifier. - * + * * Note that this does not wait for any currently executing listener callbacks to finish. - * - * + * + * */ void notifier_destroy(struct notifier *notifier); @@ -94,7 +103,7 @@ DECLARE_LOCK_OPS(notifier) /** * @brief Add a listener that should listen to this notifier. - * + * * @param notifier The notifier to listen to. * @param notify will be called when the event-producing object calls notifier_notify, and additionally * (if the notifier is a value notifier) one time with the current value immediately inside @@ -102,7 +111,7 @@ DECLARE_LOCK_OPS(notifier) * @param destroy will be called when the listener is destroyed for some reason. Either when @ref notifier_unlisten * was called or when the notify callback returned kUnlisten, or when the notifier is destroyed. * @param userdata The userdata to be passed to the @param notify and @param destroy callbacks. - * + * * @returns On success: A new listener object, only really useful for calling @ref notifier_unlisten, * or NULL when the @arg notifier is a value notifier, and the @arg notify function returned * kUnlisten when it was called synchronously inside the @ref notifier_listen function. @@ -113,10 +122,10 @@ struct listener *notifier_listen(struct notifier *notifier, listener_cb_t notify /** * @brief If @param listener is currently registered as a listener to @param notifier, de-register * it and destroy it. Otherwise, do nothing and return an error code. - * + * * This is only one way to de-register the listener. The other way is to return `kUnlisten` from * the listener callback. - * + * * @param notifier The notifier from which the listener should be removed. * @param listener The listener that should no longer receive events and be destroyed * (will only be destroyed when it's currently registered as a listener to notifier) @@ -127,10 +136,10 @@ int notifier_unlisten(struct notifier *notifier, struct listener *listener); /** * @brief Notify all listeners about a new value. For any listeners registered * to @arg notifier, call the listener callback with @arg arg as the value. - * + * * @param notifier The notifier for which all the listener callbacks should be called. * @param arg The value that should be send to the listeners. */ void notifier_notify(struct notifier *notifier, void *arg); -#endif // _FLUTTERPI_INCLUDE_NOTIFIER_LISTENER_H \ No newline at end of file +#endif // _FLUTTERPI_SRC_NOTIFIER_LISTENER_H diff --git a/src/pixel_format.c b/src/pixel_format.c index e7c19e06..6b936619 100644 --- a/src/pixel_format.c +++ b/src/pixel_format.c @@ -1,52 +1,85 @@ -#include - -#ifdef HAS_FBDEV -# define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ - .fbdev_format = { \ - .r = {.length = r_length, .offset = r_offset, .msb_right = 0}, \ - .g = {.length = g_length, .offset = g_offset, .msb_right = 0}, \ - .b = {.length = b_length, .offset = b_offset, .msb_right = 0}, \ - .a = {.length = a_length, .offset = a_offset, .msb_right = 0}, \ +#include "pixel_format.h" + +#include "config.h" + +#ifdef HAVE_FBDEV + #define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ + .fbdev_format = { \ + .r = { .length = r_length, .offset = r_offset, .msb_right = 0 }, \ + .g = { .length = g_length, .offset = g_offset, .msb_right = 0 }, \ + .b = { .length = b_length, .offset = b_offset, .msb_right = 0 }, \ + .a = { .length = a_length, .offset = a_offset, .msb_right = 0 }, \ }, #else -# define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) + #define FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) +#endif + +#ifdef HAVE_GBM + #include + #define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) .gbm_format = _gbm_format, +#else + #define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) #endif -#ifdef HAS_GBM -# include -# define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) .gbm_format = _gbm_format, +#ifdef HAVE_KMS + #include + #define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) .drm_format = _drm_format, #else -# define GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) + #define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) #endif -#ifdef HAS_KMS -# include -# define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) .drm_format = _drm_format, +#ifdef HAVE_VULKAN + #include "vulkan.h" + #define VK_FORMAT_FIELD_INITIALIZER(_vk_format) .vk_format = _vk_format, #else -# define DRM_FORMAT_FIELD_INITIALIZER(_drm_format) + #define VK_FORMAT_FIELD_INITIALIZER(_vk_format) #endif -#define PIXFMT_MAPPING(_name, _arg_name, _format, _bpp, _is_opaque, r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset, _gbm_format, _drm_format) \ - { \ - .name = _name, \ - .arg_name = _arg_name, \ - .format = _format, \ - .bits_per_pixel = _bpp, \ - .is_opaque = _is_opaque, \ - FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ - GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) \ - DRM_FORMAT_FIELD_INITIALIZER(_drm_format) \ - }, - -const struct pixfmt_info pixfmt_infos[] = { - PIXFMT_LIST(PIXFMT_MAPPING) -}; +// clang-format off +#define PIXFMT_MAPPING( \ + _name, \ + _arg_name, \ + _format, \ + _bpp, \ + _bit_depth, \ + _is_opaque, \ + _vk_format, \ + r_length, \ + r_offset, \ + g_length, \ + g_offset, \ + b_length, \ + b_offset, \ + a_length, \ + a_offset, \ + _gbm_format, \ + _drm_format \ +) { \ + .name = _name, \ + .arg_name = _arg_name, \ + .format = _format, \ + .bits_per_pixel = _bpp, \ + .bit_depth = _bit_depth, \ + .is_opaque = _is_opaque, \ + VK_FORMAT_FIELD_INITIALIZER(_vk_format) \ + FBDEV_FORMAT_FIELD_INITIALIZER(r_length, r_offset, g_length, g_offset, b_length, b_offset, a_length, a_offset) \ + GBM_FORMAT_FIELD_INITIALIZER(_gbm_format) DRM_FORMAT_FIELD_INITIALIZER(_drm_format) \ +}, +// clang-format on + +const struct pixfmt_info pixfmt_infos[] = { PIXFMT_LIST(PIXFMT_MAPPING) }; /// hack so we can use COMPILE_ASSERT. -enum { - n_pixfmt_infos_constexpr = sizeof(pixfmt_infos) / sizeof(*pixfmt_infos) -}; +enum { n_pixfmt_infos_constexpr = sizeof(pixfmt_infos) / sizeof(*pixfmt_infos) }; const size_t n_pixfmt_infos = n_pixfmt_infos_constexpr; -COMPILE_ASSERT(n_pixfmt_infos_constexpr == kMax_PixFmt+1); +COMPILE_ASSERT(n_pixfmt_infos_constexpr == PIXFMT_MAX + 1); + +#ifdef DEBUG +void assert_pixfmt_list_valid() { + for (enum pixfmt format = 0; format < PIXFMT_COUNT; format++) { + assert(pixfmt_infos[format].format == format); + } +} +#endif diff --git a/src/pixel_format.h b/src/pixel_format.h new file mode 100644 index 00000000..b4befd79 --- /dev/null +++ b/src/pixel_format.h @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: MIT +/* + * Pixel Formats + * + * A list of pixel formats that flutter-pi supports, with details + * about their composition. + * + * Provides a translations between DRM, EGL/GL, fbdev, vulkan and + * flutter software pixel foramts. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_PIXEL_FORMAT_H +#define _FLUTTERPI_SRC_PIXEL_FORMAT_H + +#include + +#include "util/asserts.h" +#include "util/collection.h" + +#include "config.h" + +#ifdef HAVE_FBDEV + #include + +/** + * @brief Description of a fbdev pixel format. + * + */ +struct fbdev_pixfmt { + struct fb_bitfield r, g, b, a; +}; + +#endif + +#ifdef HAVE_GBM + #include +#endif + +#ifdef HAVE_KMS + #include +#endif + +#ifdef HAVE_VULKAN + #include +#endif + +/** + * @brief A specific pixel format. Use @ref get_pixfmt_info to get information + * about this pixel format. + * + */ +enum pixfmt { + PIXFMT_RGB565, + PIXFMT_ARGB4444, + PIXFMT_XRGB4444, + PIXFMT_ARGB1555, + PIXFMT_XRGB1555, + PIXFMT_ARGB8888, + PIXFMT_XRGB8888, + PIXFMT_BGRA8888, + PIXFMT_BGRX8888, + PIXFMT_RGBA8888, + PIXFMT_RGBX8888, + PIXFMT_MAX = PIXFMT_RGBX8888, + PIXFMT_COUNT = PIXFMT_MAX + 1 +}; + +// Just a pedantic check so we don't update the pixfmt enum without changing PIXFMT_MAX +COMPILE_ASSERT(PIXFMT_MAX == PIXFMT_RGBX8888); + +// Vulkan doesn't support that many sRGB formats actually. +// There's two more (one packed and one non-packed) that aren't listed here. +/// TODO: We could support other formats as well though with manual colorspace conversions. +#define PIXFMT_LIST(V) \ + V("RGB 5:6:5", \ + "RGB565", \ + PIXFMT_RGB565, \ + /*bpp*/ 16, \ + /*bit_depth*/ 16, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 5, \ + 11, \ + /*G*/ 6, \ + 5, \ + /*B*/ 5, \ + 0, \ + /*A*/ 0, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_RGB565, \ + /*DRM fourcc*/ DRM_FORMAT_RGB565) \ + V("ARGB 4:4:4:4", \ + "ARGB4444", \ + PIXFMT_ARGB4444, \ + /*bpp*/ 16, \ + /*bit_depth*/ 12, \ + /*opaque*/ false, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 4, \ + 8, \ + /*G*/ 4, \ + 4, \ + /*B*/ 4, \ + 0, \ + /*A*/ 4, \ + 12, \ + /*GBM fourcc*/ GBM_FORMAT_ARGB4444, \ + /*DRM fourcc*/ DRM_FORMAT_ARGB4444) \ + V("XRGB 4:4:4:4", \ + "XRGB4444", \ + PIXFMT_XRGB4444, \ + /*bpp*/ 16, \ + /*bit_depth*/ 12, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 4, \ + 8, \ + /*G*/ 4, \ + 4, \ + /*B*/ 4, \ + 0, \ + /*A*/ 0, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_XRGB4444, \ + /*DRM fourcc*/ DRM_FORMAT_XRGB4444) \ + V("ARGB 1:5:5:5", \ + "ARGB1555", \ + PIXFMT_ARGB1555, \ + /*bpp*/ 16, \ + /*bit_depth*/ 15, \ + /*opaque*/ false, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 5, \ + 10, \ + /*G*/ 5, \ + 5, \ + /*B*/ 5, \ + 0, \ + /*A*/ 1, \ + 15, \ + /*GBM fourcc*/ GBM_FORMAT_ARGB1555, \ + /*DRM fourcc*/ DRM_FORMAT_ARGB1555) \ + V("XRGB 1:5:5:5", \ + "XRGB1555", \ + PIXFMT_XRGB1555, \ + /*bpp*/ 16, \ + /*bit_depth*/ 15, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 5, \ + 10, \ + /*G*/ 5, \ + 5, \ + /*B*/ 5, \ + 0, \ + /*A*/ 0, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_XRGB1555, \ + /*DRM fourcc*/ DRM_FORMAT_XRGB1555) \ + V("ARGB 8:8:8:8", \ + "ARGB8888", \ + PIXFMT_ARGB8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ false, \ + /*Vulkan format*/ VK_FORMAT_B8G8R8A8_SRGB, \ + /*R*/ 8, \ + 16, \ + /*G*/ 8, \ + 8, \ + /*B*/ 8, \ + 0, \ + /*A*/ 8, \ + 24, \ + /*GBM fourcc*/ GBM_FORMAT_ARGB8888, \ + /*DRM fourcc*/ DRM_FORMAT_ARGB8888) \ + V("XRGB 8:8:8:8", \ + "XRGB8888", \ + PIXFMT_XRGB8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 8, \ + 16, \ + /*G*/ 8, \ + 8, \ + /*B*/ 8, \ + 0, \ + /*A*/ 0, \ + 24, \ + /*GBM fourcc*/ GBM_FORMAT_XRGB8888, \ + /*DRM fourcc*/ DRM_FORMAT_XRGB8888) \ + V("BGRA 8:8:8:8", \ + "BGRA8888", \ + PIXFMT_BGRA8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ false, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 8, \ + 8, \ + /*G*/ 8, \ + 16, \ + /*B*/ 8, \ + 24, \ + /*A*/ 8, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_BGRA8888, \ + /*DRM fourcc*/ DRM_FORMAT_BGRA8888) \ + V("BGRX 8:8:8:8", \ + "BGRX8888", \ + PIXFMT_BGRX8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 8, \ + 8, \ + /*G*/ 8, \ + 16, \ + /*B*/ 8, \ + 24, \ + /*A*/ 0, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_BGRX8888, \ + /*DRM fourcc*/ DRM_FORMAT_BGRX8888) \ + V("RGBA 8:8:8:8", \ + "RGBA8888", \ + PIXFMT_RGBA8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ false, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 8, \ + 24, \ + /*G*/ 8, \ + 16, \ + /*B*/ 8, \ + 8, \ + /*A*/ 8, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_RGBA8888, \ + /*DRM fourcc*/ DRM_FORMAT_RGBA8888) \ + V("RGBX 8:8:8:8", \ + "RGBX8888", \ + PIXFMT_RGBX8888, \ + /*bpp*/ 32, \ + /*bit_depth*/ 24, \ + /*opaque*/ true, \ + /*Vulkan format*/ VK_FORMAT_UNDEFINED, \ + /*R*/ 8, \ + 24, \ + /*G*/ 8, \ + 16, \ + /*B*/ 8, \ + 8, \ + /*A*/ 0, \ + 0, \ + /*GBM fourcc*/ GBM_FORMAT_RGBX8888, \ + /*DRM fourcc*/ DRM_FORMAT_RGBX8888) + +// make sure the macro list we defined has as many elements as the pixfmt enum. +#define __COUNT(...) +1 +COMPILE_ASSERT(0 PIXFMT_LIST(__COUNT) == PIXFMT_MAX + 1); +#undef __COUNT + +static inline enum pixfmt pixfmt_opaque(enum pixfmt format) { + if (format == PIXFMT_ARGB8888) { + return PIXFMT_XRGB8888; + } else if (format == PIXFMT_ARGB4444) { + return PIXFMT_XRGB4444; + } else if (format == PIXFMT_ARGB1555) { + return PIXFMT_XRGB1555; + } else if (format == PIXFMT_BGRA8888) { + return PIXFMT_BGRX8888; + } else if (format == PIXFMT_RGBA8888) { + return PIXFMT_RGBX8888; + } + + /// TODO: We're potentially returning a non-opaque format here. + return format; +} + +/** + * @brief Information about a pixel format. + * + */ +struct pixfmt_info { + /** + * @brief A descriptive, human-readable name for this pixel format. + * + * Example: RGB 5:6:5 + */ + const char *name; + + /** + * @brief A short, unique name for this pixel format, to use it as a commandline argument for example. + * + * Example: RGB565 + * + */ + const char *arg_name; + + /** + * @brief The pixel format that this struct provides information about. + */ + enum pixfmt format; + + /** + * @brief How many bits per pixel does this pixel format use? + */ + int bits_per_pixel; + + /** + * @brief How many bits of the @ref bits_per_pixel are used for color (R / G / B)? + * + */ + int bit_depth; + + /** + * @brief True if there's no way to specify transparency with this format. + */ + bool is_opaque; + +#ifdef HAVE_FBDEV + /** + * @brief The fbdev format equivalent to this pixel format. + */ + struct fbdev_pixfmt fbdev_format; +#endif +#ifdef HAVE_GBM + /** + * @brief The GBM format equivalent to this pixel format. + */ + uint32_t gbm_format; +#endif +#ifdef HAVE_KMS + /** + * @brief The DRM format equivalent to this pixel format. + */ + uint32_t drm_format; +#endif +#ifdef HAVE_VULKAN + /** + * @brief The vulkan equivalent of this pixel format. + */ + VkFormat vk_format; +#endif +}; + +/** + * @brief A list of known pixel-formats, with some details about them. + * + */ +extern const struct pixfmt_info pixfmt_infos[]; +extern const size_t n_pixfmt_infos; + +#ifdef DEBUG +void assert_pixfmt_list_valid(); +#endif + +/** + * @brief Get the pixel format info for a specific pixel format. + * + */ +ATTR_CONST static inline const struct pixfmt_info *get_pixfmt_info(enum pixfmt format) { + assert(format >= 0 && format <= PIXFMT_MAX); +#ifdef DEBUG + assert_pixfmt_list_valid(); +#endif + return pixfmt_infos + format; +} + +ATTR_CONST static inline bool has_pixfmt_for_drm_format(uint32_t fourcc) { + for (int i = 0; i < n_pixfmt_infos; i++) { + if (get_pixfmt_info(i)->drm_format == fourcc) { + return true; + } + } + + return false; +} + +ATTR_CONST static inline enum pixfmt get_pixfmt_for_drm_format(uint32_t fourcc) { + for (int i = 0; i < n_pixfmt_infos; i++) { + if (get_pixfmt_info(i)->drm_format == fourcc) { + return i; + } + } + + ASSERT_MSG(false, "Check has_pixfmt_for_drm_format if an enum pixfmt exists for a specific DRM fourcc."); + return PIXFMT_RGB565; +} + +COMPILE_ASSERT(PIXFMT_RGB565 == 0); + +#define ASSERT_PIXFMT_VALID(format) ASSERT_MSG(format >= PIXFMT_RGB565 && format <= PIXFMT_MAX, "Invalid pixel format") +#define ASSUME_PIXFMT_VALID(format) ASSUME((format) >= PIXFMT_RGB565 && (format) <= PIXFMT_MAX) + +#endif // _FLUTTERPI_SRC_PIXEL_FORMAT_H diff --git a/src/platformchannel.c b/src/platformchannel.c index ba6efc53..d063258a 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1,3 +1,5 @@ +#include "platformchannel.h" + #include #include #include @@ -6,71 +8,70 @@ #include #include #include -#include -#include -#include -#include +#include +#include "flutter-pi.h" +#include "jsmn.h" +#include "util/asserts.h" struct platch_msg_resp_handler_data { - enum platch_codec codec; - platch_msg_resp_callback on_response; - void *userdata; + enum platch_codec codec; + platch_msg_resp_callback on_response; + void *userdata; }; - static int _check_remaining(size_t *remaining, int min_remaining) { - if (remaining == NULL) { - return 0; - } - if (*remaining < min_remaining) { - return EBADMSG; - } - return 0; -} -static int _read(uint8_t **pbuffer, void *dest, int n_bytes, size_t *remaining) { - int ok; - - ok = _check_remaining(remaining, n_bytes); - if (ok != 0) { - return ok; - } - - memcpy(dest, *pbuffer, (size_t) n_bytes); - if (remaining != NULL) { - *remaining -= n_bytes; - } - *pbuffer += n_bytes; - return 0; + if (remaining == NULL) { + return 0; + } + if (*remaining < min_remaining) { + return EBADMSG; + } + return 0; +} +static int _read(const uint8_t **pbuffer, void *dest, int n_bytes, size_t *remaining) { + int ok; + + ok = _check_remaining(remaining, n_bytes); + if (ok != 0) { + return ok; + } + + memcpy(dest, *pbuffer, (size_t) n_bytes); + if (remaining != NULL) { + *remaining -= n_bytes; + } + *pbuffer += n_bytes; + return 0; } static int _write(uint8_t **pbuffer, void *src, int n_bytes, size_t *remaining) { - int ok; + int ok; - ok = _check_remaining(remaining, n_bytes); - if (ok != 0) { - return ok; - } + ok = _check_remaining(remaining, n_bytes); + if (ok != 0) { + return ok; + } - memcpy(*pbuffer, src, (size_t) n_bytes); - if (remaining != NULL) { - *remaining -= n_bytes; - } - *pbuffer += n_bytes; - return 0; + memcpy(*pbuffer, src, (size_t) n_bytes); + if (remaining != NULL) { + *remaining -= n_bytes; + } + *pbuffer += n_bytes; + return 0; } static int _advance(uintptr_t *value, int n_bytes, size_t *remaining) { - int ok; + int ok; - ok = _check_remaining(remaining, n_bytes); - if (ok != 0) { - return ok; - } + ok = _check_remaining(remaining, n_bytes); + if (ok != 0) { + return ok; + } - if (remaining != NULL) { - *remaining -= n_bytes; - } + if (remaining != NULL) { + *remaining -= n_bytes; + } *value += n_bytes; return 0; } @@ -78,1651 +79,2396 @@ static int _align(uintptr_t *value, int alignment, size_t *remaining) { int diff; alignment--; - diff = ((((*value) + alignment) | alignment) - alignment) - *value; + diff = ((((*value) + alignment) | alignment) - alignment) - *value; return _advance(value, diff, remaining); } static int _advance_size_bytes(uintptr_t *value, size_t size, size_t *remaining) { if (size < 254) { - return _advance(value, 1, remaining); - } else if (size <= 0xFFFF) { - return _advance(value, 3, remaining); - } else { - return _advance(value, 5, remaining); + return _advance(value, 1, remaining); + } else if (size <= 0xFFFF) { + return _advance(value, 3, remaining); + } else { + return _advance(value, 5, remaining); } } -#define DEFINE_READ_WRITE_FUNC(suffix, value_type) \ -static int _write_##suffix(uint8_t **pbuffer, value_type value, size_t *remaining) { \ - return _write(pbuffer, &value, sizeof value, remaining); \ -} \ -static int _read_##suffix(uint8_t **pbuffer, value_type* value, size_t *remaining) { \ - return _read(pbuffer, value, sizeof *value, remaining); \ -} +#define DEFINE_READ_WRITE_FUNC(suffix, value_type) \ + UNUSED static int _write_##suffix(uint8_t **pbuffer, value_type value, size_t *remaining) { \ + return _write(pbuffer, &value, sizeof value, remaining); \ + } \ + UNUSED static int _read_##suffix(const uint8_t **pbuffer, value_type *value, size_t *remaining) { \ + return _read(pbuffer, value, sizeof *value, remaining); \ + } -DEFINE_READ_WRITE_FUNC( u8, uint8_t) -DEFINE_READ_WRITE_FUNC( u16, uint16_t) -DEFINE_READ_WRITE_FUNC( u32, uint32_t) -DEFINE_READ_WRITE_FUNC( u64, uint64_t) -DEFINE_READ_WRITE_FUNC( i8, int8_t) -DEFINE_READ_WRITE_FUNC( i16, int16_t) -DEFINE_READ_WRITE_FUNC( i32, int32_t) -DEFINE_READ_WRITE_FUNC( i64, int64_t) -DEFINE_READ_WRITE_FUNC( float, float) -DEFINE_READ_WRITE_FUNC(double, double) +DEFINE_READ_WRITE_FUNC(u8, uint8_t) +DEFINE_READ_WRITE_FUNC(u16, uint16_t) +DEFINE_READ_WRITE_FUNC(u32, uint32_t) +DEFINE_READ_WRITE_FUNC(u64, uint64_t) +DEFINE_READ_WRITE_FUNC(i8, int8_t) +DEFINE_READ_WRITE_FUNC(i16, int16_t) +DEFINE_READ_WRITE_FUNC(i32, int32_t) +DEFINE_READ_WRITE_FUNC(i64, int64_t) +DEFINE_READ_WRITE_FUNC(float, float) +DEFINE_READ_WRITE_FUNC(double, double) static int _writeSize(uint8_t **pbuffer, int size, size_t *remaining) { - int ok; + int ok; if (size < 254) { - return _write_u8(pbuffer, (uint8_t) size, remaining); - } else if (size <= 0xFFFF) { - ok = _write_u8(pbuffer, 0xFE, remaining); - if (ok != 0) return ok; - - ok = _write_u16(pbuffer, (uint16_t) size, remaining); - if (ok != 0) return ok; - } else { - ok = _write_u8(pbuffer, 0xFF, remaining); - if (ok != 0) return ok; - - ok = _write_u32(pbuffer, (uint32_t) size, remaining); - if (ok != 0) return ok; + return _write_u8(pbuffer, (uint8_t) size, remaining); + } else if (size <= 0xFFFF) { + ok = _write_u8(pbuffer, 0xFE, remaining); + if (ok != 0) + return ok; + + ok = _write_u16(pbuffer, (uint16_t) size, remaining); + if (ok != 0) + return ok; + } else { + ok = _write_u8(pbuffer, 0xFF, remaining); + if (ok != 0) + return ok; + + ok = _write_u32(pbuffer, (uint32_t) size, remaining); + if (ok != 0) + return ok; } return ok; } -static int _readSize(uint8_t **pbuffer, uint32_t *psize, size_t *remaining) { - int ok; +static int _readSize(const uint8_t **pbuffer, uint32_t *psize, size_t *remaining) { + int ok; uint8_t size8; uint16_t size16; - ok = _read_u8(pbuffer, &size8, remaining); - if (ok != 0) return ok; - + ok = _read_u8(pbuffer, &size8, remaining); + if (ok != 0) + return ok; + if (size8 <= 253) { *psize = size8; return 0; } else if (size8 == 254) { - ok = _read_u16(pbuffer, &size16, remaining); - if (ok != 0) return ok; + ok = _read_u16(pbuffer, &size16, remaining); + if (ok != 0) + return ok; *psize = size16; return 0; - } else if (size8 == 255) { - return _read_u32(pbuffer, psize, remaining); - } + } else if (size8 == 255) { + return _read_u32(pbuffer, psize, remaining); + } return 0; } - int platch_free_value_std(struct std_value *value) { - int ok; - - switch (value->type) { - case kStdString: - free(value->string_value); - break; - case kStdList: - for (int i=0; i < value->size; i++) { - ok = platch_free_value_std(&(value->list[i])); - if (ok != 0) return ok; - } - free(value->list); - break; - case kStdMap: - for (int i=0; i < value->size; i++) { - ok = platch_free_value_std(&(value->keys[i])); - if (ok != 0) return ok; - ok = platch_free_value_std(&(value->values[i])); - if (ok != 0) return ok; - } - free(value->keys); - break; - default: - break; - } - - return 0; + int ok; + + switch (value->type) { + case kStdString: free(value->string_value); break; + case kStdList: + for (int i = 0; i < value->size; i++) { + ok = platch_free_value_std(&(value->list[i])); + if (ok != 0) + return ok; + } + free(value->list); + break; + case kStdMap: + for (int i = 0; i < value->size; i++) { + ok = platch_free_value_std(&(value->keys[i])); + if (ok != 0) + return ok; + ok = platch_free_value_std(&(value->values[i])); + if (ok != 0) + return ok; + } + free(value->keys); + break; + default: break; + } + + return 0; } int platch_free_json_value(struct json_value *value, bool shallow) { - int ok; - - switch (value->type) { - case kJsonArray: - if (!shallow) { - for (int i = 0; i < value->size; i++) { - ok = platch_free_json_value(&(value->array[i]), false); - if (ok != 0) return ok; - } - } - - free(value->array); - break; - case kJsonObject: - if (!shallow) { - for (int i = 0; i < value->size; i++) { - ok = platch_free_json_value(&(value->values[i]), false); - if (ok != 0) return ok; - } - } - - free(value->keys); - break; - default: - break; - } - - return 0; + int ok; + + switch (value->type) { + case kJsonArray: + if (!shallow) { + for (int i = 0; i < value->size; i++) { + ok = platch_free_json_value(&(value->array[i]), false); + if (ok != 0) + return ok; + } + } + + free(value->array); + break; + case kJsonObject: + if (!shallow) { + for (int i = 0; i < value->size; i++) { + ok = platch_free_json_value(&(value->values[i]), false); + if (ok != 0) + return ok; + } + } + + free(value->keys); + free(value->values); + break; + default: break; + } + + return 0; } int platch_free_obj(struct platch_obj *object) { - switch (object->codec) { - case kStringCodec: - free(object->string_value); - break; - case kBinaryCodec: - break; - case kJSONMessageCodec: - platch_free_json_value(&(object->json_value), false); - break; - case kStandardMessageCodec: - platch_free_value_std(&(object->std_value)); - break; - case kStandardMethodCall: - free(object->method); - platch_free_value_std(&(object->std_arg)); - break; - case kJSONMethodCall: - platch_free_json_value(&(object->json_arg), false); - break; - default: - break; - } - - return 0; -} - -int platch_calc_value_size_std(struct std_value* value, size_t* size_out) { - enum std_value_type type = value->type; - uintptr_t size = (uintptr_t) *size_out; - size_t element_size, sizet_size = 0; - int ok; - - // Type Byte - _advance(&size, 1, NULL); - switch (type) { - case kStdNull: - case kStdTrue: - case kStdFalse: - break; - case kStdInt32: - _advance(&size, 4, NULL); - break; - case kStdInt64: - _advance(&size, 8, NULL); - break; - case kStdFloat64: - _align (&size, 8, NULL); - _advance(&size, 8, NULL); - break; - case kStdString: - case kStdLargeInt: - element_size = strlen(value->string_value); - _advance_size_bytes(&size, element_size, NULL); - _advance(&size, element_size, NULL); - break; - case kStdUInt8Array: - element_size = value->size; - _advance_size_bytes(&size, element_size, NULL); - _advance(&size, element_size, NULL); - break; - case kStdInt32Array: - element_size = value->size; - - _advance_size_bytes(&size, element_size, NULL); - _align (&size, 4, NULL); - _advance(&size, element_size*4, NULL); - - break; - case kStdInt64Array: - element_size = value->size; - - _advance_size_bytes(&size, element_size, NULL); - _align (&size, 8, NULL); - _advance(&size, element_size*8, NULL); - - break; - case kStdFloat64Array: - element_size = value->size; - - _advance_size_bytes(&size, element_size, NULL); - _align (&size, 8, NULL); - _advance(&size, element_size*8, NULL); - - break; - case kStdList: - element_size = value->size; - - _advance_size_bytes(&size, element_size, NULL); - for (int i = 0; ilist[i]), &sizet_size); - if (ok != 0) return ok; - - size = (uintptr_t) sizet_size; - } - - break; - case kStdMap: - element_size = value->size; - - _advance_size_bytes(&size, element_size, NULL); - for (int i = 0; ikeys[i]), &sizet_size); - if (ok != 0) return ok; - - ok = platch_calc_value_size_std(&(value->values[i]), &sizet_size); - if (ok != 0) return ok; - - size = (uintptr_t) sizet_size; - } - - break; - default: - return EINVAL; - } - - *size_out = (size_t) size; - - return 0; -} -int platch_write_value_to_buffer_std(struct std_value* value, uint8_t **pbuffer) { - uint8_t* byteArray; - size_t size; - int ok; - - _write_u8(pbuffer, value->type, NULL); - - switch (value->type) { - case kStdNull: - case kStdTrue: - case kStdFalse: - break; - case kStdInt32: - _write_i32(pbuffer, value->int32_value, NULL); - break; - case kStdInt64: - _write_i64(pbuffer, value->int64_value, NULL); - break; - case kStdFloat64: - _align ((uintptr_t*) pbuffer, 8, NULL); - _write_double(pbuffer, value->float64_value, NULL); - break; - case kStdLargeInt: - case kStdString: - case kStdUInt8Array: - if ((value->type == kStdLargeInt) || (value->type == kStdString)) { - size = strlen(value->string_value); - byteArray = (uint8_t*) value->string_value; - } else { - DEBUG_ASSERT(value->type == kStdUInt8Array); - size = value->size; - byteArray = value->uint8array; - } - - _writeSize(pbuffer, size, NULL); - for (int i=0; isize; - - _writeSize(pbuffer, size, NULL); - _align ((uintptr_t*) pbuffer, 4, NULL); - - for (int i=0; iint32array[i], NULL); - } - break; - case kStdInt64Array: - size = value->size; - - _writeSize(pbuffer, size, NULL); - _align((uintptr_t*) pbuffer, 8, NULL); - for (int i=0; iint64array[i], NULL); - } - break; - case kStdFloat64Array: - size = value->size; - - _writeSize(pbuffer, size, NULL); - _align((uintptr_t*) pbuffer, 8, NULL); - - for (int i=0; ifloat64array[i], NULL); - } - break; - case kStdList: - size = value->size; - - _writeSize(pbuffer, size, NULL); - for (int i=0; i < size; i++) { - ok = platch_write_value_to_buffer_std(&value->list[i], pbuffer); - if (ok != 0) return ok; - } - - break; - case kStdMap: - size = value->size; - - _writeSize(pbuffer, size, NULL); - for (int i=0; ikeys[i], pbuffer); - if (ok != 0) return ok; - - ok = platch_write_value_to_buffer_std(&value->values[i], pbuffer); - if (ok != 0) return ok; - } - break; - default: - return EINVAL; - } - - return 0; + switch (object->codec) { + case kStringCodec: free(object->string_value); break; + case kBinaryCodec: break; + case kJSONMessageCodec: platch_free_json_value(&(object->json_value), false); break; + case kStandardMessageCodec: platch_free_value_std(&(object->std_value)); break; + case kStandardMethodCall: + free(object->method); + platch_free_value_std(&(object->std_arg)); + break; + case kJSONMethodCall: platch_free_json_value(&(object->json_arg), false); break; + default: break; + } + + return 0; } -size_t platch_calc_value_size_json(struct json_value *value) { - size_t size = 0; - - switch (value->type) { - case kJsonNull: - case kJsonTrue: - return 4; - case kJsonFalse: - return 5; - case kJsonNumber: ; - char numBuffer[32]; - return sprintf(numBuffer, "%g", value->number_value); - case kJsonString: - size = 2; - - // we need to count how many characters we need to escape. - for (char *s = value->string_value; *s; s++) { - switch (*s) { - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - case '\"': - case '\\': - size += 2; - break; - default: - size++; - break; - } - } - - return size; - case kJsonArray: - size += 2; - for (int i=0; i < value->size; i++) { - size += platch_calc_value_size_json(value->array + i); - if (i+1 != value->size) size += 1; - } - return size; - case kJsonObject: - size += 2; - for (int i=0; i < value->size; i++) { - size += strlen(value->keys[i]) + 3 + platch_calc_value_size_json(&(value->values[i])); - if (i+1 != value->size) size += 1; - } - return size; - default: - return EINVAL; - } - - return 0; -} -int platch_write_value_to_buffer_json(struct json_value* value, uint8_t **pbuffer) { - switch (value->type) { - case kJsonNull: - *pbuffer += sprintf((char*) *pbuffer, "null"); - break; - case kJsonTrue: - *pbuffer += sprintf((char*) *pbuffer, "true"); - break; - case kJsonFalse: - *pbuffer += sprintf((char*) *pbuffer, "false"); - break; - case kJsonNumber: - *pbuffer += sprintf((char*) *pbuffer, "%g", value->number_value); - break; - case kJsonString: - *((*pbuffer)++) = '\"'; - - for (char *s = value->string_value; *s; s++) { - switch (*s) { - case '\b': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 'b'; - break; - case '\f': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 'f'; - break; - case '\n': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 'n'; - break; - case '\r': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 'r'; - break; - case '\t': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 't'; - break; - case '\"': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = 't'; - break; - case '\\': - *((*pbuffer)++) = '\\'; - *((*pbuffer)++) = '\\'; - break; - default: - *((*pbuffer)++) = *s; - break; - } - } - - *((*pbuffer)++) = '\"'; - - break; - case kJsonArray: - *pbuffer += sprintf((char*) *pbuffer, "["); - for (int i=0; i < value->size; i++) { - platch_write_value_to_buffer_json(&(value->array[i]), pbuffer); - if (i+1 != value->size) *pbuffer += sprintf((char*) *pbuffer, ","); - } - *pbuffer += sprintf((char*) *pbuffer, "]"); - break; - case kJsonObject: - *pbuffer += sprintf((char*) *pbuffer, "{"); - for (int i=0; i < value->size; i++) { - *pbuffer += sprintf((char*) *pbuffer, "\"%s\":", value->keys[i]); - platch_write_value_to_buffer_json(&(value->values[i]), pbuffer); - if (i+1 != value->size) *pbuffer += sprintf((char*) *pbuffer, ","); - } - *pbuffer += sprintf((char*) *pbuffer, "}"); - break; - default: - return EINVAL; - } - - return 0; -} -int platch_decode_value_std(uint8_t **pbuffer, size_t *premaining, struct std_value *value_out) { - enum std_value_type type; - uint8_t type_byte; - uint32_t size; - int ok; - - /// FIXME: Somehow, in release mode, this always reads 0. - ok = _read_u8(pbuffer, &type_byte, premaining); - if (ok != 0) return ok; - - type = (enum std_value_type) type_byte; - value_out->type = (enum std_value_type) type_byte; - switch (type) { - case kStdNull: - case kStdTrue: - case kStdFalse: - break; - case kStdInt32: - ok = _read_i32(pbuffer, &value_out->int32_value, premaining); - if (ok != 0) return ok; - - break; - case kStdInt64: - ok = _read_i64(pbuffer, &value_out->int64_value, premaining); - if (ok != 0) return ok; - - break; - case kStdFloat64: - ok = _align((uintptr_t*) pbuffer, 8, premaining); - if (ok != 0) return ok; - - ok = _read_double(pbuffer, &value_out->float64_value, premaining); - if (ok != 0) return ok; - - break; - case kStdLargeInt: - case kStdString: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - - value_out->string_value = calloc(size+1, sizeof(char)); - if (!value_out->string_value) return ENOMEM; - - ok = _read(pbuffer, value_out->string_value, size, premaining); - if (ok != 0) { - free(value_out->string_value); - return ok; - } - - break; - case kStdUInt8Array: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - if (*premaining < size) return EBADMSG; - - value_out->size = size; - value_out->uint8array = *pbuffer; - - ok = _advance((uintptr_t*) pbuffer, size, premaining); - if (ok != 0) return ok; - - break; - case kStdInt32Array: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - - ok = _align((uintptr_t*) pbuffer, 4, premaining); - if (ok != 0) return ok; - - if (*premaining < size*4) return EBADMSG; - - value_out->size = size; - value_out->int32array = (int32_t*) *pbuffer; - - ok = _advance((uintptr_t*) pbuffer, size*4, premaining); - if (ok != 0) return ok; - - break; - case kStdInt64Array: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - - ok = _align((uintptr_t*) pbuffer, 8, premaining); - if (ok != 0) return ok; - - if (*premaining < size*8) return EBADMSG; - - value_out->size = size; - value_out->int64array = (int64_t*) *pbuffer; - - ok = _advance((uintptr_t*) pbuffer, size*8, premaining); - if (ok != 0) return ok; - - break; - case kStdFloat64Array: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - - ok = _align((uintptr_t*) pbuffer, 8, premaining); - if (ok != 0) return ok; - - if (*premaining < size*8) return EBADMSG; - - value_out->size = size; - value_out->float64array = (double*) *pbuffer; - - ok = _advance((uintptr_t*) pbuffer, size*8, premaining); - if (ok != 0) return ok; - - break; - case kStdList: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; - - value_out->size = size; - value_out->list = calloc(size, sizeof(struct std_value)); - for (int i = 0; i < size; i++) { - ok = platch_decode_value_std(pbuffer, premaining, &value_out->list[i]); - if (ok != 0) return ok; - } +int platch_calc_value_size_std(struct std_value *value, size_t *size_out) { + enum std_value_type type = value->type; + uintptr_t size = (uintptr_t) *size_out; + size_t element_size, sizet_size = 0; + int ok; + + // Type Byte + _advance(&size, 1, NULL); + switch (type) { + case kStdNull: + case kStdTrue: + case kStdFalse: break; + case kStdInt32: _advance(&size, 4, NULL); break; + case kStdInt64: _advance(&size, 8, NULL); break; + case kStdFloat64: + _align(&size, 8, NULL); + _advance(&size, 8, NULL); + break; + case kStdString: + case kStdLargeInt: + element_size = strlen(value->string_value); + _advance_size_bytes(&size, element_size, NULL); + _advance(&size, element_size, NULL); + break; + case kStdUInt8Array: + element_size = value->size; + _advance_size_bytes(&size, element_size, NULL); + _advance(&size, element_size, NULL); + break; + case kStdInt32Array: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + _align(&size, 4, NULL); + _advance(&size, element_size * 4, NULL); + + break; + case kStdInt64Array: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + _align(&size, 8, NULL); + _advance(&size, element_size * 8, NULL); + + break; + case kStdFloat64Array: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + _align(&size, 8, NULL); + _advance(&size, element_size * 8, NULL); + + break; + case kStdList: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + for (int i = 0; i < element_size; i++) { + sizet_size = (size_t) size; + + ok = platch_calc_value_size_std(&(value->list[i]), &sizet_size); + if (ok != 0) + return ok; + + size = (uintptr_t) sizet_size; + } + + break; + case kStdMap: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + for (int i = 0; i < element_size; i++) { + sizet_size = (size_t) size; + + ok = platch_calc_value_size_std(&(value->keys[i]), &sizet_size); + if (ok != 0) + return ok; + + ok = platch_calc_value_size_std(&(value->values[i]), &sizet_size); + if (ok != 0) + return ok; + + size = (uintptr_t) sizet_size; + } + + break; + default: return EINVAL; + } - break; - case kStdMap: - ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) return ok; + *size_out = (size_t) size; - value_out->size = size; + return 0; +} +int platch_write_value_to_buffer_std(struct std_value *value, uint8_t **pbuffer) { + const uint8_t *byteArray; + size_t size; + int ok; + + _write_u8(pbuffer, value->type, NULL); + + switch (value->type) { + case kStdNull: + case kStdTrue: + case kStdFalse: break; + case kStdInt32: _write_i32(pbuffer, value->int32_value, NULL); break; + case kStdInt64: _write_i64(pbuffer, value->int64_value, NULL); break; + case kStdFloat64: + _align((uintptr_t *) pbuffer, 8, NULL); + _write_double(pbuffer, value->float64_value, NULL); + break; + case kStdLargeInt: + case kStdString: + case kStdUInt8Array: + if ((value->type == kStdLargeInt) || (value->type == kStdString)) { + size = strlen(value->string_value); + byteArray = (uint8_t *) value->string_value; + } else { + assert(value->type == kStdUInt8Array); + size = value->size; + byteArray = value->uint8array; + } + + _writeSize(pbuffer, size, NULL); + for (int i = 0; i < size; i++) { + _write_u8(pbuffer, byteArray[i], NULL); + } + break; + case kStdInt32Array: + size = value->size; + + _writeSize(pbuffer, size, NULL); + _align((uintptr_t *) pbuffer, 4, NULL); + + for (int i = 0; i < size; i++) { + _write_i32(pbuffer, value->int32array[i], NULL); + } + break; + case kStdInt64Array: + size = value->size; + + _writeSize(pbuffer, size, NULL); + _align((uintptr_t *) pbuffer, 8, NULL); + for (int i = 0; i < size; i++) { + _write_i64(pbuffer, value->int64array[i], NULL); + } + break; + case kStdFloat64Array: + size = value->size; + + _writeSize(pbuffer, size, NULL); + _align((uintptr_t *) pbuffer, 8, NULL); + + for (int i = 0; i < size; i++) { + _write_double(pbuffer, value->float64array[i], NULL); + } + break; + case kStdList: + size = value->size; + + _writeSize(pbuffer, size, NULL); + for (int i = 0; i < size; i++) { + ok = platch_write_value_to_buffer_std(&value->list[i], pbuffer); + if (ok != 0) + return ok; + } + + break; + case kStdMap: + size = value->size; + + _writeSize(pbuffer, size, NULL); + for (int i = 0; i < size; i++) { + ok = platch_write_value_to_buffer_std(&value->keys[i], pbuffer); + if (ok != 0) + return ok; + + ok = platch_write_value_to_buffer_std(&value->values[i], pbuffer); + if (ok != 0) + return ok; + } + break; + default: return EINVAL; + } - value_out->keys = calloc(size*2, sizeof(struct std_value)); - if (!value_out->keys) return ENOMEM; + return 0; +} +size_t platch_calc_value_size_json(struct json_value *value) { + size_t size = 0; + + switch (value->type) { + case kJsonNull: + case kJsonTrue: return 4; + case kJsonFalse: return 5; + case kJsonNumber:; char numBuffer[32]; return sprintf(numBuffer, "%g", value->number_value); + case kJsonString: + size = 2; + + // we need to count how many characters we need to escape. + for (char *s = value->string_value; *s; s++) { + switch (*s) { + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '\"': + case '\\': size += 2; break; + default: size++; break; + } + } + + return size; + case kJsonArray: + size += 2; + for (int i = 0; i < value->size; i++) { + size += platch_calc_value_size_json(value->array + i); + if (i + 1 != value->size) + size += 1; + } + return size; + case kJsonObject: + size += 2; + for (int i = 0; i < value->size; i++) { + size += strlen(value->keys[i]) + 3 + platch_calc_value_size_json(&(value->values[i])); + if (i + 1 != value->size) + size += 1; + } + return size; + default: return EINVAL; + } - value_out->values = &value_out->keys[size]; + return 0; +} +int platch_write_value_to_buffer_json(struct json_value *value, uint8_t **pbuffer) { + switch (value->type) { + case kJsonNull: *pbuffer += sprintf((char *) *pbuffer, "null"); break; + case kJsonTrue: *pbuffer += sprintf((char *) *pbuffer, "true"); break; + case kJsonFalse: *pbuffer += sprintf((char *) *pbuffer, "false"); break; + case kJsonNumber: *pbuffer += sprintf((char *) *pbuffer, "%g", value->number_value); break; + case kJsonString: + *((*pbuffer)++) = '\"'; + + for (char *s = value->string_value; *s; s++) { + switch (*s) { + case '\b': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 'b'; + break; + case '\f': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 'f'; + break; + case '\n': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 'n'; + break; + case '\r': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 'r'; + break; + case '\t': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 't'; + break; + case '\"': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = 't'; + break; + case '\\': + *((*pbuffer)++) = '\\'; + *((*pbuffer)++) = '\\'; + break; + default: *((*pbuffer)++) = *s; break; + } + } + + *((*pbuffer)++) = '\"'; + + break; + case kJsonArray: + *pbuffer += sprintf((char *) *pbuffer, "["); + for (int i = 0; i < value->size; i++) { + platch_write_value_to_buffer_json(&(value->array[i]), pbuffer); + if (i + 1 != value->size) + *pbuffer += sprintf((char *) *pbuffer, ","); + } + *pbuffer += sprintf((char *) *pbuffer, "]"); + break; + case kJsonObject: + *pbuffer += sprintf((char *) *pbuffer, "{"); + for (int i = 0; i < value->size; i++) { + *pbuffer += sprintf((char *) *pbuffer, "\"%s\":", value->keys[i]); + platch_write_value_to_buffer_json(&(value->values[i]), pbuffer); + if (i + 1 != value->size) + *pbuffer += sprintf((char *) *pbuffer, ","); + } + *pbuffer += sprintf((char *) *pbuffer, "}"); + break; + default: return EINVAL; + } - for (int i = 0; i < size; i++) { - ok = platch_decode_value_std(pbuffer, premaining, &(value_out->keys[i])); - if (ok != 0) return ok; - - ok = platch_decode_value_std(pbuffer, premaining, &(value_out->values[i])); - if (ok != 0) return ok; - } + return 0; +} +int platch_decode_value_std(const uint8_t **pbuffer, size_t *premaining, struct std_value *value_out) { + enum std_value_type type; + uint8_t type_byte; + uint32_t size; + int ok; + + /// FIXME: Somehow, in release mode, this always reads 0. + ok = _read_u8(pbuffer, &type_byte, premaining); + if (ok != 0) + return ok; + + type = (enum std_value_type) type_byte; + value_out->type = (enum std_value_type) type_byte; + switch (type) { + case kStdNull: + case kStdTrue: + case kStdFalse: break; + case kStdInt32: + ok = _read_i32(pbuffer, &value_out->int32_value, premaining); + if (ok != 0) + return ok; + + break; + case kStdInt64: + ok = _read_i64(pbuffer, &value_out->int64_value, premaining); + if (ok != 0) + return ok; + + break; + case kStdFloat64: + ok = _align((uintptr_t *) pbuffer, 8, premaining); + if (ok != 0) + return ok; + + ok = _read_double(pbuffer, &value_out->float64_value, premaining); + if (ok != 0) + return ok; + + break; + case kStdLargeInt: + case kStdString: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + value_out->string_value = calloc(size + 1, sizeof(char)); + if (!value_out->string_value) + return ENOMEM; + + ok = _read(pbuffer, value_out->string_value, size, premaining); + if (ok != 0) { + free(value_out->string_value); + return ok; + } + + break; + case kStdUInt8Array: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + if (*premaining < size) + return EBADMSG; + + value_out->size = size; + value_out->uint8array = *pbuffer; + + ok = _advance((uintptr_t *) pbuffer, size, premaining); + if (ok != 0) + return ok; + + break; + case kStdInt32Array: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + ok = _align((uintptr_t *) pbuffer, 4, premaining); + if (ok != 0) + return ok; + + if (*premaining < size * 4) + return EBADMSG; + + value_out->size = size; + value_out->int32array = (int32_t *) *pbuffer; + + ok = _advance((uintptr_t *) pbuffer, size * 4, premaining); + if (ok != 0) + return ok; + + break; + case kStdInt64Array: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + ok = _align((uintptr_t *) pbuffer, 8, premaining); + if (ok != 0) + return ok; + + if (*premaining < size * 8) + return EBADMSG; + + value_out->size = size; + value_out->int64array = (int64_t *) *pbuffer; + + ok = _advance((uintptr_t *) pbuffer, size * 8, premaining); + if (ok != 0) + return ok; + + break; + case kStdFloat64Array: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + ok = _align((uintptr_t *) pbuffer, 8, premaining); + if (ok != 0) + return ok; + + if (*premaining < size * 8) + return EBADMSG; + + value_out->size = size; + value_out->float64array = (double *) *pbuffer; + + ok = _advance((uintptr_t *) pbuffer, size * 8, premaining); + if (ok != 0) + return ok; + + break; + case kStdList: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + value_out->size = size; + value_out->list = calloc(size, sizeof(struct std_value)); + + for (int i = 0; i < size; i++) { + ok = platch_decode_value_std(pbuffer, premaining, &value_out->list[i]); + if (ok != 0) + return ok; + } + + break; + case kStdMap: + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + value_out->size = size; + + value_out->keys = calloc(size * 2, sizeof(struct std_value)); + if (!value_out->keys) + return ENOMEM; + + value_out->values = &value_out->keys[size]; + + for (int i = 0; i < size; i++) { + ok = platch_decode_value_std(pbuffer, premaining, &(value_out->keys[i])); + if (ok != 0) + return ok; + + ok = platch_decode_value_std(pbuffer, premaining, &(value_out->values[i])); + if (ok != 0) + return ok; + } - break; - default: - return EBADMSG; - } + break; + default: return EBADMSG; + } - return 0; + return 0; } int platch_decode_value_json(char *message, size_t size, jsmntok_t **pptoken, size_t *ptokensremaining, struct json_value *value_out) { - jsmntok_t *ptoken; - int result, ok; - - if (!pptoken) { - // if we have no token list yet, parse the message & create one. - - jsmntok_t tokens[JSON_DECODE_TOKENLIST_SIZE]; - jsmn_parser parser; - size_t tokensremaining; - - memset(tokens, 0, sizeof(tokens)); - - jsmn_init(&parser); - result = jsmn_parse(&parser, (const char *) message, (const size_t) size, tokens, JSON_DECODE_TOKENLIST_SIZE); - if (result < 0) return EBADMSG; - - tokensremaining = (size_t) result; - ptoken = tokens; - - ok = platch_decode_value_json(message, size, &ptoken, &tokensremaining, value_out); - if (ok != 0) return ok; - } else { - // message is already tokenized - - ptoken = *pptoken; - - (*pptoken) += 1; - *ptokensremaining -= 1; - - switch (ptoken->type) { - case JSMN_UNDEFINED: - return EBADMSG; - case JSMN_PRIMITIVE: - if (message[ptoken->start] == 'n') { - value_out->type = kJsonNull; - } else if (message[ptoken->start] == 't') { - value_out->type = kJsonTrue; - } else if (message[ptoken->start] == 'f') { - value_out->type = kJsonFalse; - } else { - value_out->type = kJsonNumber; - - // hacky, but should work in normal circumstances. If the platform message solely consists - // of this number and nothing else, this could fail. - char old = message[ptoken->end]; - message[ptoken->end] = '\0'; - value_out->number_value = strtod(message + ptoken->start, NULL); - message[ptoken->end] = old; - } - - break; - case JSMN_STRING: ; - // use zero-copy approach. - - message[ptoken->end] = '\0'; - char *string = message + ptoken->start; - - value_out->type = kJsonString; - value_out->string_value = string; - - break; - case JSMN_ARRAY: ; - struct json_value *array = calloc(ptoken->size, sizeof(struct json_value)); - if (!array) return ENOMEM; - - for (int i=0; i < ptoken->size; i++) { - ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &array[i]); - if (ok != 0) return ok; - } - - value_out->type = kJsonArray; - value_out->size = ptoken->size; - value_out->array = array; - - break; - case JSMN_OBJECT: ; - struct json_value key; - char **keys = calloc(ptoken->size, sizeof(char *)); - struct json_value *values = calloc(ptoken->size, sizeof(struct json_value)); - if ((!keys) || (!values)) return ENOMEM; - - for (int i=0; i < ptoken->size; i++) { - ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &key); - if (ok != 0) return ok; - - if (key.type != kJsonString) return EBADMSG; - keys[i] = key.string_value; - - ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &values[i]); - if (ok != 0) return ok; - } - - value_out->type = kJsonObject; - value_out->size = ptoken->size; - value_out->keys = keys; - value_out->values = values; - - break; - default: - return EBADMSG; - } - } - - return 0; + jsmntok_t *ptoken; + int result, ok; + + if (!pptoken) { + // if we have no token list yet, parse the message & create one. + + jsmntok_t tokens[JSON_DECODE_TOKENLIST_SIZE]; + jsmn_parser parser; + size_t tokensremaining; + + memset(tokens, 0, sizeof(tokens)); + + jsmn_init(&parser); + result = jsmn_parse(&parser, (const char *) message, (const size_t) size, tokens, JSON_DECODE_TOKENLIST_SIZE); + if (result < 0) + return EBADMSG; + + tokensremaining = (size_t) result; + ptoken = tokens; + + ok = platch_decode_value_json(message, size, &ptoken, &tokensremaining, value_out); + if (ok != 0) + return ok; + } else { + // message is already tokenized + + ptoken = *pptoken; + + (*pptoken) += 1; + *ptokensremaining -= 1; + + switch (ptoken->type) { + case JSMN_UNDEFINED: return EBADMSG; + case JSMN_PRIMITIVE: + if (message[ptoken->start] == 'n') { + value_out->type = kJsonNull; + } else if (message[ptoken->start] == 't') { + value_out->type = kJsonTrue; + } else if (message[ptoken->start] == 'f') { + value_out->type = kJsonFalse; + } else { + value_out->type = kJsonNumber; + + // hacky, but should work in normal circumstances. If the platform message solely consists + // of this number and nothing else, this could fail. + char old = message[ptoken->end]; + message[ptoken->end] = '\0'; + value_out->number_value = strtod(message + ptoken->start, NULL); + message[ptoken->end] = old; + } + + break; + case JSMN_STRING:; + // use zero-copy approach. + + message[ptoken->end] = '\0'; + char *string = message + ptoken->start; + + value_out->type = kJsonString; + value_out->string_value = string; + + break; + case JSMN_ARRAY:; + struct json_value *array = calloc(ptoken->size, sizeof(struct json_value)); + if (!array) + return ENOMEM; + + for (int i = 0; i < ptoken->size; i++) { + ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &array[i]); + if (ok != 0) + return ok; + } + + value_out->type = kJsonArray; + value_out->size = ptoken->size; + value_out->array = array; + + break; + case JSMN_OBJECT:; + struct json_value key; + char **keys = calloc(ptoken->size, sizeof(char *)); + struct json_value *values = calloc(ptoken->size, sizeof(struct json_value)); + if ((!keys) || (!values)) + return ENOMEM; + + for (int i = 0; i < ptoken->size; i++) { + ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &key); + if (ok != 0) + return ok; + + if (key.type != kJsonString) + return EBADMSG; + keys[i] = key.string_value; + + ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &values[i]); + if (ok != 0) + return ok; + } + + value_out->type = kJsonObject; + value_out->size = ptoken->size; + value_out->keys = keys; + value_out->values = values; + + break; + default: return EBADMSG; + } + } + + return 0; } int platch_decode_json(char *string, struct json_value *out) { - return platch_decode_value_json(string, strlen(string), NULL, NULL, out); -} - -int platch_decode(uint8_t *buffer, size_t size, enum platch_codec codec, struct platch_obj *object_out) { - struct json_value root_jsvalue; - uint8_t *buffer_cursor = buffer; - size_t remaining = size; - int ok; - - if ((size == 0) && (buffer == NULL)) { - object_out->codec = kNotImplemented; - return 0; - } - - object_out->codec = codec; - switch (codec) { - case kStringCodec: ; - /// buffer is a non-null-terminated, UTF8-encoded string. - /// it's really sad we have to allocate a new memory block for this, but we have to since string codec buffers are not null-terminated. - - char *string; - if (!(string = malloc(size +1))) return ENOMEM; - memcpy(string, buffer, size); - string[size] = '\0'; - - object_out->string_value = string; - - break; - case kBinaryCodec: - object_out->binarydata = buffer; - object_out->binarydata_size = size; - - break; - case kJSONMessageCodec: - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &(object_out->json_value)); - if (ok != 0) return ok; - - break; - case kJSONMethodCall: ; - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); - if (ok != 0) return ok; - - if (root_jsvalue.type != kJsonObject) return EBADMSG; - - for (int i=0; i < root_jsvalue.size; i++) { - if ((strcmp(root_jsvalue.keys[i], "method") == 0) && (root_jsvalue.values[i].type == kJsonString)) { - object_out->method = root_jsvalue.values[i].string_value; - } else if (strcmp(root_jsvalue.keys[i], "args") == 0) { - object_out->json_arg = root_jsvalue.values[i]; - } else return EBADMSG; - } - - platch_free_json_value(&root_jsvalue, true); - - break; - case kJSONMethodCallResponse: ; - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); - if (ok != 0) return ok; - if (root_jsvalue.type != kJsonArray) return EBADMSG; - - if (root_jsvalue.size == 1) { - object_out->success = true; - object_out->json_result = root_jsvalue.array[0]; - return platch_free_json_value(&root_jsvalue, true); - } else if ((root_jsvalue.size == 3) && + return platch_decode_value_json(string, strlen(string), NULL, NULL, out); +} + +int platch_decode(const uint8_t *buffer, size_t size, enum platch_codec codec, struct platch_obj *object_out) { + struct json_value root_jsvalue; + const uint8_t *buffer_cursor = buffer; + size_t remaining = size; + int ok; + + if ((size == 0) && (buffer == NULL)) { + object_out->codec = kNotImplemented; + return 0; + } + + object_out->codec = codec; + switch (codec) { + case kStringCodec:; + /// buffer is a non-null-terminated, UTF8-encoded string. + /// it's really sad we have to allocate a new memory block for this, but we have to since string codec buffers are not null-terminated. + + char *string; + if (!(string = malloc(size + 1))) + return ENOMEM; + memcpy(string, buffer, size); + string[size] = '\0'; + + object_out->string_value = string; + + break; + case kBinaryCodec: + object_out->binarydata = buffer; + object_out->binarydata_size = size; + + break; + case kJSONMessageCodec: + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &(object_out->json_value)); + if (ok != 0) + return ok; + + break; + case kJSONMethodCall:; + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); + if (ok != 0) + return ok; + + if (root_jsvalue.type != kJsonObject) + return EBADMSG; + + for (int i = 0; i < root_jsvalue.size; i++) { + if ((streq(root_jsvalue.keys[i], "method")) && (root_jsvalue.values[i].type == kJsonString)) { + object_out->method = root_jsvalue.values[i].string_value; + } else if (streq(root_jsvalue.keys[i], "args")) { + object_out->json_arg = root_jsvalue.values[i]; + } else + return EBADMSG; + } + + platch_free_json_value(&root_jsvalue, true); + + break; + case kJSONMethodCallResponse:; + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); + if (ok != 0) + return ok; + if (root_jsvalue.type != kJsonArray) + return EBADMSG; + + if (root_jsvalue.size == 1) { + object_out->success = true; + object_out->json_result = root_jsvalue.array[0]; + return platch_free_json_value(&root_jsvalue, true); + } else if ((root_jsvalue.size == 3) && (root_jsvalue.array[0].type == kJsonString) && ((root_jsvalue.array[1].type == kJsonString) || (root_jsvalue.array[1].type == kJsonNull))) { - - - object_out->success = false; - object_out->error_code = root_jsvalue.array[0].string_value; - object_out->error_msg = root_jsvalue.array[1].string_value; - object_out->json_error_details = root_jsvalue.array[2]; - return platch_free_json_value(&root_jsvalue, true); - } else return EBADMSG; - - break; - case kStandardMessageCodec: - ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_value); - if (ok != 0) return ok; - break; - case kStandardMethodCall: ; - struct std_value methodname; - - ok = platch_decode_value_std(&buffer_cursor, &remaining, &methodname); - if (ok != 0) return ok; - if (methodname.type != kStdString) { - platch_free_value_std(&methodname); - return EBADMSG; - } - object_out->method = methodname.string_value; - - ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_arg); - if (ok != 0) return ok; - - break; - case kStandardMethodCallResponse: ; - ok = _read_u8(&buffer_cursor, (uint8_t*) &object_out->success, &remaining); - - if (object_out->success) { - ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_result)); - if (ok != 0) return ok; - } else { - struct std_value error_code, error_msg; - - ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_code); - if (ok != 0) return ok; - ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_msg); - if (ok != 0) return ok; - ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_error_details)); - if (ok != 0) return ok; - - if ((error_code.type == kStdString) && ((error_msg.type == kStdString) || (error_msg.type == kStdNull))) { - object_out->error_code = error_code.string_value; - object_out->error_msg = (error_msg.type == kStdString) ? error_msg.string_value : NULL; - } else { - return EBADMSG; - } - } - break; - default: - return EINVAL; - } - - return 0; + object_out->success = false; + object_out->error_code = root_jsvalue.array[0].string_value; + object_out->error_msg = root_jsvalue.array[1].string_value; + object_out->json_error_details = root_jsvalue.array[2]; + return platch_free_json_value(&root_jsvalue, true); + } else + return EBADMSG; + + break; + case kStandardMessageCodec: + ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_value); + if (ok != 0) + return ok; + break; + case kStandardMethodCall:; + struct std_value methodname; + + ok = platch_decode_value_std(&buffer_cursor, &remaining, &methodname); + if (ok != 0) + return ok; + if (methodname.type != kStdString) { + platch_free_value_std(&methodname); + return EBADMSG; + } + object_out->method = methodname.string_value; + + ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_arg); + if (ok != 0) + return ok; + + break; + case kStandardMethodCallResponse:; + ok = _read_u8(&buffer_cursor, (uint8_t *) &object_out->success, &remaining); + + if (object_out->success) { + ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_result)); + if (ok != 0) + return ok; + } else { + struct std_value error_code, error_msg; + + ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_code); + if (ok != 0) + return ok; + ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_msg); + if (ok != 0) + return ok; + ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_error_details)); + if (ok != 0) + return ok; + + if ((error_code.type == kStdString) && ((error_msg.type == kStdString) || (error_msg.type == kStdNull))) { + object_out->error_code = error_code.string_value; + object_out->error_msg = (error_msg.type == kStdString) ? error_msg.string_value : NULL; + } else { + return EBADMSG; + } + } + break; + default: return EINVAL; + } + + return 0; } int platch_encode(struct platch_obj *object, uint8_t **buffer_out, size_t *size_out) { - struct std_value stdmethod, stderrcode, stderrmessage; - uint8_t *buffer, *buffer_cursor; - size_t size = 0; - int ok = 0; - - *size_out = 0; - *buffer_out = NULL; - - switch (object->codec) { - case kNotImplemented: - *size_out = 0; - *buffer_out = NULL; - return 0; - case kStringCodec: - size = strlen(object->string_value); - break; - case kBinaryCodec: - *buffer_out = object->binarydata; - *size_out = object->binarydata_size; - return 0; - case kJSONMessageCodec: - size = platch_calc_value_size_json(&(object->json_value)); - size += 1; // JSONMsgCodec uses sprintf, which null-terminates strings, - // so lets allocate one more byte for the last null-terminator. - // this is decremented again in the second switch-case, so flutter - // doesn't complain about a malformed message. - break; - case kStandardMessageCodec: - ok = platch_calc_value_size_std(&(object->std_value), &size); - if (ok != 0) return ok; - break; - case kStandardMethodCall: - stdmethod.type = kStdString; - stdmethod.string_value = object->method; - - ok = platch_calc_value_size_std(&stdmethod, &size); - if (ok != 0) return ok; - - ok = platch_calc_value_size_std(&(object->std_arg), &size); - if (ok != 0) return ok; - - break; - case kStandardMethodCallResponse: - size += 1; - - if (object->success) { - ok = platch_calc_value_size_std(&(object->std_result), &size); - if (ok != 0) return ok; - } else { - stderrcode = (struct std_value) { - .type = kStdString, - .string_value = object->error_code - }; - stderrmessage = (struct std_value) { - .type = kStdString, - .string_value = object->error_msg - }; - - ok = platch_calc_value_size_std(&stderrcode, &size); - if (ok != 0) return ok; - ok = platch_calc_value_size_std(&stderrmessage, &size); - if (ok != 0) return ok; - ok = platch_calc_value_size_std(&(object->std_error_details), &size); - if (ok != 0) return ok; - } - break; - case kJSONMethodCall: - size = platch_calc_value_size_json( - &JSONOBJECT2( - "method", JSONSTRING(object->method), - "args", object->json_arg - ) - ); - size += 1; - break; - case kJSONMethodCallResponse: - if (object->success) { - size = 1 + platch_calc_value_size_json(&JSONARRAY1(object->json_result)); - } else { - size = 1 + platch_calc_value_size_json( - &JSONARRAY3( - JSONSTRING(object->error_code), - (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, - object->json_error_details - ) - ); - } - break; - default: - return EINVAL; - } - - buffer = malloc(size); - if (buffer == NULL) { - return ENOMEM; - } - - buffer_cursor = buffer; - - switch (object->codec) { - case kStringCodec: - memcpy(buffer, object->string_value, size); - break; - case kStandardMessageCodec: - ok = platch_write_value_to_buffer_std(&(object->std_value), &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - break; - case kStandardMethodCall: - ok = platch_write_value_to_buffer_std(&stdmethod, &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - - ok = platch_write_value_to_buffer_std(&(object->std_arg), &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - - break; - case kStandardMethodCallResponse: - if (object->success) { - _write_u8(&buffer_cursor, 0x00, NULL); - - ok = platch_write_value_to_buffer_std(&(object->std_result), &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - } else { - _write_u8(&buffer_cursor, 0x01, NULL); - - ok = platch_write_value_to_buffer_std(&stderrcode, &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - ok = platch_write_value_to_buffer_std(&stderrmessage, &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - ok = platch_write_value_to_buffer_std(&(object->std_error_details), &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - } - - break; - case kJSONMessageCodec: - size -= 1; - ok = platch_write_value_to_buffer_json(&(object->json_value), &buffer_cursor); - if (ok != 0) goto free_buffer_and_return_ok; - break; - case kJSONMethodCall: - size -= 1; - ok = platch_write_value_to_buffer_json( - &JSONOBJECT2( - "method", JSONSTRING(object->method), - "args", object->json_arg - ), - &buffer_cursor - ); - if (ok != 0) { - goto free_buffer_and_return_ok; - } - break; - case kJSONMethodCallResponse: - if (object->success) { - ok = platch_write_value_to_buffer_json( - &JSONARRAY1(object->json_result), - &buffer_cursor - ); - } else { - ok = platch_write_value_to_buffer_json( - &JSONARRAY3( - JSONSTRING(object->error_code), - (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, - object->json_error_details - ), - &buffer_cursor - ); - } - size -= 1; - if (ok != 0) { - goto free_buffer_and_return_ok; - } - break; - default: - return EINVAL; - } - - *buffer_out = buffer; - *size_out = size; - return 0; - - free_buffer_and_return_ok: - free(buffer); - return ok; + struct std_value stdmethod, stderrcode, stderrmessage; + uint8_t *buffer, *buffer_cursor; + size_t size = 0; + int ok = 0; + + *size_out = 0; + *buffer_out = NULL; + + switch (object->codec) { + case kNotImplemented: + *size_out = 0; + *buffer_out = NULL; + return 0; + case kStringCodec: size = strlen(object->string_value); break; + case kBinaryCodec: + /// FIXME: Copy buffer instead + *buffer_out = (uint8_t *) object->binarydata; + *size_out = object->binarydata_size; + return 0; + case kJSONMessageCodec: + size = platch_calc_value_size_json(&(object->json_value)); + size += 1; // JSONMsgCodec uses sprintf, which null-terminates strings, + // so lets allocate one more byte for the last null-terminator. + // this is decremented again in the second switch-case, so flutter + // doesn't complain about a malformed message. + break; + case kStandardMessageCodec: + ok = platch_calc_value_size_std(&(object->std_value), &size); + if (ok != 0) + return ok; + break; + case kStandardMethodCall: + stdmethod.type = kStdString; + stdmethod.string_value = object->method; + + ok = platch_calc_value_size_std(&stdmethod, &size); + if (ok != 0) + return ok; + + ok = platch_calc_value_size_std(&(object->std_arg), &size); + if (ok != 0) + return ok; + + break; + case kStandardMethodCallResponse: + size += 1; + + if (object->success) { + ok = platch_calc_value_size_std(&(object->std_result), &size); + if (ok != 0) + return ok; + } else { + stderrcode = (struct std_value){ .type = kStdString, .string_value = object->error_code }; + stderrmessage = (struct std_value){ .type = kStdString, .string_value = object->error_msg }; + + ok = platch_calc_value_size_std(&stderrcode, &size); + if (ok != 0) + return ok; + ok = platch_calc_value_size_std(&stderrmessage, &size); + if (ok != 0) + return ok; + ok = platch_calc_value_size_std(&(object->std_error_details), &size); + if (ok != 0) + return ok; + } + break; + case kJSONMethodCall: + size = platch_calc_value_size_json(&JSONOBJECT2("method", JSONSTRING(object->method), "args", object->json_arg)); + size += 1; + break; + case kJSONMethodCallResponse: + if (object->success) { + size = 1 + platch_calc_value_size_json(&JSONARRAY1(object->json_result)); + } else { + size = 1 + platch_calc_value_size_json(&JSONARRAY3( + JSONSTRING(object->error_code), + (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, + object->json_error_details + )); + } + break; + default: return EINVAL; + } + + buffer = malloc(size); + if (buffer == NULL) { + return ENOMEM; + } + + buffer_cursor = buffer; + + switch (object->codec) { + case kStringCodec: memcpy(buffer, object->string_value, size); break; + case kStandardMessageCodec: + ok = platch_write_value_to_buffer_std(&(object->std_value), &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + break; + case kStandardMethodCall: + ok = platch_write_value_to_buffer_std(&stdmethod, &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + + ok = platch_write_value_to_buffer_std(&(object->std_arg), &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + + break; + case kStandardMethodCallResponse: + if (object->success) { + _write_u8(&buffer_cursor, 0x00, NULL); + + ok = platch_write_value_to_buffer_std(&(object->std_result), &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + } else { + _write_u8(&buffer_cursor, 0x01, NULL); + + ok = platch_write_value_to_buffer_std(&stderrcode, &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + ok = platch_write_value_to_buffer_std(&stderrmessage, &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + ok = platch_write_value_to_buffer_std(&(object->std_error_details), &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + } + + break; + case kJSONMessageCodec: + size -= 1; + ok = platch_write_value_to_buffer_json(&(object->json_value), &buffer_cursor); + if (ok != 0) + goto free_buffer_and_return_ok; + break; + case kJSONMethodCall: + size -= 1; + ok = platch_write_value_to_buffer_json( + &JSONOBJECT2("method", JSONSTRING(object->method), "args", object->json_arg), + &buffer_cursor + ); + if (ok != 0) { + goto free_buffer_and_return_ok; + } + break; + case kJSONMethodCallResponse: + if (object->success) { + ok = platch_write_value_to_buffer_json(&JSONARRAY1(object->json_result), &buffer_cursor); + } else { + ok = platch_write_value_to_buffer_json( + &JSONARRAY3( + JSONSTRING(object->error_code), + (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, + object->json_error_details + ), + &buffer_cursor + ); + } + size -= 1; + if (ok != 0) { + goto free_buffer_and_return_ok; + } + break; + default: return EINVAL; + } + + *buffer_out = buffer; + *size_out = size; + return 0; + +free_buffer_and_return_ok: + free(buffer); + return ok; } void platch_on_response_internal(const uint8_t *buffer, size_t size, void *userdata) { - struct platch_msg_resp_handler_data *handlerdata; - struct platch_obj object; - int ok; - - handlerdata = (struct platch_msg_resp_handler_data *) userdata; - ok = platch_decode((uint8_t*) buffer, size, handlerdata->codec, &object); - if (ok != 0) return; - - ok = handlerdata->on_response(&object, handlerdata->userdata); - if (ok != 0) return; - - free(handlerdata); - - ok = platch_free_obj(&object); - if (ok != 0) return; -} - -int platch_send(char *channel, struct platch_obj *object, enum platch_codec response_codec, platch_msg_resp_callback on_response, void *userdata) { - FlutterPlatformMessageResponseHandle *response_handle = NULL; - struct platch_msg_resp_handler_data *handlerdata = NULL; - FlutterEngineResult result; - uint8_t *buffer; - size_t size; - int ok; - - ok = platch_encode(object, &buffer, &size); - if (ok != 0) return ok; - - if (on_response) { - handlerdata = malloc(sizeof(struct platch_msg_resp_handler_data)); - if (!handlerdata) { - return ENOMEM; - } - - handlerdata->codec = response_codec; - handlerdata->on_response = on_response; - handlerdata->userdata = userdata; - - result = flutterpi.flutter.libflutter_engine.FlutterPlatformMessageCreateResponseHandle(flutterpi.flutter.engine, platch_on_response_internal, handlerdata, &response_handle); - if (result != kSuccess) { - fprintf(stderr, "[flutter-pi] Error create platform message response handle. FlutterPlatformMessageCreateResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); - goto fail_free_handlerdata; - } - } - - ok = flutterpi_send_platform_message( - channel, - buffer, - size, - response_handle - ); - if (ok != 0) { - goto fail_release_handle; - } - - // TODO: This won't work if we're not on the main thread - if (on_response) { - result = flutterpi.flutter.libflutter_engine.FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); - if (result != kSuccess) { - fprintf(stderr, "[flutter-pi] Error releasing platform message response handle. FlutterPlatformMessageReleaseResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); - ok = EIO; - goto fail_free_buffer; - } - } - - if (object->codec != kBinaryCodec) { - free(buffer); - } - - return 0; - - - fail_release_handle: - if (on_response) { - flutterpi.flutter.libflutter_engine.FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); - } - - fail_free_buffer: - if (object->codec != kBinaryCodec) { - free(buffer); - } - - fail_free_handlerdata: - if (on_response) { - free(handlerdata); - } - - return ok; + struct platch_msg_resp_handler_data *handlerdata; + struct platch_obj object; + int ok; + + handlerdata = (struct platch_msg_resp_handler_data *) userdata; + ok = platch_decode((uint8_t *) buffer, size, handlerdata->codec, &object); + if (ok != 0) + return; + + ok = handlerdata->on_response(&object, handlerdata->userdata); + if (ok != 0) + return; + + free(handlerdata); + + ok = platch_free_obj(&object); + if (ok != 0) + return; +} + +int platch_send( + char *channel, + struct platch_obj *object, + enum platch_codec response_codec, + platch_msg_resp_callback on_response, + void *userdata +) { + FlutterPlatformMessageResponseHandle *response_handle = NULL; + struct platch_msg_resp_handler_data *handlerdata = NULL; + uint8_t *buffer; + size_t size; + int ok; + + ok = platch_encode(object, &buffer, &size); + if (ok != 0) + return ok; + + if (on_response) { + handlerdata = malloc(sizeof(struct platch_msg_resp_handler_data)); + if (!handlerdata) { + return ENOMEM; + } + + handlerdata->codec = response_codec; + handlerdata->on_response = on_response; + handlerdata->userdata = userdata; + + response_handle = flutterpi_create_platform_message_response_handle(flutterpi, platch_on_response_internal, handlerdata); + if (response_handle == NULL) { + goto fail_free_handlerdata; + } + } + + ok = flutterpi_send_platform_message(flutterpi, channel, buffer, size, response_handle); + if (ok != 0) { + goto fail_release_handle; + } + + /// TODO: This won't work if we're not on the main thread + if (on_response) { + flutterpi_release_platform_message_response_handle(flutterpi, response_handle); + } + + if (object->codec != kBinaryCodec) { + free(buffer); + } + + return 0; + +fail_release_handle: + if (on_response) { + flutterpi_release_platform_message_response_handle(flutterpi, response_handle); + } + +fail_free_handlerdata: + if (on_response) { + free(handlerdata); + } + + return ok; } int platch_call_std(char *channel, char *method, struct std_value *argument, platch_msg_resp_callback on_response, void *userdata) { - struct platch_obj object = { - .codec = kStandardMethodCall, - .method = method, - .std_arg = *argument - }; - - return platch_send(channel, &object, kStandardMethodCallResponse, on_response, userdata); + struct platch_obj object = { .codec = kStandardMethodCall, .method = method, .std_arg = *argument }; + + return platch_send(channel, &object, kStandardMethodCallResponse, on_response, userdata); } int platch_call_json(char *channel, char *method, struct json_value *argument, platch_msg_resp_callback on_response, void *userdata) { - return platch_send(channel, - &(struct platch_obj) { - .codec = kJSONMethodCall, - .method = method, - .json_arg = *argument - }, - kJSONMethodCallResponse, - on_response, - userdata); + return platch_send( + channel, + &(struct platch_obj){ .codec = kJSONMethodCall, .method = method, .json_arg = *argument }, + kJSONMethodCallResponse, + on_response, + userdata + ); } -int platch_respond(FlutterPlatformMessageResponseHandle *handle, struct platch_obj *response) { - uint8_t *buffer = NULL; - size_t size = 0; - int ok; +int platch_respond(const FlutterPlatformMessageResponseHandle *handle, struct platch_obj *response) { + uint8_t *buffer = NULL; + size_t size = 0; + int ok; - ok = platch_encode(response, &buffer, &size); - if (ok != 0) return ok; + ok = platch_encode(response, &buffer, &size); + if (ok != 0) + return ok; - ok = flutterpi_respond_to_platform_message(handle, buffer, size); + ok = flutterpi_respond_to_platform_message(handle, buffer, size); - if (buffer != NULL) { - free(buffer); - } + if (buffer != NULL) { + free(buffer); + } - return 0; + return 0; } -int platch_respond_not_implemented(FlutterPlatformMessageResponseHandle *handle) { - return platch_respond( - (FlutterPlatformMessageResponseHandle *) handle, - &(struct platch_obj) { - .codec = kNotImplemented - }); +int platch_respond_not_implemented(const FlutterPlatformMessageResponseHandle *handle) { + return platch_respond((FlutterPlatformMessageResponseHandle *) handle, &(struct platch_obj){ .codec = kNotImplemented }); } /**************************** * STANDARD METHOD CHANNELS * ****************************/ -int platch_respond_success_std(FlutterPlatformMessageResponseHandle *handle, - struct std_value *return_value) { - return platch_respond( - handle, - &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = return_value? *return_value : STDNULL - } - ); -} - -int platch_respond_error_std(FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct std_value *error_details) { - return platch_respond(handle, &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = false, - .error_code = error_code, - .error_msg = error_msg, - .std_error_details = error_details? *error_details : STDNULL - }); +int platch_respond_success_std(const FlutterPlatformMessageResponseHandle *handle, struct std_value *return_value) { + return platch_respond( + handle, + &(struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = return_value ? *return_value : STDNULL } + ); +} + +int platch_respond_error_std( + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details +) { + return platch_respond( + handle, + &(struct platch_obj){ + .codec = kStandardMethodCallResponse, + .success = false, + .error_code = error_code, + .error_msg = error_msg, + .std_error_details = error_details ? *error_details : STDNULL, + } + ); +} + +/// Sends a platform message to `handle` with error code "illegalargument" +/// and error message `errmsg`. +int platch_respond_illegal_arg_std(const FlutterPlatformMessageResponseHandle *handle, char *error_msg) { + return platch_respond_error_std(handle, "illegalargument", error_msg, NULL); } /// Sends a platform message to `handle` with error code "illegalargument" /// and error message `errmsg`. -int platch_respond_illegal_arg_std(FlutterPlatformMessageResponseHandle *handle, - char *error_msg) { - return platch_respond_error_std(handle, "illegalargument", error_msg, NULL); +int platch_respond_illegal_arg_ext_std(const FlutterPlatformMessageResponseHandle *handle, char *error_msg, struct std_value *error_details) { + return platch_respond_error_std(handle, "illegalargument", error_msg, error_details); } /// Sends a platform message to `handle` with error code "nativeerror" /// and error messsage `strerror(_errno)` -int platch_respond_native_error_std(FlutterPlatformMessageResponseHandle *handle, - int _errno) { - return platch_respond_error_std( - handle, - "nativeerror", - strerror(_errno), - &STDINT32(_errno) - ); +int platch_respond_native_error_std(const FlutterPlatformMessageResponseHandle *handle, int _errno) { + return platch_respond_error_std(handle, "nativeerror", strerror(_errno), &STDINT32(_errno)); } - /************************ * JSON METHOD CHANNELS * ************************/ -int platch_respond_success_json(FlutterPlatformMessageResponseHandle *handle, - struct json_value *return_value) { - return platch_respond( - handle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = return_value - ? *return_value - : JSONNULL - } - ); -} - -int platch_respond_error_json(FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct json_value *error_details) { - return platch_respond(handle, &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = false, - .error_code = error_code, - .error_msg = error_msg, - .json_error_details = (error_details) ? - *error_details : - (struct json_value) {.type = kJsonNull} - }); -} - -int platch_respond_illegal_arg_json(FlutterPlatformMessageResponseHandle *handle, - char *error_msg) { - return platch_respond_error_json(handle, "illegalargument", error_msg, NULL); -} - -int platch_respond_native_error_json(FlutterPlatformMessageResponseHandle *handle, - int _errno) { - return platch_respond_error_json( - handle, - "nativeerror", - strerror(_errno), - &(struct json_value) {.type = kJsonNumber, .number_value = _errno} - ); +int platch_respond_success_json(const FlutterPlatformMessageResponseHandle *handle, struct json_value *return_value) { + return platch_respond( + handle, + &(struct platch_obj){ + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = return_value ? *return_value : JSONNULL, + } + ); +} + +int platch_respond_error_json( + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct json_value *error_details +) { + return platch_respond( + handle, + &(struct platch_obj){ + .codec = kJSONMethodCallResponse, + .success = false, + .error_code = error_code, + .error_msg = error_msg, + .json_error_details = (error_details) ? *error_details : (struct json_value){ .type = kJsonNull }, + } + ); +} + +int platch_respond_illegal_arg_json(const FlutterPlatformMessageResponseHandle *handle, char *error_msg) { + return platch_respond_error_json(handle, "illegalargument", error_msg, NULL); +} + +int platch_respond_native_error_json(const FlutterPlatformMessageResponseHandle *handle, int _errno) { + return platch_respond_error_json( + handle, + "nativeerror", + strerror(_errno), + &(struct json_value){ .type = kJsonNumber, .number_value = _errno } + ); } /************************** * PIGEON METHOD CHANNELS * **************************/ -int platch_respond_success_pigeon( - FlutterPlatformMessageResponseHandle *handle, - struct std_value *return_value -) { - return platch_respond( - handle, - &PLATCH_OBJ_STD_MSG( - STDMAP1( - STDSTRING("result"), - return_value != NULL - ? *return_value - : STDNULL - ) - ) - ); +int platch_respond_success_pigeon(const FlutterPlatformMessageResponseHandle *handle, struct std_value *return_value) { + return platch_respond(handle, &PLATCH_OBJ_STD_MSG(STDMAP1(STDSTRING("result"), return_value != NULL ? *return_value : STDNULL))); } int platch_respond_error_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_code, - char *error_msg, - struct std_value *error_details + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details ) { - return platch_respond( - handle, - &PLATCH_OBJ_STD_MSG( - STDMAP1( - STDSTRING("error"), - STDMAP3( - STDSTRING("code"), STDSTRING(error_code), - STDSTRING("message"), STDSTRING(error_msg), - STDSTRING("details"), error_details != NULL - ? *error_details - : STDNULL - ) - ) - ) - ); -} - -int platch_respond_illegal_arg_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_msg -) { - return platch_respond_error_pigeon( - handle, - "illegalargument", - error_msg, - NULL - ); + return platch_respond( + handle, + &PLATCH_OBJ_STD_MSG(STDMAP1( + STDSTRING("error"), + STDMAP3( + STDSTRING("code"), + STDSTRING(error_code), + STDSTRING("message"), + STDSTRING(error_msg), + STDSTRING("details"), + error_details != NULL ? *error_details : STDNULL + ) + )) + ); +} + +int platch_respond_illegal_arg_pigeon(const FlutterPlatformMessageResponseHandle *handle, char *error_msg) { + return platch_respond_error_pigeon(handle, "illegalargument", error_msg, NULL); } int platch_respond_illegal_arg_ext_pigeon( - FlutterPlatformMessageResponseHandle *handle, - char *error_msg, - struct std_value *error_details + const FlutterPlatformMessageResponseHandle *handle, + char *error_msg, + struct std_value *error_details ) { - return platch_respond_error_pigeon( - handle, - "illegalargument", - error_msg, - error_details - ); + return platch_respond_error_pigeon(handle, "illegalargument", error_msg, error_details); } - -int platch_respond_native_error_pigeon( - FlutterPlatformMessageResponseHandle *handle, - int _errno -) { - return platch_respond_error_pigeon( - handle, - "nativeerror", - strerror(_errno), - &STDINT32(_errno) - ); +int platch_respond_native_error_pigeon(const FlutterPlatformMessageResponseHandle *handle, int _errno) { + return platch_respond_error_pigeon(handle, "nativeerror", strerror(_errno), &STDINT32(_errno)); } /*************************** * STANDARD EVENT CHANNELS * ***************************/ int platch_send_success_event_std(char *channel, struct std_value *event_value) { - return platch_send( - channel, - &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = event_value? *event_value : STDNULL - }, - 0, NULL, NULL - ); -} - -int platch_send_error_event_std(char *channel, - char *error_code, - char *error_msg, - struct std_value *error_details) { - return platch_send( - channel, - &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = false, - .error_code = error_code, - .error_msg = error_msg, - .std_error_details = error_details? *error_details : STDNULL - }, - 0, NULL, NULL - ); + return platch_send( + channel, + &(struct platch_obj){ + .codec = kStandardMethodCallResponse, + .success = true, + .std_result = event_value ? *event_value : STDNULL, + }, + 0, + NULL, + NULL + ); +} + +int platch_send_error_event_std(char *channel, char *error_code, char *error_msg, struct std_value *error_details) { + return platch_send( + channel, + &(struct platch_obj){ + .codec = kStandardMethodCallResponse, + .success = false, + .error_code = error_code, + .error_msg = error_msg, + .std_error_details = error_details ? *error_details : STDNULL, + }, + 0, + NULL, + NULL + ); } /*********************** * JSON EVENT CHANNELS * ***********************/ int platch_send_success_event_json(char *channel, struct json_value *event_value) { - return platch_send(channel, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = event_value? *event_value : (struct json_value) {.type = kJsonNull} - }, - 0, NULL, NULL - ); -} - -int platch_send_error_event_json(char *channel, - char *error_code, - char *error_msg, - struct json_value *error_details) { - return platch_send( - channel, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = false, - .error_code = error_code, - .error_msg = error_msg, - .json_error_details = error_details? - *error_details : - (struct json_value) {.type = kJsonNull} - }, - 0, NULL, NULL - ); + return platch_send( + channel, + &(struct platch_obj){ + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = event_value ? *event_value : (struct json_value){ .type = kJsonNull }, + }, + 0, + NULL, + NULL + ); } +int platch_send_error_event_json(char *channel, char *error_code, char *error_msg, struct json_value *error_details) { + return platch_send( + channel, + &(struct platch_obj){ .codec = kJSONMethodCallResponse, + .success = false, + .error_code = error_code, + .error_msg = error_msg, + .json_error_details = error_details ? *error_details : (struct json_value){ .type = kJsonNull } }, + 0, + NULL, + NULL + ); +} bool jsvalue_equals(struct json_value *a, struct json_value *b) { - if (a == b) return true; - if ((a == NULL) ^ (b == NULL)) return false; - if (a->type != b->type) return false; - - switch (a->type) { - case kJsonNull: - case kJsonTrue: - case kJsonFalse: - return true; - case kJsonNumber: - return a->number_value == b->number_value; - case kJsonString: - return strcmp(a->string_value, b->string_value) == 0; - case kJsonArray: - if (a->size != b->size) return false; - if (a->array == b->array) return true; - for (int i = 0; i < a->size; i++) - if (!jsvalue_equals(&a->array[i], &b->array[i])) - return false; - return true; - case kJsonObject: { - if (a->size != b->size) return false; - if ((a->keys == b->keys) && (a->values == b->values)) return true; - - bool _keyInBAlsoInA[a->size]; - memset(_keyInBAlsoInA, 0, a->size * sizeof(bool)); - - for (int i = 0; i < a->size; i++) { - // The key we're searching for in b. - char *key = a->keys[i]; - - int j = 0; - while (j < a->size) { - while ((j < a->size) && _keyInBAlsoInA[j]) j++; // skip all keys with _keyInBAlsoInA set to true. - if (j >= a->size) break; - if (strcmp(key, b->keys[j]) != 0) j++; // if b->keys[j] is not equal to "key", continue searching - else { - _keyInBAlsoInA[j] = true; - - // the values of "key" in a and b must (of course) also be equivalent. - if (!jsvalue_equals(&a->values[i], &b->values[j])) return false; - break; - } - } - - // we did not find a->keys[i] in b. - if (j + 1 >= a->size) return false; - } - - return true; - } - default: - return false; - } - - return 0; + if (a == b) + return true; + if ((a == NULL) ^ (b == NULL)) + return false; + if (a->type != b->type) + return false; + + switch (a->type) { + case kJsonNull: + case kJsonTrue: + case kJsonFalse: return true; + case kJsonNumber: return a->number_value == b->number_value; + case kJsonString: return streq(a->string_value, b->string_value); + case kJsonArray: + if (a->size != b->size) + return false; + if (a->array == b->array) + return true; + for (int i = 0; i < a->size; i++) + if (!jsvalue_equals(&a->array[i], &b->array[i])) + return false; + return true; + case kJsonObject: { + if (a->size != b->size) + return false; + if ((a->keys == b->keys) && (a->values == b->values)) + return true; + + bool _keyInBAlsoInA[a->size]; + memset(_keyInBAlsoInA, 0, a->size * sizeof(bool)); + + for (int i = 0; i < a->size; i++) { + // The key we're searching for in b. + char *key = a->keys[i]; + + int j = 0; + while (j < a->size) { + while ((j < a->size) && _keyInBAlsoInA[j]) + j++; // skip all keys with _keyInBAlsoInA set to true. + if (j >= a->size) + break; + if (!streq(key, b->keys[j])) + j++; // if b->keys[j] is not equal to "key", continue searching + else { + _keyInBAlsoInA[j] = true; + + // the values of "key" in a and b must (of course) also be equivalent. + if (!jsvalue_equals(&a->values[i], &b->values[j])) + return false; + break; + } + } + + // we did not find a->keys[i] in b. + if (j + 1 >= a->size) + return false; + } + + return true; + } + default: return false; + } + + return 0; } struct json_value *jsobject_get(struct json_value *object, char *key) { - int i; - for (i=0; i < object->size; i++) - if (strcmp(object->keys[i], key) == 0) break; - - - if (i != object->size) return &(object->values[i]); - return NULL; + int i; + for (i = 0; i < object->size; i++) + if (streq(object->keys[i], key)) + break; + + if (i != object->size) + return &(object->values[i]); + return NULL; } bool stdvalue_equals(struct std_value *a, struct std_value *b) { - if (a == b) return true; - if ((a == NULL) ^ (b == NULL)) return false; - - DEBUG_ASSERT_NOT_NULL(a); - DEBUG_ASSERT_NOT_NULL(b); - - if (a->type != b->type) return false; - - switch (a->type) { - case kStdNull: - case kStdTrue: - case kStdFalse: - return true; - case kStdInt32: - return a->int32_value == b->int32_value; - case kStdInt64: - return a->int64_value == b->int64_value; - case kStdLargeInt: - case kStdString: - DEBUG_ASSERT_NOT_NULL(a->string_value); - DEBUG_ASSERT_NOT_NULL(b->string_value); - return strcmp(a->string_value, b->string_value) == 0; - case kStdFloat64: - return a->float64_value == b->float64_value; - case kStdUInt8Array: - if (a->size != b->size) return false; - if (a->uint8array == b->uint8array) return true; - - DEBUG_ASSERT_NOT_NULL(a->uint8array); - DEBUG_ASSERT_NOT_NULL(b->uint8array); - for (int i = 0; i < a->size; i++) - if (a->uint8array[i] != b->uint8array[i]) - return false; - return true; - case kStdInt32Array: - if (a->size != b->size) return false; - if (a->int32array == b->int32array) return true; - - DEBUG_ASSERT_NOT_NULL(a->int32array); - DEBUG_ASSERT_NOT_NULL(b->int32array); - for (int i = 0; i < a->size; i++) - if (a->int32array[i] != b->int32array[i]) - return false; - return true; - case kStdInt64Array: - if (a->size != b->size) return false; - if (a->int64array == b->int64array) return true; - - DEBUG_ASSERT_NOT_NULL(a->int64array); - DEBUG_ASSERT_NOT_NULL(b->int64array); - for (int i = 0; i < a->size; i++) - if (a->int64array[i] != b->int64array[i]) - return false; - return true; - case kStdFloat64Array: - if (a->size != b->size) return false; - if (a->float64array == b->float64array) return true; - - DEBUG_ASSERT_NOT_NULL(a->float64array); - DEBUG_ASSERT_NOT_NULL(b->float64array); - for (int i = 0; i < a->size; i++) - if (a->float64array[i] != b->float64array[i]) - return false; - return true; - case kStdList: - // the order of list elements is important - if (a->size != b->size) return false; - if (a->list == b->list) return true; - - DEBUG_ASSERT_NOT_NULL(a->list); - DEBUG_ASSERT_NOT_NULL(b->list); - for (int i = 0; i < a->size; i++) - if (!stdvalue_equals(a->list + i, b->list + i)) - return false; - - return true; - case kStdMap: { - // the order is not important here, which makes it a bit difficult to compare - if (a->size != b->size) return false; - if ((a->keys == b->keys) && (a->values == b->values)) return true; - - // _keyInBAlsoInA[i] == true means that there's a key in a that matches b->keys[i] - // so if we're searching for a key in b, we can safely ignore / don't need to compare - // keys in b that have they're _keyInBAlsoInA set to true. - bool *_keyInBAlsoInA = alloca(sizeof(bool) * a->size); - memset(_keyInBAlsoInA, 0, sizeof(bool) * a->size); - - for (int i = 0; i < a->size; i++) { - // The key we're searching for in b. - struct std_value *key = a->keys + i; - - int j = 0; - while (j < a->size) { - while ((j < a->size) && _keyInBAlsoInA[j]) j++; // skip all keys with _keyInBAlsoInA set to true. - if (j >= a->size) break; - if (stdvalue_equals(key, b->keys + j) == false) { - j++; // if b->keys[j] is not equal to "key", continue searching - } else { - _keyInBAlsoInA[j] = true; - - // the values of "key" in a and b must (of course) also be equivalent. - if (stdvalue_equals(a->values + i, b->values + j) == false) { - return false; - } - break; - } - } - - // we did not find a->keys[i] in b. - if (j + 1 >= a->size) return false; - } - - return true; - } - default: return false; - } - - return false; + if (a == b) + return true; + if ((a == NULL) ^ (b == NULL)) + return false; + + ASSERT_NOT_NULL(a); + ASSERT_NOT_NULL(b); + + if (a->type != b->type) + return false; + + switch (a->type) { + case kStdNull: + case kStdTrue: + case kStdFalse: return true; + case kStdInt32: return a->int32_value == b->int32_value; + case kStdInt64: return a->int64_value == b->int64_value; + case kStdLargeInt: + case kStdString: + ASSERT_NOT_NULL(a->string_value); + ASSERT_NOT_NULL(b->string_value); + return streq(a->string_value, b->string_value); + case kStdFloat64: return a->float64_value == b->float64_value; + case kStdUInt8Array: + if (a->size != b->size) + return false; + if (a->uint8array == b->uint8array) + return true; + + ASSERT_NOT_NULL(a->uint8array); + ASSERT_NOT_NULL(b->uint8array); + for (int i = 0; i < a->size; i++) + if (a->uint8array[i] != b->uint8array[i]) + return false; + return true; + case kStdInt32Array: + if (a->size != b->size) + return false; + if (a->int32array == b->int32array) + return true; + + ASSERT_NOT_NULL(a->int32array); + ASSERT_NOT_NULL(b->int32array); + for (int i = 0; i < a->size; i++) + if (a->int32array[i] != b->int32array[i]) + return false; + return true; + case kStdInt64Array: + if (a->size != b->size) + return false; + if (a->int64array == b->int64array) + return true; + + ASSERT_NOT_NULL(a->int64array); + ASSERT_NOT_NULL(b->int64array); + for (int i = 0; i < a->size; i++) + if (a->int64array[i] != b->int64array[i]) + return false; + return true; + case kStdFloat64Array: + if (a->size != b->size) + return false; + if (a->float64array == b->float64array) + return true; + + ASSERT_NOT_NULL(a->float64array); + ASSERT_NOT_NULL(b->float64array); + for (int i = 0; i < a->size; i++) + if (a->float64array[i] != b->float64array[i]) + return false; + return true; + case kStdList: + // the order of list elements is important + if (a->size != b->size) + return false; + if (a->list == b->list) + return true; + + ASSERT_NOT_NULL(a->list); + ASSERT_NOT_NULL(b->list); + for (int i = 0; i < a->size; i++) + if (!stdvalue_equals(a->list + i, b->list + i)) + return false; + + return true; + case kStdMap: { + // the order is not important here, which makes it a bit difficult to compare + if (a->size != b->size) + return false; + if ((a->keys == b->keys) && (a->values == b->values)) + return true; + + // _keyInBAlsoInA[i] == true means that there's a key in a that matches b->keys[i] + // so if we're searching for a key in b, we can safely ignore / don't need to compare + // keys in b that have they're _keyInBAlsoInA set to true. + bool *_keyInBAlsoInA = alloca(sizeof(bool) * a->size); + memset(_keyInBAlsoInA, 0, sizeof(bool) * a->size); + + for (int i = 0; i < a->size; i++) { + // The key we're searching for in b. + struct std_value *key = a->keys + i; + + int j = 0; + while (j < a->size) { + while ((j < a->size) && _keyInBAlsoInA[j]) + j++; // skip all keys with _keyInBAlsoInA set to true. + if (j >= a->size) + break; + if (stdvalue_equals(key, b->keys + j) == false) { + j++; // if b->keys[j] is not equal to "key", continue searching + } else { + _keyInBAlsoInA[j] = true; + + // the values of "key" in a and b must (of course) also be equivalent. + if (stdvalue_equals(a->values + i, b->values + j) == false) { + return false; + } + break; + } + } + + // we did not find a->keys[i] in b. + if (j + 1 >= a->size) + return false; + } + + return true; + } + default: return false; + } + + return false; } struct std_value *stdmap_get(struct std_value *map, struct std_value *key) { - DEBUG_ASSERT_NOT_NULL(map); - DEBUG_ASSERT_NOT_NULL(key); + ASSERT_NOT_NULL(map); + ASSERT_NOT_NULL(key); - for (int i=0; i < map->size; i++) { - if (stdvalue_equals(&map->keys[i], key)) { - return &map->values[i]; - } - } + for (int i = 0; i < map->size; i++) { + if (stdvalue_equals(&map->keys[i], key)) { + return &map->values[i]; + } + } - return NULL; + return NULL; } + struct std_value *stdmap_get_str(struct std_value *map, char *key) { - DEBUG_ASSERT_NOT_NULL(map); - DEBUG_ASSERT_NOT_NULL(key); - return stdmap_get(map, &STDSTRING(key)); -} \ No newline at end of file + ASSERT_NOT_NULL(map); + ASSERT_NOT_NULL(key); + return stdmap_get(map, &STDSTRING(key)); +} + +/** + * BEGIN Raw Standard Message Codec Value API. + * + * New API, for using standard-message-codec encoded buffers directly, without parsing them first. + * + */ + +ATTR_PURE enum std_value_type raw_std_value_get_type(const struct raw_std_value *value) { + return (enum std_value_type) * ((const uint8_t *) value); +} + +ATTR_CONST static const void *get_value_ptr(const struct raw_std_value *value, int alignment) { + uintptr_t addr = (uintptr_t) value; + + // skip type byte + addr++; + + // make sure we're aligned + if (alignment == 4) { + if (addr & 3) { + addr = (addr | 3) + 1; + } + } else if (alignment == 8) { + if (addr & 7) { + addr = (addr | 7) + 1; + } + } + + return (const void *) addr; +} + +ATTR_CONST UNUSED static const void *get_after_ptr(const struct raw_std_value *value, int alignment, int value_size) { + return ((const uint8_t *) get_value_ptr(value, alignment)) + value_size; +} + +ATTR_CONST static const void *get_array_value_ptr(const struct raw_std_value *value, int alignment, size_t size) { + uintptr_t addr = (uintptr_t) value; + + // skip type byte + addr++; + + // skip initial size byte + addr++; + if (size >= 254 && size < 0x00010000) { + // skip two additional size bytes + addr += 2; + } else if (size >= 0x00010000) { + // skip four additional size bytes + addr += 4; + } + + // make sure we're aligned + if (alignment == 4) { + if (addr & 3) { + addr = (addr | 3) + 1; + } + } else if (alignment == 8) { + if (addr & 7) { + addr = (addr | 7) + 1; + } + } + + return (const void *) addr; +} + +ATTR_CONST static const void *get_array_after_ptr(const struct raw_std_value *value, int alignment, size_t size, int element_size) { + uintptr_t addr = (uintptr_t) get_array_value_ptr(value, alignment, size); + + // skip all the array elements + addr += size * element_size; + + return (const void *) addr; +} + +ATTR_PURE bool raw_std_value_is_null(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdNull; +} + +ATTR_PURE bool raw_std_value_is_true(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdTrue; +} + +ATTR_PURE bool raw_std_value_is_false(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFalse; +} + +ATTR_PURE bool raw_std_value_is_int32(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt32; +} + +ATTR_PURE int32_t raw_std_value_as_int32(const struct raw_std_value *value) { + assert(raw_std_value_is_int32(value)); + int32_t result; + memcpy(&result, get_value_ptr(value, 0), sizeof(result)); + + return result; +} + +ATTR_PURE bool raw_std_value_is_int64(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt64; +} + +ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value) { + assert(raw_std_value_is_int64(value)); + + int64_t result; + memcpy(&result, get_value_ptr(value, 0), sizeof(result)); + + return result; +} + +ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat64; +} + +ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value) { + assert(raw_std_value_is_float64(value)); + return *(double *) get_value_ptr(value, 8); +} + +ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdString; +} + +MALLOCLIKE MUST_CHECK char *raw_std_string_dup(const struct raw_std_value *value) { + assert(raw_std_value_is_string(value)); + + size_t size = raw_std_value_get_size(value); + + char *str = malloc(size + 1); + if (str == NULL) { + return NULL; + } + + memcpy(str, get_array_value_ptr(value, 0, size), size); + str[size] = '\0'; + + return str; +} + +ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str) { + size_t length = raw_std_value_get_size(value); + const char *as_unterminated_string = get_array_value_ptr(value, 0, length); + return strncmp(as_unterminated_string, str, length) == 0 && str[length] == '\0'; +} + +ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdUInt8Array; +} + +ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value) { + assert(raw_std_value_is_uint8array(value)); + return get_array_value_ptr(value, 0, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_int32array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt32Array; +} + +ATTR_PURE const int32_t *raw_std_value_as_int32array(const struct raw_std_value *value) { + assert(raw_std_value_is_int32array(value)); + return get_array_value_ptr(value, 4, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_int64array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdInt64Array; +} + +ATTR_PURE const int64_t *raw_std_value_as_int64array(const struct raw_std_value *value) { + assert(raw_std_value_is_int64array(value)); + return get_array_value_ptr(value, 8, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_float64array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat64Array; +} + +ATTR_PURE const double *raw_std_value_as_float64array(const struct raw_std_value *value) { + assert(raw_std_value_is_float64array(value)); + return get_array_value_ptr(value, 8, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_is_list(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdList; +} + +ATTR_PURE size_t raw_std_list_get_size(const struct raw_std_value *list) { + assert(raw_std_value_is_list(list)); + return raw_std_value_get_size(list); +} + +ATTR_PURE bool raw_std_value_is_map(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdMap; +} + +ATTR_PURE size_t raw_std_map_get_size(const struct raw_std_value *map) { + assert(raw_std_value_is_map(map)); + return raw_std_value_get_size(map); +} + +ATTR_PURE bool raw_std_value_is_float32array(const struct raw_std_value *value) { + return raw_std_value_get_type(value) == kStdFloat32Array; +} + +ATTR_PURE const float *raw_std_value_as_float32array(const struct raw_std_value *value) { + assert(raw_std_value_is_float32array(value)); + return get_array_value_ptr(value, 4, raw_std_value_get_size(value)); +} + +ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct raw_std_value *b) { + size_t length; + int alignment, element_size; + + ASSERT_NOT_NULL(a); + ASSERT_NOT_NULL(b); + + if (a == b) { + return true; + } + + if (raw_std_value_get_type(a) != raw_std_value_get_type(b)) { + return false; + } + + switch (raw_std_value_get_type(a)) { + case kStdNull: + case kStdTrue: + case kStdFalse: return true; + case kStdInt32: return raw_std_value_as_int32(a) == raw_std_value_as_int32(b); + case kStdInt64: return raw_std_value_as_int64(a) == raw_std_value_as_int64(b); + case kStdFloat64: return raw_std_value_as_float64(a) == raw_std_value_as_float64(b); + case kStdString: + alignment = 0; + element_size = 1; + goto memcmp_arrays; + case kStdUInt8Array: + alignment = 0; + element_size = 1; + goto memcmp_arrays; + case kStdInt32Array: + alignment = 4; + element_size = 4; + goto memcmp_arrays; + case kStdInt64Array: + alignment = 8; + element_size = 8; + +memcmp_arrays: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const void *values_a = get_array_value_ptr(a, alignment, length); + const void *values_b = get_array_value_ptr(b, alignment, length); + return memcmp(values_a, values_b, length * element_size) == 0; + case kStdFloat64Array: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const double *a_doubles = raw_std_value_as_float64array(a); + const double *b_doubles = raw_std_value_as_float64array(b); + for (int i = 0; i < length; i++) { + if (a_doubles[i] != b_doubles[i]) { + return false; + } + } + + return true; + case kStdList: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + const struct raw_std_value *cursor_a = raw_std_list_get_first_element(a); + const struct raw_std_value *cursor_b = raw_std_list_get_first_element(b); + for (int i = 0; i < raw_std_value_get_size(a); + i++, cursor_a = raw_std_value_after(cursor_a), cursor_b = raw_std_value_after(cursor_b)) { + if (!raw_std_value_equals(cursor_a, cursor_b)) { + return false; + } + } + + return true; + case kStdMap: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + size_t size = raw_std_value_get_size(a); + + // key_from_b_also_in_a[i] == true means that there's a key in a that matches + // the i-th key in b. So if we're searching for a key from a in b, we can safely ignore / don't need to compare + // keys in b that have they're key_from_b_also_in_a set to true. + bool *key_from_b_found_in_a = calloc(size, sizeof(bool)); + + // for each key in a, look for a matching key in b. + // once it's found, mark it as used, so we don't try matching it again. + // then, check if the values associated with both keys are equal. + for_each_entry_in_raw_std_map(key_a, value_a, a) { + for_each_entry_in_raw_std_map_indexed(index_b, key_b, value_b, b) { + // we've already linked this entry from b to an entry from a. + // skip to the next entry. + if (key_from_b_found_in_a[index_b]) { + continue; + } + + // Keys don't match. Skip to the next entry. + if (!raw_std_value_equals(key_a, key_b)) { + continue; + } + + key_from_b_found_in_a[index_b] = true; + + // the values associated with both keys must be equal. + if (raw_std_value_equals(value_a, value_b) == false) { + free(key_from_b_found_in_a); + return false; + } + + goto found; + } + + // we didn't find key_a in b. + free(key_from_b_found_in_a); + return false; + +found: + continue; + } + + // each key, value pair in a was found in b. + // because a and b have the same number of entries, + // this must mean that every key, value pair in b must also be present in a. + // (a subset of b) and (b subset of a) ==> a equals b. + + free(key_from_b_found_in_a); + return true; + case kStdFloat32Array: + if (raw_std_value_get_size(a) != raw_std_value_get_size(b)) { + return false; + } + + length = raw_std_value_get_size(a); + const float *a_floats = raw_std_value_as_float32array(a); + const float *b_floats = raw_std_value_as_float32array(b); + for (int i = 0; i < length; i++) { + if (a_floats[i] != b_floats[i]) { + return false; + } + } + + return true; + default: assert(false); return false; + } +} + +ATTR_PURE bool raw_std_value_is_bool(const struct raw_std_value *value) { + return raw_std_value_is_true(value) || raw_std_value_is_false(value); +} + +ATTR_PURE bool raw_std_value_as_bool(const struct raw_std_value *value) { + assert(raw_std_value_is_bool(value)); + return raw_std_value_is_true(value); +} + +ATTR_PURE bool raw_std_value_is_int(const struct raw_std_value *value) { + return raw_std_value_is_int32(value) || raw_std_value_is_int64(value); +} + +ATTR_PURE int64_t raw_std_value_as_int(const struct raw_std_value *value) { + assert(raw_std_value_is_int(value)); + if (raw_std_value_is_int32(value)) { + return raw_std_value_as_int32(value); + } else { + return raw_std_value_as_int64(value); + } +} + +ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value) { + const uint8_t *byteptr; + size_t size; + + assert( + raw_std_value_is_uint8array(value) || raw_std_value_is_int32array(value) || raw_std_value_is_int64array(value) || + raw_std_value_is_float64array(value) || raw_std_value_is_string(value) || raw_std_value_is_list(value) || + raw_std_value_is_map(value) || raw_std_value_is_float32array(value) + ); + + byteptr = (const uint8_t *) value; + + // skip type byte + byteptr++; + + size = *byteptr; + if (size <= 253) { + return size; + } else if (size == 254) { + size = 0; + memcpy(&size, byteptr + 1, 2); + assert(size >= 254); + return size; + } else if (size == 255) { + size = 0; + memcpy(&size, byteptr + 1, 4); + assert(size >= 0x10000); + return size; + } + + UNREACHABLE(); +} + +ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_value *value) { + size_t size; + + switch (raw_std_value_get_type(value)) { + case kStdNull: return get_after_ptr(value, 0, 0); + case kStdTrue: return get_after_ptr(value, 0, 0); + case kStdFalse: return get_after_ptr(value, 0, 0); + case kStdInt32: return get_after_ptr(value, 0, 4); + case kStdInt64: return get_after_ptr(value, 0, 8); + case kStdLargeInt: + case kStdString: return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); + case kStdFloat64: return get_after_ptr(value, 8, 8); + case kStdUInt8Array: return get_array_after_ptr(value, 0, raw_std_value_get_size(value), 1); + case kStdInt32Array: return get_array_after_ptr(value, 4, raw_std_value_get_size(value), 4); + case kStdInt64Array: return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + case kStdFloat64Array: return get_array_after_ptr(value, 8, raw_std_value_get_size(value), 8); + case kStdList:; + size = raw_std_value_get_size(value); + + value = get_array_value_ptr(value, 0, size); + for (; size > 0; size--) { + value = raw_std_value_after(value); + } + + return value; + case kStdMap: + size = raw_std_value_get_size(value); + + value = get_array_value_ptr(value, 0, size); + for (; size > 0; size--) { + value = raw_std_value_after(value); + value = raw_std_value_after(value); + } + + return value; + case kStdFloat32Array: return get_array_after_ptr(value, 4, raw_std_value_get_size(value), 4); + default: assert(false); return value; + } +} + +ATTR_PURE const struct raw_std_value *raw_std_list_get_first_element(const struct raw_std_value *list) { + assert(raw_std_value_is_list(list)); + assert(raw_std_value_get_size(list) > 0); + return get_array_value_ptr(list, 0, raw_std_value_get_size(list)); +} + +ATTR_PURE const struct raw_std_value *raw_std_list_get_nth_element(const struct raw_std_value *list, size_t index) { + const struct raw_std_value *element; + + assert(raw_std_value_is_list(list)); + assert(raw_std_value_get_size(list) > index); + + element = raw_std_list_get_first_element(list); + while (index != 0) { + element = raw_std_value_after(element); + index--; + } + + return element; +} + +ATTR_PURE const struct raw_std_value *raw_std_map_get_first_key(const struct raw_std_value *map) { + assert(raw_std_value_is_map(map)); + + return get_array_value_ptr(map, 0, raw_std_value_get_size(map)); +} + +ATTR_PURE const struct raw_std_value *raw_std_map_find(const struct raw_std_value *map, const struct raw_std_value *key) { + assert(raw_std_value_is_map(map)); + + for_each_entry_in_raw_std_map(key_iter, value_iter, map) { + if (raw_std_value_equals(key_iter, key)) { + return value_iter; + } + } + + return NULL; +} + +ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_value *map, const char *str) { + assert(raw_std_value_is_map(map)); + + for_each_entry_in_raw_std_map(key_iter, value_iter, map) { + if (raw_std_value_is_string(key_iter) && raw_std_string_equals(key_iter, str)) { + return value_iter; + } + } + + return NULL; +} + +ATTR_PURE static bool check_size(const struct raw_std_value *value, size_t buffer_size) { + size_t size; + + const uint8_t *byteptr = (const uint8_t *) value; + + if (buffer_size < 2) { + return false; + } + + // skip type byte + byteptr++; + + size = *byteptr; + buffer_size -= 2; + + if (size == 254) { + if (buffer_size < 2) { + return false; + } + + // Calculation in @ref get_array_value_ptr assumes an array size 254 <= s < 0x10000 uses 3 size bytes + // If we allow a size smaller than 254 here, it would break that calculation. + size = 0; + memcpy(&size, byteptr + 1, 2); + if (size < 254) { + return false; + } + } else if (size == 255) { + if (buffer_size < 4) { + return false; + } + + // See above. + size = 0; + memcpy(&size, byteptr + 1, 4); + if (size < 0x10000) { + return false; + } + } + + return true; +} + +ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buffer_size) { + size_t size; + int alignment, element_size; + + if (buffer_size < 1) { + return false; + } + + switch (raw_std_value_get_type(value)) { + case kStdNull: + case kStdTrue: + case kStdFalse: return true; + case kStdInt32: return buffer_size >= 5; + case kStdInt64: return buffer_size >= 9; + case kStdFloat64: return buffer_size >= 9; + case kStdString: + alignment = 0; + element_size = 1; + goto check_arrays; + case kStdUInt8Array: + alignment = 0; + element_size = 1; + goto check_arrays; + case kStdInt32Array: + alignment = 4; + element_size = 4; + goto check_arrays; + case kStdInt64Array: + alignment = 8; + element_size = 8; + goto check_arrays; + case kStdFloat64Array: + alignment = 8; + element_size = 8; + goto check_arrays; + case kStdFloat32Array: + alignment = 4; + element_size = 4; + +// common code for checking if the buffer is large enough to contain +// a fixed size array. +check_arrays: + + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + // get the offset of the actual array values. + int diff = (intptr_t) get_array_value_ptr(value, alignment, size) - (intptr_t) value; + assert(diff >= 0); + + if (buffer_size < diff) { + return false; + } + buffer_size -= diff; + + // check if the buffer is large enough to contain all the the array values. + if (buffer_size < size * element_size) { + return false; + } + + return true; + case kStdList: + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + for_each_element_in_raw_std_list(element, value) { + int diff = (intptr_t) element - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(element, buffer_size - diff)) { + return false; + } + } + + return true; + case kStdMap: + // check if buffer is large enough to contain the value size. + if (!check_size(value, buffer_size)) { + return false; + } + + // get the value size. + size = raw_std_value_get_size(value); + + const struct raw_std_value *key = NULL, *map_value; + for (int diff, i = 0; i < size; i++) { + if (key == NULL) { + key = raw_std_map_get_first_key(value); + } else { + key = raw_std_value_after(map_value); + } + + diff = (intptr_t) key - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(key, buffer_size - diff)) { + return false; + } + + map_value = raw_std_value_after(key); + + diff = (intptr_t) map_value - (intptr_t) value; + if (buffer_size < diff) { + return false; + } + + if (!raw_std_value_check(map_value, buffer_size - diff)) { + return false; + } + } + + return true; + default: return false; + } +} + +ATTR_PURE bool raw_std_method_call_check(const struct raw_std_value *value, size_t buffer_size) { + if (!raw_std_value_check(value, buffer_size)) { + return false; + } + + // first value must be a string. (method name) + if (!raw_std_value_is_string(value)) { + return false; + } + + const struct raw_std_value *after = raw_std_value_after(value); + int diff = (intptr_t) after - (intptr_t) value; + assert(diff <= buffer_size); + + if (!raw_std_value_check(after, buffer_size - diff)) { + return false; + } + + return true; +} + +ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *value, size_t buffer_size) { + // method call responses have non-standard encoding for the first byte. + // first byte is zero for failure, non-zero for success. + if (buffer_size < 1) { + return false; + } + + bool successful = (*(const uint8_t *) (value)) != 0; + + value = (void *) ((intptr_t) value + (intptr_t) 1); + buffer_size -= 1; + + if (successful) { + if (!raw_std_value_check(value, buffer_size)) { + return false; + } + } else { + // first value should be the error code (string). + if (!raw_std_value_check(value, buffer_size) || !raw_std_value_is_string(value)) { + return false; + } + + const struct raw_std_value *second = raw_std_value_after(value); + int diff = (intptr_t) second - (intptr_t) value; + assert(diff <= buffer_size); + + // second value should be the error message. (string or null) + if (!raw_std_value_check(second, buffer_size - diff)) { + return false; + } + + if (!raw_std_value_is_string(second) && !raw_std_value_is_null(second)) { + return false; + } + + const struct raw_std_value *third = raw_std_value_after(value); + diff = (intptr_t) third - (intptr_t) value; + assert(diff <= buffer_size); + + // third value is the error detail. Can be anything. + if (!raw_std_value_check(third, buffer_size - diff)) { + return false; + } + } + + return true; +} + +ATTR_PURE bool raw_std_event_check(const struct raw_std_value *value, size_t buffer_size) { + return raw_std_method_call_response_check(value, buffer_size); +} + +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struct raw_std_value *value) { + assert(raw_std_value_is_string(value)); + return value; +} + +ATTR_PURE bool raw_std_method_call_is_method(const struct raw_std_value *value, const char *method_name) { + assert(raw_std_value_is_string(value)); + return raw_std_string_equals(value, method_name); +} + +MALLOCLIKE MUST_CHECK char *raw_std_method_call_get_method_dup(const struct raw_std_value *value) { + assert(raw_std_value_is_string(value)); + return raw_std_string_dup(value); +} + +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct raw_std_value *value) { + return raw_std_value_after(value); +} diff --git a/src/platformchannel.h b/src/platformchannel.h new file mode 100644 index 00000000..68cfef6e --- /dev/null +++ b/src/platformchannel.h @@ -0,0 +1,1641 @@ +// SPDX-License-Identifier: MIT +/* + * Platform Channels + * + * Encoding/Decoding of flutter platform messages, with different + * + * Supported codecs: + * - standard message & method codec, + * - json message & method codec + * - pigeon (not really a codec) + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_PLATFORMCHANNEL_H +#define _FLUTTERPI_SRC_PLATFORMCHANNEL_H + +#include +#include + +#include + +#include "util/collection.h" + +#define JSON_DECODE_TOKENLIST_SIZE 128 + +/* + * It may be simpler for plugins if the two message value types were unified. + * But from a performance POV, this doesn't make sense. number arrays in StandardMessageCodec + * are 4 or 8 -byte aligned for faster access. We don't have to copy them, StdMsgCodecValue.int64array (as an example) + * is just a pointer to that portion of the buffer, where the array is located. + * + * However, JSON and thus JSON Message Handlers have no idea what a int64array is, they just know of JSON arrays. + * This means we'd have to implicitly convert the int64array into a JSON array when we want to unify the two message value types, + * and this costs all the performance we (more precisely, the flutter engineers) gained by memory-aligning the arrays in StdMsgCodecValue. + * + * Let's just hope the flutter team doesn't randomly switch codecs of platform channels. Receive Handlers would + * need to be rewritten every time they do. The handlers not needing to be rewritten would probably be the only advantage + * of using a unified message value type. + */ +enum json_value_type { kJsonNull, kJsonTrue, kJsonFalse, kJsonNumber, kJsonString, kJsonArray, kJsonObject }; +struct json_value { + enum json_value_type type; + union { + double number_value; + char *string_value; + struct { + size_t size; + union { + struct json_value *array; + struct { + char **keys; + struct json_value *values; + }; + }; + }; + }; +}; + +enum std_value_type { + kStdNull = 0, + kStdTrue, + kStdFalse, + kStdInt32, + kStdInt64, + kStdLargeInt, // treat as kString + kStdFloat64, + kStdString, + kStdUInt8Array, + kStdInt32Array, + kStdInt64Array, + kStdFloat64Array, + kStdList, + kStdMap, + kStdFloat32Array +}; + +struct std_value { + enum std_value_type type; + union { + bool bool_value; + int32_t int32_value; + int64_t int64_value; + double float64_value; + char *string_value; + struct { + size_t size; + union { + const uint8_t *uint8array; + int32_t *int32array; + int64_t *int64array; + double *float64array; + struct std_value *list; + struct { + struct std_value *keys; + struct std_value *values; + }; + }; + }; + }; +}; + +#define STDVALUE_IS_NULL(value) ((value).type == kStdNull) +#define STDNULL ((struct std_value){ .type = kStdNull }) + +#define STDVALUE_IS_BOOL(value) (((value).type == kStdTrue) || ((value).type == kStdFalse)) +#define STDVALUE_AS_BOOL(value) ((value).type == kStdTrue) +#define STDBOOL(bool_value) ((struct std_value){ .type = (bool_value) ? kStdTrue : kStdFalse }) + +#define STDVALUE_IS_INT(value) (((value).type == kStdInt32) || ((value).type == kStdInt64)) +#define STDVALUE_AS_INT(value) ((value).type == kStdInt32 ? (int64_t) (value).int32_value : (value).int64_value) +#define STDINT32(_int32_value) ((struct std_value){ .type = kStdInt32, .int32_value = (_int32_value) }) +#define STDINT64(_int64_value) ((struct std_value){ .type = kStdInt64, .int64_value = (_int64_value) }) + +#define STDVALUE_IS_FLOAT(value) ((value).type == kStdFloat64) +#define STDVALUE_AS_FLOAT(value) ((value).float64_value) +#define STDFLOAT64(double_value) ((struct std_value){ .type = kStdFloat64, .float64_value = (double_value) }) + +#define STDVALUE_IS_NUM(value) (STDVALUE_IS_INT(value) || STDVALUE_IS_FLOAT(value)) +#define STDVALUE_AS_NUM(value) (STDVALUE_IS_INT(value) ? STDVALUE_AS_INT(value) : STDVALUE_AS_FLOAT(value)) + +#define STDVALUE_IS_STRING(value) ((value).type == kStdString) +#define STDVALUE_AS_STRING(value) ((value).string_value) + +#define STDSTRING(str) ((struct std_value){ .type = kStdString, .string_value = (str) }) + +#define STDVALUE_IS_LIST(value) ((value).type == kStdList) +#define STDVALUE_IS_SIZE(value, _size) ((value).size == (_size)) +#define STDVALUE_IS_SIZED_LIST(value, _size) (STDVALUE_IS_LIST(value) && STDVALUE_IS_SIZE(value, _size)) + +#define STDVALUE_IS_INT_ARRAY(value) \ + (((value).type == kStdInt32Array) || ((value).type == kStdInt64Array) || ((value).type == kStdUInt8Array)) +#define STDVALUE_IS_FLOAT_ARRAY(value) ((value).type == kStdFloat64Array) +#define STDVALUE_IS_NUM_ARRAY(value) (STDVALUE_IS_INT_ARRAY(value) || STDVALUE_IS_FLOAT_ARRAY(value)) + +#define STDVALUE_IS_MAP(value) ((value).type == kStdMap) +#define STDVALUE_IS_SIZED_MAP(value, _size) ((value).size == (_size)) + +#define STDMAP1(key1, val1) \ + ((struct std_value){ .type = kStdMap, .size = 1, .keys = (struct std_value[1]){ (key1) }, .values = (struct std_value[1]){ (val1) } }) + +#define STDMAP2(key1, val1, key2, val2) \ + ((struct std_value \ + ){ .type = kStdMap, .size = 2, .keys = (struct std_value[2]){ (key1), (key2) }, .values = (struct std_value[2]){ (val1), (val2) } }) +#define STDMAP3(key1, val1, key2, val2, key3, val3) \ + ((struct std_value){ .type = kStdMap, \ + .size = 3, \ + .keys = (struct std_value[3]){ (key1), (key2), (key3) }, \ + .values = (struct std_value[3]){ (val1), (val2), (val3) } }) +#define STDMAP4(key1, val1, key2, val2, key3, val3, key4, val4) \ + ((struct std_value){ .type = kStdMap, \ + .size = 4, \ + .keys = (struct std_value[4]){ (key1), (key2), (key3), (key4) }, \ + .values = (struct std_value[4]){ (val1), (val2), (val3), (val4) } }) +#define STDMAP5(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5) \ + ((struct std_value){ .type = kStdMap, \ + .size = 5, \ + .keys = (struct std_value[5]){ (key1), (key2), (key3), (key4), (key5) }, \ + .values = (struct std_value[5]){ (val1), (val2), (val3), (val4), (val5) } }) +#define STDMAP6(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6) \ + ((struct std_value){ .type = kStdMap, \ + .size = 6, \ + .keys = (struct std_value[6]){ (key1), (key2), (key3), (key4), (key5), (key6) }, \ + .values = (struct std_value[6]){ (val1), (val2), (val3), (val4), (val5), (val6) } }) +#define STDMAP7(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7) \ + ((struct std_value){ .type = kStdMap, \ + .size = 7, \ + .keys = (struct std_value[7]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7) }, \ + .values = (struct std_value[7]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7) } }) +#define STDMAP8(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8) \ + ((struct std_value){ .type = kStdMap, \ + .size = 8, \ + .keys = (struct std_value[8]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8) }, \ + .values = (struct std_value[8]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8) } }) +#define STDMAP9(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9) \ + ((struct std_value){ .type = kStdMap, \ + .size = 9, \ + .keys = (struct std_value[9]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9) }, \ + .values = (struct std_value[9]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9) } }) +#define STDMAP10(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10) \ + ((struct std_value \ + ){ .type = kStdMap, \ + .size = 10, \ + .keys = (struct std_value[10]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10) }, \ + .values = (struct std_value[10]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10) } }) +#define STDMAP11( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11 \ +) \ + ((struct std_value \ + ){ .type = kStdMap, \ + .size = 11, \ + .keys = (struct std_value[11]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11) }, \ + .values = (struct std_value[11]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11) } }) +#define STDMAP12( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 12, \ + .keys = (struct std_value[12] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12) }, \ + .values = (struct std_value[12] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12) } }) +#define STDMAP13( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 13, \ + .keys = (struct std_value[13] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13) }, \ + .values = (struct std_value[13] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13) } } \ + ) +#define STDMAP14( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14 \ +) \ + ((struct std_value \ + ){ .type = kStdMap, \ + .size = 14, \ + .keys = (struct std_value[14] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14) }, \ + .values = (struct std_value[14] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14) } }) +#define STDMAP15( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15 \ +) \ + ((struct std_value \ + ){ .type = kStdMap, \ + .size = 15, \ + .keys = (struct std_value[15] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15) }, \ + .values = (struct std_value[15] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15) } } \ + ) +#define STDMAP16( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 16, \ + .keys = (struct std_value[16]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16) }, \ + .values = (struct std_value[16]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16) } }) +#define STDMAP17( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 17, \ + .keys = (struct std_value[17]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17) }, \ + .values = (struct std_value[17]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17) } }) +#define STDMAP18( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 18, \ + .keys = (struct std_value[18]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17), \ + (key18) }, \ + .values = (struct std_value[18]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17), \ + (val18) } }) +#define STDMAP19( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18, \ + key19, \ + val19 \ +) \ + ((struct std_value){ .type = kStdMap, \ + .size = 19, \ + .keys = (struct std_value[19]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17), \ + (key18), \ + (key19) }, \ + .values = (struct std_value[19]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17), \ + (val18), \ + (val19) } }) +#define STDMAP20( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18, \ + key19, \ + val19, \ + key20, \ + val20 \ +) \ + ((struct std_value \ + ){ .type = kStdMap, \ + .size = 20, \ + .keys = (struct std_value[20]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), \ + (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19), (key20) }, \ + .values = (struct std_value[20]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), \ + (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19), (val20) } }) + +#define STDLIST1(val1) ((struct std_value){ .type = kStdList, .size = 1, .list = (struct std_value[1]){ (val1) } }) + +#define STDLIST2(val1, val2) ((struct std_value){ .type = kStdList, .size = 2, .list = (struct std_value[2]){ (val1), (val2) } }) + +#define JSONVALUE_IS_NULL(value) ((value).type == kJsonNull) +#define JSONNULL ((struct json_value){ .type = kJsonNull }) + +#define JSONVALUE_IS_BOOL(value) (((value).type == kJsonTrue) || ((value).type == kJsonFalse)) +#define JSONVALUE_AS_BOOL(value) ((value).type == kJsonTrue) +#define JSONBOOL(bool_value) ((struct json_value){ .type = (bool_value) ? kJsonTrue : kJsonFalse }) + +#define JSONVALUE_IS_NUM(value) ((value).type == kJsonNumber) +#define JSONVALUE_AS_NUM(value) ((value).number_value) +#define JSONNUM(_number_value) ((struct json_value){ .type = kJsonNumber, .number_value = (_number_value) }) + +#define JSONVALUE_IS_STRING(value) ((value).type == kJsonString) +#define JSONVALUE_AS_STRING(value) ((value).string_value) +#define JSONSTRING(str) ((struct json_value){ .type = kJsonString, .string_value = str }) + +#define JSONVALUE_IS_ARRAY(value) ((value).type == kJsonArray) +#define JSONVALUE_IS_SIZE(value, _size) ((value).size == (_size)) +#define JSONVALUE_IS_SIZED_ARRAY(value, _size) (JSONVALUE_IS_ARRAY(value) && JSONVALUE_IS_SIZE(value, _size)) + +#define JSONVALUE_IS_OBJECT(value) ((value).type == kJsonObject) +#define JSONVALUE_IS_SIZED_OBJECT(value, _size) (JSONVALUE_IS_OBJECT(value) && JSONVALUE_IS_SIZE(value, _size)) + +#define JSONARRAY1(val1) ((struct json_value){ .type = kJsonArray, .size = 1, .array = (struct json_value[1]){ (val1) } }) +#define JSONARRAY2(val1, val2) ((struct json_value){ .type = kJsonArray, .size = 2, .array = (struct json_value[2]){ (val1), (val2) } }) +#define JSONARRAY3(val1, val2, val3) \ + ((struct json_value){ .type = kJsonArray, .size = 3, .array = (struct json_value[3]){ (val1), (val2), (val3) } }) + +#define JSONOBJECT1(key1, val1) \ + ((struct json_value){ .type = kJsonObject, .size = 1, .keys = (char *[1]){ (key1) }, .values = (struct json_value[1]){ (val1) } }) + +#define JSONOBJECT2(key1, val1, key2, val2) \ + ((struct json_value \ + ){ .type = kJsonObject, .size = 2, .keys = (char *[2]){ (key1), (key2) }, .values = (struct json_value[2]){ (val1), (val2) } }) +#define JSONOBJECT3(key1, val1, key2, val2, key3, val3) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 3, \ + .keys = (char *[3]){ (key1), (key2), (key3) }, \ + .values = (struct json_value[3]){ (val1), (val2), (val3) } }) +#define JSONOBJECT4(key1, val1, key2, val2, key3, val3, key4, val4) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 4, \ + .keys = (char *[4]){ (key1), (key2), (key3), (key4) }, \ + .values = (struct json_value[4]){ (val1), (val2), (val3), (val4) } }) +#define JSONOBJECT5(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 5, \ + .keys = (char *[5]){ (key1), (key2), (key3), (key4), (key5) }, \ + .values = (struct json_value[5]){ (val1), (val2), (val3), (val4), (val5) } }) +#define JSONOBJECT6(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 6, \ + .keys = (char *[6]){ (key1), (key2), (key3), (key4), (key5), (key6) }, \ + .values = (struct json_value[6]){ (val1), (val2), (val3), (val4), (val5), (val6) } }) +#define JSONOBJECT7(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 7, \ + .keys = (char *[7]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7) }, \ + .values = (struct json_value[7]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7) } }) +#define JSONOBJECT8(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 8, \ + .keys = (char *[8]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8) }, \ + .values = (struct json_value[8]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8) } }) +#define JSONOBJECT9(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 9, \ + .keys = (char *[9]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9) }, \ + .values = (struct json_value[9]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9) } }) +#define JSONOBJECT10(key1, val1, key2, val2, key3, val3, key4, val4, key5, val5, key6, val6, key7, val7, key8, val8, key9, val9, key10, val10) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 10, \ + .keys = (char *[10]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10) }, \ + .values = (struct json_value[10]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10) } }) +#define JSONOBJECT11( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 11, \ + .keys = (char *[11]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11) }, \ + .values = (struct json_value[11]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11) } }) +#define JSONOBJECT12( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 12, \ + .keys = (char *[12]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12) }, \ + .values = (struct json_value[12] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12) } }) +#define JSONOBJECT13( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 13, \ + .keys = (char *[13]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13) }, \ + .values = (struct json_value[13] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13) } }) +#define JSONOBJECT14( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 14, \ + .keys = (char *[14] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14) }, \ + .values = (struct json_value[14] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14) } }) +#define JSONOBJECT15( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 15, \ + .keys = (char *[15] \ + ){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), (key11), (key12), (key13), (key14), (key15) }, \ + .values = (struct json_value[15] \ + ){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), (val11), (val12), (val13), (val14), (val15) } } \ + ) +#define JSONOBJECT16( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16 \ +) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 16, \ + .keys = (char *[16]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16) }, \ + .values = (struct json_value[16]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16) } }) +#define JSONOBJECT17( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17 \ +) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 17, \ + .keys = (char *[17]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17) }, \ + .values = (struct json_value[17]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17) } }) +#define JSONOBJECT18( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18 \ +) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 18, \ + .keys = (char *[18]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17), \ + (key18) }, \ + .values = (struct json_value[18]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17), \ + (val18) } }) +#define JSONOBJECT19( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18, \ + key19, \ + val19 \ +) \ + ((struct json_value){ .type = kJsonObject, \ + .size = 19, \ + .keys = (char *[19]){ (key1), \ + (key2), \ + (key3), \ + (key4), \ + (key5), \ + (key6), \ + (key7), \ + (key8), \ + (key9), \ + (key10), \ + (key11), \ + (key12), \ + (key13), \ + (key14), \ + (key15), \ + (key16), \ + (key17), \ + (key18), \ + (key19) }, \ + .values = (struct json_value[19]){ (val1), \ + (val2), \ + (val3), \ + (val4), \ + (val5), \ + (val6), \ + (val7), \ + (val8), \ + (val9), \ + (val10), \ + (val11), \ + (val12), \ + (val13), \ + (val14), \ + (val15), \ + (val16), \ + (val17), \ + (val18), \ + (val19) } }) +#define JSONOBJECT20( \ + key1, \ + val1, \ + key2, \ + val2, \ + key3, \ + val3, \ + key4, \ + val4, \ + key5, \ + val5, \ + key6, \ + val6, \ + key7, \ + val7, \ + key8, \ + val8, \ + key9, \ + val9, \ + key10, \ + val10, \ + key11, \ + val11, \ + key12, \ + val12, \ + key13, \ + val13, \ + key14, \ + val14, \ + key15, \ + val15, \ + key16, \ + val16, \ + key17, \ + val17, \ + key18, \ + val18, \ + key19, \ + val19, \ + key20, \ + val20 \ +) \ + ((struct json_value \ + ){ .type = kJsonObject, \ + .size = 20, \ + .keys = (char *[20]){ (key1), (key2), (key3), (key4), (key5), (key6), (key7), (key8), (key9), (key10), \ + (key11), (key12), (key13), (key14), (key15), (key16), (key17), (key18), (key19), (key20) }, \ + .values = (struct json_value[20]){ (val1), (val2), (val3), (val4), (val5), (val6), (val7), (val8), (val9), (val10), \ + (val11), (val12), (val13), (val14), (val15), (val16), (val17), (val18), (val19), (val20) } }) + +/// codec of an abstract channel object +/// These tell this API how it should encode ChannelObjects -> platform messages +/// and how to decode platform messages -> ChannelObjects. +enum platch_codec { + kNotImplemented, + kStringCodec, + kBinaryCodec, + kJSONMessageCodec, + kStandardMessageCodec, + kStandardMethodCall, + kStandardMethodCallResponse, + kJSONMethodCall, + kJSONMethodCallResponse +}; + +/// Platform Channel Object. +/// Different properties are "valid" for different codecs: +/// kNotImplemented: +/// no values associated with this "codec". +/// this represents a platform message with no buffer and zero length. (so just an empty response) +/// kStringCodec: +/// - string_value is the raw byte data of a platform message, but with an additional null-byte at the end. +/// kBinaryCodec: +/// - binarydata is an array of the raw byte data of a platform message, +/// - binarydata_size is the size of that byte data in bytes. +/// kJSONMessageCodec: +/// - json_value +/// kStandardMessageCodec: +/// - std_value +/// kStandardMethodCall: +/// - "method" is the method you'd like to call, or the method that was called +/// by flutter. +/// - std_arg contains the argument to that method call. +/// kJSONMethodCall: +/// - "method" is the method you'd like to call, or the method that was called +/// by flutter. +/// - json_arg contains the argument to that method call. +/// kStandardMethodCallResponse or kJSONMethodCallResponse: +/// - "success" is whether the method call (called by flutter or by called by you) +/// if success is false, +/// - error_code must be set to point to a valid null-terminated string, +/// - error_msg is either pointing to a valid null-terminated string or is set to NULL, +/// - if the codec is kStandardMethodCallResponse, +/// std_error_details must be a valid std_value +/// ({.type = kStdNull} is possible, but not NULL). +/// - if the codec is kJSONMethodCallResponse, +/// json_error_details must be a valid json_value +/// ({.type = kJsonNull} is possible, but not NULL) +struct platch_obj { + enum platch_codec codec; + union { + char *string_value; + struct { + size_t binarydata_size; + const uint8_t *binarydata; + }; + struct json_value json_value; + struct std_value std_value; + struct { + char *method; + union { + struct std_value std_arg; + struct json_value json_arg; + }; + }; + struct { + bool success; + union { + struct std_value std_result; + struct json_value json_result; + }; + char *error_code; + char *error_msg; + union { + struct std_value std_error_details; + struct json_value json_error_details; + }; + }; + }; +}; + +#define PLATCH_OBJ_NOT_IMPLEMENTED ((struct platch_obj){ .codec = kNotImplemented }) +#define PLATCH_OBJ_STRING(string) ((struct platch_obj){ .codec = kStringCodec, .string_value = (string) }) +#define PLATCH_OBJ_BINARY_DATA(data, data_size) \ + ((struct platch_obj){ .codec = kBinaryCodec, .binarydata_size = (data_size), .binarydata = (data) }) +#define PLATCH_OBJ_JSON_MSG(__json_value) ((struct platch_obj){ .codec = kJSONMessageCodec, .json_value = (__json_value) }) +#define PLATCH_OBJ_STD_MSG(__std_value) ((struct platch_obj){ .codec = kStandardMessageCodec, .std_value = (__std_value) }) +#define PLATCH_OBJ_STD_CALL(method_name, arg) \ + ((struct platch_obj){ .codec = kStandardMethodCall, .method = (char *) (method_name), .std_arg = (arg) }) +#define PLATCH_OBJ_JSON_CALL(method_name, arg) \ + ((struct platch_obj){ .codec = kJSONMethodCall, .method = (char *) (method_name), .json_arg = (arg) }) +#define PLATCH_OBJ_STD_CALL_SUCCESS_RESPONSE(result) \ + ((struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = (result) }) +#define PLATCH_OBJ_STD_CALL_ERROR_RESPONSE(code, msg, details) \ + ((struct platch_obj){ .codec = kStandardMethodCallResponse, \ + .success = false, \ + .error_code = (char *) (code), \ + .error_msg = (char *) (msg), \ + .std_error_details = (details) }) +#define PLATCH_OBJ_JSON_CALL_SUCCESS_RESPONSE(result) \ + ((struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = (result) }) +#define PLATCH_OBJ_JSON_CALL_ERROR_RESPONSE(code, msg, details) \ + ((struct platch_obj){ .codec = kStandardMethodCallResponse, \ + .success = false, \ + .error_code = (char *) (code), \ + .error_msg = (char *) (msg), \ + .json_error_details = (details) }) +#define PLATCH_OBJ_STD_SUCCESS_EVENT(value) PLATCH_OBJ_STD_CALL_SUCCESS_RESPONSE(value) +#define PLATCH_OBJ_STD_ERROR_EVENT(code, msg, details) PLATCH_OBJ_STD_CALL_ERROR_RESPONSE(code, msg, details) +#define PLATCH_OBJ_JSON_SUCCESS_EVENT(value) PLATCH_OBJ_JSON_CALL_SUCCESS_RESPONSE(value) +#define PLATCH_OBJ_JSON_ERROR_EVENT(code, msg, details) PLATCH_OBJ_JSON_CALL_ERROR_RESPONSE(code, msg, details) + +/// A Callback that is called when a response to a platform message you send to flutter +/// arrives. "object" is the platform message decoded using the "codec" you gave to PlatformChannel_send, +/// "userdata" is the userdata you gave to PlatformChannel_send. +typedef int (*platch_msg_resp_callback)(struct platch_obj *object, void *userdata); + +/// decodes a platform message (represented by `buffer` and `size`) as the given codec, +/// and puts the result into object_out. +/// This method will (in some cases) dynamically allocate memory, +/// so you should always call PlatformChannel_free(object_out) when you don't need it anymore. +/// +/// Additionally, PlatformChannel_decode currently "borrows" from the buffer, so if the buffer +/// is freed by flutter, the contents of object_out will in many cases be bogus. +/// If you'd like object_out to be persistent and not depend on the lifetime of the buffer, +/// you'd have to manually deep-copy it. +int platch_decode(const uint8_t *buffer, size_t size, enum platch_codec codec, struct platch_obj *object_out); + +/// Encodes a generic ChannelObject into a buffer (that is, too, allocated by PlatformChannel_encode) +/// A pointer to the buffer is put into buffer_out and the size of that buffer into size_out. +/// The lifetime of the buffer is independent of the ChannelObject, so contents of the ChannelObject +/// can be freed after the object was encoded. +int platch_encode(struct platch_obj *object, uint8_t **buffer_out, size_t *size_out); + +/// Encodes a generic ChannelObject (anything, string/binary codec or Standard/JSON Method Calls and responses) as a platform message +/// and sends it to flutter on channel `channel` +/// If you supply a response callback (i.e. on_response is != NULL): +/// when flutter responds to this message, it is automatically decoded using the codec given in `response_codec`. +/// Then, on_response is called with the decoded ChannelObject and the userdata as an argument. +/// It's possible that flutter won't respond to your platform message, like when sending events via an EventChannel. +/// userdata can be NULL. +int platch_send( + char *channel, + struct platch_obj *object, + enum platch_codec response_codec, + platch_msg_resp_callback on_response, + void *userdata +); + +/// Encodes a StandardMethodCodec method call as a platform message and sends it to flutter +/// on channel `channel`. This is just a wrapper around PlatformChannel_send +/// that builds the ChannelObject for you. +/// The response_codec is kStandardMethodCallResponse. userdata can be NULL. +int platch_call_std(char *channel, char *method, struct std_value *argument, platch_msg_resp_callback on_response, void *userdata); + +/// Encodes the arguments as a JSON method call and sends it to flutter +/// on channel [channel]. This is just a wrapper around platch_send +/// that builds the ChannelObject for you. +/// The response is automatically decoded as a JSON method call response and +/// supplied to [on_response] as an argument. userdata can be NULL. +int platch_call_json(char *channel, char *method, struct json_value *argument, platch_msg_resp_callback on_response, void *userdata); + +/// Responds to a platform message. You can (of course) only respond once to a platform message, +/// i.e. a FlutterPlatformMessageResponseHandle can only be used once. +/// The codec of `response` can be any of the available codecs. +int platch_respond(const FlutterPlatformMessageResponseHandle *handle, struct platch_obj *response); + +/// Tells flutter that the platform message that was sent to you was not handled. +/// (for example, there's no plugin that is using this channel, or there is a plugin +/// but it doesn't want to respond.) +/// You should always use this instead of not replying to a platform message, since not replying could cause a memory leak. +/// When flutter receives this response, it will throw a MissingPluginException. +/// For most channel used by the ServicesPlugin, this is not too bad since it +/// specifies many of the channels used as OptionalMethodChannels. (which will silently catch the MissingPluginException) +int platch_respond_not_implemented(const FlutterPlatformMessageResponseHandle *handle); + +int platch_respond_success_std(const FlutterPlatformMessageResponseHandle *handle, struct std_value *return_value); + +int platch_respond_error_std( + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details +); + +int platch_respond_illegal_arg_std(const FlutterPlatformMessageResponseHandle *handle, char *error_msg); + +int platch_respond_illegal_arg_ext_std(const FlutterPlatformMessageResponseHandle *handle, char *error_msg, struct std_value *error_details); + +int platch_respond_native_error_std(const FlutterPlatformMessageResponseHandle *handle, int _errno); + +int platch_respond_success_json(const FlutterPlatformMessageResponseHandle *handle, struct json_value *return_value); + +int platch_respond_error_json( + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct json_value *error_details +); + +int platch_respond_illegal_arg_json(const FlutterPlatformMessageResponseHandle *handle, char *error_msg); + +int platch_respond_native_error_json(const FlutterPlatformMessageResponseHandle *handle, int _errno); + +int platch_respond_success_pigeon(const FlutterPlatformMessageResponseHandle *handle, struct std_value *return_value); + +int platch_respond_error_pigeon( + const FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details +); + +int platch_respond_illegal_arg_pigeon(const FlutterPlatformMessageResponseHandle *handle, char *error_msg); + +int platch_respond_illegal_arg_ext_pigeon( + const FlutterPlatformMessageResponseHandle *handle, + char *error_msg, + struct std_value *error_details +); + +int platch_respond_native_error_pigeon(const FlutterPlatformMessageResponseHandle *handle, int _errno); + +/// Sends a success event with value `event_value` to an event channel +/// that uses the standard method codec. +int platch_send_success_event_std(char *channel, struct std_value *event_value); + +/// Sends an error event to an event channel that uses the standard method codec. +int platch_send_error_event_std(char *channel, char *error_code, char *error_msg, struct std_value *error_details); +/// Sends a success event with value `event_value` to an event channel +/// that uses the JSON method codec. +int platch_send_success_event_json(char *channel, struct json_value *event_value); + +/// Sends an error event to an event channel that uses the JSON method codec. +int platch_send_error_event_json(char *channel, char *error_code, char *error_msg, struct json_value *error_details); + +/// frees a ChannelObject that was decoded using PlatformChannel_decode. +/// not freeing ChannelObjects may result in a memory leak. +int platch_free_obj(struct platch_obj *object); + +int platch_free_json_value(struct json_value *value, bool shallow); + +/// returns true if values a and b are equal. +/// for JS arrays, the order of the values is relevant +/// (so two arrays are only equal if the same values appear in exactly same order) +/// for objects, the order of the entries is irrelevant. +bool jsvalue_equals(struct json_value *a, struct json_value *b); + +/// given a JS object as an argument, it searches for an entry with key "key" +/// and returns the value associated with it. +/// if the key is not found, returns NULL. +struct json_value *jsobject_get(struct json_value *object, char *key); + +/// StdMsgCodecValue equivalent of jsvalue_equals. +/// again, for lists, the order of values is relevant +/// for maps, it's not. +bool stdvalue_equals(struct std_value *a, struct std_value *b); + +/// StdMsgCodecValue equivalent of jsobject_get, just that the key can be +/// any arbitrary StdMsgCodecValue (and must not be a string as for jsobject_get) +struct std_value *stdmap_get(struct std_value *map, struct std_value *key); + +struct std_value *stdmap_get_str(struct std_value *map, char *key); + +struct raw_std_value; + +ATTR_PURE bool raw_std_value_is_null(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_true(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_false(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int32(const struct raw_std_value *value); +ATTR_PURE int32_t raw_std_value_as_int32(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int64(const struct raw_std_value *value); +ATTR_PURE int64_t raw_std_value_as_int64(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_float64(const struct raw_std_value *value); +ATTR_PURE double raw_std_value_as_float64(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_string(const struct raw_std_value *value); +MALLOCLIKE MUST_CHECK char *raw_std_string_dup(const struct raw_std_value *value); +ATTR_PURE bool raw_std_string_equals(const struct raw_std_value *value, const char *str); +ATTR_PURE bool raw_std_value_is_uint8array(const struct raw_std_value *value); +ATTR_PURE const uint8_t *raw_std_value_as_uint8array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int32array(const struct raw_std_value *value); +ATTR_PURE const int32_t *raw_std_value_as_int32array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int64array(const struct raw_std_value *value); +ATTR_PURE const int64_t *raw_std_value_as_int64array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_float64array(const struct raw_std_value *value); +ATTR_PURE const double *raw_std_value_as_float64array(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_list(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_list_get_size(const struct raw_std_value *list); +ATTR_PURE bool raw_std_value_is_map(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_map_get_size(const struct raw_std_value *map); +ATTR_PURE bool raw_std_value_is_float32array(const struct raw_std_value *value); +ATTR_PURE const float *raw_std_value_as_float32array(const struct raw_std_value *value); + +ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct raw_std_value *b); +ATTR_PURE bool raw_std_value_is_bool(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_as_bool(const struct raw_std_value *value); +ATTR_PURE bool raw_std_value_is_int(const struct raw_std_value *value); +ATTR_PURE int64_t raw_std_value_as_int(const struct raw_std_value *value); +ATTR_PURE size_t raw_std_value_get_size(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_value_after(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_list_get_first_element(const struct raw_std_value *list); +ATTR_PURE const struct raw_std_value *raw_std_list_get_nth_element(const struct raw_std_value *list, size_t index); +ATTR_PURE const struct raw_std_value *raw_std_map_get_first_key(const struct raw_std_value *map); +ATTR_PURE const struct raw_std_value *raw_std_map_find(const struct raw_std_value *map, const struct raw_std_value *key); +ATTR_PURE const struct raw_std_value *raw_std_map_find_str(const struct raw_std_value *map, const char *str); + +ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_method_call_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_method_call_response_check(const struct raw_std_value *value, size_t buffer_size); +ATTR_PURE bool raw_std_event_check(const struct raw_std_value *value, size_t buffer_size); + +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_method(const struct raw_std_value *value); +ATTR_PURE bool raw_std_method_call_is_method(const struct raw_std_value *value, const char *method_name); +MALLOCLIKE MUST_CHECK char *raw_std_method_call_get_method_dup(const struct raw_std_value *value); +ATTR_PURE const struct raw_std_value *raw_std_method_call_get_arg(const struct raw_std_value *value); + +#define CONCAT(a, b) CONCAT_INNER(a, b) +#define CONCAT_INNER(a, b) a##b + +#define UNIQUE_NAME(base) CONCAT(base, __COUNTER__) + +#define for_each_entry_in_raw_std_map_indexed(index, key, value, map) \ + for (const struct raw_std_value *key = raw_std_map_get_first_key(map), *value = raw_std_value_after(key), *guard = NULL; \ + guard == NULL; \ + guard = (void *) 1) \ + for (size_t index = 0; index < raw_std_map_get_size(map); index++, \ + key = (index) < raw_std_map_get_size(map) ? raw_std_value_after(value) : NULL, \ + value = (index) < raw_std_map_get_size(map) ? raw_std_value_after(key) : NULL) + +#define for_each_entry_in_raw_std_map(key, value, map) \ + for_each_entry_in_raw_std_map_indexed(UNIQUE_NAME(__raw_std_map_entry_index), key, value, map) + +#define for_each_element_in_raw_std_list_indexed(index, element, list) \ + for (const struct raw_std_value *element = raw_std_list_get_first_element(list), *guard = NULL; guard == NULL; guard = (void *) 1) \ + for (size_t index = 0; index < raw_std_list_get_size(list); \ + index++, element = (index) < raw_std_list_get_size(list) ? raw_std_value_after(element) : NULL) + +#define for_each_element_in_raw_std_list(value, list) \ + for_each_element_in_raw_std_list_indexed(UNIQUE_NAME(__raw_std_list_element_index), value, list) + +#endif // _FLUTTERPI_SRC_PLATFORMCHANNEL_H diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 73ca0c54..d8f8cad4 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -1,403 +1,503 @@ #ifdef __STDC_ALLOC_LIB__ -#define __STDC_WANT_LIB_EXT2__ 1 + #define __STDC_WANT_LIB_EXT2__ 1 #else -#define _POSIX_C_SOURCE 200809L + #define _POSIX_C_SOURCE 200809L #endif -#include +#include "pluginregistry.h" + +#include #include -#include + #include -#include +#include -#include -#include -#include -#include +#include -FILE_DESCR("plugin registry") +#include "flutter-pi.h" +#include "platformchannel.h" +#include "util/collection.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" /** * @brief details of a plugin for flutter-pi. - * + * * All plugins are initialized (by calling their "init" callback) * when @ref plugin_registry_ensure_plugins_initialized is called by flutter-pi. - * + * * In the init callback, you probably want to do stuff like * register callbacks for some method channels your plugin uses * or dynamically allocate memory for your plugin if you need to. */ struct plugin_instance { + struct list_head entry; + const struct flutterpi_plugin_v2 *plugin; - void *userdata; - bool initialized; + void *userdata; + bool initialized; }; struct platch_obj_cb_data { - char *channel; - enum platch_codec codec; - platch_obj_recv_callback callback; - void *userdata; + struct list_head entry; + + char *channel; + enum platch_codec codec; + platch_obj_recv_callback callback; + platform_message_callback_v2_t callback_v2; + void *userdata; }; struct plugin_registry { - struct flutterpi *flutterpi; - struct concurrent_pointer_set plugins; - struct concurrent_pointer_set callbacks; + pthread_mutex_t lock; + struct flutterpi *flutterpi; + struct list_head plugins; + struct list_head callbacks; }; -static struct concurrent_pointer_set static_plugins = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE); +DEFINE_STATIC_LOCK_OPS(plugin_registry, lock) -static struct plugin_instance *get_plugin_by_name_locked( - struct plugin_registry *registry, - const char *plugin_name -) { - struct plugin_instance *instance; +struct static_plugin_list_entry { + struct list_head entry; + const struct flutterpi_plugin_v2 *plugin; +}; - for_each_pointer_in_cpset(®istry->plugins, instance) { - if (strcmp(instance->plugin->name, plugin_name) == 0) { - break; - } - } +static pthread_once_t static_plugins_init_flag = PTHREAD_ONCE_INIT; +static pthread_mutex_t static_plugins_lock; +static struct list_head static_plugins; - return instance; +static struct plugin_instance *get_plugin_by_name_locked(struct plugin_registry *registry, const char *plugin_name) { + list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { + if (streq(instance->plugin->name, plugin_name)) { + return instance; + } + } + + return NULL; } -static struct plugin_instance *get_plugin_by_name( - struct plugin_registry *registry, - const char *plugin_name -) { - struct plugin_instance *instance; +static struct plugin_instance *get_plugin_by_name(struct plugin_registry *registry, const char *plugin_name) { + struct plugin_instance *instance; - cpset_lock(®istry->plugins); + plugin_registry_lock(registry); - instance = get_plugin_by_name_locked(registry, plugin_name); + instance = get_plugin_by_name_locked(registry, plugin_name); - cpset_unlock(®istry->plugins); + plugin_registry_unlock(registry); - return instance; + return instance; } -static struct platch_obj_cb_data *get_cb_data_by_channel_locked( - struct plugin_registry *registry, - const char *channel -) { - struct platch_obj_cb_data *data; - - for_each_pointer_in_cpset(®istry->callbacks, data) { - if (strcmp(data->channel, channel) == 0) { - break; - } - } +static struct platch_obj_cb_data *get_cb_data_by_channel_locked(struct plugin_registry *registry, const char *channel) { + list_for_each_entry(struct platch_obj_cb_data, data, ®istry->callbacks, entry) { + if (streq(data->channel, channel)) { + return data; + } + } - return data; + return NULL; } -static struct platch_obj_cb_data *get_cb_data_by_channel( - struct plugin_registry *registry, - const char *channel -) { - struct platch_obj_cb_data *data; +struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi) { + struct plugin_registry *reg; + ASSERTED int ok; - cpset_lock(®istry->callbacks); - data = get_cb_data_by_channel_locked(registry, channel); - cpset_unlock(®istry->callbacks); + reg = malloc(sizeof *reg); + if (reg == NULL) { + return NULL; + } - return data; -} + ok = pthread_mutex_init(®->lock, get_default_mutex_attrs()); + ASSERT_ZERO(ok); -struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi) { - struct plugin_registry *reg; - int ok; + list_inithead(®->plugins); + list_inithead(®->callbacks); - reg = malloc(sizeof *reg); - if (reg == NULL) { - return NULL; - } + reg->flutterpi = flutterpi; + return reg; - ok = cpset_init(®->plugins, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - goto fail_free_registry; - } + return NULL; +} + +void plugin_registry_destroy(struct plugin_registry *registry) { + plugin_registry_ensure_plugins_deinitialized(registry); + + // remove all plugins + list_for_each_entry_safe(struct plugin_instance, instance, ®istry->plugins, entry) { + assert(instance->initialized == false); + list_del(&instance->entry); + free(instance); + } + + assert(list_is_empty(®istry->plugins)); + assert(list_is_empty(®istry->callbacks)); + free(registry); +} - ok = cpset_init(®->callbacks, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - goto fail_deinit_plugins_cpset; - } +int plugin_registry_on_platform_message(struct plugin_registry *registry, const FlutterPlatformMessage *message) { + struct platch_obj_cb_data *data; + platch_obj_recv_callback callback; + platform_message_callback_v2_t callback_v2; + struct platch_obj object; + enum platch_codec codec; + void *userdata; + int ok; + + plugin_registry_lock(registry); + + data = get_cb_data_by_channel_locked(registry, message->channel); + if (data == NULL || (data->callback == NULL && data->callback_v2 == NULL)) { + ok = platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); + goto fail_unlock; + } + + codec = data->codec; + callback = data->callback; + callback_v2 = data->callback_v2; + userdata = data->userdata; + + plugin_registry_unlock(registry); + + if (callback_v2 != NULL) { + callback_v2(userdata, message); + } else { + ok = platch_decode((uint8_t *) message->message, message->message_size, codec, &object); + if (ok != 0) { + platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); + goto fail_return_ok; + } + + ok = callback( + (char *) message->channel, + &object, + (FlutterPlatformMessageResponseHandle *) message->response_handle + ); //, userdata); + if (ok != 0) { + goto fail_free_object; + } + + platch_free_obj(&object); + } + + return 0; + +fail_free_object: + platch_free_obj(&object); + +fail_unlock: + plugin_registry_unlock(registry); + +fail_return_ok: + return ok; +} - reg->flutterpi = flutterpi; - return reg; +void plugin_registry_add_plugin_locked(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin) { + struct plugin_instance *instance; + instance = malloc(sizeof *instance); + ASSERT_NOT_NULL(instance); - fail_deinit_plugins_cpset: - cpset_deinit(®->plugins); - - fail_free_registry: - free(reg); + instance->plugin = plugin; + instance->initialized = false; + instance->userdata = NULL; - return NULL; + list_addtail(&instance->entry, ®istry->plugins); } -void plugin_registry_destroy(struct plugin_registry *registry) { - struct plugin_instance *instance; - - plugin_registry_ensure_plugins_deinitialized(registry); - for_each_pointer_in_cpset(®istry->plugins, instance) { - cpset_remove_locked(®istry->plugins, instance); - free(instance); - instance = NULL; - } - cpset_deinit(®istry->callbacks); - cpset_deinit(®istry->plugins); - free(registry); +void plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin) { + plugin_registry_lock(registry); + plugin_registry_add_plugin_locked(registry, plugin); + plugin_registry_unlock(registry); } -int plugin_registry_on_platform_message(FlutterPlatformMessage *message) { - struct platch_obj_cb_data *data, data_copy; - struct platch_obj object; - int ok; +static void static_plugin_registry_ensure_initialized(); - cpset_lock(&flutterpi.plugin_registry->callbacks); +int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry) { + ASSERTED int ok; - data = get_cb_data_by_channel_locked(flutterpi.plugin_registry, message->channel); - if (data == NULL || data->callback == NULL) { - cpset_unlock(&flutterpi.plugin_registry->callbacks); - return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle*) message->response_handle); - } + static_plugin_registry_ensure_initialized(); - data_copy = *data; - cpset_unlock(&flutterpi.plugin_registry->callbacks); + ok = pthread_mutex_lock(&static_plugins_lock); + ASSERT_ZERO(ok); - ok = platch_decode((uint8_t*) message->message, message->message_size, data_copy.codec, &object); - if (ok != 0) { - return ok; - } + list_for_each_entry(struct static_plugin_list_entry, plugin, &static_plugins, entry) { + plugin_registry_add_plugin(registry, plugin->plugin); + } - ok = data_copy.callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); //, data->userdata); - if (ok != 0) { - platch_free_obj(&object); - return ok; - } + ok = pthread_mutex_unlock(&static_plugins_lock); + ASSERT_ZERO(ok); - platch_free_obj(&object); + return 0; +} - return 0; +int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry) { + enum plugin_init_result result; + + plugin_registry_lock(registry); + + list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { + if (instance->initialized == false) { + result = instance->plugin->init(registry->flutterpi, &instance->userdata); + if (result == PLUGIN_INIT_RESULT_ERROR) { + LOG_ERROR("Error initializing plugin \"%s\".\n", instance->plugin->name); + goto fail_deinit_all_initialized; + } else if (result == PLUGIN_INIT_RESULT_NOT_APPLICABLE) { + // This is not an error. + LOG_DEBUG("INFO: Plugin \"%s\" is not available in this flutter-pi instance.\n", instance->plugin->name); + continue; + } + + ASSUME(result == PLUGIN_INIT_RESULT_INITIALIZED); + instance->initialized = true; + } + } + + LOG_DEBUG("Initialized plugins: "); + list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { + if (instance->initialized) { + LOG_DEBUG_UNPREFIXED("%s, ", instance->plugin->name); + } + } + LOG_DEBUG_UNPREFIXED("\n"); + + plugin_registry_unlock(registry); + return 0; + +fail_deinit_all_initialized: + list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { + if (instance->initialized) { + instance->plugin->deinit(registry->flutterpi, instance->userdata); + instance->initialized = false; + } + } + plugin_registry_unlock(registry); + return EINVAL; } -int plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin) { - struct plugin_instance *instance; - int ok; +void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry) { + plugin_registry_lock(registry); - instance = malloc(sizeof *instance); - if (instance == NULL) { - return ENOMEM; - } + list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { + if (instance->initialized == true) { + instance->plugin->deinit(registry->flutterpi, instance->userdata); + instance->initialized = false; + } + } - instance->plugin = plugin; - instance->initialized = false; - instance->userdata = NULL; + plugin_registry_unlock(registry); +} + +/// TODO: Move this into a separate flutter messenger API +static int set_receiver_locked( + struct plugin_registry *registry, + const char *channel, + enum platch_codec codec, + platch_obj_recv_callback callback, + platform_message_callback_v2_t callback_v2, + void *userdata +) { + struct platch_obj_cb_data *data_ptr; + char *channel_dup; + + ASSERT_MSG((!!callback) != (!!callback_v2), "Exactly one of callback or callback_v2 must be non-NULL."); + ASSERT_MUTEX_LOCKED(registry->lock); + + data_ptr = get_cb_data_by_channel_locked(registry, channel); + if (data_ptr == NULL) { + channel_dup = strdup(channel); + if (channel_dup == NULL) { + return ENOMEM; + } + + struct platch_obj_cb_data *data; + + data = malloc(sizeof *data); + data->channel = channel_dup; + data->codec = codec; + data->callback = callback; + data->callback_v2 = callback_v2; + data->userdata = userdata; + + list_addtail(&data->entry, ®istry->callbacks); + } else { + data_ptr->codec = codec; + data_ptr->callback = callback; + data_ptr->callback_v2 = callback_v2; + data_ptr->userdata = userdata; + } + + return 0; +} + +static int set_receiver( + struct plugin_registry *registry, + const char *channel, + enum platch_codec codec, + platch_obj_recv_callback callback, + platform_message_callback_v2_t callback_v2, + void *userdata +) { + int ok; - ok = cpset_put(®istry->plugins, instance); - if (ok != 0) { - free(instance); - return ENOMEM; - } + plugin_registry_lock(registry); + ok = set_receiver_locked(registry, channel, codec, callback, callback_v2, userdata); + plugin_registry_unlock(registry); - return 0; + return ok; } -int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry) { - const struct flutterpi_plugin_v2 *plugin; - int ok; - - cpset_lock(&static_plugins); - for_each_pointer_in_cpset(&static_plugins, plugin) { - ok = plugin_registry_add_plugin(registry, plugin); - if (ok != 0) { - cpset_unlock(&static_plugins); - - /// TODO: Remove all previously added plugins here - return ok; - } - } - cpset_unlock(&static_plugins); - - return 0; +int plugin_registry_set_receiver_v2_locked( + struct plugin_registry *registry, + const char *channel, + platform_message_callback_v2_t callback, + void *userdata +) { + return set_receiver_locked(registry, channel, kBinaryCodec, NULL, callback, userdata); } -int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry) { - struct plugin_instance *instance; - enum plugin_init_result result; - struct pointer_set initialized_plugins; - int n_pointers; - - cpset_lock(®istry->plugins); - - n_pointers = cpset_get_count_pointers_locked(®istry->plugins); - initialized_plugins = PSET_INITIALIZER_STATIC(alloca(sizeof(void*) * n_pointers), n_pointers); - - LOG_DEBUG("Registered plugins: "); - for_each_pointer_in_cpset(®istry->plugins, instance) { - LOG_DEBUG_UNPREFIXED("%s, ", instance->plugin->name); - - if (instance->initialized == false) { - result = instance->plugin->init(registry->flutterpi, &instance->userdata); - if (result == kError_PluginInitResult) { - LOG_ERROR("Error initializing plugin \"%s\".\n", instance->plugin->name); - goto fail_deinit_all_initialized; - } else if (result == kNotApplicable_PluginInitResult) { - // This is not an error. - LOG_DEBUG("INFO: Plugin \"%s\" is not available in this flutter-pi instance.\n", instance->plugin->name); - } - - instance->initialized = true; - pset_put(&initialized_plugins, instance); - } - } - LOG_DEBUG_UNPREFIXED("\n"); - - cpset_unlock(®istry->plugins); - return 0; - - - fail_deinit_all_initialized: - for_each_pointer_in_pset(&initialized_plugins, instance) { - instance->plugin->deinit(registry->flutterpi, instance->userdata); - } - return EINVAL; +int plugin_registry_set_receiver_v2( + struct plugin_registry *registry, + const char *channel, + platform_message_callback_v2_t callback, + void *userdata +) { + return set_receiver(registry, channel, kBinaryCodec, NULL, callback, userdata); } -void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry) { - struct plugin_instance *instance; +/// TODO: Move this into a separate flutter messenger API +int plugin_registry_set_receiver_locked(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback) { + struct plugin_registry *registry; + + registry = flutterpi_get_plugin_registry(flutterpi); + ASSUME(registry != NULL); - cpset_lock(®istry->plugins); + return set_receiver_locked(registry, channel, codec, callback, NULL, NULL); +} - for_each_pointer_in_cpset(®istry->plugins, instance) { - if (instance->initialized == true) { - instance->plugin->deinit(registry->flutterpi, instance->userdata); - instance->initialized = false; - } - } +int plugin_registry_set_receiver(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback) { + struct plugin_registry *registry; - cpset_unlock(®istry->plugins); + registry = flutterpi_get_plugin_registry(flutterpi); + ASSUME(registry != NULL); + + return set_receiver(registry, channel, codec, callback, NULL, NULL); } -/// TODO: Move this into a separate flutter messenger API -int plugin_registry_set_receiver( - const char *channel, - enum platch_codec codec, - platch_obj_recv_callback callback -) { - struct platch_obj_cb_data *data; - char *channel_dup; - int ok; - - cpset_lock(&flutterpi.plugin_registry->callbacks); - - channel_dup = strdup(channel); - if (channel_dup == NULL) { - ok = ENOMEM; - goto fail_unlock_cbs; - } - - data = get_cb_data_by_channel_locked(flutterpi.plugin_registry, channel); - if (data == NULL) { - data = calloc(1, sizeof *data); - if (data == NULL) { - ok = ENOMEM; - goto fail_free_channel_dup; - } - - ok = cpset_put_locked(&flutterpi.plugin_registry->callbacks, data); - if (ok != 0) { - if (ok == ENOSPC) { - LOG_ERROR("Couldn't register platform channel listener. Callback list is filled\n"); - } - goto fail_free_data; - } - } - - data->channel = channel_dup; - data->codec = codec; - data->callback = callback; - data->userdata = NULL; - cpset_unlock(&flutterpi.plugin_registry->callbacks); - - return 0; - - - fail_free_data: - free(data); - - fail_free_channel_dup: - free(channel_dup); - - fail_unlock_cbs: - cpset_unlock(&flutterpi.plugin_registry->callbacks); - - return ok; +int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, const char *channel) { + struct platch_obj_cb_data *data; + + data = get_cb_data_by_channel_locked(registry, channel); + if (data == NULL) { + return EINVAL; + } + + list_del(&data->entry); + free(data->channel); + free(data); + + return 0; } -int plugin_registry_remove_receiver(const char *channel) { - struct platch_obj_cb_data *data; +int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel) { + int ok; - cpset_lock(&flutterpi.plugin_registry->callbacks); + plugin_registry_lock(registry); + ok = plugin_registry_remove_receiver_v2_locked(registry, channel); + plugin_registry_unlock(registry); - data = get_cb_data_by_channel_locked(flutterpi.plugin_registry, channel); - if (data == NULL) { - cpset_unlock(&flutterpi.plugin_registry->callbacks); - return EINVAL; - } + return ok; +} + +int plugin_registry_remove_receiver_locked(const char *channel) { + struct plugin_registry *registry; - cpset_remove_locked(&flutterpi.plugin_registry->callbacks, data); + registry = flutterpi_get_plugin_registry(flutterpi); + ASSUME(registry != NULL); - free(data->channel); - free(data); + return plugin_registry_remove_receiver_v2_locked(registry, channel); +} - cpset_unlock(&flutterpi.plugin_registry->callbacks); +int plugin_registry_remove_receiver(const char *channel) { + struct plugin_registry *registry; - return 0; + registry = flutterpi_get_plugin_registry(flutterpi); + ASSUME(registry != NULL); + + return plugin_registry_remove_receiver_v2(registry, channel); } -bool plugin_registry_is_plugin_present( - struct plugin_registry *registry, - const char *plugin_name -) { - return get_plugin_by_name(registry, plugin_name) != NULL; +bool plugin_registry_is_plugin_present_locked(struct plugin_registry *registry, const char *plugin_name) { + return get_plugin_by_name_locked(registry, plugin_name) != NULL; } -void *plugin_registry_get_plugin_userdata( - struct plugin_registry *registry, - const char *plugin_name -) { - struct plugin_instance *instance; +bool plugin_registry_is_plugin_present(struct plugin_registry *registry, const char *plugin_name) { + return get_plugin_by_name(registry, plugin_name) != NULL; +} + +void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name) { + struct plugin_instance *instance; + + instance = get_plugin_by_name(registry, plugin_name); + + return instance != NULL ? instance->userdata : NULL; +} + +void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registry, const char *plugin_name) { + struct plugin_instance *instance; - instance = get_plugin_by_name(registry, plugin_name); + instance = get_plugin_by_name_locked(registry, plugin_name); - return instance != NULL ? instance->userdata : NULL; + return instance != NULL ? instance->userdata : NULL; } +static void static_plugin_registry_initialize() { + ASSERTED int ok; -int static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin) { - return cpset_put(&static_plugins, (void*) plugin); + list_inithead(&static_plugins); + + ok = pthread_mutex_init(&static_plugins_lock, get_default_mutex_attrs()); + ASSERT_ZERO(ok); +} + +static void static_plugin_registry_ensure_initialized() { + pthread_once(&static_plugins_init_flag, static_plugin_registry_initialize); } -int static_plugin_registry_remove_plugin(const char *plugin_name) { - const struct flutterpi_plugin_v2 *plugin; +void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin) { + struct static_plugin_list_entry *entry; + ASSERTED int ok; + + static_plugin_registry_ensure_initialized(); + + ok = pthread_mutex_lock(&static_plugins_lock); + ASSERT_ZERO(ok); + + entry = malloc(sizeof *entry); + entry->plugin = plugin; + + list_addtail(&entry->entry, &static_plugins); + + ok = pthread_mutex_unlock(&static_plugins_lock); + ASSERT_ZERO(ok); +} - cpset_lock(&static_plugins); +void static_plugin_registry_remove_plugin(const char *plugin_name) { + ASSERTED int ok; - for_each_pointer_in_cpset(&static_plugins, plugin) { - if (strcmp(plugin->name, plugin_name) == 0) { - break; - } - } + static_plugin_registry_ensure_initialized(); - if (plugin != NULL) { - cpset_remove_locked(&static_plugins, plugin); - } + ok = pthread_mutex_lock(&static_plugins_lock); + ASSERT_ZERO(ok); - cpset_unlock(&static_plugins); + list_for_each_entry(struct static_plugin_list_entry, plugin, &static_plugins, entry) { + if (streq(plugin->plugin->name, plugin_name)) { + list_del(&plugin->entry); + free(plugin); + break; + } + } - return plugin == NULL ? EINVAL : 0; + ok = pthread_mutex_unlock(&static_plugins_lock); + ASSERT_ZERO(ok); } diff --git a/src/pluginregistry.h b/src/pluginregistry.h new file mode 100644 index 00000000..4efe6bf8 --- /dev/null +++ b/src/pluginregistry.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +/* + * Plugin Registry + * + * Initializes & deinitializes plugins, manages registration of plugins. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_PLUGINREGISTRY_H +#define _FLUTTERPI_SRC_PLUGINREGISTRY_H + +#include + +#include +#include + +#include "platformchannel.h" + +struct flutterpi; +struct plugin_registry; + +typedef enum plugin_init_result (*plugin_init_t)(struct flutterpi *flutterpi, void **userdata_out); + +typedef void (*plugin_deinit_t)(struct flutterpi *flutterpi, void *userdata); + +struct flutterpi_plugin_v2 { + const char *name; + plugin_init_t init; + plugin_deinit_t deinit; +}; + +/// The return value of a plugin initializer function. +enum plugin_init_result { + PLUGIN_INIT_RESULT_INITIALIZED, ///< The plugin was successfully initialized. + PLUGIN_INIT_RESULT_NOT_APPLICABLE, ///< The plugin couldn't be initialized, because it's not compatible with the flutter-pi instance. + /// For example, the plugin requires OpenGL but flutter-pi is using software rendering. + /// This is not an error, and flutter-pi will continue initializing the other plugins. + PLUGIN_INIT_RESULT_ERROR ///< The plugin couldn't be initialized because an unexpected error ocurred. + /// Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. +}; + +struct _FlutterPlatformMessageResponseHandle; +typedef struct _FlutterPlatformMessageResponseHandle FlutterPlatformMessageResponseHandle; + +/// A Callback that gets called when a platform message +/// arrives on a channel you registered it with. +/// channel is the method channel that received a platform message, +/// object is the object that is the result of automatically decoding +/// the platform message using the codec given to plugin_registry_set_receiver. +/// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec +/// passed to plugin_registry_set_receiver. +typedef int (*platch_obj_recv_callback)(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle); + +typedef void (*platform_message_callback_v2_t)(void *userdata, const FlutterPlatformMessage *message); + +/** + * @brief Create a new plugin registry instance and add the hardcoded plugins, but don't initialize them yet. + */ +struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi); + +void plugin_registry_destroy(struct plugin_registry *registry); + +void plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin); + +int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry); + +/** + * @brief Initialize all not-yet initialized plugins. + */ +int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry); + +/** + * @brief Deinitialize all initialized plugins. + */ +void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry); + +/** + * @brief Called by flutter-pi when a platform message arrives. + */ +int plugin_registry_on_platform_message(struct plugin_registry *registry, const FlutterPlatformMessage *message); + +/** + * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. + * + * The platform message will be automatically decoded using the codec `codec`. + */ +int plugin_registry_set_receiver_v2_locked( + struct plugin_registry *registry, + const char *channel, + platform_message_callback_v2_t callback, + void *userdata +); + +/** + * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. + * + * The platform message will be automatically decoded using the codec `codec`. + */ +int plugin_registry_set_receiver_v2( + struct plugin_registry *registry, + const char *channel, + platform_message_callback_v2_t callback, + void *userdata +); + +/** + * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. + * + * The platform message will be automatically decoded using the codec `codec`. + */ +int plugin_registry_set_receiver_locked(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); + +/** + * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. + * + * The platform message will be automatically decoded using the codec `codec`. + */ +int plugin_registry_set_receiver(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); + +/** + * @brief Removes the callback for platform channel `channel`. + * + */ +int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, const char *channel); + +/** + * @brief Removes the callback for platform channel `channel`. + * + */ +int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel); + +/** + * @brief Removes the callback for platform channel `channel`. + * + */ +int plugin_registry_remove_receiver_locked(const char *channel); + +/** + * @brief Removes the callback for platform channel `channel`. + * + */ +int plugin_registry_remove_receiver(const char *channel); + +void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name); + +void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registry, const char *plugin_name); + +/** + * @brief Returns true @ref registry has a plugin with name @ref plugin_name. + */ +bool plugin_registry_is_plugin_present(struct plugin_registry *registry, const char *plugin_name); + +/** + * @brief Returns true @ref registry has a plugin with name @ref plugin_name. + */ +bool plugin_registry_is_plugin_present_locked(struct plugin_registry *registry, const char *plugin_name); + +int plugin_registry_deinit(void); + +void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin); + +void static_plugin_registry_remove_plugin(const char *plugin_name); + +#define FLUTTERPI_PLUGIN(_name, _identifier_name, _init, _deinit) \ + __attribute__((constructor)) static void __reg_plugin_##_identifier_name() { \ + static struct flutterpi_plugin_v2 plugin = { \ + .name = (_name), \ + .init = (_init), \ + .deinit = (_deinit), \ + }; \ + static_plugin_registry_add_plugin(&plugin); \ + } \ + \ + __attribute__((destructor)) static void __unreg_plugin_##_identifier_name() { static_plugin_registry_remove_plugin(_name); } + +#endif // _FLUTTERPI_SRC_PLUGINREGISTRY_H diff --git a/include/plugins/audioplayers.h b/src/plugins/audioplayers.h similarity index 100% rename from include/plugins/audioplayers.h rename to src/plugins/audioplayers.h diff --git a/src/plugins/audioplayers/player.c b/src/plugins/audioplayers/player.c index c2c44d8f..e309ff8b 100644 --- a/src/plugins/audioplayers/player.c +++ b/src/plugins/audioplayers/player.c @@ -1,15 +1,15 @@ #include -#include "gst/gst.h" -#include "gst/gstelementfactory.h" -#include "gst/gstmessage.h" -#include "gst/gstsegment.h" -#include "platformchannel.h" - -#include -#include +#include +#include +#include +#include -FILE_DESCR("audioplayers player") +#include "flutter-pi.h" +#include "platformchannel.h" +#include "plugins/audioplayers.h" +#include "util/asserts.h" +#include "util/logging.h" struct audio_player { GstElement *playbin; @@ -211,7 +211,7 @@ void audio_player_set_playback(struct audio_player *self, int64_t seekTo, double } void audio_player_on_media_error(struct audio_player *self, GError *error, gchar *debug) { (void) debug; - char error_message[256] = {0}; + char error_message[256] = { 0 }; snprintf(error_message, sizeof(error_message), "Error: %d; message=%s", error->code, error->message); if (self->channel) { // clang-format off @@ -233,7 +233,7 @@ void audio_player_on_media_error(struct audio_player *self, GError *error, gchar void audio_player_on_media_state_change(struct audio_player *self, GstObject *src, GstState *old_state, GstState *new_state) { (void) old_state; - if (strcmp(GST_OBJECT_NAME(src), "playbin") == 0) { + if (streq(GST_OBJECT_NAME(src), "playbin")) { if (*new_state >= GST_STATE_READY) { if (!self->is_initialized) { self->is_initialized = true; @@ -430,8 +430,8 @@ void audio_player_set_position(struct audio_player *self, int64_t position) { } void audio_player_set_source_url(struct audio_player *self, char *url) { - DEBUG_ASSERT_NOT_NULL(url); - if (self->url == NULL || strcmp(self->url, url)) { + ASSERT_NOT_NULL(url); + if (self->url == NULL || !streq(self->url, url)) { if (self->url != NULL) { free(self->url); self->url = NULL; @@ -449,5 +449,5 @@ void audio_player_set_source_url(struct audio_player *self, char *url) { } bool audio_player_is_id(struct audio_player *self, char *player_id) { - return strcmp(self->player_id, player_id) == 0; + return streq(self->player_id, player_id); } diff --git a/src/plugins/audioplayers/plugin.c b/src/plugins/audioplayers/plugin.c index e62a264f..0ea8a3af 100644 --- a/src/plugins/audioplayers/plugin.c +++ b/src/plugins/audioplayers/plugin.c @@ -1,23 +1,28 @@ #define _GNU_SOURCE +#include "flutter-pi.h" +#include "platformchannel.h" +#include "pluginregistry.h" #include "plugins/audioplayers.h" - -#include -#include -#include -#include - -FILE_DESCR("audioplayers plugin") +#include "util/collection.h" +#include "util/list.h" +#include "util/logging.h" #define AUDIOPLAYERS_LOCAL_CHANNEL "xyz.luan/audioplayers" #define AUDIOPLAYERS_GLOBAL_CHANNEL "xyz.luan/audioplayers.global" static struct audio_player *audioplayers_linux_plugin_get_player(char *player_id, char *mode); +struct audio_player_entry { + struct list_head entry; + struct audio_player *player; +}; + static struct plugin { struct flutterpi *flutterpi; bool initialized; - struct concurrent_pointer_set players; + + struct list_head players; } plugin; static int on_local_method_call(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { @@ -26,6 +31,8 @@ static int on_local_method_call(char *channel, struct platch_obj *object, Flutte const char *method; char *player_id, *mode; int result = 1; + int ok; + (void) responsehandle; (void) channel; method = object->method; @@ -55,17 +62,17 @@ static int on_local_method_call(char *channel, struct platch_obj *object, Flutte return platch_respond_native_error_std(responsehandle, ENOMEM); } - if (strcmp(method, "pause") == 0) { + if (streq(method, "pause")) { audio_player_pause(player); - } else if (strcmp(method, "resume") == 0) { + } else if (streq(method, "resume")) { audio_player_resume(player); - } else if (strcmp(method, "stop") == 0) { + } else if (streq(method, "stop")) { audio_player_pause(player); audio_player_set_position(player, 0); - } else if (strcmp(method, "release") == 0) { + } else if (streq(method, "release")) { audio_player_pause(player); audio_player_set_position(player, 0); - } else if (strcmp(method, "seek") == 0) { + } else if (streq(method, "seek")) { tmp = stdmap_get_str(args, "position"); if (tmp == NULL || !STDVALUE_IS_INT(*tmp)) { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg['position']` to be an int."); @@ -73,7 +80,7 @@ static int on_local_method_call(char *channel, struct platch_obj *object, Flutte int64_t position = STDVALUE_AS_INT(*tmp); audio_player_set_position(player, position); - } else if (strcmp(method, "setSourceUrl") == 0) { + } else if (streq(method, "setSourceUrl")) { tmp = stdmap_get_str(args, "url"); if (tmp == NULL || !STDVALUE_IS_STRING(*tmp)) { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg['url']` to be a string."); @@ -88,33 +95,33 @@ static int on_local_method_call(char *channel, struct platch_obj *object, Flutte bool is_local = STDVALUE_AS_BOOL(*tmp); if (is_local) { char *local_url = NULL; - asprintf(&local_url, "file://%s", url); - if (local_url == NULL) { + ok = asprintf(&local_url, "file://%s", url); + if (ok < 0) { return platch_respond_native_error_std(responsehandle, ENOMEM); } url = local_url; } audio_player_set_source_url(player, url); - } else if (strcmp(method, "getDuration") == 0) { + } else if (streq(method, "getDuration")) { result = audio_player_get_duration(player); - } else if (strcmp(method, "setVolume") == 0) { + } else if (streq(method, "setVolume")) { tmp = stdmap_get_str(args, "volume"); if (tmp != NULL && STDVALUE_IS_FLOAT(*tmp)) { audio_player_set_volume(player, STDVALUE_AS_FLOAT(*tmp)); } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg['volume']` to be a float."); } - } else if (strcmp(method, "getCurrentPosition") == 0) { + } else if (streq(method, "getCurrentPosition")) { result = audio_player_get_position(player); - } else if (strcmp(method, "setPlaybackRate") == 0) { + } else if (streq(method, "setPlaybackRate")) { tmp = stdmap_get_str(args, "playback_rate"); if (tmp != NULL && STDVALUE_IS_FLOAT(*tmp)) { audio_player_set_playback_rate(player, STDVALUE_AS_FLOAT(*tmp)); } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg['playback_rate']` to be a float."); } - } else if (strcmp(method, "setReleaseMode") == 0) { + } else if (streq(method, "setReleaseMode")) { tmp = stdmap_get_str(args, "release_mode"); if (tmp != NULL && STDVALUE_IS_STRING(*tmp)) { char *release_mode = STDVALUE_AS_STRING(*tmp); @@ -123,7 +130,7 @@ static int on_local_method_call(char *channel, struct platch_obj *object, Flutte } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg['release_mode']` to be a string."); } - } else if (strcmp(method, "setPlayerMode") == 0) { + } else if (streq(method, "setPlayerMode")) { // TODO check support for low latency mode: // https://gstreamer.freedesktop.org/documentation/additional/design/latency.html?gi-language=c } else { @@ -142,61 +149,71 @@ static int on_global_method_call(char *channel, struct platch_obj *object, Flutt } enum plugin_init_result audioplayers_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { - (void) userdata_out; int ok; + + (void) userdata_out; + plugin.flutterpi = flutterpi; plugin.initialized = false; + list_inithead(&plugin.players); - ok = cpset_init(&plugin.players, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) - return kError_PluginInitResult; - - ok = plugin_registry_set_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL, kStandardMethodCall, on_global_method_call); + ok = plugin_registry_set_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL, kStandardMethodCall, on_global_method_call); if (ok != 0) { - goto fail_deinit_cpset; + return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver(AUDIOPLAYERS_LOCAL_CHANNEL, kStandardMethodCall, on_local_method_call); + ok = plugin_registry_set_receiver_locked(AUDIOPLAYERS_LOCAL_CHANNEL, kStandardMethodCall, on_local_method_call); if (ok != 0) { goto fail_remove_global_receiver; } - return kInitialized_PluginInitResult; + return PLUGIN_INIT_RESULT_INITIALIZED; fail_remove_global_receiver: - plugin_registry_remove_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL); + plugin_registry_remove_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL); -fail_deinit_cpset: - cpset_deinit(&plugin.players); - - return kError_PluginInitResult; + return PLUGIN_INIT_RESULT_ERROR; } void audioplayers_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { (void) flutterpi; (void) userdata; - plugin_registry_remove_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL); - plugin_registry_remove_receiver(AUDIOPLAYERS_LOCAL_CHANNEL); - struct audio_player *ptr; - for_each_pointer_in_cpset(&plugin.players, ptr) { - audio_player_destroy(ptr); - } + plugin_registry_remove_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL); + plugin_registry_remove_receiver_locked(AUDIOPLAYERS_LOCAL_CHANNEL); - cpset_deinit(&plugin.players); + list_for_each_entry_safe(struct audio_player_entry, entry, &plugin.players, entry) { + audio_player_destroy(entry->player); + list_del(&entry->entry); + free(entry); + } } static struct audio_player *audioplayers_linux_plugin_get_player(char *player_id, char *mode) { - (void) mode; + struct audio_player_entry *entry; struct audio_player *player; - for_each_pointer_in_cpset(&plugin.players, player) { - if (audio_player_is_id(player, player_id)) { - return player; + + (void) mode; + + list_for_each_entry_safe(struct audio_player_entry, entry, &plugin.players, entry) { + if (audio_player_is_id(entry->player, player_id)) { + return entry->player; } } + entry = malloc(sizeof *entry); + ASSUME(entry != NULL); + player = audio_player_new(player_id, AUDIOPLAYERS_LOCAL_CHANNEL); - cpset_put_locked(&plugin.players, player); + if (player == NULL) { + free(entry); + return NULL; + } + + entry->entry = (struct list_head){ NULL, NULL }; + entry->player = player; + + list_add(&entry->entry, &plugin.players); return player; } diff --git a/include/plugins/gstreamer_video_player.h b/src/plugins/gstreamer_video_player.h similarity index 70% rename from include/plugins/gstreamer_video_player.h rename to src/plugins/gstreamer_video_player.h index ee1ac375..02e6ed25 100644 --- a/include/plugins/gstreamer_video_player.h +++ b/src/plugins/gstreamer_video_player.h @@ -1,26 +1,22 @@ #ifndef _FLUTTERPI_INCLUDE_PLUGINS_OMXPLAYER_VIDEO_PLUGIN_H #define _FLUTTERPI_INCLUDE_PLUGINS_OMXPLAYER_VIDEO_PLUGIN_H -#include -#include -#include -#include -#include - -enum format_hint { - kNoFormatHint, - kMpegDash_FormatHint, - kHLS_FormatHint, - kSS_FormatHint, - kOther_FormatHint -}; +#include "util/collection.h" +#include "util/lock_ops.h" +#include "util/refcounting.h" -enum buffering_mode { - kStream, - kDownload, - kTimeshift, - kLive -}; +#include "config.h" + +#if !defined(HAVE_EGL_GLES2) + #error "gstreamer video player requires EGL and OpenGL ES2 support." +#else + #include "egl.h" + #include "gles.h" +#endif + +enum format_hint { FORMAT_HINT_NONE, FORMAT_HINT_MPEG_DASH, FORMAT_HINT_HLS, FORMAT_HINT_SS, FORMAT_HINT_OTHER }; + +enum buffering_mode { BUFFERING_MODE_STREAM, BUFFERING_MODE_DOWNLOAD, BUFFERING_MODE_TIMESHIFT, BUFFERING_MODE_LIVE }; struct buffering_range { int64_t start_ms; @@ -38,21 +34,21 @@ struct buffering_state { // The average input / consumption speed in bytes per second. int avg_in, avg_out; - // Time left till buffering finishes, in ms. + // Time left till buffering finishes, in ms. // 0 means not buffering right now. int64_t time_left_ms; // The ranges of already buffered video. - // For the kDownload and kTimeshift buffering modes, this specifies the ranges + // For the BUFFERING_MODE_DOWNLOAD and BUFFERING_MODE_TIMESHIFT buffering modes, this specifies the ranges // where efficient seeking is possible. - // For the kStream and kLive buffering modes, this describes the oldest and + // For the BUFFERING_MODE_STREAM and BUFFERING_MODE_LIVE buffering modes, this describes the oldest and // newest item in the buffer. int n_ranges; // Flexible array member. - // For example, if n_ranges is 2, just allocate using + // For example, if n_ranges is 2, just allocate using // `state = malloc(sizeof(struct buffering_state) + 2*sizeof(struct buffering_range))` - // and we can use state->ranges[0] and so on. + // and we can use state->ranges[0] and so on. // This is cool because we don't need to allocate two blocks of memory and we can just call // `free` once to free the whole thing. // More precisely, we don't need to define a new function we can give to value_notifier_init @@ -60,7 +56,7 @@ struct buffering_state { struct buffering_range ranges[]; }; -#define BUFFERING_STATE_SIZE(n_ranges) (sizeof(struct buffering_state) + (n_ranges)*sizeof(struct buffering_range)) +#define BUFFERING_STATE_SIZE(n_ranges) (sizeof(struct buffering_state) + (n_ranges) * sizeof(struct buffering_range)) struct video_info; struct gstplayer; @@ -70,32 +66,23 @@ struct flutterpi; /// @arg asset_path The path of the asset inside the asset bundle. /// @arg package_name The name of the package containing the asset /// @arg userdata The userdata associated with this player -struct gstplayer *gstplayer_new_from_asset( - struct flutterpi *flutterpi, - const char *asset_path, - const char *package_name, - void *userdata -); +struct gstplayer *gstplayer_new_from_asset(struct flutterpi *flutterpi, const char *asset_path, const char *package_name, void *userdata); /// Create a gstreamer video player that loads the video from a network URI. /// @arg uri The URI to the video. (for example, http://, https://, rtmp://, rtsp://) -/// @arg format_hint A hint to the format of the video. kNoFormatHint means there's no hint. +/// @arg format_hint A hint to the format of the video. FORMAT_HINT_NONE means there's no hint. /// @arg userdata The userdata associated with this player. -struct gstplayer *gstplayer_new_from_network( - struct flutterpi *flutterpi, - const char *uri, - enum format_hint format_hint, - void *userdata -); +struct gstplayer *gstplayer_new_from_network(struct flutterpi *flutterpi, const char *uri, enum format_hint format_hint, void *userdata); /// Create a gstreamer video player that loads the video from a file URI. /// @arg uri The file:// URI to the video. /// @arg userdata The userdata associated with this player. -struct gstplayer *gstplayer_new_from_file( - struct flutterpi *flutterpi, - const char *uri, - void *userdata -); +struct gstplayer *gstplayer_new_from_file(struct flutterpi *flutterpi, const char *uri, void *userdata); + +/// Create a gstreamer video player with a custom gstreamer pipeline. +/// @arg pipeline The description of the custom pipeline that should be used. Should contain an appsink called "sink". +/// @arg userdata The userdata associated with this player. +struct gstplayer *gstplayer_new_from_pipeline(struct flutterpi *flutterpi, const char *pipeline, void *userdata); /// Destroy this gstreamer player instance and the resources /// associated with it. (texture, gstreamer pipeline, etc) @@ -177,8 +164,8 @@ int gstplayer_step_forward(struct gstplayer *player); int gstplayer_step_backward(struct gstplayer *player); -/// @brief Get the value notifier for the video info. -/// +/// @brief Get the value notifier for the video info. +/// /// Gets notified with a value of type `struct video_info*` when the video info changes. /// The listeners will be called on an internal gstreamer thread. /// So you need to make sure you do the proper rethreading in the listener callback. @@ -186,7 +173,7 @@ struct notifier *gstplayer_get_video_info_notifier(struct gstplayer *player); /// @brief Get the value notifier for the buffering state. /// -/// Gets notified with a value of type `struct buffering_state*` when the buffering state changes. +/// Gets notified with a value of type `struct buffering_state*` when the buffering state changes. /// The listeners will be called on the main flutterpi platform thread. struct notifier *gstplayer_get_buffering_state_notifier(struct gstplayer *player); @@ -195,30 +182,30 @@ struct notifier *gstplayer_get_buffering_state_notifier(struct gstplayer *player /// Gets notified when an error happens. (Not yet implemented) struct notifier *gstplayer_get_error_notifier(struct gstplayer *player); - - struct video_frame; +struct gl_renderer; + +struct egl_modified_format { + uint32_t format; + uint64_t modifier; + bool external_only; +}; -struct frame_interface { - struct gbm_device *gbm_device; - EGLDisplay display; +struct frame_interface; - pthread_mutex_t context_lock; - EGLContext context; - PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; - PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; +struct frame_interface *frame_interface_new(struct gl_renderer *renderer); - bool supports_extended_imports; - PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; +ATTR_PURE int frame_interface_get_n_formats(struct frame_interface *interface); - refcount_t n_refs; -}; +ATTR_PURE const struct egl_modified_format *frame_interface_get_format(struct frame_interface *interface, int index); -struct frame_interface *frame_interface_new(); +#define for_each_format_in_frame_interface(index, format, interface) \ + for (const struct egl_modified_format *format = frame_interface_get_format((interface), 0), *guard = NULL; guard == NULL; \ + guard = (void *) 1) \ + for (size_t index = 0; index < frame_interface_get_n_formats(interface); index++, \ + format = (index) < frame_interface_get_n_formats(interface) ? frame_interface_get_format((interface), (index)) : NULL) -DEFINE_INLINE_LOCK_OPS(frame_interface, context_lock) +DECLARE_LOCK_OPS(frame_interface) DECLARE_REF_OPS(frame_interface) @@ -241,11 +228,9 @@ struct frame_info { struct _GstSample; -struct video_frame *frame_new( - struct frame_interface *interface, - const struct frame_info *meta, - struct _GstSample *sample -); +ATTR_CONST GstVideoFormat gst_video_format_from_drm_format(uint32_t drm_format); + +struct video_frame *frame_new(struct frame_interface *interface, GstSample *sample, const GstVideoInfo *info); void frame_destroy(struct video_frame *frame); @@ -253,4 +238,4 @@ struct gl_texture_frame; const struct gl_texture_frame *frame_get_gl_frame(struct video_frame *frame); -#endif \ No newline at end of file +#endif diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 5c196b6f..9d9291a0 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -1,22 +1,32 @@ +#include +#include #include #include -#include -#include + #include #include #include -#include #include +#include -#include -#include -#include +#include "flutter-pi.h" +#include "texture_registry.h" -FILE_DESCR("gstreamer video_player") +// This will error if we don't have EGL / OpenGL ES support. +#include "gl_renderer.h" +#include "plugins/gstreamer_video_player.h" +#include "util/logging.h" +#include "util/refcounting.h" #define MAX_N_PLANES 4 +#define GSTREAMER_VER(major, minor, patch) ((((major) &0xFF) << 16) | (((minor) &0xFF) << 8) | ((patch) &0xFF)) +#define THIS_GSTREAMER_VER GSTREAMER_VER(LIBGSTREAMER_VERSION_MAJOR, LIBGSTREAMER_VERSION_MINOR, LIBGSTREAMER_VERSION_PATCH) + +#define DRM_FOURCC_FORMAT "c%c%c%c" +#define DRM_FOURCC_ARGS(format) (format) & 0xFF, ((format) >> 8) & 0xFF, ((format) >> 16) & 0xFF, ((format) >> 24) & 0xFF + struct video_frame { GstSample *sample; @@ -27,76 +37,278 @@ struct video_frame { int n_dmabuf_fds; int dmabuf_fds[MAX_N_PLANES]; - EGLImage image; + EGLImageKHR image; size_t width, height; struct gl_texture_frame gl_frame; }; -struct frame_interface *frame_interface_new(struct flutterpi *flutterpi) { +struct frame_interface { + struct gbm_device *gbm_device; + EGLDisplay display; + + pthread_mutex_t context_lock; + EGLContext context; + + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + + bool supports_external_target; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + + bool supports_extended_imports; +#ifdef EGL_EXT_image_dma_buf_import_modifiers + PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; +#endif + + int n_formats; + struct egl_modified_format *formats; + + refcount_t n_refs; +}; + +#ifdef EGL_EXT_image_dma_buf_import_modifiers +static bool query_formats( + EGLDisplay display, + PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats, + PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers, + int *n_formats_out, + struct egl_modified_format **formats_out +) { + // EGLuint64KHR is defined by EGL_KHR_stream + #ifndef EGL_KHR_stream + #error "EGL header definitions for extension EGL_KHR_stream are required." + #endif + + struct egl_modified_format *modified_formats; + EGLBoolean *external_only; + EGLuint64KHR *modifiers; + EGLint *formats; + EGLint egl_ok, n_modifiers; + int n_formats, n_modified_formats, max_n_modifiers; + + egl_ok = egl_query_dmabuf_formats(display, 0, NULL, &n_formats); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query number of dmabuf formats supported by EGL.\n"); + goto fail; + } + + formats = malloc(n_formats * sizeof *formats); + if (formats == NULL) { + goto fail; + } + + egl_ok = egl_query_dmabuf_formats(display, n_formats, formats, &n_formats); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + // first, count how many modifiers we have for each format. + n_modified_formats = 0; + max_n_modifiers = 0; + for (int i = 0; i < n_formats; i++) { + egl_ok = egl_query_dmabuf_modifiers(display, formats[i], 0, NULL, NULL, &n_modifiers); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + n_modified_formats += n_modifiers; + + if (n_modifiers > max_n_modifiers) { + max_n_modifiers = n_modifiers; + } + } + + modified_formats = malloc(n_modified_formats * sizeof *modified_formats); + if (modified_formats == NULL) { + goto fail_free_formats; + } + + modifiers = malloc(max_n_modifiers * sizeof *modifiers); + if (modifiers == NULL) { + goto fail_free_modified_formats; + } + + external_only = malloc(max_n_modifiers * sizeof *external_only); + if (external_only == NULL) { + goto fail_free_modifiers; + } + + LOG_DEBUG("supported formats for EGL import: "); + for (int i = 0, j = 0; i < n_formats; i++) { + egl_ok = egl_query_dmabuf_modifiers(display, formats[i], max_n_modifiers, modifiers, external_only, &n_modifiers); + if (egl_ok != EGL_TRUE) { + LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); + goto fail_free_formats; + } + + LOG_DEBUG_UNPREFIXED("%" DRM_FOURCC_FORMAT ", ", DRM_FOURCC_ARGS(formats[i])); + + for (int k = 0; k < n_modifiers; k++, j++) { + modified_formats[j].format = formats[i]; + modified_formats[j].modifier = modifiers[k]; + modified_formats[j].external_only = external_only[k]; + } + } + + LOG_DEBUG_UNPREFIXED("\n"); + + free(formats); + free(modifiers); + free(external_only); + + *n_formats_out = n_modified_formats; + *formats_out = modified_formats; + return true; + +fail_free_modifiers: + free(modifiers); + +fail_free_modified_formats: + free(modified_formats); + +fail_free_formats: + free(formats); + +fail: + *n_formats_out = 0; + *formats_out = NULL; + return false; +} +#endif + +struct frame_interface *frame_interface_new(struct gl_renderer *renderer) { struct frame_interface *interface; + struct gbm_device *gbm_device; EGLBoolean egl_ok; EGLContext context; EGLDisplay display; + struct egl_modified_format *formats; + bool supports_extended_imports, supports_external_target; + int n_formats; interface = malloc(sizeof *interface); if (interface == NULL) { return NULL; } - display = flutterpi_get_egl_display(flutterpi); + if (!gl_renderer_supports_egl_extension(renderer, "EGL_EXT_image_dma_buf_import")) { + LOG_ERROR("EGL does not support EGL_EXT_image_dma_buf_import extension. Video frames cannot be uploaded.\n"); + goto fail_free; + } + + if (gl_renderer_supports_egl_extension(renderer, "EGL_EXT_image_dma_buf_import_modifiers")) { +#ifndef EGL_EXT_image_dma_buf_import_modifiers + LOG_ERROR( + "EGL supports EGL_EXT_image_dma_buf_import_modifiers, " + "but EGL headers didn't contain definitions for EGL_EXT_image_dma_buf_import_modifiers." + "Extended imports and pixel format information will not be used.\n" + ); + supports_extended_imports = false; +#else + supports_extended_imports = true; +#endif + } else { + supports_extended_imports = false; + } + + if (gl_renderer_supports_gl_extension(renderer, "GL_OES_EGL_image_external")) { + supports_external_target = true; + } else { + supports_external_target = false; + } + + display = gl_renderer_get_egl_display(renderer); if (display == EGL_NO_DISPLAY) { goto fail_free; } - context = flutterpi_create_egl_context(flutterpi); + context = gl_renderer_create_context(renderer); if (context == EGL_NO_CONTEXT) { goto fail_free; } - PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR"); + PFNEGLCREATEIMAGEKHRPROC create_image = (PFNEGLCREATEIMAGEKHRPROC) gl_renderer_get_proc_address(renderer, "eglCreateImageKHR"); if (create_image == NULL) { - LOG_ERROR("Could not resolve eglCreateImageKHR egl procedure.\n"); + LOG_ERROR("Could not resolve eglCreateImageKHR EGL procedure.\n"); goto fail_destroy_context; } - PFNEGLDESTROYIMAGEKHRPROC destroy_image = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR"); + PFNEGLDESTROYIMAGEKHRPROC destroy_image = (PFNEGLDESTROYIMAGEKHRPROC) gl_renderer_get_proc_address(renderer, "eglDestroyImageKHR"); if (destroy_image == NULL) { - LOG_ERROR("Could not resolve eglDestroyImageKHR egl procedure.\n"); + LOG_ERROR("Could not resolve eglDestroyImageKHR EGL procedure.\n"); goto fail_destroy_context; } - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture2d = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture2d = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC + ) gl_renderer_get_proc_address(renderer, "glEGLImageTargetTexture2DOES"); if (gl_egl_image_target_texture2d == NULL) { - LOG_ERROR("Could not resolve glEGLImageTargetTexture2DOES egl procedure.\n"); + LOG_ERROR("Could not resolve glEGLImageTargetTexture2DOES EGL procedure.\n"); goto fail_destroy_context; } // These two are optional. // Might be useful in the future. - PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats = (PFNEGLQUERYDMABUFFORMATSEXTPROC) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); - PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); +#ifdef EGL_EXT_image_dma_buf_import_modifiers + PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats = (PFNEGLQUERYDMABUFFORMATSEXTPROC + ) gl_renderer_get_proc_address(renderer, "eglQueryDmaBufFormatsEXT"); + if (egl_query_dmabuf_formats == NULL && supports_extended_imports) { + LOG_ERROR("Could not resolve eglQueryDmaBufFormatsEXT egl procedure, even though it is listed as supported.\n"); + supports_extended_imports = false; + } + + PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC + ) gl_renderer_get_proc_address(renderer, "eglQueryDmaBufModifiersEXT"); + if (egl_query_dmabuf_modifiers == NULL && supports_extended_imports) { + LOG_ERROR("Could not resolve eglQueryDmaBufModifiersEXT egl procedure, even though it is listed as supported.\n"); + supports_extended_imports = false; + } +#endif + + gbm_device = gl_renderer_get_gbm_device(renderer); + if (gbm_device == NULL) { + LOG_ERROR("GL Render doesn't have a GBM device associated with it, which is necessary for importing the video frames.\n"); + goto fail_destroy_context; + } + + if (supports_extended_imports) { +#ifdef EGL_EXT_image_dma_buf_import_modifiers + query_formats(display, egl_query_dmabuf_formats, egl_query_dmabuf_modifiers, &n_formats, &formats); +#else + UNREACHABLE(); +#endif + } else { + n_formats = 0; + formats = NULL; + } - interface->gbm_device = flutterpi_get_gbm_device(flutterpi); + interface->gbm_device = gbm_device; interface->display = display; - pthread_mutex_init(&interface->context_lock, NULL); + pthread_mutex_init(&interface->context_lock, NULL); interface->context = context; interface->eglCreateImageKHR = create_image; interface->eglDestroyImageKHR = destroy_image; + interface->supports_external_target = supports_external_target; interface->glEGLImageTargetTexture2DOES = gl_egl_image_target_texture2d; - interface->supports_extended_imports = false; + interface->supports_extended_imports = supports_extended_imports; +#ifdef EGL_EXT_image_dma_buf_import_modifiers interface->eglQueryDmaBufFormatsEXT = egl_query_dmabuf_formats; interface->eglQueryDmaBufModifiersEXT = egl_query_dmabuf_modifiers; +#endif + interface->n_formats = n_formats; + interface->formats = formats; interface->n_refs = REFCOUNT_INIT_1; return interface; - - fail_destroy_context: +fail_destroy_context: egl_ok = eglDestroyContext(display, context); - DEBUG_ASSERT_EGL_TRUE(egl_ok); + ASSERT_EGL_TRUE(egl_ok); (void) egl_ok; - fail_free: +fail_free: free(interface); return NULL; } @@ -106,27 +318,42 @@ void frame_interface_destroy(struct frame_interface *interface) { pthread_mutex_destroy(&interface->context_lock); egl_ok = eglDestroyContext(interface->display, interface->context); - DEBUG_ASSERT_EGL_TRUE(egl_ok); (void) egl_ok; + ASSERT_EGL_TRUE(egl_ok); + (void) egl_ok; + if (interface->formats != NULL) { + free(interface->formats); + } free(interface); } +ATTR_PURE int frame_interface_get_n_formats(struct frame_interface *interface) { + return interface->n_formats; +} + +ATTR_PURE const struct egl_modified_format *frame_interface_get_format(struct frame_interface *interface, int index) { + assert(index < interface->n_formats); + return interface->formats + index; +} + +DEFINE_LOCK_OPS(frame_interface, context_lock) + DEFINE_REF_OPS(frame_interface, n_refs) /** * @brief Create a dmabuf fd from the given GstBuffer. - * + * * Calls gst_buffer_map on the buffer, so buffer could have changed after the call. - * + * */ -int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { +UNUSED int dup_gst_buffer_range_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer, unsigned int memory_index, int n_memories) { struct gbm_bo *bo; GstMapInfo map_info; uint32_t stride; gboolean gst_ok; void *map, *map_data; int fd; - - gst_ok = gst_buffer_map(buffer, &map_info, GST_MAP_READ); + + gst_ok = gst_buffer_map_range(buffer, memory_index, n_memories, &map_info, GST_MAP_READ); if (gst_ok == FALSE) { LOG_ERROR("Couldn't map gstreamer video frame buffer to copy it into a dma buffer.\n"); return -1; @@ -155,205 +382,674 @@ int dup_gst_buffer_as_dmabuf(struct gbm_device *gbm_device, GstBuffer *buffer) { goto fail_destroy_bo; } - /// TODO: Should we dup the fd before we destroy the bo? + /// TODO: Should we dup the fd before we destroy the bo? gbm_bo_destroy(bo); gst_buffer_unmap(buffer, &map_info); return fd; - fail_destroy_bo: +fail_destroy_bo: gbm_bo_destroy(bo); - fail_unmap_buffer: +fail_unmap_buffer: gst_buffer_unmap(buffer, &map_info); return -1; } -struct video_frame *frame_new( - struct frame_interface *interface, - const struct frame_info *info, - GstSample *sample -) { -# define PUT_ATTR(_key, _value) do { *attr_cursor++ = _key; *attr_cursor++ = _value; } while (false) - struct video_frame *frame; - GstVideoMeta *meta; - EGLBoolean egl_ok; - EGLImage egl_image; - GstBuffer *buffer; - GstMemory *memory; - GLenum gl_error; - EGLint attributes[2*7 + MAX_N_PLANES*2*5 + 1], *attr_cursor; - GLuint texture; - EGLint egl_error; - bool is_dmabuf_memory; - int dmabuf_fd, n_mems, n_planes, width, height; +/** + * @brief Create a dmabuf fd from the given GstMemory. + * + * Calls gst_memory_map on the memory. + * + */ +UNUSED int dup_gst_memory_as_dmabuf(struct gbm_device *gbm_device, GstMemory *memory) { + struct gbm_bo *bo; + GstMapInfo map_info; + uint32_t stride; + gboolean gst_ok; + void *map, *map_data; + int fd; - struct { - int fd; - int offset; - int pitch; - bool has_modifier; - uint64_t modifier; - } planes[MAX_N_PLANES]; + gst_ok = gst_memory_map(memory, &map_info, GST_MAP_READ); + if (gst_ok == FALSE) { + LOG_ERROR("Couldn't map gstreamer video frame memory to copy it into a dma buffer.\n"); + return -1; + } - buffer = gst_sample_get_buffer(sample); + bo = gbm_bo_create(gbm_device, map_info.size, 1, GBM_FORMAT_R8, GBM_BO_USE_LINEAR); + if (bo == NULL) { + LOG_ERROR("Couldn't create GBM BO to copy video frame into.\n"); + goto fail_unmap_buffer; + } - frame = malloc(sizeof *frame); - if (frame == NULL) { - goto fail_unref_buffer; + map_data = NULL; + map = gbm_bo_map(bo, 0, 0, map_info.size, 1, GBM_BO_TRANSFER_WRITE, &stride, &map_data); + if (map == NULL) { + LOG_ERROR("Couldn't mmap GBM BO to copy video frame into it.\n"); + goto fail_destroy_bo; } - memory = gst_buffer_peek_memory(buffer, 0); - is_dmabuf_memory = gst_is_dmabuf_memory(memory); - n_mems = gst_buffer_n_memory(buffer); + memcpy(map, map_info.data, map_info.size); - /// TODO: Do we really need to dup() here? - if (is_dmabuf_memory) { - dmabuf_fd = dup(gst_dmabuf_memory_get_fd(memory)); - } else { - dmabuf_fd = dup_gst_buffer_as_dmabuf(interface->gbm_device, buffer); - - //LOG_ERROR("Only dmabuf memory is supported for video frame buffers right now, but gstreamer didn't provide a dmabuf memory buffer.\n"); - //goto fail_free_frame; + gbm_bo_unmap(bo, map_data); + + fd = gbm_bo_get_fd(bo); + if (fd < 0) { + LOG_ERROR("Couldn't filedescriptor of video frame GBM BO.\n"); + goto fail_destroy_bo; } - if (n_mems > 1) { - LOG_ERROR("Multiple dmabufs for a single frame buffer is not supported right now.\n"); - goto fail_free_frame; + /// TODO: Should we dup the fd before we destroy the bo? + gbm_bo_destroy(bo); + gst_memory_unmap(memory, &map_info); + return fd; + +fail_destroy_bo: + gbm_bo_destroy(bo); + +fail_unmap_buffer: + gst_memory_unmap(memory, &map_info); + return -1; +} + +struct plane_info { + int fd; + uint32_t offset; + uint32_t pitch; + bool has_modifier; + uint64_t modifier; +}; + +#if THIS_GSTREAMER_VER < GSTREAMER_VER(1, 14, 0) + #error "Unsupported gstreamer version." +#endif + +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 18, 0) +static bool get_plane_sizes_from_meta(const GstVideoMeta *meta, size_t plane_sizes_out[4]) { + GstVideoMeta *meta_non_const; + gboolean gst_ok; + + #ifdef DEBUG + GstVideoMeta _meta_non_const; + meta_non_const = &_meta_non_const; + memcpy(meta_non_const, meta, sizeof *meta); + #else + meta_non_const = (GstVideoMeta *) meta; + #endif + + gst_ok = gst_video_meta_get_plane_size(meta_non_const, plane_sizes_out); + if (gst_ok != TRUE) { + LOG_ERROR("Could not query video frame plane size. gst_video_meta_get_plane_size\n"); + return false; } - width = GST_VIDEO_INFO_WIDTH(info->gst_info); - height = GST_VIDEO_INFO_HEIGHT(info->gst_info); - n_planes = GST_VIDEO_INFO_N_PLANES(info->gst_info); + return true; +} + +static bool get_plane_sizes_from_video_info(const GstVideoInfo *info, size_t plane_sizes_out[4]) { + GstVideoAlignment alignment; + GstVideoInfo *info_non_const; + gboolean gst_ok; + + gst_video_alignment_reset(&alignment); + + #ifdef DEBUG + info_non_const = gst_video_info_copy(info); + #else + info_non_const = (GstVideoInfo *) info; + #endif + gst_ok = gst_video_info_align_full(info_non_const, &alignment, plane_sizes_out); + if (gst_ok != TRUE) { + LOG_ERROR("Could not query video frame plane size. gst_video_info_align_full\n"); + return false; + } + + #ifdef DEBUG + assert(gst_video_info_is_equal(info, info_non_const)); + gst_video_info_free(info_non_const); + #endif + + return true; +} + +static bool calculate_plane_size(const GstVideoInfo *info, int plane_index, size_t *plane_size_out) { + // Taken from: https://github.com/GStreamer/gstreamer/blob/621604aa3e4caa8db27637f63fa55fac2f7721e5/subprojects/gst-plugins-base/gst-libs/gst/video/video-info.c#L1278-L1301 + + #if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 21, 3) + if (GST_VIDEO_FORMAT_INFO_IS_TILED(info->finfo)) { + guint x_tiles = GST_VIDEO_TILE_X_TILES(info->stride[plane_index]); + guint y_tiles = GST_VIDEO_TILE_Y_TILES(info->stride[plane_index]); + *plane_size_out = x_tiles * y_tiles * GST_VIDEO_FORMAT_INFO_TILE_SIZE(info->finfo, plane_index); + return true; + } + #endif + + gint comp[GST_VIDEO_MAX_COMPONENTS]; + guint plane_height; + + /* Convert plane index to component index */ + gst_video_format_info_component(info->finfo, plane_index, comp); + plane_height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info->finfo, comp[0], GST_VIDEO_INFO_FIELD_HEIGHT(info)); + + *plane_size_out = plane_height * GST_VIDEO_INFO_PLANE_STRIDE(info, plane_index); + return true; +} + +#else +static bool get_plane_sizes_from_meta(UNUSED const GstVideoMeta *meta, UNUSED size_t plane_sizes_out[4]) { + return false; +} +static bool get_plane_sizes_from_video_info(UNUSED const GstVideoInfo *info, UNUSED size_t plane_sizes_out[4]) { + return false; +} +static bool calculate_plane_size(UNUSED const GstVideoInfo *info, UNUSED int plane_index, UNUSED size_t *plane_size_out) { + return false; +} +#endif + +static int +get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device *gbm_device, struct plane_info plane_infos[MAX_N_PLANES]) { + GstVideoMeta *meta; + GstMemory *memory; + gboolean gst_ok; + size_t plane_sizes[4] = { 0 }; + bool has_plane_sizes; + int n_planes; + + n_planes = GST_VIDEO_INFO_N_PLANES(info); + + // There's so many ways to get the plane sizes. + // 1. Preferably we should use the video meta. + // 2. If that doesn't work, we'll use gst_video_info_align_full() with the video info. + // 3. If that doesn't work, we'll calculate them ourselves. + // 4. If that doesn't work, we can't determine the plane sizes. + // In that case, we'll error if we have more than one plane. + has_plane_sizes = false; meta = gst_buffer_get_video_meta(buffer); if (meta != NULL) { + has_plane_sizes = get_plane_sizes_from_meta(meta, plane_sizes); + } + + if (!has_plane_sizes) { + has_plane_sizes = get_plane_sizes_from_video_info(info, plane_sizes); + } + + if (!has_plane_sizes) { + has_plane_sizes = true; for (int i = 0; i < n_planes; i++) { - planes[i].fd = dmabuf_fd; - planes[i].offset = meta->offset[i]; - planes[i].pitch = meta->stride[i]; - planes[i].has_modifier = false; - planes[i].modifier = DRM_FORMAT_MOD_LINEAR; + has_plane_sizes = has_plane_sizes && calculate_plane_size(info, i, plane_sizes + i); + } + } + + if (!has_plane_sizes) { + // We couldn't determine the plane sizes. + // We can still continue if we have only one plane. + if (n_planes != 1) { + LOG_ERROR( + "Couldn't determine video frame plane sizes. Without plane sizes, only planar framebuffer formats are supported, but the " + "supplied format was not planar.\n" + ); + return EINVAL; + } + + // We'll just assume the first plane spans the entire buffer. + plane_sizes[0] = gst_buffer_get_size(buffer); + has_plane_sizes = true; + } + + for (int i = 0; i < n_planes; i++) { + size_t offset_in_memory = 0; + size_t offset_in_buffer = 0; + unsigned memory_index = 0; + unsigned n_memories = 0; + int stride, ok; + + if (meta) { + offset_in_buffer = meta->offset[i]; + stride = meta->stride[i]; + } else { + offset_in_buffer = GST_VIDEO_INFO_PLANE_OFFSET(info, i); + stride = GST_VIDEO_INFO_PLANE_STRIDE(info, i); + } + + gst_ok = gst_buffer_find_memory(buffer, offset_in_buffer, plane_sizes[i], &memory_index, &n_memories, &offset_in_memory); + if (gst_ok != TRUE) { + LOG_ERROR("Could not find video frame memory for plane.\n"); + ok = EIO; + goto fail_close_fds; + } + + if (n_memories != 1) { + ok = dup_gst_buffer_range_as_dmabuf(gbm_device, buffer, memory_index, n_memories); + if (ok < 0) { + LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; + } + + plane_infos[i].fd = ok; + } else { + memory = gst_buffer_peek_memory(buffer, memory_index); + if (gst_is_dmabuf_memory(memory)) { + ok = gst_dmabuf_memory_get_fd(memory); + if (ok < 0) { + LOG_ERROR("Could not get gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; + } + + ok = dup(ok); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not dup fd. dup: %s\n", strerror(ok)); + goto fail_close_fds; + } + + plane_infos[i].fd = ok; + } else { + /// TODO: When duping, duplicate all non-dmabuf memories into one + /// gbm buffer instead. + ok = dup_gst_memory_as_dmabuf(gbm_device, memory); + if (ok < 0) { + LOG_ERROR("Could not duplicate gstreamer memory as dmabuf.\n"); + ok = EIO; + goto fail_close_fds; + } + + plane_infos[i].fd = ok; + } + + offset_in_memory += memory->offset; + } + + plane_infos[i].offset = offset_in_memory; + plane_infos[i].pitch = stride; + + /// TODO: Detect modifiers here + /// Modifiers will be supported in future gstreamer, see: + /// https://gstreamer.freedesktop.org/documentation/additional/design/dmabuf.html?gi-language=c + plane_infos[i].has_modifier = false; + plane_infos[i].modifier = DRM_FORMAT_MOD_LINEAR; + continue; + +fail_close_fds: + for (int j = i - 1; j > 0; j--) { + close(plane_infos[i].fd); } + return ok; + } + + return 0; +} + +static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { + switch (GST_VIDEO_INFO_FORMAT(info)) { + case GST_VIDEO_FORMAT_YUY2: return DRM_FORMAT_YUYV; + case GST_VIDEO_FORMAT_YVYU: return DRM_FORMAT_YVYU; + case GST_VIDEO_FORMAT_UYVY: return DRM_FORMAT_UYVY; + case GST_VIDEO_FORMAT_VYUY: return DRM_FORMAT_VYUY; + case GST_VIDEO_FORMAT_AYUV: return DRM_FORMAT_AYUV; +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) + case GST_VIDEO_FORMAT_VUYA: return DRM_FORMAT_AYUV; +#endif + case GST_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; + case GST_VIDEO_FORMAT_NV21: return DRM_FORMAT_NV21; + case GST_VIDEO_FORMAT_NV16: return DRM_FORMAT_NV16; + case GST_VIDEO_FORMAT_NV61: return DRM_FORMAT_NV61; + case GST_VIDEO_FORMAT_NV24: return DRM_FORMAT_NV24; + case GST_VIDEO_FORMAT_YUV9: return DRM_FORMAT_YUV410; + case GST_VIDEO_FORMAT_YVU9: return DRM_FORMAT_YVU410; + case GST_VIDEO_FORMAT_Y41B: return DRM_FORMAT_YUV411; + case GST_VIDEO_FORMAT_I420: return DRM_FORMAT_YUV420; + case GST_VIDEO_FORMAT_YV12: return DRM_FORMAT_YVU420; + case GST_VIDEO_FORMAT_Y42B: return DRM_FORMAT_YUV422; + case GST_VIDEO_FORMAT_Y444: return DRM_FORMAT_YUV444; + case GST_VIDEO_FORMAT_RGB16: return DRM_FORMAT_RGB565; + case GST_VIDEO_FORMAT_BGR16: return DRM_FORMAT_BGR565; + case GST_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888; + case GST_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888; + case GST_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888; + case GST_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888; + case GST_VIDEO_FORMAT_ARGB: return DRM_FORMAT_BGRA8888; + case GST_VIDEO_FORMAT_xRGB: return DRM_FORMAT_BGRX8888; + case GST_VIDEO_FORMAT_ABGR: return DRM_FORMAT_RGBA8888; + case GST_VIDEO_FORMAT_xBGR: return DRM_FORMAT_RGBX8888; + default: return DRM_FORMAT_INVALID; + } +} + +ATTR_CONST GstVideoFormat gst_video_format_from_drm_format(uint32_t drm_format) { + switch (drm_format) { + case DRM_FORMAT_YUYV: return GST_VIDEO_FORMAT_YUY2; + case DRM_FORMAT_YVYU: return GST_VIDEO_FORMAT_YVYU; + case DRM_FORMAT_UYVY: return GST_VIDEO_FORMAT_UYVY; + case DRM_FORMAT_VYUY: return GST_VIDEO_FORMAT_VYUY; + case DRM_FORMAT_AYUV: return GST_VIDEO_FORMAT_AYUV; +#if THIS_GSTREAMER_VER >= GSTREAMER_VER(1, 16, 0) + // GST_VIDEO_FORMAT_AYUV and _VUYA both map to DRM_FORMAT_AYUV + // case DRM_FORMAT_AYUV: return GST_VIDEO_FORMAT_VUYA; +#endif + case DRM_FORMAT_NV12: return GST_VIDEO_FORMAT_NV12; + case DRM_FORMAT_NV21: return GST_VIDEO_FORMAT_NV21; + case DRM_FORMAT_NV16: return GST_VIDEO_FORMAT_NV16; + case DRM_FORMAT_NV61: return GST_VIDEO_FORMAT_NV61; + case DRM_FORMAT_NV24: return GST_VIDEO_FORMAT_NV24; + case DRM_FORMAT_YUV410: return GST_VIDEO_FORMAT_YUV9; + case DRM_FORMAT_YVU410: return GST_VIDEO_FORMAT_YVU9; + case DRM_FORMAT_YUV411: return GST_VIDEO_FORMAT_Y41B; + case DRM_FORMAT_YUV420: return GST_VIDEO_FORMAT_I420; + case DRM_FORMAT_YVU420: return GST_VIDEO_FORMAT_YV12; + case DRM_FORMAT_YUV422: return GST_VIDEO_FORMAT_Y42B; + case DRM_FORMAT_YUV444: return GST_VIDEO_FORMAT_Y444; + case DRM_FORMAT_RGB565: return GST_VIDEO_FORMAT_RGB16; + case DRM_FORMAT_BGR565: return GST_VIDEO_FORMAT_BGR16; + case DRM_FORMAT_ABGR8888: return GST_VIDEO_FORMAT_RGBA; + case DRM_FORMAT_XBGR8888: return GST_VIDEO_FORMAT_RGBx; + case DRM_FORMAT_ARGB8888: return GST_VIDEO_FORMAT_BGRA; + case DRM_FORMAT_XRGB8888: return GST_VIDEO_FORMAT_BGRx; + case DRM_FORMAT_BGRA8888: return GST_VIDEO_FORMAT_ARGB; + case DRM_FORMAT_BGRX8888: return GST_VIDEO_FORMAT_xRGB; + case DRM_FORMAT_RGBA8888: return GST_VIDEO_FORMAT_ABGR; + case DRM_FORMAT_RGBX8888: return GST_VIDEO_FORMAT_xBGR; + default: return GST_VIDEO_FORMAT_UNKNOWN; + } +} + +static EGLint egl_color_space_from_gst_info(const GstVideoInfo *info) { + if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT601)) { + return EGL_ITU_REC601_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT709)) { + return EGL_ITU_REC709_EXT; + } else if (gst_video_colorimetry_matches(&GST_VIDEO_INFO_COLORIMETRY(info), GST_VIDEO_COLORIMETRY_BT2020)) { + return EGL_ITU_REC2020_EXT; } else { - for (int i = 0; i < n_planes; i++) { - planes[i].fd = dmabuf_fd; - planes[i].offset = GST_VIDEO_INFO_PLANE_OFFSET(info->gst_info, i); - planes[i].pitch = GST_VIDEO_INFO_PLANE_STRIDE(info->gst_info, i); - planes[i].has_modifier = false; - planes[i].modifier = DRM_FORMAT_MOD_LINEAR; + LOG_DEBUG("Unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(&GST_VIDEO_INFO_COLORIMETRY(info))); + return EGL_NONE; + } +} + +static EGLint egl_sample_range_hint_from_gst_info(const GstVideoInfo *info) { + if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_0_255) { + return EGL_YUV_FULL_RANGE_EXT; + } else if (GST_VIDEO_INFO_COLORIMETRY(info).range == GST_VIDEO_COLOR_RANGE_16_235) { + return EGL_YUV_NARROW_RANGE_EXT; + } else { + return EGL_NONE; + } +} + +static EGLint egl_horizontal_chroma_siting_from_gst_info(const GstVideoInfo *info) { + GstVideoChromaSite chroma_site = GST_VIDEO_INFO_CHROMA_SITE(info); + + if (chroma_site == GST_VIDEO_CHROMA_SITE_H_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else if (chroma_site == GST_VIDEO_CHROMA_SITE_V_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_NONE) { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } else { + return EGL_NONE; + } +} + +static EGLint egl_vertical_chroma_siting_from_gst_info(const GstVideoInfo *info) { + GstVideoChromaSite chroma_site = GST_VIDEO_INFO_CHROMA_SITE(info); + + if (chroma_site == GST_VIDEO_CHROMA_SITE_V_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_COSITED) { + return EGL_YUV_CHROMA_SITING_0_EXT; + } else if (chroma_site == GST_VIDEO_CHROMA_SITE_H_COSITED || chroma_site == GST_VIDEO_CHROMA_SITE_NONE) { + return EGL_YUV_CHROMA_SITING_0_5_EXT; + } else { + return EGL_NONE; + } +} + +struct video_frame *frame_new(struct frame_interface *interface, GstSample *sample, const GstVideoInfo *info) { +#define PUT_ATTR(_key, _value) \ + do { \ + assert(attr_index + 2 <= ARRAY_SIZE(attributes)); \ + attributes[attr_index++] = (_key); \ + attributes[attr_index++] = (_value); \ + } while (false) + struct video_frame *frame; + struct plane_info planes[MAX_N_PLANES]; + GstVideoInfo _info; + EGLBoolean egl_ok; + GstBuffer *buffer; + EGLImageKHR egl_image; + gboolean gst_ok; + uint32_t drm_format; + GstCaps *caps; + GLuint texture; + GLenum gl_error; + EGLint egl_error; + EGLint attributes[2 * 7 + MAX_N_PLANES * 2 * 5 + 1]; + EGLint egl_color_space, egl_sample_range_hint, egl_horizontal_chroma_siting, egl_vertical_chroma_siting; + int ok, width, height, n_planes, attr_index; + + buffer = gst_sample_get_buffer(sample); + if (buffer == NULL) { + LOG_ERROR("Could not get buffer from video sample.\n"); + return NULL; + } + + // If we don't have an explicit info given, we determine it from the sample caps. + if (info == NULL) { + caps = gst_sample_get_caps(sample); + if (caps == NULL) { + return NULL; } + + info = &_info; + + gst_ok = gst_video_info_from_caps(&_info, caps); + if (gst_ok == FALSE) { + LOG_ERROR("Could not get video info from video sample caps.\n"); + return NULL; + } + } else { + caps = NULL; } - attr_cursor = attributes; + // Determine some basic frame info. + width = GST_VIDEO_INFO_WIDTH(info); + height = GST_VIDEO_INFO_HEIGHT(info); + n_planes = GST_VIDEO_INFO_N_PLANES(info); + + // query the drm format for this sample + drm_format = drm_format_from_gst_info(info); + if (drm_format == DRM_FORMAT_INVALID) { + LOG_ERROR("Video format has no EGL equivalent.\n"); + return NULL; + } + + bool external_only; + for_each_format_in_frame_interface(i, format, interface) { + if (format->format == drm_format && format->modifier == DRM_FORMAT_MOD_LINEAR) { + external_only = format->external_only; + goto format_supported; + } + } + + LOG_ERROR( + "Video format is not supported by EGL: %" DRM_FOURCC_FORMAT " (modifier: %" PRIu64 ").\n", + DRM_FOURCC_ARGS(drm_format), + (uint64_t) DRM_FORMAT_MOD_LINEAR + ); + return NULL; + +format_supported: + + // query the color space for this sample + egl_color_space = egl_color_space_from_gst_info(info); + + // check the sample range + egl_sample_range_hint = egl_sample_range_hint_from_gst_info(info); + + // check the chroma siting + egl_horizontal_chroma_siting = egl_horizontal_chroma_siting_from_gst_info(info); + egl_vertical_chroma_siting = egl_vertical_chroma_siting_from_gst_info(info); + + frame = malloc(sizeof *frame); + if (frame == NULL) { + return NULL; + } + + ok = get_plane_infos(buffer, info, interface->gbm_device, planes); + if (ok != 0) { + goto fail_free_frame; + } + + // Start putting together the EGL attributes. + attr_index = 0; + +#ifndef EGL_EXT_image_dma_buf_import + #error "EGL header definitions for extension EGL_EXT_image_dma_buf_import are required." +#endif // first, put some of our basic attributes like // frame size and format PUT_ATTR(EGL_WIDTH, width); PUT_ATTR(EGL_HEIGHT, height); - PUT_ATTR(EGL_LINUX_DRM_FOURCC_EXT, info->drm_format); + PUT_ATTR(EGL_LINUX_DRM_FOURCC_EXT, drm_format); // if we have a color space, put that too // could be one of EGL_ITU_REC601_EXT, EGL_ITU_REC709_EXT or EGL_ITU_REC2020_EXT - if (info->egl_color_space != EGL_NONE) { - PUT_ATTR(EGL_YUV_COLOR_SPACE_HINT_EXT, info->egl_color_space); + if (egl_color_space != EGL_NONE) { + PUT_ATTR(EGL_YUV_COLOR_SPACE_HINT_EXT, egl_color_space); } // if we have information about the sample range, put that into the attributes too - if (GST_VIDEO_INFO_COLORIMETRY(info->gst_info).range == GST_VIDEO_COLOR_RANGE_0_255) { - PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_FULL_RANGE_EXT); - } else if (GST_VIDEO_INFO_COLORIMETRY(info->gst_info).range == GST_VIDEO_COLOR_RANGE_16_235) { - PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_NARROW_RANGE_EXT); + if (egl_sample_range_hint != EGL_NONE) { + PUT_ATTR(EGL_SAMPLE_RANGE_HINT_EXT, egl_sample_range_hint); } - // Check that we can actually represent that siting info using the attributes EGL gives us. - // For example, we can't represent GST_VIDEO_CHROMA_SITE_ALT_LINE. - if ((GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & ~(GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED)) == 0) { - if (GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & GST_VIDEO_CHROMA_SITE_H_COSITED) { - PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT); - } else { - PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_5_EXT); - } - if (GST_VIDEO_INFO_CHROMA_SITE(info->gst_info) & GST_VIDEO_CHROMA_SITE_V_COSITED) { - PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT); - } else { - PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_5_EXT); - } + // chroma siting + if (egl_horizontal_chroma_siting != EGL_NONE) { + PUT_ATTR(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, egl_horizontal_chroma_siting); + } + + if (egl_vertical_chroma_siting != EGL_NONE) { + PUT_ATTR(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, egl_vertical_chroma_siting); } - - // now begin with putting in information about plane memory + + // add plane 1 PUT_ATTR(EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd); PUT_ATTR(EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset); PUT_ATTR(EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].pitch); if (planes[0].has_modifier) { if (interface->supports_extended_imports) { +#ifdef EGL_EXT_image_dma_buf_import_modifiers PUT_ATTR(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, uint32_to_int32(planes[0].modifier & 0xFFFFFFFFlu)); PUT_ATTR(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, uint32_to_int32(planes[0].modifier >> 32)); +#else + UNREACHABLE(); +#endif } else { - LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + LOG_ERROR( + "video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n" + ); + goto fail_release_planes; } } + // add plane 2 (if present) if (n_planes >= 2) { PUT_ATTR(EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd); PUT_ATTR(EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset); PUT_ATTR(EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].pitch); if (planes[1].has_modifier) { if (interface->supports_extended_imports) { +#ifdef EGL_EXT_image_dma_buf_import_modifiers PUT_ATTR(EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, uint32_to_int32(planes[1].modifier & 0xFFFFFFFFlu)); PUT_ATTR(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, uint32_to_int32(planes[1].modifier >> 32)); +#else + UNREACHABLE(); +#endif } else { - LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + LOG_ERROR( + "video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers " + "extension.\n" + ); + goto fail_release_planes; } } } + // add plane 3 (if present) if (n_planes >= 3) { PUT_ATTR(EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd); PUT_ATTR(EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset); PUT_ATTR(EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].pitch); if (planes[2].has_modifier) { if (interface->supports_extended_imports) { +#ifdef EGL_EXT_image_dma_buf_import_modifiers PUT_ATTR(EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, uint32_to_int32(planes[2].modifier & 0xFFFFFFFFlu)); PUT_ATTR(EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, uint32_to_int32(planes[2].modifier >> 32)); +#else + UNREACHABLE(); +#endif } else { - LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + LOG_ERROR( + "video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers " + "extension.\n" + ); + goto fail_release_planes; } } } + // add plane 4 (if present) if (n_planes >= 4) { if (!interface->supports_extended_imports) { - LOG_ERROR("The video frame has more than 3 planes but that can't be imported as a GL texture if EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; + LOG_ERROR( + "The video frame has more than 3 planes but that can't be imported as a GL texture if EGL doesn't support the " + "EGL_EXT_image_dma_buf_import_modifiers extension.\n" + ); + goto fail_release_planes; } +#ifdef EGL_EXT_image_dma_buf_import_modifiers PUT_ATTR(EGL_DMA_BUF_PLANE3_FD_EXT, planes[3].fd); PUT_ATTR(EGL_DMA_BUF_PLANE3_OFFSET_EXT, planes[3].offset); PUT_ATTR(EGL_DMA_BUF_PLANE3_PITCH_EXT, planes[3].pitch); if (planes[3].has_modifier) { - if (interface->supports_extended_imports) { - PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, uint32_to_int32(planes[3].modifier & 0xFFFFFFFFlu)); - PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, uint32_to_int32(planes[3].modifier >> 32)); - } else { - LOG_ERROR("video frame buffer uses modified format but EGL doesn't support the EGL_EXT_image_dma_buf_import_modifiers extension.\n"); - goto fail_close_dmabuf_fd; - } + PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, uint32_to_int32(planes[3].modifier & 0xFFFFFFFFlu)); + PUT_ATTR(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, uint32_to_int32(planes[3].modifier >> 32)); } +#else + UNREACHABLE(); +#endif } - // add a EGL_NONE to mark the end of the buffer - *attr_cursor++ = EGL_NONE; + assert(attr_index < ARRAY_SIZE(attributes)); + attributes[attr_index++] = EGL_NONE; egl_image = interface->eglCreateImageKHR(interface->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attributes); if (egl_image == EGL_NO_IMAGE_KHR) { - goto fail_close_dmabuf_fd; + LOG_ERROR("Couldn't create EGL image from video sample.\n"); + goto fail_release_planes; } frame_interface_lock(interface); + /// TODO: Make a single EGL context current for the whole lifetime of a + /// gstreamer thread, instead of making one current and clearing it after + /// for the duration of this procedure, to save some cycles. + /// + /// Make context current in GST_STREAM_STATUS_TYPE_ENTER message, clear it + /// in GST_STREAM_STATUS_TYPE_LEAVE message. + /// + /// See https://gstreamer.freedesktop.org/documentation/additional/design/stream-status.html + /// + /// Alternatively, don't unconditionally create & make current an EGL + /// context in the ENTER message, but create one lazily as soon as we need + /// one, but keep track of it an destroy it in the LEAVE message. + /// + /// The STREAM_STATUS messages need to be handled synchronously of course, + /// on the streaming thread, using either: + /// - gst_bus_set_sync_handler + /// https://gstreamer.freedesktop.org/documentation/gstreamer/gstbus.html#gst_bus_set_sync_handler + /// - or GstBus sync-message signal + /// https://gstreamer.freedesktop.org/documentation/gstreamer/gstbus.html#GstBus::sync-message egl_ok = eglMakeCurrent(interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, interface->context); if (egl_ok == EGL_FALSE) { egl_error = eglGetError(); @@ -368,9 +1064,23 @@ struct video_frame *frame_new( goto fail_clear_context; } - glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); - interface->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_image); - glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); + GLenum target; + if (external_only) { + target = GL_TEXTURE_EXTERNAL_OES; + } else { + target = GL_TEXTURE_2D; + } + + glBindTexture(target, texture); + + interface->glEGLImageTargetTexture2DOES(target, egl_image); + gl_error = glGetError(); + if (gl_error != GL_NO_ERROR) { + LOG_ERROR("Couldn't attach EGL Image to OpenGL texture. glEGLImageTargetTexture2DOES: %" PRIu32 "\n", gl_error); + goto fail_unbind_texture; + } + + glBindTexture(target, 0); egl_ok = eglMakeCurrent(interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (egl_ok == EGL_FALSE) { @@ -381,66 +1091,68 @@ struct video_frame *frame_new( frame_interface_unlock(interface); - frame->sample = sample; + frame->sample = gst_sample_ref(sample); frame->interface = frame_interface_ref(interface); - frame->drm_format = info->drm_format; - frame->n_dmabuf_fds = 1; - frame->dmabuf_fds[0] = dmabuf_fd; + frame->drm_format = drm_format; + frame->n_dmabuf_fds = n_planes; + frame->dmabuf_fds[0] = planes[0].fd; + frame->dmabuf_fds[1] = planes[1].fd; + frame->dmabuf_fds[2] = planes[2].fd; + frame->dmabuf_fds[3] = planes[3].fd; frame->image = egl_image; - frame->gl_frame.target = GL_TEXTURE_EXTERNAL_OES; + frame->gl_frame.target = target; frame->gl_frame.name = texture; frame->gl_frame.format = GL_RGBA8_OES; frame->gl_frame.width = 0; frame->gl_frame.height = 0; - return frame; - fail_delete_texture: +fail_unbind_texture: + glBindTexture(texture, 0); + +fail_delete_texture: glDeleteTextures(1, &texture); - fail_clear_context: +fail_clear_context: eglMakeCurrent(interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - fail_unlock_interface: +fail_unlock_interface: frame_interface_unlock(interface); interface->eglDestroyImageKHR(interface->display, egl_image); - fail_close_dmabuf_fd: - close(dmabuf_fd); +fail_release_planes: + for (int i = 0; i < n_planes; i++) + close(planes[i].fd); - fail_free_frame: +fail_free_frame: free(frame); - - fail_unref_buffer: - gst_sample_unref(sample); return NULL; - -# undef PUT_ATTR } void frame_destroy(struct video_frame *frame) { EGLBoolean egl_ok; int ok; - /// TODO: See TODO in frame_new - gst_sample_unref(frame->sample); - frame_interface_lock(frame->interface); egl_ok = eglMakeCurrent(frame->interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, frame->interface->context); - DEBUG_ASSERT_EGL_TRUE(egl_ok); (void) egl_ok; + ASSERT_EGL_TRUE(egl_ok); + (void) egl_ok; glDeleteTextures(1, &frame->gl_frame.name); - DEBUG_ASSERT(GL_NO_ERROR == glGetError()); + assert(GL_NO_ERROR == glGetError()); egl_ok = eglMakeCurrent(frame->interface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - DEBUG_ASSERT_EGL_TRUE(egl_ok); + ASSERT_EGL_TRUE(egl_ok); frame_interface_unlock(frame->interface); - + egl_ok = frame->interface->eglDestroyImageKHR(frame->interface->display, frame->image); - DEBUG_ASSERT_EGL_TRUE(egl_ok); + ASSERT_EGL_TRUE(egl_ok); frame_interface_unref(frame->interface); for (int i = 0; i < frame->n_dmabuf_fds; i++) { ok = close(frame->dmabuf_fds[i]); - DEBUG_ASSERT(ok == 0); (void) ok; + assert(ok == 0); + (void) ok; } + + gst_sample_unref(frame->sample); free(frame); } diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 54c46bd0..8c948cda 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -1,41 +1,55 @@ #define _GNU_SOURCE +#include #include -#include #include -#include -#include + +#include #include +#include +#include #include #include #include -#include -#include #include +#include -#include -#include -#include -#include -#include -#include -#include - -FILE_DESCR("gstreamer video_player") +#include "flutter-pi.h" +#include "notifier_listener.h" +#include "platformchannel.h" +#include "pluginregistry.h" +#include "plugins/gstreamer_video_player.h" +#include "texture_registry.h" +#include "util/collection.h" +#include "util/logging.h" #ifdef DEBUG -# define DEBUG_TRACE_BEGIN(player, name) trace_begin(player, name) -# define DEBUG_TRACE_END(player, name) trace_end(player, name) -# define DEBUG_TRACE_INSTANT(player, name) trace_instant(player, name) + #define DEBUG_TRACE_BEGIN(player, name) trace_begin(player, name) + #define DEBUG_TRACE_END(player, name) trace_end(player, name) + #define DEBUG_TRACE_INSTANT(player, name) trace_instant(player, name) #else -# define DEBUG_TRACE_BEGIN(player, name) do {} while (0) -# define DEBUG_TRACE_END(player, name) do {} while (0) -# define DEBUG_TRACE_INSTANT(player, name) do {} while (0) + #define DEBUG_TRACE_BEGIN(player, name) \ + do { \ + } while (0) + #define DEBUG_TRACE_END(player, name) \ + do { \ + } while (0) + #define DEBUG_TRACE_INSTANT(player, name) \ + do { \ + } while (0) #endif -#define LOG_GST_SET_STATE_ERROR(_element) LOG_ERROR("setting gstreamer playback state failed. gst_element_set_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", GST_ELEMENT_NAME(_element)) -#define LOG_GST_GET_STATE_ERROR(_element) LOG_ERROR("last gstreamer state change failed. gst_element_get_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", GST_ELEMENT_NAME(_element)) +#define LOG_GST_SET_STATE_ERROR(_element) \ + LOG_ERROR( \ + "setting gstreamer playback state failed. gst_element_set_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", \ + GST_ELEMENT_NAME(_element) \ + ) +#define LOG_GST_GET_STATE_ERROR(_element) \ + LOG_ERROR( \ + "last gstreamer state change failed. gst_element_get_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", \ + GST_ELEMENT_NAME(_element) \ + ) struct incomplete_video_info { bool has_resolution; @@ -45,63 +59,60 @@ struct incomplete_video_info { struct video_info info; }; -enum playpause_state { - kPaused, - kPlaying, - kStepping -}; +enum playpause_state { kPaused, kPlaying, kStepping }; -enum playback_direction { - kForward, - kBackward -}; +enum playback_direction { kForward, kBackward }; -#define PLAYPAUSE_STATE_AS_STRING(playpause_state) ( \ - (playpause_state) == kPaused ? "paused" : \ - (playpause_state) == kPlaying ? "playing" : \ - (playpause_state) == kStepping ? "stepping" : "?") +#define PLAYPAUSE_STATE_AS_STRING(playpause_state) \ + ((playpause_state) == kPaused ? "paused" : \ + (playpause_state) == kPlaying ? "playing" : \ + (playpause_state) == kStepping ? "stepping" : \ + "?") struct gstplayer { pthread_mutex_t lock; - + struct flutterpi *flutterpi; void *userdata; + char *video_uri; + char *pipeline_description; + GstStructure *headers; /** * @brief The desired playback rate that should be used when @ref playpause_state is kPlayingForward. (should be > 0) - * + * */ double playback_rate_forward; /** * @brief The desired playback rate that should be used when @ref playpause_state is kPlayingBackward. (should be < 0) - * + * */ double playback_rate_backward; /** * @brief True if the video should seemlessly start from the beginning once the end is reached. - * + * */ atomic_bool looping; /** * @brief The desired playback state. Either paused, playing, or single-frame stepping. - * + * */ enum playpause_state playpause_state; - + /** * @brief The desired playback direction. - * + * */ enum playback_direction direction; - + /** - * @brief The actual, currently used playback rate. - * + * @brief The actual, currently used playback rate. + * */ double current_playback_rate; @@ -114,30 +125,28 @@ struct gstplayer { /** * @brief True if there's a position that apply_playback_state should seek to. - * + * */ bool has_desired_position; /** * @brief True if gstplayer should seek to the nearest keyframe instead, which is a bit faster. - * + * */ bool do_fast_seeking; /** * @brief The position, if any, that apply_playback_state should seek to. - * + * */ int64_t desired_position_ms; - bool is_forcing_sw_decoding; - bool is_currently_falling_back_to_sw_decoding; - struct notifier video_info_notifier, buffering_state_notifier, error_notifier; - + + bool is_initialized; bool has_sent_info; struct incomplete_video_info info; - + bool has_gst_info; GstVideoInfo gst_info; @@ -149,32 +158,30 @@ struct gstplayer { GstElement *pipeline, *sink; GstBus *bus; sd_event_source *busfd_events; - uint32_t drm_format; - bool has_drm_modifier; - uint64_t drm_modifier; - EGLint egl_color_space; + + bool is_live; }; #define MAX_N_PLANES 4 -#define MAX_N_EGL_DMABUF_IMAGE_ATTRIBUTES 6 + 6*MAX_N_PLANES + 1 +#define MAX_N_EGL_DMABUF_IMAGE_ATTRIBUTES 6 + 6 * MAX_N_PLANES + 1 -static inline void lock(struct gstplayer *player) { +UNUSED static inline void lock(struct gstplayer *player) { pthread_mutex_lock(&player->lock); } -static inline void unlock(struct gstplayer *player) { +UNUSED static inline void unlock(struct gstplayer *player) { pthread_mutex_unlock(&player->lock); } -static inline void trace_instant(struct gstplayer *player, const char *name) { +UNUSED static inline void trace_instant(struct gstplayer *player, const char *name) { return flutterpi_trace_event_instant(player->flutterpi, name); } -static inline void trace_begin(struct gstplayer *player, const char *name) { +UNUSED static inline void trace_begin(struct gstplayer *player, const char *name) { return flutterpi_trace_event_begin(player->flutterpi, name); } -static inline void trace_end(struct gstplayer *player, const char *name) { +UNUSED static inline void trace_end(struct gstplayer *player, const char *name) { return flutterpi_trace_event_end(player->flutterpi, name); } @@ -200,8 +207,14 @@ static void fetch_duration(struct gstplayer *player) { ok = gst_element_query_duration(player->pipeline, GST_FORMAT_TIME, &duration); if (ok == FALSE) { - LOG_ERROR("Could not fetch duration. (gst_element_query_duration)\n"); - return; + if (player->is_live) { + player->info.info.duration_ms = INT64_MAX; + player->info.has_duration = true; + return; + } else { + LOG_ERROR("Could not fetch duration. (gst_element_query_duration)\n"); + return; + } } player->info.info.duration_ms = GST_TIME_AS_MSECONDS(duration); @@ -216,7 +229,16 @@ static void fetch_seeking(struct gstplayer *player) { seeking_query = gst_query_new_seeking(GST_FORMAT_TIME); ok = gst_element_query(player->pipeline, seeking_query); if (ok == FALSE) { - return; + if (player->is_live) { + player->info.info.can_seek = false; + player->info.info.seek_begin_ms = 0; + player->info.info.seek_end_ms = 0; + player->info.has_seeking_info = true; + return; + } else { + LOG_DEBUG("Could not query seeking info. (gst_element_query)\n"); + return; + } } gst_query_parse_seeking(seeking_query, NULL, &seekable, &seek_begin, &seek_end); @@ -241,7 +263,7 @@ static void update_buffering_state(struct gstplayer *player) { ok = gst_element_query(player->pipeline, query); if (ok == FALSE) { LOG_ERROR("Could not query buffering state. (gst_element_query)\n"); - return; + goto fail_unref_query; } gst_query_parse_buffering_percent(query, &busy, &percent); @@ -251,56 +273,54 @@ static void update_buffering_state(struct gstplayer *player) { state = malloc(sizeof(*state) + n_ranges * sizeof(struct buffering_range)); if (state == NULL) { - return; + goto fail_unref_query; } for (int i = 0; i < n_ranges; i++) { ok = gst_query_parse_nth_buffering_range(query, (unsigned int) i, &start, &stop); if (ok == FALSE) { LOG_ERROR("Could not parse %dth buffering range from buffering state. (gst_query_parse_nth_buffering_range)\n", i); - return; + goto fail_free_state; } state->ranges[i].start_ms = GST_TIME_AS_MSECONDS(start); - state->ranges[i].stop_ms = GST_TIME_AS_MSECONDS(stop); + state->ranges[i].stop_ms = GST_TIME_AS_MSECONDS(stop); } gst_query_unref(query); state->percent = percent; - state->mode = (mode == GST_BUFFERING_STREAM ? kStream : - mode == GST_BUFFERING_DOWNLOAD ? kDownload : - mode == GST_BUFFERING_TIMESHIFT ? kTimeshift : - mode == GST_BUFFERING_LIVE ? kLive : (assert(0), kStream) ); + state->mode = + (mode == GST_BUFFERING_STREAM ? BUFFERING_MODE_STREAM : + mode == GST_BUFFERING_DOWNLOAD ? BUFFERING_MODE_DOWNLOAD : + mode == GST_BUFFERING_TIMESHIFT ? BUFFERING_MODE_TIMESHIFT : + mode == GST_BUFFERING_LIVE ? BUFFERING_MODE_LIVE : + (assert(0), BUFFERING_MODE_STREAM)); state->avg_in = avg_in; state->avg_out = avg_out; state->time_left_ms = buffering_left; state->n_ranges = n_ranges; notifier_notify(&player->buffering_state_notifier, state); + return; + +fail_free_state: + free(state); + +fail_unref_query: + gst_query_unref(query); } static int init(struct gstplayer *player, bool force_sw_decoders); static void maybe_deinit(struct gstplayer *player); -static void fallback_to_sw_decoding(struct gstplayer *player) { - maybe_deinit(player); - player->is_currently_falling_back_to_sw_decoding = true; - init(player, true); -} - static int apply_playback_state(struct gstplayer *player) { GstStateChangeReturn ok; GstState desired_state, current_state, pending_state; double desired_rate; int64_t position; - // if we're currently falling back to software decoding, don't do anything. - if (player->is_currently_falling_back_to_sw_decoding) { - return 0; - } - desired_state = player->playpause_state == kPlaying ? GST_STATE_PLAYING : GST_STATE_PAUSED; /* use GST_STATE_PAUSED if we're stepping */ /// Use 1.0 if we're stepping, otherwise use the stored playback rate for the current direction. @@ -322,43 +342,57 @@ static int apply_playback_state(struct gstplayer *player) { } if (player->direction == kForward) { - LOG_DEBUG("gst_element_seek(..., rate: %f, start: %"GST_TIME_FORMAT", end: %"GST_TIME_FORMAT", ...)\n", desired_rate, GST_TIME_ARGS(position), GST_TIME_ARGS(GST_CLOCK_TIME_NONE)); + LOG_DEBUG( + "gst_element_seek(..., rate: %f, start: %" GST_TIME_FORMAT ", end: %" GST_TIME_FORMAT ", ...)\n", + desired_rate, + GST_TIME_ARGS(position), + GST_TIME_ARGS(GST_CLOCK_TIME_NONE) + ); ok = gst_element_seek( GST_ELEMENT(player->pipeline), desired_rate, GST_FORMAT_TIME, - GST_SEEK_FLAG_FLUSH | (player->do_fast_seeking ? GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_NEAREST : GST_SEEK_FLAG_ACCURATE), - GST_SEEK_TYPE_SET, position, - GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE + GST_SEEK_FLAG_FLUSH | + (player->do_fast_seeking ? GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_NEAREST : GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, + position, + GST_SEEK_TYPE_SET, + GST_CLOCK_TIME_NONE ); if (ok == FALSE) { - LOG_ERROR("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", desired_rate, GST_TIME_ARGS(position)); + LOG_ERROR( + "Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", + desired_rate, + GST_TIME_ARGS(position) + ); return EIO; } } else { - LOG_DEBUG("gst_element_seek(..., rate: %f, start: %"GST_TIME_FORMAT", end: %"GST_TIME_FORMAT", ...)\n", desired_rate, GST_TIME_ARGS(0), GST_TIME_ARGS(position)); + LOG_DEBUG( + "gst_element_seek(..., rate: %f, start: %" GST_TIME_FORMAT ", end: %" GST_TIME_FORMAT ", ...)\n", + desired_rate, + GST_TIME_ARGS(0), + GST_TIME_ARGS(position) + ); ok = gst_element_seek( GST_ELEMENT(player->pipeline), desired_rate, GST_FORMAT_TIME, - GST_SEEK_FLAG_FLUSH | (player->do_fast_seeking ? GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_NEAREST : GST_SEEK_FLAG_ACCURATE), - GST_SEEK_TYPE_SET, 0, - GST_SEEK_TYPE_SET, position + GST_SEEK_FLAG_FLUSH | + (player->do_fast_seeking ? GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_NEAREST : GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, + 0, + GST_SEEK_TYPE_SET, + position ); if (ok == FALSE) { - if (player->is_forcing_sw_decoding == false) { - LOG_DEBUG("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", desired_rate, GST_TIME_ARGS(position)); - LOG_DEBUG("Falling back to software decoding to set the new playback speed / position.\n"); - player->has_desired_position = true; - player->desired_position_ms = GST_TIME_AS_MSECONDS(position); - player->fallback_position_ms = GST_TIME_AS_MSECONDS(position); - fallback_to_sw_decoding(player); - return 0; - } else { - LOG_ERROR("Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ") and player is already using software decoding.\n", desired_rate, GST_TIME_ARGS(position)); - return EIO; - } + LOG_ERROR( + "Could not set the new playback speed / playback position (speed: %f, pos: %" GST_TIME_FORMAT ").\n", + desired_rate, + GST_TIME_ARGS(position) + ); + return EIO; } } @@ -372,21 +406,31 @@ static int apply_playback_state(struct gstplayer *player) { DEBUG_TRACE_END(player, "gst_element_get_state"); if (ok == GST_STATE_CHANGE_FAILURE) { - LOG_ERROR("last gstreamer pipeline state change failed. gst_element_get_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", GST_ELEMENT_NAME(player->pipeline)); + LOG_ERROR( + "last gstreamer pipeline state change failed. gst_element_get_state(element name: %s): GST_STATE_CHANGE_FAILURE\n", + GST_ELEMENT_NAME(player->pipeline) + ); DEBUG_TRACE_END(player, "apply_playback_state"); return EIO; } - if (pending_state == GST_STATE_VOID_PENDING) { + if (pending_state == GST_STATE_VOID_PENDING) { if (current_state == desired_state) { // we're already in the desired state, and we're also not changing it // no need to do anything. - LOG_DEBUG("apply_playback_state(playing: %s): already in desired state and none pending\n", PLAYPAUSE_STATE_AS_STRING(player->playpause_state)); + LOG_DEBUG( + "apply_playback_state(playing: %s): already in desired state and none pending\n", + PLAYPAUSE_STATE_AS_STRING(player->playpause_state) + ); DEBUG_TRACE_END(player, "apply_playback_state"); return 0; } - LOG_DEBUG("apply_playback_state(playing: %s): setting state to %s\n", PLAYPAUSE_STATE_AS_STRING(player->playpause_state), gst_element_state_get_name(desired_state)); + LOG_DEBUG( + "apply_playback_state(playing: %s): setting state to %s\n", + PLAYPAUSE_STATE_AS_STRING(player->playpause_state), + gst_element_state_get_name(desired_state) + ); DEBUG_TRACE_BEGIN(player, "gst_element_set_state"); ok = gst_element_set_state(player->pipeline, desired_state); @@ -401,12 +445,16 @@ static int apply_playback_state(struct gstplayer *player) { // queue to be executed when pending async state change completes /// TODO: Implement properly - LOG_DEBUG("apply_playback_state(playing: %s): async state change in progress, setting state to %s\n", PLAYPAUSE_STATE_AS_STRING(player->playpause_state), gst_element_state_get_name(desired_state)); - + LOG_DEBUG( + "apply_playback_state(playing: %s): async state change in progress, setting state to %s\n", + PLAYPAUSE_STATE_AS_STRING(player->playpause_state), + gst_element_state_get_name(desired_state) + ); + DEBUG_TRACE_BEGIN(player, "gst_element_set_state"); ok = gst_element_set_state(player->pipeline, desired_state); DEBUG_TRACE_END(player, "gst_element_set_state"); - + if (ok == GST_STATE_CHANGE_FAILURE) { LOG_GST_SET_STATE_ERROR(player->pipeline); DEBUG_TRACE_END(player, "apply_playback_state"); @@ -428,12 +476,13 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &error, &debug_info); - fprintf(stderr, "[gstreamer video player] gstreamer error: code: %d, domain: %s, msg: %s (debug info: %s)\n", error->code, g_quark_to_string(error->domain), error->message, debug_info); - if (error->domain == GST_STREAM_ERROR && error->code == GST_STREAM_ERROR_DECODE && strcmp(error->message, "No valid frames decoded before end of stream") == 0) { - LOG_ERROR("Hardware decoder failed. Falling back to software decoding...\n"); - fallback_to_sw_decoding(player); - } - + LOG_ERROR( + "gstreamer error: code: %d, domain: %s, msg: %s (debug info: %s)\n", + error->code, + g_quark_to_string(error->domain), + error->message, + debug_info + ); g_clear_error(&error); g_free(debug_info); break; @@ -451,37 +500,38 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { g_clear_error(&error); g_free(debug_info); break; - - case GST_MESSAGE_BUFFERING: - { - GstBufferingMode mode; - int64_t buffering_left; - int percent, avg_in, avg_out; - - gst_message_parse_buffering(msg, &percent); - gst_message_parse_buffering_stats(msg, &mode, &avg_in, &avg_out, &buffering_left); - - LOG_DEBUG( - "buffering, src: %s, percent: %d, mode: %s, avg in: %d B/s, avg out: %d B/s, %" GST_TIME_FORMAT "\n", - GST_MESSAGE_SRC_NAME(msg), - percent, - mode == GST_BUFFERING_STREAM ? "stream" : - mode == GST_BUFFERING_DOWNLOAD ? "download" : - mode == GST_BUFFERING_TIMESHIFT ? "timeshift" : - mode == GST_BUFFERING_LIVE ? "live" : "?", - avg_in, avg_out, - GST_TIME_ARGS(buffering_left * GST_MSECOND) - ); - /// TODO: GST_MESSAGE_BUFFERING is only emitted when we actually need to wait on some buffering till we can resume the playback. - /// However, the info we send to the callback also contains information on the buffered video ranges. - /// That information is constantly changing, but we only notify the player about it when we actively wait for the buffer to be filled. - DEBUG_TRACE_BEGIN(player, "update_buffering_state"); - update_buffering_state(player); - DEBUG_TRACE_END(player, "update_buffering_state"); + case GST_MESSAGE_BUFFERING: { + GstBufferingMode mode; + int64_t buffering_left; + int percent, avg_in, avg_out; + + gst_message_parse_buffering(msg, &percent); + gst_message_parse_buffering_stats(msg, &mode, &avg_in, &avg_out, &buffering_left); - break; - }; + LOG_DEBUG( + "buffering, src: %s, percent: %d, mode: %s, avg in: %d B/s, avg out: %d B/s, %" GST_TIME_FORMAT "\n", + GST_MESSAGE_SRC_NAME(msg), + percent, + mode == GST_BUFFERING_STREAM ? "stream" : + mode == GST_BUFFERING_DOWNLOAD ? "download" : + mode == GST_BUFFERING_TIMESHIFT ? "timeshift" : + mode == GST_BUFFERING_LIVE ? "live" : + "?", + avg_in, + avg_out, + GST_TIME_ARGS(buffering_left * GST_MSECOND) + ); + + /// TODO: GST_MESSAGE_BUFFERING is only emitted when we actually need to wait on some buffering till we can resume the playback. + /// However, the info we send to the callback also contains information on the buffered video ranges. + /// That information is constantly changing, but we only notify the player about it when we actively wait for the buffer to be filled. + DEBUG_TRACE_BEGIN(player, "update_buffering_state"); + update_buffering_state(player); + DEBUG_TRACE_END(player, "update_buffering_state"); + + break; + }; case GST_MESSAGE_STATE_CHANGED: gst_message_parse_state_changed(msg, &old, ¤t, &pending); @@ -506,13 +556,8 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { } } break; - - case GST_MESSAGE_ASYNC_DONE: - if (GST_MESSAGE_SRC(msg) == GST_OBJECT(player->pipeline) && player->is_currently_falling_back_to_sw_decoding) { - player->is_currently_falling_back_to_sw_decoding = false; - apply_playback_state(player); - } - break; + + case GST_MESSAGE_ASYNC_DONE: break; case GST_MESSAGE_LATENCY: LOG_DEBUG("gstreamer: redistributing latency\n"); @@ -521,9 +566,7 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { DEBUG_TRACE_END(player, "gst_bin_recalculate_latency"); break; - case GST_MESSAGE_EOS: - LOG_DEBUG("end of stream, src: %s\n", GST_MESSAGE_SRC_NAME(msg)); - break; + case GST_MESSAGE_EOS: LOG_DEBUG("end of stream, src: %s\n", GST_MESSAGE_SRC_NAME(msg)); break; case GST_MESSAGE_REQUEST_STATE: gst_message_parse_request_state(msg, &requested); @@ -548,8 +591,10 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { player->current_playback_rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, - GST_SEEK_TYPE_SET, 0, - GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE + GST_SEEK_TYPE_SET, + 0, + GST_SEEK_TYPE_SET, + GST_CLOCK_TIME_NONE ); DEBUG_TRACE_END(player, "gst_element_seek"); @@ -557,9 +602,7 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { } break; - default: - LOG_DEBUG("gstreamer message: %s, src: %s\n", GST_MESSAGE_TYPE_NAME(msg), GST_MESSAGE_SRC_NAME(msg)); - break; + default: LOG_DEBUG("gstreamer message: %s, src: %s\n", GST_MESSAGE_TYPE_NAME(msg), GST_MESSAGE_SRC_NAME(msg)); break; } DEBUG_TRACE_END(player, "on_bus_message"); return; @@ -572,9 +615,9 @@ static int on_bus_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *u (void) s; (void) fd; (void) revents; - + player = userdata; - + DEBUG_TRACE_BEGIN(player, "on_bus_fd_ready"); msg = gst_bus_pop(player->bus); @@ -594,7 +637,11 @@ static GstPadProbeReturn on_query_appsink(GstPad *pad, GstPadProbeInfo *info, vo (void) pad; (void) userdata; - query = info->data; + query = gst_pad_probe_info_get_query(info); + if (query == NULL) { + LOG_DEBUG("Couldn't get query from pad probe info.\n"); + return GST_PAD_PROBE_OK; + } if (GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION) { return GST_PAD_PROBE_OK; @@ -623,7 +670,6 @@ static void on_element_added(GstBin *bin, GstElement *element, void *userdata) { static GstPadProbeReturn on_probe_pad(GstPad *pad, GstPadProbeInfo *info, void *userdata) { struct gstplayer *player; - GstVideoInfo vinfo; GstEvent *event; GstCaps *caps; gboolean ok; @@ -643,59 +689,25 @@ static GstPadProbeReturn on_probe_pad(GstPad *pad, GstPadProbeInfo *info, void * return GST_PAD_PROBE_OK; } - ok = gst_video_info_from_caps(&vinfo, caps); + ok = gst_video_info_from_caps(&player->gst_info, caps); if (!ok) { LOG_ERROR("gstreamer: caps event with invalid video caps\n"); return GST_PAD_PROBE_OK; } - switch (GST_VIDEO_INFO_FORMAT(&vinfo)) { - case GST_VIDEO_FORMAT_Y42B: - player->drm_format = DRM_FORMAT_YUV422; - break; - case GST_VIDEO_FORMAT_YV12: - player->drm_format = DRM_FORMAT_YVU420; - break; - case GST_VIDEO_FORMAT_I420: - player->drm_format = DRM_FORMAT_YUV420; - break; - case GST_VIDEO_FORMAT_NV12: - player->drm_format = DRM_FORMAT_NV12; - break; - case GST_VIDEO_FORMAT_NV21: - player->drm_format = DRM_FORMAT_NV21; - break; - case GST_VIDEO_FORMAT_YUY2: - player->drm_format = DRM_FORMAT_YUYV; - break; - default: - LOG_ERROR("unsupported video format: %s\n", GST_VIDEO_INFO_NAME(&vinfo)); - player->drm_format = 0; - break; - } - - const GstVideoColorimetry *color = &GST_VIDEO_INFO_COLORIMETRY(&vinfo); - - if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT601)) { - player->egl_color_space = EGL_ITU_REC601_EXT; - } else if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT709)) { - player->egl_color_space = EGL_ITU_REC709_EXT; - } else if (gst_video_colorimetry_matches(color, GST_VIDEO_COLORIMETRY_BT2020)) { - player->egl_color_space = EGL_ITU_REC2020_EXT; - } else { - LOG_ERROR("unsupported video colorimetry: %s\n", gst_video_colorimetry_to_string(color)); - player->egl_color_space = EGL_NONE; - } - - - memcpy(&player->gst_info, &vinfo, sizeof vinfo); player->has_gst_info = true; - LOG_DEBUG("on_probe_pad, fps: %f, res: % 4d x % 4d\n", (double) vinfo.fps_n / vinfo.fps_d, vinfo.width, vinfo.height); + LOG_DEBUG( + "on_probe_pad, fps: %f, res: % 4d x % 4d, format: %s\n", + (double) GST_VIDEO_INFO_FPS_N(&player->gst_info) / GST_VIDEO_INFO_FPS_D(&player->gst_info), + GST_VIDEO_INFO_WIDTH(&player->gst_info), + GST_VIDEO_INFO_HEIGHT(&player->gst_info), + gst_video_format_to_string(player->gst_info.finfo->format) + ); - player->info.info.width = vinfo.width; - player->info.info.height = vinfo.height; - player->info.info.fps = (double) vinfo.fps_n / vinfo.fps_d; + player->info.info.width = GST_VIDEO_INFO_WIDTH(&player->gst_info); + player->info.info.height = GST_VIDEO_INFO_HEIGHT(&player->gst_info); + player->info.info.fps = (double) GST_VIDEO_INFO_FPS_N(&player->gst_info) / GST_VIDEO_INFO_FPS_D(&player->gst_info); player->info.has_resolution = true; player->info.has_fps = true; maybe_send_info(player); @@ -708,8 +720,8 @@ static void on_destroy_texture_frame(const struct texture_frame *texture_frame, (void) texture_frame; - DEBUG_ASSERT_NOT_NULL(texture_frame); - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(texture_frame); + ASSERT_NOT_NULL(userdata); frame = userdata; @@ -719,8 +731,8 @@ static void on_destroy_texture_frame(const struct texture_frame *texture_frame, static void on_appsink_eos(GstAppSink *appsink, void *userdata) { gboolean ok; - DEBUG_ASSERT_NOT_NULL(appsink); - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(appsink); + ASSERT_NOT_NULL(userdata); (void) userdata; @@ -732,10 +744,7 @@ static void on_appsink_eos(GstAppSink *appsink, void *userdata) { // @ref on_bus_message. ok = gst_element_post_message( GST_ELEMENT(appsink), - gst_message_new_application( - GST_OBJECT(appsink), - gst_structure_new_empty("appsink-eos") - ) + gst_message_new_application(GST_OBJECT(appsink), gst_structure_new_empty("appsink-eos")) ); if (ok == FALSE) { LOG_ERROR("Could not post appsink end-of-stream event to the message bus.\n"); @@ -747,8 +756,8 @@ static GstFlowReturn on_appsink_new_preroll(GstAppSink *appsink, void *userdata) struct gstplayer *player; GstSample *sample; - DEBUG_ASSERT_NOT_NULL(appsink); - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(appsink); + ASSERT_NOT_NULL(userdata); player = userdata; @@ -758,22 +767,19 @@ static GstFlowReturn on_appsink_new_preroll(GstAppSink *appsink, void *userdata) return GST_FLOW_ERROR; } - frame = frame_new( - player->frame_interface, - &(struct frame_info) { - .drm_format = player->drm_format, - .egl_color_space = player->egl_color_space, - .gst_info = &player->gst_info - }, - sample - ); + frame = frame_new(player->frame_interface, sample, player->has_gst_info ? &player->gst_info : NULL); + + gst_sample_unref(sample); if (frame != NULL) { - texture_push_frame(player->texture, &(struct texture_frame) { - .gl = *frame_get_gl_frame(frame), - .destroy = on_destroy_texture_frame, - .userdata = frame, - }); + texture_push_frame( + player->texture, + &(struct texture_frame){ + .gl = *frame_get_gl_frame(frame), + .destroy = on_destroy_texture_frame, + .userdata = frame, + } + ); } return GST_FLOW_OK; @@ -784,8 +790,8 @@ static GstFlowReturn on_appsink_new_sample(GstAppSink *appsink, void *userdata) struct gstplayer *player; GstSample *sample; - DEBUG_ASSERT_NOT_NULL(appsink); - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(appsink); + ASSERT_NOT_NULL(userdata); player = userdata; @@ -795,22 +801,19 @@ static GstFlowReturn on_appsink_new_sample(GstAppSink *appsink, void *userdata) return GST_FLOW_ERROR; } - frame = frame_new( - player->frame_interface, - &(struct frame_info) { - .drm_format = player->drm_format, - .egl_color_space = player->egl_color_space, - .gst_info = &player->gst_info - }, - sample - ); + frame = frame_new(player->frame_interface, sample, player->has_gst_info ? &player->gst_info : NULL); + + gst_sample_unref(sample); if (frame != NULL) { - texture_push_frame(player->texture, &(struct texture_frame) { - .gl = *frame_get_gl_frame(frame), - .destroy = on_destroy_texture_frame, - .userdata = frame, - }); + texture_push_frame( + player->texture, + &(struct texture_frame){ + .gl = *frame_get_gl_frame(frame), + .destroy = on_destroy_texture_frame, + .userdata = frame, + } + ); } return GST_FLOW_OK; @@ -820,14 +823,25 @@ static void on_appsink_cbs_destroy(void *userdata) { struct gstplayer *player; LOG_DEBUG("on_appsink_cbs_destroy()\n"); - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(userdata); player = userdata; (void) player; } +void on_source_setup(GstElement *bin, GstElement *source, gpointer userdata) { + (void) bin; + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != NULL) { + g_object_set(source, "extra-headers", (GstStructure *) userdata, NULL); + } else { + LOG_ERROR("Failed to set custom HTTP headers because gstreamer source element has no 'extra-headers' property.\n"); + } +} + static int init(struct gstplayer *player, bool force_sw_decoders) { + GstStateChangeReturn state_change_return; sd_event_source *busfd_event_source; GstElement *pipeline, *sink, *src; GstBus *bus; @@ -835,8 +849,15 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { GPollFD fd; GError *error = NULL; int ok; - - static const char *pipeline_descr = "uridecodebin name=\"src\" ! video/x-raw ! appsink sync=true name=\"sink\""; + + static const char *default_pipeline_descr = "uridecodebin name=\"src\" ! video/x-raw ! appsink sync=true name=\"sink\""; + + const char *pipeline_descr; + if (player->pipeline_description != NULL) { + pipeline_descr = player->pipeline_description; + } else { + pipeline_descr = default_pipeline_descr; + } pipeline = gst_parse_launch(pipeline_descr, &error); if (pipeline == NULL) { @@ -857,111 +878,114 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { ok = EINVAL; goto fail_unref_sink; } - - gst_pad_add_probe( - pad, - GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, - on_query_appsink, - player, - NULL - ); + + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, on_query_appsink, player, NULL); src = gst_bin_get_by_name(GST_BIN(pipeline), "src"); - if (src == NULL) { - LOG_ERROR("Couldn't find uridecodebin in pipeline bin.\n"); - ok = EINVAL; - goto fail_unref_sink; + + if (player->video_uri != NULL) { + if (src != NULL) { + g_object_set(G_OBJECT(src), "uri", player->video_uri, NULL); + } else { + LOG_ERROR("Couldn't find \"src\" element to configure Video URI.\n"); + } + } + + if (force_sw_decoders) { + if (src != NULL) { + g_object_set(G_OBJECT(src), "force-sw-decoders", force_sw_decoders, NULL); + } else { + LOG_ERROR("Couldn't find \"src\" element to force sw decoding.\n"); + } } - g_object_set(G_OBJECT(src), "uri", player->video_uri, "force-sw-decoders", force_sw_decoders, NULL); + if (player->headers != NULL) { + if (src != NULL) { + g_signal_connect(G_OBJECT(src), "source-setup", G_CALLBACK(on_source_setup), player->headers); + } else { + LOG_ERROR("Couldn't find \"src\" element to configure additional HTTP headers.\n"); + } + } gst_base_sink_set_max_lateness(GST_BASE_SINK(sink), 20 * GST_MSECOND); gst_base_sink_set_qos_enabled(GST_BASE_SINK(sink), TRUE); + gst_base_sink_set_sync(GST_BASE_SINK(sink), TRUE); gst_app_sink_set_max_buffers(GST_APP_SINK(sink), 2); gst_app_sink_set_emit_signals(GST_APP_SINK(sink), TRUE); gst_app_sink_set_drop(GST_APP_SINK(sink), FALSE); + // configure our caps + // we only accept video formats that we can actually upload to EGL + GstCaps *caps = gst_caps_new_empty(); + for_each_format_in_frame_interface(i, format, player->frame_interface) { + GstVideoFormat gst_format = gst_video_format_from_drm_format(format->format); + if (gst_format == GST_VIDEO_FORMAT_UNKNOWN) { + continue; + } + + gst_caps_append(caps, gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, gst_video_format_to_string(gst_format), NULL)); + } + gst_app_sink_set_caps(GST_APP_SINK(sink), caps); + gst_caps_unref(caps); + gst_app_sink_set_callbacks( GST_APP_SINK(sink), - &(GstAppSinkCallbacks) { - .eos = on_appsink_eos, - .new_preroll = on_appsink_new_preroll, - .new_sample = on_appsink_new_sample, - ._gst_reserved = {0} - }, + &(GstAppSinkCallbacks + ){ .eos = on_appsink_eos, .new_preroll = on_appsink_new_preroll, .new_sample = on_appsink_new_sample, ._gst_reserved = { 0 } }, player, on_appsink_cbs_destroy ); - gst_pad_add_probe( - pad, - GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - on_probe_pad, - player, - NULL - ); + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, on_probe_pad, player, NULL); - g_signal_connect(src, "element-added", G_CALLBACK(on_element_added), player); + /// FIXME: Make this work for custom pipelines as well. + if (src != NULL) { + g_signal_connect(src, "element-added", G_CALLBACK(on_element_added), player); + } else { + LOG_DEBUG("Couldn't find \"src\" element to setup v4l2 'capture-io-mode' to 'dmabuf'.\n"); + } + + if (src != NULL) { + gst_object_unref(src); + src = NULL; + } bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); gst_bus_get_pollfd(bus, &fd); - flutterpi_sd_event_add_io( - &busfd_event_source, - fd.fd, - EPOLLIN, - on_bus_fd_ready, - player - ); + flutterpi_sd_event_add_io(&busfd_event_source, fd.fd, EPOLLIN, on_bus_fd_ready, player); LOG_DEBUG("Setting state to paused...\n"); - gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PAUSED); + state_change_return = gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PAUSED); + if (state_change_return == GST_STATE_CHANGE_NO_PREROLL) { + LOG_DEBUG("Is Live!\n"); + player->is_live = true; + } else { + LOG_DEBUG("Not live!\n"); + player->is_live = false; + } player->sink = sink; /// FIXME: Not sure we need this here. pipeline is floating after gst_parse_launch, which /// means we should take a reference, but the examples don't increase the refcount. - player->pipeline = pipeline; //gst_object_ref(pipeline); + player->pipeline = pipeline; //gst_object_ref(pipeline); player->bus = bus; player->busfd_events = busfd_event_source; - player->is_forcing_sw_decoding = force_sw_decoders; - gst_object_unref(src); gst_object_unref(pad); return 0; - fail_unref_sink: +fail_unref_sink: gst_object_unref(sink); - fail_unref_pipeline: +fail_unref_pipeline: gst_object_unref(pipeline); return ok; } static void maybe_deinit(struct gstplayer *player) { - struct my_gst_object { - GInitiallyUnowned object; - - /*< public >*/ /* with LOCK */ - GMutex lock; /* object LOCK */ - gchar *name; /* object name */ - GstObject *parent; /* this object's parent, weak ref */ - guint32 flags; - - /*< private >*/ - GList *control_bindings; /* List of GstControlBinding */ - guint64 control_rate; - guint64 last_sync; - - gpointer _gst_reserved; - }; - - struct my_gst_object *sink = (struct my_gst_object*) player->sink, *bus = (struct my_gst_object*) player->bus, *pipeline = (struct my_gst_object*) player->pipeline; - (void) sink; - (void) bus; - (void) pipeline; - if (player->busfd_events != NULL) { sd_event_source_unrefp(&player->busfd_events); } @@ -982,48 +1006,72 @@ static void maybe_deinit(struct gstplayer *player) { DEFINE_LOCK_OPS(gstplayer, lock) -static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char *uri, void *userdata) { +static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char *uri, const char *pipeline_descr, void *userdata) { struct frame_interface *frame_interface; struct gstplayer *player; struct texture *texture; GstStructure *gst_headers; int64_t texture_id; - char *uri_owned; + char *uri_owned, *pipeline_descr_owned; int ok; + ASSERT_NOT_NULL(flutterpi); + assert((uri != NULL) != (pipeline_descr != NULL)); + player = malloc(sizeof *player); - if (player == NULL) return NULL; + if (player == NULL) + return NULL; texture = flutterpi_create_texture(flutterpi); - if (texture == NULL) goto fail_free_player; - - frame_interface = frame_interface_new(flutterpi); - if (frame_interface == NULL) goto fail_destroy_texture; + if (texture == NULL) + goto fail_free_player; + + frame_interface = frame_interface_new(flutterpi_get_gl_renderer(flutterpi)); + if (frame_interface == NULL) + goto fail_destroy_texture; texture_id = texture_get_id(texture); - uri_owned = strdup(uri); - if (uri_owned == NULL) goto fail_destroy_frame_interface; + if (uri != NULL) { + uri_owned = strdup(uri); + if (uri_owned == NULL) + goto fail_destroy_frame_interface; + } else { + uri_owned = NULL; + } + + if (pipeline_descr != NULL) { + pipeline_descr_owned = strdup(pipeline_descr); + if (pipeline_descr_owned == NULL) + goto fail_destroy_frame_interface; + } else { + pipeline_descr_owned = NULL; + } gst_headers = gst_structure_new_empty("http-headers"); ok = pthread_mutex_init(&player->lock, NULL); - if (ok != 0) goto fail_free_gst_headers; + if (ok != 0) + goto fail_free_gst_headers; ok = value_notifier_init(&player->video_info_notifier, NULL, free /* free(NULL) is a no-op, I checked */); - if (ok != 0) goto fail_destroy_mutex; - + if (ok != 0) + goto fail_destroy_mutex; + ok = value_notifier_init(&player->buffering_state_notifier, NULL, free); - if (ok != 0) goto fail_deinit_video_info_notifier; + if (ok != 0) + goto fail_deinit_video_info_notifier; ok = change_notifier_init(&player->error_notifier); - if (ok != 0) goto fail_deinit_buffering_state_notifier; + if (ok != 0) + goto fail_deinit_buffering_state_notifier; player->flutterpi = flutterpi; player->userdata = userdata; player->video_uri = uri_owned; + player->pipeline_description = pipeline_descr_owned; player->headers = gst_headers; - player->playback_rate_forward = 1.0; + player->playback_rate_forward = 1.0; player->playback_rate_backward = 1.0; player->looping = false; player->playpause_state = kPaused; @@ -1032,8 +1080,6 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->fallback_position_ms = 0; player->has_desired_position = false; player->desired_position_ms = 0; - player->is_forcing_sw_decoding = false; - player->is_currently_falling_back_to_sw_decoding = false; player->has_sent_info = false; player->info.has_resolution = false; player->info.has_fps = false; @@ -1048,85 +1094,72 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * player->sink = NULL; player->bus = NULL; player->busfd_events = NULL; - player->drm_format = 0; + player->is_live = false; return player; //fail_deinit_error_notifier: //notifier_deinit(&player->error_notifier); - fail_deinit_buffering_state_notifier: +fail_deinit_buffering_state_notifier: notifier_deinit(&player->buffering_state_notifier); - - fail_deinit_video_info_notifier: + +fail_deinit_video_info_notifier: notifier_deinit(&player->video_info_notifier); - fail_destroy_mutex: +fail_destroy_mutex: pthread_mutex_destroy(&player->lock); - fail_free_gst_headers: +fail_free_gst_headers: gst_structure_free(gst_headers); free(uri_owned); - fail_destroy_frame_interface: +fail_destroy_frame_interface: frame_interface_unref(frame_interface); - fail_destroy_texture: +fail_destroy_texture: texture_destroy(texture); - fail_free_player: +fail_free_player: free(player); return NULL; } -struct gstplayer *gstplayer_new_from_asset( - struct flutterpi *flutterpi, - const char *asset_path, - const char *package_name, - void *userdata -) { +struct gstplayer *gstplayer_new_from_asset(struct flutterpi *flutterpi, const char *asset_path, const char *package_name, void *userdata) { struct gstplayer *player; char *uri; + int ok; (void) package_name; - asprintf(&uri, "file://%s/%s", flutterpi_get_asset_bundle_path(flutterpi), asset_path); - if (uri == NULL) { + ok = asprintf(&uri, "file://%s/%s", flutterpi_get_asset_bundle_path(flutterpi), asset_path); + if (ok < 0) { return NULL; } - player = gstplayer_new(flutterpi, uri, userdata); + player = gstplayer_new(flutterpi, uri, NULL, userdata); free(uri); return player; } -struct gstplayer *gstplayer_new_from_network( - struct flutterpi *flutterpi, - const char *uri, - enum format_hint format_hint, - void *userdata -) { +struct gstplayer *gstplayer_new_from_network(struct flutterpi *flutterpi, const char *uri, enum format_hint format_hint, void *userdata) { (void) format_hint; - return gstplayer_new(flutterpi, uri, userdata); + return gstplayer_new(flutterpi, uri, NULL, userdata); } -struct gstplayer *gstplayer_new_from_file( - struct flutterpi *flutterpi, - const char *uri, - void *userdata -) { - return gstplayer_new(flutterpi, uri, userdata); +struct gstplayer *gstplayer_new_from_file(struct flutterpi *flutterpi, const char *uri, void *userdata) { + return gstplayer_new(flutterpi, uri, NULL, userdata); } -struct gstplayer *gstplayer_new_from_content_uri( - struct flutterpi *flutterpi, - const char *uri, - void *userdata -) { - return gstplayer_new(flutterpi, uri, userdata); -} +struct gstplayer *gstplayer_new_from_content_uri(struct flutterpi *flutterpi, const char *uri, void *userdata) { + return gstplayer_new(flutterpi, uri, NULL, userdata); +} + +struct gstplayer *gstplayer_new_from_pipeline(struct flutterpi *flutterpi, const char *pipeline, void *userdata) { + return gstplayer_new(flutterpi, NULL, pipeline, userdata); +} void gstplayer_destroy(struct gstplayer *player) { LOG_DEBUG("gstplayer_destroy(%p)\n", player); @@ -1135,8 +1168,15 @@ void gstplayer_destroy(struct gstplayer *player) { notifier_deinit(&player->error_notifier); maybe_deinit(player); pthread_mutex_destroy(&player->lock); - if (player->headers != NULL) gst_structure_free(player->headers); - free(player->video_uri); + if (player->headers != NULL) { + gst_structure_free(player->headers); + } + if (player->video_uri != NULL) { + free(player->video_uri); + } + if (player->pipeline_description != NULL) { + free(player->pipeline_description); + } frame_interface_unref(player->frame_interface); texture_destroy(player->texture); free(player); @@ -1179,7 +1219,7 @@ int gstplayer_pause(struct gstplayer *player) { } int gstplayer_set_looping(struct gstplayer *player, bool looping) { - LOG_DEBUG("gstplayer_set_looping(%s)\n", looping? "true" : "false"); + LOG_DEBUG("gstplayer_set_looping(%s)\n", looping ? "true" : "false"); player->looping = looping; return 0; } @@ -1197,13 +1237,7 @@ int64_t gstplayer_get_position(struct gstplayer *player) { gboolean ok; int64_t position; - // If we're currently falling back to software decoding, - // report the position we'll make gstreamer seek to afterwards. - if (player->is_currently_falling_back_to_sw_decoding) { - return player->desired_position_ms; - } - - GstStateChangeReturn statechange = gst_element_get_state(GST_ELEMENT(player->pipeline), ¤t, &pending, 0); + GstStateChangeReturn statechange = gst_element_get_state(GST_ELEMENT(player->pipeline), ¤t, &pending, 0); if (statechange == GST_STATE_CHANGE_FAILURE) { LOG_GST_GET_STATE_ERROR(player->pipeline); return -1; @@ -1239,7 +1273,7 @@ int gstplayer_seek_to(struct gstplayer *player, int64_t position, bool nearest_k int gstplayer_set_playback_speed(struct gstplayer *player, double playback_speed) { LOG_DEBUG("gstplayer_set_playback_speed(%f)\n", playback_speed); - DEBUG_ASSERT_MSG(playback_speed > 0, "playback speed must be > 0."); + ASSERT_MSG(playback_speed > 0, "playback speed must be > 0."); player->playback_rate_forward = playback_speed; return apply_playback_state(player); } @@ -1248,7 +1282,7 @@ int gstplayer_step_forward(struct gstplayer *player) { gboolean gst_ok; int ok; - DEBUG_ASSERT_NOT_NULL(player); + ASSERT_NOT_NULL(player); player->playpause_state = kStepping; player->direction = kForward; @@ -1257,21 +1291,10 @@ int gstplayer_step_forward(struct gstplayer *player) { return ok; } - if (player->is_currently_falling_back_to_sw_decoding == false) { - gst_ok = gst_element_send_event( - player->pipeline, - gst_event_new_step( - GST_FORMAT_BUFFERS, - 1, - 1, - TRUE, - FALSE - ) - ); - if (gst_ok == FALSE) { - LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); - return EIO; - } + gst_ok = gst_element_send_event(player->pipeline, gst_event_new_step(GST_FORMAT_BUFFERS, 1, 1, TRUE, FALSE)); + if (gst_ok == FALSE) { + LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); + return EIO; } return 0; } @@ -1280,7 +1303,7 @@ int gstplayer_step_backward(struct gstplayer *player) { gboolean gst_ok; int ok; - DEBUG_ASSERT_NOT_NULL(player); + ASSERT_NOT_NULL(player); player->playpause_state = kStepping; player->direction = kBackward; @@ -1289,21 +1312,10 @@ int gstplayer_step_backward(struct gstplayer *player) { return ok; } - if (player->is_currently_falling_back_to_sw_decoding == false) { - gst_ok = gst_element_send_event( - player->pipeline, - gst_event_new_step( - GST_FORMAT_BUFFERS, - 1, - 1, - TRUE, - FALSE - ) - ); - if (gst_ok == FALSE) { - LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); - return EIO; - } + gst_ok = gst_element_send_event(player->pipeline, gst_event_new_step(GST_FORMAT_BUFFERS, 1, 1, TRUE, FALSE)); + if (gst_ok == FALSE) { + LOG_ERROR("Could not send frame-step event to pipeline. (gst_element_send_event)\n"); + return EIO; } return 0; diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index b2830d98..b4b3df95 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -1,34 +1,34 @@ #define _GNU_SOURCE -#include +#include #include -#include +#include #include -#include + +#include #include #include -#include -#include -#include -#include -#include -#include -#include - -FILE_DESCR("gstreamer video_player plugin") - -enum data_source_type { - kDataSourceTypeAsset, - kDataSourceTypeNetwork, - kDataSourceTypeFile, - kDataSourceTypeContentUri -}; +#include "flutter-pi.h" +#include "notifier_listener.h" +#include "platformchannel.h" +#include "pluginregistry.h" +#include "plugins/gstreamer_video_player.h" +#include "texture_registry.h" +#include "util/collection.h" +#include "util/list.h" +#include "util/logging.h" + +enum data_source_type { kDataSourceTypeAsset, kDataSourceTypeNetwork, kDataSourceTypeFile, kDataSourceTypeContentUri }; struct gstplayer_meta { + struct list_head entry; + + struct gstplayer *player; + char *event_channel_name; - + // We have a listener to the video player event channel. bool has_listener; @@ -47,53 +47,77 @@ struct gstplayer_meta { }; static struct plugin { + pthread_mutex_t lock; + struct flutterpi *flutterpi; bool initialized; - struct concurrent_pointer_set players; + struct list_head players; } plugin; +DEFINE_LOCK_OPS(plugin, lock); + /// Add a player instance to the player collection. -static int add_player(struct gstplayer *player) { - return cpset_put(&plugin.players, player); +static void add_player(struct gstplayer_meta *meta) { + plugin_lock(&plugin); + + list_add(&meta->entry, &plugin.players); + + plugin_unlock(&plugin); } /// Get a player instance by its id. static struct gstplayer *get_player_by_texture_id(int64_t texture_id) { - struct gstplayer *player; - - cpset_lock(&plugin.players); - for_each_pointer_in_cpset(&plugin.players, player) { - if (gstplayer_get_texture_id(player) == texture_id) { - cpset_unlock(&plugin.players); - return player; + plugin_lock(&plugin); + + list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { + if (gstplayer_get_texture_id(meta->player) == texture_id) { + plugin_unlock(&plugin); + return meta->player; } } - cpset_unlock(&plugin.players); + plugin_unlock(&plugin); return NULL; } /// Get a player instance by its event channel name. static struct gstplayer *get_player_by_evch(const char *const event_channel_name) { - struct gstplayer_meta *meta; - struct gstplayer *player; - - cpset_lock(&plugin.players); - for_each_pointer_in_cpset(&plugin.players, player) { - meta = gstplayer_get_userdata_locked(player); - if (strcmp(meta->event_channel_name, event_channel_name) == 0) { - cpset_unlock(&plugin.players); - return player; + plugin_lock(&plugin); + + list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { + if (streq(meta->event_channel_name, event_channel_name)) { + plugin_unlock(&plugin); + return meta->player; } } - cpset_unlock(&plugin.players); + plugin_unlock(&plugin); return NULL; } -/// Remove a player instance from the player collection. -static int remove_player(struct gstplayer *player) { - return cpset_remove(&plugin.players, player); +/** + * @brief Remove a player instance from the player list. + * + * Assumes the plugin struct is not locked. + * + */ +static void remove_player(struct gstplayer_meta *meta) { + plugin_lock(&plugin); + + list_del(&meta->entry); + + plugin_unlock(&plugin); +} + +/** + * @brief Remove a player instance from the player list. + * + * Assumes the plugin struct is locked. + * + */ +static void remove_player_locked(struct gstplayer_meta *meta) { + ASSERT_MUTEX_LOCKED(plugin.lock); + list_del(&meta->entry); } static struct gstplayer_meta *get_meta(struct gstplayer *player) { @@ -103,33 +127,24 @@ static struct gstplayer_meta *get_meta(struct gstplayer *player) { /// Get the player id from the given arg, which is a kStdMap. /// (*texture_id_out = arg['playerId']) /// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_texture_id_from_map_arg( - struct std_value *arg, - int64_t *texture_id_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int +get_texture_id_from_map_arg(struct std_value *arg, int64_t *texture_id_out, FlutterPlatformMessageResponseHandle *responsehandle) { struct std_value *id; int ok; - if (arg->type != kStdMap) { - ok = platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg` to be a Map, but was: ", - arg - ); - if (ok != 0) return ok; + if (!STDVALUE_IS_MAP(*arg)) { + ok = platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg` to be a Map, but was: ", arg); + if (ok != 0) + return ok; return EINVAL; } id = stdmap_get_str(arg, "textureId"); if (id == NULL || !STDVALUE_IS_INT(*id)) { - ok = platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['textureId']` to be an integer, but was: ", - id - ); - if (ok != 0) return ok; + ok = platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['textureId']` to be an integer, but was: ", id); + if (ok != 0) + return ok; return EINVAL; } @@ -142,11 +157,8 @@ static int get_texture_id_from_map_arg( /// Get the player associated with the id in the given arg, which is a kStdMap. /// (*player_out = get_player_by_texture_id(get_texture_id_from_map_arg(arg))) /// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_player_from_map_arg( - struct std_value *arg, - struct gstplayer **player_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int +get_player_from_map_arg(struct std_value *arg, struct gstplayer **player_out, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; int64_t texture_id; int ok; @@ -159,61 +171,35 @@ static int get_player_from_map_arg( player = get_player_by_texture_id(texture_id); if (player == NULL) { - cpset_lock(&plugin.players); + plugin_lock(&plugin); - int n_texture_ids = cpset_get_count_pointers_locked(&plugin.players); + int n_texture_ids = list_length(&plugin.players); int64_t *texture_ids = alloca(sizeof(int64_t) * n_texture_ids); int64_t *texture_ids_cursor = texture_ids; - for_each_pointer_in_cpset(&plugin.players, player) { - *texture_ids_cursor++ = gstplayer_get_texture_id(player); + list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { + *texture_ids_cursor++ = gstplayer_get_texture_id(meta->player); } - - cpset_unlock(&plugin.players); + + plugin_unlock(&plugin); ok = platch_respond_illegal_arg_ext_pigeon( responsehandle, - "Expected `arg['textureId']` to be a valid texture id.", + "Expected `arg['textureId']` to be a valid texture id.", &STDMAP2( - STDSTRING("textureId"), STDINT64(texture_id), - STDSTRING("registeredTextureIds"), ((struct std_value) { - .type = kStdInt64Array, - .size = n_texture_ids, - .int64array = texture_ids - }) + STDSTRING("textureId"), + STDINT64(texture_id), + STDSTRING("registeredTextureIds"), + ((struct std_value){ .type = kStdInt64Array, .size = n_texture_ids, .int64array = texture_ids }) ) ); - if (ok != 0) return ok; + if (ok != 0) + return ok; return EINVAL; } *player_out = player; - - return 0; -} - -static int get_player_and_meta_from_map_arg( - struct std_value *arg, - struct gstplayer **player_out, - struct gstplayer_meta **meta_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct gstplayer *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) { - return ok; - } - - if (player_out) { - *player_out = player; - } - - if (meta_out) { - *meta_out = (struct gstplayer_meta*) gstplayer_get_userdata_locked(player); - } return 0; } @@ -245,33 +231,36 @@ static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { ); } +static int respond_init_failed_v2(FlutterPlatformMessageResponseHandle *handle) { + return platch_respond_error_std( + handle, + "couldnotinit", + "gstreamer video player plugin failed to initialize gstreamer. See flutter-pi log for details.", + NULL + ); +} static int send_initialized_event(struct gstplayer_meta *meta, bool is_stream, int width, int height, int64_t duration_ms) { return platch_send_success_event_std( meta->event_channel_name, &STDMAP4( - STDSTRING("event"), STDSTRING("initialized"), - STDSTRING("duration"), STDINT64(is_stream? INT64_MAX : duration_ms), - STDSTRING("width"), STDINT32(width), - STDSTRING("height"), STDINT32(height) + STDSTRING("event"), + STDSTRING("initialized"), + STDSTRING("duration"), + STDINT64(is_stream ? INT64_MAX : duration_ms), + STDSTRING("width"), + STDINT32(width), + STDSTRING("height"), + STDINT32(height) ) ); } -static int send_completed_event(struct gstplayer_meta *meta) { - return platch_send_success_event_std( - meta->event_channel_name, - &STDMAP1( - STDSTRING("event"), STDSTRING("completed") - ) - ); +UNUSED static int send_completed_event(struct gstplayer_meta *meta) { + return platch_send_success_event_std(meta->event_channel_name, &STDMAP1(STDSTRING("event"), STDSTRING("completed"))); } -static int send_buffering_update( - struct gstplayer_meta *meta, - int n_ranges, - const struct buffering_range *ranges -) { +static int send_buffering_update(struct gstplayer_meta *meta, int n_ranges, const struct buffering_range *ranges) { struct std_value values; values.type = kStdList; @@ -289,36 +278,23 @@ static int send_buffering_update( return platch_send_success_event_std( meta->event_channel_name, - &STDMAP2( - STDSTRING("event"), STDSTRING("bufferingUpdate"), - STDSTRING("values"), values - ) + &STDMAP2(STDSTRING("event"), STDSTRING("bufferingUpdate"), STDSTRING("values"), values) ); } static int send_buffering_start(struct gstplayer_meta *meta) { - return platch_send_success_event_std( - meta->event_channel_name, - &STDMAP1( - STDSTRING("event"), STDSTRING("bufferingStart") - ) - ); + return platch_send_success_event_std(meta->event_channel_name, &STDMAP1(STDSTRING("event"), STDSTRING("bufferingStart"))); } static int send_buffering_end(struct gstplayer_meta *meta) { - return platch_send_success_event_std( - meta->event_channel_name, - &STDMAP1( - STDSTRING("event"), STDSTRING("bufferingEnd") - ) - ); + return platch_send_success_event_std(meta->event_channel_name, &STDMAP1(STDSTRING("event"), STDSTRING("bufferingEnd"))); } static enum listener_return on_video_info_notify(void *arg, void *userdata) { struct gstplayer_meta *meta; struct video_info *info; - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(userdata); meta = userdata; info = arg; @@ -331,7 +307,8 @@ static enum listener_return on_video_info_notify(void *arg, void *userdata) { LOG_DEBUG( "Got video info: stream? %s, w x h: % 4d x % 4d, duration: %" GST_TIME_FORMAT "\n", !info->can_seek ? "yes" : "no", - info->width, info->height, + info->width, + info->height, GST_TIME_ARGS(info->duration_ms * GST_MSECOND) ); @@ -348,8 +325,8 @@ static enum listener_return on_buffering_state_notify(void *arg, void *userdata) struct buffering_state *state; struct gstplayer_meta *meta; bool new_is_buffering; - - DEBUG_ASSERT_NOT_NULL(userdata); + + ASSERT_NOT_NULL(userdata); meta = userdata; state = arg; @@ -375,19 +352,13 @@ static enum listener_return on_buffering_state_notify(void *arg, void *userdata) * CHANNEL HANDLERS * * handle method calls on the method and event channel * *******************************************************/ -static int on_receive_evch( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_receive_evch(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer_meta *meta; struct gstplayer *player; const char *method; method = object->method; - LOG_DEBUG("on_receive_evch\n"); - player = get_player_by_evch(channel); if (player == NULL) { return platch_respond_not_implemented(responsehandle); @@ -395,20 +366,19 @@ static int on_receive_evch( meta = gstplayer_get_userdata_locked(player); - if STREQ("listen", method) { + if (streq("listen", method)) { platch_respond_success_std(responsehandle, NULL); meta->has_listener = true; meta->video_info_listener = notifier_listen(gstplayer_get_video_info_notifier(player), on_video_info_notify, NULL, meta); - if (meta->video_info_listener == NULL) { - LOG_ERROR("Couldn't listen for video info events in gstplayer.\n"); - } + // We don't care if it's NULL, it could also be on_video_info_notify was called synchronously. (And returned kUnlisten) - meta->buffering_state_listener = notifier_listen(gstplayer_get_buffering_state_notifier(player), on_buffering_state_notify, NULL, meta); + meta->buffering_state_listener = + notifier_listen(gstplayer_get_buffering_state_notifier(player), on_buffering_state_notify, NULL, meta); if (meta->buffering_state_listener == NULL) { LOG_ERROR("Couldn't listen for buffering events in gstplayer.\n"); } - } else if STREQ("cancel", method) { + } else if (streq("cancel", method)) { platch_respond_success_std(responsehandle, NULL); meta->has_listener = false; @@ -427,11 +397,7 @@ static int on_receive_evch( return 0; } -static int on_initialize( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_initialize(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { int ok; (void) channel; @@ -439,29 +405,21 @@ static int on_initialize( ok = ensure_initialized(); if (ok != 0) { - return respond_init_failed(responsehandle); + return respond_init_failed_v2(responsehandle); } - LOG_DEBUG("on_initialize\n"); - // what do we even do here? return platch_respond_success_pigeon(responsehandle, NULL); } -static int check_headers( - const struct std_value *headers, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int check_headers(const struct std_value *headers, FlutterPlatformMessageResponseHandle *responsehandle) { const struct std_value *key, *value; if (headers == NULL || STDVALUE_IS_NULL(*headers)) { return 0; - } else if (headers->type != kStdMap) { - platch_respond_illegal_arg_pigeon( - responsehandle, - "Expected `arg['httpHeaders']` to be a map of strings or null." - ); + } else if (!STDVALUE_IS_MAP(*headers)) { + platch_respond_illegal_arg_pigeon(responsehandle, "Expected `arg['httpHeaders']` to be a map of strings or null."); return EINVAL; } @@ -476,10 +434,7 @@ static int check_headers( // valid too continue; } else { - platch_respond_illegal_arg_pigeon( - responsehandle, - "Expected `arg['httpHeaders']` to be a map of strings or null." - ); + platch_respond_illegal_arg_pigeon(responsehandle, "Expected `arg['httpHeaders']` to be a map of strings or null."); return EINVAL; } } @@ -487,16 +442,13 @@ static int check_headers( return 0; } -static int add_headers_to_player( - const struct std_value *headers, - struct gstplayer *player -) { +static int add_headers_to_player(const struct std_value *headers, struct gstplayer *player) { const struct std_value *key, *value; if (headers == NULL || STDVALUE_IS_NULL(*headers)) { return 0; - } else if (headers->type != kStdMap) { - DEBUG_ASSERT(false); + } else if (!STDVALUE_IS_MAP(*headers)) { + assert(false); } for (int i = 0; i < headers->size; i++) { @@ -509,7 +461,7 @@ static int add_headers_to_player( } else if (STDVALUE_IS_STRING(*key) && STDVALUE_IS_STRING(*value)) { gstplayer_put_http_header(player, STDVALUE_AS_STRING(*key), STDVALUE_AS_STRING(*value)); } else { - DEBUG_ASSERT(false); + assert(false); } } @@ -519,26 +471,24 @@ static int add_headers_to_player( /// Allocates and initializes a gstplayer_meta struct, which we /// use to store additional information in a gstplayer instance. /// (The event channel name for that player) -static struct gstplayer_meta *create_meta(int64_t texture_id) { +static struct gstplayer_meta *create_meta(int64_t texture_id, struct gstplayer *player) { struct gstplayer_meta *meta; char *event_channel_name; + int ok; meta = malloc(sizeof *meta); if (meta == NULL) { return NULL; } - asprintf( - &event_channel_name, - "flutter.io/videoPlayer/videoEvents%" PRId64, - texture_id - ); - - if (event_channel_name == NULL) { + ok = asprintf(&event_channel_name, "flutter.io/videoPlayer/videoEvents%" PRId64, texture_id); + if (ok < 0) { free(meta); return NULL; } + meta->player = player; + list_inithead(&meta->entry); meta->event_channel_name = event_channel_name; meta->has_listener = false; meta->is_buffering = false; @@ -550,13 +500,40 @@ static void destroy_meta(struct gstplayer_meta *meta) { free(meta); } +static void dispose_player(struct gstplayer *player, bool plugin_registry_locked, bool plugin_locked) { + struct gstplayer_meta *meta; + + meta = get_meta(player); + + if (plugin_registry_locked) { + plugin_registry_remove_receiver_locked(meta->event_channel_name); + } else { + plugin_registry_remove_receiver(meta->event_channel_name); + } + + if (plugin_locked) { + remove_player_locked(meta); + } else { + remove_player(meta); + } + + if (meta->video_info_listener != NULL) { + notifier_unlisten(gstplayer_get_video_info_notifier(player), meta->video_info_listener); + meta->video_info_listener = NULL; + } + if (meta->buffering_state_listener != NULL) { + notifier_unlisten(gstplayer_get_buffering_state_notifier(player), meta->buffering_state_listener); + meta->buffering_state_listener = NULL; + } + + destroy_meta(meta); + + gstplayer_destroy(player); +} + /// Creates a new video player. /// Should respond to the platform message when the player has established its viewport. -static int on_create( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_create(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer_meta *meta; struct gstplayer *player; struct std_value *arg, *temp; @@ -574,71 +551,55 @@ static int on_create( } if (!STDVALUE_IS_MAP(*arg)) { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg` to be a Map, but was:", - arg - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg` to be a Map, but was:", arg); } temp = stdmap_get_str(arg, "asset"); - if (temp == NULL || temp->type == kStdNull) { + if (temp == NULL || STDVALUE_IS_NULL(*temp)) { asset = NULL; - } else if (temp != NULL && temp->type == kStdString) { - asset = temp->string_value; + } else if (STDVALUE_IS_STRING(*temp)) { + asset = STDVALUE_AS_STRING(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['asset']` to be a String or null, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['asset']` to be a String or null, but was:", temp); } temp = stdmap_get_str(arg, "uri"); - if (temp == NULL || temp->type == kStdNull) { + if (temp == NULL || STDVALUE_IS_NULL(*temp)) { uri = NULL; - } else if (temp != NULL && temp->type == kStdString) { - uri = temp->string_value; + } else if (STDVALUE_IS_STRING(*temp)) { + uri = STDVALUE_AS_STRING(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['uri']` to be a String or null, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['uri']` to be a String or null, but was:", temp); } temp = stdmap_get_str(arg, "packageName"); - if (temp == NULL || temp->type == kStdNull) { + if (temp == NULL || STDVALUE_IS_NULL(*temp)) { package_name = NULL; - } else if (temp != NULL && temp->type == kStdString) { - package_name = temp->string_value; + } else if (STDVALUE_IS_STRING(*temp)) { + package_name = STDVALUE_AS_STRING(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['packageName']` to be a String or null, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['packageName']` to be a String or null, but was:", temp); } temp = stdmap_get_str(arg, "formatHint"); - if (temp == NULL || temp->type == kStdNull) { - format_hint = kNoFormatHint; - } else if (temp != NULL && temp->type == kStdString) { - char *format_hint_str = temp->string_value; - - if STREQ("ss", format_hint_str) { - format_hint = kSS_FormatHint; - } else if STREQ("hls", format_hint_str) { - format_hint = kHLS_FormatHint; - } else if STREQ("dash", format_hint_str) { - format_hint = kMpegDash_FormatHint; - } else if STREQ("other", format_hint_str) { - format_hint = kOther_FormatHint; + if (temp == NULL || STDVALUE_IS_NULL(*temp)) { + format_hint = FORMAT_HINT_NONE; + } else if (STDVALUE_IS_STRING(*temp)) { + char *format_hint_str = STDVALUE_AS_STRING(*temp); + + if (streq("ss", format_hint_str)) { + format_hint = FORMAT_HINT_SS; + } else if (streq("hls", format_hint_str)) { + format_hint = FORMAT_HINT_HLS; + } else if (streq("dash", format_hint_str)) { + format_hint = FORMAT_HINT_MPEG_DASH; + } else if (streq("other", format_hint_str)) { + format_hint = FORMAT_HINT_OTHER; } else { goto invalid_format_hint; } } else { - invalid_format_hint: +invalid_format_hint: return platch_respond_illegal_arg_ext_pigeon( responsehandle, @@ -657,9 +618,9 @@ static int on_create( // create our actual player (this doesn't initialize it) if (asset != NULL) { - player = gstplayer_new_from_asset(&flutterpi, asset, package_name, NULL); + player = gstplayer_new_from_asset(flutterpi, asset, package_name, NULL); } else { - player = gstplayer_new_from_network(&flutterpi, uri, format_hint, NULL); + player = gstplayer_new_from_network(flutterpi, uri, format_hint, NULL); } if (player == NULL) { LOG_ERROR("Couldn't create gstreamer video player.\n"); @@ -669,29 +630,22 @@ static int on_create( // create a meta object so we can store the event channel name // of a player with it - meta = create_meta(gstplayer_get_texture_id(player)); + meta = create_meta(gstplayer_get_texture_id(player), player); if (meta == NULL) { ok = ENOMEM; goto fail_destroy_player; } - + gstplayer_set_userdata_locked(player, meta); // Add all our HTTP headers to gstplayer using gstplayer_put_http_header add_headers_to_player(temp, player); // add it to our player collection - ok = add_player(player); - if (ok != 0) { - goto fail_destroy_meta; - } + add_player(meta); // set a receiver on the videoEvents event channel - ok = plugin_registry_set_receiver( - meta->event_channel_name, - kStandardMethodCall, - on_receive_evch - ); + ok = plugin_registry_set_receiver(meta->event_channel_name, kStandardMethodCall, on_receive_evch); if (ok != 0) { goto fail_remove_player; } @@ -702,37 +656,23 @@ static int on_create( goto fail_remove_receiver; } - LOG_DEBUG("respond success on_create\n"); - - return platch_respond_success_pigeon( - responsehandle, - &STDMAP1( - STDSTRING("textureId"), STDINT64(gstplayer_get_texture_id(player)) - ) - ); + return platch_respond_success_pigeon(responsehandle, &STDMAP1(STDSTRING("textureId"), STDINT64(gstplayer_get_texture_id(player)))); - fail_remove_receiver: +fail_remove_receiver: plugin_registry_remove_receiver(meta->event_channel_name); - fail_remove_player: - remove_player(player); - - fail_destroy_meta: +fail_remove_player: + remove_player(meta); destroy_meta(meta); - fail_destroy_player: +fail_destroy_player: gstplayer_destroy(player); - fail_respond_error: +fail_respond_error: return platch_respond_native_error_pigeon(responsehandle, ok); } -static int on_dispose( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct gstplayer_meta *meta; +static int on_dispose(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg; int ok; @@ -745,30 +685,13 @@ static int on_dispose( if (ok != 0) { return 0; } - - meta = get_meta(player); - plugin_registry_remove_receiver(meta->event_channel_name); + dispose_player(player, false, false); - remove_player(player); - if (meta->video_info_listener != NULL) { - notifier_unlisten(gstplayer_get_video_info_notifier(player), meta->video_info_listener); - meta->video_info_listener = NULL; - } - if (meta->buffering_state_listener != NULL) { - notifier_unlisten(gstplayer_get_buffering_state_notifier(player), meta->buffering_state_listener); - meta->buffering_state_listener = NULL; - } - destroy_meta(meta); - gstplayer_destroy(player); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_set_looping( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_looping(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg, *temp; bool loop; @@ -779,28 +702,21 @@ static int on_set_looping( arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; + if (ok != 0) + return ok; temp = stdmap_get_str(arg, "isLooping"); if (temp && STDVALUE_IS_BOOL(*temp)) { loop = STDVALUE_AS_BOOL(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['isLooping']` to be a boolean, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['isLooping']` to be a boolean, but was:", temp); } - + gstplayer_set_looping(player, loop); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_set_volume( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_volume(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg, *temp; double volume; @@ -811,28 +727,21 @@ static int on_set_volume( arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; + if (ok != 0) + return ok; temp = stdmap_get_str(arg, "volume"); if (STDVALUE_IS_FLOAT(*temp)) { volume = STDVALUE_AS_FLOAT(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['volume']` to be a float/double, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['volume']` to be a float/double, but was:", temp); } gstplayer_set_volume(player, volume); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_set_playback_speed( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_playback_speed(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg, *temp; double speed; @@ -843,28 +752,21 @@ static int on_set_playback_speed( arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; + if (ok != 0) + return ok; temp = stdmap_get_str(arg, "speed"); if (STDVALUE_IS_FLOAT(*temp)) { speed = STDVALUE_AS_FLOAT(*temp); } else { - return platch_respond_illegal_arg_ext_pigeon( - responsehandle, - "Expected `arg['speed']` to be a float/double, but was:", - temp - ); + return platch_respond_illegal_arg_ext_pigeon(responsehandle, "Expected `arg['speed']` to be a float/double, but was:", temp); } gstplayer_set_playback_speed(player, speed); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_play( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_play(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg; int ok; @@ -874,17 +776,14 @@ static int on_play( arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return 0; + if (ok != 0) + return 0; gstplayer_play(player); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_get_position( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_get_position(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg; int64_t position; @@ -896,32 +795,19 @@ static int on_get_position( arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return 0; + if (ok != 0) + return 0; position = gstplayer_get_position(player); if (position >= 0) { - return platch_respond_success_pigeon( - responsehandle, - &STDMAP1( - STDSTRING("position"), STDINT64(position) - ) - ); + return platch_respond_success_pigeon(responsehandle, &STDMAP1(STDSTRING("position"), STDINT64(position))); } else { - return platch_respond_error_pigeon( - responsehandle, - "native-error", - "An unexpected gstreamer error ocurred.", - NULL - ); + return platch_respond_error_pigeon(responsehandle, "native-error", "An unexpected gstreamer error ocurred.", NULL); } } -static int on_seek_to( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_seek_to(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg, *temp; int64_t position; @@ -932,64 +818,51 @@ static int on_seek_to( arg = &(object->std_value); ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return 0; + if (ok != 0) + return 0; temp = stdmap_get_str(arg, "position"); if (STDVALUE_IS_INT(*temp)) { position = STDVALUE_AS_INT(*temp); } else { - return platch_respond_illegal_arg_pigeon( - responsehandle, - "Expected `arg['position']` to be an integer." - ); + return platch_respond_illegal_arg_pigeon(responsehandle, "Expected `arg['position']` to be an integer."); } gstplayer_seek_to(player, position, false); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_pause( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_pause(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *arg; int ok; (void) channel; - + arg = &object->std_value; ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return 0; + if (ok != 0) + return 0; gstplayer_pause(player); return platch_respond_success_pigeon(responsehandle, NULL); } -static int on_set_mix_with_others( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_mix_with_others(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct std_value *arg; (void) channel; - + arg = &object->std_value; (void) arg; /// TODO: Should we do anything other here than just returning? - LOG_DEBUG("on_set_mix_with_others\n"); return platch_respond_success_std(responsehandle, &STDNULL); } -static int on_step_forward( - struct std_value *arg, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_step_forward(struct std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; int ok; @@ -1000,19 +873,13 @@ static int on_step_forward( ok = gstplayer_step_forward(player); if (ok != 0) { - return platch_respond_native_error_std( - responsehandle, - ok - ); + return platch_respond_native_error_std(responsehandle, ok); } return platch_respond_success_std(responsehandle, NULL); } -static int on_step_backward( - struct std_value *arg, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_step_backward(struct std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; int ok; @@ -1023,19 +890,13 @@ static int on_step_backward( ok = gstplayer_step_backward(player); if (ok != 0) { - return platch_respond_native_error_std( - responsehandle, - ok - ); + return platch_respond_native_error_std(responsehandle, ok); } return platch_respond_success_std(responsehandle, NULL); } -static int on_fast_seek( - struct std_value *arg, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_fast_seek(struct std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { struct gstplayer *player; struct std_value *temp; int64_t position; @@ -1050,181 +911,846 @@ static int on_fast_seek( if (STDVALUE_IS_INT(*temp)) { position = STDVALUE_AS_INT(*temp); } else { - return platch_respond_illegal_arg_pigeon( - responsehandle, - "Expected `arg['position']` to be an integer." - ); + return platch_respond_illegal_arg_pigeon(responsehandle, "Expected `arg['position']` to be an integer."); } ok = gstplayer_seek_to(player, position, true); if (ok != 0) { - return platch_respond_native_error_std( - responsehandle, - ok - ); + return platch_respond_native_error_std(responsehandle, ok); } return platch_respond_success_std(responsehandle, NULL); } -static int on_receive_method_channel( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_receive_method_channel(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { const char *method; (void) channel; method = object->method; - if STREQ("stepForward", method) { + if (streq("stepForward", method)) { return on_step_forward(&object->std_arg, responsehandle); - } else if STREQ("stepBackward", method) { + } else if (streq("stepBackward", method)) { return on_step_backward(&object->std_arg, responsehandle); - } else if STREQ("fastSeek", method) { + } else if (streq("fastSeek", method)) { return on_fast_seek(&object->std_arg, responsehandle); } else { return platch_respond_not_implemented(responsehandle); } } -enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { - int ok; +static struct gstplayer * +get_player_from_texture_id_with_custom_errmsg(int64_t texture_id, FlutterPlatformMessageResponseHandle *responsehandle, char *error_message) { + struct gstplayer *player; - (void) userdata_out; + player = get_player_by_texture_id(texture_id); + if (player == NULL) { + plugin_lock(&plugin); - plugin.flutterpi = flutterpi; - plugin.initialized = false; + int n_texture_ids = list_length(&plugin.players); + int64_t *texture_ids = alloca(sizeof(int64_t) * n_texture_ids); + int64_t *texture_ids_cursor = texture_ids; - ok = cpset_init(&plugin.players, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - return kError_PluginInitResult; - } + list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { + *texture_ids_cursor++ = gstplayer_get_texture_id(meta->player); + } - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize", kStandardMessageCodec, on_initialize); - if (ok != 0) { - goto fail_deinit_cpset; - } + plugin_unlock(&plugin); - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.create", kStandardMessageCodec, on_create); - if (ok != 0) { - goto fail_remove_initialize_receiver; - } + platch_respond_illegal_arg_ext_std( + responsehandle, + error_message, + &STDMAP2( + STDSTRING("receivedTextureId"), + STDINT64(texture_id), + STDSTRING("registeredTextureIds"), + ((struct std_value){ .type = kStdInt64Array, .size = n_texture_ids, .int64array = texture_ids }) + ) + ); - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose", kStandardMessageCodec, on_dispose); - if (ok != 0) { - goto fail_remove_create_receiver; + return NULL; } - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping", kStandardMessageCodec, on_set_looping); - if (ok != 0) { - goto fail_remove_dispose_receiver; - } + return player; +} - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume", kStandardMessageCodec, on_set_volume); - if (ok != 0) { - goto fail_remove_setLooping_receiver; - } +static struct gstplayer * +get_player_from_v2_root_arg(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int64_t texture_id; - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed", kStandardMessageCodec, on_set_playback_speed); - if (ok != 0) { - goto fail_remove_setVolume_receiver; + if (!raw_std_value_is_int(arg)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be an integer."); + return NULL; } - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.play", kStandardMessageCodec, on_play); - if (ok != 0) { - goto fail_remove_setPlaybackSpeed_receiver; - } + texture_id = raw_std_value_as_int(arg); - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.position", kStandardMessageCodec, on_get_position); - if (ok != 0) { - goto fail_remove_play_receiver; - } + return get_player_from_texture_id_with_custom_errmsg(texture_id, responsehandle, "Expected `arg` to be a valid texture id."); +} - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo", kStandardMessageCodec, on_seek_to); - if (ok != 0) { - goto fail_remove_position_receiver; +static struct gstplayer * +get_player_from_v2_list_arg(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int64_t texture_id; + + if (!(raw_std_value_is_list(arg) && raw_std_list_get_size(arg) >= 1)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be a list with at least one element."); + return NULL; } - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.pause", kStandardMessageCodec, on_pause); - if (ok != 0) { - goto fail_remove_seekTo_receiver; + const struct raw_std_value *first_element = raw_std_list_get_first_element(arg); + if (!raw_std_value_is_int(first_element)) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be an integer."); + return NULL; } - ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers", kStandardMessageCodec, on_set_mix_with_others); - if (ok != 0) { - goto fail_remove_pause_receiver; + texture_id = raw_std_value_as_int(first_element); + + return get_player_from_texture_id_with_custom_errmsg(texture_id, responsehandle, "Expected `arg[0]` to be a valid texture id."); +} + +static int +check_arg_is_minimum_sized_list(const struct raw_std_value *arg, size_t minimum_size, FlutterPlatformMessageResponseHandle *responsehandle) { + int ok; + + if (!(raw_std_value_is_list(arg) && raw_std_list_get_size(arg) >= minimum_size)) { + char *error_message = NULL; + + ok = asprintf(&error_message, "Expected `arg` to be a list with at least %zu element(s).", minimum_size); + if (ok < 0) { + platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be a list with a minimum size."); + return EINVAL; + } + + platch_respond_illegal_arg_std(responsehandle, error_message); + + free(error_message); + + return EINVAL; } - ok = plugin_registry_set_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls", kStandardMethodCall, on_receive_method_channel); + return 0; +} + +static int on_initialize_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + int ok; + + (void) arg; + + ok = ensure_initialized(); if (ok != 0) { - goto fail_remove_setMixWithOthers_receiver; + return respond_init_failed(responsehandle); } - return kInitialized_PluginInitResult; - - - fail_remove_setMixWithOthers_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); - - fail_remove_pause_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.pause"); - - fail_remove_seekTo_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo"); - - fail_remove_position_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.position"); - - fail_remove_play_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.play"); - - fail_remove_setPlaybackSpeed_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); - - fail_remove_setVolume_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume"); - - fail_remove_setLooping_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping"); - - fail_remove_dispose_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose"); - - fail_remove_create_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.create"); - - fail_remove_initialize_receiver: - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize"); - - fail_deinit_cpset: - cpset_deinit(&plugin.players); - return kError_PluginInitResult; + return platch_respond_success_std(responsehandle, &STDNULL); } -void gstplayer_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { - (void) flutterpi; - (void) userdata; - - plugin_registry_remove_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.pause"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.position"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.play"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.create"); - plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize"); - cpset_deinit(&plugin.players); +static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *headers; + struct gstplayer_meta *meta; + struct gstplayer *player; + enum format_hint format_hint; + char *asset, *uri, *package_name, *pipeline; + size_t size; + int ok; + + ok = ensure_initialized(); + if (ok != 0) { + return respond_init_failed_v2(responsehandle); + } + + if (!raw_std_value_is_list(arg)) { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg` to be a List."); + } + + size = raw_std_list_get_size(arg); + + // arg[0]: Asset Path + if (size >= 1) { + arg = raw_std_list_get_first_element(arg); + + if (raw_std_value_is_null(arg)) { + asset = NULL; + } else if (raw_std_value_is_string(arg)) { + asset = raw_std_string_dup(arg); + if (asset == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be a String or null."); + } + } else { + asset = NULL; + } + + // arg[1]: Package Name + if (size >= 2) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + package_name = NULL; + } else if (raw_std_value_is_string(arg)) { + package_name = raw_std_string_dup(arg); + if (package_name == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a String or null."); + } + } else { + package_name = NULL; + } + + // arg[1]: URI + if (size >= 3) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + uri = NULL; + } else if (raw_std_value_is_string(arg)) { + uri = raw_std_string_dup(arg); + if (uri == NULL) { + ok = ENOMEM; + goto fail_respond_error; + } + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[2]` to be a String or null."); + } + } else { + uri = NULL; + } + + // arg[3]: Format Hint + if (size >= 4) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + format_hint = FORMAT_HINT_NONE; + } else if (raw_std_value_is_string(arg)) { + if (raw_std_string_equals(arg, "ss")) { + format_hint = FORMAT_HINT_SS; + } else if (raw_std_string_equals(arg, "hls")) { + format_hint = FORMAT_HINT_HLS; + } else if (raw_std_string_equals(arg, "dash")) { + format_hint = FORMAT_HINT_MPEG_DASH; + } else if (raw_std_string_equals(arg, "other")) { + format_hint = FORMAT_HINT_OTHER; + } else { + goto invalid_format_hint; + } + } else { +invalid_format_hint: + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[3]` to be one of 'ss', 'hls', 'dash', 'other' or null."); + } + } else { + format_hint = FORMAT_HINT_NONE; + } + + // arg[4]: HTTP Headers + if (size >= 5) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + headers = NULL; + } else if (raw_std_value_is_map(arg)) { + for_each_entry_in_raw_std_map(key, value, arg) { + if (!raw_std_value_is_string(key) || !raw_std_value_is_string(value)) { + goto invalid_headers; + } + } + headers = arg; + } else { +invalid_headers: + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[4]` to be a map of strings or null."); + } + } else { + headers = NULL; + } + + // arg[5]: Gstreamer Pipeline + if (size >= 6) { + arg = raw_std_value_after(arg); + + if (raw_std_value_is_null(arg)) { + pipeline = NULL; + } else if (raw_std_value_is_string(arg)) { + pipeline = raw_std_string_dup(arg); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[5]` to be a string or null."); + } + } else { + pipeline = NULL; + } + + if ((asset ? 1 : 0) + (uri ? 1 : 0) + (pipeline ? 1 : 0) != 1) { + return platch_respond_illegal_arg_std(responsehandle, "Expected exactly one of `arg[0]`, `arg[2]` or `arg[5]` to be non-null."); + } + + // Create our actual player (this doesn't initialize it) + if (asset != NULL) { + player = gstplayer_new_from_asset(flutterpi, asset, package_name, NULL); + + // gstplayer_new_from_network will construct a file:// URI out of the + // asset path internally. + free(asset); + asset = NULL; + } else if (uri != NULL) { + player = gstplayer_new_from_network(flutterpi, uri, format_hint, NULL); + + // gstplayer_new_from_network will dup the uri internally. + free(uri); + uri = NULL; + } else if (pipeline != NULL) { + player = gstplayer_new_from_pipeline(flutterpi, pipeline, NULL); + + // gstplayer_new_from_network will dup the pipeline internally. + free(pipeline); + pipeline = NULL; + } else { + UNREACHABLE(); + } + + if (player == NULL) { + LOG_ERROR("Couldn't create gstreamer video player.\n"); + ok = EIO; + goto fail_respond_error; + } + + // create a meta object so we can store the event channel name + // of a player with it + meta = create_meta(gstplayer_get_texture_id(player), player); + if (meta == NULL) { + ok = ENOMEM; + goto fail_destroy_player; + } + + gstplayer_set_userdata_locked(player, meta); + + // Add all the HTTP headers to gstplayer using gstplayer_put_http_header + if (headers != NULL) { + for_each_entry_in_raw_std_map(header_name, header_value, headers) { + char *header_name_duped = raw_std_string_dup(header_name); + char *header_value_duped = raw_std_string_dup(header_value); + + gstplayer_put_http_header(player, header_name_duped, header_value_duped); + + free(header_value_duped); + free(header_name_duped); + } + } + + // Add it to our player collection + add_player(meta); + + // Set a receiver on the videoEvents event channel + ok = plugin_registry_set_receiver(meta->event_channel_name, kStandardMethodCall, on_receive_evch); + if (ok != 0) { + goto fail_remove_player; + } + + // Finally, start initializing + ok = gstplayer_initialize(player); + if (ok != 0) { + goto fail_remove_receiver; + } + + return platch_respond_success_std(responsehandle, &STDINT64(gstplayer_get_texture_id(player))); + +fail_remove_receiver: + plugin_registry_remove_receiver(meta->event_channel_name); + +fail_remove_player: + remove_player(meta); + destroy_meta(meta); + +fail_destroy_player: + gstplayer_destroy(player); + +fail_respond_error: + return platch_respond_native_error_std(responsehandle, ok); +} + +static int on_dispose_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + dispose_player(player, false, false); + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_set_looping_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + bool looping; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_bool(second)) { + looping = raw_std_value_as_bool(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a bool."); + } + + ok = gstplayer_set_looping(player, looping); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_set_volume_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + double volume; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_float64(second)) { + volume = raw_std_value_as_float64(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a double."); + } + + ok = gstplayer_set_volume(player, volume); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_set_playback_speed_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + double speed; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_float64(second)) { + speed = raw_std_value_as_float64(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a double."); + } + + ok = gstplayer_set_playback_speed(player, speed); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_play_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_play(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_get_position_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int64_t position; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + position = gstplayer_get_position(player); + if (position < 0) { + return platch_respond_native_error_std(responsehandle, EIO); + } + + return platch_respond_success_std(responsehandle, &STDINT64(position)); +} + +static int on_seek_to_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + int64_t position; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_int(second)) { + position = raw_std_value_as_int(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be an integer."); + } + + ok = gstplayer_seek_to(player, position, false); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_pause_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_pause(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_set_mix_with_others_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + (void) arg; + (void) responsehandle; + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_fast_seek_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *second; + struct gstplayer *player; + int64_t position; + int ok; + + ok = check_arg_is_minimum_sized_list(arg, 2, responsehandle); + if (ok != 0) { + return 0; + } + + player = get_player_from_v2_list_arg(arg, responsehandle); + if (player == NULL) { + return 0; + } + + second = raw_std_list_get_nth_element(arg, 1); + if (raw_std_value_is_int(second)) { + position = raw_std_value_as_int(second); + } else { + return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be an integer."); + } + + ok = gstplayer_seek_to(player, position, true); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_step_forward_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_step_forward(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_step_backward_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { + struct gstplayer *player; + int ok; + + player = get_player_from_v2_root_arg(arg, responsehandle); + if (player == NULL) { + return EINVAL; + } + + ok = gstplayer_step_backward(player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + return platch_respond_success_std(responsehandle, &STDNULL); +} + +static int on_receive_method_channel_v2(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + const struct raw_std_value *envelope, *method, *arg; + + ASSERT_NOT_NULL(channel); + ASSERT_NOT_NULL(object); + ASSERT_NOT_NULL(responsehandle); + ASSERT_EQUALS(object->codec, kBinaryCodec); + assert(object->binarydata_size != 0); + (void) channel; + + envelope = (const struct raw_std_value *) (object->binarydata); + + if (!raw_std_method_call_check(envelope, object->binarydata_size)) { + return platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + } + + method = raw_std_method_call_get_method(envelope); + arg = raw_std_method_call_get_arg(envelope); + + if (raw_std_string_equals(method, "initialize")) { + return on_initialize_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "create")) { + return on_create_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "dispose")) { + return on_dispose_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setLooping")) { + return on_set_looping_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setVolume")) { + return on_set_volume_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setPlaybackSpeed")) { + return on_set_playback_speed_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "play")) { + return on_play_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "getPosition")) { + return on_get_position_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "seekTo")) { + return on_seek_to_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "pause")) { + return on_pause_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "setMixWithOthers")) { + return on_set_mix_with_others_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "stepForward")) { + return on_step_forward_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "stepBackward")) { + return on_step_backward_v2(arg, responsehandle); + } else if (raw_std_string_equals(method, "fastSeek")) { + return on_fast_seek_v2(arg, responsehandle); + } else { + return platch_respond_not_implemented(responsehandle); + } +} + +enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { + int ok; + + (void) userdata_out; + + plugin.flutterpi = flutterpi; + plugin.initialized = false; + + ok = pthread_mutex_init(&plugin.lock, get_default_mutex_attrs()); + if (ok != 0) { + return PLUGIN_INIT_RESULT_ERROR; + } + + list_inithead(&plugin.players); + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize", kStandardMessageCodec, on_initialize); + if (ok != 0) { + goto fail_destroy_lock; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create", kStandardMessageCodec, on_create); + if (ok != 0) { + goto fail_remove_initialize_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose", kStandardMessageCodec, on_dispose); + if (ok != 0) { + goto fail_remove_create_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping", kStandardMessageCodec, on_set_looping); + if (ok != 0) { + goto fail_remove_dispose_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume", kStandardMessageCodec, on_set_volume); + if (ok != 0) { + goto fail_remove_setLooping_receiver; + } + + ok = plugin_registry_set_receiver_locked( + "dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed", + kStandardMessageCodec, + on_set_playback_speed + ); + if (ok != 0) { + goto fail_remove_setVolume_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play", kStandardMessageCodec, on_play); + if (ok != 0) { + goto fail_remove_setPlaybackSpeed_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position", kStandardMessageCodec, on_get_position); + if (ok != 0) { + goto fail_remove_play_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo", kStandardMessageCodec, on_seek_to); + if (ok != 0) { + goto fail_remove_position_receiver; + } + + ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause", kStandardMessageCodec, on_pause); + if (ok != 0) { + goto fail_remove_seekTo_receiver; + } + + ok = plugin_registry_set_receiver_locked( + "dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers", + kStandardMessageCodec, + on_set_mix_with_others + ); + if (ok != 0) { + goto fail_remove_pause_receiver; + } + + ok = plugin_registry_set_receiver_locked( + "flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls", + kStandardMethodCall, + on_receive_method_channel + ); + if (ok != 0) { + goto fail_remove_setMixWithOthers_receiver; + } + + ok = plugin_registry_set_receiver_locked("flutter-pi/gstreamerVideoPlayer", kBinaryCodec, on_receive_method_channel_v2); + if (ok != 0) { + goto fail_remove_advancedControls_receiver; + } + + return PLUGIN_INIT_RESULT_INITIALIZED; + +fail_remove_advancedControls_receiver: + plugin_registry_remove_receiver_locked("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); + +fail_remove_setMixWithOthers_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); + +fail_remove_pause_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause"); + +fail_remove_seekTo_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo"); + +fail_remove_position_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position"); + +fail_remove_play_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play"); + +fail_remove_setPlaybackSpeed_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); + +fail_remove_setVolume_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume"); + +fail_remove_setLooping_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping"); + +fail_remove_dispose_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose"); + +fail_remove_create_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create"); + +fail_remove_initialize_receiver: + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize"); + +fail_destroy_lock: + pthread_mutex_destroy(&plugin.lock); + return PLUGIN_INIT_RESULT_ERROR; +} + +void gstplayer_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { + (void) flutterpi; + (void) userdata; + + plugin_lock(&plugin); + + list_for_each_entry_safe(struct gstplayer_meta, meta, &plugin.players, entry) { + // will also remove the player from the player list. + dispose_player(meta->player, true, true); + } + + if (plugin.initialized) { + gst_deinit(); + plugin.initialized = false; + } + + plugin_unlock(&plugin); + + plugin_registry_remove_receiver_locked("flutter-pi/gstreamerVideoPlayer"); + plugin_registry_remove_receiver_locked("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create"); + plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize"); + pthread_mutex_destroy(&plugin.lock); } -FLUTTERPI_PLUGIN( - "gstreamer video_player", - gstplayer, - gstplayer_plugin_init, - gstplayer_plugin_deinit -) \ No newline at end of file +FLUTTERPI_PLUGIN("gstreamer video_player", gstplayer, gstplayer_plugin_init, gstplayer_plugin_deinit) diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c deleted file mode 100644 index d6050c18..00000000 --- a/src/plugins/omxplayer_video_player.c +++ /dev/null @@ -1,1554 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -FILE_DESCR("omxplayer video_player plugin") - -static struct { - bool initialized; - - /// On creation of a new player, - /// the id stored here will be used and incremented. - int64_t next_unused_player_id; - - /// Collection of players. - /* - struct omxplayer_video_player **players; - size_t size_players; - size_t n_players; - */ - - struct concurrent_pointer_set players; - - /// The D-Bus that where omxplayer instances can be talked to. - /// Typically the session dbus. - //sd_bus *dbus; - //pthread_t dbus_processor_thread; -} omxpvidpp = { - .initialized = false, - .next_unused_player_id = 1, - .players = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) -}; - -/// Add a player instance to the player collection. -static int add_player(struct omxplayer_video_player *player) { - return cpset_put(&omxpvidpp.players, player); -} - -/// Get a player instance by its id. -struct omxplayer_video_player *get_player_by_id(int64_t player_id) { - struct omxplayer_video_player *player; - - cpset_lock(&omxpvidpp.players); - for_each_pointer_in_cpset(&omxpvidpp.players, player) { - if (player->player_id == player_id) { - cpset_unlock(&omxpvidpp.players); - return player; - } - } - - cpset_unlock(&omxpvidpp.players); - return NULL; -} - -/// Get a player instance by its event channel name. -struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { - struct omxplayer_video_player *player; - - cpset_lock(&omxpvidpp.players); - for_each_pointer_in_cpset(&omxpvidpp.players, player) { - if (strcmp(player->event_channel_name, event_channel_name) == 0) { - cpset_unlock(&omxpvidpp.players); - return player; - } - } - - cpset_unlock(&omxpvidpp.players); - return NULL; -} - -/// Remove a player instance from the player collection. -static int remove_player(struct omxplayer_video_player *player) { - return cpset_remove(&omxpvidpp.players, player); -} - -/// Get the player id from the given arg, which is a kStdMap. -/// (*player_id_out = arg['playerId']) -/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_player_id_from_map_arg( - struct std_value *arg, - int64_t *player_id_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - int ok; - - if (arg->type != kStdMap) { - ok = platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg` to be a Map" - ); - if (ok != 0) return ok; - - return EINVAL; - } - - struct std_value *id = stdmap_get_str(arg, "playerId"); - if (id == NULL || !STDVALUE_IS_INT(*id)) { - ok = platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['playerId']` to be an integer" - ); - if (ok != 0) return ok; - - return EINVAL; - } - - *player_id_out = STDVALUE_AS_INT(*id); - - return 0; -} - -/// Get the player associated with the id in the given arg, which is a kStdMap. -/// (*player_out = get_player_by_id(get_player_id_from_map_arg(arg))) -/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. -static int get_player_from_map_arg( - struct std_value *arg, - struct omxplayer_video_player **player_out, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct omxplayer_video_player *player; - int64_t player_id; - int ok; - - player_id = 0; - ok = get_player_id_from_map_arg(arg, &player_id, responsehandle); - if (ok != 0) { - return ok; - } - - player = get_player_by_id(player_id); - if (player == NULL) { - ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg['playerId']` to be a valid player id."); - if (ok != 0) return ok; - - return EINVAL; - } - - *player_out = player; - - return 0; -} - -static int get_orientation_from_rotation(double rotation) { - if ((rotation <= 45) || (rotation > 315)) { - rotation = 0; - } else if (rotation <= 135) { - rotation = 90; - } else if (rotation <= 225) { - rotation = 180; - } else if (rotation <= 315) { - rotation = 270; - } - - return rotation; -} - -/// Called on the flutter rasterizer thread when a players platform view is presented -/// for the first time after it was unmounted or initialized. -static int on_mount( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - if (zpos == 1) { - zpos = -126; - } - - struct aa_rect rect = get_aa_bounding_rect(params->rect); - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .responsehandle = NULL, - .offset_x = round(rect.offset.x), - .offset_y = round(rect.offset.y), - .width = round(rect.size.x), - .height = round(rect.size.y), - .zpos = zpos, - .orientation = get_orientation_from_rotation(params->rotation) - } - ); -} - -/// Called on the flutter rasterizer thread when a players platform view is not present -/// in the currently being drawn frame after it was present in the previous frame. -static int on_unmount( - int64_t view_id, - struct drmdev_atomic_req *req, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .offset_x = 0, - .offset_y = 0, - .width = 1, - .height = 1, - .zpos = -128 - } - ); -} - -/// Called on the flutter rasterizer thread when the presentation details (offset, mutations, dimensions, zpos) -/// changed from the previous frame. -static int on_update_view( - int64_t view_id, - struct drmdev_atomic_req *req, - const struct platform_view_params *params, - int zpos, - void *userdata -) { - struct omxplayer_video_player *player = userdata; - - (void) view_id; - (void) req; - - if (zpos == 1) { - zpos = -126; - } - - struct aa_rect rect = get_aa_bounding_rect(params->rect); - - return cqueue_enqueue( - &player->mgr->task_queue, - &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .responsehandle = NULL, - .offset_x = rect.offset.x, - .offset_y = rect.offset.y, - .width = rect.size.x, - .height = rect.size.y, - .zpos = zpos, - .orientation = get_orientation_from_rotation(params->rotation) - } - ); -} - -static int respond_sd_bus_error( - FlutterPlatformMessageResponseHandle *handle, - sd_bus_error *err -) { - char str[256]; - - snprintf(str, sizeof(str), "%s: %s", err->name, err->message); - - return platch_respond_error_std( - handle, - "dbus-error", - str, - NULL - ); -} - -/// Unfortunately, we can't use sd_bus for this, because it -/// wraps some things in containers. -static int get_dbus_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *ret_error, - char type, - void *ret_ptr -) { - sd_bus_message *msg; - int ok; - - ok = sd_bus_call_method( - bus, - destination, - path, - DBUS_PROPERTY_FACE, - DBUS_PROPERTY_GET, - ret_error, - &msg, - "ss", - interface, - member - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s, %s\n", ret_error->name, ret_error->message); - return -ok; - } - - ok = sd_bus_message_read_basic(msg, type, ret_ptr); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s\n", strerror(-ok)); - sd_bus_message_unref(msg); - return -ok; - } - - sd_bus_message_unref(msg); - - return 0; -} - -/// Callback to be called when the omxplayer manager receives -/// a DBus message. (Currently only used for listening to NameOwnerChanged messages, -/// to find out when omxplayer registers to the dbus.) -static int mgr_on_dbus_message( - sd_bus_message *m, - void *userdata, - sd_bus_error *ret_error -) { - struct omxplayer_mgr_task *task; - const char *sender, *member; - char *old_owner, *new_owner, *name; - int ok; - - (void) ret_error; - - task = userdata; - - sender = sd_bus_message_get_sender(m); - member = sd_bus_message_get_member(m); - - if (STREQ(sender, "org.freedesktop.DBus") && STREQ(member, "NameOwnerChanged")) { - ok = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); - if (ok < 0) { - fprintf(stderr, "Could not read message"); - return -1; - } - - if STREQ(name, task->omxplayer_dbus_name) { - task->omxplayer_online = true; - } - } - - return 0; -} - -/// The entry function of the manager thread. -/// Manager thread has the ownership over the player / manager / task queue objects -/// and must free them when it quits. -static void *mgr_entry(void *userdata) { - struct omxplayer_mgr_task task; - struct concurrent_queue *q; - struct omxplayer_mgr *mgr; - sd_bus_message *msg; - sd_bus_error err; - sd_bus_slot *slot; - int64_t duration_us, video_width, video_height, current_zpos, current_orientation; - sd_bus *bus; - pid_t omxplayer_pid; - char dbus_name[256]; - bool has_sent_initialized_event; - bool is_stream; - int ok; - - (void) current_orientation; - - mgr = userdata; - q = &mgr->task_queue; - - // dequeue the first task of the queue (creation task) - ok = cqueue_dequeue(q, &task); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not dequeue creation task in manager thread. cqueue_dequeue: %s\n", strerror(ok)); - platch_respond_error_std( - task.responsehandle, - "internal-error", - "Could not dequeue creation task in manager thread.", - NULL - ); - goto fail_remove_evch_listener; - } - - // check that it really is a creation task - if (task.type != kCreate || task.responsehandle == NULL) { - fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); - platch_respond_error_std( - task.responsehandle, - "internal-error", - "First task of manager thread is not a creation task.", - NULL - ); - goto fail_remove_evch_listener; - } - - // determine whether we're watching a stream or not. - // this is a heuristic. unfortunately, omxplayer itself doesn't even know whether it's playing - // back a stream or a video file. - if (strstr(mgr->player->video_uri, "rtsp://") == mgr->player->video_uri) { - is_stream = true; - } else { - is_stream = false; - } - - // generate the player name - snprintf( - dbus_name, - sizeof(dbus_name), - "org.mpris.MediaPlayer2.omxplayer_%d_%" PRId64, - (int) getpid(), - mgr->player->player_id - ); - - // open the session dbus - ok = sd_bus_open_user(&bus); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); - platch_respond_native_error_std(task.responsehandle, -ok); - goto fail_remove_evch_listener; - } - - // register a callbacks that tells us when - // omxplayer has registered to the dbus - task.omxplayer_online = false; - task.omxplayer_dbus_name = dbus_name; - ok = sd_bus_match_signal( - bus, - &slot, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "NameOwnerChanged", - mgr_on_dbus_message, - &task - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for omxplayer DBus registration in manager thread. sd_bus_match_signal: %s\n", strerror(-ok)); - platch_respond_native_error_std(task.responsehandle, -ok); - goto fail_close_dbus; - } - - // spawn the omxplayer process - current_zpos = -128; - current_orientation = task.orientation; - pid_t me = fork(); - if (me == 0) { - char orientation_str[16] = {0}; - snprintf(orientation_str, sizeof orientation_str, "%d", task.orientation); - - // I'm the child! - prctl(PR_SET_PDEATHSIG, SIGKILL); - int _ok = execvp( - "omxplayer.bin", - (char*[]) { - "omxplayer.bin", - "--nohdmiclocksync", - "--no-osd", - "--no-keys", - "--loop", - "--layer", "-128", - "--win", "0,0,1,1", - "--orientation", orientation_str, - "--dbus_name", dbus_name, - mgr->player->video_uri, - NULL - } - ); - - if (_ok != 0) { - exit(_ok); - } - exit(0); - } else if (me > 0) { - // I'm the parent! - omxplayer_pid = me; - } else if (me < 0) { - // something went wrong. - ok = errno; - perror("[omxplayer_video_player plugin] Could not spawn omxplayer subprocess. fork"); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_unref_slot; - } - - while (!task.omxplayer_online) { - ok = sd_bus_wait(bus, 1000*1000*5); - if (ok < 0) { - ok = -ok; - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_kill_unregistered_player; - } - - ok = sd_bus_process(bus, NULL); - if (ok < 0) { - ok = -ok; - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); - platch_respond_native_error_std(task.responsehandle, ok); - goto fail_kill_unregistered_player; - } - } - - sd_bus_slot_unref(slot); - slot = NULL; - - duration_us = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Duration", - &err, - 'x', - &duration_us - ); - if (ok != 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // wait for the first frame to appear - { - struct timespec delta = { - .tv_sec = 0, - .tv_nsec = 300*1000*1000 - }; - while (nanosleep(&delta, &delta)); - } - - // pause right on the first frame - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Play", - &err, - &msg, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send initial pause message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - sd_bus_message_unref(msg); - msg = NULL; - - // get the video duration - duration_us = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Duration", - &err, - 'x', - &duration_us - ); - if (ok != 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // get the video width - video_width = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "ResWidth", - &err, - 'x', - &video_width - ); - if (ok < 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - // get the video width - video_height = 0; - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "ResHeight", - &err, - 'x', - &video_height - ); - if (ok < 0) { - respond_sd_bus_error(task.responsehandle, &err); - goto fail_kill_registered_player; - } - - - LOG_DEBUG("respond success on_create(). player_id: %" PRId64 "\n", mgr->player->player_id); - // creation was a success! respond to the dart-side with our player id. - platch_respond_success_std(task.responsehandle, &STDINT64(mgr->player->player_id)); - - has_sent_initialized_event = false; - while (1) { - ok = cqueue_dequeue(q, &task); - - if (task.type == kUpdateView) { - struct omxplayer_mgr_task *peek; - - cqueue_lock(q); - - cqueue_peek_locked(q, (void**) &peek); - while ((peek != NULL) && (peek->type == kUpdateView)) { - cqueue_dequeue_locked(q, &task); - cqueue_peek_locked(q, (void**) &peek); - } - - cqueue_unlock(q); - } - - if (task.type == kCreate) { - printf("[omxplayer_video_player plugin] Omxplayer manager got a creation task, even though the player is already running.\n"); - } else if (task.type == kDispose) { - if (mgr->player->has_view) { - fprintf(stderr, "[omxplayer_video_player plugin] flutter attempted to dispose the video player before its view was disposed.\n"); - - compositor_remove_view_callbacks(mgr->player->view_id); - - mgr->player->has_view = false; - mgr->player->view_id = -1; - } - - // tell omxplayer to quit - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_ROOT_FACE, - "Quit", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send Quit message to omxplayer: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - ok = (int) waitpid(omxplayer_pid, NULL, 0); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] omxplayer quit with exit code %d\n", ok); - } - - sd_bus_unref(bus); - - plugin_registry_remove_receiver(mgr->player->event_channel_name); - - remove_player(mgr->player); - - free(mgr->player); - mgr->player = NULL; - - cqueue_deinit(&mgr->task_queue); - - free(mgr); - mgr = NULL; - - platch_respond_success_std(task.responsehandle, NULL); - - break; - } else if (task.type == kListen) { - platch_respond_success_std(task.responsehandle, NULL); - - if (!has_sent_initialized_event) { - platch_send_success_event_std( - mgr->player->event_channel_name, - &(struct std_value) { - .type = kStdMap, - .size = 4, - .keys = (struct std_value[4]) { - STDSTRING("event"), - STDSTRING("duration"), - STDSTRING("width"), - STDSTRING("height") - }, - .values = (struct std_value[4]) { - STDSTRING("initialized"), - STDINT64(is_stream? INT64_MAX : duration_us / 1000), - STDINT32(video_width), - STDINT32(video_height) - } - } - ); - - has_sent_initialized_event = true; - } - } else if (task.type == kUnlisten) { - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kPlay) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Play", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send play message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kPause) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Pause", - &err, - NULL, - "" - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not send pause message: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - msg = NULL; - - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kUpdateView) { - char video_pos_str[256]; - - // Use integers here even if omxplayer supports floats because if we print floats, - // snprintf might use `,` as the decimal delimiter depending on the locale. - // This is only an issue because I set the application to be locale-aware using setlocale(LC_ALL, "") - // since that's needed for locale support. - snprintf( - video_pos_str, - sizeof(video_pos_str), - "%d %d %d %d", - task.offset_x, - task.offset_y, - task.offset_x + task.width, - task.offset_y + task.height - ); - - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "VideoPos", - &err, - NULL, - "os", - "/obj/not/used", - video_pos_str - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer viewport. %s, %s\n", err.name, err.message); - continue; - } - - if (current_zpos != task.zpos) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetLayer", - &err, - NULL, - "x", - (int64_t) task.zpos - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer layer. %s, %s\n", err.name, err.message); - continue; - } - - current_zpos = task.zpos; - } - -#ifdef OMXPLAYER_SUPPORTS_RUNTIME_ROTATION - if (current_orientation != task.orientation) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetTransform", - &err, - NULL, - "x", - (360 - (int64_t) task.orientation) % 360 - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer rotation. %s, %s\n", err.name, err.message); - continue; - } - current_orientation = task.orientation; - } -#endif - } else if (task.type == kGetPosition) { - int64_t position = 0; - - ok = get_dbus_property( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "Position", - &err, - 'x', - &position - ); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not get omxplayer position: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - position = position / 1000; - - platch_respond_success_std(task.responsehandle, &STDINT64(position)); - } else if (task.type == kSetPosition) { - if (is_stream) { - if (task.position == -1) { - // TODO: implement seek-to-end - - platch_respond_success_std( - task.responsehandle, - NULL - ); - } else { - // Don't allow flutter to seek to anything other than the end on a stream. - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream).\n"); - - platch_respond_error_std( - task.responsehandle, - "state-error", - "Attempted to seek on non-seekable video (a stream)", - NULL - ); - } - } else { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetPosition", - &err, - NULL, - "ox", - "/path/not/used", - (int64_t) (task.position * 1000) - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s, %s\n", strerror(-ok), err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } - } else if (task.type == kSetLooping) { - platch_respond_success_std(task.responsehandle, NULL); - } else if (task.type == kSetVolume) { - ok = sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_PROPERTY_FACE, - DBUS_PROPRETY_SET, - &err, - NULL, - "ssd", - DBUS_OMXPLAYER_PLAYER_FACE, - "Volume", - (double) task.volume - ); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer volume: %s, %s\n", err.name, err.message); - respond_sd_bus_error(task.responsehandle, &err); - continue; - } - - platch_respond_success_std(task.responsehandle, NULL); - } - } - - return (void*) EXIT_SUCCESS; - - - fail_kill_registered_player: - kill(omxplayer_pid, SIGKILL); - waitpid(omxplayer_pid, NULL, 0); - goto fail_close_dbus; - - fail_kill_unregistered_player: - kill(omxplayer_pid, SIGKILL); - waitpid(omxplayer_pid, NULL, 0); - - fail_unref_slot: - sd_bus_slot_unref(slot); - slot = NULL; - - fail_close_dbus: - sd_bus_unref(bus); - - fail_remove_evch_listener: - plugin_registry_remove_receiver(mgr->player->event_channel_name); - remove_player(mgr->player); - free(mgr->player); - cqueue_deinit(&mgr->task_queue); - free(mgr); - mgr = NULL; - return (void*) EXIT_FAILURE; -} - -/// Ensures the bindings to libsystemd are initialized. -static int ensure_binding_initialized(void) { - int ok; - - if (omxpvidpp.initialized) return 0; - - ok = access("/usr/bin/omxplayer.bin", X_OK); - if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] omxplayer doesn't seem to be installed. Please install using 'sudo apt install omxplayer'. access: %s\n", strerror(errno)); - return errno; - } - - omxpvidpp.initialized = true; - - return 0; -} - -/// Respond to the handle with a "initialization failed" message. -static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { - return platch_respond_error_std( - handle, - "couldnotinit", - "omxplayer_video_player plugin failed to initialize libsystemd bindings. See flutter-pi log for details.", - NULL - ); -} - -/******************************************************* - * CHANNEL HANDLERS * - * handle method calls on the method and event channel * - *******************************************************/ -static int on_receive_evch( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - struct omxplayer_video_player *player; - - player = get_player_by_evch(channel); - if (player == NULL) { - return platch_respond_not_implemented(responsehandle); - } - - if STREQ("listen", object->method) { - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kListen, - .responsehandle = responsehandle - }); - } else if STREQ("cancel", object->method) { - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kUnlisten, - .responsehandle = responsehandle - }); - } else { - return platch_respond_not_implemented(responsehandle); - } -} - -static int on_initialize( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - int ok; - - (void) arg; - - ok = ensure_binding_initialized(); - if (ok != 0) { - return respond_init_failed(responsehandle); - } - - LOG_DEBUG("on_initialize\n"); - - return platch_respond_success_std(responsehandle, NULL); -} - -/// Creates a new video player. -/// Should respond to the platform message when the player has established its viewport. -static int on_create( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct omxplayer_mgr *mgr; - enum data_source_type source_type; - struct std_value *temp; - char *asset, *uri, *package_name, *format_hint; - int ok; - - (void) source_type; - (void) package_name; - (void) format_hint; - - ok = ensure_binding_initialized(); - if (ok != 0) { - return respond_init_failed(responsehandle); - } - - temp = stdmap_get_str(arg, "sourceType"); - if (temp != NULL && STDVALUE_IS_STRING(*temp)) { - char *source_type_str = temp->string_value; - - if STREQ("DataSourceType.asset", source_type_str) { - source_type = kDataSourceTypeAsset; - } else if STREQ("DataSourceType.network", source_type_str) { - source_type = kDataSourceTypeNetwork; - } else if STREQ("DataSourceType.file", source_type_str) { - source_type = kDataSourceTypeFile; - } else { - goto invalid_source_type; - } - } else { - invalid_source_type: - - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['sourceType']` to be a stringification of the [DataSourceType] enum." - ); - } - - temp = stdmap_get_str(arg, "asset"); - if (temp == NULL || temp->type == kStdNull) { - asset = NULL; - } else if (temp != NULL && temp->type == kStdString) { - asset = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['asset']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "uri"); - if (temp == NULL || temp->type == kStdNull) { - uri = NULL; - } else if (temp != NULL && temp->type == kStdString) { - uri = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['uri']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "packageName"); - if (temp == NULL || temp->type == kStdNull) { - package_name = NULL; - } else if (temp != NULL && temp->type == kStdString) { - package_name = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['packageName']` to be a String or null." - ); - } - - temp = stdmap_get_str(arg, "formatHint"); - if (temp == NULL || temp->type == kStdNull) { - format_hint = NULL; - } else if (temp != NULL && temp->type == kStdString) { - format_hint = temp->string_value; - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['formatHint']` to be a String or null." - ); - } - - LOG_DEBUG( - "on_create(sourceType: %s, asset: %s, uri: \"%s\", packageName: %s, formatHint: %s)\n", - source_type == kDataSourceTypeAsset ? "asset" : source_type == kDataSourceTypeNetwork ? "network" : "file", - asset, - uri, - package_name, - format_hint - ); - - mgr = calloc(1, sizeof *mgr); - if (mgr == NULL) { - return platch_respond_native_error_std(responsehandle, ENOMEM); - } - - ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_SIZE); - if (ok != 0) { - goto fail_free_mgr; - } - - // Allocate the player metadata - player = calloc(1, sizeof(*player)); - if (player == NULL) { - goto fail_deinit_task_queue; - } - - player->player_id = omxpvidpp.next_unused_player_id++; - player->mgr = mgr; - if (asset != NULL) { - snprintf(player->video_uri, sizeof(player->video_uri), "%s/%s", flutterpi.flutter.paths->asset_bundle_path, asset); - } else { - strncpy(player->video_uri, uri, sizeof(player->video_uri)); - } - - mgr->player = player; - - ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kCreate, - .responsehandle = responsehandle, - .orientation = flutterpi.view.rotation - }); - - if (ok != 0) { - goto fail_free_player; - } - - snprintf( - player->event_channel_name, - sizeof(player->event_channel_name), - "flutter.io/omxplayerVideoPlayer/videoEvents%" PRId64, - player->player_id - ); - - // add it to our player collection - ok = add_player(player); - if (ok != 0) { - goto fail_free_player; - } - - // set a receiver on the videoEvents event channel - ok = plugin_registry_set_receiver( - player->event_channel_name, - kStandardMethodCall, - on_receive_evch - ); - if (ok != 0) { - goto fail_remove_player; - } - - ok = pthread_create(&mgr->thread, NULL, mgr_entry, mgr); - if (ok != 0) { - goto fail_remove_evch_listener; - } - - return 0; - - - fail_remove_evch_listener: - plugin_registry_remove_receiver(player->event_channel_name); - - fail_remove_player: - remove_player(player); - - fail_free_player: - free(player); - player = NULL; - - fail_deinit_task_queue: - cqueue_deinit(&mgr->task_queue); - - fail_free_mgr: - free(mgr); - mgr = NULL; - - return platch_respond_native_error_std(responsehandle, ok); -} - -static int on_dispose( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) { - return ok; - } - - LOG_DEBUG("on_dispose(%"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kDispose, - .responsehandle = responsehandle - }); -} - -static int on_set_looping( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - bool loop; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "looping"); - if (STDVALUE_IS_BOOL(*temp)) { - loop = STDVALUE_AS_BOOL(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['looping']` to be a boolean." - ); - } - - LOG_DEBUG("on_set_looping(player_id: %"PRId64", looping: %s)\n", player->player_id, loop ? "yes" : "no"); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetLooping, - .loop = loop, - .responsehandle = responsehandle - }); -} - -static int on_set_volume( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - float volume; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "volume"); - if (STDVALUE_IS_FLOAT(*temp)) { - volume = STDVALUE_AS_FLOAT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['volume']` to be a float/double." - ); - } - - LOG_DEBUG("on_set_volume(player_id: %"PRId64", volume: %f)\n", player->player_id, volume); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetVolume, - .volume = volume, - .responsehandle = responsehandle - }); -} - -static int on_play( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_play(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kPlay, - .responsehandle = responsehandle - }); -} - -static int on_get_position( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_get_position(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kGetPosition, - .responsehandle = responsehandle - }); -} - -static int on_seek_to( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t position; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "position"); - if (STDVALUE_IS_INT(*temp)) { - position = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['position']` to be an integer." - ); - } - - LOG_DEBUG("on_seek_to(player_id: %"PRId64", position: %"PRId64")\n", player->player_id, position); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kSetPosition, - .position = position, - .responsehandle = responsehandle - }); -} - -static int on_pause( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - LOG_DEBUG("on_pause(player_id: %"PRId64")\n", player->player_id); - - return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { - .type = kPause, - .responsehandle = responsehandle - }); -} - -static int on_create_platform_view( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t view_id; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "platformViewId"); - if (STDVALUE_IS_INT(*temp)) { - view_id = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['platformViewId']` to be an integer." - ); - } - - LOG_DEBUG("on_create_platform_view(player_id: %"PRId64", platform view id: %"PRId64")\n", player->player_id, view_id); - - if (player->has_view) { - LOG_ERROR("Flutter attempted to register more than one platform view for one player instance.\n"); - return platch_respond_illegal_arg_std( - responsehandle, - "Attempted to register more than one platform view for this player instance." - ); - } else { - ok = compositor_set_view_callbacks( - view_id, - on_mount, - on_unmount, - on_update_view, - NULL, - player - ); - if (ok != 0) { - return platch_respond_native_error_std(responsehandle, ok); - } - - player->has_view = true; - player->view_id = view_id; - - return platch_respond_success_std(responsehandle, NULL); - } -} - -static int on_dispose_platform_view( - struct std_value *arg, - FlutterPlatformMessageResponseHandle* responsehandle -) { - struct omxplayer_video_player *player; - struct std_value *temp; - int64_t view_id; - int ok; - - ok = get_player_from_map_arg(arg, &player, responsehandle); - if (ok != 0) return ok; - - temp = stdmap_get_str(arg, "platformViewId"); - if (STDVALUE_IS_INT(*temp)) { - view_id = STDVALUE_AS_INT(*temp); - } else { - return platch_respond_illegal_arg_std( - responsehandle, - "Expected `arg['platformViewId']` to be an integer." - ); - } - - LOG_DEBUG("on_dispose_platform_view(player_id: %"PRId64", platform view id: %"PRId64")\n", player->player_id, view_id); - - if (player->view_id != view_id) { - LOG_ERROR("Flutter attempted to dispose an omxplayer platform view that is not associated with this player.\n"); - - return platch_respond_illegal_arg_std(responsehandle, "Attempted to dispose on omxplayer view that is not associated with `arg['playerId']`."); - } else { - ok = compositor_remove_view_callbacks(view_id); - if (ok != 0) { - LOG_ERROR("Could not remove view callbacks for platform view %" PRId64 ". compositor_remove_view_callbacks: %s\n", view_id, strerror(ok)); - return platch_respond_native_error_std(responsehandle, ok); - } - - player->has_view = false; - player->view_id = -1; - - // hide omxplayer - cqueue_enqueue(&player->mgr->task_queue, &(struct omxplayer_mgr_task) { - .type = kUpdateView, - .offset_x = 0, - .offset_y = 0, - .width = 1, - .height = 1, - .zpos = -128 - }); - - return platch_respond_success_std(responsehandle, NULL); - } -} - -/// Called when a platform channel object is received on the method channel. -/// Finds out which method was called an then calls the corresponding above method, -/// or else responds with not implemented. -static int on_receive_mch( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - (void) channel; - - if STREQ("init", object->method) { - return on_initialize(&object->std_arg, responsehandle); - } else if STREQ("create", object->method) { - return on_create(&object->std_arg, responsehandle); - } else if STREQ("dispose", object->method) { - return on_dispose(&object->std_arg, responsehandle); - } else if STREQ("setLooping", object->method) { - return on_set_looping(&object->std_arg, responsehandle); - } else if STREQ("setVolume", object->method) { - return on_set_volume(&object->std_arg, responsehandle); - } else if STREQ("play", object->method) { - return on_play(&object->std_arg, responsehandle); - } else if STREQ("pause", object->method) { - return on_pause(&object->std_arg, responsehandle); - } else if STREQ("getPosition", object->method) { - return on_get_position(&object->std_arg, responsehandle); - } else if STREQ("seekTo", object->method) { - return on_seek_to(&object->std_arg, responsehandle); - } else if STREQ("createPlatformView", object->method) { - return on_create_platform_view(&object->std_arg, responsehandle); - } else if STREQ("disposePlatformView", object->method) { - return on_dispose_platform_view(&object->std_arg, responsehandle); - } - - return platch_respond_not_implemented(responsehandle); -} - -int8_t omxpvidpp_is_present(void) { - return plugin_registry_is_plugin_present(flutterpi.plugin_registry, "omxplayer video_player plugin"); -} - -enum plugin_init_result omxpvidpp_init(struct flutterpi *flutterpi, void **userdata_out) { - int ok; - - (void) flutterpi; - (void) userdata_out; - - ok = plugin_registry_set_receiver("flutter.io/omxplayerVideoPlayer", kStandardMethodCall, on_receive_mch); - if (ok != 0) { - return kError_PluginInitResult; - } - - return kInitialized_PluginInitResult; -} - -void omxpvidpp_deinit(struct flutterpi *flutterpi, void *userdata) { - plugin_registry_remove_receiver("flutter.io/omxplayerVideoPlayer"); - (void) flutterpi; - (void) userdata; -} - -FLUTTERPI_PLUGIN( - "omxplayer video_player plugin", - omxpvidpp, - omxpvidpp_init, - omxpvidpp_deinit -) \ No newline at end of file diff --git a/src/plugins/raw_keyboard.c b/src/plugins/raw_keyboard.c index 43af1a50..a814088a 100644 --- a/src/plugins/raw_keyboard.c +++ b/src/plugins/raw_keyboard.c @@ -1,14 +1,645 @@ +#include "raw_keyboard.h" + #include #include -#include #include #include +#include + +#include +#include +#include + +#include "flutter-pi.h" +#include "keyboard.h" +#include "pluginregistry.h" +#include "util/asserts.h" + +ATTR_CONST static uint64_t apply_key_plane(uint64_t keycode, uint64_t plane) { + return (keycode & 0x000FFFFFFFF) | plane; +} + +ATTR_CONST static uint64_t apply_gtk_key_plane(uint64_t keycode) { + return apply_key_plane(keycode, 0x01500000000); +} + +ATTR_CONST static uint64_t apply_unicode_key_plane(uint64_t keycode) { + return apply_key_plane(keycode, 0x00000000000); +} + +ATTR_CONST static uint64_t apply_flutter_key_plane(uint64_t keycode) { + return apply_key_plane(keycode, 0x00200000000); +} + +ATTR_CONST static uint64_t physical_key_for_evdev_keycode(uint16_t evdev_keycode) { + // clang-format off + static const uint32_t physical_keys[] = { + [KEY_RESERVED] = 0, + [KEY_ESC] = 0x00070029, // escape + [KEY_1] = 0x0007001e, // digit1 + [KEY_2] = 0x0007001f, // digit2 + [KEY_3] = 0x00070020, // digit3 + [KEY_4] = 0x00070021, // digit4 + [KEY_5] = 0x00070022, // digit5 + [KEY_6] = 0x00070023, // digit6 + [KEY_7] = 0x00070024, // digit7 + [KEY_8] = 0x00070025, // digit8 + [KEY_9] = 0x00070026, // digit9 + [KEY_0] = 0x00070027, // digit0 + [KEY_MINUS] = 0x0007002d, // minus + [KEY_EQUAL] = 0x0007002e, // equal + [KEY_BACKSPACE] = 0x0007002a, // backspace + [KEY_TAB] = 0x0007002b, // tab + [KEY_Q] = 0x00070014, // keyQ + [KEY_W] = 0x0007001a, // keyW + [KEY_E] = 0x00070008, // keyE + [KEY_R] = 0x00070015, // keyR + [KEY_T] = 0x00070017, // keyT + [KEY_Y] = 0x0007001c, // keyY + [KEY_U] = 0x00070018, // keyU + [KEY_I] = 0x0007000c, // keyI + [KEY_O] = 0x00070012, // keyO + [KEY_P] = 0x00070013, // keyP + [KEY_LEFTBRACE] = 0x0007002f, // bracketLeft + [KEY_RIGHTBRACE] = 0x00070030, // bracketRight + [KEY_ENTER] = 0x00070028, // enter + [KEY_LEFTCTRL] = 0x000700e0, // controlLeft + [KEY_A] = 0x00070004, // keyA + [KEY_S] = 0x00070016, // keyS + [KEY_D] = 0x00070007, // keyD + [KEY_F] = 0x00070009, // keyF + [KEY_G] = 0x0007000a, // keyG + [KEY_H] = 0x0007000b, // keyH + [KEY_J] = 0x0007000d, // keyJ + [KEY_K] = 0x0007000e, // keyK + [KEY_L] = 0x0007000f, // keyL + [KEY_SEMICOLON] = 0x00070033, // semicolon + [KEY_APOSTROPHE] = 0x00070034, // quote + [KEY_GRAVE] = 0x00070035, // backquote + [KEY_LEFTSHIFT] = 0x000700e1, // shiftLeft + [KEY_BACKSLASH] = 0x00070031, // backslash + [KEY_Z] = 0x0007001d, // keyZ + [KEY_X] = 0x0007001b, // keyX + [KEY_C] = 0x00070006, // keyC + [KEY_V] = 0x00070019, // keyV + [KEY_B] = 0x00070005, // keyB + [KEY_N] = 0x00070011, // keyN + [KEY_M] = 0x00070010, // keyM + [KEY_COMMA] = 0x00070036, // comma + [KEY_DOT] = 0x00070037, // period + [KEY_SLASH] = 0x00070038, // slash + [KEY_RIGHTSHIFT] = 0x000700e5, // shiftRight + [KEY_KPASTERISK] = 0x00070055, // numpadMultiply + [KEY_LEFTALT] = 0x000700e2, // altLeft + [KEY_SPACE] = 0x0007002c, // space + [KEY_CAPSLOCK] = 0x00070039, // capsLock + [KEY_F1] = 0x0007003a, // f1 + [KEY_F2] = 0x0007003b, // f2 + [KEY_F3] = 0x0007003c, // f3 + [KEY_F4] = 0x0007003d, // f4 + [KEY_F5] = 0x0007003e, // f5 + [KEY_F6] = 0x0007003f, // f6 + [KEY_F7] = 0x00070040, // f7 + [KEY_F8] = 0x00070041, // f8 + [KEY_F9] = 0x00070042, // f9 + [KEY_F10] = 0x00070043, // f10 + [KEY_NUMLOCK] = 0x00070053, // numLock + [KEY_SCROLLLOCK] = 0x00070047, // scrollLock + [KEY_KP7] = 0x0007005f, // numpad7 + [KEY_KP8] = 0x00070060, // numpad8 + [KEY_KP9] = 0x00070061, // numpad9 + [KEY_KPMINUS] = 0x00070056, // numpadSubtract + [KEY_KP4] = 0x0007005c, // numpad4 + [KEY_KP5] = 0x0007005d, // numpad5 + [KEY_KP6] = 0x0007005e, // numpad6 + [KEY_KPPLUS] = 0x00070057, // numpadAdd + [KEY_KP1] = 0x00070059, // numpad1 + [KEY_KP2] = 0x0007005a, // numpad2 + [KEY_KP3] = 0x0007005b, // numpad3 + [KEY_KP0] = 0x00070062, // numpad0 + [KEY_KPDOT] = 0x00070063, // numpadDecimal + [KEY_ZENKAKUHANKAKU] = 0x00070094, // lang5 + [KEY_102ND] = 0x00070064, // intlBackslash + [KEY_F11] = 0x00070044, // f11 + [KEY_F12] = 0x00070045, // f12 + [KEY_RO] = 0x00070087, // intlRo + [KEY_KATAKANA] = 0x00070092, // lang3 + [KEY_HIRAGANA] = 0x00070093, // lang4 + [KEY_HENKAN] = 0x0007008a, // convert + [KEY_KATAKANAHIRAGANA] = 0x00070088, // kanaMode + [KEY_MUHENKAN] = 0x0007008b, // nonConvert + [KEY_KPJPCOMMA] = 0, + [KEY_KPENTER] = 0x00070058, // numpadEnter + [KEY_RIGHTCTRL] = 0x000700e4, // controlRight + [KEY_KPSLASH] = 0x00070054, // numpadDivide + [KEY_SYSRQ] = 0x00070046, // printScreen + [KEY_RIGHTALT] = 0x000700e6, // altRight + [KEY_LINEFEED] = 0, + [KEY_HOME] = 0x0007004a, // home + [KEY_UP] = 0x00070052, // arrowUp + [KEY_PAGEUP] = 0x0007004b, // pageUp + [KEY_LEFT] = 0x00070050, // arrowLeft + [KEY_RIGHT] = 0x0007004f, // arrowRight + [KEY_END] = 0x0007004d, // end + [KEY_DOWN] = 0x00070051, // arrowDown + [KEY_PAGEDOWN] = 0x0007004e, // pageDown + [KEY_INSERT] = 0x00070049, // insert + [KEY_DELETE] = 0x0007004c, // delete + [KEY_MACRO] = 0, + [KEY_MUTE] = 0x0007007f, // audioVolumeMute + [KEY_VOLUMEDOWN] = 0x00070081, // audioVolumeDown + [KEY_VOLUMEUP] = 0x00070080, // audioVolumeUp + [KEY_POWER] = 0x00070066, // power + [KEY_KPEQUAL] = 0x00070067, // numpadEqual + [KEY_KPPLUSMINUS] = 0x000700d7, // numpadSignChange + [KEY_PAUSE] = 0x00070048, // pause + [KEY_SCALE] = 0x000c029f, // showAllWindows + [KEY_KPCOMMA] = 0x00070085, // numpadComma + [KEY_HANGEUL] = 0x00070090, // lang1 + [KEY_HANJA] = 0x00070091, // lang2 + [KEY_YEN] = 0x00070089, // intlYen + [KEY_LEFTMETA] = 0x000700e3, // metaLeft + [KEY_RIGHTMETA] = 0x000700e7, // metaRight + [KEY_COMPOSE] = 0x00070065, // contextMenu + [KEY_STOP] = 0x000c0226, // browserStop + [KEY_AGAIN] = 0x00070079, // again + [KEY_PROPS] = 0, + [KEY_UNDO] = 0x0007007a, // undo + [KEY_FRONT] = 0x00070077, // select + [KEY_COPY] = 0x0007007c, // copy + [KEY_OPEN] = 0x00070074, // open + [KEY_PASTE] = 0x0007007d, // paste + [KEY_FIND] = 0x0007007e, // find + [KEY_CUT] = 0x0007007b, // cut + [KEY_HELP] = 0x00070075, // help + [KEY_MENU] = 0, + [KEY_CALC] = 0x000c0192, // launchApp2 + [KEY_SETUP] = 0, + [KEY_SLEEP] = 0x00010082, // sleep + [KEY_WAKEUP] = 0x00010083, // wakeUp + [KEY_FILE] = 0x000c0194, // launchApp1 + [KEY_SENDFILE] = 0, + [KEY_DELETEFILE] = 0, + [KEY_XFER] = 0, + [KEY_PROG1] = 0, + [KEY_PROG2] = 0, + [KEY_WWW] = 0x000c0196, // launchInternetBrowser + [KEY_MSDOS] = 0, + [KEY_COFFEE] = 0x000c019e, // lockScreen + [KEY_ROTATE_DISPLAY] = 0, + [KEY_CYCLEWINDOWS] = 0, + [KEY_MAIL] = 0x000c018a, // launchMail + [KEY_BOOKMARKS] = 0x000c022a, // browserFavorites + [KEY_COMPUTER] = 0, + [KEY_BACK] = 0x000c0224, // browserBack + [KEY_FORWARD] = 0x000c0225, // browserForward + [KEY_CLOSECD] = 0, + [KEY_EJECTCD] = 0x000c00b8, // eject + [KEY_EJECTCLOSECD] = 0, + [KEY_NEXTSONG] = 0x000c00b5, // mediaTrackNext + [KEY_PLAYPAUSE] = 0x000c00cd, // mediaPlayPause + [KEY_PREVIOUSSONG] = 0x000c00b6, // mediaTrackPrevious + [KEY_STOPCD] = 0x000c00b7, // mediaStop + [KEY_RECORD] = 0x000c00b2, // mediaRecord + [KEY_REWIND] = 0x000c00b4, // mediaRewind + [KEY_PHONE] = 0x000c008c, // launchPhone + [KEY_ISO] = 0, + [KEY_CONFIG] = 0x000c0183, // mediaSelect + [KEY_HOMEPAGE] = 0x000c0223, // browserHome + [KEY_REFRESH] = 0x000c0227, // browserRefresh + [KEY_EXIT] = 0x000c0094, // exit + [KEY_MOVE] = 0, + [KEY_EDIT] = 0, + [KEY_SCROLLUP] = 0, + [KEY_SCROLLDOWN] = 0, + [KEY_KPLEFTPAREN] = 0x000700b6, // numpadParenLeft + [KEY_KPRIGHTPAREN] = 0x000700b7, // numpadParenRight + [KEY_NEW] = 0x000c0201, // newKey + [KEY_REDO] = 0x000c0279, // redo + [KEY_F13] = 0x00070068, // f13 + [KEY_F14] = 0x00070069, // f14 + [KEY_F15] = 0x0007006a, // f15 + [KEY_F16] = 0x0007006b, // f16 + [KEY_F17] = 0x0007006c, // f17 + [KEY_F18] = 0x0007006d, // f18 + [KEY_F19] = 0x0007006e, // f19 + [KEY_F20] = 0x0007006f, // f20 + [KEY_F21] = 0x00070070, // f21 + [KEY_F22] = 0x00070071, // f22 + [KEY_F23] = 0x00070072, // f23 + [KEY_F24] = 0x00070073, // f24 + [KEY_PLAYCD] = 0, + [KEY_PAUSECD] = 0x000c00b1, // mediaPause + [KEY_PROG3] = 0, + [KEY_PROG4] = 0, + +// present since kernel 5.17 +#ifndef KEY_ALL_APPLICATIONS + #define KEY_ALL_APPLICATIONS 204 +#endif + [KEY_ALL_APPLICATIONS] = 0, + [KEY_SUSPEND] = 0, + [KEY_CLOSE] = 0x000c0203, // close + [KEY_PLAY] = 0x000c00b0, // mediaPlay + [KEY_FASTFORWARD] = 0x000c00b3, // mediaFastForward + [KEY_BASSBOOST] = 0x000c00e5, // bassBoost + [KEY_PRINT] = 0x000c0208, // print + [KEY_HP] = 0, + [KEY_CAMERA] = 0, + [KEY_SOUND] = 0, + [KEY_QUESTION] = 0, + [KEY_EMAIL] = 0, + [KEY_CHAT] = 0, + [KEY_SEARCH] = 0x000c0221, // browserSearch + [KEY_CONNECT] = 0, + [KEY_FINANCE] = 0, + [KEY_SPORT] = 0, + [KEY_SHOP] = 0, + [KEY_ALTERASE] = 0, + [KEY_CANCEL] = 0, + [KEY_BRIGHTNESSDOWN] = 0x000c0070, // brightnessDown + [KEY_BRIGHTNESSUP] = 0x000c006f, // brightnessUp + [KEY_MEDIA] = 0, + [KEY_SWITCHVIDEOMODE] = 0x000100b5, // displayToggleIntExt + [KEY_KBDILLUMTOGGLE] = 0, + [KEY_KBDILLUMDOWN] = 0x000c007a, // kbdIllumDown + [KEY_KBDILLUMUP] = 0x000c0079, // kbdIllumUp + [KEY_SEND] = 0x000c028c, // mailSend + [KEY_REPLY] = 0x000c0289, // mailReply + [KEY_FORWARDMAIL] = 0x000c028b, // mailForward + [KEY_SAVE] = 0x000c0207, // save + [KEY_DOCUMENTS] = 0x000c01a7, // launchDocuments + [KEY_BATTERY] = 0, + [KEY_BLUETOOTH] = 0, + [KEY_WLAN] = 0, + [KEY_UWB] = 0, + [KEY_UNKNOWN] = 0, + [KEY_VIDEO_NEXT] = 0, + [KEY_VIDEO_PREV] = 0, + [KEY_BRIGHTNESS_CYCLE] = 0, + [KEY_BRIGHTNESS_AUTO] = 0x000c0075, // brightnessAuto + [KEY_DISPLAY_OFF] = 0, + [KEY_WWAN] = 0, + [KEY_RFKILL] = 0, + [KEY_MICMUTE] = 0x00000018, // microphoneMuteToggle + // reserved for buttons (BTN_MISC ... BTN_GEAR_UP) + [KEY_OK] = 0, + [KEY_SELECT] = 0, + [KEY_GOTO] = 0, + [KEY_CLEAR] = 0, + [KEY_POWER2] = 0, + [KEY_OPTION] = 0, + [KEY_INFO] = 0x000c0060, // info + [KEY_TIME] = 0, + [KEY_VENDOR] = 0, + [KEY_ARCHIVE] = 0, + [KEY_PROGRAM] = 0x000c008d, // programGuide + [KEY_CHANNEL] = 0, + [KEY_FAVORITES] = 0, + [KEY_EPG] = 0, + [KEY_PVR] = 0, + [KEY_MHP] = 0, + [KEY_LANGUAGE] = 0, + [KEY_TITLE] = 0, + [KEY_SUBTITLE] = 0x000c0061, // closedCaptionToggle + [KEY_ANGLE] = 0, + [KEY_ZOOM] = 0x000c0232, // zoomToggle + [KEY_MODE] = 0, + [KEY_KEYBOARD] = 0x000c01ae, // launchKeyboardLayout + [KEY_SCREEN] = 0, + [KEY_PC] = 0, + [KEY_TV] = 0, + [KEY_TV2] = 0, + [KEY_VCR] = 0, + [KEY_VCR2] = 0, + [KEY_SAT] = 0, + [KEY_SAT2] = 0, + [KEY_CD] = 0, + [KEY_TAPE] = 0, + [KEY_RADIO] = 0, + [KEY_TUNER] = 0, + [KEY_PLAYER] = 0, + [KEY_TEXT] = 0, + [KEY_DVD] = 0, + [KEY_AUX] = 0, + [KEY_MP3] = 0, + [KEY_AUDIO] = 0x000c01b7, // launchAudioBrowser + [KEY_VIDEO] = 0, + [KEY_DIRECTORY] = 0, + [KEY_LIST] = 0, + [KEY_MEMO] = 0, + [KEY_CALENDAR] = 0x000c018e, // launchCalendar + [KEY_RED] = 0, + [KEY_GREEN] = 0, + [KEY_YELLOW] = 0, + [KEY_BLUE] = 0, + [KEY_CHANNELUP] = 0, + [KEY_CHANNELDOWN] = 0, + [KEY_FIRST] = 0, + [KEY_LAST] = 0x000c0083, // mediaLast + [KEY_AB] = 0, + [KEY_NEXT] = 0, + [KEY_RESTART] = 0, + [KEY_SLOW] = 0, + [KEY_SHUFFLE] = 0x000c009c, // channelUp + [KEY_BREAK] = 0x000c009d, // channelDown + [KEY_PREVIOUS] = 0, + [KEY_DIGITS] = 0, + [KEY_TEEN] = 0, + [KEY_TWEN] = 0, + [KEY_VIDEOPHONE] = 0, + [KEY_GAMES] = 0, + [KEY_ZOOMIN] = 0x000c022d, // zoomIn + [KEY_ZOOMOUT] = 0x000c022e, // zoomOut + [KEY_ZOOMRESET] = 0, + [KEY_WORDPROCESSOR] = 0x000c0184, // launchWordProcessor + [KEY_EDITOR] = 0, + [KEY_SPREADSHEET] = 0x000c0186, // launchSpreadsheet + [KEY_GRAPHICSEDITOR] = 0, + [KEY_PRESENTATION] = 0, + [KEY_DATABASE] = 0, + [KEY_NEWS] = 0, + [KEY_VOICEMAIL] = 0, + [KEY_ADDRESSBOOK] = 0x000c018d, // launchContacts + [KEY_MESSENGER] = 0, + [KEY_DISPLAYTOGGLE] = 0x000c0072, // brightnessToggle + [KEY_SPELLCHECK] = 0x000c01ab, // spellCheck + [KEY_LOGOFF] = 0x000c019c, // logOff + [KEY_CONTROLPANEL] = 0x000c019f, // launchControlPanel + [KEY_APPSELECT] = 0x000c01a2, // selectTask + [KEY_SCREENSAVER] = 0x000c01b1, // launchScreenSaver + [KEY_VOICECOMMAND] = 0x000c00cf, // speechInputToggle + +// present since kernel 4.13 +#ifndef KEY_ASSISTANT + #define KEY_ASSISTANT 0x247 +#endif + + [KEY_ASSISTANT] = 0x000c01cb, // launchAssistant + +// present since kernel 5.2 +#ifndef KEY_KBD_LAYOUT_NEXT +# define KEY_KBD_LAYOUT_NEXT 0x248 +#endif + [KEY_KBD_LAYOUT_NEXT] = 0x000c029d, // keyboardLayoutSelect -#include -#include -#include +// present since kernel 5.13 +#ifndef KEY_EMOJI_PICKER +# define KEY_EMOJI_PICKER 0x249 +#endif + [KEY_EMOJI_PICKER] = 0, -#include +// present since kernel 5.17 +#ifndef KEY_DICTATE +# define KEY_DICTATE 0x24a +#endif + [KEY_DICTATE] = 0, + + // unused + [KEY_BRIGHTNESS_MIN] = 0x000c0073, // brightnessMinimum + [KEY_BRIGHTNESS_MAX] = 0x000c0074, // brightnessMaximum + // KEY_KBDINPUTASSIST_PREV ... KEY_ONSCREEN_KEYBOARD + +// present since kernel 5.5 +#ifndef KEY_PRIVACY_SCREEN_TOGGLE +# define KEY_PRIVACY_SCREEN_TOGGLE 0x279 +#endif + [KEY_PRIVACY_SCREEN_TOGGLE] = 0x00000017 // privacyScreenToggle + }; + // clang-format on + + uint64_t physical = 0; + if (evdev_keycode < ARRAY_SIZE(physical_keys)) { + physical = physical_keys[evdev_keycode]; + } + + // In case we don't have a match for this evdev keycode, + // instead return the XKB keycode (== evdev keycode + 8) with the GTK key plane. + if (physical == 0) { + physical = apply_gtk_key_plane(evdev_keycode + 8ull); + } + + return physical; +} + +ATTR_CONST static uint64_t physical_key_for_xkb_keycode(xkb_keycode_t xkb_keycode) { + assert(xkb_keycode >= 8); + return physical_key_for_evdev_keycode(xkb_keycode - 8); +} + +ATTR_CONST static char eascii_to_lower(unsigned char n) { + if (n >= 'A' && n <= 'Z') { + return n - 'A' + 'a'; + } + + if (n >= XKB_KEY_Agrave && n <= XKB_KEY_THORN && n != XKB_KEY_multiply) { + return n - XKB_KEY_Agrave + XKB_KEY_agrave; + } + + return n; +} + +ATTR_CONST static uint32_t logical_key_for_xkb_keysym(xkb_keysym_t keysym) { + // clang-format off + static const uint64_t logical_keys_1[] = { + [0x0000fd06 - 0xfd06] = 0x00100000405, // 3270_EraseEOF + [0x0000fd0e - 0xfd06] = 0x00100000503, // 3270_Attn + [0x0000fd15 - 0xfd06] = 0x00100000402, // 3270_Copy + [0x0000fd16 - 0xfd06] = 0x00100000d2f, // 3270_Play + [0x0000fd1b - 0xfd06] = 0x00100000406, // 3270_ExSelect + [0x0000fd1d - 0xfd06] = 0x00100000608, // 3270_PrintScreen + [0x0000fd1e - 0xfd06] = 0x0010000000d, // 3270_Enter + [0x0000fe03 - 0xfd06] = 0x00200000105, // ISO_Level3_Shift + [0x0000fe08 - 0xfd06] = 0x00100000709, // ISO_Next_Group + [0x0000fe0a - 0xfd06] = 0x0010000070a, // ISO_Prev_Group + [0x0000fe0c - 0xfd06] = 0x00100000707, // ISO_First_Group + [0x0000fe0e - 0xfd06] = 0x00100000708, // ISO_Last_Group + [0x0000fe20 - 0xfd06] = 0x00100000009, // ISO_Left_Tab + [0x0000fe34 - 0xfd06] = 0x0010000000d, // ISO_Enter + [0x0000ff08 - 0xfd06] = 0x00100000008, // BackSpace + [0x0000ff09 - 0xfd06] = 0x00100000009, // Tab + [0x0000ff0b - 0xfd06] = 0x00100000401, // Clear + [0x0000ff0d - 0xfd06] = 0x0010000000d, // Return + [0x0000ff13 - 0xfd06] = 0x00100000509, // Pause + [0x0000ff14 - 0xfd06] = 0x0010000010c, // Scroll_Lock + [0x0000ff1b - 0xfd06] = 0x0010000001b, // Escape + [0x0000ff21 - 0xfd06] = 0x00100000719, // Kanji + [0x0000ff24 - 0xfd06] = 0x0010000071b, // Romaji + [0x0000ff25 - 0xfd06] = 0x00100000716, // Hiragana + [0x0000ff26 - 0xfd06] = 0x0010000071a, // Katakana + [0x0000ff27 - 0xfd06] = 0x00100000717, // Hiragana_Katakana + [0x0000ff28 - 0xfd06] = 0x0010000071c, // Zenkaku + [0x0000ff29 - 0xfd06] = 0x00100000715, // Hankaku + [0x0000ff2a - 0xfd06] = 0x0010000071d, // Zenkaku_Hankaku + [0x0000ff2f - 0xfd06] = 0x00100000714, // Eisu_Shift + [0x0000ff31 - 0xfd06] = 0x00100000711, // Hangul + [0x0000ff34 - 0xfd06] = 0x00100000712, // Hangul_Hanja + [0x0000ff37 - 0xfd06] = 0x00100000703, // Codeinput + [0x0000ff3c - 0xfd06] = 0x00100000710, // SingleCandidate + [0x0000ff3e - 0xfd06] = 0x0010000070e, // PreviousCandidate + [0x0000ff50 - 0xfd06] = 0x00100000306, // Home + [0x0000ff51 - 0xfd06] = 0x00100000302, // Left + [0x0000ff52 - 0xfd06] = 0x00100000304, // Up + [0x0000ff53 - 0xfd06] = 0x00100000303, // Right + [0x0000ff54 - 0xfd06] = 0x00100000301, // Down + [0x0000ff55 - 0xfd06] = 0x00100000308, // Page_Up + [0x0000ff56 - 0xfd06] = 0x00100000307, // Page_Down + [0x0000ff57 - 0xfd06] = 0x00100000305, // End + [0x0000ff60 - 0xfd06] = 0x0010000050c, // Select + [0x0000ff61 - 0xfd06] = 0x00100000a0c, // Print + [0x0000ff62 - 0xfd06] = 0x00100000506, // Execute + [0x0000ff63 - 0xfd06] = 0x00100000407, // Insert + [0x0000ff65 - 0xfd06] = 0x0010000040a, // Undo + [0x0000ff66 - 0xfd06] = 0x00100000409, // Redo + [0x0000ff67 - 0xfd06] = 0x00100000505, // Menu + [0x0000ff68 - 0xfd06] = 0x00100000507, // Find + [0x0000ff69 - 0xfd06] = 0x00100000504, // Cancel + [0x0000ff6a - 0xfd06] = 0x00100000508, // Help + [0x0000ff7e - 0xfd06] = 0x0010000070b, // Mode_switch + [0x0000ff7f - 0xfd06] = 0x0010000010a, // Num_Lock + [0x0000ff80 - 0xfd06] = 0x00000000020, // KP_Space + [0x0000ff89 - 0xfd06] = 0x00100000009, // KP_Tab + [0x0000ff8d - 0xfd06] = 0x0020000020d, // KP_Enter + [0x0000ff91 - 0xfd06] = 0x00100000801, // KP_F1 + [0x0000ff92 - 0xfd06] = 0x00100000802, // KP_F2 + [0x0000ff93 - 0xfd06] = 0x00100000803, // KP_F3 + [0x0000ff94 - 0xfd06] = 0x00100000804, // KP_F4 + [0x0000ff95 - 0xfd06] = 0x00200000237, // KP_Home + [0x0000ff96 - 0xfd06] = 0x00200000234, // KP_Left + [0x0000ff97 - 0xfd06] = 0x00200000238, // KP_Up + [0x0000ff98 - 0xfd06] = 0x00200000236, // KP_Right + [0x0000ff99 - 0xfd06] = 0x00200000232, // KP_Down + [0x0000ff9a - 0xfd06] = 0x00200000239, // KP_Page_Up + [0x0000ff9b - 0xfd06] = 0x00200000233, // KP_Page_Down + [0x0000ff9c - 0xfd06] = 0x00200000231, // KP_End + [0x0000ff9e - 0xfd06] = 0x00200000230, // KP_Insert + [0x0000ff9f - 0xfd06] = 0x0020000022e, // KP_Delete + [0x0000ffaa - 0xfd06] = 0x0020000022a, // KP_Multiply + [0x0000ffab - 0xfd06] = 0x0020000022b, // KP_Add + [0x0000ffad - 0xfd06] = 0x0020000022d, // KP_Subtract + [0x0000ffae - 0xfd06] = 0x0000000002e, // KP_Decimal + [0x0000ffaf - 0xfd06] = 0x0020000022f, // KP_Divide + [0x0000ffb0 - 0xfd06] = 0x00200000230, // KP_0 + [0x0000ffb1 - 0xfd06] = 0x00200000231, // KP_1 + [0x0000ffb2 - 0xfd06] = 0x00200000232, // KP_2 + [0x0000ffb3 - 0xfd06] = 0x00200000233, // KP_3 + [0x0000ffb4 - 0xfd06] = 0x00200000234, // KP_4 + [0x0000ffb5 - 0xfd06] = 0x00200000235, // KP_5 + [0x0000ffb6 - 0xfd06] = 0x00200000236, // KP_6 + [0x0000ffb7 - 0xfd06] = 0x00200000237, // KP_7 + [0x0000ffb8 - 0xfd06] = 0x00200000238, // KP_8 + [0x0000ffb9 - 0xfd06] = 0x00200000239, // KP_9 + [0x0000ffbd - 0xfd06] = 0x0020000023d, // KP_Equal + [0x0000ffbe - 0xfd06] = 0x00100000801, // F1 + [0x0000ffbf - 0xfd06] = 0x00100000802, // F2 + [0x0000ffc0 - 0xfd06] = 0x00100000803, // F3 + [0x0000ffc1 - 0xfd06] = 0x00100000804, // F4 + [0x0000ffc2 - 0xfd06] = 0x00100000805, // F5 + [0x0000ffc3 - 0xfd06] = 0x00100000806, // F6 + [0x0000ffc4 - 0xfd06] = 0x00100000807, // F7 + [0x0000ffc5 - 0xfd06] = 0x00100000808, // F8 + [0x0000ffc6 - 0xfd06] = 0x00100000809, // F9 + [0x0000ffc7 - 0xfd06] = 0x0010000080a, // F10 + [0x0000ffc8 - 0xfd06] = 0x0010000080b, // F11 + [0x0000ffc9 - 0xfd06] = 0x0010000080c, // F12 + [0x0000ffca - 0xfd06] = 0x0010000080d, // F13 + [0x0000ffcb - 0xfd06] = 0x0010000080e, // F14 + [0x0000ffcc - 0xfd06] = 0x0010000080f, // F15 + [0x0000ffcd - 0xfd06] = 0x00100000810, // F16 + [0x0000ffce - 0xfd06] = 0x00100000811, // F17 + [0x0000ffcf - 0xfd06] = 0x00100000812, // F18 + [0x0000ffd0 - 0xfd06] = 0x00100000813, // F19 + [0x0000ffd1 - 0xfd06] = 0x00100000814, // F20 + [0x0000ffd2 - 0xfd06] = 0x00100000815, // F21 + [0x0000ffd3 - 0xfd06] = 0x00100000816, // F22 + [0x0000ffd4 - 0xfd06] = 0x00100000817, // F23 + [0x0000ffd5 - 0xfd06] = 0x00100000818, // F24 + [0x0000ffe1 - 0xfd06] = 0x00200000102, // Shift_L + [0x0000ffe2 - 0xfd06] = 0x00200000103, // Shift_R + [0x0000ffe3 - 0xfd06] = 0x00200000100, // Control_L + [0x0000ffe4 - 0xfd06] = 0x00200000101, // Control_R + [0x0000ffe5 - 0xfd06] = 0x00100000104, // Caps_Lock + [0x0000ffe7 - 0xfd06] = 0x00200000106, // Meta_L + [0x0000ffe8 - 0xfd06] = 0x00200000107, // Meta_R + [0x0000ffe9 - 0xfd06] = 0x00200000104, // Alt_L + [0x0000ffea - 0xfd06] = 0x00200000105, // Alt_R + [0x0000ffeb - 0xfd06] = 0x0010000010e, // Super_L + [0x0000ffec - 0xfd06] = 0x0010000010e, // Super_R + [0x0000ffed - 0xfd06] = 0x00100000108, // Hyper_L + [0x0000ffee - 0xfd06] = 0x00100000108, // Hyper_R + [0x0000ffff - 0xfd06] = 0x0010000007f, // Delete + }; + + static const uint64_t logical_keys_2[] = { + [0x1008ff02 - 0x1008ff02] = 0x00100000602, // MonBrightnessUp + [0x1008ff03 - 0x1008ff02] = 0x00100000601, // MonBrightnessDown + [0x1008ff10 - 0x1008ff02] = 0x0010000060a, // Standby + [0x1008ff11 - 0x1008ff02] = 0x00100000a0f, // AudioLowerVolume + [0x1008ff12 - 0x1008ff02] = 0x00100000a11, // AudioMute + [0x1008ff13 - 0x1008ff02] = 0x00100000a10, // AudioRaiseVolume + [0x1008ff14 - 0x1008ff02] = 0x00100000d2f, // AudioPlay + [0x1008ff15 - 0x1008ff02] = 0x00100000a07, // AudioStop + [0x1008ff16 - 0x1008ff02] = 0x00100000a09, // AudioPrev + [0x1008ff17 - 0x1008ff02] = 0x00100000a08, // AudioNext + [0x1008ff18 - 0x1008ff02] = 0x00100000c04, // HomePage + [0x1008ff19 - 0x1008ff02] = 0x00100000b03, // Mail + [0x1008ff1b - 0x1008ff02] = 0x00100000c06, // Search + [0x1008ff1c - 0x1008ff02] = 0x00100000d30, // AudioRecord + [0x1008ff20 - 0x1008ff02] = 0x00100000b02, // Calendar + [0x1008ff26 - 0x1008ff02] = 0x00100000c01, // Back + [0x1008ff27 - 0x1008ff02] = 0x00100000c03, // Forward + [0x1008ff28 - 0x1008ff02] = 0x00100000c07, // Stop + [0x1008ff29 - 0x1008ff02] = 0x00100000c05, // Refresh + [0x1008ff2a - 0x1008ff02] = 0x00100000607, // PowerOff + [0x1008ff2b - 0x1008ff02] = 0x0010000060b, // WakeUp + [0x1008ff2c - 0x1008ff02] = 0x00100000604, // Eject + [0x1008ff2d - 0x1008ff02] = 0x00100000b07, // ScreenSaver + [0x1008ff2f - 0x1008ff02] = 0x00200000002, // Sleep + [0x1008ff30 - 0x1008ff02] = 0x00100000c02, // Favorites + [0x1008ff31 - 0x1008ff02] = 0x00100000d2e, // AudioPause + [0x1008ff3e - 0x1008ff02] = 0x00100000d31, // AudioRewind + [0x1008ff56 - 0x1008ff02] = 0x00100000a01, // Close + [0x1008ff57 - 0x1008ff02] = 0x00100000402, // Copy + [0x1008ff58 - 0x1008ff02] = 0x00100000404, // Cut + [0x1008ff61 - 0x1008ff02] = 0x00100000605, // LogOff + [0x1008ff68 - 0x1008ff02] = 0x00100000a0a, // New + [0x1008ff6b - 0x1008ff02] = 0x00100000a0b, // Open + [0x1008ff6d - 0x1008ff02] = 0x00100000408, // Paste + [0x1008ff6e - 0x1008ff02] = 0x00100000b0d, // Phone + [0x1008ff72 - 0x1008ff02] = 0x00100000a03, // Reply + [0x1008ff77 - 0x1008ff02] = 0x00100000a0d, // Save + [0x1008ff7b - 0x1008ff02] = 0x00100000a04, // Send + [0x1008ff7c - 0x1008ff02] = 0x00100000a0e, // Spell + [0x1008ff8b - 0x1008ff02] = 0x0010000050d, // ZoomIn + [0x1008ff8c - 0x1008ff02] = 0x0010000050e, // ZoomOut + [0x1008ff90 - 0x1008ff02] = 0x00100000a02, // MailForward + [0x1008ff97 - 0x1008ff02] = 0x00100000d2c, // AudioForward + [0x1008ffa7 - 0x1008ff02] = 0x00200000000, // Suspend + }; + // clang-format on + + uint64_t logical = 0; + if (keysym == XKB_KEY_yen) { + return apply_flutter_key_plane(0x00022); + } else if (keysym < 256) { + return apply_unicode_key_plane(eascii_to_lower(keysym)); + } else if (keysym >= 0xfd06 && keysym - 0xfd06 < ARRAY_SIZE(logical_keys_1)) { + logical = logical_keys_1[keysym]; + } else if (keysym >= 0x1008ff02 && keysym - 0x1008ff02 < ARRAY_SIZE(logical_keys_2)) { + logical = logical_keys_2[keysym]; + } + + if (logical == 0) { + return apply_gtk_key_plane(keysym); + } + + return logical; +} + +struct rawkb { + struct key_event_interface interface; + void *userdata; +}; int rawkb_send_android_keyevent( uint32_t flags, @@ -43,15 +674,15 @@ int rawkb_send_android_keyevent( */ (void) plain_code_point; - + + // clang-format off return platch_send( KEY_EVENT_CHANNEL, - &(struct platch_obj) { - .codec = kJSONMessageCodec, + &(struct platch_obj){ .codec = kJSONMessageCodec, .json_value = { .type = kJsonObject, .size = 14, - .keys = (char*[14]) { + .keys = (char *[14]){ "keymap", "flags", "codePoint", @@ -65,39 +696,34 @@ int rawkb_send_android_keyevent( "deviceId", "repeatCount", "type", - "character" + "character", }, - .values = (struct json_value[14]) { - /* keymap */ {.type = kJsonString, .string_value = "android"}, - /* flags */ {.type = kJsonNumber, .number_value = flags}, - /* codePoint */ {.type = kJsonNumber, .number_value = code_point}, - /* keyCode */ {.type = kJsonNumber, .number_value = key_code}, - /* plainCodePoint */ {.type = kJsonNumber, .number_value = code_point}, - /* scanCode */ {.type = kJsonNumber, .number_value = scan_code}, - /* metaState */ {.type = kJsonNumber, .number_value = meta_state}, - /* source */ {.type = kJsonNumber, .number_value = source}, - /* vendorId */ {.type = kJsonNumber, .number_value = vendor_id}, - /* productId */ {.type = kJsonNumber, .number_value = product_id}, - /* deviceId */ {.type = kJsonNumber, .number_value = device_id}, - /* repeatCount */ {.type = kJsonNumber, .number_value = repeat_count}, - /* type */ {.type = kJsonString, .string_value = is_down? "keydown" : "keyup"}, - /* character */ {.type = character? kJsonString : kJsonNull, .string_value = character} - } - } + .values = (struct json_value[14]){ + /* keymap */ { .type = kJsonString, .string_value = "android" }, + /* flags */ { .type = kJsonNumber, .number_value = flags }, + /* codePoint */ { .type = kJsonNumber, .number_value = code_point }, + /* keyCode */ { .type = kJsonNumber, .number_value = key_code }, + /* plainCodePoint */ { .type = kJsonNumber, .number_value = code_point }, + /* scanCode */ { .type = kJsonNumber, .number_value = scan_code }, + /* metaState */ { .type = kJsonNumber, .number_value = meta_state }, + /* source */ { .type = kJsonNumber, .number_value = source }, + /* vendorId */ { .type = kJsonNumber, .number_value = vendor_id }, + /* productId */ { .type = kJsonNumber, .number_value = product_id }, + /* deviceId */ { .type = kJsonNumber, .number_value = device_id }, + /* repeatCount */ { .type = kJsonNumber, .number_value = repeat_count }, + /* type */ { .type = kJsonString, .string_value = is_down ? "keydown" : "keyup" }, + /* character */ { .type = character ? kJsonString : kJsonNull, .string_value = character }, + }, + }, }, kJSONMessageCodec, NULL, NULL ); + // clang-format on } -int rawkb_send_gtk_keyevent( - uint32_t unicode_scalar_values, - uint32_t key_code, - uint32_t scan_code, - uint32_t modifiers, - bool is_down -) { +int rawkb_send_gtk_keyevent(uint32_t unicode_scalar_values, uint32_t key_code, uint32_t scan_code, uint32_t modifiers, bool is_down) { /** * keymap: linux * toolkit: glfw @@ -108,43 +734,180 @@ int rawkb_send_gtk_keyevent( * type: is_down? "keydown" : "keyup" */ + // clang-format off return platch_send( KEY_EVENT_CHANNEL, - &(struct platch_obj) { + &(struct platch_obj){ .codec = kJSONMessageCodec, .json_value = { .type = kJsonObject, .size = 7, - .keys = (char*[7]) { - "keymap", - "toolkit", - "unicodeScalarValues", - "keyCode", - "scanCode", - "modifiers", - "type" + .keys = (char *[7]){ "keymap", "toolkit", "unicodeScalarValues", "keyCode", "scanCode", "modifiers", "type", }, + .values = (struct json_value[7]){ + /* keymap */ { .type = kJsonString, .string_value = "linux" }, + /* toolkit */ { .type = kJsonString, .string_value = "gtk" }, + /* unicodeScalarValues */ { .type = kJsonNumber, .number_value = unicode_scalar_values }, + /* keyCode */ { .type = kJsonNumber, .number_value = key_code }, + /* scanCode */ { .type = kJsonNumber, .number_value = scan_code }, + /* modifiers */ { .type = kJsonNumber, .number_value = modifiers }, + /* type */ { .type = kJsonString, .string_value = is_down ? "keydown" : "keyup", }, }, - .values = (struct json_value[7]) { - /* keymap */ {.type = kJsonString, .string_value = "linux"}, - /* toolkit */ {.type = kJsonString, .string_value = "gtk"}, - /* unicodeScalarValues */ {.type = kJsonNumber, .number_value = unicode_scalar_values}, - /* keyCode */ {.type = kJsonNumber, .number_value = key_code}, - /* scanCode */ {.type = kJsonNumber, .number_value = scan_code}, - /* modifiers */ {.type = kJsonNumber, .number_value = modifiers}, - /* type */ {.type = kJsonString, .string_value = is_down? "keydown" : "keyup"} - } - } + }, }, kJSONMessageCodec, NULL, NULL ); + // clang-format on +} + +int rawkb_send_flutter_keyevent( + struct rawkb *rawkb, + double timestamp_us, + FlutterKeyEventType type, + uint64_t physical, + uint64_t logical, + const char *character, + bool synthesized +) { + COMPILE_ASSERT(sizeof(FlutterKeyEvent) == 48 || sizeof(FlutterKeyEvent) == 56); + rawkb->interface.send_key_event( + rawkb->userdata, + &(FlutterKeyEvent){ + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = timestamp_us, + .type = type, + .physical = physical, + .logical = logical, + .character = character, + .synthesized = synthesized, + } + ); + return 0; +} + +int rawkb_on_key_event( + struct rawkb *rawkb, + uint64_t timestamp_us, + xkb_keycode_t xkb_keycode, + xkb_keysym_t xkb_keysym, + uint32_t plain_codepoint, + key_modifiers_t modifiers, + const char *text, + bool is_down, + bool is_repeat +) { + FlutterKeyEventType type; + uint64_t physical, logical; + int ok; + + physical = physical_key_for_xkb_keycode(xkb_keycode); + logical = logical_key_for_xkb_keysym(xkb_keysym); + + if (is_down && !is_repeat) { + type = kFlutterKeyEventTypeDown; + } else if (is_down && is_repeat) { + type = kFlutterKeyEventTypeRepeat; + } else { + assert(!is_repeat); + type = kFlutterKeyEventTypeUp; + } + + ok = rawkb_send_flutter_keyevent(rawkb, (double) timestamp_us, type, physical, logical, text, false); + if (ok != 0) { + return ok; + } + + ok = rawkb_send_gtk_keyevent(plain_codepoint, xkb_keysym, xkb_keycode, modifiers.u32, is_down); + if (ok != 0) { + return ok; + } + + return 0; +} + +static void assert_key_modifiers_work() { + key_modifiers_t mods; + memset(&mods, 0, sizeof(mods)); + + mods.u32 = 1; + ASSERT_EQUALS(mods.shift, true); + ASSERT_EQUALS(mods.capslock, false); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, false); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + mods.u32 = 1 << 1; + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, true); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, false); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + mods.u32 = 1 << 2; + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, false); + ASSERT_EQUALS(mods.ctrl, true); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, false); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + mods.u32 = 1 << 3; + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, false); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, true); + ASSERT_EQUALS(mods.numlock, false); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + mods.u32 = 1 << 4; + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, false); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, true); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + mods.u32 = 1 << 28; + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, false); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, false); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, true); + + memset(&mods, 0, sizeof(mods)); + mods.shift = true; + mods.meta = true; + ASSERT_EQUALS(mods.u32, ((1 << 0) | (1 << 28))); + + mods.u32 = (1 << 1) | (1 << 4); + ASSERT_EQUALS(mods.shift, false); + ASSERT_EQUALS(mods.capslock, true); + ASSERT_EQUALS(mods.ctrl, false); + ASSERT_EQUALS(mods.alt, false); + ASSERT_EQUALS(mods.numlock, true); + ASSERT_EQUALS(mods.__pad, 0); + ASSERT_EQUALS(mods.meta, false); + + (void) mods; } enum plugin_init_result rawkb_init(struct flutterpi *flutterpi, void **userdata_out) { (void) flutterpi; (void) userdata_out; - return kInitialized_PluginInitResult; + + assert_key_modifiers_work(); + + return PLUGIN_INIT_RESULT_INITIALIZED; } void rawkb_deinit(struct flutterpi *flutterpi, void *userdata) { @@ -152,9 +915,4 @@ void rawkb_deinit(struct flutterpi *flutterpi, void *userdata) { (void) userdata; } -FLUTTERPI_PLUGIN( - "raw keyboard plugin", - rawkb, - rawkb_init, - rawkb_deinit -) +FLUTTERPI_PLUGIN("raw keyboard plugin", rawkb, rawkb_init, rawkb_deinit) diff --git a/src/plugins/raw_keyboard.h b/src/plugins/raw_keyboard.h new file mode 100644 index 00000000..7bb44592 --- /dev/null +++ b/src/plugins/raw_keyboard.h @@ -0,0 +1,64 @@ +#ifndef _KEY_EVENT_H +#define _KEY_EVENT_H + +#include +#include + +#include +#include +#include + +typedef struct { + union { + struct { + bool shift : 1; + bool capslock : 1; + bool ctrl : 1; + bool alt : 1; + bool numlock : 1; + int __pad : 23; + bool meta : 1; + }; + uint32_t u32; + }; +} key_modifiers_t; + +struct key_event_interface { + void (*send_key_event)(void *userdata, const FlutterKeyEvent *event); +}; + +struct rawkb; + +#define KEY_EVENT_CHANNEL "flutter/keyevent" + +int rawkb_send_android_keyevent( + uint32_t flags, + uint32_t code_point, + unsigned int key_code, + uint32_t plain_code_point, + uint32_t scan_code, + uint32_t meta_state, + uint32_t source, + uint16_t vendor_id, + uint16_t product_id, + uint16_t device_id, + int repeat_count, + bool is_down, + char *character +); + +int rawkb_send_gtk_keyevent(uint32_t unicode_scalar_values, uint32_t key_code, uint32_t scan_code, uint32_t modifiers, bool is_down); + +int rawkb_on_key_event( + struct rawkb *rawkb, + uint64_t timestamp_us, + xkb_keycode_t xkb_keycode, + xkb_keysym_t xkb_keysym, + uint32_t plain_codepoint, + key_modifiers_t modifiers, + const char *text, + bool is_down, + bool is_repeat +); + +#endif diff --git a/src/plugins/services.c b/src/plugins/services.c index 6cffe42f..cbc3cdb3 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -1,79 +1,99 @@ +#include "plugins/services.h" + #include #include -#include -#include -#include -#include +#include "cursor.h" +#include "flutter-pi.h" +#include "pluginregistry.h" +#include "util/asserts.h" +#include "util/logging.h" -static struct { +struct plugin { + struct flutterpi *flutterpi; char label[256]; uint32_t primary_color; // ARGB8888 (blue is the lowest byte) char isolate_id[32]; -} services = {0}; - +}; -static int on_receive_navigation(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - (void) channel; - (void) object; - return platch_respond_not_implemented(responsehandle); +static void on_receive_navigation(ASSERTED void *userdata, const FlutterPlatformMessage *message) { + ASSUME(userdata); + ASSUME(message); + platch_respond_not_implemented(message->response_handle); } -static int on_receive_isolate(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - (void) channel; - if (object->binarydata_size > sizeof(services.isolate_id)) { - return EINVAL; - } else { - memcpy(services.isolate_id, object->binarydata, object->binarydata_size); - } - - return platch_respond_not_implemented(responsehandle); +static void on_receive_isolate(void *userdata, const FlutterPlatformMessage *message) { + struct plugin *plugin; + + ASSUME(userdata); + ASSUME(message); + plugin = userdata; + + ASSERT(message->message_size <= sizeof(plugin->isolate_id)); + memcpy(plugin->isolate_id, message->message, message->message_size); + platch_respond_not_implemented(message->response_handle); } -static int on_receive_platform(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static void on_receive_platform(void *userdata, const FlutterPlatformMessage *message) { + struct platch_obj object; struct json_value *value; - struct json_value *arg = &(object->json_arg); + struct json_value *arg; + struct plugin *plugin; + int ok; - (void) channel; + ASSUME(userdata); + ASSUME(message); + plugin = userdata; - if (strcmp(object->method, "Clipboard.setData") == 0) { + ok = platch_decode(message->message, message->message_size, kJSONMethodCall, &object); + if (ok != 0) { + platch_respond_error_json(message->response_handle, "malformed-message", "The platform channel message was malformed.", NULL); + return; + } + + arg = &(object.json_arg); + + if (streq(object.method, "Clipboard.setData")) { /* * Clipboard.setData(Map data) * Places the data from the text entry of the argument, * which must be a Map, onto the system clipboard. */ - } else if (strcmp(object->method, "Clipboard.getData") == 0) { + } else if (streq(object.method, "Clipboard.getData")) { /* * Clipboard.getData(String format) * Returns the data that has the format specified in the argument * from the system clipboard. The only currently supported is "text/plain". * The result is a Map with a single key, "text". - */ - } else if (strcmp(object->method, "HapticFeedback.vibrate") == 0) { + */ + } else if (streq(object.method, "HapticFeedback.vibrate")) { /* * HapticFeedback.vibrate(void) * Triggers a system-default haptic response. */ - } else if (strcmp(object->method, "SystemSound.play") == 0) { + } else if (streq(object.method, "SystemSound.play")) { /* * SystemSound.play(String soundName) * Triggers a system audio effect. The argument must * be a String describing the desired effect; currently only "click" is * supported. */ - } else if (strcmp(object->method, "SystemChrome.setPreferredOrientations") == 0) { + } else if (streq(object.method, "SystemChrome.setPreferredOrientations")) { /* * SystemChrome.setPreferredOrientations(DeviceOrientation[]) * Informs the operating system of the desired orientation of the display. The argument is a [List] of * values which are string representations of values of the [DeviceOrientation] enum. - * + * * enum DeviceOrientation { * portraitUp, landscapeLeft, portraitDown, landscapeRight * } */ - + + /// TODO: Implement + + /* value = &object->json_arg; - + if ((value->type != kJsonArray) || (value->size == 0)) { return platch_respond_illegal_arg_json( responsehandle, @@ -91,7 +111,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter "Expected `arg` to to only contain strings." ); } - + enum device_orientation o = ORIENTATION_FROM_STRING(value->array[i].string_value); if (o == -1) { @@ -123,7 +143,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter // send updated window metrics to flutter result = flutterpi.flutter.libflutter_engine.FlutterEngineSendWindowMetricsEvent(flutterpi.flutter.engine, &(const FlutterWindowMetricsEvent) { .struct_size = sizeof(FlutterWindowMetricsEvent), - .width = flutterpi.view.width, + .width = flutterpi.view.width, .height = flutterpi.view.height, .pixel_ratio = flutterpi.display.pixel_ratio }); @@ -140,7 +160,8 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter responsehandle, "Expected `arg` to contain at least one element." ); - } else if (strcmp(object->method, "SystemChrome.setApplicationSwitcherDescription") == 0) { + */ + } else if (streq(object.method, "SystemChrome.setApplicationSwitcherDescription")) { /* * SystemChrome.setApplicationSwitcherDescription(Map description) * Informs the operating system of the desired label and color to be used @@ -151,35 +172,38 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * and the high eight bits being set, as from Color.value for an opaque color). * The "primaryColor" can also be zero to indicate that the system default should be used. */ - + value = jsobject_get(arg, "label"); - if (value && (value->type == kJsonString)) - snprintf(services.label, sizeof(services.label), "%s", value->string_value); - - return platch_respond_success_json(responsehandle, NULL); - } else if (strcmp(object->method, "SystemChrome.setEnabledSystemUIOverlays") == 0) { + if (value && (value->type == kJsonString)) { + strncpy(plugin->label, value->string_value, sizeof(plugin->label) - 1); + } + + platch_free_obj(&object); + platch_respond_success_json(message->response_handle, NULL); + return; + } else if (streq(object.method, "SystemChrome.setEnabledSystemUIOverlays")) { /* * SystemChrome.setEnabledSystemUIOverlays(List overlays) * Specifies the set of system overlays to have visible when the application * is running. The argument is a List of values which are * string representations of values of the SystemUIOverlay enum. - * + * * enum SystemUIOverlay { * top, bottom * } - * + * */ - } else if (strcmp(object->method, "SystemChrome.restoreSystemUIOverlays") == 0) { + } else if (streq(object.method, "SystemChrome.restoreSystemUIOverlays")) { /* * SystemChrome.restoreSystemUIOverlays(void) */ - } else if (strcmp(object->method, "SystemChrome.setSystemUIOverlayStyle") == 0) { - /* + } else if (streq(object.method, "SystemChrome.setSystemUIOverlayStyle")) { + /* * SystemChrome.setSystemUIOverlayStyle(struct SystemUIOverlayStyle) - * + * * enum Brightness: * light, dark - * + * * struct SystemUIOverlayStyle: * systemNavigationBarColor: null / uint32 * statusBarColor: null / uint32 @@ -187,100 +211,277 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * statusBarBrightness: null / Brightness * systemNavigationBarIconBrightness: null / Brightness */ - } else if (strcmp(object->method, "SystemNavigator.pop") == 0) { - LOG_FLUTTERPI_ERROR("received SystemNavigator.pop. Exiting...\n"); - flutterpi_schedule_exit(); + } else if (streq(object.method, "SystemNavigator.pop")) { + LOG_DEBUG("received SystemNavigator.pop. Exiting...\n"); + flutterpi_schedule_exit(flutterpi); } - return platch_respond_not_implemented(responsehandle); + platch_free_obj(&object); + platch_respond_not_implemented(message->response_handle); } -static int on_receive_accessibility(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - (void) channel; - (void) object; - return platch_respond_not_implemented(responsehandle); +static void on_receive_accessibility(ASSERTED void *userdata, const FlutterPlatformMessage *message) { + ASSUME(userdata); + ASSUME(message); + platch_respond_not_implemented(message->response_handle); } -static int on_receive_platform_views(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - (void) channel; - (void) object; +static void on_receive_platform_views(ASSERTED void *userdata, const FlutterPlatformMessage *message) { + ASSUME(userdata); + ASSUME(message); - if STREQ("create", object->method) { - return platch_respond_not_implemented(responsehandle); - } else if STREQ("dispose", object->method) { - return platch_respond_not_implemented(responsehandle); + // if (streq("create", object->method)) { + // return platch_respond_not_implemented(responsehandle); + // } else if (streq("dispose", object->method)) { + // return platch_respond_not_implemented(responsehandle); + // } + + platch_respond_not_implemented(message->response_handle); +} + +static void on_receive_mouse_cursor(ASSERTED void *userdata, const FlutterPlatformMessage *message) { + const struct raw_std_value *method_call; + const struct raw_std_value *arg; + struct plugin *plugin; + + ASSERT_NOT_NULL(userdata); + plugin = userdata; + + method_call = (const struct raw_std_value *) (message->message); + + if (!raw_std_method_call_check(method_call, message->message_size)) { + platch_respond_illegal_arg_std(message->response_handle, "Malformed platform message."); + return; } - return platch_respond_not_implemented(responsehandle); + arg = raw_std_method_call_get_arg(method_call); + + if (raw_std_method_call_is_method(method_call, "activateSystemCursor")) { + if (!raw_std_value_is_map(arg)) { + platch_respond_illegal_arg_std(message->response_handle, "Expected `arg` to be a Map."); + return; + } + + bool has_device = false; + UNUSED int64_t device; + + bool has_kind = false; + enum pointer_kind kind = POINTER_KIND_BASIC; + + for_each_entry_in_raw_std_map(key, value, arg) { + if (!raw_std_value_is_string(key)) { + continue; + } + + if (raw_std_string_equals(key, "device")) { + if (!raw_std_value_is_int(value)) { + platch_respond_illegal_arg_std(message->response_handle, "Expected `arg['device']` to be an int."); + return; + } + + has_device = true; + device = raw_std_value_as_int(value); + } else if (raw_std_string_equals(key, "kind")) { + if (!raw_std_value_is_string(value)) { + platch_respond_illegal_arg_std(message->response_handle, "Expected `arg['kind']` to be a string."); + return; + } + + if (raw_std_string_equals(value, "none")) { + kind = POINTER_KIND_NONE; + } else if (raw_std_string_equals(value, "basic")) { + kind = POINTER_KIND_BASIC; + } else if (raw_std_string_equals(value, "click")) { + kind = POINTER_KIND_CLICK; + } else if (raw_std_string_equals(value, "forbidden")) { + kind = POINTER_KIND_FORBIDDEN; + } else if (raw_std_string_equals(value, "wait")) { + kind = POINTER_KIND_WAIT; + } else if (raw_std_string_equals(value, "progress")) { + kind = POINTER_KIND_PROGRESS; + } else if (raw_std_string_equals(value, "contextMenu")) { + kind = POINTER_KIND_CONTEXT_MENU; + } else if (raw_std_string_equals(value, "help")) { + kind = POINTER_KIND_HELP; + } else if (raw_std_string_equals(value, "text")) { + kind = POINTER_KIND_TEXT; + } else if (raw_std_string_equals(value, "verticalText")) { + kind = POINTER_KIND_VERTICAL_TEXT; + } else if (raw_std_string_equals(value, "cell")) { + kind = POINTER_KIND_CELL; + } else if (raw_std_string_equals(value, "precise")) { + kind = POINTER_KIND_PRECISE; + } else if (raw_std_string_equals(value, "move")) { + kind = POINTER_KIND_MOVE; + } else if (raw_std_string_equals(value, "grab")) { + kind = POINTER_KIND_GRAB; + } else if (raw_std_string_equals(value, "grabbing")) { + kind = POINTER_KIND_GRABBING; + } else if (raw_std_string_equals(value, "noDrop")) { + kind = POINTER_KIND_NO_DROP; + } else if (raw_std_string_equals(value, "alias")) { + kind = POINTER_KIND_ALIAS; + } else if (raw_std_string_equals(value, "copy")) { + kind = POINTER_KIND_COPY; + } else if (raw_std_string_equals(value, "disappearing")) { + kind = POINTER_KIND_DISAPPEARING; + } else if (raw_std_string_equals(value, "allScroll")) { + kind = POINTER_KIND_ALL_SCROLL; + } else if (raw_std_string_equals(value, "resizeLeftRight")) { + kind = POINTER_KIND_RESIZE_LEFT_RIGHT; + } else if (raw_std_string_equals(value, "resizeUpDown")) { + kind = POINTER_KIND_RESIZE_UP_DOWN; + } else if (raw_std_string_equals(value, "resizeUpLeftDownRight")) { + kind = POINTER_KIND_RESIZE_UP_LEFT_DOWN_RIGHT; + } else if (raw_std_string_equals(value, "resizeUpRightDownLeft")) { + kind = POINTER_KIND_RESIZE_UP_RIGHT_DOWN_LEFT; + } else if (raw_std_string_equals(value, "resizeUp")) { + kind = POINTER_KIND_RESIZE_UP; + } else if (raw_std_string_equals(value, "resizeDown")) { + kind = POINTER_KIND_RESIZE_DOWN; + } else if (raw_std_string_equals(value, "resizeLeft")) { + kind = POINTER_KIND_RESIZE_LEFT; + } else if (raw_std_string_equals(value, "resizeRight")) { + kind = POINTER_KIND_RESIZE_RIGHT; + } else if (raw_std_string_equals(value, "resizeUpLeft")) { + kind = POINTER_KIND_RESIZE_UP_LEFT; + } else if (raw_std_string_equals(value, "resizeUpRight")) { + kind = POINTER_KIND_RESIZE_UP_RIGHT; + } else if (raw_std_string_equals(value, "resizeDownLeft")) { + kind = POINTER_KIND_RESIZE_DOWN_LEFT; + } else if (raw_std_string_equals(value, "resizeDownRight")) { + kind = POINTER_KIND_RESIZE_DOWN_RIGHT; + } else if (raw_std_string_equals(value, "resizeColumn")) { + kind = POINTER_KIND_RESIZE_COLUMN; + } else if (raw_std_string_equals(value, "resizeRow")) { + kind = POINTER_KIND_RESIZE_ROW; + } else if (raw_std_string_equals(value, "zoomIn")) { + kind = POINTER_KIND_ZOOM_IN; + } else if (raw_std_string_equals(value, "zoomOut")) { + kind = POINTER_KIND_ZOOM_OUT; + } else { + platch_respond_illegal_arg_std(message->response_handle, "Expected `arg['kind']` to be a valid mouse pointer kind."); + return; + } + + has_kind = true; + } + } + + if (!has_device || !has_kind) { + platch_respond_illegal_arg_std(message->response_handle, "Expected both `arg['device']` and `arg['kind']` to be non-null."); + return; + } + + flutterpi_set_pointer_kind(plugin->flutterpi, kind); + + platch_respond_success_std(message->response_handle, &STDNULL); + } else { + platch_respond_not_implemented(message->response_handle); + } } enum plugin_init_result services_init(struct flutterpi *flutterpi, void **userdata_out) { + struct plugin_registry *registry; + struct plugin *plugin; int ok; - (void) flutterpi; - (void) userdata_out; + ASSUME(flutterpi); + + registry = flutterpi_get_plugin_registry(flutterpi); - ok = plugin_registry_set_receiver(FLUTTER_NAVIGATION_CHANNEL, kJSONMethodCall, on_receive_navigation); + plugin = malloc(sizeof *plugin); + if (plugin == NULL) { + goto fail_return_error; + } + + plugin->flutterpi = flutterpi; + + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL, on_receive_navigation, plugin); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"" FLUTTER_NAVIGATION_CHANNEL "\" platform message receiver: %s\n", strerror(ok)); - goto fail_return_ok; + LOG_ERROR("Could not set \"" FLUTTER_NAVIGATION_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); + goto fail_free_plugin; } - ok = plugin_registry_set_receiver(FLUTTER_ISOLATE_CHANNEL, kBinaryCodec, on_receive_isolate); + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL, on_receive_isolate, plugin); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"" FLUTTER_ISOLATE_CHANNEL "\" ChannelObject receiver: %s\n", strerror(ok)); + LOG_ERROR("Could not set \"" FLUTTER_ISOLATE_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_remove_navigation_receiver; } - ok = plugin_registry_set_receiver(FLUTTER_PLATFORM_CHANNEL, kJSONMethodCall, on_receive_platform); + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL, on_receive_platform, plugin); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"" FLUTTER_PLATFORM_CHANNEL "\" ChannelObject receiver: %s\n", strerror(ok)); + LOG_ERROR("Could not set \"" FLUTTER_PLATFORM_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_remove_isolate_receiver; } - ok = plugin_registry_set_receiver(FLUTTER_ACCESSIBILITY_CHANNEL, kBinaryCodec, on_receive_accessibility); + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL, on_receive_accessibility, plugin); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"" FLUTTER_ACCESSIBILITY_CHANNEL "\" ChannelObject receiver: %s\n", strerror(ok)); + LOG_ERROR( + "Could not set \"" FLUTTER_ACCESSIBILITY_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", + strerror(ok) + ); goto fail_remove_platform_receiver; } - ok = plugin_registry_set_receiver(FLUTTER_PLATFORM_VIEWS_CHANNEL, kStandardMethodCall, on_receive_platform_views); + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL, on_receive_platform_views, plugin); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"" FLUTTER_PLATFORM_VIEWS_CHANNEL "\" ChannelObject receiver: %s\n", strerror(ok)); + LOG_ERROR( + "Could not set \"" FLUTTER_PLATFORM_VIEWS_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", + strerror(ok) + ); goto fail_remove_accessibility_receiver; } + ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_MOUSECURSOR_CHANNEL, on_receive_mouse_cursor, plugin); + if (ok != 0) { + LOG_ERROR("Could not set \"" FLUTTER_MOUSECURSOR_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); + goto fail_remove_platform_views_receiver; + } + + *userdata_out = plugin; + return 0; - fail_remove_accessibility_receiver: - plugin_registry_remove_receiver(FLUTTER_ACCESSIBILITY_CHANNEL); +fail_remove_platform_views_receiver: + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); + +fail_remove_accessibility_receiver: + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL); + +fail_remove_platform_receiver: + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL); - fail_remove_platform_receiver: - plugin_registry_remove_receiver(FLUTTER_PLATFORM_CHANNEL); +fail_remove_isolate_receiver: + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL); - fail_remove_isolate_receiver: - plugin_registry_remove_receiver(FLUTTER_ISOLATE_CHANNEL); +fail_remove_navigation_receiver: + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL); - fail_remove_navigation_receiver: - plugin_registry_remove_receiver(FLUTTER_NAVIGATION_CHANNEL); +fail_free_plugin: + free(plugin); - fail_return_ok: - return kError_PluginInitResult; +fail_return_error: + return PLUGIN_INIT_RESULT_ERROR; } void services_deinit(struct flutterpi *flutterpi, void *userdata) { - (void) flutterpi; - (void) userdata; - plugin_registry_remove_receiver(FLUTTER_NAVIGATION_CHANNEL); - plugin_registry_remove_receiver(FLUTTER_ISOLATE_CHANNEL); - plugin_registry_remove_receiver(FLUTTER_PLATFORM_CHANNEL); - plugin_registry_remove_receiver(FLUTTER_ACCESSIBILITY_CHANNEL); - plugin_registry_remove_receiver(FLUTTER_PLATFORM_VIEWS_CHANNEL); + struct plugin_registry *registry; + struct plugin *plugin; + + ASSUME(flutterpi); + ASSUME(userdata); + + registry = flutterpi_get_plugin_registry(flutterpi); + plugin = userdata; + + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL); + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL); + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL); + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL); + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); + plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_MOUSECURSOR_CHANNEL); + free(plugin); } -FLUTTERPI_PLUGIN( - "services", - services, - services_init, - services_deinit -) +FLUTTERPI_PLUGIN("services", services, services_init, services_deinit) diff --git a/src/plugins/services.h b/src/plugins/services.h new file mode 100644 index 00000000..748ea9b0 --- /dev/null +++ b/src/plugins/services.h @@ -0,0 +1,21 @@ +#ifndef _SERVICES_PLUGIN_H +#define _SERVICES_PLUGIN_H + +#include +#include + +#define ORIENTATION_FROM_STRING(str) \ + (streq(str, "DeviceOrientation.portraitUp") ? kPortraitUp : \ + streq(str, "DeviceOrientation.landscapeLeft") ? kLandscapeLeft : \ + streq(str, "DeviceOrientation.portraitDown") ? kPortraitDown : \ + streq(str, "DeviceOrientation.landscapeRight") ? kLandscapeRight : \ + -1) + +#define FLUTTER_NAVIGATION_CHANNEL "flutter/navigation" +#define FLUTTER_ISOLATE_CHANNEL "flutter/isolate" +#define FLUTTER_PLATFORM_CHANNEL "flutter/platform" +#define FLUTTER_ACCESSIBILITY_CHANNEL "flutter/accessibility" +#define FLUTTER_PLATFORM_VIEWS_CHANNEL "flutter/platform_views" +#define FLUTTER_MOUSECURSOR_CHANNEL "flutter/mousecursor" + +#endif diff --git a/src/plugins/testplugin.c b/src/plugins/testplugin.c index db05456d..822ccccb 100644 --- a/src/plugins/testplugin.c +++ b/src/plugins/testplugin.c @@ -1,37 +1,29 @@ +#include "plugins/testplugin.h" + +#include #include -#include #include -#include +#include -#include -#include -#include +#include "flutter-pi.h" +#include "pluginregistry.h" #define INDENT_STRING " " int __printJSON(struct json_value *value, int indent) { switch (value->type) { - case kJsonNull: - printf("null"); - break; - case kJsonTrue: - printf("true"); - break; - case kJsonFalse: - printf("false"); - break; - case kJsonNumber: - printf("%f", value->number_value); - break; - case kJsonString: - printf("\"%s\"", value->string_value); - break; + case kJsonNull: printf("null"); break; + case kJsonTrue: printf("true"); break; + case kJsonFalse: printf("false"); break; + case kJsonNumber: printf("%f", value->number_value); break; + case kJsonString: printf("\"%s\"", value->string_value); break; case kJsonArray: printf("[\n"); for (int i = 0; i < value->size; i++) { printf("%.*s", indent + 2, INDENT_STRING); __printJSON(&(value->array[i]), indent + 2); - if (i+1 != value->size) printf(",\n"); + if (i + 1 != value->size) + printf(",\n"); } printf("\n%.*s]", indent, INDENT_STRING); break; @@ -40,7 +32,8 @@ int __printJSON(struct json_value *value, int indent) { for (int i = 0; i < value->size; i++) { printf("%.*s\"%s\": ", indent + 2, INDENT_STRING, value->keys[i]); __printJSON(&(value->values[i]), indent + 2); - if (i+1 != value->size) printf(",\n"); + if (i + 1 != value->size) + printf(",\n"); } printf("\n%.*s}", indent, INDENT_STRING); break; @@ -57,33 +50,20 @@ int printJSON(struct json_value *value, int indent) { } int __printStd(struct std_value *value, int indent) { switch (value->type) { - case kStdNull: - printf("null"); - break; - case kStdTrue: - printf("true"); - break; - case kStdFalse: - printf("false"); - break; - case kStdInt32: - printf("%" PRIi32, value->int32_value); - break; - case kStdInt64: - printf("%" PRIi64, value->int64_value); - break; - case kStdFloat64: - printf("%lf", value->float64_value); - break; + case kStdNull: printf("null"); break; + case kStdTrue: printf("true"); break; + case kStdFalse: printf("false"); break; + case kStdInt32: printf("%" PRIi32, value->int32_value); break; + case kStdInt64: printf("%" PRIi64, value->int64_value); break; + case kStdFloat64: printf("%lf", value->float64_value); break; case kStdString: - case kStdLargeInt: - printf("\"%s\"", value->string_value); - break; + case kStdLargeInt: printf("\"%s\"", value->string_value); break; case kStdUInt8Array: printf("(uint8_t) ["); for (int i = 0; i < value->size; i++) { printf("0x%02X", value->uint8array[i]); - if (i + 1 != value->size) printf(", "); + if (i + 1 != value->size) + printf(", "); } printf("]"); break; @@ -91,7 +71,8 @@ int __printStd(struct std_value *value, int indent) { printf("(int32_t) ["); for (int i = 0; i < value->size; i++) { printf("%" PRIi32, value->int32array[i]); - if (i + 1 != value->size) printf(", "); + if (i + 1 != value->size) + printf(", "); } printf("]"); break; @@ -99,7 +80,8 @@ int __printStd(struct std_value *value, int indent) { printf("(int64_t) ["); for (int i = 0; i < value->size; i++) { printf("%" PRIi64, value->int64array[i]); - if (i + 1 != value->size) printf(", "); + if (i + 1 != value->size) + printf(", "); } printf("]"); break; @@ -107,7 +89,8 @@ int __printStd(struct std_value *value, int indent) { printf("(double) ["); for (int i = 0; i < value->size; i++) { printf("%f", value->float64array[i]); - if (i + 1 != value->size) printf(", "); + if (i + 1 != value->size) + printf(", "); } printf("]"); break; @@ -116,7 +99,8 @@ int __printStd(struct std_value *value, int indent) { for (int i = 0; i < value->size; i++) { printf("%.*s", indent + 2, INDENT_STRING); __printStd(&(value->list[i]), indent + 2); - if (i + 1 != value->size) printf(",\n"); + if (i + 1 != value->size) + printf(",\n"); } printf("\n%.*s]", indent, INDENT_STRING); break; @@ -127,12 +111,12 @@ int __printStd(struct std_value *value, int indent) { __printStd(&(value->keys[i]), indent + 2); printf(": "); __printStd(&(value->values[i]), indent + 2); - if (i + 1 != value->size) printf(",\n"); + if (i + 1 != value->size) + printf(",\n"); } printf("\n%.*s}", indent, INDENT_STRING); break; - default: - break; + default: break; } return 0; } @@ -148,55 +132,54 @@ int printStd(struct std_value *value, int indent) { static uint64_t testplugin_time_offset; static int on_response_json(struct platch_obj *object, void *userdata) { - uint64_t dt = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); + uint64_t dt = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime() - *((uint64_t *) userdata); free(userdata); - + if (object->codec == kNotImplemented) { printf("channel " TESTPLUGIN_CHANNEL_JSON " not implented on flutter side\n"); return 0; } if (object->success) { - printf("on_response_json(dt: %lu)\n" - " success\n" - " result:\n", dt); + printf( + "on_response_json(dt: %lu)\n" + " success\n" + " result:\n", + dt + ); printJSON(&object->json_result, 4); } else { printf("testp_on_response_json(dt: %lu)\n", dt); - printf(" failure\n" - " error code: %s\n" - " error message: %s\n" - " error details:\n", object->error_code, (object->error_msg != NULL) ? object->error_msg : "null"); + printf( + " failure\n" + " error code: %s\n" + " error message: %s\n" + " error details:\n", + object->error_code, + (object->error_msg != NULL) ? object->error_msg : "null" + ); printJSON(&object->json_result, 4); } return 0; } static int send_json(void) { - uint64_t* time = malloc(sizeof(uint64_t)); + uint64_t *time = malloc(sizeof(uint64_t)); *time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); char *method = "test"; struct json_value argument = { .type = kJsonObject, .size = 5, - .keys = (char*[]) { - "key1", - "key2", - "key3", - "key4", - "array" - }, - .values = (struct json_value[]) { - {.type = kJsonString, .string_value = "value1"}, - {.type = kJsonTrue}, - {.type = kJsonNumber, .number_value = -1000}, - {.type = kJsonNumber, .number_value = -5.0005}, - {.type = kJsonArray, .size = 2, .array = (struct json_value[]) { - {.type = kJsonString, .string_value = "array1"}, - {.type = kJsonNumber, .number_value = 2} - }} - }, + .keys = (char *[]){ "key1", "key2", "key3", "key4", "array" }, + .values = (struct json_value[]){ { .type = kJsonString, .string_value = "value1" }, + { .type = kJsonTrue }, + { .type = kJsonNumber, .number_value = -1000 }, + { .type = kJsonNumber, .number_value = -5.0005 }, + { .type = kJsonArray, + .size = 2, + .array = (struct json_value[]){ { .type = kJsonString, .string_value = "array1" }, + { .type = kJsonNumber, .number_value = 2 } } } }, }; int ok = platch_call_json(TESTPLUGIN_CHANNEL_JSON, method, &argument, on_response_json, time); @@ -206,7 +189,7 @@ static int send_json(void) { return 0; } static int on_response_std(struct platch_obj *object, void *userdata) { - uint64_t dt = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); + uint64_t dt = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime() - *((uint64_t *) userdata); free(userdata); if (object->codec == kNotImplemented) { @@ -215,16 +198,23 @@ static int on_response_std(struct platch_obj *object, void *userdata) { } if (object->success) { - printf("testp_on_response_std(dt: %lu)\n" - " success\n" - " result:\n", dt); + printf( + "testp_on_response_std(dt: %lu)\n" + " success\n" + " result:\n", + dt + ); printStd(&object->std_result, 4); } else { printf("testp_on_response_std(dt: %lu)\n", dt); - printf(" failure\n" - " error code: %s\n" - " error message: %s\n" - " error details:\n", object->error_code, (object->error_msg != NULL) ? object->error_msg : "null"); + printf( + " failure\n" + " error code: %s\n" + " error message: %s\n" + " error details:\n", + object->error_code, + (object->error_msg != NULL) ? object->error_msg : "null" + ); printStd(&object->std_error_details, 4); } @@ -238,122 +228,104 @@ static int send_std() { struct std_value argument = { .type = kStdMap, .size = 7, - .keys = (struct std_value[]) { - {.type = kStdString, .string_value = "key1"}, - {.type = kStdString, .string_value = "key2"}, - {.type = kStdString, .string_value = "key3"}, - {.type = kStdString, .string_value = "key4"}, - {.type = kStdInt32, .int32_value = 5}, - {.type = kStdString, .string_value = "timestamp"}, - {.type = kStdString, .string_value = "array"} - }, - .values = (struct std_value[]) { - {.type = kStdString, .string_value = "value1"}, - {.type = kStdTrue}, - {.type = kStdInt32, .int32_value = -1000}, - {.type = kStdFloat64, .float64_value = -5.0005}, - {.type = kStdUInt8Array, .uint8array = (uint8_t[]) {0x00, 0x01, 0x02, 0x03, 0xFF}, .size = 5}, - {.type = kStdInt64, .int64_value = *time & 0x7FFFFFFFFFFFFFFF}, - {.type = kStdList, .size = 2, .list = (struct std_value[]) { - {.type = kStdString, .string_value = "array1"}, - {.type = kStdInt32, .int32_value = 2} - }} - }, + .keys = (struct std_value[]){ { .type = kStdString, .string_value = "key1" }, + { .type = kStdString, .string_value = "key2" }, + { .type = kStdString, .string_value = "key3" }, + { .type = kStdString, .string_value = "key4" }, + { .type = kStdInt32, .int32_value = 5 }, + { .type = kStdString, .string_value = "timestamp" }, + { .type = kStdString, .string_value = "array" } }, + .values = (struct std_value[] + ){ { .type = kStdString, .string_value = "value1" }, + { .type = kStdTrue }, + { .type = kStdInt32, .int32_value = -1000 }, + { .type = kStdFloat64, .float64_value = -5.0005 }, + { .type = kStdUInt8Array, .uint8array = (uint8_t[]){ 0x00, 0x01, 0x02, 0x03, 0xFF }, .size = 5 }, + { .type = kStdInt64, .int64_value = *time & 0x7FFFFFFFFFFFFFFF }, + { .type = kStdList, + .size = 2, + .list = (struct std_value[]){ { .type = kStdString, .string_value = "array1" }, { .type = kStdInt32, .int32_value = 2 } } } }, }; platch_call_std(TESTPLUGIN_CHANNEL_STD, method, &argument, on_response_std, time); return 0; } - static int on_receive_json(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - printf("[test plugin] on_receive_json(channel: %s)\n" - " method: %s\n" - " args: \n", channel, object->method); + printf( + "[test plugin] on_receive_json(channel: %s)\n" + " method: %s\n" + " args: \n", + channel, + object->method + ); printJSON(&(object->json_arg), 4); - + send_json(); - return platch_respond(responsehandle, &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = { - .type = kJsonTrue - } - }); + return platch_respond( + responsehandle, + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonTrue } } + ); } static int on_receive_std(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - printf("[test plugin] on_receive_std(channel: %s)\n" - " method: %s\n" - " args: \n", channel, object->method); + printf( + "[test plugin] on_receive_std(channel: %s)\n" + " method: %s\n" + " args: \n", + channel, + object->method + ); printStd(&(object->std_arg), 4); send_std(); - + return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = { - .type = kStdTrue - } - } + &(struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = { .type = kStdTrue } } ); } static int on_receive_ping(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - return platch_respond( - responsehandle, - &(struct platch_obj) { - .codec = kStringCodec, - .string_value = "pong" - } - ); + return platch_respond(responsehandle, &(struct platch_obj){ .codec = kStringCodec, .string_value = "pong" }); } enum plugin_init_result testp_init(struct flutterpi *flutterpi, void **userdata_out) { int ok; - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_JSON, kJSONMethodCall, on_receive_json); + ok = plugin_registry_set_receiver_locked(TESTPLUGIN_CHANNEL_JSON, kJSONMethodCall, on_receive_json); if (ok != 0) { - return kError_PluginInitResult; + return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_STD, kStandardMethodCall, on_receive_std); + ok = plugin_registry_set_receiver_locked(TESTPLUGIN_CHANNEL_STD, kStandardMethodCall, on_receive_std); if (ok != 0) { goto fail_remove_json_receiver; } - - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_PING, kStringCodec, on_receive_ping); + + ok = plugin_registry_set_receiver_locked(TESTPLUGIN_CHANNEL_PING, kStringCodec, on_receive_ping); if (ok != 0) { goto fail_remove_std_receiver; } *userdata_out = NULL; - return kInitialized_PluginInitResult; - + return PLUGIN_INIT_RESULT_INITIALIZED; - fail_remove_std_receiver: - plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_STD); +fail_remove_std_receiver: + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TESTPLUGIN_CHANNEL_STD); - fail_remove_json_receiver: - plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_JSON); +fail_remove_json_receiver: + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TESTPLUGIN_CHANNEL_JSON); - return kError_PluginInitResult; + return PLUGIN_INIT_RESULT_ERROR; } void testp_deinit(struct flutterpi *flutterpi, void *userdata) { - plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_PING); - plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_STD); - plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_JSON); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TESTPLUGIN_CHANNEL_PING); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TESTPLUGIN_CHANNEL_STD); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TESTPLUGIN_CHANNEL_JSON); return 0; } -FLUTTERPI_PLUGIN( - "test plugin", - test_plugin, - testp_init, - testp_deinit -) \ No newline at end of file +FLUTTERPI_PLUGIN("test plugin", test_plugin, testp_init, testp_deinit) diff --git a/include/plugins/testplugin.h b/src/plugins/testplugin.h similarity index 97% rename from include/plugins/testplugin.h rename to src/plugins/testplugin.h index d6d54ad6..8d02c71e 100644 --- a/include/plugins/testplugin.h +++ b/src/plugins/testplugin.h @@ -11,4 +11,4 @@ extern int testp_init(void); extern int testp_deinit(void); -#endif \ No newline at end of file +#endif diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 65a3ca0f..c9e3e8e9 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -1,13 +1,16 @@ +#include "plugins/text_input.h" + #include #include -#include #include #include +#include -#include -#include #include -#include + +#include "flutter-pi.h" +#include "pluginregistry.h" +#include "util/asserts.h" struct text_input { int64_t connection_id; @@ -19,14 +22,12 @@ struct text_input { bool autocorrect; enum text_input_action input_action; char text[TEXT_INPUT_MAX_CHARS]; - int selection_base, selection_extent; + int selection_base, selection_extent; bool selection_affinity_is_downstream; bool selection_is_directional; - int composing_base, composing_extent; + int composing_base, composing_extent; bool warned_about_autocorrect; -} text_input = { - .connection_id = -1 -}; +} text_input = { .connection_id = -1 }; /** * UTF8 utility functions @@ -43,22 +44,22 @@ static inline uint8_t utf8_symbol_length(uint8_t c) { } if ((c & 0b10000000) == 0b10000000) { // XXX should we return 1 and don't care here? - DEBUG_ASSERT_MSG(false, "Invalid UTF-8 character"); + ASSERT_MSG(false, "Invalid UTF-8 character"); return 0; } return 1; } static inline uint8_t *symbol_at(unsigned int symbol_index) { - uint8_t *cursor = (uint8_t*) text_input.text; + uint8_t *cursor = (uint8_t *) text_input.text; for (; symbol_index && *cursor; symbol_index--) cursor += utf8_symbol_length(*cursor); - return symbol_index? NULL : cursor; + return symbol_index ? NULL : cursor; } -static inline int to_byte_index(unsigned int symbol_index) { +UNUSED static inline int to_byte_index(unsigned int symbol_index) { char *cursor = text_input.text; while ((*cursor) && (symbol_index--)) @@ -80,16 +81,13 @@ static inline int to_symbol_index(unsigned int byte_index) { symbol_index++; } - return cursor < target_cursor? -1 : symbol_index; + return cursor < target_cursor ? -1 : symbol_index; } /** * Platform message callbacks */ -static int on_set_client( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_client(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { enum text_input_action input_action; enum text_input_type input_type; struct json_value *temp, *temp2, *config; @@ -109,26 +107,17 @@ static int on_set_client( * [TextInputConfiguration.toJSON]. This method must be invoked before any * others (except `TextInput.hide`). See [TextInput.attach]. */ - + if ((object->json_arg.type != kJsonArray) || (object->json_arg.size != 2)) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg` to be an array with length 2." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg` to be an array with length 2."); } if (object->json_arg.array[0].type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[0]` to be a number" - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[0]` to be a number"); } if (object->json_arg.array[1].type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[1]` to be an map." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[1]` to be an map."); } config = &object->json_arg.array[1]; @@ -136,14 +125,11 @@ static int on_set_client( // AUTOCORRECT temp = jsobject_get(config, "autocorrect"); if (temp == NULL || (temp->type != kJsonTrue && temp->type != kJsonFalse)) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[1]['autocorrect']` to be a boolean." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[1]['autocorrect']` to be a boolean."); } else { autocorrect = temp->type == kJsonTrue; } - + // INPUT ACTION temp = jsobject_get(config, "inputAction"); if (temp == NULL || temp->type != kJsonString) { @@ -152,46 +138,44 @@ static int on_set_client( "Expected `arg[1]['inputAction']` to be a string-ification of `TextInputAction`." ); } - - if STREQ("TextInputAction.none", temp->string_value) + + if (streq("TextInputAction.none", temp->string_value)) { input_action = kTextInputActionNone; - else if STREQ("TextInputAction.unspecified", temp->string_value) + } else if (streq("TextInputAction.unspecified", temp->string_value)) { input_action = kTextInputActionUnspecified; - else if STREQ("TextInputAction.done", temp->string_value) + } else if (streq("TextInputAction.done", temp->string_value)) { input_action = kTextInputActionDone; - else if STREQ("TextInputAction.go", temp->string_value) + } else if (streq("TextInputAction.go", temp->string_value)) { input_action = kTextInputActionGo; - else if STREQ("TextInputAction.search", temp->string_value) + } else if (streq("TextInputAction.search", temp->string_value)) { input_action = kTextInputActionSearch; - else if STREQ("TextInputAction.send", temp->string_value) + } else if (streq("TextInputAction.send", temp->string_value)) { input_action = kTextInputActionSend; - else if STREQ("TextInputAction.next", temp->string_value) + } else if (streq("TextInputAction.next", temp->string_value)) { input_action = kTextInputActionNext; - else if STREQ("TextInputAction.previous", temp->string_value) + } else if (streq("TextInputAction.previous", temp->string_value)) { input_action = kTextInputActionPrevious; - else if STREQ("TextInputAction.continueAction", temp->string_value) + } else if (streq("TextInputAction.continueAction", temp->string_value)) { input_action = kTextInputActionContinueAction; - else if STREQ("TextInputAction.join", temp->string_value) + } else if (streq("TextInputAction.join", temp->string_value)) { input_action = kTextInputActionJoin; - else if STREQ("TextInputAction.route", temp->string_value) + } else if (streq("TextInputAction.route", temp->string_value)) { input_action = kTextInputActionRoute; - else if STREQ("TextInputAction.emergencyCall", temp->string_value) + } else if (streq("TextInputAction.emergencyCall", temp->string_value)) { input_action = kTextInputActionEmergencyCall; - else if STREQ("TextInputAction.newline", temp->string_value) + } else if (streq("TextInputAction.newline", temp->string_value)) { input_action = kTextInputActionNewline; - else + } else { return platch_respond_illegal_arg_json( responsehandle, "Expected `arg[1]['inputAction']` to be a string-ification of `TextInputAction`." ); + } // INPUT TYPE temp = jsobject_get(config, "inputType"); if (temp == NULL || temp->type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[1]['inputType']` to be a map." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[1]['inputType']` to be a map."); } temp2 = jsobject_get(temp, "signed"); @@ -201,10 +185,7 @@ static int on_set_client( has_allow_signs = true; allow_signs = temp2->type == kJsonTrue; } else { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[1]['inputType']['signed']` to be a boolean or null." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[1]['inputType']['signed']` to be a boolean or null."); } temp2 = jsobject_get(temp, "decimal"); @@ -214,10 +195,7 @@ static int on_set_client( has_allow_decimal = true; allow_decimal = temp2->type == kJsonTrue; } else { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg[1]['inputType']['decimal']` to be a boolean or null." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg[1]['inputType']['decimal']` to be a boolean or null."); } temp2 = jsobject_get(temp, "name"); @@ -228,25 +206,25 @@ static int on_set_client( ); } - if STREQ("TextInputType.text", temp2->string_value) { + if (streq("TextInputType.text", temp2->string_value)) { input_type = kInputTypeText; - } else if STREQ("TextInputType.multiline", temp2->string_value) { + } else if (streq("TextInputType.multiline", temp2->string_value)) { input_type = kInputTypeMultiline; - } else if STREQ("TextInputType.number", temp2->string_value) { + } else if (streq("TextInputType.number", temp2->string_value)) { input_type = kInputTypeNumber; - } else if STREQ("TextInputType.phone", temp2->string_value) { + } else if (streq("TextInputType.phone", temp2->string_value)) { input_type = kInputTypePhone; - } else if STREQ("TextInputType.datetime", temp2->string_value) { + } else if (streq("TextInputType.datetime", temp2->string_value)) { input_type = kInputTypeDatetime; - } else if STREQ("TextInputType.emailAddress", temp2->string_value) { + } else if (streq("TextInputType.emailAddress", temp2->string_value)) { input_type = kInputTypeEmailAddress; - } else if STREQ("TextInputType.url", temp2->string_value) { + } else if (streq("TextInputType.url", temp2->string_value)) { input_type = kInputTypeUrl; - } else if STREQ("TextInputType.visiblePassword", temp2->string_value) { + } else if (streq("TextInputType.visiblePassword", temp2->string_value)) { input_type = kInputTypeVisiblePassword; - } else if STREQ("TextInputType.name", temp2->string_value) { + } else if (streq("TextInputType.name", temp2->string_value)) { input_type = kInputTypeName; - } else if STREQ("TextInputType.address", temp2->string_value) { + } else if (streq("TextInputType.address", temp2->string_value)) { input_type = kInputTypeAddress; } else { return platch_respond_illegal_arg_json( @@ -265,30 +243,25 @@ static int on_set_client( text_input.input_type = input_type; if (autocorrect && !text_input.warned_about_autocorrect) { - printf("[text_input] warning: flutter requested native autocorrect, which" - "is not supported by flutter-pi.\n"); + printf( + "[text_input] warning: flutter requested native autocorrect, which" + "is not supported by flutter-pi.\n" + ); text_input.warned_about_autocorrect = true; } return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_hide( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_hide(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { /* * TextInput.hide() * Hide the keyboard. Unlike the other methods, this can be called * at any time. See [TextInputConnection.close]. - * + * */ (void) object; @@ -296,24 +269,17 @@ static int on_hide( // do nothing since we use a physical keyboard. return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_clear_client( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { - /* +static int on_clear_client(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + /* * TextInput.clearClient() * End the current transaction. The next method called must be * `TextInput.setClient` (or `TextInput.hide`). * See [TextInputConnection.close]. - * + * */ (void) object; @@ -322,18 +288,11 @@ static int on_clear_client( return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_set_editing_state( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_editing_state(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct json_value *temp, *state; char *text; bool selection_affinity_is_downstream, selection_is_directional; @@ -345,44 +304,32 @@ static int on_set_editing_state( * [String] with a JSON-encoded object with seven keys, as * obtained from [TextEditingValue.toJSON]. * See [TextInputConnection.setEditingState]. - * + * */ state = &object->json_arg; if (state->type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg` to be a map." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg` to be a map."); } temp = jsobject_get(state, "text"); if (temp == NULL || temp->type != kJsonString) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['text']` to be a string." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['text']` to be a string."); } else { text = temp->string_value; } temp = jsobject_get(state, "selectionBase"); if (temp == NULL || temp->type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['selectionBase']` to be a number." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['selectionBase']` to be a number."); } else { selection_base = (int) temp->number_value; } temp = jsobject_get(state, "selectionExtent"); if (temp == NULL || temp->type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['selectionExtent']` to be a number." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['selectionExtent']` to be a number."); } else { selection_extent = (int) temp->number_value; } @@ -394,9 +341,9 @@ static int on_set_editing_state( "Expected `arg['selectionAffinity']` to be a string-ification of `TextAffinity`." ); } else { - if STREQ("TextAffinity.downstream", temp->string_value) { + if (streq("TextAffinity.downstream", temp->string_value)) { selection_affinity_is_downstream = true; - } else if STREQ("TextAffinity.upstream", temp->string_value) { + } else if (streq("TextAffinity.upstream", temp->string_value)) { selection_affinity_is_downstream = false; } else { return platch_respond_illegal_arg_json( @@ -408,30 +355,21 @@ static int on_set_editing_state( temp = jsobject_get(state, "selectionIsDirectional"); if (temp == NULL || (temp->type != kJsonTrue && temp->type != kJsonFalse)) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['selectionIsDirectional']` to be a bool." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['selectionIsDirectional']` to be a bool."); } else { selection_is_directional = temp->type == kJsonTrue; } temp = jsobject_get(state, "composingBase"); if (temp == NULL || temp->type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['composingBase']` to be a number." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['composingBase']` to be a number."); } else { composing_base = (int) temp->number_value; } temp = jsobject_get(state, "composingExtent"); if (temp == NULL || temp->type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg['composingExtent']` to be a number." - ); + return platch_respond_illegal_arg_json(responsehandle, "Expected `arg['composingExtent']` to be a number."); } else { composing_extent = (int) temp->number_value; } @@ -446,22 +384,15 @@ static int on_set_editing_state( return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_show( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_show(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { /* * TextInput.show() * Show the keyboard. See [TextInputConnection.show]. - * + * */ (void) object; @@ -469,99 +400,63 @@ static int on_show( // do nothing since we use a physical keyboard. return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_request_autofill( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_request_autofill(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { (void) object; return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_set_editable_size_and_transform( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +UNUSED static int on_set_editable_size_and_transform(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { (void) object; return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_set_style( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_set_style(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { (void) object; return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_finish_autofill_context( - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_finish_autofill_context(struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { (void) object; return platch_respond( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + &(struct platch_obj){ .codec = kJSONMethodCallResponse, .success = true, .json_result = { .type = kJsonNull } } ); } -static int on_receive( - char *channel, - struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle -) { +static int on_receive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { (void) channel; (void) object; - if STREQ("TextInput.setClient", object->method) { + if (streq("TextInput.setClient", object->method)) { return on_set_client(object, responsehandle); - } else if STREQ("TextInput.hide", object->method) { + } else if (streq("TextInput.hide", object->method)) { return on_hide(object, responsehandle); - } else if STREQ("TextInput.clearClient", object->method) { + } else if (streq("TextInput.clearClient", object->method)) { return on_clear_client(object, responsehandle); - } else if STREQ("TextInput.setEditingState", object->method) { + } else if (streq("TextInput.setEditingState", object->method)) { return on_set_editing_state(object, responsehandle); - } else if STREQ("TextInput.show", object->method) { + } else if (streq("TextInput.show", object->method)) { return on_show(object, responsehandle); - } else if STREQ("TextInput.requestAutofill", object->method) { + } else if (streq("TextInput.requestAutofill", object->method)) { return on_request_autofill(object, responsehandle); - } else if STREQ("TextInput.setEditableSizeAndTransform", object->method) { - return on_set_style(object, responsehandle); - } else if STREQ("TextInput.setStyle", object->method) { + } else if (streq("TextInput.setEditableSizeAndTransform", object->method)) { + return on_set_editable_size_and_transform(object, responsehandle); + } else if (streq("TextInput.setStyle", object->method)) { return on_set_style(object, responsehandle); - } else if STREQ("TextInput.finishAutofillContext", object->method) { + } else if (streq("TextInput.finishAutofillContext", object->method)) { return on_finish_autofill_context(object, responsehandle); } @@ -584,13 +479,20 @@ static int client_update_editing_state( &JSONARRAY2( JSONNUM(connection_id), JSONOBJECT7( - "text", JSONSTRING(text), - "selectionBase", JSONNUM(selection_base), - "selectionExtent", JSONNUM(selection_extent), - "selectionAffinity", JSONSTRING(selection_affinity_is_downstream ? "TextAffinity.downstream" : "TextAffinity.upstream"), - "selectionIsDirectional", JSONBOOL(selection_is_directional), - "composingBase", JSONNUM(composing_base), - "composingExtent", JSONNUM(composing_extent) + "text", + JSONSTRING(text), + "selectionBase", + JSONNUM(selection_base), + "selectionExtent", + JSONNUM(selection_extent), + "selectionAffinity", + JSONSTRING(selection_affinity_is_downstream ? "TextAffinity.downstream" : "TextAffinity.upstream"), + "selectionIsDirectional", + JSONBOOL(selection_is_directional), + "composingBase", + JSONNUM(composing_base), + "composingExtent", + JSONNUM(composing_extent) ) ), NULL, @@ -598,42 +500,31 @@ static int client_update_editing_state( ); } -int client_perform_action( - double connection_id, - enum text_input_action action -) { - char *action_str = - (action == kTextInputActionNone) ? "TextInputAction.none" : - (action == kTextInputActionUnspecified) ? "TextInputAction.unspecified" : - (action == kTextInputActionDone) ? "TextInputAction.done" : - (action == kTextInputActionGo) ? "TextInputAction.go" : - (action == kTextInputActionSearch) ? "TextInputAction.search" : - (action == kTextInputActionSend) ? "TextInputAction.send" : - (action == kTextInputActionNext) ? "TextInputAction.next" : - (action == kTextInputActionPrevious) ? "TextInputAction.previous" : - (action == kTextInputActionContinueAction) ? "TextInputAction.continueAction" : - (action == kTextInputActionJoin) ? "TextInputAction.join" : - (action == kTextInputActionRoute) ? "TextInputAction.route" : - (action == kTextInputActionEmergencyCall) ? "TextInputAction.emergencyCall" : - "TextInputAction.newline"; +int client_perform_action(double connection_id, enum text_input_action action) { + char *action_str = (action == kTextInputActionNone) ? "TextInputAction.none" : + (action == kTextInputActionUnspecified) ? "TextInputAction.unspecified" : + (action == kTextInputActionDone) ? "TextInputAction.done" : + (action == kTextInputActionGo) ? "TextInputAction.go" : + (action == kTextInputActionSearch) ? "TextInputAction.search" : + (action == kTextInputActionSend) ? "TextInputAction.send" : + (action == kTextInputActionNext) ? "TextInputAction.next" : + (action == kTextInputActionPrevious) ? "TextInputAction.previous" : + (action == kTextInputActionContinueAction) ? "TextInputAction.continueAction" : + (action == kTextInputActionJoin) ? "TextInputAction.join" : + (action == kTextInputActionRoute) ? "TextInputAction.route" : + (action == kTextInputActionEmergencyCall) ? "TextInputAction.emergencyCall" : + "TextInputAction.newline"; return platch_call_json( TEXT_INPUT_CHANNEL, "TextInputClient.performAction", - &JSONARRAY2( - JSONNUM(connection_id), - JSONSTRING(action_str) - ), + &JSONARRAY2(JSONNUM(connection_id), JSONSTRING(action_str)), NULL, NULL ); } -int client_perform_private_command( - double connection_id, - char *action, - struct json_value *data -) { +int client_perform_private_command(double connection_id, char *action, struct json_value *data) { if (data != NULL && data->type != kJsonNull && data->type != kJsonObject) { return EINVAL; } @@ -641,38 +532,24 @@ int client_perform_private_command( return platch_call_json( TEXT_INPUT_CHANNEL, "TextInputClient.performPrivateCommand", - &JSONARRAY2( - JSONNUM(connection_id), - JSONOBJECT2( - "action", JSONSTRING(action), - "data", *data - ) - ), + &JSONARRAY2(JSONNUM(connection_id), JSONOBJECT2("action", JSONSTRING(action), "data", *data)), NULL, NULL ); } -int client_update_floating_cursor( - double connection_id, - enum floating_cursor_drag_state text_cursor_action, - double x, - double y -) { +int client_update_floating_cursor(double connection_id, enum floating_cursor_drag_state text_cursor_action, double x, double y) { return platch_call_json( TEXT_INPUT_CHANNEL, "TextInputClient.updateFloatingCursor", &JSONARRAY3( JSONNUM(connection_id), JSONSTRING( - text_cursor_action == kFloatingCursorDragStateStart ? "FloatingCursorDragState.start" : + text_cursor_action == kFloatingCursorDragStateStart ? "FloatingCursorDragState.start" : text_cursor_action == kFloatingCursorDragStateUpdate ? "FloatingCursorDragState.update" : - "FloatingCursorDragState.end" + "FloatingCursorDragState.end" ), - JSONOBJECT2( - "X", JSONNUM(x), - "Y", JSONNUM(y) - ) + JSONOBJECT2("X", JSONNUM(x), "Y", JSONNUM(y)) ), NULL, NULL @@ -680,30 +557,14 @@ int client_update_floating_cursor( } int client_on_connection_closed(double connection_id) { - return platch_call_json( - TEXT_INPUT_CHANNEL, - "TextInputClient.onConnectionClosed", - &JSONARRAY1( - JSONNUM(connection_id) - ), - NULL, - NULL - ); + return platch_call_json(TEXT_INPUT_CHANNEL, "TextInputClient.onConnectionClosed", &JSONARRAY1(JSONNUM(connection_id)), NULL, NULL); } -int client_show_autocorrection_prompt_rect( - double connection_id, - double start, - double end -) { +int client_show_autocorrection_prompt_rect(double connection_id, double start, double end) { return platch_call_json( TEXT_INPUT_CHANNEL, "TextInputClient.showAutocorrectionPromptRect", - &JSONARRAY3( - JSONNUM(connection_id), - JSONNUM(start), - JSONNUM(end) - ), + &JSONARRAY3(JSONNUM(connection_id), JSONNUM(start), JSONNUM(end)), NULL, NULL ); @@ -713,32 +574,32 @@ int client_show_autocorrection_prompt_rect( * Text Input Model functions. */ static inline int selection_start(void) { - return min(text_input.selection_base, text_input.selection_extent); + return MIN2(text_input.selection_base, text_input.selection_extent); } static inline int selection_end(void) { - return max(text_input.selection_base, text_input.selection_extent); + return MAX2(text_input.selection_base, text_input.selection_extent); } /** * Erases the characters between `start` and `end` (both inclusive) and returns * `start`. */ -static int model_erase(unsigned int start, unsigned int end) { +static int model_erase(unsigned int start, unsigned int end) { // 0 <= start <= end < len - uint8_t *start_str = symbol_at(start); - uint8_t *after_end_str = symbol_at(end+1); + uint8_t *start_str = symbol_at(start); + uint8_t *after_end_str = symbol_at(end + 1); if (start_str && after_end_str) - memmove(start_str, after_end_str, strlen((char*) after_end_str) + 1 /* null byte */); + memmove(start_str, after_end_str, strlen((char *) after_end_str) + 1 /* null byte */); return start; } static bool model_delete_selected(void) { // erase selected text - text_input.selection_base = model_erase(selection_start(), selection_end()-1); + text_input.selection_base = model_erase(selection_start(), selection_end() - 1); text_input.selection_extent = text_input.selection_base; return true; } @@ -753,7 +614,7 @@ static bool model_add_utf8_char(uint8_t *c) { // find out where in our string we need to insert the utf8 symbol symbol_length = utf8_symbol_length(*c); - to_move = symbol_at(text_input.selection_base); + to_move = symbol_at(text_input.selection_base); if (!to_move || !symbol_length) return false; @@ -761,7 +622,7 @@ static bool model_add_utf8_char(uint8_t *c) { // move the string behind the insertion position to // make place for the utf8 charactercursor - memmove(to_move + symbol_length, to_move, strlen((char*) to_move) + 1 /* null byte */); + memmove(to_move + symbol_length, to_move, strlen((char *) to_move) + 1 /* null byte */); // after the move, to_move points to the memory // where c should be inserted @@ -778,7 +639,7 @@ static bool model_add_utf8_char(uint8_t *c) { static bool model_backspace(void) { if (text_input.selection_base != text_input.selection_extent) return model_delete_selected(); - + if (text_input.selection_base != 0) { int base = text_input.selection_base - 1; text_input.selection_base = model_erase(base, base); @@ -792,7 +653,7 @@ static bool model_backspace(void) { static bool model_delete(void) { if (text_input.selection_base != text_input.selection_extent) return model_delete_selected(); - + if (selection_start() < strlen(text_input.text)) { text_input.selection_base = model_erase(selection_start(), selection_end()); text_input.selection_extent = text_input.selection_base; @@ -824,7 +685,7 @@ static bool model_move_cursor_to_end(void) { return false; } -static bool model_move_cursor_forward(void) { +UNUSED static bool model_move_cursor_forward(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_base = text_input.selection_extent; return true; @@ -839,10 +700,10 @@ static bool model_move_cursor_forward(void) { return false; } -static bool model_move_cursor_back(void) { +UNUSED static bool model_move_cursor_back(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_extent = text_input.selection_base; - return true; + return true; } if (text_input.selection_base > 0) { @@ -854,8 +715,6 @@ static bool model_move_cursor_back(void) { return false; } - - static int sync_editing_state(void) { return client_update_editing_state( text_input.connection_id, @@ -892,29 +751,21 @@ int textin_on_xkb_keysym(xkb_keysym_t keysym) { return 0; switch (keysym) { - case XKB_KEY_BackSpace: - needs_sync = model_backspace(); - break; + case XKB_KEY_BackSpace: needs_sync = model_backspace(); break; case XKB_KEY_Delete: - case XKB_KEY_KP_Delete: - needs_sync = model_delete(); - break; + case XKB_KEY_KP_Delete: needs_sync = model_delete(); break; case XKB_KEY_End: - case XKB_KEY_KP_End: - needs_sync = model_move_cursor_to_end(); - break; + case XKB_KEY_KP_End: needs_sync = model_move_cursor_to_end(); break; case XKB_KEY_Return: case XKB_KEY_KP_Enter: case XKB_KEY_ISO_Enter: if (text_input.input_type == kInputTypeMultiline) - needs_sync = model_add_utf8_char((uint8_t*) "\n"); - + needs_sync = model_add_utf8_char((uint8_t *) "\n"); + perform_action = true; break; case XKB_KEY_Home: - case XKB_KEY_KP_Home: - needs_sync = model_move_cursor_to_beginning(); - break; + case XKB_KEY_KP_Home: needs_sync = model_move_cursor_to_beginning(); break; case XKB_KEY_Left: case XKB_KEY_KP_Left: // handled inside of flutter @@ -925,18 +776,19 @@ int textin_on_xkb_keysym(xkb_keysym_t keysym) { // handled inside of flutter // needs_sync = model_move_cursor_forward(); break; - default: - break; + default: break; } if (needs_sync) { ok = sync_editing_state(); - if (ok != 0) return ok; + if (ok != 0) + return ok; } if (perform_action) { ok = client_perform_action(text_input.connection_id, text_input.input_action); - if (ok != 0) return ok; + if (ok != 0) + return ok; } return 0; @@ -950,13 +802,13 @@ enum plugin_init_result textin_init(struct flutterpi *flutterpi, void **userdata textin = malloc(sizeof *textin); if (textin == NULL) { - return kError_PluginInitResult; + return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver(TEXT_INPUT_CHANNEL, kJSONMethodCall, on_receive); + ok = plugin_registry_set_receiver_locked(TEXT_INPUT_CHANNEL, kJSONMethodCall, on_receive); if (ok != 0) { free(textin); - return kError_PluginInitResult; + return PLUGIN_INIT_RESULT_ERROR; } textin->connection_id = -1; @@ -976,17 +828,12 @@ enum plugin_init_result textin_init(struct flutterpi *flutterpi, void **userdata textin->composing_extent = 0; textin->warned_about_autocorrect = false; *userdata_out = textin; - return kInitialized_PluginInitResult; + return PLUGIN_INIT_RESULT_INITIALIZED; } void textin_deinit(struct flutterpi *flutterpi, void *userdata) { - (void) flutterpi; - plugin_registry_remove_receiver(TEXT_INPUT_CHANNEL); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TEXT_INPUT_CHANNEL); free(userdata); } -FLUTTERPI_PLUGIN( - "text input", text_input, - textin_init, - textin_deinit -) +FLUTTERPI_PLUGIN("text input", text_input, textin_init, textin_deinit) diff --git a/include/plugins/text_input.h b/src/plugins/text_input.h similarity index 88% rename from include/plugins/text_input.h rename to src/plugins/text_input.h index be1b54a5..e10d1497 100644 --- a/include/plugins/text_input.h +++ b/src/plugins/text_input.h @@ -1,6 +1,8 @@ #ifndef _TEXT_INPUT_H #define _TEXT_INPUT_H +#include + #include #define TEXT_INPUT_CHANNEL "flutter/textinput" @@ -42,15 +44,11 @@ struct text_input_configuration { enum text_input_action input_action; }; -enum floating_cursor_drag_state { - kFloatingCursorDragStateStart, - kFloatingCursorDragStateUpdate, - kFloatingCursorDragStateEnd -}; +enum floating_cursor_drag_state { kFloatingCursorDragStateStart, kFloatingCursorDragStateUpdate, kFloatingCursorDragStateEnd }; // parses the input string as linux terminal input and calls the TextInput model functions // accordingly. int textin_on_utf8_char(uint8_t *c); int textin_on_xkb_keysym(xkb_keysym_t keysym); -#endif \ No newline at end of file +#endif diff --git a/src/render_surface.c b/src/render_surface.c new file mode 100644 index 00000000..8016fa0e --- /dev/null +++ b/src/render_surface.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +/* + * render surface + * + * - A surface that can be scanned out, and that flutter can render into. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "render_surface.h" + +#include +#include +#include + +#include "compositor_ng.h" +#include "render_surface_private.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" + +// just so we can be sure &render_surface->surface is the same as (struct surface*) render_surface +COMPILE_ASSERT(offsetof(struct render_surface, surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0x78, 0x70, 0x45, 0x13, 0xa8, 0xf3, 0x43, 0x34, 0xa0, 0xa3, 0xae, 0x90, 0xf1, 0x11, 0x41, 0xe0); +#endif + +void render_surface_deinit(struct surface *s); + +int render_surface_init(struct render_surface *surface, struct tracer *tracer, struct vec2i size) { + int ok; + + ok = surface_init(&surface->surface, tracer); + if (ok != 0) { + return ok; + } + + surface->surface.deinit = render_surface_deinit; + surface->surface.present_kms = NULL; + surface->surface.present_fbdev = NULL; + +#ifdef DEBUG + uuid_copy(&surface->uuid, uuid); +#endif + + surface->size = size; + surface->fill = NULL; + surface->queue_present = NULL; + return 0; +} + +void render_surface_deinit(struct surface *s) { + surface_deinit(s); +} + +int render_surface_fill(struct render_surface *surface, FlutterBackingStore *fl_store) { + int ok; + + ASSERT_NOT_NULL(surface); + ASSERT_NOT_NULL(fl_store); + ASSERT_NOT_NULL(surface->fill); + + ASSERT_EQUALS(fl_store->user_data, NULL); + ASSERT_EQUALS(fl_store->did_update, false); + + TRACER_BEGIN(surface->surface.tracer, "render_surface_fill"); + ok = surface->fill(surface, fl_store); + TRACER_END(surface->surface.tracer, "render_surface_fill"); + + ASSERT_EQUALS(fl_store->user_data, NULL); + ASSERT_EQUALS(fl_store->did_update, false); + + return ok; +} + +int render_surface_queue_present(struct render_surface *surface, const FlutterBackingStore *fl_store) { + int ok; + + ASSERT_NOT_NULL(surface); + ASSERT_NOT_NULL(fl_store); + ASSERT_NOT_NULL(surface->queue_present); + + TRACER_BEGIN(surface->surface.tracer, "render_surface_queue_present"); + ok = surface->queue_present(surface, fl_store); + TRACER_END(surface->surface.tracer, "render_surface_queue_present"); + + return ok; +} + +#ifdef DEBUG +ATTR_PURE struct render_surface *__checked_cast_render_surface(void *ptr) { + struct render_surface *surface; + + surface = CAST_RENDER_SURFACE_UNCHECKED(ptr); + assert(uuid_equals(surface->uuid, uuid)); + return surface; +} +#endif diff --git a/src/render_surface.h b/src/render_surface.h new file mode 100644 index 00000000..994742e1 --- /dev/null +++ b/src/render_surface.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +/* + * Render surface + * - are special kinds of surfaces that flutter can render into + * - usually a render surface will have multiple framebuffers internally + * - the compositor or window will request a framebuffer for flutter to render into + * in form of a framebuffer using render_surface_fill. + * - Once flutter has rendered into that backing store (whatever it's backed by), + * the compositor will call render_surface_queue_present on the render surface, + * and the argument backing store is the one that was provided using render_surface_fill. + * - That framebuffer is the one that should be committed when the compositor/window calls surface_present_... + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_RENDER_SURFACE_H +#define _FLUTTERPI_SRC_RENDER_SURFACE_H + +#include + +#include "util/collection.h" + +struct surface; +struct render_surface; + +#define CAST_RENDER_SURFACE_UNCHECKED(ptr) ((struct render_surface *) (ptr)) +#ifdef DEBUG + #define CAST_RENDER_SURFACE(ptr) __checked_cast_render_surface(ptr) +ATTR_PURE struct render_surface *__checked_cast_render_surface(void *ptr); +#else + #define CAST_RENDER_SURFACE(ptr) CAST_RENDER_SURFACE_UNCHECKED(ptr) +#endif + +int render_surface_fill(struct render_surface *store, FlutterBackingStore *fl_store); + +int render_surface_queue_present(struct render_surface *store, const FlutterBackingStore *fl_store); + +#endif // _FLUTTERPI_SRC_BACKING_STORE_H diff --git a/src/render_surface_private.h b/src/render_surface_private.h new file mode 100644 index 00000000..d93e1aed --- /dev/null +++ b/src/render_surface_private.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +/* + * Render surface implementation + * + * - private implementation for render surfaces + * - needed for implementing specific kinds of render surfaces + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H +#define _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H + +#include + +#include "compositor_ng.h" +#include "surface_private.h" +#include "util/collection.h" + +struct render_surface { + struct surface surface; + +#ifdef DEBUG + uuid_t uuid; +#endif + + struct vec2i size; + int (*fill)(struct render_surface *surface, FlutterBackingStore *fl_store); + int (*queue_present)(struct render_surface *surface, const FlutterBackingStore *fl_store); +}; + +int render_surface_init(struct render_surface *surface, struct tracer *tracer, struct vec2i size); + +void render_surface_deinit(struct surface *s); + +#endif // _FLUTTERPI_SRC_RENDER_SURFACE_PRIVATE_H diff --git a/src/surface.c b/src/surface.c new file mode 100644 index 00000000..ecb85114 --- /dev/null +++ b/src/surface.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +/* + * Window Surface + * + * - provides an object that can be composited by flutter-pi + * - (by calling present_kms or present_fbdev on it) + * - == basically the thing that stores the graphics of a FlutterLayer + * - render surfaces are special kinds of scanout surfaces that flutter can render into + * - every scanout surface can be registered as a platform view to display it + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "surface.h" + +#include +#include + +#include "compositor_ng.h" +#include "surface_private.h" +#include "tracer.h" +#include "util/collection.h" + +void surface_deinit(struct surface *s); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0xce, 0x35, 0x87, 0x0c, 0x82, 0x08, 0x46, 0x09, 0xbd, 0xab, 0x80, 0x67, 0x28, 0x15, 0x45, 0xb5); + +ATTR_PURE struct surface *__checked_cast_surface(void *ptr) { + struct surface *s; + + s = CAST_SURFACE_UNCHECKED(ptr); + assert(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +int surface_init(struct surface *s, struct tracer *tracer) { +#ifdef DEBUG + uuid_copy(&s->uuid, uuid); +#endif + s->n_refs = REFCOUNT_INIT_1; + pthread_mutex_init(&s->lock, NULL); + s->tracer = tracer_ref(tracer); + s->revision = 1; + s->present_kms = NULL; + s->present_fbdev = NULL; + s->deinit = surface_deinit; + return 0; +} + +void surface_deinit(struct surface *s) { + pthread_mutex_destroy(&s->lock); + tracer_unref(s->tracer); +} + +struct surface *surface_new(struct tracer *tracer) { + struct surface *s; + int ok; + + s = malloc(sizeof *s); + if (s == NULL) { + return NULL; + } + + ok = surface_init(s, tracer); + if (ok != 0) { + free(s); + return NULL; + } + + return s; +} + +void surface_destroy(struct surface *s) { + ASSERT_NOT_NULL(s->deinit); + s->deinit(s); + free(s); +} + +DEFINE_LOCK_OPS(surface, lock) + +DEFINE_REF_OPS(surface, n_refs) + +int64_t surface_get_revision(struct surface *s) { + ASSERT_NOT_NULL(s); + return s->revision; +} + +int surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + int ok; + + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(props); + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(s->present_kms); + + TRACER_BEGIN(s->tracer, "surface_present_kms"); + ok = s->present_kms(s, props, builder); + TRACER_END(s->tracer, "surface_present_kms"); + + return ok; +} + +int surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + int ok; + + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(props); + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(s->present_fbdev); + + TRACER_BEGIN(s->tracer, "surface_present_fbdev"); + ok = s->present_fbdev(s, props, builder); + TRACER_END(s->tracer, "surface_present_fbdev"); + + return ok; +} diff --git a/src/surface.h b/src/surface.h new file mode 100644 index 00000000..8d6b2987 --- /dev/null +++ b/src/surface.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * Surface + * + * - rendering / scanout surface interface + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_SURFACE_H +#define _FLUTTERPI_SRC_SURFACE_H + +#include "util/collection.h" +#include "util/lock_ops.h" +#include "util/refcounting.h" + +struct surface; +struct compositor; +struct fl_layer_props; +struct kms_req_builder; +struct fbdev_commit_builder; + +#define CAST_SURFACE_UNCHECKED(ptr) ((struct surface *) (ptr)) +#ifdef DEBUG + #define CAST_SURFACE(ptr) __checked_cast_surface(ptr) +ATTR_PURE struct surface *__checked_cast_surface(void *ptr); +#else + #define CAST_SURFACE(ptr) CAST_SURFACE_UNCHECKED(ptr) +#endif + +void surface_destroy(struct surface *s); + +DECLARE_LOCK_OPS(surface) + +DECLARE_REF_OPS(surface) + +ATTR_PURE static inline struct surface *surface_from_id(int64_t id) { + return CAST_SURFACE(int64_to_ptr(id)); +} + +ATTR_PURE int64_t surface_get_revision(struct surface *s); + +int surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); + +int surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); + +#endif // _FLUTTERPI_SRC_SURFACE_H diff --git a/src/surface_private.h b/src/surface_private.h new file mode 100644 index 00000000..72488a3b --- /dev/null +++ b/src/surface_private.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +/* + * Surface Implementation details + * + * - should be included for expanding the surface (i.e. for a backing store or platform view surface type) + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_SURFACE_PRIVATE_H +#define _FLUTTERPI_SRC_SURFACE_PRIVATE_H + +#include + +#include + +#include "util/collection.h" +#include "util/refcounting.h" +#include "util/uuid.h" + +struct fl_layer_props; +struct kms_req_builder; +struct fbdev_commit_builder; +struct tracer; + +struct surface { +#ifdef DEBUG + uuid_t uuid; +#endif + refcount_t n_refs; + pthread_mutex_t lock; + struct tracer *tracer; + int64_t revision; + + int (*present_kms)(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); + int (*present_fbdev)(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); + void (*deinit)(struct surface *s); +}; + +int surface_init(struct surface *s, struct tracer *tracer); + +void surface_deinit(struct surface *s); + +#endif // _FLUTTERPI_SRC_SURFACE_PRIVATE_H diff --git a/src/texture_registry.c b/src/texture_registry.c index ef7e7ef7..a2ab95b0 100644 --- a/src/texture_registry.c +++ b/src/texture_registry.c @@ -1,34 +1,50 @@ -#include -#include -#include +#include "texture_registry.h" + #include -#include #include +#include +#include +#include +#include +#include #include -#include -#include - -FILE_DESCR("texture registry") +#include "flutter-pi.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/refcounting.h" struct texture_registry { - struct flutter_external_texture_interface texture_interface; - pthread_mutex_t next_unused_id_mutex; - int64_t next_unused_id; - struct concurrent_pointer_set textures; + struct texture_registry_interface interface; + void *userdata; + + pthread_mutex_t lock; + + atomic_int_least64_t next_unused_id; + struct list_head textures; }; +DEFINE_STATIC_LOCK_OPS(texture_registry, lock) + struct counted_texture_frame { refcount_t n_refs; + + bool is_resolved; struct texture_frame frame; + + struct unresolved_texture_frame unresolved_frame; }; void counted_texture_frame_destroy(struct counted_texture_frame *frame) { - frame->frame.destroy( - &frame->frame, - frame->frame.userdata - ); + if (frame->is_resolved) { + if (frame->frame.destroy != NULL) { + frame->frame.destroy(&frame->frame, frame->frame.userdata); + } + } else if (frame->unresolved_frame.destroy != NULL) { + frame->unresolved_frame.destroy(frame->unresolved_frame.userdata); + } free(frame); } @@ -39,6 +55,8 @@ struct texture { pthread_mutex_t lock; + struct list_head entry; + /// The texture id the flutter engine uses to identify this texture. int64_t id; @@ -49,103 +67,58 @@ struct texture { /** * @brief True if next_frame was not yet fetched by the engine. So if @ref texture_push_frame is called, * we can just replace @ref next_frame with the new frame and don't need to call mark frame available again. - * + * */ bool dirty; }; -struct texture_registry *texture_registry_new( - const struct flutter_external_texture_interface *texture_interface -) { +struct texture_registry *texture_registry_new(const struct texture_registry_interface *interface, void *userdata) { struct texture_registry *reg; - int ok; reg = malloc(sizeof *reg); if (reg == NULL) { return NULL; } - pthread_mutex_init(®->next_unused_id_mutex, NULL); + pthread_mutex_init(®->lock, get_default_mutex_attrs()); - memcpy(®->texture_interface, texture_interface, sizeof(*texture_interface)); + memcpy(®->interface, interface, sizeof(*interface)); + reg->userdata = userdata; reg->next_unused_id = 1; - - ok = cpset_init(®->textures, CPSET_DEFAULT_MAX_SIZE); - if (ok != 0) { - free(reg); - return NULL; - } + list_inithead(®->textures); return reg; } void texture_registry_destroy(struct texture_registry *reg) { - cpset_lock(®->textures); - int count = cpset_get_count_pointers_locked(®->textures); +#ifndef NDEBUG + int count = list_length(®->textures); if (count > 0) { LOG_ERROR("Error destroying texture registry: There are still %d textures registered. This is an application bug.\n", count); + assert(false); } - cpset_unlock(®->textures); +#endif - cpset_deinit(®->textures); - pthread_mutex_destroy(®->next_unused_id_mutex); + pthread_mutex_destroy(®->lock); free(reg); } int64_t texture_registry_allocate_id(struct texture_registry *reg) { - pthread_mutex_lock(®->next_unused_id_mutex); - - int64_t id = reg->next_unused_id++; - - pthread_mutex_unlock(®->next_unused_id_mutex); - - return id; -} - -static int texture_registry_engine_register_id(struct texture_registry *reg, int64_t id) { - FlutterEngineResult engine_result; - - engine_result = reg->texture_interface.register_external_texture(reg->texture_interface.engine, id); - if (engine_result != kSuccess) { - LOG_ERROR("Error registering external texture to flutter engine. FlutterEngineRegisterExternalTexture: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; - } - - return 0; -} - -static void texture_registry_engine_unregister_id(struct texture_registry *reg, int64_t id) { - FlutterEngineResult engine_result; - - engine_result = reg->texture_interface.unregister_external_texture(reg->texture_interface.engine, id); - if (engine_result != kSuccess) { - LOG_ERROR("Error unregistering external texture from flutter engine. FlutterEngineUnregisterExternalTexture: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - } -} - -static int texture_registry_engine_notify_frame_available(struct texture_registry *reg, int64_t id) { - FlutterEngineResult engine_result; - - engine_result = reg->texture_interface.mark_external_texture_frame_available(reg->texture_interface.engine, id); - if (engine_result != kSuccess) { - LOG_ERROR("Error notifying flutter engine about new external texture frame. FlutterEngineMarkExternalTextureFrameAvailable: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - return EINVAL; - } - - return 0; + return atomic_fetch_add(®->next_unused_id, 1); } static int texture_registry_register_texture(struct texture_registry *reg, struct texture *texture) { int ok; - ok = cpset_put(®->textures, texture); - if (ok != 0) { - return ok; - } + texture_registry_lock(reg); + list_add(&texture->entry, ®->textures); + texture_registry_unlock(reg); - ok = texture_registry_engine_register_id(reg, texture->id); + ok = reg->interface.register_texture(reg->userdata, texture->id); if (ok != 0) { - cpset_remove(®->textures, texture); + texture_registry_lock(reg); + list_del(&texture->entry); + texture_registry_unlock(reg); return ok; } @@ -153,15 +126,16 @@ static int texture_registry_register_texture(struct texture_registry *reg, struc } static void texture_registry_unregister_texture(struct texture_registry *reg, struct texture *texture) { - texture_registry_engine_unregister_id(reg, texture->id); - cpset_remove(®->textures, texture); + reg->interface.unregister_texture(reg->userdata, texture->id); + + texture_registry_lock(reg); + list_del(&texture->entry); + texture_registry_unlock(reg); } -static bool texture_gl_external_texture_frame_callback( - struct texture *texture, - size_t width, size_t height, - FlutterOpenGLTexture *texture_out -); +#ifdef HAVE_EGL_GLES2 +static bool +texture_gl_external_texture_frame_callback(struct texture *texture, size_t width, size_t height, FlutterOpenGLTexture *texture_out); bool texture_registry_gl_external_texture_frame_callback( struct texture_registry *reg, @@ -170,17 +144,21 @@ bool texture_registry_gl_external_texture_frame_callback( size_t height, FlutterOpenGLTexture *texture_out ) { - struct texture *t; + struct texture *texture; bool result; - cpset_lock(®->textures); - for_each_pointer_in_cpset(®->textures, t) { + texture_registry_lock(reg); + + texture = NULL; + list_for_each_entry_safe(struct texture, t, ®->textures, entry) { if (t->id == texture_id) { + texture = t; break; } } - if (t != NULL) { - result = texture_gl_external_texture_frame_callback(t, width, height, texture_out); + + if (texture != NULL) { + result = texture_gl_external_texture_frame_callback(texture, width, height, texture_out); } else { // the texture was destroyed after notifying the engine of a new texture frame. // just report an empty frame here. @@ -193,11 +171,12 @@ bool texture_registry_gl_external_texture_frame_callback( texture_out->width = 0; texture_out->height = 0; } - cpset_unlock(®->textures); + + texture_registry_unlock(reg); return result; } - +#endif DEFINE_INLINE_LOCK_OPS(texture, lock) @@ -212,8 +191,8 @@ struct texture *texture_new(struct texture_registry *reg) { } id = texture_registry_allocate_id(reg); - - pthread_mutex_init(&texture->lock, NULL); + + pthread_mutex_init(&texture->lock, get_default_mutex_attrs()); texture->registry = reg; texture->id = id; texture->next_frame = NULL; @@ -232,29 +211,36 @@ int64_t texture_get_id(struct texture *texture) { return texture->id; } -int texture_push_frame(struct texture *texture, const struct texture_frame *frame) { +static int push_frame( + struct texture *texture, + bool is_resolved, + const struct texture_frame *frame, + const struct unresolved_texture_frame *unresolved_frame +) { struct counted_texture_frame *counted_frame; int ok; + // I know there's memdup, but let's just be explicit here. counted_frame = malloc(sizeof *counted_frame); if (counted_frame == NULL) { return ENOMEM; } - counted_frame->n_refs = REFCOUNT_INIT_1; - counted_frame->frame = *frame; + counted_frame->n_refs = REFCOUNT_INIT_0; + counted_frame->is_resolved = is_resolved; + if (frame != NULL) { + counted_frame->frame = *frame; + } + if (unresolved_frame != NULL) { + counted_frame->unresolved_frame = *unresolved_frame; + } texture_lock(texture); - if (texture->next_frame != NULL) { - counted_texture_frame_unref(texture->next_frame); - texture->next_frame = NULL; - } + counted_texture_frame_swap_ptrs(&texture->next_frame, counted_frame); - texture->next_frame = counted_frame; - if (texture->dirty == false) { - ok = texture_registry_engine_notify_frame_available(texture->registry, texture->id); + ok = texture->registry->interface.mark_frame_available(texture->registry->userdata, texture->id); if (ok != 0) { /// TODO: Don't really know what do to here. } @@ -271,6 +257,14 @@ int texture_push_frame(struct texture *texture, const struct texture_frame *fram return 0; } +int texture_push_frame(struct texture *texture, const struct texture_frame *frame) { + return push_frame(texture, true, frame, NULL); +} + +int texture_push_unresolved_frame(struct texture *texture, const struct unresolved_texture_frame *frame) { + return push_frame(texture, false, NULL, frame); +} + void texture_destroy(struct texture *texture) { texture_registry_unregister_texture(texture->registry, texture); if (texture->next_frame != NULL) { @@ -280,32 +274,30 @@ void texture_destroy(struct texture *texture) { free(texture); } -static void on_flutter_acquired_frame_destroy(void *userdata) { +UNUSED static void on_flutter_acquired_frame_destroy(void *userdata) { struct counted_texture_frame *frame; - DEBUG_ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(userdata); frame = userdata; counted_texture_frame_unref(frame); } -static bool texture_gl_external_texture_frame_callback( - struct texture *texture, - size_t width, - size_t height, - FlutterOpenGLTexture *texture_out -) { +#ifdef HAVE_EGL_GLES2 +static bool +texture_gl_external_texture_frame_callback(struct texture *texture, size_t width, size_t height, FlutterOpenGLTexture *texture_out) { struct counted_texture_frame *frame; + int ok; (void) width; (void) height; - DEBUG_ASSERT_NOT_NULL(texture); - DEBUG_ASSERT_NOT_NULL(texture_out); + ASSERT_NOT_NULL(texture); + ASSERT_NOT_NULL(texture_out); texture_lock(texture); - + if (texture->next_frame != NULL) { /// TODO: If acquiring the texture frame fails, flutter will destroy the texture frame two times. /// So we'll probably have a segfault if that happens. @@ -313,11 +305,24 @@ static bool texture_gl_external_texture_frame_callback( } else { frame = NULL; } - + /// flutter has now fetched the texture, so if we want to present a new frame /// we need to call @ref texture_registry_engine_notify_frame_available again. texture->dirty = false; - + + if (frame != NULL && !frame->is_resolved) { + // resolve the frame to an actual OpenGL frame. + ok = frame->unresolved_frame.resolve(width, height, frame->unresolved_frame.userdata, &frame->frame); + if (ok != 0) { + LOG_ERROR("Couldn't resolve texture frame.\n"); + counted_texture_frame_unrefp(&frame); + counted_texture_frame_unrefp(&texture->next_frame); + } + + frame->unresolved_frame.destroy(frame->unresolved_frame.userdata); + frame->is_resolved = true; + } + texture_unlock(texture); // only actually fill out the frame info when we have a frame. @@ -344,3 +349,4 @@ static bool texture_gl_external_texture_frame_callback( return false; } } +#endif diff --git a/src/texture_registry.h b/src/texture_registry.h new file mode 100644 index 00000000..cc30122a --- /dev/null +++ b/src/texture_registry.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +/* + * Texture Registry + * + * Manages flutter external texture registration, texture frame callbacks. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_TEXTURE_REGISTRY_H +#define _FLUTTERPI_SRC_TEXTURE_REGISTRY_H + +#include + +#include "config.h" + +#ifdef HAVE_EGL_GLES2 + #include "gles.h" +#endif + +struct texture_registry_interface { + int (*register_texture)(void *userdata, int64_t texture_identifier); + int (*unregister_texture)(void *userdata, int64_t texture_identifier); + int (*mark_frame_available)(void *userdata, int64_t texture_identifier); +}; + +struct texture_registry; +struct texture; + +#ifdef HAVE_EGL_GLES2 +struct gl_texture_frame { + GLenum target; + GLuint name; + GLuint format; + size_t width; + size_t height; +}; +#endif + +struct texture_frame; +struct texture_frame { + union { +#ifdef HAVE_EGL_GLES2 + struct gl_texture_frame gl; +#endif + }; + void (*destroy)(const struct texture_frame *frame, void *userdata); + void *userdata; +}; + +struct unresolved_texture_frame { + int (*resolve)(size_t width, size_t height, void *userdata, struct texture_frame *frame_out); + void (*destroy)(void *userdata); + void *userdata; +}; + +struct texture_registry *texture_registry_new(const struct texture_registry_interface *interface, void *userdata); + +void texture_registry_destroy(struct texture_registry *reg); + +#ifdef HAVE_EGL_GLES2 +bool texture_registry_gl_external_texture_frame_callback( + struct texture_registry *reg, + int64_t texture_id, + size_t width, + size_t height, + FlutterOpenGLTexture *texture_out +); +#endif + +struct texture *texture_new(struct texture_registry *reg); + +int64_t texture_get_id(struct texture *texture); + +int texture_push_frame(struct texture *texture, const struct texture_frame *frame); + +int texture_push_unresolved_frame(struct texture *texture, const struct unresolved_texture_frame *frame); + +void texture_destroy(struct texture *texture); + +#endif // _FLUTTERPI_SRC_TEXTURE_REGISTRY_H diff --git a/src/tracer.c b/src/tracer.c new file mode 100644 index 00000000..9177fede --- /dev/null +++ b/src/tracer.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +/* + * Tracer - simple event tracing based on flutter event tracing interface + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "tracer.h" + +#include + +#include + +#include "util/asserts.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +struct tracer { + refcount_t n_refs; + atomic_bool has_cbs; + FlutterEngineTraceEventDurationBeginFnPtr trace_begin; + FlutterEngineTraceEventDurationEndFnPtr trace_end; + FlutterEngineTraceEventInstantFnPtr trace_instant; + + atomic_bool logged_discarded_events; +}; + +struct tracer *tracer_new_with_cbs( + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +) { + struct tracer *tracer; + + tracer = malloc(sizeof *tracer); + if (tracer == NULL) { + goto fail_return_null; + } + + tracer->n_refs = REFCOUNT_INIT_1; + tracer->has_cbs = true; + tracer->trace_begin = trace_begin; + tracer->trace_end = trace_end; + tracer->trace_instant = trace_instant; + tracer->logged_discarded_events = false; + return tracer; + +fail_return_null: + return NULL; +} + +struct tracer *tracer_new_with_stubs() { + struct tracer *tracer; + + tracer = malloc(sizeof *tracer); + if (tracer == NULL) { + goto fail_return_null; + } + + tracer->n_refs = REFCOUNT_INIT_1; + tracer->has_cbs = false; + tracer->trace_begin = NULL; + tracer->trace_end = NULL; + tracer->trace_instant = NULL; + tracer->logged_discarded_events = false; + return tracer; + +fail_return_null: + return NULL; +} + +void tracer_set_cbs( + struct tracer *tracer, + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +) { + tracer->trace_begin = trace_begin; + tracer->trace_end = trace_end; + tracer->trace_instant = trace_instant; + + bool already_set_before = atomic_exchange(&tracer->has_cbs, true); + ASSERT_MSG(!already_set_before, "tracing callbacks can only be set once."); + (void) already_set_before; +} + +void tracer_destroy(struct tracer *tracer) { + free(tracer); +} + +DEFINE_REF_OPS(tracer, n_refs) + +static void log_discarded_event(struct tracer *tracer, const char *name) { + (void) name; + if (atomic_exchange(&tracer->logged_discarded_events, true) == false) { + LOG_DEBUG("Tracing event was discarded because tracer not initialized yet: %s. This message will only be logged once.\n", name); + } +} + +void __tracer_begin(struct tracer *tracer, const char *name) { + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_begin(name); + } else { + log_discarded_event(tracer, name); + } +} + +void __tracer_end(struct tracer *tracer, const char *name) { + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_end(name); + } else { + log_discarded_event(tracer, name); + } +} + +void __tracer_instant(struct tracer *tracer, const char *name) { + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(name); + if (atomic_load(&tracer->has_cbs)) { + tracer->trace_instant(name); + } else { + log_discarded_event(tracer, name); + } +} diff --git a/src/tracer.h b/src/tracer.h new file mode 100644 index 00000000..896ee686 --- /dev/null +++ b/src/tracer.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +/* + * Tracer - simple object for tracing using flutter event tracing interface. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_TRACER_H +#define _FLUTTERPI_SRC_TRACER_H + +#include + +#include "util/refcounting.h" + +struct tracer; + +struct tracer *tracer_new_with_cbs( + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +); + +struct tracer *tracer_new_with_stubs(); + +DECLARE_REF_OPS(tracer); + +void __tracer_begin(struct tracer *tracer, const char *name); + +void __tracer_end(struct tracer *tracer, const char *name); + +void __tracer_instant(struct tracer *tracer, const char *name); + +void tracer_set_cbs( + struct tracer *tracer, + FlutterEngineTraceEventDurationBeginFnPtr trace_begin, + FlutterEngineTraceEventDurationEndFnPtr trace_end, + FlutterEngineTraceEventInstantFnPtr trace_instant +); + +#ifdef DEBUG + #define TRACER_BEGIN(tracer, name) __tracer_begin(tracer, name) + #define TRACER_END(tracer, name) __tracer_end(tracer, name) + #define TRACER_INSTANT(tracer, name) __tracer_instant(tracer, name) +#else + // Cast tracer & name to void so we don't get unused variable warnings + // in release mode. + #define TRACER_BEGIN(tracer, name) \ + do { \ + (void) tracer; \ + (void) name; \ + } while (0) + #define TRACER_END(tracer, name) \ + do { \ + (void) tracer; \ + (void) name; \ + } while (0) + #define TRACER_INSTANT(tracer, name) \ + do { \ + (void) tracer; \ + (void) name; \ + } while (0) +#endif + +#define DECLARE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name) \ + static void trace_begin(struct obj_type_name *obj_var_name, const char *name); \ + static void trace_end(struct obj_type_name *obj_var_name, const char *name); \ + static void trace_instant(struct obj_type_name *obj_var_name, const char *name); + +#define DEFINE_STATIC_TRACING_CALLS(obj_type_name, obj_var_name, tracer_member_name) \ + static void trace_begin(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_begin(obj_var_name->tracer_member_name, name); \ + } \ + static void trace_end(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_end(obj_var_name->tracer_member_name, name); \ + } \ + static void trace_instant(struct obj_type_name *obj_var_name, const char *name) { \ + return tracer_instant(obj_var_name->tracer_member_name, name); \ + } + +#endif // _FLUTTERPI_SRC_TRACER_H diff --git a/src/user_input.c b/src/user_input.c index 28583589..a568d957 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -1,44 +1,47 @@ -#include +#include "user_input.h" + +#include +#include #include -#include +#include + #include -#include -#include #include -#include +#include +#include +#include #include +#include #include -#include - -#include -#include -#include -#include -FILE_DESCR("user input") +#include "compositor_ng.h" +#include "flutter-pi.h" +#include "keyboard.h" +#include "util/collection.h" +#include "util/logging.h" -#define LIBINPUT_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) +#define LIBINPUT_VER(major, minor, patch) ((((major) &0xFF) << 16) | (((minor) &0xFF) << 8) | ((patch) &0xFF)) #define THIS_LIBINPUT_VER LIBINPUT_VER(LIBINPUT_VERSION_MAJOR, LIBINPUT_VERSION_MINOR, LIBINPUT_VERSION_PATCH) struct input_device_data { - int64_t flutter_device_id_offset; - struct keyboard_state *keyboard_state; - double x, y; - int64_t buttons; - uint64_t timestamp; + int64_t flutter_device_id_offset; + struct keyboard_state *keyboard_state; + double x, y; + int64_t buttons; + uint64_t timestamp; /** * @brief Whether libinput has ever emitted any pointer / mouse events * for this device. - * + * * Only applies to devices which have LIBINPUT_DEVICE_CAP_POINTER. */ bool has_emitted_pointer_events; /** * @brief Only applies to tablets. True if the tablet tool is in contact with the screen right now. - * + * */ bool tip; }; @@ -47,21 +50,21 @@ struct user_input { struct user_input_interface interface; void *userdata; - struct libinput *libinput; - struct keyboard_config *kbdcfg; - int64_t next_unused_flutter_device_id; + struct libinput *libinput; + struct keyboard_config *kbdcfg; + int64_t next_unused_flutter_device_id; /// TODO: Maybe fetch the transform, display dimensions, cursor pos dynamically using a callback instead? - /** + /** * @brief transforms normalized display coordinates (0 .. display_width-1, 0 .. display_height-1) to the coordinates * used in the flutter pointer events */ - FlutterTransformation display_to_view_transform; - FlutterTransformation view_to_display_transform_nontranslating; - unsigned int display_width; - unsigned int display_height; - + struct mat3f display_to_view_transform; + struct mat3f view_to_display_transform_nontranslating; + unsigned int display_width; + unsigned int display_height; + /** * @brief The number of devices connected that want a mouse cursor. * libinput calls them pointer devices, flutter calls them mice. @@ -87,87 +90,83 @@ struct user_input { }; // libinput interface -static int open_restricted(const char *path, int flags, void *userdata) { - (void) userdata; - return open(path, flags | O_CLOEXEC); +static int on_open(const char *path, int flags, void *userdata) { + struct user_input *input; + + ASSERT_NOT_NULL(path); + ASSERT_NOT_NULL(userdata); + input = userdata; + + return input->interface.open(path, flags | O_CLOEXEC, input->userdata); } -static void close_restricted(int fd, void *userdata) { - (void) userdata; - close(fd); +static void on_close(int fd, void *userdata) { + struct user_input *input; + + ASSERT_NOT_NULL(userdata); + input = userdata; + + return input->interface.close(fd, input->userdata); } -static const struct libinput_interface libinput_interface = { - .open_restricted = open_restricted, - .close_restricted = close_restricted -}; +static const struct libinput_interface libinput_interface = { .open_restricted = on_open, .close_restricted = on_close }; struct user_input *user_input_new( - const struct user_input_interface *interface, + const struct user_input_interface *interface, void *userdata, - const FlutterTransformation *display_to_view_transform, - const FlutterTransformation *view_to_display_transform, - unsigned int display_width, - unsigned int display_height + const struct mat3f *display_to_view_transform, + const struct mat3f *view_to_display_transform, + unsigned int display_width, + unsigned int display_height ) { - struct keyboard_config *kbdcfg; - struct user_input *input; - struct libinput *libinput; - struct udev *udev; - int ok; - - input = malloc(sizeof *input); - if (input == NULL) { - goto fail_return_null; - } - - udev = udev_new(); - if (udev == NULL) { - perror("[flutter-pi] Could not create udev instance. udev_new"); - goto fail_free_input; - } - - libinput = libinput_udev_create_context( - &libinput_interface, - input, - udev - ); - if (libinput == NULL) { - perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); - goto fail_unref_udev; - } - - udev_unref(udev); - - ok = libinput_udev_assign_seat(libinput, "seat0"); - if (ok < 0) { - LOG_ERROR("Could not assign udev seat to libinput instance. libinput_udev_assign_seat: %s\n", strerror(-ok)); - goto fail_unref_libinput; - } + struct keyboard_config *kbdcfg; + struct user_input *input; + struct libinput *libinput; + struct udev *udev; + int ok; + + input = malloc(sizeof *input); + if (input == NULL) { + goto fail_return_null; + } + + udev = udev_new(); + if (udev == NULL) { + perror("[flutter-pi] Could not create udev instance. udev_new"); + goto fail_free_input; + } + + input->interface = *interface; + input->userdata = userdata; + + libinput = libinput_udev_create_context(&libinput_interface, input, udev); + if (libinput == NULL) { + perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); + goto fail_unref_udev; + } + + udev_unref(udev); + + ok = libinput_udev_assign_seat(libinput, "seat0"); + if (ok < 0) { + LOG_ERROR("Could not assign udev seat to libinput instance. libinput_udev_assign_seat: %s\n", strerror(-ok)); + goto fail_unref_libinput; + } #ifdef BUILD_TEXT_INPUT_PLUGIN - kbdcfg = keyboard_config_new(); - if (kbdcfg == NULL) { - LOG_ERROR("Could not initialize keyboard configuration. Flutter-pi will run without text/raw keyboard input.\n"); - } + kbdcfg = keyboard_config_new(); + if (kbdcfg == NULL) { + LOG_ERROR("Could not initialize keyboard configuration. Flutter-pi will run without text/raw keyboard input.\n"); + } #else kbdcfg = NULL; #endif - input->interface = *interface; - input->userdata = userdata; + input->libinput = libinput; + input->kbdcfg = kbdcfg; + input->next_unused_flutter_device_id = 0; - input->libinput = libinput; - input->kbdcfg = kbdcfg; - input->next_unused_flutter_device_id = 0; - - user_input_set_transform( - input, - display_to_view_transform, - view_to_display_transform, - display_width, - display_height - ); + user_input_set_transform(input, display_to_view_transform, view_to_display_transform, display_width, display_height); input->n_cursor_devices = 0; input->cursor_flutter_device_id = -1; @@ -176,47 +175,71 @@ struct user_input *user_input_new( input->n_collected_flutter_pointer_events = 0; - return input; - + return input; - fail_unref_libinput: - libinput_unref(libinput); - goto fail_free_input; +fail_unref_libinput: + libinput_unref(libinput); + goto fail_free_input; - fail_unref_udev: - udev_unref(udev); +fail_unref_udev: + udev_unref(udev); - fail_free_input: - free(input); +fail_free_input: + free(input); - fail_return_null: - return NULL; +fail_return_null: + return NULL; } +static int on_device_removed(struct user_input *input, struct libinput_event *event, uint64_t timestamp, bool emit_flutter_events); + void user_input_destroy(struct user_input *input) { - DEBUG_ASSERT(input != NULL); + enum libinput_event_type event_type; + struct libinput_event *event; + ASSERTED int ok; + + assert(input != NULL); /// TODO: Destroy all the input device data, maybe add an additional /// parameter to indicate whether any flutter device removal events should be /// emitted. + libinput_suspend(input->libinput); + libinput_dispatch(input->libinput); + + // handle all device removal events + while (libinput_next_event_type(input->libinput) != LIBINPUT_EVENT_NONE) { + event = libinput_get_event(input->libinput); + event_type = libinput_event_get_type(event); + + switch (event_type) { + case LIBINPUT_EVENT_DEVICE_REMOVED: + ok = on_device_removed(input, event, 0, false); + ASSERT_ZERO(ok); + break; + default: break; + } + + libinput_event_destroy(event); + } + if (input->kbdcfg != NULL) { keyboard_config_destroy(input->kbdcfg); } libinput_unref(input->libinput); - free(input); + free(input); } void user_input_set_transform( struct user_input *input, - const FlutterTransformation *display_to_view_transform, - const FlutterTransformation *view_to_display_transform, + const struct mat3f *display_to_view_transform, + const struct mat3f *view_to_display_transform, unsigned int display_width, unsigned int display_height ) { - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(display_to_view_transform != NULL); - DEBUG_ASSERT(view_to_display_transform != NULL); + assert(input != NULL); + assert(display_to_view_transform != NULL); + assert(view_to_display_transform != NULL); input->display_to_view_transform = *display_to_view_transform; input->view_to_display_transform_nontranslating = *view_to_display_transform; @@ -227,13 +250,30 @@ void user_input_set_transform( } int user_input_get_fd(struct user_input *input) { - DEBUG_ASSERT(input != NULL); + assert(input != NULL); return libinput_get_fd(input->libinput); } +void user_input_suspend(struct user_input *input) { + ASSERT_NOT_NULL(input); + libinput_suspend(input->libinput); +} + +int user_input_resume(struct user_input *input) { + int ok; + + ASSERT_NOT_NULL(input); + ok = libinput_resume(input->libinput); + if (ok < 0) { + LOG_ERROR("Couldn't resume libinput event processing. libinput_resume: %s\n", strerror(errno)); + return errno; + } + + return 0; +} static void flush_pointer_events(struct user_input *input) { - DEBUG_ASSERT(input != NULL); + assert(input != NULL); if (input->n_collected_flutter_pointer_events > 0) { input->interface.on_flutter_pointer_event( @@ -241,14 +281,14 @@ static void flush_pointer_events(struct user_input *input) { input->collected_flutter_pointer_events, input->n_collected_flutter_pointer_events ); - + input->n_collected_flutter_pointer_events = 0; } } static void emit_pointer_events(struct user_input *input, const FlutterPointerEvent *events, size_t n_events) { - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(events != NULL); + assert(input != NULL); + assert(events != NULL); size_t to_copy; @@ -259,7 +299,7 @@ static void emit_pointer_events(struct user_input *input, const FlutterPointerEv } // how many pointer events we can copy into the internal pointer event buffer? - to_copy = min(n_events, MAX_COLLECTED_FLUTTER_POINTER_EVENTS - input->n_collected_flutter_pointer_events); + to_copy = MIN2(n_events, MAX_COLLECTED_FLUTTER_POINTER_EVENTS - input->n_collected_flutter_pointer_events); // copy into the internal pointer event buffer memcpy( @@ -273,7 +313,7 @@ static void emit_pointer_events(struct user_input *input, const FlutterPointerEv // advance events so it points to the first remaining unemitted event events += to_copy; - + // advance the number of stored pointer events input->n_collected_flutter_pointer_events += to_copy; } @@ -284,19 +324,16 @@ static void emit_pointer_events(struct user_input *input, const FlutterPointerEv * it it isn't yet enabled. */ static void maybe_enable_mouse_cursor(struct user_input *input, uint64_t timestamp) { - DEBUG_ASSERT(input != NULL); + assert(input != NULL); if (input->n_cursor_devices == 1) { - input->cursor_flutter_device_id = input->next_unused_flutter_device_id++; + if (input->cursor_flutter_device_id == -1) { + input->cursor_flutter_device_id = input->next_unused_flutter_device_id++; + } emit_pointer_events( input, - &FLUTTER_POINTER_MOUSE_ADD_EVENT( - timestamp, - input->cursor_x, input->cursor_y, - input->cursor_flutter_device_id, - 0 - ), + &FLUTTER_POINTER_MOUSE_ADD_EVENT(timestamp, input->cursor_x, input->cursor_y, input->cursor_flutter_device_id, 0), 1 ); } @@ -306,31 +343,25 @@ static void maybe_enable_mouse_cursor(struct user_input *input, uint64_t timesta * @brief Called when input->n_cursor_devices was decreased to maybe disable the mouse cursor. */ static void maybe_disable_mouse_cursor(struct user_input *input, uint64_t timestamp) { - DEBUG_ASSERT(input != NULL); + assert(input != NULL); if (input->n_cursor_devices == 0) { emit_pointer_events( input, - &FLUTTER_POINTER_MOUSE_REMOVE_EVENT( - timestamp, - input->cursor_x, input->cursor_y, - input->cursor_flutter_device_id, - 0 - ), + &FLUTTER_POINTER_MOUSE_REMOVE_EVENT(timestamp, input->cursor_x, input->cursor_y, input->cursor_flutter_device_id, 0), 1 ); } } - static int on_device_added(struct user_input *input, struct libinput_event *event, uint64_t timestamp) { struct input_device_data *data; struct libinput_device *device; int64_t device_id; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); - + assert(input != NULL); + assert(event != NULL); + device = libinput_event_get_device(event); data = malloc(sizeof *data); @@ -354,22 +385,19 @@ static int on_device_added(struct user_input *input, struct libinput_event *even // mouse pointer will be added as soon as the device actually sends a // mouse event, as some devices will erroneously have a LIBINPUT_DEVICE_CAP_POINTER // even though they aren't mice. (My keyboard for example is a mouse smh) - } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + + // reserve one id for the mouse pointer + // input->next_unused_flutter_device_id++; + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { // add all touch slots as individual touch devices to flutter for (int i = 0; i < libinput_device_touch_get_touch_count(device); i++) { device_id = input->next_unused_flutter_device_id++; - emit_pointer_events( - input, - &FLUTTER_POINTER_TOUCH_ADD_EVENT( - timestamp, - 0.0, 0.0, - device_id - ), - 1 - ); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_ADD_EVENT(timestamp, 0.0, 0.0, device_id), 1); } - } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { // create a new keyboard state for this keyboard if (input->kbdcfg) { data->keyboard_state = keyboard_state_new(input->kbdcfg, NULL, NULL); @@ -377,58 +405,59 @@ static int on_device_added(struct user_input *input, struct libinput_event *even // If we don't have a keyboard config data->keyboard_state = NULL; } - } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { device_id = input->next_unused_flutter_device_id++; /// TODO: Use kFlutterPointerDeviceKindStylus here - emit_pointer_events( - input, - &FLUTTER_POINTER_TOUCH_ADD_EVENT( - timestamp, - 0.0, 0.0, - device_id - ), - 1 - ); - } else { - // We don't handle this device, so we don't need the data. - libinput_device_set_user_data(device, NULL); - free(data); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_ADD_EVENT(timestamp, 0.0, 0.0, device_id), 1); } - + return 0; } -static int on_device_removed(struct user_input *input, struct libinput_event *event, uint64_t timestamp) { +static int on_device_removed(struct user_input *input, struct libinput_event *event, uint64_t timestamp, bool emit_flutter_events) { struct input_device_data *data; struct libinput_device *device; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); - + assert(input != NULL); + assert(event != NULL); + device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); + // on_device_removed is special here because it's also invoked + // outside the normal user_input_on_fd_ready function as well, + // in user_input_destroy. + // + // user_input_destroy calls libinput_suspend and then handles all events, + // but only the DEVICE_REMOVED events, so it could be we skip some + // DEVICE_ADDED events as well + // + // so it might be the data is null here because on_device_added + // was never called for this device. + if (data == NULL) { + return 0; + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { - // if we don't have a mouse cursor added to flutter yet, add one if (data->has_emitted_pointer_events) { input->n_cursor_devices--; - maybe_disable_mouse_cursor(input, timestamp); + if (emit_flutter_events) { + maybe_disable_mouse_cursor(input, timestamp); + } } - } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { // add all touch slots as individual touch devices to flutter - for (int i = 0; i < libinput_device_touch_get_touch_count(device); i++) { - emit_pointer_events( - input, - &FLUTTER_POINTER_TOUCH_REMOVE_EVENT( - timestamp, - 0.0, 0.0, - data->flutter_device_id_offset + i - ), - 1 - ); + if (emit_flutter_events) { + for (int i = 0; i < libinput_device_touch_get_touch_count(device); i++) { + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_REMOVE_EVENT(timestamp, 0.0, 0.0, data->flutter_device_id_offset + i), 1); + } } - } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { // create a new keyboard state for this keyboard if (data->keyboard_state != NULL) { keyboard_state_destroy(data->keyboard_state); @@ -438,7 +467,7 @@ static int on_device_removed(struct user_input *input, struct libinput_event *ev if (data != NULL) { free(data); } - + libinput_device_set_user_data(device, NULL); return 0; @@ -454,8 +483,8 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) uint16_t evdev_keycode; int ok; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); key_event = libinput_event_get_keyboard_event(event); device = libinput_event_get_device(event); @@ -464,6 +493,8 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) evdev_keycode = (uint16_t) libinput_event_keyboard_get_key(key_event); key_state = libinput_event_keyboard_get_key_state(key_event); + LOG_DEBUG("on_key_event\n"); + // If we don't have a keyboard state (for example if we couldn't load /etc/default/keyboard) // we just return here. if (data->keyboard_state == NULL) { @@ -472,13 +503,9 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) // Let the keyboard advance its statemachine. // keysym/codepoint are 0 when none were emitted. - ok = keyboard_state_process_key_event( - data->keyboard_state, - evdev_keycode, - (int32_t) key_state, - &keysym, - &codepoint - ); + keysym = 0; + codepoint = 0; + ok = keyboard_state_process_key_event(data->keyboard_state, evdev_keycode, (int32_t) key_state, &keysym, &codepoint); if (ok != 0) { return ok; } @@ -487,22 +514,20 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) /// TODO: Maybe remove the evdev_value parameter? plain_codepoint = keyboard_state_get_plain_codepoint(data->keyboard_state, evdev_keycode, 1); - // call the GTK keyevent callback. - /// TODO: Simplify the meta state construction. - input->interface.on_gtk_keyevent( - input->userdata, - plain_codepoint, - (uint32_t) keysym, - evdev_keycode + (uint32_t) 8, - keyboard_state_is_shift_active(data->keyboard_state) - | (keyboard_state_is_capslock_active(data->keyboard_state) << 1) - | (keyboard_state_is_ctrl_active(data->keyboard_state) << 2) - | (keyboard_state_is_alt_active(data->keyboard_state) << 3) - | (keyboard_state_is_numlock_active(data->keyboard_state) << 4) - | (keyboard_state_is_meta_active(data->keyboard_state) << 28), - key_state + LOG_DEBUG( + "keyboard state ctrl active: %s, alt active: %s, keysym: 0x%04" PRIx32 "\n", + keyboard_state_is_ctrl_active(data->keyboard_state) ? "yes" : "no", + keyboard_state_is_alt_active(data->keyboard_state) ? "yes" : "no", + keysym ); + if (input->interface.on_switch_vt != NULL && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { + // "switch VT" keybind + input->interface.on_switch_vt(input->userdata, keysym - XKB_KEY_XF86Switch_VT_1 + 1); + } + + uint8_t utf8_character[5] = { 0 }; + // Call the UTF8 character callback if we've got a codepoint. // Code very similiar to that of the linux kernel in drivers/tty/vt/keyboard.c, to_utf8 if (codepoint) { @@ -510,48 +535,67 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) // we emit UTF8 unconditionally here, // maybe we should check if codepoint is a control character? if (isprint(codepoint)) { - input->interface.on_utf8_character( - input->userdata, - (uint8_t[1]) {codepoint} - ); + utf8_character[0] = codepoint; } } else if (codepoint < 0x800) { - input->interface.on_utf8_character( - input->userdata, - (uint8_t[2]) { - 0xc0 | (codepoint >> 6), - 0x80 | (codepoint & 0x3f) - } - ); + utf8_character[0] = 0xc0 | (codepoint >> 6); + utf8_character[1] = 0x80 | (codepoint & 0x3f); } else if (codepoint < 0x10000) { // the console keyboard driver of the linux kernel checks // at this point whether `codepoint` is a UTF16 high surrogate (U+D800 to U+DFFF) // or U+FFFF and returns without emitting UTF8 in that case. // don't know whether we should do this here too - input->interface.on_utf8_character( - input->userdata, - (uint8_t[3]) { - 0xe0 | (codepoint >> 12), - 0x80 | ((codepoint >> 6) & 0x3f), - 0x80 | (codepoint & 0x3f) - } - ); + utf8_character[0] = 0xe0 | (codepoint >> 12); + utf8_character[1] = 0x80 | ((codepoint >> 6) & 0x3f); + utf8_character[2] = 0x80 | (codepoint & 0x3f); } else if (codepoint < 0x110000) { - input->interface.on_utf8_character( - input->userdata, - (uint8_t[4]) { - 0xf0 | (codepoint >> 18), - 0x80 | ((codepoint >> 12) & 0x3f), - 0x80 | ((codepoint >> 6) & 0x3f), - 0x80 | (codepoint & 0x3f) - } - ); + utf8_character[0] = 0xf0 | (codepoint >> 18); + utf8_character[1] = 0x80 | ((codepoint >> 12) & 0x3f); + utf8_character[2] = 0x80 | ((codepoint >> 6) & 0x3f); + utf8_character[3] = 0x80 | (codepoint & 0x3f); } } - - // Call the XKB keysym callback if we've got a keysym. - if (keysym) { - input->interface.on_xkb_keysym(input->userdata, keysym); + + if (input->interface.on_key_event) { + input->interface.on_key_event( + input->userdata, + libinput_event_keyboard_get_time_usec(key_event), + evdev_keycode + 8u, + keysym, + plain_codepoint, + (key_modifiers_t){ .shift = keyboard_state_is_shift_active(data->keyboard_state), + .capslock = keyboard_state_is_capslock_active(data->keyboard_state), + .ctrl = keyboard_state_is_ctrl_active(data->keyboard_state), + .alt = keyboard_state_is_alt_active(data->keyboard_state), + .numlock = keyboard_state_is_numlock_active(data->keyboard_state), + .__pad = 0, + .meta = keyboard_state_is_meta_active(data->keyboard_state) }, + (char *) utf8_character, + key_state == LIBINPUT_KEY_STATE_PRESSED, + false + ); + } else { + // call the GTK keyevent callback. + /// TODO: Simplify the meta state construction. + input->interface.on_gtk_keyevent( + input->userdata, + plain_codepoint, + (uint32_t) keysym, + evdev_keycode + (uint32_t) 8, + keyboard_state_is_shift_active(data->keyboard_state) | (keyboard_state_is_capslock_active(data->keyboard_state) << 1) | + (keyboard_state_is_ctrl_active(data->keyboard_state) << 2) | (keyboard_state_is_alt_active(data->keyboard_state) << 3) | + (keyboard_state_is_numlock_active(data->keyboard_state) << 4) | (keyboard_state_is_meta_active(data->keyboard_state) << 28), + key_state + ); + + if (utf8_character[0]) { + input->interface.on_utf8_character(input->userdata, utf8_character); + } + + // Call the XKB keysym callback if we've got a keysym. + if (keysym) { + input->interface.on_xkb_keysym(input->userdata, keysym); + } } return 0; @@ -561,13 +605,12 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct vec2f delta, pos_display, pos_view; uint64_t timestamp; - double new_cursor_x, new_cursor_y; - double dx, dy; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); - + assert(input != NULL); + assert(event != NULL); + pointer_event = libinput_event_get_pointer_event(event); device = libinput_event_get_device(event); data = libinput_device_get_user_data(device); @@ -575,36 +618,31 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event data->timestamp = timestamp; - dx = libinput_event_pointer_get_dx(pointer_event); - dy = libinput_event_pointer_get_dy(pointer_event); - - // transform the deltas. when the display is rotated - // we want the mouse to move in different directions as well. - // the dx and dy is still in display (not view) coordinates though, - // we only changed the movement direction. - apply_flutter_transformation(input->view_to_display_transform_nontranslating, &dx, &dy); + delta = transform_point( + input->view_to_display_transform_nontranslating, + VEC2F(libinput_event_pointer_get_dx(pointer_event), libinput_event_pointer_get_dy(pointer_event)) + ); - new_cursor_x = input->cursor_x + dx; - new_cursor_y = input->cursor_y + dy; + pos_display = VEC2F(input->cursor_x + delta.x, input->cursor_y + delta.y); // check if we're ran over the display boundaries. - if (new_cursor_x < 0.0) { - new_cursor_x = 0.0; - } else if (new_cursor_x > input->display_width - 1) { - new_cursor_x = input->display_width - 1; + if (pos_display.x < 0.0) { + pos_display.x = 0.0; + } else if (pos_display.x > input->display_width - 1) { + pos_display.x = input->display_width - 1; } - if (new_cursor_y < 0.0) { - new_cursor_y = 0.0; - } else if (new_cursor_y > input->display_height - 1) { - new_cursor_y = input->display_height - 1; + if (pos_display.y < 0.0) { + pos_display.y = 0.0; + } else if (pos_display.y > input->display_height - 1) { + pos_display.y = input->display_height - 1; } - input->cursor_x = new_cursor_x; - input->cursor_y = new_cursor_y; + input->cursor_x = pos_display.x; + input->cursor_y = pos_display.y; // transform the cursor pos to view (flutter) coordinates. - apply_flutter_transformation(input->display_to_view_transform, &new_cursor_x, &new_cursor_y); + pos_view = transform_point(input->display_to_view_transform, pos_display); if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; @@ -615,13 +653,7 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event // send the pointer event to flutter. emit_pointer_events( input, - &FLUTTER_POINTER_MOUSE_MOVE_EVENT( - timestamp, - new_cursor_x, - new_cursor_y, - data->flutter_device_id_offset, - data->buttons - ), + &FLUTTER_POINTER_MOUSE_MOVE_EVENT(timestamp, pos_view.x, pos_view.y, input->cursor_flutter_device_id, data->buttons), 1 ); @@ -638,11 +670,11 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct vec2f pos_display, pos_view; uint64_t timestamp; - double x, y; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); pointer_event = libinput_event_get_pointer_event(event); device = libinput_event_get_device(event); @@ -650,20 +682,22 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin timestamp = libinput_event_pointer_get_time_usec(pointer_event); // get the new mouse position in display coordinates - x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, input->display_width - 1); - y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, input->display_height - 1); + pos_display = VEC2F( + libinput_event_pointer_get_absolute_x_transformed(pointer_event, input->display_width), + libinput_event_pointer_get_absolute_y_transformed(pointer_event, input->display_height) + ); /// FIXME: Why do we store the coordinates here? - data->x = x; - data->y = y; + data->x = pos_display.x; + data->y = pos_display.y; data->timestamp = timestamp; // update the "global" cursor position - input->cursor_x = x; - input->cursor_y = y; + input->cursor_x = pos_display.x; + input->cursor_y = pos_display.y; // transform x & y to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, pos_display); if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; @@ -673,12 +707,7 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin emit_pointer_events( input, - &FLUTTER_POINTER_MOUSE_MOVE_EVENT( - timestamp, - x, y, - data->flutter_device_id_offset, - data->buttons - ), + &FLUTTER_POINTER_MOUSE_MOVE_EVENT(timestamp, pos_view.x, pos_view.y, input->cursor_flutter_device_id, data->buttons), 1 ); @@ -691,14 +720,14 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event struct input_device_data *data; struct libinput_device *device; FlutterPointerPhase pointer_phase; + struct vec2f pos_view; uint64_t timestamp; uint16_t evdev_code; int64_t flutter_button; int64_t new_flutter_button_state; - double x, y; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); pointer_event = libinput_event_get_pointer_event(event); device = libinput_event_get_device(event); @@ -751,24 +780,22 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event pointer_phase = kDown; } else { // some button was pressed or released, - // but it + // but it pointer_phase = kMove; } - x = input->cursor_x; - y = input->cursor_y; - // since the stored coords are in display, not view coordinates, // we need to transform them again - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, VEC2F(input->cursor_x, input->cursor_y)); emit_pointer_events( input, &FLUTTER_POINTER_MOUSE_BUTTON_EVENT( pointer_phase, timestamp, - x, y, - data->flutter_device_id_offset, + pos_view.x, + pos_view.y, + input->cursor_flutter_device_id, new_flutter_button_state ), 1 @@ -785,38 +812,36 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * struct libinput_event_pointer *pointer_event; struct input_device_data *data; struct libinput_device *device; + struct vec2f pos_view; uint64_t timestamp; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); pointer_event = libinput_event_get_pointer_event(event); device = libinput_event_get_device(event); data = libinput_device_get_user_data(device); timestamp = libinput_event_pointer_get_time_usec(pointer_event); - double x = input->cursor_x; - double y = input->cursor_y; - // since the stored coords are in display, not view coordinates, // we need to transform them again - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point(input->display_to_view_transform, VEC2F(input->cursor_x, input->cursor_y)); - double scroll_x = libinput_event_pointer_has_axis(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) - ? libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) - : 0.0; + double scroll_x = libinput_event_pointer_has_axis(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) ? + libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) : + 0.0; - double scroll_y = libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) - ? libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) - : 0.0; + double scroll_y = libinput_event_pointer_has_axis(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) ? + libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) : + 0.0; emit_pointer_events( input, &FLUTTER_POINTER_MOUSE_SCROLL_EVENT( timestamp, - x, - y, - data->flutter_device_id_offset, + pos_view.x, + pos_view.y, + input->cursor_flutter_device_id, scroll_x / 15.0 * 53.0, scroll_y / 15.0 * 53.0, data->buttons @@ -830,13 +855,13 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * static int on_touch_down(struct user_input *input, struct libinput_event *event) { struct libinput_event_touch *touch_event; struct input_device_data *data; + struct vec2f pos_view; uint64_t timestamp; int64_t device_id; - double x, y; int slot; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); data = libinput_device_get_user_data(libinput_event_get_device(event)); touch_event = libinput_event_get_touch_event(event); @@ -851,18 +876,21 @@ static int on_touch_down(struct user_input *input, struct libinput_event *event) device_id = data->flutter_device_id_offset + slot; - x = libinput_event_touch_get_x_transformed(touch_event, input->display_width - 1); - y = libinput_event_touch_get_y_transformed(touch_event, input->display_height - 1); - // transform the display coordinates to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point( + input->display_to_view_transform, + VEC2F( + libinput_event_touch_get_x_transformed(touch_event, input->display_width), + libinput_event_touch_get_y_transformed(touch_event, input->display_height) + ) + ); // emit the flutter pointer event - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, pos_view.x, pos_view.y, device_id), 1); // alter our device state - data->x = x; - data->y = y; + data->x = pos_view.x; + data->y = pos_view.y; data->timestamp = timestamp; return 0; @@ -875,8 +903,8 @@ static int on_touch_up(struct user_input *input, struct libinput_event *event) { int64_t device_id; int slot; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); data = libinput_device_get_user_data(libinput_event_get_device(event)); touch_event = libinput_event_get_touch_event(event); @@ -899,13 +927,13 @@ static int on_touch_up(struct user_input *input, struct libinput_event *event) { static int on_touch_motion(struct user_input *input, struct libinput_event *event) { struct libinput_event_touch *touch_event; struct input_device_data *data; + struct vec2f pos_view; uint64_t timestamp; int64_t device_id; - double x, y; int slot; - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); data = libinput_device_get_user_data(libinput_event_get_device(event)); touch_event = libinput_event_get_touch_event(event); @@ -920,41 +948,44 @@ static int on_touch_motion(struct user_input *input, struct libinput_event *even device_id = data->flutter_device_id_offset + slot; - x = libinput_event_touch_get_x_transformed(touch_event, input->display_width - 1); - y = libinput_event_touch_get_y_transformed(touch_event, input->display_height - 1); - // transform the display coordinates to view (flutter) coordinates - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos_view = transform_point( + FLUTTER_TRANSFORM_AS_MAT3F(input->display_to_view_transform), + VEC2F( + libinput_event_touch_get_x_transformed(touch_event, input->display_width), + libinput_event_touch_get_y_transformed(touch_event, input->display_height) + ) + ); // emit the flutter pointer event - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, pos_view.x, pos_view.y, device_id), 1); // alter our device state - data->x = x; - data->y = y; + data->x = pos_view.x; + data->y = pos_view.y; data->timestamp = timestamp; return 0; } static int on_touch_cancel(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); - + assert(input != NULL); + assert(event != NULL); + (void) input; (void) event; - + /// TODO: Implement touch cancel return 0; } static int on_touch_frame(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT(input != NULL); - DEBUG_ASSERT(event != NULL); + assert(input != NULL); + assert(event != NULL); (void) input; (void) event; - + /// TODO: Implement touch frame return 0; } @@ -962,15 +993,15 @@ static int on_touch_frame(struct user_input *input, struct libinput_event *event static int on_tablet_tool_axis(struct user_input *input, struct libinput_event *event) { struct libinput_event_tablet_tool *tablet_event; struct input_device_data *data; + struct vec2f pos; uint64_t timestamp; int64_t device_id; - double x, y; - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); data = libinput_device_get_user_data(libinput_event_get_device(event)); - DEBUG_ASSERT_NOT_NULL(data); + ASSERT_NOT_NULL(data); tablet_event = libinput_event_get_tablet_tool_event(event); timestamp = libinput_event_tablet_tool_get_time_usec(tablet_event); @@ -981,20 +1012,20 @@ static int on_tablet_tool_axis(struct user_input *input, struct libinput_event * /// TODO: Maybe report hover events when it's not in contact? /// FIXME: Use kFlutterPointerDeviceKindStylus here if (data->tip) { - x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); - y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); + pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); + pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); - apply_flutter_transformation(input->display_to_view_transform, &x, &y); - - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, x, y, device_id), 1); + pos = transform_point(input->display_to_view_transform, pos); + + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_MOVE_EVENT(timestamp, pos.x, pos.y, device_id), 1); } return 0; } static int on_tablet_tool_proximity(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1007,39 +1038,39 @@ static int on_tablet_tool_tip(struct user_input *input, struct libinput_event *e struct input_device_data *data; uint64_t timestamp; int64_t device_id; - double x, y; + struct vec2f pos; - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); data = libinput_device_get_user_data(libinput_event_get_device(event)); - DEBUG_ASSERT_NOT_NULL(data); + ASSERT_NOT_NULL(data); tablet_event = libinput_event_get_tablet_tool_event(event); timestamp = libinput_event_tablet_tool_get_time_usec(tablet_event); device_id = data->flutter_device_id_offset; - x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); - y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); + pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); + pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); - apply_flutter_transformation(input->display_to_view_transform, &x, &y); + pos = transform_point(input->display_to_view_transform, pos); /// FIXME: Use kFlutterPointerDeviceKindStylus here if (libinput_event_tablet_tool_get_tip_state(tablet_event) == LIBINPUT_TABLET_TOOL_TIP_DOWN) { data->tip = true; - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_DOWN_EVENT(timestamp, pos.x, pos.y, device_id), 1); } else { data->tip = false; - emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_UP_EVENT(timestamp, x, y, device_id), 1); + emit_pointer_events(input, &FLUTTER_POINTER_TOUCH_UP_EVENT(timestamp, pos.x, pos.y, device_id), 1); } return 0; } static int on_tablet_tool_button(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1048,8 +1079,8 @@ static int on_tablet_tool_button(struct user_input *input, struct libinput_event } static int on_tablet_pad_button(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1058,8 +1089,8 @@ static int on_tablet_pad_button(struct user_input *input, struct libinput_event } static int on_tablet_pad_ring(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1068,8 +1099,8 @@ static int on_tablet_pad_ring(struct user_input *input, struct libinput_event *e } static int on_tablet_pad_strip(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1079,8 +1110,8 @@ static int on_tablet_pad_strip(struct user_input *input, struct libinput_event * #if THIS_LIBINPUT_VER >= LIBINPUT_VER(1, 15, 0) static int on_tablet_pad_key(struct user_input *input, struct libinput_event *event) { - DEBUG_ASSERT_NOT_NULL(input); - DEBUG_ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(event); (void) input; (void) event; @@ -1094,7 +1125,7 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) struct libinput_event *event; int ok; - DEBUG_ASSERT(input != NULL); + assert(input != NULL); while (libinput_next_event_type(input->libinput) != LIBINPUT_EVENT_NONE) { event = libinput_get_event(input->libinput); @@ -1108,7 +1139,7 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) } break; case LIBINPUT_EVENT_DEVICE_REMOVED: - ok = on_device_removed(input, event, timestamp); + ok = on_device_removed(input, event, timestamp, true); if (ok != 0) { goto fail_destroy_event; } @@ -1125,19 +1156,19 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) goto fail_destroy_event; } break; - case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: ok = on_mouse_motion_absolute_event(input, event); if (ok != 0) { goto fail_destroy_event; } break; - case LIBINPUT_EVENT_POINTER_BUTTON: + case LIBINPUT_EVENT_POINTER_BUTTON: ok = on_mouse_button_event(input, event); if (ok != 0) { goto fail_destroy_event; } break; - case LIBINPUT_EVENT_POINTER_AXIS: + case LIBINPUT_EVENT_POINTER_AXIS: ok = on_mouse_axis_event(input, event); if (ok != 0) { goto fail_destroy_event; @@ -1149,19 +1180,19 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) goto fail_destroy_event; } break; - case LIBINPUT_EVENT_TOUCH_UP: + case LIBINPUT_EVENT_TOUCH_UP: ok = on_touch_up(input, event); if (ok != 0) { goto fail_destroy_event; } break; - case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_MOTION: ok = on_touch_motion(input, event); if (ok != 0) { goto fail_destroy_event; } break; - case LIBINPUT_EVENT_TOUCH_CANCEL: + case LIBINPUT_EVENT_TOUCH_CANCEL: ok = on_touch_cancel(input, event); if (ok != 0) { goto fail_destroy_event; @@ -1223,28 +1254,26 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) } break; #endif - default: - break; + default: break; } - + libinput_event_destroy(event); } return 0; - - fail_destroy_event: +fail_destroy_event: libinput_event_destroy(event); return ok; } int user_input_on_fd_ready(struct user_input *input) { - unsigned int cursor_x, cursor_y, cursor_x_before, cursor_y_before; - uint64_t timestamp; + int cursor_x, cursor_y, cursor_x_before, cursor_y_before; + uint64_t timestamp; bool cursor_enabled, cursor_enabled_before; int ok; - DEBUG_ASSERT(input != NULL); + assert(input != NULL); // get a timestamp because some libinput events don't provide one // needs to be in milliseconds, since that's what the other libinput events @@ -1260,8 +1289,8 @@ int user_input_on_fd_ready(struct user_input *input) { // record cursor state before handling events cursor_enabled_before = input->n_cursor_devices > 0; - cursor_x_before = (unsigned int) round(input->cursor_x); - cursor_y_before = (unsigned int) round(input->cursor_y); + cursor_x_before = round(input->cursor_x); + cursor_y_before = round(input->cursor_y); // handle all available libinput events ok = process_libinput_events(input, timestamp); @@ -1272,8 +1301,8 @@ int user_input_on_fd_ready(struct user_input *input) { // record cursor state after handling events cursor_enabled = input->n_cursor_devices > 0; - cursor_x = (unsigned int) round(input->cursor_x); - cursor_y = (unsigned int) round(input->cursor_y); + cursor_x = round(input->cursor_x); + cursor_y = round(input->cursor_y); // make sure we've dispatched all the flutter pointer events flush_pointer_events(input); @@ -1287,7 +1316,7 @@ int user_input_on_fd_ready(struct user_input *input) { // only move the pointer if the cursor is enabled now if (cursor_enabled && ((cursor_x != cursor_x_before) || (cursor_y != cursor_y_before))) { - input->interface.on_move_cursor(input->userdata, cursor_x, cursor_y); + input->interface.on_move_cursor(input->userdata, VEC2F(cursor_x - cursor_x_before, cursor_y - cursor_y_before)); } return 0; diff --git a/src/user_input.h b/src/user_input.h new file mode 100644 index 00000000..ae5b382c --- /dev/null +++ b/src/user_input.h @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: MIT +/* + * User Input + * + * Collects user input from libinput, transforms it (partially into + * more flutter compatible forms), and calls configured handler callbacks. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_USER_INPUT_H +#define _FLUTTERPI_SRC_USER_INPUT_H + +#include +#include + +#include "plugins/raw_keyboard.h" +#include "util/asserts.h" +#include "util/collection.h" +#include "util/geometry.h" + +#define MAX_COLLECTED_FLUTTER_POINTER_EVENTS 64 + +COMPILE_ASSERT(sizeof(FlutterPointerEvent) == 104 || sizeof(FlutterPointerEvent) == 112); + +#define FLUTTER_POINTER_EVENT( \ + _phase, \ + _timestamp, \ + _x, \ + _y, \ + _device, \ + _signal_kind, \ + _scroll_delta_x, \ + _scroll_delta_y, \ + _device_kind, \ + _buttons, \ + _pan_x, \ + _pan_y, \ + _scale, \ + _rotation \ +) \ + (FlutterPointerEvent) { \ + .struct_size = sizeof(FlutterPointerEvent), .phase = (_phase), .timestamp = (_timestamp), .x = (_x), .y = (_y), \ + .device = (_device), .signal_kind = (_signal_kind), .scroll_delta_x = (_scroll_delta_x), .scroll_delta_y = (_scroll_delta_y), \ + .device_kind = (_device_kind), .buttons = (_buttons), .pan_x = (_pan_x), .pan_y = (_pan_y), .scale = (_scale), \ + .rotation = (_rotation) \ + } + +#define FLUTTER_POINTER_TOUCH_ADD_EVENT(_timestamp, _x, _y, _device_id) \ + FLUTTER_POINTER_EVENT( \ + kAdd, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindTouch, \ + 0, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_TOUCH_REMOVE_EVENT(_timestamp, _x, _y, _device_id) \ + FLUTTER_POINTER_EVENT( \ + kRemove, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindTouch, \ + 0, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_TOUCH_MOVE_EVENT(_timestamp, _x, _y, _device_id) \ + FLUTTER_POINTER_EVENT( \ + kMove, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindTouch, \ + 0, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_TOUCH_DOWN_EVENT(_timestamp, _x, _y, _device_id) \ + FLUTTER_POINTER_EVENT( \ + kDown, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindTouch, \ + 0, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_TOUCH_UP_EVENT(_timestamp, _x, _y, _device_id) \ + FLUTTER_POINTER_EVENT( \ + kUp, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindTouch, \ + 0, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_MOUSE_BUTTON_EVENT(_phase, _timestamp, _x, _y, _device_id, _buttons) \ + FLUTTER_POINTER_EVENT( \ + _phase, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_MOUSE_ADD_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ + FLUTTER_POINTER_EVENT( \ + kAdd, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_MOUSE_SCROLL_EVENT(_timestamp, _x, _y, _device_id, _scroll_x, _scroll_y, _buttons) \ + FLUTTER_POINTER_EVENT( \ + ((_buttons) != 0) ? kMove : kHover, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindScroll, \ + _scroll_x, \ + _scroll_y, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_MOUSE_REMOVE_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ + FLUTTER_POINTER_EVENT( \ + kRemove, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_REMOVE_EVENT(_timestamp, _x, _y, _device, _buttons) \ + FLUTTER_POINTER_EVENT( \ + kRemove, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +#define FLUTTER_POINTER_MOUSE_MOVE_EVENT(_timestamp, _x, _y, _device_id, _buttons) \ + FLUTTER_POINTER_EVENT( \ + (_buttons) &kFlutterPointerButtonMousePrimary ? kMove : kHover, \ + _timestamp, \ + _x, \ + _y, \ + _device_id, \ + kFlutterPointerSignalKindNone, \ + 0.0, \ + 0.0, \ + kFlutterPointerDeviceKindMouse, \ + _buttons, \ + 0.0, \ + 0.0, \ + 0.0, \ + 0.0 \ + ) + +typedef void (*flutter_pointer_event_callback_t)(void *userdata, const FlutterPointerEvent *events, size_t n_events); + +typedef void (*utf8_character_callback_t)(void *userdata, uint8_t *character); + +typedef void (*xkb_keysym_callback_t)(void *userdata, xkb_keysym_t keysym); + +// clang-format off +typedef void (*gtk_keyevent_callback_t)( + void *userdata, + uint32_t unicode_scalar_values, + uint32_t key_code, + uint32_t scan_code, + uint32_t modifiers, + bool is_down +); +// clang-format on + +typedef void (*set_cursor_enabled_callback_t)(void *userdata, bool enabled); + +typedef void (*move_cursor_callback_t)(void *userdata, struct vec2f delta); + +// clang-format off +typedef void (*keyevent_callback_t)( + void *userdata, + uint64_t timestamp_us, + xkb_keycode_t xkb_keycode, + xkb_keysym_t xkb_keysym, + uint32_t plain_codepoint, + key_modifiers_t modifiers, + const char *text, + bool is_down, + bool is_repeat +); +// clang-format on + +struct user_input_interface { + flutter_pointer_event_callback_t on_flutter_pointer_event; + utf8_character_callback_t on_utf8_character; + xkb_keysym_callback_t on_xkb_keysym; + gtk_keyevent_callback_t on_gtk_keyevent; + set_cursor_enabled_callback_t on_set_cursor_enabled; + move_cursor_callback_t on_move_cursor; + int (*open)(const char *path, int flags, void *userdata); + void (*close)(int fd, void *userdata); + void (*on_switch_vt)(void *userdata, int vt); + keyevent_callback_t on_key_event; +}; + +struct user_input; + +/** + * @brief Create a new user input instance. Will try to load the default keyboard config from /etc/default/keyboard + * and create a udev-backed libinput instance. + */ +struct user_input *user_input_new( + const struct user_input_interface *interface, + void *userdata, + const struct mat3f *display_to_view_transform, + const struct mat3f *view_to_display_transform, + unsigned int display_width, + unsigned int display_height +); + +/** + * @brief Destroy this user input instance and free all allocated memory. This will not remove any input devices + * added to flutter and won't invoke any callbacks in the user input interface at all. + */ +void user_input_destroy(struct user_input *input); + +/** + * @brief Set a 3x3 matrix and display width / height so user_input can transform any device coordinates into + * proper flutter view coordinates. (For example to account for a rotated display) + * Will also transform absolute & relative mouse movements. + * + * @param display_to_view_transform will be copied internally. + */ +void user_input_set_transform( + struct user_input *input, + const struct mat3f *display_to_view_transform, + const struct mat3f *view_to_display_transform, + unsigned int display_width, + unsigned int display_height +); + +/** + * @brief Returns a filedescriptor used for input event notification. The returned + * filedescriptor should be listened to with EPOLLIN | EPOLLRDHUP | EPOLLPRI or equivalent. + * When the fd becomes ready, @ref user_input_on_fd_ready should be called not long after it + * became ready. (libinput somehow relies on that) + */ +int user_input_get_fd(struct user_input *input); + +/** + * @brief Should be called when the fd returned by @ref user_input_get_fd becomes ready. + * The user_input_interface callbacks will be called inside this function. + */ +int user_input_on_fd_ready(struct user_input *input); + +void user_input_suspend(struct user_input *input); + +int user_input_resume(struct user_input *input); + +#endif // _FLUTTERPI_SRC_USER_INPUT_H diff --git a/src/util/asserts.h b/src/util/asserts.h new file mode 100644 index 00000000..b2926833 --- /dev/null +++ b/src/util/asserts.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +/* + * Common assert macros + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_ASSERTS_H +#define _FLUTTERPI_SRC_UTIL_ASSERTS_H + +#include + +#define ASSERT assert +#define ASSERT_MSG(__cond, __msg) assert((__cond) && (__msg)) +#define ASSERT_NOT_NULL(__var) assert(__var != NULL) +#define ASSERT_NOT_NULL_MSG(__var, __msg) ASSERT_MSG(__var != NULL, __msg) +#define ASSERT_EQUALS(__a, __b) assert((__a) == (__b)) +#define ASSERT_EQUALS_MSG(__a, __b, __msg) ASSERT_MSG((__a) == (__b), __msg) +#define ASSERT_EGL_TRUE(__var) assert((__var) == EGL_TRUE) +#define ASSERT_EGL_TRUE_MSG(__var, __msg) ASSERT_MSG((__var) == EGL_TRUE, __msg) +#define ASSERT_MUTEX_LOCKED(__mutex) \ + assert(({ \ + bool result; \ + int r = pthread_mutex_trylock(&(__mutex)); \ + if (r == 0) { \ + pthread_mutex_unlock(&(__mutex)); \ + result = false; \ + } else { \ + result = true; \ + } \ + result; \ + })) +#define ASSERT_ZERO(__var) assert((__var) == 0) +#define ASSERT_ZERO_MSG(__var, __msg) ASSERT_MSG((__var) == 0, __msg) + +#define ASSERT_IMPLIES(__cond1, __cond2) assert(!(__cond1) || (__cond2)) +#define ASSERT_IMPLIES_MSG(__cond1, __cond2, __msg) ASSERT_MSG(!(__cond1) || (__cond2), __msg) + +#if !(201112L <= __STDC_VERSION__ || (!defined __STRICT_ANSI__ && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR >= 6)))) + #error "Needs C11 or later or GCC (not in pedantic mode) 4.6.0 or later for compile time asserts." +#endif + +#define COMPILE_ASSERT_MSG(expression, msg) _Static_assert(expression, msg) +#define COMPILE_ASSERT(expression) COMPILE_ASSERT_MSG(expression, "Expression evaluates to false") + +#endif // _FLUTTERPI_SRC_UTIL_ASSERTS_H diff --git a/src/util/bitscan.c b/src/util/bitscan.c new file mode 100644 index 00000000..df0e045b --- /dev/null +++ b/src/util/bitscan.c @@ -0,0 +1,75 @@ +/************************************************************************** + * + * Copyright 2008 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ + +#include "bitscan.h" + +#ifdef HAVE___BUILTIN_FFS +#elif defined(_MSC_VER) && (_M_IX86 || _M_ARM || _M_AMD64 || _M_IA64) +#else +int ffs(int i) { + int bit = 0; + if (!i) + return bit; + if (!(i & 0xffff)) { + bit += 16; + i >>= 16; + } + if (!(i & 0xff)) { + bit += 8; + i >>= 8; + } + if (!(i & 0xf)) { + bit += 4; + i >>= 4; + } + if (!(i & 0x3)) { + bit += 2; + i >>= 2; + } + if (!(i & 0x1)) + bit += 1; + return bit + 1; +} +#endif + +#ifdef HAVE___BUILTIN_FFSLL +#elif defined(_MSC_VER) && (_M_AMD64 || _M_ARM64 || _M_IA64) +#else +int ffsll(long long int val) { + int bit; + + bit = ffs((unsigned) (val & 0xffffffff)); + if (bit != 0) + return bit; + + bit = ffs((unsigned) (val >> 32)); + if (bit != 0) + return 32 + bit; + + return 0; +} +#endif diff --git a/src/util/bitscan.h b/src/util/bitscan.h new file mode 100644 index 00000000..7fa47180 --- /dev/null +++ b/src/util/bitscan.h @@ -0,0 +1,347 @@ +/************************************************************************** + * + * Copyright 2008 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ + +#ifndef BITSCAN_H +#define BITSCAN_H + +#include +#include +#include +#include + +#if defined(_MSC_VER) + #include +#endif + +#if defined(__POPCNT__) + #include +#endif + +// for HAVE___BUILTIN_... +#include "macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Find first bit set in word. Least significant bit is 1. + * Return 0 if no bits set. + */ +#ifdef HAVE___BUILTIN_FFS + #define ffs __builtin_ffs +#elif defined(_MSC_VER) && (_M_IX86 || _M_ARM || _M_AMD64 || _M_IA64) +static inline int ffs(int i) { + unsigned long index; + if (_BitScanForward(&index, i)) + return index + 1; + else + return 0; +} +#else +extern int ffs(int i); +#endif + +#ifdef HAVE___BUILTIN_FFSLL + #define ffsll __builtin_ffsll +#elif defined(_MSC_VER) && (_M_AMD64 || _M_ARM64 || _M_IA64) +static inline int ffsll(long long int i) { + unsigned long index; + if (_BitScanForward64(&index, i)) + return index + 1; + else + return 0; +} +#else +extern int ffsll(long long int val); +#endif + +/* Destructively loop over all of the bits in a mask as in: + * + * while (mymask) { + * int i = u_bit_scan(&mymask); + * ... process element i + * } + * + */ +static inline int u_bit_scan(unsigned *mask) { + const int i = ffs(*mask) - 1; + *mask ^= (1u << i); + return i; +} + +#define u_foreach_bit(b, dword) for (uint32_t __dword = (dword), b; ((b) = ffs(__dword) - 1, __dword); __dword &= ~(1 << (b))) + +static inline int u_bit_scan64(uint64_t *mask) { + const int i = ffsll(*mask) - 1; + *mask ^= (((uint64_t) 1) << i); + return i; +} + +#define u_foreach_bit64(b, dword) for (uint64_t __dword = (dword), b; ((b) = ffsll(__dword) - 1, __dword); __dword &= ~(1ull << (b))) + +/* Determine if an unsigned value is a power of two. + * + * \note + * Zero is treated as a power of two. + */ +static inline bool util_is_power_of_two_or_zero(unsigned v) { + return (v & (v - 1)) == 0; +} + +/* Determine if an uint64_t value is a power of two. + * + * \note + * Zero is treated as a power of two. + */ +static inline bool util_is_power_of_two_or_zero64(uint64_t v) { + return (v & (v - 1)) == 0; +} + +/* Determine if an unsigned value is a power of two. + * + * \note + * Zero is \b not treated as a power of two. + */ +static inline bool util_is_power_of_two_nonzero(unsigned v) { + /* __POPCNT__ is different from HAVE___BUILTIN_POPCOUNT. The latter + * indicates the existence of the __builtin_popcount function. The former + * indicates that _mm_popcnt_u32 exists and is a native instruction. + * + * The other alternative is to use SSE 4.2 compile-time flags. This has + * two drawbacks. First, there is currently no build infrastructure for + * SSE 4.2 (only 4.1), so that would have to be added. Second, some AMD + * CPUs support POPCNT but not SSE 4.2 (e.g., Barcelona). + */ +#ifdef __POPCNT__ + return _mm_popcnt_u32(v) == 1; +#else + return v != 0 && (v & (v - 1)) == 0; +#endif +} + +/* For looping over a bitmask when you want to loop over consecutive bits + * manually, for example: + * + * while (mask) { + * int start, count, i; + * + * u_bit_scan_consecutive_range(&mask, &start, &count); + * + * for (i = 0; i < count; i++) + * ... process element (start+i) + * } + */ +static inline void u_bit_scan_consecutive_range(unsigned *mask, int *start, int *count) { + if (*mask == 0xffffffff) { + *start = 0; + *count = 32; + *mask = 0; + return; + } + *start = ffs(*mask) - 1; + *count = ffs(~(*mask >> *start)) - 1; + *mask &= ~(((1u << *count) - 1) << *start); +} + +static inline void u_bit_scan_consecutive_range64(uint64_t *mask, int *start, int *count) { + if (*mask == ~0ull) { + *start = 0; + *count = 64; + *mask = 0; + return; + } + *start = ffsll(*mask) - 1; + *count = ffsll(~(*mask >> *start)) - 1; + *mask &= ~(((((uint64_t) 1) << *count) - 1) << *start); +} + +/** + * Find last bit set in a word. The least significant bit is 1. + * Return 0 if no bits are set. + * Essentially ffs() in the reverse direction. + */ +static inline unsigned util_last_bit(unsigned u) { +#if defined(HAVE___BUILTIN_CLZ) + return u == 0 ? 0 : 32 - __builtin_clz(u); +#elif defined(_MSC_VER) && (_M_IX86 || _M_ARM || _M_AMD64 || _M_IA64) + unsigned long index; + if (_BitScanReverse(&index, u)) + return index + 1; + else + return 0; +#else + unsigned r = 0; + while (u) { + r++; + u >>= 1; + } + return r; +#endif +} + +/** + * Find last bit set in a word. The least significant bit is 1. + * Return 0 if no bits are set. + * Essentially ffsll() in the reverse direction. + */ +static inline unsigned util_last_bit64(uint64_t u) { +#if defined(HAVE___BUILTIN_CLZLL) + return u == 0 ? 0 : 64 - __builtin_clzll(u); +#elif defined(_MSC_VER) && (_M_AMD64 || _M_ARM64 || _M_IA64) + unsigned long index; + if (_BitScanReverse64(&index, u)) + return index + 1; + else + return 0; +#else + unsigned r = 0; + while (u) { + r++; + u >>= 1; + } + return r; +#endif +} + +/** + * Find last bit in a word that does not match the sign bit. The least + * significant bit is 1. + * Return 0 if no bits are set. + */ +static inline unsigned util_last_bit_signed(int i) { + if (i >= 0) + return util_last_bit(i); + else + return util_last_bit(~(unsigned) i); +} + +/* Returns a bitfield in which the first count bits starting at start are + * set. + */ +static inline unsigned u_bit_consecutive(unsigned start, unsigned count) { + assert(start + count <= 32); + if (count == 32) + return ~0; + return ((1u << count) - 1) << start; +} + +static inline uint64_t u_bit_consecutive64(unsigned start, unsigned count) { + assert(start + count <= 64); + if (count == 64) + return ~(uint64_t) 0; + return (((uint64_t) 1 << count) - 1) << start; +} + +/** + * Return number of bits set in n. + */ +static inline unsigned util_bitcount(unsigned n) { +#if defined(HAVE___BUILTIN_POPCOUNT) + return __builtin_popcount(n); +#else + /* K&R classic bitcount. + * + * For each iteration, clear the LSB from the bitfield. + * Requires only one iteration per set bit, instead of + * one iteration per bit less than highest set bit. + */ + unsigned bits; + for (bits = 0; n; bits++) { + n &= n - 1; + } + return bits; +#endif +} + +/** + * Return the number of bits set in n using the native popcnt instruction. + * The caller is responsible for ensuring that popcnt is supported by the CPU. + * + * gcc doesn't use it if -mpopcnt or -march= that has popcnt is missing. + * + */ +static inline unsigned util_popcnt_inline_asm(unsigned n) { +#if defined(USE_X86_64_ASM) || defined(USE_X86_ASM) + uint32_t out; + __asm volatile("popcnt %1, %0" : "=r"(out) : "r"(n)); + return out; +#else + /* We should never get here by accident, but I'm sure it'll happen. */ + return util_bitcount(n); +#endif +} + +static inline unsigned util_bitcount64(uint64_t n) { +#ifdef HAVE___BUILTIN_POPCOUNTLL + return __builtin_popcountll(n); +#else + return util_bitcount(n) + util_bitcount(n >> 32); +#endif +} + +/** + * Widens the given bit mask by a multiplier, meaning that it will + * replicate each bit by that amount. + * + * For example: + * 0b101 widened by 2 will become: 0b110011 + * + * This is typically used in shader I/O to transform a 64-bit + * writemask to a 32-bit writemask. + */ +static inline uint32_t util_widen_mask(uint32_t mask, unsigned multiplier) { + uint32_t new_mask = 0; + u_foreach_bit(i, mask) new_mask |= ((1u << multiplier) - 1u) << (i * multiplier); + return new_mask; +} + +#ifdef __cplusplus +} + +/* util_bitcount has large measurable overhead (~2%), so it's recommended to + * use the POPCNT instruction via inline assembly if the CPU supports it. + */ +enum util_popcnt { + POPCNT_NO, + POPCNT_YES, +}; + +/* Convenient function to select popcnt through a C++ template argument. + * This should be used as part of larger functions that are optimized + * as a whole. + */ +template inline unsigned util_bitcount_fast(unsigned n) { + if (POPCNT == POPCNT_YES) + return util_popcnt_inline_asm(n); + else + return util_bitcount(n); +} + +#endif /* __cplusplus */ + +#endif /* BITSCAN_H */ diff --git a/src/util/bitset.h b/src/util/bitset.h new file mode 100644 index 00000000..d6cfe6ce --- /dev/null +++ b/src/util/bitset.h @@ -0,0 +1,470 @@ +/* + * Mesa 3-D graphics library + * + * Copyright (C) 2006 Brian Paul All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * \file bitset.h + * \brief Bitset of arbitrary size definitions. + * \author Michal Krol + */ + +#ifndef BITSET_H +#define BITSET_H + +#include "bitscan.h" +#include "macros.h" + +/**************************************************************************** + * generic bitset implementation + */ + +#define BITSET_WORD unsigned int +#define BITSET_WORDBITS (sizeof(BITSET_WORD) * 8) + +/* bitset declarations + */ +#define BITSET_WORDS(bits) (((bits) + BITSET_WORDBITS - 1) / BITSET_WORDBITS) +#define BITSET_DECLARE(name, bits) BITSET_WORD name[BITSET_WORDS(bits)] + +/* bitset operations + */ +#define BITSET_COPY(x, y) memcpy((x), (y), sizeof(x)) +#define BITSET_EQUAL(x, y) (memcmp((x), (y), sizeof(x)) == 0) +#define BITSET_ZERO(x) memset((x), 0, sizeof(x)) +#define BITSET_ONES(x) memset((x), 0xff, sizeof(x)) +#define BITSET_SIZE(x) (8 * sizeof(x)) // bitset size in bits + +#define BITSET_BITWORD(b) ((b) / BITSET_WORDBITS) +#define BITSET_BIT(b) (1u << ((b) % BITSET_WORDBITS)) + +/* single bit operations + */ +#define BITSET_TEST(x, b) (((x)[BITSET_BITWORD(b)] & BITSET_BIT(b)) != 0) +#define BITSET_SET(x, b) ((x)[BITSET_BITWORD(b)] |= BITSET_BIT(b)) +#define BITSET_CLEAR(x, b) ((x)[BITSET_BITWORD(b)] &= ~BITSET_BIT(b)) + +#define BITSET_MASK(b) (((b) % BITSET_WORDBITS == 0) ? ~0 : BITSET_BIT(b) - 1) +#define BITSET_RANGE(b, e) ((BITSET_MASK((e) + 1)) & ~(BITSET_BIT(b) - 1)) + +/* logic bit operations + */ +static inline void __bitset_and(BITSET_WORD *r, const BITSET_WORD *x, const BITSET_WORD *y, unsigned n) { + for (unsigned i = 0; i < n; i++) + r[i] = x[i] & y[i]; +} + +static inline void __bitset_or(BITSET_WORD *r, const BITSET_WORD *x, const BITSET_WORD *y, unsigned n) { + for (unsigned i = 0; i < n; i++) + r[i] = x[i] | y[i]; +} + +static inline void __bitset_not(BITSET_WORD *x, unsigned n) { + for (unsigned i = 0; i < n; i++) + x[i] = ~x[i]; +} + +#define BITSET_AND(r, x, y) \ + do { \ + assert(ARRAY_SIZE(r) == ARRAY_SIZE(x)); \ + assert(ARRAY_SIZE(r) == ARRAY_SIZE(y)); \ + __bitset_and(r, x, y, ARRAY_SIZE(r)); \ + } while (0) + +#define BITSET_OR(r, x, y) \ + do { \ + assert(ARRAY_SIZE(r) == ARRAY_SIZE(x)); \ + assert(ARRAY_SIZE(r) == ARRAY_SIZE(y)); \ + __bitset_or(r, x, y, ARRAY_SIZE(r)); \ + } while (0) + +#define BITSET_NOT(x) __bitset_not(x, ARRAY_SIZE(x)) + +static inline void __bitset_rotate_right(BITSET_WORD *x, unsigned amount, unsigned n) { + assert(amount < BITSET_WORDBITS); + + if (amount == 0) + return; + + for (unsigned i = 0; i < n - 1; i++) { + x[i] = (x[i] >> amount) | (x[i + 1] << (BITSET_WORDBITS - amount)); + } + + x[n - 1] = x[n - 1] >> amount; +} + +static inline void __bitset_rotate_left(BITSET_WORD *x, unsigned amount, unsigned n) { + assert(amount < BITSET_WORDBITS); + + if (amount == 0) + return; + + for (int i = n - 1; i > 0; i--) { + x[i] = (x[i] << amount) | (x[i - 1] >> (BITSET_WORDBITS - amount)); + } + + x[0] = x[0] << amount; +} + +static inline void __bitset_shr(BITSET_WORD *x, unsigned amount, unsigned n) { + const unsigned int words = amount / BITSET_WORDBITS; + + if (amount == 0) + return; + + if (words) { + unsigned i; + + for (i = 0; i < n - words; i++) + x[i] = x[i + words]; + + while (i < n) + x[i++] = 0; + + amount %= BITSET_WORDBITS; + } + + __bitset_rotate_right(x, amount, n); +} + +static inline void __bitset_shl(BITSET_WORD *x, unsigned amount, unsigned n) { + const int words = amount / BITSET_WORDBITS; + + if (amount == 0) + return; + + if (words) { + int i; + + for (i = n - 1; i >= words; i--) { + x[i] = x[i - words]; + } + + while (i >= 0) { + x[i--] = 0; + } + + amount %= BITSET_WORDBITS; + } + + __bitset_rotate_left(x, amount, n); +} + +#define BITSET_SHR(x, n) __bitset_shr(x, n, ARRAY_SIZE(x)); + +#define BITSET_SHL(x, n) __bitset_shl(x, n, ARRAY_SIZE(x)); + +/* bit range operations + */ +#define BITSET_TEST_RANGE_INSIDE_WORD(x, b, e) \ + (BITSET_BITWORD(b) == BITSET_BITWORD(e) ? (((x)[BITSET_BITWORD(b)] & BITSET_RANGE(b, e)) != 0) : \ + (assert(!"BITSET_TEST_RANGE: bit range crosses word boundary"), 0)) +#define BITSET_SET_RANGE_INSIDE_WORD(x, b, e) \ + (BITSET_BITWORD(b) == BITSET_BITWORD(e) ? ((x)[BITSET_BITWORD(b)] |= BITSET_RANGE(b, e)) : \ + (assert(!"BITSET_SET_RANGE_INSIDE_WORD: bit range crosses word boundary"), 0)) +#define BITSET_CLEAR_RANGE_INSIDE_WORD(x, b, e) \ + (BITSET_BITWORD(b) == BITSET_BITWORD(e) ? ((x)[BITSET_BITWORD(b)] &= ~BITSET_RANGE(b, e)) : \ + (assert(!"BITSET_CLEAR_RANGE: bit range crosses word boundary"), 0)) + +static inline bool __bitset_test_range(const BITSET_WORD *r, unsigned start, unsigned end) { + const unsigned size = end - start + 1; + const unsigned start_mod = start % BITSET_WORDBITS; + + if (start_mod + size <= BITSET_WORDBITS) { + return BITSET_TEST_RANGE_INSIDE_WORD(r, start, end); + } else { + const unsigned first_size = BITSET_WORDBITS - start_mod; + + return __bitset_test_range(r, start, start + first_size - 1) || __bitset_test_range(r, start + first_size, end); + } +} + +#define BITSET_TEST_RANGE(x, b, e) __bitset_test_range(x, b, e) + +static inline void __bitset_set_range(BITSET_WORD *r, unsigned start, unsigned end) { + const unsigned size = end - start + 1; + const unsigned start_mod = start % BITSET_WORDBITS; + + if (start_mod + size <= BITSET_WORDBITS) { + BITSET_SET_RANGE_INSIDE_WORD(r, start, end); + } else { + const unsigned first_size = BITSET_WORDBITS - start_mod; + + __bitset_set_range(r, start, start + first_size - 1); + __bitset_set_range(r, start + first_size, end); + } +} + +#define BITSET_SET_RANGE(x, b, e) __bitset_set_range(x, b, e) + +static inline void __bitclear_clear_range(BITSET_WORD *r, unsigned start, unsigned end) { + const unsigned size = end - start + 1; + const unsigned start_mod = start % BITSET_WORDBITS; + + if (start_mod + size <= BITSET_WORDBITS) { + BITSET_CLEAR_RANGE_INSIDE_WORD(r, start, end); + } else { + const unsigned first_size = BITSET_WORDBITS - start_mod; + + __bitclear_clear_range(r, start, start + first_size - 1); + __bitclear_clear_range(r, start + first_size, end); + } +} + +#define BITSET_CLEAR_RANGE(x, b, e) __bitclear_clear_range(x, b, e) + +static inline unsigned __bitset_prefix_sum(const BITSET_WORD *x, unsigned b, unsigned n) { + unsigned prefix = 0; + + for (unsigned i = 0; i < n; i++) { + if ((i + 1) * BITSET_WORDBITS <= b) { + prefix += util_bitcount(x[i]); + } else { + prefix += util_bitcount(x[i] & BITFIELD_MASK(b - i * BITSET_WORDBITS)); + break; + } + } + return prefix; +} + +/* Count set bits in the bitset (compute the size/cardinality of the bitset). + * This is a special case of prefix sum, but this convenience method is more + * natural when applicable. + */ + +static inline unsigned __bitset_count(const BITSET_WORD *x, unsigned n) { + return __bitset_prefix_sum(x, ~0, n); +} + +#define BITSET_PREFIX_SUM(x, b) __bitset_prefix_sum(x, b, ARRAY_SIZE(x)) + +#define BITSET_COUNT(x) __bitset_count(x, ARRAY_SIZE(x)) + +/* Get first bit set in a bitset. + */ +static inline int __bitset_ffs(const BITSET_WORD *x, int n) { + for (int i = 0; i < n; i++) { + if (x[i]) + return ffs(x[i]) + BITSET_WORDBITS * i; + } + + return 0; +} + +/* Get the last bit set in a bitset. + */ +static inline int __bitset_last_bit(const BITSET_WORD *x, int n) { + for (int i = n - 1; i >= 0; i--) { + if (x[i]) + return util_last_bit(x[i]) + BITSET_WORDBITS * i; + } + + return 0; +} + +#define BITSET_FFS(x) __bitset_ffs(x, ARRAY_SIZE(x)) +#define BITSET_LAST_BIT(x) __bitset_last_bit(x, ARRAY_SIZE(x)) +#define BITSET_LAST_BIT_SIZED(x, size) __bitset_last_bit(x, size) + +static inline unsigned __bitset_next_set(unsigned i, BITSET_WORD *tmp, const BITSET_WORD *set, unsigned size) { + unsigned bit, word; + + /* NOTE: The initial conditions for this function are very specific. At + * the start of the loop, the tmp variable must be set to *set and the + * initial i value set to 0. This way, if there is a bit set in the first + * word, we ignore the i-value and just grab that bit (so 0 is ok, even + * though 0 may be returned). If the first word is 0, then the value of + * `word` will be 0 and we will go on to look at the second word. + */ + word = BITSET_BITWORD(i); + while (*tmp == 0) { + word++; + + if (word >= BITSET_WORDS(size)) + return size; + + *tmp = set[word]; + } + + /* Find the next set bit in the non-zero word */ + bit = ffs(*tmp) - 1; + + /* Unset the bit */ + *tmp &= ~(1ull << bit); + + return word * BITSET_WORDBITS + bit; +} + +/** + * Iterates over each set bit in a set + * + * @param __i iteration variable, bit number + * @param __set the bitset to iterate (will not be modified) + * @param __size number of bits in the set to consider + */ +#define BITSET_FOREACH_SET(__i, __set, __size) \ + for (BITSET_WORD __tmp = (__size) == 0 ? 0 : *(__set), *__foo = &__tmp; __foo != NULL; __foo = NULL) \ + for (__i = 0; (__i = __bitset_next_set(__i, &__tmp, __set, __size)) < __size;) + +static inline void __bitset_next_range(unsigned *start, unsigned *end, const BITSET_WORD *set, unsigned size) { + /* To find the next start, start searching from end. In the first iteration + * it will be at 0, in every subsequent iteration it will be at the first + * 0-bit after the range. + */ + unsigned word = BITSET_BITWORD(*end); + if (word >= BITSET_WORDS(size)) { + *start = *end = size; + return; + } + BITSET_WORD tmp = set[word] & ~(BITSET_BIT(*end) - 1); + while (!tmp) { + word++; + if (word >= BITSET_WORDS(size)) { + *start = *end = size; + return; + } + tmp = set[word]; + } + + *start = word * BITSET_WORDBITS + ffs(tmp) - 1; + + /* Now do the opposite to find end. Here we can start at start + 1, because + * we know that the bit at start is 1 and we're searching for the first + * 0-bit. + */ + word = BITSET_BITWORD(*start + 1); + if (word >= BITSET_WORDS(size)) { + *end = size; + return; + } + tmp = set[word] | (BITSET_BIT(*start + 1) - 1); + while (~tmp == 0) { + word++; + if (word >= BITSET_WORDS(size)) { + *end = size; + return; + } + tmp = set[word]; + } + + /* Cap "end" at "size" in case there are extra bits past "size" set in the + * word. This is only necessary for "end" because we terminate the loop if + * "start" goes past "size". + */ + *end = MIN2(word * BITSET_WORDBITS + ffs(~tmp) - 1, size); +} + +/** + * Iterates over each contiguous range of set bits in a set + * + * @param __start the first 1 bit of the current range + * @param __end the bit after the last 1 bit of the current range + * @param __set the bitset to iterate (will not be modified) + * @param __size number of bits in the set to consider + */ +#define BITSET_FOREACH_RANGE(__start, __end, __set, __size) \ + for (__start = 0, __end = 0, __bitset_next_range(&__start, &__end, __set, __size); __start < __size; \ + __bitset_next_range(&__start, &__end, __set, __size)) + +#ifdef __cplusplus + + /** + * Simple C++ wrapper of a bitset type of static size, with value semantics + * and basic bitwise arithmetic operators. The operators defined below are + * expected to have the same semantics as the same operator applied to other + * fundamental integer types. T is the name of the struct to instantiate + * it as, and N is the number of bits in the bitset. + */ + #define DECLARE_BITSET_T(T, N) \ + struct T { \ + explicit operator bool() const { \ + for (unsigned i = 0; i < BITSET_WORDS(N); i++) \ + if (words[i]) \ + return true; \ + return false; \ + } \ + \ + T &operator=(int x) { \ + const T c = { { (BITSET_WORD) x } }; \ + return *this = c; \ + } \ + \ + friend bool operator==(const T &b, const T &c) { return BITSET_EQUAL(b.words, c.words); } \ + \ + friend bool operator!=(const T &b, const T &c) { return !(b == c); } \ + \ + friend bool operator==(const T &b, int x) { \ + const T c = { { (BITSET_WORD) x } }; \ + return b == c; \ + } \ + \ + friend bool operator!=(const T &b, int x) { return !(b == x); } \ + \ + friend T operator~(const T &b) { \ + T c; \ + for (unsigned i = 0; i < BITSET_WORDS(N); i++) \ + c.words[i] = ~b.words[i]; \ + return c; \ + } \ + \ + T &operator|=(const T &b) { \ + for (unsigned i = 0; i < BITSET_WORDS(N); i++) \ + words[i] |= b.words[i]; \ + return *this; \ + } \ + \ + friend T operator|(const T &b, const T &c) { \ + T d = b; \ + d |= c; \ + return d; \ + } \ + \ + T &operator&=(const T &b) { \ + for (unsigned i = 0; i < BITSET_WORDS(N); i++) \ + words[i] &= b.words[i]; \ + return *this; \ + } \ + \ + friend T operator&(const T &b, const T &c) { \ + T d = b; \ + d &= c; \ + return d; \ + } \ + \ + bool test(unsigned i) const { return BITSET_TEST(words, i); } \ + \ + T &set(unsigned i) { \ + BITSET_SET(words, i); \ + return *this; \ + } \ + \ + T &clear(unsigned i) { \ + BITSET_CLEAR(words, i); \ + return *this; \ + } \ + \ + BITSET_WORD words[BITSET_WORDS(N)]; \ + } + +#endif + +#endif diff --git a/src/util/collection.c b/src/util/collection.c new file mode 100644 index 00000000..323f4fb2 --- /dev/null +++ b/src/util/collection.c @@ -0,0 +1,18 @@ +#include "util/collection.h" + +static pthread_mutexattr_t default_mutex_attrs; + +static void init_default_mutex_attrs() { + pthread_mutexattr_init(&default_mutex_attrs); +#ifdef DEBUG + pthread_mutexattr_settype(&default_mutex_attrs, PTHREAD_MUTEX_ERRORCHECK); +#endif +} + +const pthread_mutexattr_t *get_default_mutex_attrs() { + static pthread_once_t init_once_ctl = PTHREAD_ONCE_INIT; + + pthread_once(&init_once_ctl, init_default_mutex_attrs); + + return &default_mutex_attrs; +} diff --git a/src/util/collection.h b/src/util/collection.h new file mode 100644 index 00000000..bbc48d23 --- /dev/null +++ b/src/util/collection.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +/* + * Collection - common useful functions & macros + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_COLLECTION_H +#define _FLUTTERPI_SRC_UTIL_COLLECTION_H + +#if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500L + #define _XOPEN_SOURCE 500L +#endif + +#include +#include +#include +#include + +#include + +#include "macros.h" + +static inline void *memdup(const void *restrict src, const size_t n) { + void *__restrict__ dest; + + if ((src == NULL) || (n == 0)) + return NULL; + + dest = malloc(n); + if (dest == NULL) + return NULL; + + return memcpy(dest, src, n); +} + +/** + * @brief Get the current time of the system monotonic clock. + * @returns time in nanoseconds. + */ +static inline uint64_t get_monotonic_time(void) { + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time.tv_nsec + time.tv_sec * 1000000000ull; +} + +#define BITCAST(to_type, value) (*((const to_type *) (&(value)))) + +static inline int32_t uint32_to_int32(const uint32_t v) { + return BITCAST(int32_t, v); +} + +static inline uint32_t int32_to_uint32(const int32_t v) { + return BITCAST(uint32_t, v); +} + +static inline uint64_t int64_to_uint64(const int64_t v) { + return BITCAST(uint64_t, v); +} + +static inline int64_t uint64_to_int64(const uint64_t v) { + return BITCAST(int64_t, v); +} + +static inline int64_t ptr_to_int64(const void *const ptr) { + union { + const void *ptr; + int64_t int64; + } u; + + u.int64 = 0; + u.ptr = ptr; + return u.int64; +} + +static inline void *int64_to_ptr(const int64_t v) { + union { + void *ptr; + int64_t int64; + } u; + + u.int64 = v; + return u.ptr; +} + +static inline int64_t ptr_to_uint32(const void *const ptr) { + union { + const void *ptr; + uint32_t u32; + } u; + + u.u32 = 0; + u.ptr = ptr; + return u.u32; +} + +static inline void *uint32_to_ptr(const uint32_t v) { + union { + void *ptr; + uint32_t u32; + } u; + + u.ptr = NULL; + u.u32 = v; + return u.ptr; +} + +#define MAX_ALIGNMENT (__alignof__(max_align_t)) +#define IS_MAX_ALIGNED(num) ((num) % MAX_ALIGNMENT == 0) + +#define DOUBLE_TO_FP1616(v) ((uint32_t) ((v) *65536)) +#define DOUBLE_TO_FP1616_ROUNDED(v) (((uint32_t) (v)) << 16) + +typedef void (*void_callback_t)(void *userdata); + +ATTR_PURE static inline bool streq(const char *a, const char *b) { + return strcmp(a, b) == 0; +} + +const pthread_mutexattr_t *get_default_mutex_attrs(); + +#endif // _FLUTTERPI_SRC_UTIL_COLLECTION_H diff --git a/src/util/dynarray.h b/src/util/dynarray.h new file mode 100644 index 00000000..2d9f2101 --- /dev/null +++ b/src/util/dynarray.h @@ -0,0 +1,256 @@ +/************************************************************************** + * + * Copyright 2010 Luca Barbieri + * Copyright 2023 Hannes Winkler + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ + +#ifndef U_DYNARRAY_H +#define U_DYNARRAY_H + +#include +#include +#include + +#include "macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static unsigned util_dynarray_is_data_stack_allocated; + +/* A zero-initialized version of this is guaranteed to represent an + * empty array. + * + * Also, size <= capacity and data != 0 if and only if capacity != 0 + * capacity will always be the allocation size of data + */ +struct util_dynarray { + void *mem_ctx; + void *data; + unsigned size; + unsigned capacity; +}; + +static inline void util_dynarray_init(struct util_dynarray *buf) { + memset(buf, 0, sizeof(*buf)); + buf->mem_ctx = NULL; +} + +static inline void util_dynarray_init_with_memctx(struct util_dynarray *buf, void *mem_ctx) { + memset(buf, 0, sizeof(*buf)); + buf->mem_ctx = mem_ctx; +} + +static inline void util_dynarray_init_from_stack(struct util_dynarray *buf, void *data, unsigned capacity) { + memset(buf, 0, sizeof(*buf)); + buf->mem_ctx = &util_dynarray_is_data_stack_allocated; + buf->data = data; + buf->capacity = capacity; +} + +static inline void util_dynarray_fini(struct util_dynarray *buf) { + if (buf->data) { + if (buf->mem_ctx == &util_dynarray_is_data_stack_allocated) { + } else if (buf->mem_ctx) { + free(buf->data); + } else { + free(buf->data); + } + util_dynarray_init(buf); + } +} + +static inline void util_dynarray_clear(struct util_dynarray *buf) { + buf->size = 0; +} + +#define DYN_ARRAY_INITIAL_SIZE 64 + +MUST_CHECK static inline void *util_dynarray_ensure_cap(struct util_dynarray *buf, unsigned newcap) { + if (newcap > buf->capacity) { + unsigned capacity = MAX3(DYN_ARRAY_INITIAL_SIZE, buf->capacity * 2, newcap); + void *data; + + if (buf->mem_ctx == &util_dynarray_is_data_stack_allocated) { + data = malloc(capacity); + if (data) { + memcpy(data, buf->data, buf->size); + buf->mem_ctx = NULL; + } + } else { + data = realloc(buf->data, capacity); + } + if (!data) + return NULL; + + buf->data = data; + buf->capacity = capacity; + } + + return (void *) ((char *) buf->data + buf->size); +} + +/* use util_dynarray_trim to reduce the allocated storage */ +MUST_CHECK static inline void *util_dynarray_resize_bytes(struct util_dynarray *buf, unsigned nelts, size_t eltsize) { + if (UNLIKELY(nelts > UINT_MAX / eltsize)) + return NULL; + + unsigned newsize = nelts * eltsize; + void *p = util_dynarray_ensure_cap(buf, newsize); + if (!p) + return NULL; + + buf->size = newsize; + + return p; +} + +static inline void util_dynarray_clone(struct util_dynarray *buf, void *mem_ctx, struct util_dynarray *from_buf) { + util_dynarray_init_with_memctx(buf, mem_ctx); + if (util_dynarray_resize_bytes(buf, from_buf->size, 1)) { + memcpy(buf->data, from_buf->data, from_buf->size); + } +} + +MUST_CHECK static inline void *util_dynarray_grow_bytes(struct util_dynarray *buf, unsigned ngrow, size_t eltsize) { + unsigned growbytes = ngrow * eltsize; + + if (UNLIKELY(ngrow > (UINT_MAX / eltsize) || growbytes > UINT_MAX - buf->size)) + return NULL; + + unsigned newsize = buf->size + growbytes; + void *p = util_dynarray_ensure_cap(buf, newsize); + if (!p) + return NULL; + + buf->size = newsize; + + return p; +} + +static inline void util_dynarray_trim(struct util_dynarray *buf) { + if (buf->mem_ctx == &util_dynarray_is_data_stack_allocated) + return; + + if (buf->size != buf->capacity) { + if (buf->size) { + buf->data = realloc(buf->data, buf->size); + buf->capacity = buf->size; + } else { + free(buf->data); + buf->data = NULL; + buf->capacity = 0; + } + } +} + +static inline void util_dynarray_append_dynarray(struct util_dynarray *buf, const struct util_dynarray *other) { + if (other->size > 0) { + void *p = util_dynarray_grow_bytes(buf, 1, other->size); + memcpy(p, other->data, other->size); + } +} + +#define util_dynarray_append(buf, type, v) \ + do { \ + type __v = (v); \ + memcpy(util_dynarray_grow_bytes((buf), 1, sizeof(type)), &__v, sizeof(type)); \ + } while (0) +/* Returns a pointer to the space of the first new element (in case of growth) or NULL on failure. */ +#define util_dynarray_resize(buf, type, nelts) util_dynarray_resize_bytes(buf, (nelts), sizeof(type)) +#define util_dynarray_grow(buf, type, ngrow) util_dynarray_grow_bytes(buf, (ngrow), sizeof(type)) +#define util_dynarray_top_ptr(buf, type) (type *) ((char *) (buf)->data + (buf)->size - sizeof(type)) +#define util_dynarray_top(buf, type) *util_dynarray_top_ptr(buf, type) +#define util_dynarray_pop_ptr(buf, type) (type *) ((char *) (buf)->data + ((buf)->size -= sizeof(type))) +#define util_dynarray_pop(buf, type) *util_dynarray_pop_ptr(buf, type) +#define util_dynarray_contains(buf, type) ((buf)->size >= sizeof(type)) +#define util_dynarray_element(buf, type, idx) ((type *) (buf)->data + (idx)) +#define util_dynarray_begin(buf) ((buf)->data) +#define util_dynarray_end(buf) ((void *) util_dynarray_element((buf), char, (buf)->size)) +#define util_dynarray_num_elements(buf, type) ((buf)->size / sizeof(type)) + +#define util_dynarray_foreach(buf, type, elem) \ + for (type *elem = (type *) (buf)->data; elem < (type *) ((char *) (buf)->data + (buf)->size); elem++) + +#define util_dynarray_foreach_reverse(buf, type, elem) \ + if ((buf)->size > 0) \ + for (type *elem = util_dynarray_top_ptr(buf, type); elem; elem = elem > (type *) (buf)->data ? elem - 1 : NULL) + +#define util_dynarray_delete_unordered(buf, type, v) \ + do { \ + unsigned num_elements = (buf)->size / sizeof(type); \ + unsigned i; \ + for (i = 0; i < num_elements; i++) { \ + type __v = *util_dynarray_element((buf), type, (i)); \ + if (v == __v) { \ + memcpy(util_dynarray_element((buf), type, (i)), util_dynarray_pop_ptr((buf), type), sizeof(type)); \ + break; \ + } \ + } \ + } while (0) + +#define util_dynarray_delete_unordered_ext(buf, type, v, equals_fn) \ + do { \ + unsigned num_elements = (buf)->size / sizeof(type); \ + unsigned i; \ + for (i = 0; i < num_elements; i++) { \ + type __v = *util_dynarray_element((buf), type, (i)); \ + if (equals_fn((v), __v)) { \ + memcpy(util_dynarray_element((buf), type, (i)), util_dynarray_pop_ptr((buf), type), sizeof(type)); \ + break; \ + } \ + } \ + } while (0) + +#define util_dynarray_delete_where_unordered(buf, type, where_fn, userdata) \ + do { \ + unsigned num_elements = (buf)->size / sizeof(type); \ + unsigned i; \ + for (i = 0; i < num_elements; i++) { \ + type __v = *util_dynarray_element((buf), type, (i)); \ + if (where_fn((__v), userdata)) { \ + memcpy(util_dynarray_element((buf), type, (i)), util_dynarray_pop_ptr((buf), type), sizeof(type)); \ + } \ + } \ + } while (0) + +#define util_dynarray_delete_single_where_unordered(buf, type, where_fn, userdata) \ + do { \ + unsigned num_elements = (buf)->size / sizeof(type); \ + unsigned i; \ + for (i = 0; i < num_elements; i++) { \ + type __v = *util_dynarray_element((buf), type, (i)); \ + if (where_fn((__v), userdata)) { \ + memcpy(util_dynarray_element((buf), type, (i)), util_dynarray_pop_ptr((buf), type), sizeof(type)); \ + break; \ + } \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* U_DYNARRAY_H */ diff --git a/src/util/geometry.h b/src/util/geometry.h new file mode 100644 index 00000000..11481244 --- /dev/null +++ b/src/util/geometry.h @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT +/* + * Geometry - structs and functions for working with vectors & rectangles + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_GEOMETRY_H +#define _FLUTTERPI_SRC_UTIL_GEOMETRY_H + +#include +#include + +#include "macros.h" + +/** + * @brief A 2-dimensional vector with 2 float coordinates. + * + */ +struct vec2f { + double x, y; +}; + +#define VEC2F(_x, _y) ((struct vec2f){ .x = (_x), .y = (_y) }) + +ATTR_CONST static inline struct vec2f vec2f_add(struct vec2f a, struct vec2f b) { + return VEC2F(a.x + b.x, a.y + b.y); +} + +ATTR_CONST static inline struct vec2f vec2f_sub(struct vec2f a, struct vec2f b) { + return VEC2F(a.x - b.x, a.y - b.y); +} + +ATTR_CONST static inline bool vec2f_equals(struct vec2f a, struct vec2f b) { + return a.x == b.x && a.y == b.y; +} + +ATTR_CONST static inline struct vec2f vec2f_round(struct vec2f a) { + return VEC2F(round(a.x), round(a.y)); +} + +struct vec2i { + int x, y; +}; + +#define VEC2I(_x, _y) ((struct vec2i){ .x = (_x), .y = (_y) }) + +ATTR_CONST static inline struct vec2i vec2i_add(struct vec2i a, struct vec2i b) { + return VEC2I(a.x + b.x, a.y + b.y); +} + +ATTR_CONST static inline struct vec2i vec2i_sub(struct vec2i a, struct vec2i b) { + return VEC2I(a.x - b.x, a.y - b.y); +} + +ATTR_CONST static inline struct vec2i vec2i_swap_xy(const struct vec2i point) { + return VEC2I(point.y, point.x); +} + +/** + * @brief A quadrilateral with 4 2-dimensional float coordinates. + * + */ +struct quad { + struct vec2f top_left, top_right, bottom_left, bottom_right; +}; + +#define QUAD(_top_left, _top_right, _bottom_left, _bottom_right) \ + ((struct quad){ \ + .top_left = _top_left, \ + .top_right = _top_right, \ + .bottom_left = _bottom_left, \ + .bottom_right = _bottom_right, \ + }) + +#define QUAD_FROM_COORDS(_x1, _y1, _x2, _y2, _x3, _y3, _x4, _y4) QUAD(VEC2F(_x1, _y1), VEC2F(_x2, _y2), VEC2F(_x3, _y3), VEC2F(_x4, _y4)) + +struct aa_rect { + struct vec2f offset, size; +}; + +#define AA_RECT(_offset, _size) \ + ((struct aa_rect){ \ + .offset = offset, \ + .size = size, \ + }) + +#define AA_RECT_FROM_COORDS(offset_x, offset_y, width, height) \ + ((struct aa_rect){ \ + .offset = VEC2F(offset_x, offset_y), \ + .size = VEC2F(width, height), \ + }) + +ATTR_CONST static inline struct aa_rect quad_get_aa_bounding_rect(const struct quad _rect) { + double l = MIN4(_rect.top_left.x, _rect.top_right.x, _rect.bottom_left.x, _rect.bottom_right.x); + double r = MAX4(_rect.top_left.x, _rect.top_right.x, _rect.bottom_left.x, _rect.bottom_right.x); + double t = MIN4(_rect.top_left.y, _rect.top_right.y, _rect.bottom_left.y, _rect.bottom_right.y); + double b = MAX4(_rect.top_left.y, _rect.top_right.y, _rect.bottom_left.y, _rect.bottom_right.y); + return AA_RECT_FROM_COORDS(l, t, r - l, b - t); +} + +ATTR_CONST static inline struct vec2f aa_rect_top_left(const struct aa_rect rect) { + return rect.offset; +} + +ATTR_CONST static inline struct vec2f aa_rect_top_right(const struct aa_rect rect) { + return VEC2F(rect.offset.x + rect.size.x, rect.offset.y); +} + +ATTR_CONST static inline struct vec2f aa_rect_bottom_left(const struct aa_rect rect) { + return VEC2F(rect.offset.x, rect.offset.y + rect.size.y); +} + +ATTR_CONST static inline struct vec2f aa_rect_bottom_right(const struct aa_rect rect) { + return vec2f_add(rect.offset, rect.size); +} + +ATTR_CONST static inline struct quad get_quad(const struct aa_rect rect) { + return (struct quad){ + .top_left = rect.offset, + .top_right.x = rect.offset.x + rect.size.x, + .top_right.y = rect.offset.y, + .bottom_left.x = rect.offset.x, + .bottom_left.y = rect.offset.y + rect.size.y, + .bottom_right.x = rect.offset.x + rect.size.x, + .bottom_right.y = rect.offset.y + rect.size.y, + }; +} + +ATTR_CONST static inline bool quad_is_axis_aligned(const struct quad quad) { + struct aa_rect aa = quad_get_aa_bounding_rect(quad); + + return vec2f_equals(quad.top_left, aa_rect_top_left(aa)) && + vec2f_equals(quad.top_right, aa_rect_top_right(aa)) && + vec2f_equals(quad.bottom_left, aa_rect_bottom_left(aa)) && + vec2f_equals(quad.bottom_right, aa_rect_bottom_right(aa)); +} + +struct mat3f { + double scaleX; + double skewX; + double transX; + double skewY; + double scaleY; + double transY; + double pers0; + double pers1; + double pers2; +}; + +#define FLUTTER_TRANSFORM_AS_MAT3F(_t) \ + ((struct mat3f){ \ + (_t).scaleX, \ + (_t).skewX, \ + (_t).transX, \ + (_t).skewY, \ + (_t).scaleY, \ + (_t).transY, \ + (_t).pers0, \ + (_t).pers1, \ + (_t).pers2, \ + }) + +#define MAT3F_AS_FLUTTER_TRANSFORM(_t) \ + ((FlutterTransformation){ \ + (_t).scaleX, \ + (_t).skewX, \ + (_t).transX, \ + (_t).skewY, \ + (_t).scaleY, \ + (_t).transY, \ + (_t).pers0, \ + (_t).pers1, \ + (_t).pers2, \ + }) + +#define MAT3F_TRANSLATION(translate_x, translate_y) \ + ((struct mat3f){ \ + .scaleX = 1, \ + .skewX = 0, \ + .transX = translate_x, \ + .skewY = 0, \ + .scaleY = 1, \ + .transY = translate_y, \ + .pers0 = 0, \ + .pers1 = 0, \ + .pers2 = 1, \ + }) + +/** + * @brief A flutter transformation that rotates any coords around the x-axis, counter-clockwise. + */ +#define MAT3F_ROTX(deg) \ + ((struct mat3f){ \ + .scaleX = 1, \ + .skewX = 0, \ + .transX = 0, \ + .skewY = 0, \ + .scaleY = cos(((double) (deg)) / 180.0 * M_PI), \ + .transY = -sin(((double) (deg)) / 180.0 * M_PI), \ + .pers0 = 0, \ + .pers1 = sin(((double) (deg)) / 180.0 * M_PI), \ + .pers2 = cos(((double) (deg)) / 180.0 * M_PI), \ + }) + +/** + * @brief A flutter transformation that rotates any coords around the y-axis, counter-clockwise. + */ +#define MAT3F_ROTY(deg) \ + ((struct mat3f){ \ + .scaleX = cos(((double) (deg)) / 180.0 * M_PI), \ + .skewX = 0, \ + .transX = sin(((double) (deg)) / 180.0 * M_PI), \ + .skewY = 0, \ + .scaleY = 1, \ + .transY = 0, \ + .pers0 = -sin(((double) (deg)) / 180.0 * M_PI), \ + .pers1 = 0, \ + .pers2 = cos(((double) (deg)) / 180.0 * M_PI), \ + }) + +/** + * @brief A flutter transformation that rotates any coords around the z-axis, counter-clockwise. + */ +#define MAT3F_ROTZ(deg) \ + ((struct mat3f){ \ + .scaleX = cos(((double) (deg)) / 180.0 * M_PI), \ + .skewX = -sin(((double) (deg)) / 180.0 * M_PI), \ + .transX = 0, \ + .skewY = sin(((double) (deg)) / 180.0 * M_PI), \ + .scaleY = cos(((double) (deg)) / 180.0 * M_PI), \ + .transY = 0, \ + .pers0 = 0, \ + .pers1 = 0, \ + .pers2 = 1, \ + }) + +/** + * @brief Returns a matrix that is the result of matrix-multiplying a with b. + * + * @param a The first (lhs) input matrix. + * @param b The second (rhs) input matrix. + * @return struct mat3f The product of a x b. + */ +ATTR_CONST static inline struct mat3f multiply_mat3f(const struct mat3f a, const struct mat3f b) { + return (struct mat3f){ + .scaleX = a.scaleX * b.scaleX + a.skewX * b.skewY + a.transX * b.pers0, + .skewX = a.scaleX * b.skewX + a.skewX * b.scaleY + a.transX * b.pers1, + .transX = a.scaleX * b.transX + a.skewX * b.transY + a.transX * b.pers2, + .skewY = a.skewY * b.scaleX + a.scaleY * b.skewY + a.transY * b.pers0, + .scaleY = a.skewY * b.skewX + a.scaleY * b.scaleY + a.transY * b.pers1, + .transY = a.skewY * b.transX + a.scaleY * b.transY + a.transY * b.pers2, + .pers0 = a.pers0 * b.scaleX + a.pers1 * b.skewY + a.pers2 * b.pers0, + .pers1 = a.pers0 * b.skewX + a.pers1 * b.scaleY + a.pers2 * b.pers1, + .pers2 = a.pers0 * b.transX + a.pers1 * b.transY + a.pers2 * b.pers2, + }; +} + +/** + * @brief Returns a matrix that is the result of element-wise addition of a and b. + * + * @param a The lhs input matrix. + * @param b The rhs input matrix. + * @return struct mat3f The result of a + b. (element-wise) + */ +ATTR_CONST static inline struct mat3f add_mat3f(const struct mat3f a, const struct mat3f b) { + return (struct mat3f){ + .scaleX = a.scaleX + b.scaleX, + .skewX = a.skewX + b.skewX, + .transX = a.transX + b.transX, + .skewY = a.skewY + b.skewY, + .scaleY = a.scaleY + b.scaleY, + .transY = a.transY + b.transY, + .pers0 = a.pers0 + b.pers0, + .pers1 = a.pers1 + b.pers1, + .pers2 = a.pers2 + b.pers2, + }; +} + +/** + * @brief Returns the transponated of a. + * + * @param a The input matrix. + * @return struct mat3f a transponated. + */ +ATTR_CONST static inline struct mat3f transponate_mat3f(const struct mat3f a) { + return (struct mat3f){ + .scaleX = a.scaleX, + .skewX = a.skewY, + .transX = a.pers0, + .skewY = a.skewX, + .scaleY = a.scaleY, + .transY = a.pers1, + .pers0 = a.transX, + .pers1 = a.transY, + .pers2 = a.pers2, + }; +} + +ATTR_CONST static inline struct vec2f transform_point(const struct mat3f transform, const struct vec2f point) { + return VEC2F( + transform.scaleX * point.x + transform.skewX * point.y + transform.transX, + transform.skewY * point.x + transform.scaleY * point.y + transform.transY + ); +} + +ATTR_CONST static inline struct quad transform_quad(const struct mat3f transform, const struct quad rect) { + return QUAD( + transform_point(transform, rect.top_left), + transform_point(transform, rect.top_right), + transform_point(transform, rect.bottom_left), + transform_point(transform, rect.bottom_right) + ); +} + +ATTR_CONST static inline struct quad transform_aa_rect(const struct mat3f transform, const struct aa_rect rect) { + return transform_quad(transform, get_quad(rect)); +} + +ATTR_CONST static inline struct vec2f vec2f_swap_xy(const struct vec2f point) { + return VEC2F(point.y, point.x); +} + +#endif // _FLUTTERPI_SRC_UTIL_GEOMETRY_H diff --git a/src/util/list.h b/src/util/list.h new file mode 100644 index 00000000..28440cd8 --- /dev/null +++ b/src/util/list.h @@ -0,0 +1,245 @@ +/************************************************************************** + * + * Copyright 2006 VMware, Inc., Bismarck, ND. USA. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + **************************************************************************/ + +/** + * \file + * List macros heavily inspired by the Linux kernel + * list handling. No list looping yet. + * + * Is not threadsafe, so common operations need to + * be protected using an external mutex. + */ + +#ifndef _UTIL_LIST_H_ +#define _UTIL_LIST_H_ + +#include +#include +#include + +#define list_assert(cond, msg) assert(cond &&msg) + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +static inline void list_inithead(struct list_head *item) { + item->prev = item; + item->next = item; +} + +/** + * Prepend an item to a list + * + * @param item The element to add to the list + * @param list The list to prepend to + */ +static inline void list_add(struct list_head *item, struct list_head *list) { + item->prev = list; + item->next = list->next; + list->next->prev = item; + list->next = item; +} + +/** + * Append an item to a list + * + * @param item The element to add to the list + * @param list The list to append to + */ +static inline void list_addtail(struct list_head *item, struct list_head *list) { + item->next = list; + item->prev = list->prev; + list->prev->next = item; + list->prev = item; +} + +static inline bool list_is_empty(const struct list_head *list); + +static inline void list_replace(struct list_head *from, struct list_head *to) { + if (list_is_empty(from)) { + list_inithead(to); + } else { + to->prev = from->prev; + to->next = from->next; + from->next->prev = to; + from->prev->next = to; + } +} + +static inline void list_del(struct list_head *item) { + item->prev->next = item->next; + item->next->prev = item->prev; + item->prev = item->next = NULL; +} + +static inline void list_delinit(struct list_head *item) { + item->prev->next = item->next; + item->next->prev = item->prev; + item->next = item; + item->prev = item; +} + +static inline bool list_is_empty(const struct list_head *list) { + return list->next == list; +} + +static inline bool list_is_linked(const struct list_head *list) { + /* both must be NULL or both must be not NULL */ + assert((list->prev != NULL) == (list->next != NULL)); + + return list->next != NULL; +} + +/** + * Returns whether the list has exactly one element. + */ +static inline bool list_is_singular(const struct list_head *list) { + return list_is_linked(list) && !list_is_empty(list) && list->next->next == list; +} + +static inline unsigned list_length(const struct list_head *list) { + struct list_head *node; + unsigned length = 0; + for (node = list->next; node != list; node = node->next) + length++; + return length; +} + +static inline void list_splice(struct list_head *src, struct list_head *dst) { + if (list_is_empty(src)) + return; + + src->next->prev = dst; + src->prev->next = dst->next; + dst->next->prev = src->prev; + dst->next = src->next; +} + +static inline void list_splicetail(struct list_head *src, struct list_head *dst) { + if (list_is_empty(src)) + return; + + src->prev->next = dst; + src->next->prev = dst->prev; + dst->prev->next = src->next; + dst->prev = src->prev; +} + +static inline void list_validate(const struct list_head *list) { + struct list_head *node; + assert(list_is_linked(list)); + assert(list->next->prev == list && list->prev->next == list); + for (node = list->next; node != list; node = node->next) + assert(node->next->prev == node && node->prev->next == node); +} + +/** + * Move an item from one place in a list to another + * + * The item can be in this list, or in another. + * + * @param item The item to move + * @param loc The element to put the item in front of + */ +static inline void list_move_to(struct list_head *item, struct list_head *loc) { + list_del(item); + list_add(item, loc); +} + +#define list_entry(__item, __type, __field) ((__type *) (((char *) (__item)) - offsetof(__type, __field))) + +/** + * Cast from a pointer to a member of a struct back to the containing struct. + * + * 'sample' MUST be initialized, or else the result is undefined! + */ +#define list_container_of(ptr, sample, member) (void *) ((char *) (ptr) - ((char *) &(sample)->member - (char *) (sample))) + +#define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member) + +#define list_last_entry(ptr, type, member) list_entry((ptr)->prev, type, member) + +#define LIST_FOR_EACH_ENTRY(pos, head, member) \ + for (pos = NULL, pos = list_container_of((head)->next, pos, member); &pos->member != (head); \ + pos = list_container_of(pos->member.next, pos, member)) + +#define LIST_FOR_EACH_ENTRY_SAFE(pos, storage, head, member) \ + for (pos = NULL, pos = list_container_of((head)->next, pos, member), storage = list_container_of(pos->member.next, pos, member); \ + &pos->member != (head); \ + pos = storage, storage = list_container_of(storage->member.next, storage, member)) + +#define LIST_FOR_EACH_ENTRY_SAFE_REV(pos, storage, head, member) \ + for (pos = NULL, pos = list_container_of((head)->prev, pos, member), storage = list_container_of(pos->member.prev, pos, member); \ + &pos->member != (head); \ + pos = storage, storage = list_container_of(storage->member.prev, storage, member)) + +#define LIST_FOR_EACH_ENTRY_FROM(pos, start, head, member) \ + for (pos = NULL, pos = list_container_of((start), pos, member); &pos->member != (head); \ + pos = list_container_of(pos->member.next, pos, member)) + +#define LIST_FOR_EACH_ENTRY_FROM_REV(pos, start, head, member) \ + for (pos = NULL, pos = list_container_of((start), pos, member); &pos->member != (head); \ + pos = list_container_of(pos->member.prev, pos, member)) + +#define list_for_each_entry(type, pos, head, member) \ + for (type *pos = list_entry((head)->next, type, member), *__next = list_entry(pos->member.next, type, member); &pos->member != (head); \ + pos = list_entry(pos->member.next, type, member), \ + list_assert(pos == __next, "use _safe iterator"), \ + __next = list_entry(__next->member.next, type, member)) + +#define list_for_each_entry_safe(type, pos, head, member) \ + for (type *pos = list_entry((head)->next, type, member), *__next = list_entry(pos->member.next, type, member); &pos->member != (head); \ + pos = __next, __next = list_entry(__next->member.next, type, member)) + +#define list_for_each_entry_rev(type, pos, head, member) \ + for (type *pos = list_entry((head)->prev, type, member), *__prev = list_entry(pos->member.prev, type, member); &pos->member != (head); \ + pos = list_entry(pos->member.prev, type, member), \ + list_assert(pos == __prev, "use _safe iterator"), \ + __prev = list_entry(__prev->member.prev, type, member)) + +#define list_for_each_entry_safe_rev(type, pos, head, member) \ + for (type *pos = list_entry((head)->prev, type, member), *__prev = list_entry(pos->member.prev, type, member); &pos->member != (head); \ + pos = __prev, __prev = list_entry(__prev->member.prev, type, member)) + +#define list_for_each_entry_from(type, pos, start, head, member) \ + for (type *pos = list_entry((start), type, member); &pos->member != (head); pos = list_entry(pos->member.next, type, member)) + +#define list_for_each_entry_from_safe(type, pos, start, head, member) \ + for (type *pos = list_entry((start), type, member), *__next = list_entry(pos->member.next, type, member); &pos->member != (head); \ + pos = __next, __next = list_entry(__next->member.next, type, member)) + +#define list_for_each_entry_from_rev(type, pos, start, head, member) \ + for (type *pos = list_entry((start), type, member); &pos->member != (head); pos = list_entry(pos->member.prev, type, member)) + +#define list_pair_for_each_entry(type, pos1, pos2, head1, head2, member) \ + for (type *pos1 = list_entry((head1)->next, type, member), *pos2 = list_entry((head2)->next, type, member); \ + &pos1->member != (head1) && &pos2->member != (head2); \ + pos1 = list_entry(pos1->member.next, type, member), pos2 = list_entry(pos2->member.next, type, member)) + +#endif /*_UTIL_LIST_H_*/ diff --git a/src/util/lock_ops.h b/src/util/lock_ops.h new file mode 100644 index 00000000..dc0a31a0 --- /dev/null +++ b/src/util/lock_ops.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +/* + * Lock Ops - Macros for defining locking operations for a struct with + * sane defaults. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_LOCK_OPS_H +#define _FLUTTERPI_SRC_UTIL_LOCK_OPS_H + +#include + +#include "asserts.h" + +#define DECLARE_LOCK_OPS(obj_name) \ + UNUSED void obj_name##_lock(struct obj_name *obj); \ + UNUSED void obj_name##_unlock(struct obj_name *obj); + +#define DEFINE_LOCK_OPS(obj_name, mutex_member_name) \ + UNUSED void obj_name##_lock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_lock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ + (void) ok; \ + } \ + UNUSED void obj_name##_unlock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_unlock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ + (void) ok; \ + } + +#define DEFINE_STATIC_LOCK_OPS(obj_name, mutex_member_name) \ + UNUSED static void obj_name##_lock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_lock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ + (void) ok; \ + } \ + UNUSED static void obj_name##_unlock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_unlock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ + (void) ok; \ + } + +#define DEFINE_INLINE_LOCK_OPS(obj_name, mutex_member_name) \ + UNUSED static inline void obj_name##_lock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_lock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error locking mutex."); \ + (void) ok; \ + } \ + UNUSED static inline void obj_name##_unlock(struct obj_name *obj) { \ + int ok; \ + ok = pthread_mutex_unlock(&obj->mutex_member_name); \ + ASSERT_EQUALS_MSG(ok, 0, "Error unlocking mutex."); \ + (void) ok; \ + } + +#endif // _FLUTTERPI_SRC_UTIL_LOCK_OPS_H diff --git a/src/util/logging.h b/src/util/logging.h new file mode 100644 index 00000000..81473c0d --- /dev/null +++ b/src/util/logging.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +/* + * Logging - Provides debug & error logging macros. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_LOGGING_H +#define _FLUTTERPI_SRC_UTIL_LOGGING_H + +#define LOG_ERROR(fmtstring, ...) fprintf(stderr, "%s: " fmtstring, __FILE__, ##__VA_ARGS__) +#define LOG_ERROR_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) + +#ifdef DEBUG + #define LOG_DEBUG(fmtstring, ...) fprintf(stderr, "%s: " fmtstring, __FILE__, ##__VA_ARGS__) + #define LOG_DEBUG_UNPREFIXED(fmtstring, ...) fprintf(stderr, fmtstring, ##__VA_ARGS__) +#else + #define LOG_DEBUG(fmtstring, ...) \ + do { \ + } while (0) + #define LOG_DEBUG_UNPREFIXED(fmtstring, ...) \ + do { \ + } while (0) +#endif + +#endif // _FLUTTERPI_SRC_UTIL_LOGGING_H diff --git a/src/util/macros.h b/src/util/macros.h new file mode 100644 index 00000000..e0f7933a --- /dev/null +++ b/src/util/macros.h @@ -0,0 +1,614 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef UTIL_MACROS_H +#define UTIL_MACROS_H + +#include +#include +#include +#include + +/* Compute the size of an array */ +#ifndef ARRAY_SIZE + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +/* For compatibility with Clang's __has_builtin() */ +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif + +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif + +/** + * Define HAVE___BUILTIN_... macros + */ +#if __has_builtin(__builtin_bswap32) + #define HAVE___BUILTIN_BSWAP32 +#endif +#if __has_builtin(__builtin_bswap64) + #define HAVE___BUILTIN_BSWAP64 +#endif +#if __has_builtin(__builtin_clz) + #define HAVE___BUILTIN_CLZ +#endif +#if __has_builtin(__builtin_clzll) + #define HAVE___BUILTIN_CLZLL +#endif +#if __has_builtin(__builtin_ctz) + #define HAVE___BUILTIN_CTZ +#endif +#if __has_builtin(__builtin_expect) + #define HAVE___BUILTIN_EXPECT +#endif +#if __has_builtin(__builtin_ffs) + #define HAVE___BUILTIN_FFS +#endif +#if __has_builtin(__builtin_ffsll) + #define HAVE___BUILTIN_FFSLL +#endif +#if __has_builtin(__builtin_popcount) + #define HAVE___BUILTIN_POPCOUNT +#endif +#if __has_builtin(__builtin_popcountll) + #define HAVE___BUILTIN_POPCOUNTLL +#endif +#if __has_builtin(__builtin_unreachable) + #define HAVE___BUILTIN_UNREACHABLE +#endif +#if __has_builtin(__builtin_types_compatible_p) + #define HAVE___BUILTIN_TYPES_COMPATIBLE_P +#endif +#if __has_builtin(__builtin_trap) + #define HAVE___BUILTIN_TRAP +#endif + +#if __has_attribute(const) + #define HAVE_FUNC_ATTRIBUTE_CONST +#endif +#if __has_attribute(flatten) + #define HAVE_FUNC_ATTRIBUTE_FLATTEN +#endif +#if __has_attribute(malloc) + #define HAVE_FUNC_ATTRIBUTE_MALLOC +#endif +#if __has_attribute(pure) + #define HAVE_FUNC_ATTRIBUTE_PURE +#endif +#if __has_attribute(unused) + #define HAVE_FUNC_ATTRIBUTE_UNUSED +#endif +#if __has_attribute(warn_unused_result) + #define HAVE_FUNC_ATTRIBUTE_WARN_UNUSED_RESULT +#endif +#if __has_attribute(weak) + #define HAVE_FUNC_ATTRIBUTE_WEAK +#endif +#if __has_attribute(format) + #define HAVE_FUNC_ATTRIBUTE_FORMAT +#endif +#if __has_attribute(packed) + #define HAVE_FUNC_ATTRIBUTE_PACKED +#endif +#if __has_attribute(returns_nonnull) + #define HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL +#endif +#if __has_attribute(alias) + #define HAVE_FUNC_ATTRIBUTE_ALIAS +#endif +#if __has_attribute(noreturn) + #define HAVE_FUNC_ATTRIBUTE_NORETURN +#endif + +/** + * __builtin_expect macros + */ +#if !defined(HAVE___BUILTIN_EXPECT) + #define __builtin_expect(x, y) (x) +#endif + +#ifndef LIKELY + #ifdef HAVE___BUILTIN_EXPECT + #define LIKELY(x) __builtin_expect(!!(x), 1) + #define UNLIKELY(x) __builtin_expect(!!(x), 0) + #else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) + #endif +#endif + +/** + * __builtin_types_compatible_p compat + */ +#if defined(__cplusplus) || !defined(HAVE___BUILTIN_TYPES_COMPATIBLE_P) + #define __builtin_types_compatible_p(type1, type2) (1) +#endif + +/* This should match linux gcc cdecl semantics everywhere, so that we + * just codegen one calling convention on all platforms. + */ +#ifdef _MSC_VER + #define UTIL_CDECL __cdecl +#else + #define UTIL_CDECL +#endif + +/** + * Static (compile-time) assertion. + */ +#define STATIC_ASSERT(cond) \ + do { \ + static_assert(cond, #cond); \ + } while (0) + +/** + * CONTAINER_OF - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + */ +#ifndef __GNUC__ + /* a grown-up compiler is required for the extra type checking: */ + #define CONTAINER_OF(ptr, type, member) (type *) ((uint8_t *) ptr - offsetof(type, member)) +#else + #define __same_type(a, b) __builtin_types_compatible_p(__typeof__(a), __typeof__(b)) + #define CONTAINER_OF(ptr, type, member) \ + ({ \ + uint8_t *__mptr = (uint8_t *) (ptr); \ + static_assert( \ + __same_type(*(ptr), ((type *) 0)->member) || __same_type(*(ptr), void), \ + "pointer type mismatch in CONTAINER_OF()" \ + ); \ + ((type *) (__mptr - offsetof(type, member))); \ + }) +#endif + +/** + * Unreachable macro. Useful for suppressing "control reaches end of non-void + * function" warnings. + */ +#if defined(HAVE___BUILTIN_UNREACHABLE) || __has_builtin(__builtin_unreachable) + #define UNREACHABLE_MSG(str) \ + do { \ + assert(!str); \ + __builtin_unreachable(); \ + } while (0) + #define UNREACHABLE() \ + do { \ + assert(0); \ + __builtin_unreachable(); \ + } while (0) +#elif defined(_MSC_VER) + #define UNREACHABLE_MSG(str) \ + do { \ + assert(!str); \ + __assume(0); \ + } while (0) + #define UNREACHABLE() \ + do { \ + assert(0); \ + __assume(0); \ + } while (0) +#else + #define UNREACHABLE_MSG(str) assert(!str) + #define UNREACHABLE() assert(0) +#endif + +/** + * Assume macro. Useful for expressing our assumptions to the compiler, + * typically for purposes of silencing warnings. + */ +#if __has_builtin(__builtin_assume) + #define ASSUME(expr) \ + do { \ + assert(expr); \ + __builtin_assume(expr); \ + } while (0) +#elif defined HAVE___BUILTIN_UNREACHABLE + #define ASSUME(expr) ((expr) ? ((void) 0) : (assert(!"assumption failed"), __builtin_unreachable())) +#elif defined(_MSC_VER) + #define ASSUME(expr) __assume(expr) +#else + #define ASSUME(expr) assert(expr) +#endif + +/* Attribute const is used for functions that have no effects other than their + * return value, and only rely on the argument values to compute the return + * value. As a result, calls to it can be CSEed. Note that using memory + * pointed to by the arguments is not allowed for const functions. + */ +#if !defined(__clang__) && defined(HAVE_FUNC_ATTRIBUTE_CONST) + #define ATTR_CONST __attribute__((__const__)) +#else + #define ATTR_CONST +#endif + +#ifdef HAVE_FUNC_ATTRIBUTE_FLATTEN + #define FLATTEN __attribute__((__flatten__)) +#else + #define FLATTEN +#endif + +#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT + #if defined(__MINGW_PRINTF_FORMAT) + #define PRINTFLIKE(f, a) __attribute__((format(__MINGW_PRINTF_FORMAT, f, a))) + #else + #define PRINTFLIKE(f, a) __attribute__((format(__printf__, f, a))) + #endif +#else + #define PRINTFLIKE(f, a) +#endif + +#ifdef HAVE_FUNC_ATTRIBUTE_MALLOC + #define MALLOCLIKE __attribute__((__malloc__)) +#else + #define MALLOCLIKE +#endif + +/* Forced function inlining */ +/* Note: Clang also sets __GNUC__ (see other cases below) */ +#ifndef ALWAYS_INLINE + #if defined(__GNUC__) + #define ALWAYS_INLINE inline __attribute__((always_inline)) + #elif defined(_MSC_VER) + #define ALWAYS_INLINE __forceinline + #else + #define ALWAYS_INLINE inline + #endif +#endif + +/* Used to optionally mark structures with misaligned elements or size as + * packed, to trade off performance for space. + */ +#ifdef HAVE_FUNC_ATTRIBUTE_PACKED + #define PACKED __attribute__((__packed__)) +#else + #define PACKED +#endif + +/* Attribute pure is used for functions that have no effects other than their + * return value. As a result, calls to it can be dead code eliminated. + */ +#ifdef HAVE_FUNC_ATTRIBUTE_PURE + #define ATTR_PURE __attribute__((__pure__)) +#else + #define ATTR_PURE +#endif + +#ifdef HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL + #define ATTR_RETURNS_NONNUL __attribute__((__returns_nonnull__)) +#else + #define ATTR_RETURNS_NONNUL +#endif + +#ifndef NORETURN + #ifdef _MSC_VER + #define NORETURN __declspec(noreturn) + #elif defined HAVE_FUNC_ATTRIBUTE_NORETURN + #define NORETURN __attribute__((__noreturn__)) + #else + #define NORETURN + #endif +#endif + +#ifdef __cplusplus + /** + * Macro function that evaluates to true if T is a trivially + * destructible type -- that is, if its (non-virtual) destructor + * performs no action and all member variables and base classes are + * trivially destructible themselves. + */ + #if defined(__clang__) + #if __has_builtin(__is_trivially_destructible) + #define HAS_TRIVIAL_DESTRUCTOR(T) __is_trivially_destructible(T) + #elif (defined(__has_feature) && __has_feature(has_trivial_destructor)) + #define HAS_TRIVIAL_DESTRUCTOR(T) __has_trivial_destructor(T) + #endif + #elif defined(__GNUC__) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3))) + #define HAS_TRIVIAL_DESTRUCTOR(T) __has_trivial_destructor(T) + #endif + #elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) + #define HAS_TRIVIAL_DESTRUCTOR(T) __has_trivial_destructor(T) + #endif + #ifndef HAS_TRIVIAL_DESTRUCTOR + /* It's always safe (if inefficient) to assume that a + * destructor is non-trivial. + */ + #define HAS_TRIVIAL_DESTRUCTOR(T) (false) + #endif +#endif + +/** + * PUBLIC/USED macros + * + * If we build the library with gcc's -fvisibility=hidden flag, we'll + * use the PUBLIC macro to mark functions that are to be exported. + * + * We also need to define a USED attribute, so the optimizer doesn't + * inline a static function that we later use in an alias. - ajax + */ +#ifndef PUBLIC + #if defined(_WIN32) + #define PUBLIC __declspec(dllexport) + #define USED + #elif defined(__GNUC__) + #define PUBLIC __attribute__((visibility("default"))) + #define USED __attribute__((used)) + #else + #define PUBLIC + #define USED + #endif +#endif + +/** + * UNUSED marks variables (or sometimes functions) that have to be defined, + * but are sometimes (or always) unused beyond that. A common case is for + * a function parameter to be used in some build configurations but not others. + * Another case is fallback vfuncs that don't do anything with their params. + * + * Note that this should not be used for identifiers used in `assert()`; + * see ASSERTED below. + */ +#ifdef HAVE_FUNC_ATTRIBUTE_UNUSED + #define UNUSED __attribute__((unused)) +#elif defined(_MSC_VER) + #define UNUSED __pragma(warning(suppress : 4100 4101 4189)) +#else + #define UNUSED +#endif + +/** + * Use ASSERTED to indicate that an identifier is unused outside of an `assert()`, + * so that assert-free builds don't get "unused variable" warnings. + */ +#ifdef NDEBUG + #define ASSERTED UNUSED +#else + #define ASSERTED +#endif + +#ifdef HAVE_FUNC_ATTRIBUTE_WARN_UNUSED_RESULT + #define MUST_CHECK __attribute__((warn_unused_result)) +#else + #define MUST_CHECK +#endif + +#if defined(__GNUC__) + #define ATTR_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) + #define ATTR_NOINLINE __declspec(noinline) +#else + #define ATTR_NOINLINE +#endif + +/** + * Check that STRUCT::FIELD can hold MAXVAL. We use a lot of bitfields + * in Mesa/gallium. We have to be sure they're of sufficient size to + * hold the largest expected value. + * Note that with MSVC, enums are signed and enum bitfields need one extra + * high bit (always zero) to ensure the max value is handled correctly. + * This macro will detect that with MSVC, but not GCC. + */ +#define ASSERT_BITFIELD_SIZE(STRUCT, FIELD, MAXVAL) \ + do { \ + ASSERTED STRUCT s; \ + s.FIELD = (MAXVAL); \ + assert((int) s.FIELD == (MAXVAL) && "Insufficient bitfield size!"); \ + } while (0) + +/** Compute ceiling of integer quotient of A divided by B. */ +#define DIV_ROUND_UP(A, B) (((A) + (B) -1) / (B)) + +/** + * Clamp X to [MIN,MAX]. Turn NaN into MIN, arbitrarily. + * + * glib defines this as well. So check we don't redefine it. + */ +#ifndef CLAMP + #define CLAMP(X, MIN, MAX) ((X) > (MIN) ? ((X) > (MAX) ? (MAX) : (X)) : (MIN)) +#endif + +/* Syntax sugar occuring frequently in graphics code */ +#define SATURATE(X) CLAMP(X, 0.0f, 1.0f) + +/** Minimum of two values: */ +#define MIN2(A, B) ((A) < (B) ? (A) : (B)) + +/** Maximum of two values: */ +#define MAX2(A, B) ((A) > (B) ? (A) : (B)) + +/** Minimum and maximum of three values: */ +#define MIN3(A, B, C) ((A) < (B) ? MIN2(A, C) : MIN2(B, C)) +#define MAX3(A, B, C) ((A) > (B) ? MAX2(A, C) : MAX2(B, C)) + +/** Minimum and maximum of four values: */ +#define MIN4(A, B, C, D) ((A) < (B) ? MIN3(A, C, D) : MIN3(B, C, D)) +#define MAX4(A, B, C, D) ((A) > (B) ? MAX3(A, C, D) : MAX3(B, C, D)) + +/** Align a value to a power of two */ +#define ALIGN_POT(x, pot_align) (((x) + (pot_align) -1) & ~((pot_align) -1)) + +/** Checks is a value is a power of two. Does not handle zero. */ +#define IS_POT(v) (((v) & ((v) -1)) == 0) + +/** Set a single bit */ +#define BITFIELD_BIT(b) (1u << (b)) +/** Set all bits up to excluding bit b */ +#define BITFIELD_MASK(b) ((b) == 32 ? (~0u) : BITFIELD_BIT((b) % 32) - 1) +/** Set count bits starting from bit b */ +#define BITFIELD_RANGE(b, count) (BITFIELD_MASK((b) + (count)) & ~BITFIELD_MASK(b)) + +/** Set a single bit */ +#define BITFIELD64_BIT(b) (1ull << (b)) +/** Set all bits up to excluding bit b */ +#define BITFIELD64_MASK(b) ((b) == 64 ? (~0ull) : BITFIELD64_BIT(b) - 1) +/** Set count bits starting from bit b */ +#define BITFIELD64_RANGE(b, count) (BITFIELD64_MASK((b) + (count)) & ~BITFIELD64_MASK(b)) + +static inline int64_t u_intN_max(unsigned bit_size) { + assert(bit_size <= 64 && bit_size > 0); + return INT64_MAX >> (64 - bit_size); +} + +static inline int64_t u_intN_min(unsigned bit_size) { + /* On 2's compliment platforms, which is every platform Mesa is likely to + * every worry about, stdint.h generally calculated INT##_MIN in this + * manner. + */ + return (-u_intN_max(bit_size)) - 1; +} + +static inline uint64_t u_uintN_max(unsigned bit_size) { + assert(bit_size <= 64 && bit_size > 0); + return UINT64_MAX >> (64 - bit_size); +} + +/* alignas usage + * For struct or union, use alignas(align_size) on any member + * of it will make it aligned to align_size. + * See https://en.cppreference.com/w/c/language/_Alignas for + * details. We can use static_assert and alignof to check if + * the alignment result of alignas(align_size) on struct or + * union is valid. + * For example: + * static_assert(alignof(struct tgsi_exec_machine) == 16, "") + * Also, we can use special code to see the size of the aligned + * struct or union at the compile time with GCC, Clang or MSVC. + * So we can see if the size of union or struct are as expected + * when using alignas(align_size) on its member. + * For example: + * char (*__kaboom)[sizeof(struct tgsi_exec_machine)] = 1; + * can show us the size of struct tgsi_exec_machine at compile + * time. + */ +#ifndef __cplusplus + #ifdef _MSC_VER + #define alignof _Alignof + #define alignas _Alignas + #else + #include + #endif +#endif + +/* Macros for static type-safety checking. + * + * https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + */ + +#if __has_attribute(capability) +typedef int __attribute__((capability("mutex"))) lock_cap_t; + + #define GUARDED_BY(l) __attribute__((guarded_by(l))) + #define ACQUIRE_CAP(l) __attribute((acquire_capability(l), no_thread_safety_analysis)) + #define RELEASE_CAP(l) __attribute((release_capability(l), no_thread_safety_analysis)) + #define ASSERT_CAP(l) __attribute((assert_capability(l), no_thread_safety_analysis)) + #define REQUIRES_CAP(l) __attribute((requires_capability(l))) + #define DISABLE_THREAD_SAFETY_ANALYSIS __attribute((no_thread_safety_analysis)) + +#else + +typedef int lock_cap_t; + + #define GUARDED_BY(l) + #define ACQUIRE_CAP(l) + #define RELEASE_CAP(l) + #define ASSERT_CAP(l) + #define REQUIRES_CAP(l) + #define DISABLE_THREAD_SAFETY_ANALYSIS + +#endif + +#define DO_PRAGMA(X) _Pragma(#X) + +#if defined(__clang__) + #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define PRAGMA_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") + #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(clang diagnostic error #X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(clang diagnostic warning #X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(clang diagnostic ignored #X) +#elif defined(__GNUC__) + #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define PRAGMA_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(GCC diagnostic error #X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(GCC diagnostic warning #X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(GCC diagnostic ignored #X) +#else + #define PRAGMA_DIAGNOSTIC_PUSH + #define PRAGMA_DIAGNOSTIC_POP + #define PRAGMA_DIAGNOSTIC_ERROR(X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) +#endif + +#define PASTE2(a, b) a##b +#define PASTE3(a, b, c) a##b##c +#define PASTE4(a, b, c, d) a##b##c##d + +#define CONCAT2(a, b) PASTE2(a, b) +#define CONCAT3(a, b, c) PASTE3(a, b, c) +#define CONCAT4(a, b, c, d) PASTE4(a, b, c, d) + +#if defined(__GNUC__) + #define PRAGMA_POISON(X) DO_PRAGMA(GCC poison X) +#elif defined(__clang__) + #define PRAGMA_POISON(X) DO_PRAGMA(clang poison X) +#else + #define PRAGMA_POISON +#endif + +#ifdef HAVE___BUILTIN_TRAP + #define TRAP() __builtin_trap() +#else + #define TRAP() abort() +#endif + +#define UNIMPLEMENTED() \ + do { \ + fprintf(stderr, "%s%s:%u: Unimplemented\n", __FILE__, __func__, __LINE__); \ + TRAP(); \ + } while (0) + +#ifdef HAVE___BUILTIN_POPCOUNT + #define HWEIGHT(x) __builtin_popcount(x) +#else +static inline int hamming_weight(uint64_t x) { + // put count of each 2 bits into those 2 bits + x -= (x >> 1) & 0x5555555555555555; + + // put count of each 4 bits into those 4 bits + x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); + + // put count of each 8 bits into those 8 bits + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f; + + // returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... + return (x * 0x0101010101010101) >> 56; +} + #define HWEIGHT(x) hamming_weight(x) +#endif + +#endif /* UTIL_MACROS_H */ diff --git a/src/util/refcounting.h b/src/util/refcounting.h new file mode 100644 index 00000000..6f35f300 --- /dev/null +++ b/src/util/refcounting.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +/* + * Refcounting - Defines functions and macros for reference keeping. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_REFCOUNTING_H +#define _FLUTTERPI_SRC_UTIL_REFCOUNTING_H + +#include +#include + +#include "macros.h" + +typedef _Atomic(int) refcount_t; + +static inline int refcount_inc_n(refcount_t *refcount, int n) { + return atomic_fetch_add_explicit(refcount, n, memory_order_relaxed); +} + +/// Increments the reference count and returns the previous value. +static inline int refcount_inc(refcount_t *refcount) { + return refcount_inc_n(refcount, 1); +} + +/// Decrement the reference count, return true if the refcount afterwards +/// is still non-zero. +static inline bool refcount_dec(refcount_t *refcount) { + return atomic_fetch_sub_explicit(refcount, 1, memory_order_acq_rel) != 1; +} + +/// Returns true if the reference count is one. +/// If this is the case you that means this thread has exclusive access +/// to the object. +static inline bool refcount_is_one(refcount_t *refcount) { + return atomic_load_explicit(refcount, memory_order_acquire) == 1; +} + +/// Returns true if the reference count is zero. Should never be true +/// in practice because that'd only be the case for a destroyed object. +/// So this is only really useful for debugging. +static inline bool refcount_is_zero(refcount_t *refcount) { + return atomic_load_explicit(refcount, memory_order_acquire) == 0; +} + +/// Get the current reference count, without any memory ordering restrictions. +/// Not strictly correct, should only be used for debugging. +static inline int refcount_get_for_debug(refcount_t *refcount) { + return atomic_load_explicit(refcount, memory_order_relaxed); +} + +#define REFCOUNT_INIT_0 (0) +#define REFCOUNT_INIT_1 (1) +#define REFCOUNT_INIT_N(n) (n) + +#define DECLARE_REF_OPS(obj_name) \ + UNUSED struct obj_name *obj_name##_ref(struct obj_name *obj); \ + UNUSED void obj_name##_unref(struct obj_name *obj); \ + UNUSED void obj_name##_unrefp(struct obj_name **obj); \ + UNUSED void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj); \ + UNUSED void obj_name##_unref_void(void *obj); + +#define DEFINE_REF_OPS(obj_name, refcount_member_name) \ + UNUSED struct obj_name *obj_name##_ref(struct obj_name *obj) { \ + refcount_inc(&obj->refcount_member_name); \ + return obj; \ + } \ + UNUSED void obj_name##_unref(struct obj_name *obj) { \ + if (refcount_dec(&obj->refcount_member_name) == false) { \ + obj_name##_destroy(obj); \ + } \ + } \ + UNUSED void obj_name##_unrefp(struct obj_name **obj) { \ + obj_name##_unref(*obj); \ + *obj = NULL; \ + } \ + UNUSED void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ + if (obj != NULL) { \ + obj_name##_ref(obj); \ + } \ + if (*objp != NULL) { \ + obj_name##_unrefp(objp); \ + } \ + *objp = obj; \ + } \ + UNUSED void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } + +#define DEFINE_STATIC_REF_OPS(obj_name, refcount_member_name) \ + UNUSED static struct obj_name *obj_name##_ref(struct obj_name *obj) { \ + refcount_inc(&obj->refcount_member_name); \ + return obj; \ + } \ + UNUSED static void obj_name##_unref(struct obj_name *obj) { \ + if (refcount_dec(&obj->refcount_member_name) == false) { \ + obj_name##_destroy(obj); \ + } \ + } \ + UNUSED static void obj_name##_unrefp(struct obj_name **obj) { \ + obj_name##_unref(*obj); \ + *obj = NULL; \ + } \ + UNUSED static void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj) { \ + if (obj != NULL) { \ + obj_name##_ref(obj); \ + } \ + if (*objp != NULL) { \ + obj_name##_unrefp(objp); \ + } \ + *objp = obj; \ + } \ + UNUSED static void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } + +#endif // _FLUTTERPI_SRC_UTIL_REFCOUNTING_H diff --git a/src/util/uuid.h b/src/util/uuid.h new file mode 100644 index 00000000..160d16d4 --- /dev/null +++ b/src/util/uuid.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +/* + * UUID - Defines an uuid struct and macros & functions for working with it. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_UTIL_UUID_H +#define _FLUTTERPI_SRC_UTIL_UUID_H + +#include +#include + +typedef struct { + uint8_t bytes[16]; +} uuid_t; + +#define UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + ((uuid_t){ \ + .bytes = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 }, \ + }) + +#define CONST_UUID(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + ((const uuid_t){ \ + .bytes = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 }, \ + }) + +static inline bool uuid_equals(const uuid_t a, const uuid_t b) { + return memcmp(&a, &b, sizeof(uuid_t)) == 0; +} + +static inline void uuid_copy(uuid_t *dst, const uuid_t src) { + memcpy(dst, &src, sizeof(uuid_t)); +} + +#endif // _FLUTTERPI_SRC_UTIL_UUID_H diff --git a/src/util/vector.c b/src/util/vector.c new file mode 100644 index 00000000..8dd5ace9 --- /dev/null +++ b/src/util/vector.c @@ -0,0 +1,105 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "util/vector.h" + +#include + +/** @file vector.c + * + * A dynamically growable, circular buffer. Elements are added at head and + * removed from tail. head and tail are free-running uint32_t indices and we + * only compute the modulo with size when accessing the array. This way, + * number of bytes in the queue is always head - tail, even in case of + * wraparound. + */ + +/** + * initial_element_count and element_size must be power-of-two. + */ +int u_vector_init_pow2(struct u_vector *vector, uint32_t initial_element_count, uint32_t element_size) { + assert(util_is_power_of_two_nonzero(initial_element_count)); + assert(util_is_power_of_two_nonzero(element_size)); + + vector->head = 0; + vector->tail = 0; + vector->element_size = element_size; + vector->size = element_size * initial_element_count; + vector->data = malloc(vector->size); + + return vector->data != NULL; +} + +void *u_vector_add(struct u_vector *vector) { + uint32_t offset, size, split, src_tail, dst_tail; + void *data; + + if (vector->head - vector->tail == vector->size) { + size = vector->size * 2; + data = malloc(size); + if (data == NULL) + return NULL; + src_tail = vector->tail & (vector->size - 1); + dst_tail = vector->tail & (size - 1); + if (src_tail == 0) { + /* Since we know that the vector is full, this means that it's + * linear from start to end so we can do one copy. + */ + memcpy((char *) data + dst_tail, vector->data, vector->size); + } else { + /* In this case, the vector is split into two pieces and we have + * to do two copies. We have to be careful to make sure each + * piece goes to the right locations. Thanks to the change in + * size, it may or may not still wrap around. + */ + split = u_align_u32(vector->tail, vector->size); + assert(vector->tail <= split && split < vector->head); + memcpy((char *) data + dst_tail, (char *) vector->data + src_tail, split - vector->tail); + memcpy((char *) data + (split & (size - 1)), vector->data, vector->head - split); + } + free(vector->data); + vector->data = data; + vector->size = size; + } + + assert(vector->head - vector->tail < vector->size); + + offset = vector->head & (vector->size - 1); + vector->head += vector->element_size; + + return (char *) vector->data + offset; +} + +void *u_vector_remove(struct u_vector *vector) { + uint32_t offset; + + if (vector->head == vector->tail) + return NULL; + + assert(vector->head - vector->tail <= vector->size); + + offset = vector->tail & (vector->size - 1); + vector->tail += vector->element_size; + + return (char *) vector->data + offset; +} diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 00000000..47cad43b --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,130 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * u_vector is a vector based queue for storing arbitrary + * sized arrays of objects without using a linked list. + */ + +#ifndef U_VECTOR_H +#define U_VECTOR_H + +#include +#include + +#include "util/bitscan.h" +#include "util/macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns the smallest power of two >= x + */ +static inline unsigned util_next_power_of_two(unsigned x) { +#if defined(HAVE___BUILTIN_CLZ) + if (x <= 1) + return 1; + + return (1 << ((sizeof(unsigned) * 8) - __builtin_clz(x - 1))); +#else + unsigned val = x; + + if (x <= 1) + return 1; + + if (util_is_power_of_two_or_zero(x)) + return x; + + val--; + val = (val >> 1) | val; + val = (val >> 2) | val; + val = (val >> 4) | val; + val = (val >> 8) | val; + val = (val >> 16) | val; + val++; + return val; +#endif +} + +/* TODO - move to u_math.h - name it better etc */ +static inline uint32_t u_align_u32(uint32_t v, uint32_t a) { + assert(a != 0 && a == (a & -((int32_t) a))); + return (v + a - 1) & ~(a - 1); +} + +struct u_vector { + uint32_t head; + uint32_t tail; + uint32_t element_size; + uint32_t size; + void *data; +}; + +int u_vector_init_pow2(struct u_vector *queue, uint32_t initial_element_count, uint32_t element_size); + +void *u_vector_add(struct u_vector *queue); +void *u_vector_remove(struct u_vector *queue); + +static inline int u_vector_init(struct u_vector *queue, uint32_t initial_element_count, uint32_t element_size) { + initial_element_count = util_next_power_of_two(initial_element_count); + element_size = util_next_power_of_two(element_size); + return u_vector_init_pow2(queue, initial_element_count, element_size); +} + +static inline int u_vector_length(struct u_vector *queue) { + return (queue->head - queue->tail) / queue->element_size; +} + +static inline void *u_vector_head(struct u_vector *vector) { + assert(vector->tail < vector->head); + return (void *) ((char *) vector->data + ((vector->head - vector->element_size) & (vector->size - 1))); +} + +static inline void *u_vector_tail(struct u_vector *vector) { + return (void *) ((char *) vector->data + (vector->tail & (vector->size - 1))); +} + +static inline void u_vector_finish(struct u_vector *queue) { + free(queue->data); +} + +#ifdef __cplusplus + #define u_vector_element_cast(elem) (decltype(elem)) +#else + #define u_vector_element_cast(elem) (void *) +#endif + +#define u_vector_foreach(elem, queue) \ + STATIC_ASSERT(__builtin_types_compatible_p(__typeof__(queue), struct u_vector *)); \ + for (uint32_t __u_vector_offset = (queue)->tail; \ + elem = u_vector_element_cast(elem)((char *) (queue)->data + (__u_vector_offset & ((queue)->size - 1))), \ + __u_vector_offset != (queue)->head; \ + __u_vector_offset += (queue)->element_size) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vk_gbm_render_surface.c b/src/vk_gbm_render_surface.c new file mode 100644 index 00000000..e42106e2 --- /dev/null +++ b/src/vk_gbm_render_surface.c @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan GBM Backing Store + * + * - a render surface that can be used for filling flutter vulkan backing stores + * - and for scanout using KMS + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "vk_gbm_render_surface.h" + +#include +#include +#include + +#include + +#include + +#include "render_surface.h" +#include "render_surface_private.h" +#include "surface.h" +#include "surface_private.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" +#include "vk_renderer.h" + +struct vk_gbm_render_surface; +struct vk_renderer; + +struct fb { + struct gbm_bo *bo; + VkDeviceMemory memory; + VkImage image; + FlutterVulkanImage fl_image; +}; + +struct locked_fb { + struct vk_gbm_render_surface *surface; + atomic_flag is_locked; + refcount_t n_refs; + struct fb *fb; +}; + +struct vk_gbm_render_surface { + union { + struct surface surface; + struct render_surface render_surface; + }; + +#ifdef DEBUG + uuid_t uuid; +#endif + + /** + * @brief The vulkan renderer we use for talking to vulkan. + * + */ + struct vk_renderer *renderer; + + /** + * @brief Just some vulkan images that are compatible with GBM/DRM/KMS. + * + * 4 framebuffers is good enough for most use-cases. + */ + struct fb fbs[4]; + + /** + * @brief This is just some locking wrapper around the simple fbs above. + * + * Any locked_fb for which is_locked is false can be locked and then freely used for anything. + * Everything that needs that locked_fb for something should keep a reference on it. + * Once the reference count drops to zero, is_locked will be set to false and the fb is ready to be reused again. + * + */ + struct locked_fb locked_fbs[4]; + + /** + * @brief The framebuffer that was last queued to be presented using @ref vk_gbm_render_surface_queue_present_vulkan. + * + * This framebuffer is still locked so we can present it again any time, without worrying about it being acquired by + * flutter using @ref vk_gbm_render_surface_fill_vulkan. Even when @ref vk_gbm_render_surface_present_kms is called, + * we don't set this to NULL. + * + * This is the framebuffer that will be presented on screen when @ref vk_gbm_render_surface_present_kms or + * @ref vk_gbm_render_surface_present_fbdev is called. + * + */ + struct locked_fb *front_fb; + + /** + * @brief The pixel format to use for all framebuffers. + * + */ + enum pixfmt pixel_format; + +#ifdef DEBUG + /** + * @brief The number of framebuffers that are currently locked. + * + */ + atomic_int n_locked_fbs; +#endif +}; + +static void locked_fb_destroy(struct locked_fb *fb) { + struct vk_gbm_render_surface *surface; + + surface = fb->surface; + fb->surface = NULL; + +#ifdef DEBUG + atomic_fetch_sub(&surface->n_locked_fbs, 1); +#endif + atomic_flag_clear(&fb->is_locked); + surface_unref(CAST_SURFACE(surface)); +} + +DEFINE_STATIC_REF_OPS(locked_fb, n_refs) + +UNUSED static bool atomic_flag_test(atomic_flag *flag) { + bool before = atomic_flag_test_and_set(flag); + if (before == false) { + atomic_flag_clear(flag); + } + return before; +} + +static void log_locked_fbs(struct vk_gbm_render_surface *surface, const char *note) { +#ifdef VK_LOG_LOCKED_FBS + LOG_DEBUG( + "locked: %c, %c, %c, %c", + atomic_flag_test(&surface->locked_fbs[0].is_locked) ? 'y' : 'n', + atomic_flag_test(&surface->locked_fbs[1].is_locked) ? 'y' : 'n', + atomic_flag_test(&surface->locked_fbs[2].is_locked) ? 'y' : 'n', + atomic_flag_test(&surface->locked_fbs[3].is_locked) ? 'y' : 'n' + ); + + if (note != NULL) { + LOG_DEBUG_UNPREFIXED(" (%s)\n", note); + } else { + LOG_DEBUG_UNPREFIXED("\n"); + } +#else + (void) surface; + (void) note; +#endif +} + +COMPILE_ASSERT(offsetof(struct vk_gbm_render_surface, surface) == 0); +COMPILE_ASSERT(offsetof(struct vk_gbm_render_surface, render_surface.surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0x26, 0xfe, 0x91, 0x53, 0x75, 0xf2, 0x41, 0x90, 0xa1, 0xf5, 0xba, 0xe1, 0x1b, 0x28, 0xd5, 0xe5); +#endif + +#define CAST_THIS(ptr) CAST_VK_GBM_RENDER_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct vk_gbm_render_surface *__checked_cast_vk_gbm_render_surface(void *ptr) { + struct vk_gbm_render_surface *surface; + + surface = CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr); + ASSERT(uuid_equals(surface->uuid, uuid)); + return surface; +} +#endif + +void vk_gbm_render_surface_deinit(struct surface *s); +static int vk_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int vk_gbm_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int vk_gbm_render_surface_fill(struct render_surface *surface, FlutterBackingStore *fl_store); +static int vk_gbm_render_surface_queue_present(struct render_surface *surface, const FlutterBackingStore *fl_store); + +static bool is_srgb_format(VkFormat vk_format) { + return vk_format == VK_FORMAT_R8G8B8A8_SRGB || vk_format == VK_FORMAT_B8G8R8A8_SRGB; +} + +static VkFormat srgb_to_unorm_format(VkFormat vk_format) { + assert(is_srgb_format(vk_format)); + if (vk_format == VK_FORMAT_R8G8B8A8_SRGB) { + return VK_FORMAT_R8G8B8A8_UNORM; + } else if (vk_format == VK_FORMAT_B8G8R8A8_SRGB) { + return VK_FORMAT_B8G8R8A8_UNORM; + } else { + UNREACHABLE(); + } +} + +static int +fb_init(struct fb *fb, struct gbm_device *gbm_device, struct vk_renderer *renderer, int width, int height, enum pixfmt pixel_format) { + PFN_vkGetMemoryFdPropertiesKHR get_memory_fd_props; + VkSubresourceLayout layout; + VkDeviceMemory img_device_memory; + struct gbm_bo *bo; + VkFormat vk_format; + VkDevice device; + VkResult ok; + VkImage vkimg; + int fd; + + ASSERT_MSG( + get_pixfmt_info(pixel_format)->vk_format != VK_FORMAT_UNDEFINED, + "Given pixel format is not compatible with any vulkan sRGB format." + ); + + device = vk_renderer_get_device(renderer); + + /// FIXME: Right now, using any _SRGB format (for example VK_FORMAT_B8G8R8A8_SRGB) will not work because + /// that'll break some assertions inside flutter / skia. (VK_FORMAT_B8G8R8A8_SRGB maps to GrColorType::kRGBA_8888_SRGB, + /// but some other part of flutter will use GrColorType::kRGBA_8888 so you'll get a mismatch at some point) + /// We're just converting the _SRGB to a _UNORM here, but I'm not really sure that's guaranteed to work. + /// (_UNORM can mean anything basically) + + vk_format = get_pixfmt_info(pixel_format)->vk_format; + if (is_srgb_format(vk_format)) { + vk_format = srgb_to_unorm_format(vk_format); + } + + bo = gbm_bo_create(gbm_device, width, height, get_pixfmt_info(pixel_format)->gbm_format, GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); + if (bo == NULL) { + LOG_ERROR("Could not create GBM BO. gbm_bo_create: %s\n", strerror(errno)); + return EIO; + } + + ok = vkCreateImage( + device, + &(VkImageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = vk_format, + .extent = { .width = width, .height = height, .depth = 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + // Tell vulkan that the tiling we want to use is determined by a DRM format modifier + // (in our case DRM_FORMAT_MOD_LINEAR, but could be something else as well, using a device-supported + // modifier is probably faster if that's possible) + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + // These are the usage flags flutter will use too internally + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = 0, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .pNext = + &(VkExternalMemoryImageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .pNext = + &(VkImageDrmFormatModifierExplicitCreateInfoEXT){ + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifierPlaneCount = 1, + .drmFormatModifier = gbm_bo_get_modifier(bo), + .pPlaneLayouts = + (VkSubresourceLayout[1]){ + { + /// These are just dummy values, but they need to be there AFAIK + .offset = gbm_bo_get_offset(bo, 0), + .size = gbm_bo_get_stride_for_plane(bo, 0) * gbm_bo_get_height(bo) + gbm_bo_get_offset(bo, 0), + .rowPitch = gbm_bo_get_stride_for_plane(bo, 0), + .arrayPitch = 0, + .depthPitch = 0, + }, + }, + }, + }, + }, + NULL, + &vkimg + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create Vulkan image. vkCreateImage"); + goto fail_destroy_bo; + } + + // We _should_ only have one plane in the linear case. + // Query the layout of that plane to check if it matches the GBM BOs layout. + vkGetImageSubresourceLayout( + device, + vkimg, + &(VkImageSubresource){ + .aspectMask = VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, // For v3dv, this doesn't really matter + .mipLevel = 0, + .arrayLayer = 0, + }, + &layout + ); + + // Just some paranoid checks that the layout matches (had some issues with that initially) + if (gbm_bo_get_offset(bo, 0) != layout.offset) { + LOG_ERROR("GBM BO layout doesn't match image layout. This is probably a driver / kernel bug.\n"); + goto fail_destroy_image; + } + + if (gbm_bo_get_stride_for_plane(bo, 0) != layout.rowPitch) { + LOG_ERROR("GBM BO layout doesn't match image layout. This is probably a driver / kernel bug.\n"); + goto fail_destroy_image; + } + + // gbm_bo_get_fd will dup us a new dmabuf fd. + // So if we don't use it, we need to close it. + fd = gbm_bo_get_fd(bo); + if (fd < 0) { + LOG_ERROR("Couldn't get dmabuf fd for GBM buffer. gbm_bo_get_fd: %s\n", strerror(errno)); + goto fail_destroy_image; + } + + // find out as which memory types we can import our dmabuf fd + VkMemoryFdPropertiesKHR fd_memory_props = { + .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR, + .pNext = NULL, + .memoryTypeBits = 0, + }; + + get_memory_fd_props = (PFN_vkGetMemoryFdPropertiesKHR) vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR"); + if (get_memory_fd_props == NULL) { + LOG_ERROR("Couldn't resolve vkGetMemoryFdPropertiesKHR.\n"); + goto fail_close_fd; + } + + ok = get_memory_fd_props(device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, fd, &fd_memory_props); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't get dmabuf memory properties. vkGetMemoryFdPropertiesKHR"); + goto fail_close_fd; + } + + // Find out the memory requirements for our image (the supported memory types for import) + VkMemoryRequirements2 image_memory_reqs = { .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + .memoryRequirements = { 0 }, + .pNext = NULL }; + + vkGetImageMemoryRequirements2( + device, + &(VkImageMemoryRequirementsInfo2){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2, + .image = vkimg, + .pNext = NULL, + }, + &image_memory_reqs + ); + + // Find a memory type that fits both to the dmabuf and the image + int mem = vk_renderer_find_mem_type(renderer, 0, image_memory_reqs.memoryRequirements.memoryTypeBits & fd_memory_props.memoryTypeBits); + if (mem < 0) { + LOG_ERROR("Couldn't find a memory type that's both supported by the image and the dmabuffer.\n"); + goto fail_close_fd; + } + + // now, create a VkDeviceMemory instance from our dmabuf. + // after successful import, the fd is owned by the device memory object + // and we don't need to close it. + ok = vkAllocateMemory( + device, + &(VkMemoryAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = layout.size, + .memoryTypeIndex = mem, + .pNext = + &(VkImportMemoryFdInfoKHR){ + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = fd, + .pNext = + &(VkMemoryDedicatedAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .image = vkimg, + .buffer = VK_NULL_HANDLE, + .pNext = NULL, + }, + }, + }, + NULL, + &img_device_memory + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't import dmabuf as vulkan device memory. vkAllocateMemory"); + goto fail_close_fd; + } + + ok = vkBindImageMemory2( + device, + 1, + &(VkBindImageMemoryInfo){ + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, + .image = vkimg, + .memory = img_device_memory, + .memoryOffset = 0, + .pNext = NULL, + } + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't bind dmabuf-backed vulkan device memory to vulkan image. vkBindImageMemory2"); + goto fail_free_device_memory; + } + + fb->bo = bo; + fb->memory = img_device_memory; + fb->image = vkimg; + + COMPILE_ASSERT(sizeof(FlutterVulkanImage) == 24); + fb->fl_image = (FlutterVulkanImage){ + .struct_size = sizeof(FlutterVulkanImage), + .image = (FlutterVulkanImageHandle) fb->image, + .format = vk_format, + }; + + return 0; + +fail_free_device_memory: + vkFreeMemory(device, img_device_memory, NULL); + goto fail_destroy_bo; + +fail_close_fd: + close(fd); + +fail_destroy_image: + vkDestroyImage(device, vkimg, NULL); + +fail_destroy_bo: + gbm_bo_destroy(bo); + return EIO; +} + +static void fb_deinit(struct fb *fb, VkDevice device) { + ASSERT_NOT_NULL(fb); + + vkFreeMemory(device, fb->memory, NULL); + gbm_bo_destroy(fb->bo); + vkDestroyImage(device, fb->image, NULL); +} + +int vk_gbm_render_surface_init( + struct vk_gbm_render_surface *surface, + struct tracer *tracer, + struct vec2i size, + struct gbm_device *gbm_device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +) { + int ok; + + ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(surface), tracer, size); + if (ok != 0) { + return EIO; + } + + for (int i = 0; i < ARRAY_SIZE(surface->fbs); i++) { + ok = fb_init(surface->fbs + i, gbm_device, renderer, (int) size.x, (int) size.y, pixel_format); + if (ok != 0) { + LOG_ERROR("Could not initialize vulkan GBM framebuffer.\n"); + goto fail_deinit_previous_fbs; + } + + continue; + +fail_deinit_previous_fbs: + for (int j = 0; j < i; j++) { + fb_deinit(surface->fbs + j, vk_renderer_get_device(renderer)); + } + goto fail_deinit_render_surface; + } + + for (int i = 0; i < ARRAY_SIZE(surface->locked_fbs); i++) { + surface->locked_fbs[i].surface = NULL; + surface->locked_fbs[i].is_locked = (atomic_flag) ATOMIC_FLAG_INIT; + surface->locked_fbs[i].n_refs = REFCOUNT_INIT_0; + surface->locked_fbs[i].fb = surface->fbs + i; + } + + COMPILE_ASSERT(ARRAY_SIZE(surface->fbs) == ARRAY_SIZE(surface->locked_fbs)); + + surface->surface.present_kms = vk_gbm_render_surface_present_kms; + surface->surface.present_fbdev = vk_gbm_render_surface_present_fbdev; + surface->surface.deinit = vk_gbm_render_surface_deinit; + surface->render_surface.fill = vk_gbm_render_surface_fill; + surface->render_surface.queue_present = vk_gbm_render_surface_queue_present; + +#ifdef DEBUG + uuid_copy(&surface->uuid, uuid); +#endif + + surface->renderer = vk_renderer_ref(renderer); + surface->front_fb = NULL; + surface->pixel_format = pixel_format; +#ifdef DEBUG + surface->n_locked_fbs = 0; +#endif + return 0; + +fail_deinit_render_surface: + render_surface_deinit(CAST_SURFACE_UNCHECKED(surface)); + return EIO; +} + +struct vk_gbm_render_surface *vk_gbm_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +) { + struct vk_gbm_render_surface *surface; + int ok; + + surface = malloc(sizeof *surface); + if (surface == NULL) { + goto fail_return_null; + } + + ok = vk_gbm_render_surface_init(surface, tracer, size, device, renderer, pixel_format); + if (ok != 0) { + goto fail_free_surface; + } + + return surface; + +fail_free_surface: + free(surface); + +fail_return_null: + return NULL; +} + +void vk_gbm_render_surface_deinit(struct surface *s) { + struct vk_gbm_render_surface *vk_surface; + + vk_surface = CAST_THIS(s); + + for (int i = 0; i < ARRAY_SIZE(vk_surface->fbs); i++) { + fb_deinit(vk_surface->fbs + i, vk_renderer_get_device(vk_surface->renderer)); + } + + render_surface_deinit(s); +} + +struct gbm_bo_meta { + struct drmdev *drmdev; + uint32_t fb_id; +}; + +static void on_destroy_gbm_bo_meta(struct gbm_bo *bo, void *meta_void) { + struct gbm_bo_meta *meta; + int ok; + + ASSERT_NOT_NULL(bo); + ASSERT_NOT_NULL(meta_void); + meta = meta_void; + (void) bo; + + ok = drmdev_rm_fb(meta->drmdev, meta->fb_id); + if (ok != 0) { + LOG_ERROR("Couldn't remove DRM framebuffer.\n"); + } + + drmdev_unref(meta->drmdev); + free(meta); +} + +static void on_release_layer(void *userdata) { + struct vk_gbm_render_surface *surface; + struct locked_fb *fb; + + ASSERT_NOT_NULL(userdata); + + fb = userdata; + surface = CAST_THIS_UNCHECKED(surface_ref(CAST_SURFACE_UNCHECKED(fb->surface))); + + locked_fb_unref(fb); + + log_locked_fbs(surface, "release_layer"); + surface_unref(CAST_SURFACE_UNCHECKED(surface)); +} + +static int vk_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + struct vk_gbm_render_surface *vk_surface; + struct gbm_bo_meta *meta; + struct drmdev *drmdev; + struct gbm_bo *bo; + enum pixfmt pixel_format; + uint32_t fb_id; + int ok; + + vk_surface = CAST_THIS(s); + (void) props; + (void) builder; + + /// TODO: Implement non axis-aligned fl_layer_props + ASSERT_MSG(props->is_aa_rect, "only axis aligned view geometry is supported right now"); + + surface_lock(s); + + ASSERT_NOT_NULL_MSG( + vk_surface->front_fb, + "There's no framebuffer available for scanout right now. Make sure you called render_surface_queue_present() before presenting." + ); + + bo = vk_surface->front_fb->fb->bo; + meta = gbm_bo_get_user_data(bo); + if (meta == NULL) { + meta = malloc(sizeof *meta); + if (meta == NULL) { + ok = ENOMEM; + goto fail_unlock; + } + + drmdev = kms_req_builder_get_drmdev(builder); + ASSERT_NOT_NULL(drmdev); + + TRACER_BEGIN(vk_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); + fb_id = drmdev_add_fb( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + vk_surface->pixel_format, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + true, + gbm_bo_get_modifier(bo) + ); + TRACER_END(vk_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); + + if (fb_id == 0) { + ok = EIO; + LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); + goto fail_free_meta; + } + + meta->drmdev = drmdev_ref(drmdev); + meta->fb_id = fb_id; + gbm_bo_set_user_data(bo, meta, on_destroy_gbm_bo_meta); + } else { + // We can only add this GBM BO to a single KMS device as an fb right now. + ASSERT_EQUALS_MSG( + meta->drmdev, + kms_req_builder_get_drmdev(builder), + "Currently GBM BOs can only be scanned out on a single KMS device for their whole lifetime." + ); + } + + /* + LOG_DEBUG( + "vk_gbm_render_surface_present_kms:\n" + " src_x, src_y, src_w, src_h: %f %f %f %f\n" + " dst_x, dst_y, dst_w, dst_h: %f %f %f %f\n", + 0.0, 0.0, + s->render_surface.size.x, + s->render_surface.size.y, + props->aa_rect.offset.x, + props->aa_rect.offset.y, + props->aa_rect.size.x, + props->aa_rect.size.y + ); + */ + + // The bottom-most layer should preferably be an opaque layer. + // For example, on Pi 4, even though ARGB8888 is listed as supported for the primary plane, + // rendering is completely off. + // So we just cast our fb to an XRGB8888 framebuffer and scanout that instead. + fb_id = meta->fb_id; + pixel_format = vk_surface->pixel_format; + + vkDeviceWaitIdle(vk_renderer_get_device(vk_surface->renderer)); + + TRACER_BEGIN(vk_surface->surface.tracer, "kms_req_builder_push_fb_layer"); + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer){ + .drm_fb_id = fb_id, + .format = pixel_format, + .has_modifier = true, + .modifier = gbm_bo_get_modifier(bo), + + .dst_x = (int32_t) props->aa_rect.offset.x, + .dst_y = (int32_t) props->aa_rect.offset.y, + .dst_w = (uint32_t) props->aa_rect.size.x, + .dst_h = (uint32_t) props->aa_rect.size.y, + + .src_x = 0, + .src_y = 0, + .src_w = DOUBLE_TO_FP1616_ROUNDED(vk_surface->render_surface.size.x), + .src_h = DOUBLE_TO_FP1616_ROUNDED(vk_surface->render_surface.size.y), + + .has_rotation = false, + .rotation = PLANE_TRANSFORM_ROTATE_0, + + .has_in_fence_fd = false, + .in_fence_fd = 0, + }, + on_release_layer, + NULL, + locked_fb_ref(vk_surface->front_fb) + ); + TRACER_END(vk_surface->surface.tracer, "kms_req_builder_push_fb_layer"); + if (ok != 0) { + goto fail_unref_locked_fb; + } + + surface_unlock(s); + return ok; + +fail_unref_locked_fb: + locked_fb_unref(vk_surface->front_fb); + goto fail_unlock; + +fail_free_meta: + free(meta); + +fail_unlock: + surface_unlock(s); + return ok; +} + +static int +vk_gbm_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct vk_gbm_render_surface *render_surface; + + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev + /// TODO: Print a warning here if we're not using explicit linear tiling and transition to linear layout with vulkan instead of gbm_bo_map in that case + + render_surface = CAST_THIS(s); + (void) render_surface; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + +static int vk_gbm_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { + struct vk_gbm_render_surface *render_surface; + int i, ok; + + render_surface = CAST_THIS(s); + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + // Try to find & lock a locked_fb we can use. + // Note we use atomics here even though we hold the surfaces' mutex because + // releasing a locked_fb is possibly done without the mutex. + for (i = 0; i < ARRAY_SIZE(render_surface->locked_fbs); i++) { + if (atomic_flag_test_and_set(&render_surface->locked_fbs[i].is_locked) == false) { + goto locked; + } + } + + // If we reached this point, we couldn't lock one of the 4 locked_fbs. + // Which shouldn't happen except we have an application bug. + ASSERT_MSG(false, "Couldn't find a free slot to lock the surfaces front framebuffer."); + ok = EIO; + goto fail_unlock; + +locked:; +/// TODO: Remove this once we're using triple buffering +#ifdef DEBUG + atomic_fetch_add(&render_surface->n_locked_fbs, 1); + log_locked_fbs(CAST_THIS_UNCHECKED(s), "fill"); + //ASSERT_MSG(before + 1 <= 3, "sanity check failed: too many locked fbs for double-buffered vsync"); +#endif + render_surface->locked_fbs[i].surface = CAST_VK_GBM_RENDER_SURFACE(surface_ref(CAST_SURFACE_UNCHECKED(s))); + render_surface->locked_fbs[i].n_refs = REFCOUNT_INIT_1; + + COMPILE_ASSERT(sizeof(FlutterVulkanBackingStore) == 16 || sizeof(FlutterVulkanBackingStore) == 32); + fl_store->type = kFlutterBackingStoreTypeVulkan; + fl_store->vulkan = (FlutterVulkanBackingStore){ + .struct_size = sizeof(FlutterVulkanBackingStore), + .image = &render_surface->locked_fbs[i].fb->fl_image, + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(render_surface)), + .destruction_callback = surface_unref_void, + }; + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + + return 0; + +fail_unlock: + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return ok; +} + +static int vk_gbm_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { + struct vk_gbm_render_surface *vk_surface; + struct locked_fb *fb; + + vk_surface = CAST_THIS(s); + + ASSERT_EQUALS(fl_store->type, kFlutterBackingStoreTypeVulkan); + /// TODO: Implement handling if fl_store->did_update == false + + surface_lock(CAST_SURFACE_UNCHECKED(s)); + + // find out which fb this image belongs too + fb = NULL; + for (int i = 0; i < ARRAY_SIZE(vk_surface->locked_fbs); i++) { + if (vk_surface->locked_fbs[i].fb->fl_image.image == fl_store->vulkan.image->image) { + fb = vk_surface->locked_fbs + i; + break; + } + } + + if (fb == NULL) { + LOG_ERROR("The vulkan image flutter wants to present is not known to this render surface.\n"); + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return EINVAL; + } + + // Replace the front fb with the new one + // (will unref the old one if not NULL internally) + locked_fb_swap_ptrs(&vk_surface->front_fb, fb); + + // Since flutter no longer uses this fb for rendering, we need to unref it + locked_fb_unref(fb); + + log_locked_fbs(vk_surface, "queue_present"); + + surface_unlock(CAST_SURFACE_UNCHECKED(s)); + return 0; +} diff --git a/src/vk_gbm_render_surface.h b/src/vk_gbm_render_surface.h new file mode 100644 index 00000000..03d67250 --- /dev/null +++ b/src/vk_gbm_render_surface.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan GBM render surface + * + * - used as a render target for flutter vulkan rendering + * - can be scanned out using KMS + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H +#define _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H + +#include "compositor_ng.h" +#include "pixel_format.h" +#include "util/collection.h" + +struct tracer; +struct gbm_device; +struct vk_gbm_render_surface; + +#define CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr) ((struct vk_gbm_render_surface *) (ptr)) +#ifdef DEBUG + #define CAST_VK_GBM_RENDER_SURFACE(ptr) __checked_cast_vk_gbm_render_surface(ptr) +ATTR_PURE struct vk_gbm_render_surface *__checked_cast_vk_gbm_render_surface(void *ptr); +#else + #define CAST_VK_GBM_RENDER_SURFACE(ptr) CAST_VK_GBM_RENDER_SURFACE_UNCHECKED(ptr) +#endif + +struct vk_gbm_render_surface *vk_gbm_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gbm_device *device, + struct vk_renderer *renderer, + enum pixfmt pixel_format +); + +#endif // _FLUTTERPI_SRC_VK_GBM_RENDER_SURFACE_H diff --git a/src/vk_renderer.c b/src/vk_renderer.c new file mode 100644 index 00000000..4a96b810 --- /dev/null +++ b/src/vk_renderer.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan Renderer Implementation + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "vk_renderer.h" + +#include + +#include +#include + +#include "util/asserts.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation" + +UNUSED static VkLayerProperties *get_layer_props(int n_layers, VkLayerProperties *layers, const char *layer_name) { + for (int i = 0; i < n_layers; i++) { + if (streq(layers[i].layerName, layer_name)) { + return layers + i; + } + } + return NULL; +} + +UNUSED static bool supports_layer(int n_layers, VkLayerProperties *layers, const char *layer_name) { + return get_layer_props(n_layers, layers, layer_name) != NULL; +} + +static VkExtensionProperties *get_extension_props(int n_extensions, VkExtensionProperties *extensions, const char *extension_name) { + for (int i = 0; i < n_extensions; i++) { + if (streq(extensions[i].extensionName, extension_name)) { + return extensions + i; + } + } + return NULL; +} + +UNUSED static bool supports_extension(int n_extensions, VkExtensionProperties *extensions, const char *extension_name) { + return get_extension_props(n_extensions, extensions, extension_name) != NULL; +} + +static VkBool32 on_debug_utils_message( + UNUSED VkDebugUtilsMessageSeverityFlagBitsEXT severity, + UNUSED VkDebugUtilsMessageTypeFlagsEXT types, + UNUSED const VkDebugUtilsMessengerCallbackDataEXT *data, + UNUSED void *userdata +) { + LOG_DEBUG( + "[%s] (%d, %s) %s (queues: %d, cmdbufs: %d, objects: %d)\n", + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT ? "VERBOSE" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT ? "INFO" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ? "WARNING" : + severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT ? "ERROR" : + "unknown severity", + data->messageIdNumber, + data->pMessageIdName, + data->pMessage, + data->queueLabelCount, + data->cmdBufLabelCount, + data->objectCount + ); + return VK_TRUE; +} + +static int get_graphics_queue_family_index(VkPhysicalDevice device) { + uint32_t n_queue_families; + + vkGetPhysicalDeviceQueueFamilyProperties(device, &n_queue_families, NULL); + + VkQueueFamilyProperties queue_families[n_queue_families]; + vkGetPhysicalDeviceQueueFamilyProperties(device, &n_queue_families, queue_families); + + for (unsigned i = 0; i < n_queue_families; i++) { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + return i; + } + } + + return -1; +} + +static int score_physical_device(VkPhysicalDevice device, const char **required_device_extensions) { + VkPhysicalDeviceProperties props; + VkPhysicalDeviceFeatures features; + VkResult ok; + uint32_t n_available_extensions; + int graphics_queue_fam_index; + int score = 1; + + vkGetPhysicalDeviceProperties(device, &props); + vkGetPhysicalDeviceFeatures(device, &features); + + if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 15; + } else if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { + score += 10; + } + + graphics_queue_fam_index = get_graphics_queue_family_index(device); + if (graphics_queue_fam_index == -1) { + LOG_ERROR("Physical device does not support a graphics queue.\n"); + return 0; + } + + ok = vkEnumerateDeviceExtensionProperties(device, NULL, &n_available_extensions, NULL); + if (ok != 0) { + LOG_VK_ERROR(ok, "Could not query available physical device extensions. vkEnumerateDeviceExtensionProperties"); + return 0; + } + + VkExtensionProperties available_extensions[n_available_extensions]; + ok = vkEnumerateDeviceExtensionProperties(device, NULL, &n_available_extensions, available_extensions); + if (ok != 0) { + LOG_VK_ERROR(ok, "Could not query available physical device extensions. vkEnumerateDeviceExtensionProperties"); + return 0; + } + + for (const char **cursor = required_device_extensions; *cursor != NULL; cursor++) { + for (unsigned i = 0; i < n_available_extensions; i++) { + if (streq(available_extensions[i].extensionName, *cursor)) { + goto found; + } + } + LOG_ERROR("Required extension %s is not supported by vulkan device.\n", *cursor); + return 0; + +found: + continue; + } + + return score; +} + +struct vk_renderer { + refcount_t n_refs; + + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue graphics_queue; + VkDebugUtilsMessengerEXT debug_utils_messenger; + VkCommandPool graphics_cmd_pool; + + PFN_vkCreateDebugUtilsMessengerEXT create_debug_utils_messenger; + PFN_vkDestroyDebugUtilsMessengerEXT destroy_debug_utils_messenger; + + int n_enabled_layers; + const char **enabled_layers; + + int n_enabled_instance_extensions; + const char **enabled_instance_extensions; + + int n_enabled_device_extensions; + const char **enabled_device_extensions; +}; + +MUST_CHECK struct vk_renderer *vk_renderer_new() { + PFN_vkDestroyDebugUtilsMessengerEXT destroy_debug_utils_messenger; + PFN_vkCreateDebugUtilsMessengerEXT create_debug_utils_messenger; + VkDebugUtilsMessengerEXT debug_utils_messenger; + struct vk_renderer *renderer; + VkPhysicalDevice physical_device; + VkCommandPool graphics_cmd_pool; + VkInstance instance; + VkDevice device; + VkResult ok; + VkQueue graphics_queue; + uint32_t n_available_layers, n_available_instance_extensions, n_available_device_extensions, n_physical_devices; + const char **enabled_layers, **enabled_instance_extensions, **enabled_device_extensions; + bool enable_debug_utils_messenger; + int n_enabled_layers, n_enabled_instance_extensions, n_enabled_device_extensions; + int graphics_queue_family_index; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) { + return NULL; + } + + ok = vkEnumerateInstanceLayerProperties(&n_available_layers, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance layers. vkEnumerateInstanceLayerProperties"); + goto fail_free_renderer; + } + + VkLayerProperties *available_layers = alloca(sizeof(VkLayerProperties) * n_available_layers); + ok = vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance layers. vkEnumerateInstanceLayerProperties"); + goto fail_free_renderer; + } + + n_enabled_layers = 0; + enabled_layers = malloc(n_available_layers * sizeof(*enabled_layers)); + if (enabled_layers == NULL) { + goto fail_free_renderer; + } + +#ifdef VULKAN_DEBUG + if (supports_layer(n_available_layers, available_layers, VALIDATION_LAYER_NAME)) { + enabled_layers[n_enabled_layers] = VALIDATION_LAYER_NAME; + n_enabled_layers++; + } else { + LOG_DEBUG("Vulkan validation layer was not found. Validation will not be enabled.\n"); + } +#endif + + ok = vkEnumerateInstanceExtensionProperties(NULL, &n_available_instance_extensions, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance extensions. vkEnumerateInstanceExtensionProperties"); + goto fail_free_enabled_layers; + } + + VkExtensionProperties *available_instance_extensions = alloca(sizeof(VkExtensionProperties) * n_available_instance_extensions); + ok = vkEnumerateInstanceExtensionProperties(NULL, &n_available_instance_extensions, available_instance_extensions); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query vulkan instance extensions. vkEnumerateInstanceExtensionProperties"); + goto fail_free_enabled_layers; + } + + n_enabled_instance_extensions = 0; + enabled_instance_extensions = malloc(n_available_instance_extensions * sizeof *enabled_instance_extensions); + if (enabled_instance_extensions == NULL) { + goto fail_free_enabled_layers; + } + + enable_debug_utils_messenger = false; +#ifdef VULKAN_DEBUG + if (supports_extension(n_available_instance_extensions, available_instance_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + enabled_instance_extensions[n_enabled_instance_extensions] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + n_enabled_instance_extensions++; + enable_debug_utils_messenger = true; + } else { + LOG_DEBUG("Vulkan debug utils extension was not found. Debug logging will not be enabled.\n"); + } +#endif + + /// TODO: Maybe enable some other useful instance extensions here? + + ok = vkCreateInstance( + &(VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .flags = 0, + .pApplicationInfo = + &(VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "flutter-pi", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "flutter-pi", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_MAKE_VERSION(1, 1, 0), + .pNext = NULL, + }, + .enabledLayerCount = n_enabled_layers, + .ppEnabledLayerNames = enabled_layers, + .enabledExtensionCount = n_enabled_instance_extensions, + .ppEnabledExtensionNames = enabled_instance_extensions, + .pNext = + enable_debug_utils_messenger ? + &(VkDebugUtilsMessengerCreateInfoEXT){ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = on_debug_utils_message, + .pUserData = NULL, + .pNext = NULL, + } : + NULL, + }, + NULL, + &instance + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create instance. vkCreateInstance"); + goto fail_free_enabled_instance_extensions; + } + + if (enable_debug_utils_messenger) { + create_debug_utils_messenger = (PFN_vkCreateDebugUtilsMessengerEXT + ) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (create_debug_utils_messenger == NULL) { + LOG_ERROR("Could not resolve vkCreateDebugUtilsMessengerEXT function.\n"); + goto fail_destroy_instance; + } + + destroy_debug_utils_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT + ) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (destroy_debug_utils_messenger == NULL) { + LOG_ERROR("Could not resolve vkDestroyDebugUtilsMessengerEXT function.\n"); + goto fail_destroy_instance; + } + + ok = create_debug_utils_messenger( + instance, + &(VkDebugUtilsMessengerCreateInfoEXT){ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = on_debug_utils_message, + .pUserData = NULL, + .pNext = NULL, + }, + NULL, + &debug_utils_messenger + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create debug utils messenger. vkCreateDebugUtilsMessengerEXT"); + goto fail_destroy_instance; + } + } else { + debug_utils_messenger = VK_NULL_HANDLE; + create_debug_utils_messenger = NULL; + destroy_debug_utils_messenger = NULL; + } + + ok = vkEnumeratePhysicalDevices(instance, &n_physical_devices, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not enumerate physical devices. vkEnumeratePhysicalDevices"); + goto fail_maybe_destroy_messenger; + } + + VkPhysicalDevice *physical_devices = alloca(sizeof(VkPhysicalDevice) * n_physical_devices); + ok = vkEnumeratePhysicalDevices(instance, &n_physical_devices, physical_devices); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not enumerate physical devices. vkEnumeratePhysicalDevices"); + goto fail_maybe_destroy_messenger; + } + + static const char *required_device_extensions[] = { + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, + VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, NULL + }; + + physical_device = VK_NULL_HANDLE; + int score = 0; + for (unsigned i = 0; i < n_physical_devices; i++) { + VkPhysicalDevice this = physical_devices[i]; + int this_score = score_physical_device(this, required_device_extensions); + + if (this_score > score) { + physical_device = this; + score = this_score; + } + } + + if (physical_device == VK_NULL_HANDLE) { + LOG_ERROR("No suitable physical device found.\n"); + goto fail_maybe_destroy_messenger; + } + + ok = vkEnumerateDeviceExtensionProperties(physical_device, NULL, &n_available_device_extensions, NULL); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query device extensions. vkEnumerateDeviceExtensionProperties"); + goto fail_maybe_destroy_messenger; + } + + VkExtensionProperties *available_device_extensions = alloca(sizeof(VkExtensionProperties) * n_available_device_extensions); + ok = vkEnumerateDeviceExtensionProperties(physical_device, NULL, &n_available_device_extensions, available_device_extensions); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not query device extensions. vkEnumerateDeviceExtensionProperties"); + goto fail_maybe_destroy_messenger; + } + + n_enabled_device_extensions = 0; + enabled_device_extensions = malloc(n_available_device_extensions * sizeof *enabled_device_extensions); + if (enabled_device_extensions == NULL) { + goto fail_maybe_destroy_messenger; + } + + // add all the required extensions to the list of enabled device extensions + for (const char **cursor = required_device_extensions; cursor != NULL && *cursor != NULL; cursor++) { + enabled_device_extensions[n_enabled_device_extensions] = *cursor; + n_enabled_device_extensions++; + } + + /// TODO: Maybe enable some other useful device extensions here? + + graphics_queue_family_index = get_graphics_queue_family_index(physical_device); + + ok = vkCreateDevice( + physical_device, + &(const VkDeviceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .flags = 0, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = + (const VkDeviceQueueCreateInfo[1]){ + { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .flags = 0, + .queueFamilyIndex = graphics_queue_family_index, + .queueCount = 1, + .pQueuePriorities = (float[1]){ 1.0f }, + .pNext = NULL, + }, + }, + .enabledLayerCount = n_enabled_layers, + .ppEnabledLayerNames = enabled_layers, + .enabledExtensionCount = n_enabled_device_extensions, + .ppEnabledExtensionNames = enabled_device_extensions, + .pEnabledFeatures = &(const VkPhysicalDeviceFeatures){ 0 }, + .pNext = NULL, + }, + NULL, + &device + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create logical device. vkCreateDevice"); + goto fail_free_enabled_device_extensions; + } + + vkGetDeviceQueue(device, graphics_queue_family_index, 0, &graphics_queue); + + ok = vkCreateCommandPool( + device, + &(const VkCommandPoolCreateInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = graphics_queue_family_index, + .pNext = NULL, + }, + NULL, + &graphics_cmd_pool + ); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Could not create command pool for allocating graphics command buffers. vkCreateCommandPool"); + goto fail_destroy_device; + } + + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "Vulkan Info:\n" + " enabled layers: " + ); + + for (int i = 0; i < n_enabled_layers; i++) { + LOG_DEBUG_UNPREFIXED("%s%s", enabled_layers[i], (i + 1 != n_enabled_layers) ? ", " : ""); + } + + LOG_DEBUG_UNPREFIXED( + "\n" + " enabled instance extensions: " + ); + + for (int i = 0; i < n_enabled_instance_extensions; i++) { + LOG_DEBUG_UNPREFIXED("%s%s", enabled_instance_extensions[i], (i + 1 != n_enabled_instance_extensions) ? ", " : ""); + } + + LOG_DEBUG_UNPREFIXED( + "\n" + " enabled device extensions: " + ); + + for (int i = 0; i < n_enabled_device_extensions; i++) { + LOG_DEBUG_UNPREFIXED("%s%s", enabled_device_extensions[i], (i + 1 != n_enabled_device_extensions) ? ", " : ""); + } + + LOG_DEBUG_UNPREFIXED( + "\n" + "===================================\n" + ); + + renderer->device = device; + renderer->physical_device = physical_device; + renderer->instance = instance; + renderer->graphics_queue = graphics_queue; + renderer->debug_utils_messenger = debug_utils_messenger; + renderer->graphics_cmd_pool = graphics_cmd_pool; + renderer->create_debug_utils_messenger = create_debug_utils_messenger; + renderer->destroy_debug_utils_messenger = destroy_debug_utils_messenger; + renderer->n_enabled_layers = n_enabled_layers; + renderer->enabled_layers = enabled_layers; + renderer->n_enabled_instance_extensions = n_enabled_instance_extensions; + renderer->enabled_instance_extensions = enabled_instance_extensions; + renderer->n_enabled_device_extensions = n_enabled_device_extensions; + renderer->enabled_device_extensions = enabled_device_extensions; + return renderer; + +fail_destroy_device: + vkDestroyDevice(device, NULL); + +fail_free_enabled_device_extensions: + free(enabled_device_extensions); + +fail_maybe_destroy_messenger: + if (debug_utils_messenger != VK_NULL_HANDLE) { + destroy_debug_utils_messenger(instance, debug_utils_messenger, NULL); + } + +fail_destroy_instance: + vkDestroyInstance(instance, NULL); + +fail_free_enabled_instance_extensions: + free(enabled_instance_extensions); + +fail_free_enabled_layers: + free(enabled_layers); + +fail_free_renderer: + free(renderer); + return NULL; +} + +void vk_renderer_destroy(struct vk_renderer *renderer) { + VkResult ok; + + ok = vkDeviceWaitIdle(renderer->device); + if (ok != VK_SUCCESS) { + LOG_VK_ERROR(ok, "Couldn't wait for vulkan device idle to destroy it. vkDeviceWaitIdle"); + } + + vkDestroyCommandPool(renderer->device, renderer->graphics_cmd_pool, NULL); + vkDestroyDevice(renderer->device, NULL); + free(renderer->enabled_device_extensions); + if (renderer->debug_utils_messenger != VK_NULL_HANDLE) { + renderer->destroy_debug_utils_messenger(renderer->instance, renderer->debug_utils_messenger, NULL); + } + vkDestroyInstance(renderer->instance, NULL); + free(renderer->enabled_instance_extensions); + free(renderer->enabled_layers); + free(renderer); +} + +DEFINE_REF_OPS(vk_renderer, n_refs) + +ATTR_CONST uint32_t vk_renderer_get_vk_version(ASSERTED struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return VK_MAKE_VERSION(1, 1, 0); +} + +ATTR_PURE VkInstance vk_renderer_get_instance(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->instance; +} + +ATTR_PURE VkPhysicalDevice vk_renderer_get_physical_device(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->physical_device; +} + +ATTR_PURE VkDevice vk_renderer_get_device(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->device; +} + +ATTR_PURE uint32_t vk_renderer_get_queue_family_index(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return (uint32_t) get_graphics_queue_family_index(renderer->physical_device); +} + +ATTR_PURE VkQueue vk_renderer_get_queue(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->graphics_queue; +} + +ATTR_PURE int vk_renderer_get_enabled_instance_extension_count(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->n_enabled_instance_extensions; +} + +ATTR_PURE const char **vk_renderer_get_enabled_instance_extensions(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->enabled_instance_extensions; +} + +ATTR_PURE int vk_renderer_get_enabled_device_extension_count(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->n_enabled_device_extensions; +} + +ATTR_PURE const char **vk_renderer_get_enabled_device_extensions(struct vk_renderer *renderer) { + ASSERT_NOT_NULL(renderer); + return renderer->enabled_device_extensions; +} + +ATTR_PURE int vk_renderer_find_mem_type(struct vk_renderer *renderer, VkMemoryPropertyFlags flags, uint32_t req_bits) { + VkPhysicalDeviceMemoryProperties props; + + vkGetPhysicalDeviceMemoryProperties(renderer->physical_device, &props); + + for (unsigned i = 0u; i < props.memoryTypeCount; ++i) { + if (req_bits & (1 << i)) { + if ((props.memoryTypes[i].propertyFlags & flags) == flags) { + return i; + } + } + } + + return -1; +} diff --git a/src/vk_renderer.h b/src/vk_renderer.h new file mode 100644 index 00000000..6e25b145 --- /dev/null +++ b/src/vk_renderer.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +/* + * Vulkan Renderer + * + * - provides a vulkan renderer object + * - a vulkan renderer object is basically a combination of: + * - a vulkan instance (VkInstance) + * - a vulkan physical device (VkPhysicalDevice) + * - a vulkan logical device (VkDevice) + * - a vulkan graphics queue (VkQueue) + * - a vulkan command buffer pool (VkCommandPool or something) + * - and utilities for using those + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_VK_RENDERER_H +#define _FLUTTERPI_SRC_VK_RENDERER_H + +#include "util/collection.h" +#include "util/refcounting.h" + +#include "config.h" + +#ifndef HAVE_VULKAN + #error "vk_renderer.h was included but Vulkan support is disabled." +#endif + +#include "vulkan.h" + +struct vk_renderer; + +/** + * @brief Create a new vulkan renderer with some reasonable defaults. + * + * Creates a vulkan instance with: + * - app name `flutter-pi`, version 1.0.0 + * - engine `flutter-pi`, version 1.0.0 + * - vulkan version 1.1.0 + * - khronos validation layers and debug utils enabled, if supported and VULKAN_DEBUG is defined + * + * Selects a good physical device (dedicated GPU > integrated GPU > software) that has a + * graphics queue family and supports the following device extensions: + * - `VK_KHR_external_memory` + * - `VK_KHR_external_memory_fd` + * - `VK_KHR_external_semaphore` + * - `VK_KHR_external_semaphore_fd` + * - `VK_EXT_external_memory_dma_buf` + * - `VK_KHR_image_format_list` + * - `VK_EXT_image_drm_format_modifier` + * + * Those extensions will also be enabled when create the logical device of course. + * + * Will also create a graphics queue. + * + * @return New vulkan renderer instance. + */ +struct vk_renderer *vk_renderer_new(); + +void vk_renderer_destroy(struct vk_renderer *renderer); + +DECLARE_REF_OPS(vk_renderer) + +/** + * @brief Get the vulkan version of this renderer. This is unconditionally VK_MAKE_VERSION(1, 1, 0) for now. + * + * @param renderer renderer instance + * @return VK_MAKE_VERSION(1, 1, 0) + */ +ATTR_CONST uint32_t vk_renderer_get_vk_version(struct vk_renderer *renderer); + +/** + * @brief Get the vulkan instance of this renderer. See @ref vk_renderer_new for details on this instance. + * + * @param renderer renderer instance + * @return vulkan instance + */ +ATTR_PURE VkInstance vk_renderer_get_instance(struct vk_renderer *renderer); + +/** + * @brief Get the physical device that's used by this renderer. See @ref vk_renderer_new for details. + * + * @param renderer renderer instance + * @return vulkan physical device + */ +ATTR_PURE VkPhysicalDevice vk_renderer_get_physical_device(struct vk_renderer *renderer); + +/** + * @brief Get the logical device that's used by this renderer. See @ref vk_renderer_new for details. + * + * @param renderer renderer instance + * @return vulkan logical device + */ +ATTR_PURE VkDevice vk_renderer_get_device(struct vk_renderer *renderer); + +/** + * @brief Get the index of the graphics queue family. + * + * @param renderer renderer instance + * @return instance of the graphics queue family. + */ +ATTR_PURE uint32_t vk_renderer_get_queue_family_index(struct vk_renderer *renderer); + +/** + * @brief Get the graphics queue of this renderer. + * + * @param renderer renderer instance + * @return graphics queue + */ +ATTR_PURE VkQueue vk_renderer_get_queue(struct vk_renderer *renderer); + +ATTR_PURE int vk_renderer_get_enabled_instance_extension_count(struct vk_renderer *renderer); + +ATTR_PURE const char **vk_renderer_get_enabled_instance_extensions(struct vk_renderer *renderer); + +ATTR_PURE int vk_renderer_get_enabled_device_extension_count(struct vk_renderer *renderer); + +ATTR_PURE const char **vk_renderer_get_enabled_device_extensions(struct vk_renderer *renderer); + +/** + * @brief Find the index of a memory type for which the following conditions are true: + * - (1 < 32) & @param req_bits is not 0 + * - the memory types property flags support the flags that are given in @param flags + * + * @param renderer renderer instance + * @param flags Which property flags the memory type should support. + * @param req_bits Which memory types are allowed to choose from. + * @return index of the found memory type or -1 if none was found. + */ +ATTR_PURE int vk_renderer_find_mem_type(struct vk_renderer *renderer, VkMemoryPropertyFlags flags, uint32_t req_bits); + +#endif // _FLUTTERPI_SRC_VK_RENDERER_H diff --git a/src/vulkan.h b/src/vulkan.h new file mode 100644 index 00000000..3821805a --- /dev/null +++ b/src/vulkan.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +/* + * Just a shim for including vulkan headers, and disabling vulkan function prototypes if vulkan is not present + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_VULKAN_H +#define _FLUTTERPI_SRC_VULKAN_H + +#include "config.h" + +#ifndef HAVE_VULKAN + #error "vulkan.h was included but Vulkan support is disabled." +#endif + +#include + +static inline const char *vk_strerror(VkResult result) { + switch (result) { + case VK_SUCCESS: return "VK_SUCCESS"; + case VK_NOT_READY: return "VK_NOT_READY"; + case VK_TIMEOUT: return "VK_TIMEOUT"; + case VK_EVENT_SET: return "VK_EVENT_SET"; + case VK_EVENT_RESET: return "VK_EVENT_RESET"; + case VK_INCOMPLETE: return "VK_INCOMPLETE"; + case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; + case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; + case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; +#if VK_HEADER_VERSION >= 131 + case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; +#endif + case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY"; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; +#if VK_HEADER_VERSION >= 131 + case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; +#endif +#if VK_HEADER_VERSION >= 204 + case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; +#endif + case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR"; + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR"; + case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; + case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; + case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT"; + case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV"; +#if VK_HEADER_VERSION >= 218 && VK_ENABLE_BETA_EXTENSIONS + case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; +#endif +#if VK_HEADER_VERSION >= 89 + case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; +#endif +#if VK_HEADER_VERSION >= 204 + case VK_ERROR_NOT_PERMITTED_KHR: return "VK_ERROR_NOT_PERMITTED_KHR"; +#endif +#if VK_HEADER_VERSION >= 105 + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; +#endif +#if VK_HEADER_VERSION >= 135 + case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR"; + case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR"; + case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR"; + case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR"; +#endif +#if VK_HEADER_VERSION >= 213 + case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; +#endif + default: return ""; + } +} + +#define LOG_VK_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ vk_strerror(result)) + +#endif // _FLUTTERPI_SRC_VULKAN_H diff --git a/src/window.c b/src/window.c new file mode 100644 index 00000000..e23addb9 --- /dev/null +++ b/src/window.c @@ -0,0 +1,1576 @@ +// SPDX-License-Identifier: MIT +/* + * window object + * + * - a window is something where flutter graphics can be presented on. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#define _GNU_SOURCE +#include "window.h" + +#include +#include + +#include + +#include + +#include "compositor_ng.h" +#include "cursor.h" +#include "flutter-pi.h" +#include "frame_scheduler.h" +#include "modesetting.h" +#include "render_surface.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +#include "config.h" + +#ifdef HAVE_EGL_GLES2 + #include "egl_gbm_render_surface.h" + #include "gl_renderer.h" +#endif + +#ifdef HAVE_VULKAN + #include "vk_gbm_render_surface.h" + #include "vk_renderer.h" +#endif + +struct window { + pthread_mutex_t lock; + refcount_t n_refs; + + /** + * @brief Event tracing interface. + * + * Used to report timing information to the dart observatory. + * + */ + struct tracer *tracer; + + /** + * @brief Manages the frame scheduling for this window. + * + */ + struct frame_scheduler *frame_scheduler; + + /** + * @brief Refresh rate of the selected video mode / display. + * + */ + double refresh_rate; + + /** + * @brief Flutter device pixel ratio (in the horizontal axis). Number of physical pixels per logical pixel. + * + * There are always 38 logical pixels per cm, or 96 per inch. This is roughly equivalent to DPI / 100. + * A device pixel ratio of 1.0 is roughly a dpi of 96, which is the most common dpi for full-hd desktop displays. + * To calculate this, the physical dimensions of the display are required. If there are no physical dimensions, + * this will default to 1.0. + */ + double pixel_ratio; + + /** + * @brief Whether we have physical screen dimensions and @ref width_mm and @ref height_mm contain usable values. + * + */ + bool has_dimensions; + + /** + * @brief Width, height of the display in millimeters. + * + */ + int width_mm, height_mm; + + /** + * @brief The size of the view, as reported to flutter, in pixels. + * + * If no rotation and scaling is applied, this probably equals the display size. + * For example, if flutter-pi should render at 1/2 the resolution of a full-hd display, this would be + * 960x540 and the display size 1920x1080. + */ + struct vec2f view_size; + + /** + * @brief The actual size of the view on the display, pixels. + * + */ + struct vec2f display_size; + + /** + * @brief The rotation we should apply to the flutter layers to present them on screen. + */ + drm_plane_transform_t rotation; + + /** + * @brief The current device orientation and the original (startup) device orientation. + * + * @ref original_orientation is kLandscapeLeft for displays that are more wide than high, and kPortraitUp for displays that are + * more high than wide. Though this can also be anything else theoretically, if the user specifies weird combinations of rotation + * and orientation via cmdline arguments. + * + * @ref orientation should always equal to rotating @ref original_orientation clock-wise by the angle in the @ref rotation field. + */ + enum device_orientation orientation, original_orientation; + + /** + * @brief Matrix for transforming display coordinates to view coordinates. + * + * For example for transforming pointer events (which are in the display coordinate space) to flutter coordinates. + * Useful if for example flutter has specified a custom device orientation (for example kPortraitDown), in that case we of course + * also need to transform the touch coords. + * + */ + struct mat3f display_to_view_transform; + + /** + * @brief Matrix for transforming view coordinates to display coordinates. + * + * Can be used as a root surface transform, for fitting the flutter view into the desired display frame. + * + * Useful if for example flutter has specified a custom device orientation (for example kPortraitDown), + * because we need to rotate the flutter view in that case. + */ + struct mat3f view_to_display_transform; + + /** + * @brief True if we should use a specific pixel format. + * + */ + bool has_forced_pixel_format; + + /** + * @brief The forced pixel format if @ref has_forced_pixel_format is true. + * + */ + enum pixfmt forced_pixel_format; + + /** + * @brief The current flutter layer composition that should be output on screen. + * + */ + struct fl_layer_composition *composition; + + /** + * @brief KMS-specific fields if this is a KMS window. + * + */ + struct { + struct drmdev *drmdev; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + drmModeModeInfo *mode; + + bool should_apply_mode; + + const struct pointer_icon *pointer_icon; + struct cursor_buffer *cursor; + } kms; + + /** + * @brief The type of rendering that should be used. (gl, vk) + * + */ + enum renderer_type renderer_type; + + /** + * @brief The OpenGL renderer if OpenGL rendering should be used. + * + */ + struct gl_renderer *gl_renderer; + + /** + * @brief The Vulkan renderer if Vulkan rendering should be used. + * + */ + struct vk_renderer *vk_renderer; + + /** + * @brief Our main render surface, if we have one yet. + * + * Otherwise a new one should be created using the render surface interface. + * + */ + struct render_surface *render_surface; + +#ifdef HAVE_EGL_GLES2 + /** + * @brief The EGLSurface of this window, if any. + * + * Should be EGL_NO_SURFACE if this window is not associated with any EGL surface. + * This is really just a workaround because flutter doesn't support arbitrary EGL surfaces as render targets right now. + * (Just one global EGLSurface) + * + */ + EGLSurface egl_surface; +#endif + + /** + * @brief Whether this window currently shows a mouse cursor. + * + */ + bool cursor_enabled; + + /** + * @brief The position of the mouse cursor. + * + */ + struct vec2i cursor_pos; + + int (*push_composition)(struct window *window, struct fl_layer_composition *composition); + struct render_surface *(*get_render_surface)(struct window *window, struct vec2i size); + +#ifdef HAVE_EGL_GLES2 + bool (*has_egl_surface)(struct window *window); + EGLSurface (*get_egl_surface)(struct window *window); +#endif + + int (*set_cursor_locked + )(struct window *window, bool has_enabled, bool enabled, bool has_kind, enum pointer_kind kind, bool has_pos, struct vec2i pos); + void (*deinit)(struct window *window); +}; + +void window_destroy(struct window *window); + +DEFINE_STATIC_LOCK_OPS(window, lock) +DEFINE_REF_OPS(window, n_refs) + +static void fill_view_matrices( + drm_plane_transform_t transform, + int display_width, + int display_height, + struct mat3f *display_to_view_transform_out, + struct mat3f *view_to_display_transform_out +) { + assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(transform)); + + if (transform.rotate_0) { + *view_to_display_transform_out = MAT3F_TRANSLATION(0, 0); + + *display_to_view_transform_out = MAT3F_TRANSLATION(0, 0); + } else if (transform.rotate_90) { + *view_to_display_transform_out = MAT3F_ROTZ(90); + view_to_display_transform_out->transX = display_width; + + *display_to_view_transform_out = MAT3F_ROTZ(-90); + display_to_view_transform_out->transY = display_width; + } else if (transform.rotate_180) { + *view_to_display_transform_out = MAT3F_ROTZ(180); + view_to_display_transform_out->transX = display_width; + view_to_display_transform_out->transY = display_height; + + *display_to_view_transform_out = MAT3F_ROTZ(-180); + display_to_view_transform_out->transX = display_width; + display_to_view_transform_out->transY = display_height; + } else if (transform.rotate_270) { + *view_to_display_transform_out = MAT3F_ROTZ(270); + view_to_display_transform_out->transY = display_height; + + *display_to_view_transform_out = MAT3F_ROTZ(-270); + display_to_view_transform_out->transX = display_height; + } +} + +static void window_deinit(struct window *window); + +static int window_init( + // clang-format off + struct window *window, + struct tracer *tracer, + struct frame_scheduler *scheduler, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + int width, int height, + bool has_dimensions, int width_mm, int height_mm, + double refresh_rate, + bool has_forced_pixel_format, enum pixfmt forced_pixel_format + // clang-format on +) { + enum device_orientation original_orientation; + double pixel_ratio; + + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(scheduler); + assert(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + assert(!has_orientation || ORIENTATION_IS_VALID(orientation)); + assert(!has_dimensions || (width_mm > 0 && height_mm > 0)); + + if (has_dimensions == false) { + LOG_DEBUG( + "WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default " + "to 1.0, which may not be the fitting device-pixel ratio for your display. \n" + "Use the `-d` commandline parameter to specify the physical dimensions of your display.\n" + ); + pixel_ratio = 1.0; + } else { + pixel_ratio = (10.0 * width) / (width_mm * 38.0); + + int horizontal_dpi = (int) (width / (width_mm / 25.4)); + int vertical_dpi = (int) (height / (height_mm / 25.4)); + + if (horizontal_dpi != vertical_dpi) { + // See https://github.com/flutter/flutter/issues/71865 for current status of this issue. + LOG_DEBUG("INFO: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); + } + } + + assert(!has_rotation || PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + + if (width > height) { + original_orientation = kLandscapeLeft; + } else { + original_orientation = kPortraitUp; + } + + if (!has_rotation && !has_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_0; + orientation = original_orientation; + has_rotation = true; + has_orientation = true; + } else if (!has_orientation) { + drm_plane_transform_t r = rotation; + orientation = original_orientation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + orientation = ORIENTATION_ROTATE_CW(orientation); + r = PLANE_TRANSFORM_ROTATE_CCW(r); + } + has_orientation = true; + } else if (!has_rotation) { + enum device_orientation o = orientation; + rotation = PLANE_TRANSFORM_ROTATE_0; + while (o != original_orientation) { + rotation = PLANE_TRANSFORM_ROTATE_CW(rotation); + o = ORIENTATION_ROTATE_CCW(o); + } + has_rotation = true; + } else { + enum device_orientation o = orientation; + drm_plane_transform_t r = rotation; + while (r.u64 != PLANE_TRANSFORM_ROTATE_0.u64) { + r = PLANE_TRANSFORM_ROTATE_CCW(r); + o = ORIENTATION_ROTATE_CCW(o); + } + + if (ORIENTATION_IS_LANDSCAPE(o) && !(width >= height)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more high than wide, but de-rotated orientation is landscape)\n" + ); + } else if (ORIENTATION_IS_PORTRAIT(o) && !(height >= width)) { + LOG_DEBUG( + "Explicit orientation and rotation given, but orientation is inconsistent with orientation. (display " + "is more wide than high, but de-rotated orientation is portrait)\n" + ); + } + + original_orientation = o; + } + + assert(has_orientation && has_rotation); + + fill_view_matrices(rotation, width, height, &window->display_to_view_transform, &window->view_to_display_transform); + + pthread_mutex_init(&window->lock, NULL); + window->n_refs = REFCOUNT_INIT_1; + window->tracer = tracer_ref(tracer); + window->frame_scheduler = frame_scheduler_ref(scheduler); + window->refresh_rate = refresh_rate; + window->pixel_ratio = pixel_ratio; + window->has_dimensions = has_dimensions; + window->width_mm = width_mm; + window->height_mm = height_mm; + window->view_size = rotation.rotate_90 || rotation.rotate_270 ? VEC2F(height, width) : VEC2F(width, height); + window->display_size = VEC2F(width, height); + window->rotation = rotation; + window->orientation = orientation; + window->original_orientation = original_orientation; + window->has_forced_pixel_format = has_forced_pixel_format; + window->forced_pixel_format = forced_pixel_format; + window->composition = NULL; + window->renderer_type = kOpenGL_RendererType; + window->gl_renderer = NULL; + window->vk_renderer = NULL; + window->render_surface = NULL; + window->cursor_enabled = false; + window->cursor_pos = VEC2I(0, 0); + window->push_composition = NULL; + window->get_render_surface = NULL; +#ifdef HAVE_EGL_GLES2 + window->has_egl_surface = NULL; + window->get_egl_surface = NULL; +#endif + window->set_cursor_locked = NULL; + window->deinit = window_deinit; + return 0; +} + +static void window_deinit(struct window *window) { + // It's possible we're destroying the window before any frame was presented. + if (window->composition != NULL) { + fl_layer_composition_unref(window->composition); + } + + frame_scheduler_unref(window->frame_scheduler); + tracer_unref(window->tracer); + pthread_mutex_destroy(&window->lock); +} + +void window_destroy(struct window *window) { + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(window->deinit); + + window->deinit(window); + free(window); +} + +int window_push_composition(struct window *window, struct fl_layer_composition *composition) { + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(composition); + ASSERT_NOT_NULL(window->push_composition); + return window->push_composition(window, composition); +} + +struct view_geometry window_get_view_geometry(struct window *window) { + ASSERT_NOT_NULL(window); + + window_lock(window); + struct view_geometry geometry = { + .view_size = window->view_size, + .display_size = window->display_size, + .display_to_view_transform = window->display_to_view_transform, + .view_to_display_transform = window->view_to_display_transform, + .device_pixel_ratio = window->pixel_ratio, + }; + window_unlock(window); + + return geometry; +} + +double window_get_refresh_rate(struct window *window) { + ASSERT_NOT_NULL(window); + + return window->refresh_rate; +} + +int window_get_next_vblank(struct window *window, uint64_t *next_vblank_ns_out) { + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(next_vblank_ns_out); + (void) window; + (void) next_vblank_ns_out; + + /// TODO: Implement + UNIMPLEMENTED(); + + return 0; +} + +#ifdef HAVE_EGL_GLES2 +bool window_has_egl_surface(struct window *window) { + return window->egl_surface; +} + +EGLSurface window_get_egl_surface(struct window *window) { + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(window->get_egl_surface); + return window->get_egl_surface(window); +} +#endif + +struct render_surface *window_get_render_surface(struct window *window, struct vec2i size) { + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(window->get_render_surface); + return window->get_render_surface(window, size); +} + +bool window_is_cursor_enabled(struct window *window) { + bool enabled; + + ASSERT_NOT_NULL(window); + + window_lock(window); + enabled = window->cursor_enabled; + window_unlock(window); + + return enabled; +} + +int window_set_cursor( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +) { + int ok; + + ASSERT_NOT_NULL(window); + + window_lock(window); + + ok = window->set_cursor_locked(window, has_enabled, enabled, has_kind, kind, has_pos, pos); + + window_unlock(window); + + return ok; +} + +struct cursor_buffer { + refcount_t n_refs; + + const struct pointer_icon *icon; + enum pixfmt format; + int width, height; + drm_plane_transform_t rotation; + + struct drmdev *drmdev; + int drm_fb_id; + struct gbm_bo *bo; + + struct vec2i hotspot; +}; + +static struct vec2i get_rotated_hotspot(const struct pointer_icon *icon, drm_plane_transform_t rotation) { + struct vec2i size; + struct vec2i hotspot; + + assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + size = pointer_icon_get_size(icon); + hotspot = pointer_icon_get_hotspot(icon); + + if (rotation.rotate_0) { + return hotspot; + } else if (rotation.rotate_90) { + return VEC2I(size.y - hotspot.y - 1, hotspot.x); + } else if (rotation.rotate_180) { + return VEC2I(size.x - hotspot.x - 1, size.y - hotspot.y - 1); + } else { + ASSUME(rotation.rotate_270); + return VEC2I(hotspot.y, size.x - hotspot.x - 1); + } +} + +static struct cursor_buffer *cursor_buffer_new(struct drmdev *drmdev, const struct pointer_icon *icon, drm_plane_transform_t rotation) { + struct cursor_buffer *b; + struct gbm_bo *bo; + uint32_t fb_id; + struct vec2i size, rotated_size; + int ok; + + ASSERT_NOT_NULL(drmdev); + assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + + size = pointer_icon_get_size(icon); + rotated_size = size; + + b = malloc(sizeof *b); + if (b == NULL) { + return NULL; + } + + if (rotation.rotate_90 || rotation.rotate_270) { + rotated_size = vec2i_swap_xy(size); + } + + bo = gbm_bo_create( + drmdev_get_gbm_device(drmdev), + rotated_size.x, + rotated_size.y, + get_pixfmt_info(PIXFMT_ARGB8888)->gbm_format, + GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT | GBM_BO_USE_WRITE | GBM_BO_USE_CURSOR + ); + if (bo == NULL) { + LOG_ERROR("Could not create GBM buffer for uploading mouse cursor icon. gbm_bo_create: %s\n", strerror(errno)); + goto fail_free_b; + } + + if (gbm_bo_get_stride(bo) != rotated_size.x * 4) { + LOG_ERROR("GBM BO has unsupported framebuffer stride %u, expected was: %d\n", gbm_bo_get_stride(bo), size.x * 4); + goto fail_destroy_bo; + } + + uint32_t *pixel_data = pointer_icon_dup_pixels(icon); + if (pixel_data == NULL) { + goto fail_destroy_bo; + } + + if (rotation.rotate_0) { + ok = gbm_bo_write(bo, pixel_data, gbm_bo_get_stride(bo) * size.y); + if (ok != 0) { + LOG_ERROR("Couldn't write cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); + goto fail_free_duped_pixel_data; + } + } else { + ASSUME(rotation.rotate_90 || rotation.rotate_180 || rotation.rotate_270); + + uint32_t *rotated = malloc(size.x * size.y * 4); + if (rotated == NULL) { + goto fail_free_duped_pixel_data; + } + + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + int buffer_x, buffer_y; + if (rotation.rotate_90) { + buffer_x = size.y - y - 1; + buffer_y = x; + } else if (rotation.rotate_180) { + buffer_x = size.y - y - 1; + buffer_y = size.x - x - 1; + } else { + ASSUME(rotation.rotate_270); + buffer_x = y; + buffer_y = size.x - x - 1; + } + + int buffer_offset = rotated_size.x * buffer_y + buffer_x; + int cursor_offset = size.x * y + x; + + rotated[buffer_offset] = pixel_data[cursor_offset]; + } + } + + ok = gbm_bo_write(bo, rotated, gbm_bo_get_stride(bo) * rotated_size.y); + + free(rotated); + + if (ok != 0) { + LOG_ERROR("Couldn't write rotated cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); + goto fail_free_duped_pixel_data; + } + } + + free(pixel_data); + + fb_id = drmdev_add_fb( + drmdev, + rotated_size.x, + rotated_size.y, + PIXFMT_ARGB8888, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, + gbm_bo_get_modifier(bo) + ); + if (fb_id == 0) { + goto fail_destroy_bo; + } + + b->n_refs = REFCOUNT_INIT_1; + b->icon = icon; + b->format = PIXFMT_ARGB8888; + b->width = rotated_size.x; + b->height = rotated_size.y; + b->rotation = rotation; + b->drmdev = drmdev_ref(drmdev); + b->drm_fb_id = fb_id; + b->bo = bo; + b->hotspot = get_rotated_hotspot(icon, rotation); + return b; + +fail_free_duped_pixel_data: + free(pixel_data); + +fail_destroy_bo: + gbm_bo_destroy(bo); + +fail_free_b: + free(b); + return NULL; +} + +static void cursor_buffer_destroy(struct cursor_buffer *buffer) { + drmdev_rm_fb(buffer->drmdev, buffer->drm_fb_id); + gbm_bo_destroy(buffer->bo); + drmdev_unref(buffer->drmdev); + free(buffer); +} + +static void cursor_buffer_destroy_with_locked_drmdev(struct cursor_buffer *buffer) { + drmdev_rm_fb_locked(buffer->drmdev, buffer->drm_fb_id); + gbm_bo_destroy(buffer->bo); + drmdev_unref(buffer->drmdev); + free(buffer); +} + +DEFINE_STATIC_REF_OPS(cursor_buffer, n_refs) + +static void cursor_buffer_unref_with_locked_drmdev(void *userdata) { + struct cursor_buffer *cursor; + + ASSERT_NOT_NULL(userdata); + cursor = userdata; + + if (refcount_dec(&cursor->n_refs) == false) { + cursor_buffer_destroy_with_locked_drmdev(cursor); + } +} + +static int select_mode( + struct drmdev *drmdev, + struct drm_connector **connector_out, + struct drm_encoder **encoder_out, + struct drm_crtc **crtc_out, + drmModeModeInfo **mode_out, + const char *desired_videomode +) { + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + drmModeModeInfo *mode, *mode_iter; + int ok; + + // find any connected connector + for_each_connector_in_drmdev(drmdev, connector) { + if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { + break; + } + } + + if (connector == NULL) { + LOG_ERROR("Could not find a connected connector!\n"); + return EINVAL; + } + + mode = NULL; + if (desired_videomode != NULL) { + for_each_mode_in_connector(connector, mode_iter) { + char *modeline = NULL, *modeline_nohz = NULL; + + ok = asprintf(&modeline, "%" PRIu16 "x%" PRIu16 "@%" PRIu32, mode_iter->hdisplay, mode_iter->vdisplay, mode_iter->vrefresh); + if (ok < 0) { + return ENOMEM; + } + + ok = asprintf(&modeline_nohz, "%" PRIu16 "x%" PRIu16, mode_iter->hdisplay, mode_iter->vdisplay); + if (ok < 0) { + return ENOMEM; + } + + if (streq(modeline, desired_videomode)) { + // Probably a bit superfluos, but the refresh rate can still vary in the decimal places. + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } else if (streq(modeline_nohz, desired_videomode)) { + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } + + free(modeline); + free(modeline_nohz); + } + + if (mode == NULL) { + LOG_ERROR("Didn't find a videomode matching \"%s\"! Falling back to display preferred mode.\n", desired_videomode); + } + } + + // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) + // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, + // prefer higher refresh rates. After that, prefer progressive scanout modes. + if (mode == NULL) { + for_each_mode_in_connector(connector, mode_iter) { + if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { + mode = mode_iter; + break; + } else if (mode == NULL) { + mode = mode_iter; + } else { + int area = mode_iter->hdisplay * mode_iter->vdisplay; + int old_area = mode->hdisplay * mode->vdisplay; + + if ((area > old_area) || ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || + ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { + mode = mode_iter; + } + } + } + + if (mode == NULL) { + LOG_ERROR("Could not find a preferred output mode!\n"); + return EINVAL; + } + } + + ASSERT_NOT_NULL(mode); + + // Find the encoder that's linked to the connector right now + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->committed_state.encoder_id) { + break; + } + } + + // Otherwise use use any encoder that the connector supports linking to + if (encoder == NULL) { + for (int i = 0; i < connector->n_encoders; i++, encoder = NULL) { + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->encoders[i]) { + break; + } + } + + if (encoder->encoder->possible_crtcs) { + // only use this encoder if there's a crtc we can use with it + break; + } + } + } + + if (encoder == NULL) { + LOG_ERROR("Could not find a suitable DRM encoder.\n"); + return EINVAL; + } + + // Find the CRTC that's currently linked to this encoder + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->id == encoder->encoder->crtc_id) { + break; + } + } + + // Otherwise use any CRTC that this encoder supports linking to + if (crtc == NULL) { + for_each_crtc_in_drmdev(drmdev, crtc) { + if (encoder->encoder->possible_crtcs & crtc->bitmask) { + // find a CRTC that is possible to use with this encoder + break; + } + } + } + + if (crtc == NULL) { + LOG_ERROR("Could not find a suitable DRM CRTC.\n"); + return EINVAL; + } + + *connector_out = connector; + *encoder_out = encoder; + *crtc_out = crtc; + *mode_out = mode; + return 0; +} + +static int kms_window_push_composition(struct window *window, struct fl_layer_composition *composition); +static struct render_surface *kms_window_get_render_surface(struct window *window, struct vec2i size); + +#ifdef HAVE_EGL_GLES2 +static bool kms_window_has_egl_surface(struct window *window); +static EGLSurface kms_window_get_egl_surface(struct window *window); +#endif + +static void kms_window_deinit(struct window *window); +static int kms_window_set_cursor_locked( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +); + +MUST_CHECK struct window *kms_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + bool has_explicit_dimensions, int width_mm, int height_mm, + bool has_forced_pixel_format, enum pixfmt forced_pixel_format, + struct drmdev *drmdev, + const char *desired_videomode + // clang-format on +) { + struct window *window; + struct drm_connector *selected_connector; + struct drm_encoder *selected_encoder; + struct drm_crtc *selected_crtc; + drmModeModeInfo *selected_mode; + bool has_dimensions; + int ok; + + ASSERT_NOT_NULL(drmdev); + +#if !defined(HAVE_VULKAN) + ASSUME(renderer_type != kVulkan_RendererType); +#endif + +#if !defined(HAVE_EGL_GLES2) + ASSUME(renderer_type != kOpenGL_RendererType); +#endif + + // if opengl --> gl_renderer != NULL && vk_renderer == NULL + assert(renderer_type != kOpenGL_RendererType || (gl_renderer != NULL && vk_renderer == NULL)); + + // if vulkan --> vk_renderer != NULL && gl_renderer == NULL + assert(renderer_type != kVulkan_RendererType || (vk_renderer != NULL && gl_renderer == NULL)); + + window = malloc(sizeof *window); + if (window == NULL) { + return NULL; + } + + ok = select_mode(drmdev, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode, desired_videomode); + if (ok != 0) { + goto fail_free_window; + } + + if (has_explicit_dimensions) { + has_dimensions = true; + } else if (selected_connector->variable_state.width_mm % 10 || selected_connector->variable_state.height_mm % 10) { + // as a heuristic, assume the physical dimensions are valid if they're not both multiples of 10. + // dimensions like 160x90mm, 150x100mm are often bogus. + has_dimensions = true; + width_mm = selected_connector->variable_state.width_mm; + height_mm = selected_connector->variable_state.height_mm; + } else if (selected_connector->type == DRM_MODE_CONNECTOR_DSI + && selected_connector->variable_state.width_mm == 0 + && selected_connector->variable_state.height_mm == 0) { + // assume this is the official Raspberry Pi DSI display. + has_dimensions = true; + width_mm = 155; + height_mm = 86; + } else { + has_dimensions = false; + } + + ok = window_init( + // clang-format off + window, + tracer, + scheduler, + has_rotation, rotation, + has_orientation, orientation, + selected_mode->hdisplay, selected_mode->vdisplay, + has_dimensions, width_mm, height_mm, + mode_get_vrefresh(selected_mode), + has_forced_pixel_format, forced_pixel_format + // clang-format on + ); + if (ok != 0) { + free(window); + return NULL; + } + + LOG_DEBUG_UNPREFIXED( + "display mode:\n" + " resolution: %" PRIu16 " x %" PRIu16 + "\n" + " refresh rate: %fHz\n" + " physical size: %dmm x %dmm\n" + " flutter device pixel ratio: %f\n" + " pixel format: %s\n", + selected_mode->hdisplay, + selected_mode->vdisplay, + mode_get_vrefresh(selected_mode), + width_mm, + height_mm, + window->pixel_ratio, + has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" + ); + + window->kms.drmdev = drmdev_ref(drmdev); + window->kms.connector = selected_connector; + window->kms.encoder = selected_encoder; + window->kms.crtc = selected_crtc; + window->kms.mode = selected_mode; + window->kms.should_apply_mode = true; + window->kms.cursor = NULL; + window->kms.pointer_icon = NULL; + window->renderer_type = renderer_type; + if (gl_renderer != NULL) { +#ifdef HAVE_EGL_GLES2 + window->gl_renderer = gl_renderer_ref(gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (vk_renderer != NULL) { +#ifdef HAVE_VULKAN + window->vk_renderer = vk_renderer_ref(vk_renderer); +#else + UNREACHABLE(); +#endif + } else { + window->vk_renderer = NULL; + } + window->push_composition = kms_window_push_composition; + window->get_render_surface = kms_window_get_render_surface; +#ifdef HAVE_EGL_GLES2 + window->has_egl_surface = kms_window_has_egl_surface; + window->get_egl_surface = kms_window_get_egl_surface; +#endif + window->deinit = kms_window_deinit; + window->set_cursor_locked = kms_window_set_cursor_locked; + return window; + +fail_free_window: + free(window); + return NULL; +} + +void kms_window_deinit(struct window *window) { + /// TODO: Do we really need to do this? + /* + struct kms_req_builder *builder; + struct kms_req *req; + int ok; + + builder = drmdev_create_request_builder(window->kms.drmdev, window->kms.crtc->id); + ASSERT_NOT_NULL(builder); + + ok = kms_req_builder_unset_mode(builder); + ASSERT_EQUALS(ok, 0); + + req = kms_req_builder_build(builder); + ASSERT_NOT_NULL(req); + + kms_req_builder_unref(builder); + + ok = kms_req_commit_blocking(req, NULL); + ASSERT_EQUALS(ok, 0); + (void) ok; + + kms_req_unref(req); + */ + + if (window->kms.cursor != NULL) { + cursor_buffer_unref(window->kms.cursor); + } + if (window->render_surface != NULL) { + surface_unref(CAST_SURFACE(window->render_surface)); + } + if (window->gl_renderer != NULL) { +#ifdef HAVE_EGL_GLES2 + gl_renderer_unref(window->gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (window->vk_renderer != NULL) { +#ifdef HAVE_VULKAN + vk_renderer_unref(window->vk_renderer); +#else + UNREACHABLE(); +#endif + } + drmdev_unref(window->kms.drmdev); + window_deinit(window); +} + +struct frame { + struct tracer *tracer; + struct kms_req *req; + bool unset_should_apply_mode_on_commit; +}; + +UNUSED static void on_scanout(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { + ASSERT_NOT_NULL(drmdev); + (void) drmdev; + (void) vblank_ns; + (void) userdata; + + /// TODO: What should we do here? +} + +static void on_present_frame(void *userdata) { + struct frame *frame; + int ok; + + ASSERT_NOT_NULL(userdata); + + frame = userdata; + + TRACER_BEGIN(frame->tracer, "kms_req_commit_nonblocking"); + ok = kms_req_commit_blocking(frame->req, NULL); + TRACER_END(frame->tracer, "kms_req_commit_nonblocking"); + + if (ok != 0) { + LOG_ERROR("Could not commit frame request.\n"); + } + + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + free(frame); +} + +static void on_cancel_frame(void *userdata) { + struct frame *frame; + ASSERT_NOT_NULL(userdata); + + frame = userdata; + + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + free(frame); +} + +static int kms_window_push_composition_locked(struct window *window, struct fl_layer_composition *composition) { + struct kms_req_builder *builder; + struct kms_req *req; + struct frame *frame; + int ok; + + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(composition); + + // If flutter won't request frames (because the vsync callback is broken), + // we'll wait here for the previous frame to be presented / rendered. + // Otherwise the surface_swap_buffers at the bottom might allocate an + // additional buffer and we'll potentially use more buffers than we're + // trying to use. + // if (!window->use_frame_requests) { + // TRACER_BEGIN(window->tracer, "window_request_frame_and_wait_for_begin"); + // ok = window_request_frame_and_wait_for_begin(window); + // TRACER_END(window->tracer, "window_request_frame_and_wait_for_begin"); + // if (ok != 0) { + // LOG_ERROR("Could not wait for frame begin.\n"); + // return ok; + // } + // } + + /// TODO: If we don't have new revisions, we don't need to scanout anything. + fl_layer_composition_swap_ptrs(&window->composition, composition); + + builder = drmdev_create_request_builder(window->kms.drmdev, window->kms.crtc->id); + if (builder == NULL) { + ok = ENOMEM; + goto fail_unref_builder; + } + + // We only set the mode once, at the first atomic request. + if (window->kms.should_apply_mode) { + ok = kms_req_builder_set_connector(builder, window->kms.connector->id); + if (ok != 0) { + LOG_ERROR("Couldn't select connector.\n"); + goto fail_unref_builder; + } + + ok = kms_req_builder_set_mode(builder, window->kms.mode); + if (ok != 0) { + LOG_ERROR("Couldn't apply output mode.\n"); + goto fail_unref_builder; + } + } + + for (size_t i = 0; i < fl_layer_composition_get_n_layers(composition); i++) { + struct fl_layer *layer = fl_layer_composition_peek_layer(composition, i); + + ok = surface_present_kms(layer->surface, &layer->props, builder); + if (ok != 0) { + LOG_ERROR("Couldn't present flutter layer on screen. surface_present_kms: %s\n", strerror(ok)); + goto fail_unref_builder; + } + } + + // add cursor infos + if (window->kms.cursor != NULL) { + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer){ + .drm_fb_id = window->kms.cursor->drm_fb_id, + .format = window->kms.cursor->format, + .has_modifier = true, + .modifier = DRM_FORMAT_MOD_LINEAR, + .src_x = 0, + .src_y = 0, + .src_w = ((uint16_t) window->kms.cursor->width) << 16, + .src_h = ((uint16_t) window->kms.cursor->height) << 16, + .dst_x = window->cursor_pos.x - window->kms.cursor->hotspot.x, + .dst_y = window->cursor_pos.y - window->kms.cursor->hotspot.y, + .dst_w = window->kms.cursor->width, + .dst_h = window->kms.cursor->height, + .has_rotation = false, + .rotation = PLANE_TRANSFORM_NONE, + .has_in_fence_fd = false, + .in_fence_fd = 0, + .prefer_cursor = true, + }, + cursor_buffer_unref_with_locked_drmdev, + NULL, + window->kms.cursor + ); + if (ok != 0) { + LOG_ERROR("Couldn't present cursor.\n"); + } else { + cursor_buffer_ref(window->kms.cursor); + } + } + + req = kms_req_builder_build(builder); + if (req == NULL) { + goto fail_unref_builder; + } + + kms_req_builder_unref(builder); + builder = NULL; + + frame = malloc(sizeof *frame); + if (frame == NULL) { + goto fail_unref_req; + } + + frame->req = req; + frame->tracer = tracer_ref(window->tracer); + frame->unset_should_apply_mode_on_commit = window->kms.should_apply_mode; + + frame_scheduler_present_frame(window->frame_scheduler, on_present_frame, frame, on_cancel_frame); + + // if (window->present_mode == kDoubleBufferedVsync_PresentMode) { + // TRACER_BEGIN(window->tracer, "kms_req_builder_commit"); + // ok = kms_req_commit(req, /* blocking: */ false); + // TRACER_END(window->tracer, "kms_req_builder_commit"); + // + // if (ok != 0) { + // LOG_ERROR("Could not commit frame request.\n"); + // goto fail_unref_window2; + // } + // + // if (window->set_set_mode) { + // window->set_mode = false; + // window->set_set_mode = false; + // } + // } else { + // ASSERT_EQUALS(window->present_mode, kTripleBufferedVsync_PresentMode); + // + // if (window->present_immediately) { + // TRACER_BEGIN(window->tracer, "kms_req_builder_commit"); + // ok = kms_req_commit(req, /* blocking: */ false); + // TRACER_END(window->tracer, "kms_req_builder_commit"); + // + // if (ok != 0) { + // LOG_ERROR("Could not commit frame request.\n"); + // goto fail_unref_window2; + // } + // + // if (window->set_set_mode) { + // window->set_mode = false; + // window->set_set_mode = false; + // } + // + // window->present_immediately = false; + // } else { + // if (window->next_frame != NULL) { + // /// FIXME: Call the release callbacks when the kms_req is destroyed, not when it's unrefed. + // /// Not sure this here will lead to the release callbacks being called multiple times. + // kms_req_call_release_callbacks(window->next_frame); + // kms_req_unref(window->next_frame); + // } + // + // window->next_frame = kms_req_ref(req); + // window->set_set_mode = window->set_mode; + // } + // } + + // KMS Req is committed now and drmdev keeps a ref + // on it internally, so we don't need to keep this one. + // kms_req_unref(req); + + // window_on_rendering_complete(window); + + return 0; + +fail_unref_req: + kms_req_unref(req); + return ok; + +fail_unref_builder: + kms_req_builder_unref(builder); + return ok; +} + +static int kms_window_push_composition(struct window *window, struct fl_layer_composition *composition) { + int ok; + + window_lock(window); + + ok = kms_window_push_composition_locked(window, composition); + + window_unlock(window); + + return ok; +} + +static bool count_modifiers_for_pixel_format( + UNUSED struct drm_plane *plane, + UNUSED int index, + enum pixfmt pixel_format, + UNUSED uint64_t modifier, + void *userdata +) { + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } *context = userdata; + + if (pixel_format == context->format) { + context->n_modifiers++; + } + + return true; +} + +static bool extract_modifiers_for_pixel_format( + UNUSED struct drm_plane *plane, + UNUSED int index, + enum pixfmt pixel_format, + uint64_t modifier, + void *userdata +) { + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } *context = userdata; + + if (pixel_format == context->format) { + context->modifiers[context->index++] = modifier; + } + + return true; +} + +static struct render_surface *kms_window_get_render_surface_internal(struct window *window, bool has_size, UNUSED struct vec2i size) { + struct render_surface *render_surface; + + ASSERT_NOT_NULL(window); + + if (window->render_surface != NULL) { + return window->render_surface; + } + + if (!has_size) { + // Flutter wants a render surface, but hasn't told us the backing store dimensions yet. + // Just make a good guess about the dimensions. + LOG_DEBUG("Flutter requested render surface before supplying surface dimensions.\n"); + size = VEC2I(window->kms.mode->hdisplay, window->kms.mode->vdisplay); + } + + enum pixfmt pixel_format; + if (window->has_forced_pixel_format) { + pixel_format = window->forced_pixel_format; + } else { + // Actually, more devices support ARGB8888 might sometimes not be supported by devices, + // for example for primary planes. But we can just cast ARGB8888 to XRGB8888 if we need to, + // and ARGB8888 is still a good default choice because casting XRGB to ARGB might not work, + // and sometimes we need alpha for overlay planes. + // Also vulkan doesn't work with XRGB yet so we definitely need to use ARGB to vulkan too. + pixel_format = PIXFMT_ARGB8888; + } + + // Possibly populate this with the supported modifiers for this pixel format. + // If no plane lists modifiers for this pixel format, this will be left at NULL, + // and egl_gbm_render_surface_new... will create the GBM surface using usage flags + // (GBM_USE_SCANOUT | GBM_USE_RENDER) instead. + uint64_t *allowed_modifiers = NULL; + size_t n_allowed_modifiers = 0; + + // For now just set the supported modifiers for the first plane that supports this pixel format + // as the allowed modifiers. + /// TODO: Find a way to rank pixel formats, maybe by number of planes that support them for scanout. + { + struct drm_plane *plane; + for_each_plane_in_drmdev(window->kms.drmdev, plane) { + if (!(plane->possible_crtcs & window->kms.crtc->bitmask)) { + // Only query planes that are possible to connect to the CRTC we're using. + continue; + } + + if (plane->type != kPrimary_DrmPlaneType && plane->type != kOverlay_DrmPlaneType) { + // We explicitly only look for primary and overlay planes. + continue; + } + + if (!plane->supports_modifiers) { + // The plane does not have an IN_FORMATS property and does not support + // explicit modifiers. + // + // Calling drm_plane_for_each_modified_format below will segfault. + continue; + } + + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } context = { + .format = pixel_format, + .modifiers = NULL, + .n_modifiers = 0, + .index = 0, + }; + + // First, count the allowed modifiers for this pixel format. + drm_plane_for_each_modified_format(plane, count_modifiers_for_pixel_format, &context); + + n_allowed_modifiers = context.n_modifiers; + allowed_modifiers = calloc(n_allowed_modifiers, sizeof(*context.modifiers)); + context.modifiers = allowed_modifiers; + + // Next, fill context.modifiers with the allowed modifiers. + drm_plane_for_each_modified_format(plane, extract_modifiers_for_pixel_format, &context); + break; + } + } + + if (window->renderer_type == kOpenGL_RendererType) { + // opengl +#ifdef HAVE_EGL_GLES2 + // EGL_NO_CONFIG_KHR is defined by EGL_KHR_no_config_context. + #ifndef EGL_KHR_no_config_context + #error "EGL header definitions for extension EGL_KHR_no_config_context are required." + #endif + + struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( + window->tracer, + size, + gl_renderer_get_gbm_device(window->gl_renderer), + window->gl_renderer, + pixel_format, + EGL_NO_CONFIG_KHR, + allowed_modifiers, + n_allowed_modifiers + ); + if (egl_surface == NULL) { + LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); + render_surface = NULL; + } else { + render_surface = CAST_RENDER_SURFACE(egl_surface); + } + +#else + UNREACHABLE(); +#endif + } else { + ASSUME(window->renderer_type == kVulkan_RendererType); + + // vulkan +#ifdef HAVE_VULKAN + struct vk_gbm_render_surface *vk_surface = + vk_gbm_render_surface_new(window->tracer, size, drmdev_get_gbm_device(window->kms.drmdev), window->vk_renderer, pixel_format); + if (vk_surface == NULL) { + LOG_ERROR("Couldn't create Vulkan GBM rendering surface.\n"); + render_surface = NULL; + } else { + render_surface = CAST_RENDER_SURFACE(vk_surface); + } +#else + UNREACHABLE(); +#endif + } + + if (allowed_modifiers != NULL) { + free(allowed_modifiers); + } + + window->render_surface = render_surface; + return render_surface; +} + +static struct render_surface *kms_window_get_render_surface(struct window *window, struct vec2i size) { + ASSERT_NOT_NULL(window); + return kms_window_get_render_surface_internal(window, true, size); +} + +#ifdef HAVE_EGL_GLES2 +static bool kms_window_has_egl_surface(struct window *window) { + if (window->renderer_type == kOpenGL_RendererType) { + return window->render_surface != NULL; + } else { + return false; + } +} + +static EGLSurface kms_window_get_egl_surface(struct window *window) { + if (window->renderer_type == kOpenGL_RendererType) { + struct render_surface *render_surface = kms_window_get_render_surface_internal(window, false, VEC2I(0, 0)); + return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); + } else { + return EGL_NO_SURFACE; + } +} +#endif + +static int kms_window_set_cursor_locked( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +) { + const struct pointer_icon *icon; + struct cursor_buffer *cursor; + + if (has_kind) { + if (window->kms.pointer_icon == NULL || pointer_icon_get_kind(window->kms.pointer_icon) != kind) { + window->kms.pointer_icon = pointer_icon_for_details(kind, window->pixel_ratio); + ASSERT_NOT_NULL(window->kms.pointer_icon); + } + } + + enabled = has_enabled ? enabled : window->cursor_enabled; + icon = has_kind ? pointer_icon_for_details(kind, window->pixel_ratio) : window->kms.pointer_icon; + pos = has_pos ? pos : window->cursor_pos; + cursor = window->kms.cursor; + + if (enabled && icon == NULL) { + // default to the arrow icon. + icon = pointer_icon_for_details(POINTER_KIND_BASIC, window->pixel_ratio); + ASSERT_NOT_NULL(icon); + } + + if (window->kms.pointer_icon != icon) { + window->kms.pointer_icon = icon; + } + + if (enabled) { + if (cursor == NULL || icon != cursor->icon) { + cursor = cursor_buffer_new(window->kms.drmdev, window->kms.pointer_icon, window->rotation); + if (cursor == NULL) { + return EIO; + } + + cursor_buffer_swap_ptrs(&window->kms.cursor, cursor); + + // cursor is created with refcount 1. cursor_buffer_swap_ptrs + // increases refcount by one. deref here so we don't leak a + // reference. + cursor_buffer_unrefp(&cursor); + + // apply the new cursor icon & position by scanning out a new frame. + window->cursor_pos = pos; + if (window->composition != NULL) { + kms_window_push_composition_locked(window, window->composition); + } + } else if (has_pos) { + // apply the new cursor position using drmModeMoveCursor + window->cursor_pos = pos; + drmdev_move_cursor(window->kms.drmdev, window->kms.crtc->id, vec2i_sub(pos, window->kms.cursor->hotspot)); + } + } else { + if (window->kms.cursor != NULL) { + cursor_buffer_unrefp(&window->kms.cursor); + } + } + + window->cursor_enabled = enabled; + return 0; +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 00000000..ed7616f4 --- /dev/null +++ b/src/window.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +/* + * Window + * + * - a window is a 2D rect inside a real, physical display where flutter content can be displayed, and input events can be routed. + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_WINDOW_H +#define _FLUTTERPI_SRC_WINDOW_H + +#include "compositor_ng.h" +#include "modesetting.h" +#include "pixel_format.h" +#include "util/refcounting.h" + +#include "config.h" + +struct surface; +struct window; +struct tracer; +struct frame_scheduler; +struct fl_layer_composition; + +struct view_geometry { + struct vec2f view_size, display_size; + struct mat3f display_to_view_transform; + struct mat3f view_to_display_transform; + double device_pixel_ratio; +}; + +enum renderer_type { kOpenGL_RendererType, kVulkan_RendererType }; + +DECLARE_REF_OPS(window) + +/** + * @brief Creates a new KMS window. + * + * @param tracer + * @param scheduler + * @param render_surface_interface + * @param has_rotation + * @param rotation + * @param has_orientation + * @param orientation + * @param has_explicit_dimensions + * @param width_mm + * @param height_mm + * @param has_forced_pixel_format + * @param forced_pixel_format + * @param drmdev + * @param desired_videomode + * @return struct window* The new KMS window. + */ +struct window *kms_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + bool has_explicit_dimensions, int width_mm, int height_mm, + bool has_forced_pixel_format, enum pixfmt forced_pixel_format, + struct drmdev *drmdev, + const char *desired_videomode + // clang-format on +); + +/** + * @brief Push a new flutter composition to the window, outputting a new frame. + * + * @param window The window instance. + * @param composition The composition that should be presented. + * @return int Zero if successful, errno-code otherwise. + */ +int window_push_composition(struct window *window, struct fl_layer_composition *composition); + +/** + * @brief Get the current view geometry of this window. + * + * @param window The window instance. + * @return struct view_geometry + */ +struct view_geometry window_get_view_geometry(struct window *window); + +/** + * @brief Returns the vertical refresh rate of the chosen mode & display. + * + * @param window The window instance. + * @return double The refresh rate. + */ +ATTR_PURE double window_get_refresh_rate(struct window *window); + +/** + * @brief Returns the timestamp of the next vblank signal in @param next_vblank_ns_out. + * + * @param window The window instance. + * @param next_vblank_ns_out Next vblank timestamp will be stored here. Must be non-null. + * @return int Zero if successful, errno-code otherwise. + */ +int window_get_next_vblank(struct window *window, uint64_t *next_vblank_ns_out); + +#ifdef HAVE_EGL_GLES2 +bool window_has_egl_surface(struct window *window); + +EGLSurface window_get_egl_surface(struct window *window); +#endif + +/** + * @brief Gets a render surface, used as the backing store for an engine layer. + * + * This only makes sense if there's a single UI (engine) layer. If there's multiple ones, lifetimes become weird. + * + */ +struct render_surface *window_get_render_surface(struct window *window, struct vec2i size); + +bool window_is_cursor_enabled(struct window *window); + +int window_set_cursor( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +); + +#endif // _FLUTTERPI_SRC_WINDOW_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..25f61ef9 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(platformchannel_test + platformchannel_test.c +) + +target_link_libraries( + platformchannel_test + flutterpi_module + Unity +) + +add_test(platformchannel_test platformchannel_test) diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c new file mode 100644 index 00000000..5047a082 --- /dev/null +++ b/test/platformchannel_test.c @@ -0,0 +1,1420 @@ +#define _GNU_SOURCE +#include "platformchannel.h" + +#include +#include +#include + +#include + +#define RAW_STD_BUF(...) (const struct raw_std_value *) ((const uint8_t[]){ __VA_ARGS__ }) +#define AS_RAW_STD_VALUE(_value) ((const struct raw_std_value *) (_value)) + +// required by Unity. +void setUp() { +} + +void tearDown() { +} + +void test_raw_std_value_is_null() { + TEST_ASSERT_TRUE(raw_std_value_is_null(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_is_null(RAW_STD_BUF(kStdTrue))); +} + +void test_raw_std_value_is_true() { + TEST_ASSERT_TRUE(raw_std_value_is_true(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_is_true(RAW_STD_BUF(kStdFalse))); +} + +void test_raw_std_value_is_false() { + TEST_ASSERT_TRUE(raw_std_value_is_false(RAW_STD_BUF(kStdFalse))); + TEST_ASSERT_FALSE(raw_std_value_is_false(RAW_STD_BUF(kStdTrue))); +} + +void test_raw_std_value_is_int32() { + TEST_ASSERT_TRUE(raw_std_value_is_int32(RAW_STD_BUF(kStdInt32))); + TEST_ASSERT_FALSE(raw_std_value_is_int32(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int32() { + // clang-format off + alignas(16) uint8_t buffer[5] = { + kStdInt32, + 0, 0, 0, 0 + }; + // clang-format on + + TEST_ASSERT_EQUAL_INT32(0, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); + + int value = -2003205; + memcpy(buffer + 1, &value, sizeof(int)); + + TEST_ASSERT_EQUAL_INT32(-2003205, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_int64() { + TEST_ASSERT_TRUE(raw_std_value_is_int64(RAW_STD_BUF(kStdInt64))); + TEST_ASSERT_FALSE(raw_std_value_is_int64(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int64() { + // clang-format off + alignas(16) uint8_t buffer[9] = { + kStdInt64, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + // clang-format on + + TEST_ASSERT_EQUAL_INT64(0, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); + + int64_t value = -7998090352538419200; + memcpy(buffer + 1, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_INT64(-7998090352538419200, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_float64() { + TEST_ASSERT_TRUE(raw_std_value_is_float64(RAW_STD_BUF(kStdFloat64))); + TEST_ASSERT_FALSE(raw_std_value_is_float64(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_float64() { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdFloat64, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + // clang-format on + + double value = M_PI; + memcpy(buffer + 8, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_DOUBLE(M_PI, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); + + value = INFINITY; + memcpy(buffer + 8, &value, sizeof(value)); + + TEST_ASSERT_EQUAL_DOUBLE(INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_string() { + TEST_ASSERT_TRUE(raw_std_value_is_string(RAW_STD_BUF(kStdString))); + TEST_ASSERT_FALSE(raw_std_value_is_string(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_string_dup() { + const char *str = "The quick brown fox jumps over the lazy dog."; + + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 45] = { + kStdString, 45, 0 + }; + // clang-format on + + memcpy(buffer + 2, str, strlen(str)); + + char *str_duped = raw_std_string_dup(AS_RAW_STD_VALUE(buffer)); + TEST_ASSERT_NOT_NULL(str_duped); + TEST_ASSERT_EQUAL_STRING(str, str_duped); + + free(str_duped); + + buffer[1] = 0; + str_duped = raw_std_string_dup(AS_RAW_STD_VALUE(buffer)); + TEST_ASSERT_NOT_NULL(str_duped); + TEST_ASSERT_EQUAL_STRING("", str_duped); + + free(str_duped); +} + +void test_raw_std_string_equals() { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + strlen(str)]; + + buffer[0] = kStdString; + buffer[1] = strlen(str); + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + memcpy(buffer + 2, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "The quick brown fox jumps over the lazy dog.")); + TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "The quick brown fox jumps over the lazy dog")); + + buffer[1] = 0; + TEST_ASSERT_TRUE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "")); + TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "anything")); +} + +void test_raw_std_value_is_uint8array() { + TEST_ASSERT_TRUE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdUInt8Array))); + TEST_ASSERT_FALSE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_uint8array() { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t expected[] = { + 1, 2, 3, 4 + }; + // clang-format on + + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); + + buffer[2] = 0; + expected[0] = 0; + + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); +} + +void test_raw_std_value_is_int32array() { + TEST_ASSERT_TRUE(raw_std_value_is_int32array(RAW_STD_BUF(kStdInt32Array))); + TEST_ASSERT_FALSE(raw_std_value_is_int32array(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int32array() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + // clang-format on + + // clang-format off + int32_t expected[] = { + INT_MIN, + 0x12345678, + }; + // clang-format on + + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0; + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); +} + +void test_raw_std_value_is_int64array() { + TEST_ASSERT_TRUE(raw_std_value_is_int64array(RAW_STD_BUF(kStdInt64Array))); + TEST_ASSERT_FALSE(raw_std_value_is_int64array(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_int64array() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + int64_t expected[] = { + INT64_MIN, + 0x123456789ABCDEF, + }; + + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0; + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); +} + +void test_raw_std_value_is_float64array() { + TEST_ASSERT_TRUE(raw_std_value_is_float64array(RAW_STD_BUF(kStdFloat64Array))); + TEST_ASSERT_FALSE(raw_std_value_is_float64array(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_float64array() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + // clang-format off + double expected[] = { + M_PI, + INFINITY, + }; + // clang-format on + + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0.0; + memcpy(buffer + 8, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); +} + +void test_raw_std_value_is_list() { + TEST_ASSERT_TRUE(raw_std_value_is_list(RAW_STD_BUF(kStdList))); + TEST_ASSERT_FALSE(raw_std_value_is_list(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_list_get_size() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdList, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_size_t(2, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); + + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_map() { + TEST_ASSERT_TRUE(raw_std_value_is_map(RAW_STD_BUF(kStdMap))); + TEST_ASSERT_FALSE(raw_std_value_is_map(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_map_get_size() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdMap, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_size_t(2, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); + + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); + + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_is_float32array() { + TEST_ASSERT_TRUE(raw_std_value_is_float32array(RAW_STD_BUF(kStdFloat32Array))); + TEST_ASSERT_FALSE(raw_std_value_is_float32array(RAW_STD_BUF(kStdNull))); +} + +void test_raw_std_value_as_float32array() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + // clang-format on + + // clang-format off + float expected[] = { + M_PI, + INFINITY, + }; + // clang-format on + + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); + + expected[0] = 0.0; + memcpy(buffer + 4, expected, sizeof(expected)); + + TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); +} + +void test_raw_std_value_equals() { + TEST_ASSERT_TRUE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdTrue), RAW_STD_BUF(kStdFalse))); + + // int32 + { + // clang-format off + alignas(16) uint8_t lhs[] = { + kStdInt32, + 1, 2, 3, 4, + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t rhs[] = { + kStdInt32, + 1, 2, 3, 4, + }; + // clang-format on + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[4] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int64 + { + // clang-format off + alignas(16) uint8_t lhs[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t rhs[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + // clang-format on + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[8] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float64 + { + // clang-format off + alignas(16) uint8_t lhs[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t rhs[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + double f = M_PI; + + memcpy(lhs + 8, &f, sizeof(f)); + memcpy(rhs + 8, &f, sizeof(f)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + f = NAN; + memcpy(rhs + 8, &f, sizeof(f)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // string + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t lhs[1 + 1 + strlen(str)]; + lhs[0] = kStdString; + lhs[1] = strlen(str); + + alignas(16) uint8_t rhs[1 + 1 + strlen(str)]; + rhs[0] = kStdString; + rhs[1] = strlen(str); + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + memcpy(lhs + 2, str, strlen(str)); + memcpy(rhs + 2, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = strlen(str) - 1; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + const char *str2 = "The quick brown fox jumps over the lazy DOG "; + TEST_ASSERT_EQUAL_size_t(strlen(str), strlen(str2)); + rhs[1] = strlen(str2); + memcpy(rhs + 2, str2, strlen(str2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // uint8array + { + // clang-format off + alignas(16) uint8_t lhs[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t rhs[] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + // clang-format on + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 3; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 4; + rhs[5] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int32array + { + // clang-format off + alignas(16) uint8_t lhs[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + // clang-format on + + // clang-format off + alignas(16) uint8_t rhs[] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + // clang-format on + + // clang-format off + int32_t array[] = { + INT_MIN, + 0x12345678, + }; + // clang-format on + + memcpy(lhs + 4, array, sizeof(array)); + memcpy(rhs + 4, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + // clang-format off + int32_t array2[] = { + INT_MAX, + 0x12345678, + }; + // clang-format on + memcpy(rhs + 4, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // int64array + { + // clang-format off + alignas(16) uint8_t lhs[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + alignas(16) uint8_t rhs[] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + int64_t array[] = { + INT64_MIN, + 0x123456789ABCDEF, + }; + // clang-format on + + memcpy(lhs + 8, array, sizeof(array)); + memcpy(rhs + 8, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + // clang-format off + int64_t array2[] = { + INT64_MAX, + 0x123456789ABCDEF, + }; + // clang-format on + memcpy(rhs + 8, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float64array + { + // clang-format off + alignas(16) uint8_t lhs[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + alignas(16) uint8_t rhs[] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + double array[] = { + M_PI, + INFINITY, + }; + // clang-format on + + memcpy(lhs + 8, array, sizeof(array)); + memcpy(rhs + 8, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + double array2[] = { + 0.0, + INFINITY, + }; + memcpy(rhs + 8, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // list + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t lhs[1 + 1 + 1 + 1 + strlen(str) + 1]; + lhs[0] = kStdList; + lhs[1] = 2; + lhs[2] = kStdString; + lhs[3] = strlen(str); + lhs[4 + strlen(str)] = kStdTrue; + + alignas(16) uint8_t rhs[1 + 1 + 1 + 1 + strlen(str) + 1]; + rhs[0] = kStdList; + rhs[1] = 2; + rhs[2] = kStdString; + rhs[3] = strlen(str); + rhs[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + memcpy(lhs + 4, str, strlen(str)); + memcpy(rhs + 4, str, strlen(str)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + rhs[3] = strlen(str) - 1; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[3] = strlen(str); + rhs[3 + strlen(str)] = kStdFalse; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // map + { + // clang-format off + alignas(16) uint8_t lhs[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + + alignas(16) uint8_t rhs[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdFloat32Array, + [3] = 2, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdTrue, + [13] = kStdNull, + [14] = kStdInt64, + [15] = 0, 0, 0, 0, 0, 0, 0, 0, + }; + + int64_t int64 = (int64_t) INT64_MIN; + float floats[] = { + M_PI, + INFINITY, + }; + // clang-format on + + memcpy(lhs + 4, &int64, sizeof(int64)); + memcpy(rhs + 15, &int64, sizeof(int64)); + memcpy(lhs + 16, floats, sizeof(floats)); + memcpy(rhs + 4, floats, sizeof(floats)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + rhs[13] = kStdTrue; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[13] = kStdNull; + rhs[3] = 1; + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } + + // float32array + { + // clang-format off + alignas(16) uint8_t lhs[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + alignas(16) uint8_t rhs[] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 floats + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + float array[] = { + M_PI, + INFINITY, + }; + // clang-format on + + memcpy(lhs + 4, array, sizeof(array)); + memcpy(rhs + 4, array, sizeof(array)); + + TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 0; + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + + rhs[1] = 2; + // clang-format off + float array2[] = { + 0.0, + INFINITY, + }; + // clang-format on + memcpy(rhs + 4, array2, sizeof(array2)); + + TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); + } +} + +void test_raw_std_value_is_bool() { + TEST_ASSERT_FALSE(raw_std_value_is_bool(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdFalse))); +} + +void test_raw_std_value_as_bool() { + TEST_ASSERT_TRUE(raw_std_value_as_bool(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_as_bool(RAW_STD_BUF(kStdFalse))); +} + +void test_raw_std_value_is_int() { + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdNull))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdTrue))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFalse))); + TEST_ASSERT_TRUE(raw_std_value_is_int(RAW_STD_BUF(kStdInt32))); + TEST_ASSERT_TRUE(raw_std_value_is_int(RAW_STD_BUF(kStdInt64))); + TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFloat64))); +} + +void test_raw_std_value_as_int() { + // clang-format off + alignas(16) uint8_t buffer[9] = { + kStdInt32, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + // clang-format on + + int64_t int64 = INT64_MAX; + buffer[0] = kStdInt64; + memcpy(buffer + 1, &int64, sizeof(int64)); + + TEST_ASSERT_EQUAL_INT64(INT64_MAX, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); + + buffer[0] = kStdInt32; + TEST_ASSERT_NOT_EQUAL_INT64(INT64_MAX, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); + + int32_t int32 = INT32_MIN; + buffer[0] = kStdInt32; + memcpy(buffer + 1, &int32, sizeof(int32)); + + TEST_ASSERT_EQUAL_INT64(INT32_MIN, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_get_size() { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type + kStdList, + // size + 2, + // space for more size bytes + 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_size_t(2, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + + TEST_ASSERT_EQUAL_size_t(0, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + + uint32_t size = 0xDEAD; + + buffer[1] = 254; + memcpy(buffer + 2, &size, 2); + memcpy(buffer + 4, &size, 2); + + TEST_ASSERT_EQUAL_size_t(0xDEAD, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); + + size = 0xDEADBEEF; + buffer[1] = 255; + memcpy(buffer + 2, &size, 4); + + TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_value_after() { + // null + { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdNull, + 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // true + { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdTrue, + 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // true + { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdFalse, + 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int32 + { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdInt32, + 1, 2, 3, 4, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 5, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int64 + { + // clang-format off + alignas(16) uint8_t buffer[] = { + kStdInt64, + 1, 2, 3, 4, 5, 6, 7, 8 + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 9, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float64 + { + // clang-format off + alignas(16) uint8_t buffer[] = { + // type byte + kStdFloat64, + // 7 alignment bytes + 0, 0, 0, 0, 0, 0, 0, + // bytes for 1 float64 + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 7 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // string + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4] = { + kStdString, + strlen(str), + 0 + }; + // clang-format on + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + strlen(str), raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 0x00010000, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // uint8array + { + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4 + 0x00010000] = { + kStdUInt8Array, + 4, + 1, 2, 3, 4 + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 0x00010000, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int32array + { + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*4] = { + // type + kStdInt32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254 * 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000 * 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // int64array + { + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*8] = { + // type + kStdInt64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 int64_t's + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6 + 2 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 254 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float64array + { + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x010000*8] = { + // type + kStdFloat64Array, + // size + 2, + // 6 alignment bytes + 0, 0, 0, 0, 0, 0, + // space for 2 doubles + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6 + 2 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 6, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 254 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000 * 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // list + { + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str) + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 1; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 1 + 1 + strlen(str), raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // map + { + // clang-format off + alignas(16) uint8_t buffer[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 25, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 1; + TEST_ASSERT_EQUAL_PTR(buffer + 12, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } + + // float32array + { + // clang-format off + alignas(16) uint8_t buffer[1 + 1 + 4 + 2 + 0x040000] = { + // type + kStdFloat32Array, + // size + 2, + // 2 alignment bytes + 0, 0, + // space for 2 int32_t's + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 8, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2 + 254 * 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 1; + buffer[5] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4 + 2 + 0x010000 * 4, raw_std_value_after(AS_RAW_STD_VALUE(buffer))); + } +} + +void test_raw_std_list_get_first_element() { + // list + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer))); + + TEST_ASSERT_EQUAL_PTR( + buffer + 1 + 1 + 1 + 1 + strlen(str), + raw_std_value_after(raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer))) + ); +} + +void test_raw_std_list_get_nth_element() { + // list + const char *str = "The quick brown fox jumps over the lazy dog."; + + alignas(16) uint8_t buffer[1 + 1 + 4 + 1 + 1 + 4 + strlen(str) + 1]; + buffer[0] = kStdList; + buffer[1] = 2; + buffer[2] = kStdString; + buffer[3] = strlen(str); + buffer[4 + strlen(str)] = kStdTrue; + + // only string lengths less or equal 253 are actually encoded as one byte in + // the standard message codec encoding. + TEST_ASSERT_LESS_OR_EQUAL_size_t(253, strlen(str)); + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_list_get_nth_element(AS_RAW_STD_VALUE(buffer), 0)); + + TEST_ASSERT_EQUAL_PTR( + buffer + 1 + 1 + 1 + 1 + strlen(str), + raw_std_value_after(raw_std_list_get_first_element(AS_RAW_STD_VALUE(buffer))) + ); +} + +void test_raw_std_map_get_first_key() { + // map + // clang-format off + alignas(16) uint8_t buffer[] = { + [0] = kStdMap, + [1] = 2, + [2] = kStdNull, + [3] = kStdInt64, + [4] = 0, 0, 0, 0, 0, 0, 0, 0, + [12] = kStdFloat32Array, + [13] = 2, + [16] = 0, 0, 0, 0, 0, 0, 0, 0, + [24] = kStdTrue, + }; + // clang-format on + + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 254; + buffer[2] = 254; + buffer[3] = 0; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 2, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); + + buffer[1] = 255; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x01; + buffer[5] = 0x00; + TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); +} + +void test_raw_std_map_find() { +} + +void test_raw_std_map_find_str() { +} + +void test_raw_std_value_check() { +} + +void test_raw_std_method_call_check() { +} + +void test_raw_std_method_call_response_check() { +} + +void test_raw_std_event_check() { +} + +void test_raw_std_method_call_get_method() { +} + +void test_raw_std_method_call_get_method_dup() { +} + +void test_raw_std_method_call_get_arg() { +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_raw_std_value_is_null); + RUN_TEST(test_raw_std_value_is_true); + RUN_TEST(test_raw_std_value_is_false); + RUN_TEST(test_raw_std_value_is_int32); + RUN_TEST(test_raw_std_value_as_int32); + RUN_TEST(test_raw_std_value_is_int64); + RUN_TEST(test_raw_std_value_as_int64); + RUN_TEST(test_raw_std_value_is_float64); + RUN_TEST(test_raw_std_value_as_float64); + RUN_TEST(test_raw_std_value_is_string); + RUN_TEST(test_raw_std_string_dup); + RUN_TEST(test_raw_std_string_equals); + RUN_TEST(test_raw_std_value_is_uint8array); + RUN_TEST(test_raw_std_value_as_uint8array); + RUN_TEST(test_raw_std_value_is_int32array); + RUN_TEST(test_raw_std_value_as_int32array); + RUN_TEST(test_raw_std_value_is_int64array); + RUN_TEST(test_raw_std_value_as_int64array); + RUN_TEST(test_raw_std_value_is_float64array); + RUN_TEST(test_raw_std_value_as_float64array); + RUN_TEST(test_raw_std_value_is_list); + RUN_TEST(test_raw_std_list_get_size); + RUN_TEST(test_raw_std_value_is_map); + RUN_TEST(test_raw_std_map_get_size); + RUN_TEST(test_raw_std_value_is_float32array); + RUN_TEST(test_raw_std_value_as_float32array); + RUN_TEST(test_raw_std_value_equals); + RUN_TEST(test_raw_std_value_is_bool); + RUN_TEST(test_raw_std_value_as_bool); + RUN_TEST(test_raw_std_value_is_int); + RUN_TEST(test_raw_std_value_as_int); + RUN_TEST(test_raw_std_value_get_size); + RUN_TEST(test_raw_std_value_after); + RUN_TEST(test_raw_std_list_get_first_element); + RUN_TEST(test_raw_std_list_get_nth_element); + RUN_TEST(test_raw_std_map_get_first_key); + RUN_TEST(test_raw_std_map_find); + RUN_TEST(test_raw_std_map_find_str); + RUN_TEST(test_raw_std_value_check); + RUN_TEST(test_raw_std_method_call_check); + RUN_TEST(test_raw_std_method_call_response_check); + RUN_TEST(test_raw_std_event_check); + RUN_TEST(test_raw_std_method_call_get_method); + RUN_TEST(test_raw_std_method_call_get_method_dup); + RUN_TEST(test_raw_std_method_call_get_arg); + + return UNITY_END(); +} diff --git a/test/texture_registry.c b/test/texture_registry.c deleted file mode 100644 index e69de29b..00000000 diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 00000000..66c03cd9 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(Unity STATIC + Unity/src/unity.c +) + +target_include_directories(Unity PUBLIC + Unity/src +) + +target_compile_definitions(Unity PUBLIC + UNITY_SUPPORT_64 + UNITY_INCLUDE_DOUBLE +) diff --git a/third_party/Unity b/third_party/Unity new file mode 160000 index 00000000..5204c1ba --- /dev/null +++ b/third_party/Unity @@ -0,0 +1 @@ +Subproject commit 5204c1bacf37e0c38211d2fbb6d9796e410223f8 diff --git a/third_party/flutter_embedder_header/engine.version b/third_party/flutter_embedder_header/engine.version new file mode 100644 index 00000000..a7aa73a8 --- /dev/null +++ b/third_party/flutter_embedder_header/engine.version @@ -0,0 +1 @@ +d44b5a94c976fbb65815374f61ab5392a220b084 \ No newline at end of file diff --git a/third_party/flutter_embedder_header/include/flutter_embedder.h b/third_party/flutter_embedder_header/include/flutter_embedder.h new file mode 100644 index 00000000..5cdba06e --- /dev/null +++ b/third_party/flutter_embedder_header/include/flutter_embedder.h @@ -0,0 +1,2980 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_EMBEDDER_H_ +#define FLUTTER_EMBEDDER_H_ + +#include +#include +#include + +// This file defines an Application Binary Interface (ABI), which requires more +// stability than regular code to remain functional for exchanging messages +// between different versions of the embedding and the engine, to allow for both +// forward and backward compatibility. +// +// Specifically, +// - The order, type, and size of the struct members below must remain the same, +// and members should not be removed. +// - New structures that are part of the ABI must be defined with "size_t +// struct_size;" as their first member, which should be initialized using +// "sizeof(Type)". +// - Enum values must not change or be removed. +// - Enum members without explicit values must not be reordered. +// - Function signatures (names, argument counts, argument order, and argument +// type) cannot change. +// - The core behavior of existing functions cannot change. +// - Instead of nesting structures by value within another structure, prefer +// nesting by pointer. This ensures that adding members to the nested struct +// does not break the ABI of the parent struct. +// - Instead of array of structures, prefer array of pointers to structures. +// This ensures that array indexing does not break if members are added +// to the structure. +// +// These changes are allowed: +// - Adding new struct members at the end of a structure as long as the struct +// is not nested within another struct by value. +// - Adding new enum members with a new value. +// - Renaming a struct member as long as its type, size, and intent remain the +// same. +// - Renaming an enum member as long as its value and intent remains the same. +// +// It is expected that struct members and implicitly-valued enums will not +// always be declared in an order that is optimal for the reader, since members +// will be added over time, and they can't be reordered. +// +// Existing functions should continue to appear from the caller's point of view +// to operate as they did when they were first introduced, so introduce a new +// function instead of modifying the core behavior of a function (and continue +// to support the existing function with the previous behavior). + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef FLUTTER_EXPORT +#define FLUTTER_EXPORT +#endif // FLUTTER_EXPORT + +#ifdef FLUTTER_API_SYMBOL_PREFIX +#define FLUTTER_EMBEDDING_CONCAT(a, b) a##b +#define FLUTTER_EMBEDDING_ADD_PREFIX(symbol, prefix) \ + FLUTTER_EMBEDDING_CONCAT(prefix, symbol) +#define FLUTTER_API_SYMBOL(symbol) \ + FLUTTER_EMBEDDING_ADD_PREFIX(symbol, FLUTTER_API_SYMBOL_PREFIX) +#else +#define FLUTTER_API_SYMBOL(symbol) symbol +#endif + +#define FLUTTER_ENGINE_VERSION 1 + +typedef enum { + kSuccess = 0, + kInvalidLibraryVersion, + kInvalidArguments, + kInternalInconsistency, +} FlutterEngineResult; + +typedef enum { + kOpenGL, + kSoftware, + /// Metal is only supported on Darwin platforms (macOS / iOS). + /// iOS version >= 10.0 (device), 13.0 (simulator) + /// macOS version >= 10.14 + kMetal, + kVulkan, +} FlutterRendererType; + +/// Additional accessibility features that may be enabled by the platform. +/// Must match the `AccessibilityFeatures` enum in window.dart. +typedef enum { + /// Indicate there is a running accessibility service which is changing the + /// interaction model of the device. + kFlutterAccessibilityFeatureAccessibleNavigation = 1 << 0, + /// Indicate the platform is inverting the colors of the application. + kFlutterAccessibilityFeatureInvertColors = 1 << 1, + /// Request that animations be disabled or simplified. + kFlutterAccessibilityFeatureDisableAnimations = 1 << 2, + /// Request that text be rendered at a bold font weight. + kFlutterAccessibilityFeatureBoldText = 1 << 3, + /// Request that certain animations be simplified and parallax effects + /// removed. + kFlutterAccessibilityFeatureReduceMotion = 1 << 4, + /// Request that UI be rendered with darker colors. + kFlutterAccessibilityFeatureHighContrast = 1 << 5, + /// Request to show on/off labels inside switches. + kFlutterAccessibilityFeatureOnOffSwitchLabels = 1 << 6, +} FlutterAccessibilityFeature; + +/// The set of possible actions that can be conveyed to a semantics node. +/// +/// Must match the `SemanticsAction` enum in semantics.dart. +typedef enum { + /// The equivalent of a user briefly tapping the screen with the finger + /// without moving it. + kFlutterSemanticsActionTap = 1 << 0, + /// The equivalent of a user pressing and holding the screen with the finger + /// for a few seconds without moving it. + kFlutterSemanticsActionLongPress = 1 << 1, + /// The equivalent of a user moving their finger across the screen from right + /// to left. + kFlutterSemanticsActionScrollLeft = 1 << 2, + /// The equivalent of a user moving their finger across the screen from left + /// to + /// right. + kFlutterSemanticsActionScrollRight = 1 << 3, + /// The equivalent of a user moving their finger across the screen from bottom + /// to top. + kFlutterSemanticsActionScrollUp = 1 << 4, + /// The equivalent of a user moving their finger across the screen from top to + /// bottom. + kFlutterSemanticsActionScrollDown = 1 << 5, + /// Increase the value represented by the semantics node. + kFlutterSemanticsActionIncrease = 1 << 6, + /// Decrease the value represented by the semantics node. + kFlutterSemanticsActionDecrease = 1 << 7, + /// A request to fully show the semantics node on screen. + kFlutterSemanticsActionShowOnScreen = 1 << 8, + /// Move the cursor forward by one character. + kFlutterSemanticsActionMoveCursorForwardByCharacter = 1 << 9, + /// Move the cursor backward by one character. + kFlutterSemanticsActionMoveCursorBackwardByCharacter = 1 << 10, + /// Set the text selection to the given range. + kFlutterSemanticsActionSetSelection = 1 << 11, + /// Copy the current selection to the clipboard. + kFlutterSemanticsActionCopy = 1 << 12, + /// Cut the current selection and place it in the clipboard. + kFlutterSemanticsActionCut = 1 << 13, + /// Paste the current content of the clipboard. + kFlutterSemanticsActionPaste = 1 << 14, + /// Indicate that the node has gained accessibility focus. + kFlutterSemanticsActionDidGainAccessibilityFocus = 1 << 15, + /// Indicate that the node has lost accessibility focus. + kFlutterSemanticsActionDidLoseAccessibilityFocus = 1 << 16, + /// Indicate that the user has invoked a custom accessibility action. + kFlutterSemanticsActionCustomAction = 1 << 17, + /// A request that the node should be dismissed. + kFlutterSemanticsActionDismiss = 1 << 18, + /// Move the cursor forward by one word. + kFlutterSemanticsActionMoveCursorForwardByWord = 1 << 19, + /// Move the cursor backward by one word. + kFlutterSemanticsActionMoveCursorBackwardByWord = 1 << 20, + /// Replace the current text in the text field. + kFlutterSemanticsActionSetText = 1 << 21, +} FlutterSemanticsAction; + +/// The set of properties that may be associated with a semantics node. +/// +/// Must match the `SemanticsFlag` enum in semantics.dart. +typedef enum { + /// The semantics node has the quality of either being "checked" or + /// "unchecked". + kFlutterSemanticsFlagHasCheckedState = 1 << 0, + /// Whether a semantics node is checked. + kFlutterSemanticsFlagIsChecked = 1 << 1, + /// Whether a semantics node is selected. + kFlutterSemanticsFlagIsSelected = 1 << 2, + /// Whether the semantic node represents a button. + kFlutterSemanticsFlagIsButton = 1 << 3, + /// Whether the semantic node represents a text field. + kFlutterSemanticsFlagIsTextField = 1 << 4, + /// Whether the semantic node currently holds the user's focus. + kFlutterSemanticsFlagIsFocused = 1 << 5, + /// The semantics node has the quality of either being "enabled" or + /// "disabled". + kFlutterSemanticsFlagHasEnabledState = 1 << 6, + /// Whether a semantic node that hasEnabledState is currently enabled. + kFlutterSemanticsFlagIsEnabled = 1 << 7, + /// Whether a semantic node is in a mutually exclusive group. + kFlutterSemanticsFlagIsInMutuallyExclusiveGroup = 1 << 8, + /// Whether a semantic node is a header that divides content into sections. + kFlutterSemanticsFlagIsHeader = 1 << 9, + /// Whether the value of the semantics node is obscured. + kFlutterSemanticsFlagIsObscured = 1 << 10, + /// Whether the semantics node is the root of a subtree for which a route name + /// should be announced. + kFlutterSemanticsFlagScopesRoute = 1 << 11, + /// Whether the semantics node label is the name of a visually distinct route. + kFlutterSemanticsFlagNamesRoute = 1 << 12, + /// Whether the semantics node is considered hidden. + kFlutterSemanticsFlagIsHidden = 1 << 13, + /// Whether the semantics node represents an image. + kFlutterSemanticsFlagIsImage = 1 << 14, + /// Whether the semantics node is a live region. + kFlutterSemanticsFlagIsLiveRegion = 1 << 15, + /// The semantics node has the quality of either being "on" or "off". + kFlutterSemanticsFlagHasToggledState = 1 << 16, + /// If true, the semantics node is "on". If false, the semantics node is + /// "off". + kFlutterSemanticsFlagIsToggled = 1 << 17, + /// Whether the platform can scroll the semantics node when the user attempts + /// to move the accessibility focus to an offscreen child. + /// + /// For example, a `ListView` widget has implicit scrolling so that users can + /// easily move the accessibility focus to the next set of children. A + /// `PageView` widget does not have implicit scrolling, so that users don't + /// navigate to the next page when reaching the end of the current one. + kFlutterSemanticsFlagHasImplicitScrolling = 1 << 18, + /// Whether the value of the semantics node is coming from a multi-line text + /// field. + /// + /// This is used for text fields to distinguish single-line text fields from + /// multi-line ones. + kFlutterSemanticsFlagIsMultiline = 1 << 19, + /// Whether the semantic node is read only. + /// + /// Only applicable when kFlutterSemanticsFlagIsTextField flag is on. + kFlutterSemanticsFlagIsReadOnly = 1 << 20, + /// Whether the semantic node can hold the user's focus. + kFlutterSemanticsFlagIsFocusable = 1 << 21, + /// Whether the semantics node represents a link. + kFlutterSemanticsFlagIsLink = 1 << 22, + /// Whether the semantics node represents a slider. + kFlutterSemanticsFlagIsSlider = 1 << 23, + /// Whether the semantics node represents a keyboard key. + kFlutterSemanticsFlagIsKeyboardKey = 1 << 24, + /// Whether the semantics node represents a tristate checkbox in mixed state. + kFlutterSemanticsFlagIsCheckStateMixed = 1 << 25, +} FlutterSemanticsFlag; + +typedef enum { + /// Text has unknown text direction. + kFlutterTextDirectionUnknown = 0, + /// Text is read from right to left. + kFlutterTextDirectionRTL = 1, + /// Text is read from left to right. + kFlutterTextDirectionLTR = 2, +} FlutterTextDirection; + +/// Valid values for priority of Thread. +typedef enum { + /// Suitable for threads that shouldn't disrupt high priority work. + kBackground = 0, + /// Default priority level. + kNormal = 1, + /// Suitable for threads which generate data for the display. + kDisplay = 2, + /// Suitable for thread which raster data. + kRaster = 3, +} FlutterThreadPriority; + +typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); + +typedef struct { + /// horizontal scale factor + double scaleX; + /// horizontal skew factor + double skewX; + /// horizontal translation + double transX; + /// vertical skew factor + double skewY; + /// vertical scale factor + double scaleY; + /// vertical translation + double transY; + /// input x-axis perspective factor + double pers0; + /// input y-axis perspective factor + double pers1; + /// perspective scale factor + double pers2; +} FlutterTransformation; + +typedef void (*VoidCallback)(void* /* user data */); + +typedef enum { + /// Specifies an OpenGL texture target type. Textures are specified using + /// the FlutterOpenGLTexture struct. + kFlutterOpenGLTargetTypeTexture, + /// Specifies an OpenGL frame-buffer target type. Framebuffers are specified + /// using the FlutterOpenGLFramebuffer struct. + kFlutterOpenGLTargetTypeFramebuffer, +} FlutterOpenGLTargetType; + +/// A pixel format to be used for software rendering. +/// +/// A single pixel always stored as a POT number of bytes. (so in practice +/// either 1, 2, 4, 8, 16 bytes per pixel) +/// +/// There are two kinds of pixel formats: +/// - formats where all components are 8 bits, called array formats +/// The component order as specified in the pixel format name is the +/// order of the components' bytes in memory, with the leftmost component +/// occupying the lowest memory address. +/// +/// - all other formats are called packed formats, and the component order +/// as specified in the format name refers to the order in the native type. +/// for example, for kFlutterSoftwarePixelFormatRGB565, the R component +/// uses the 5 least significant bits of the uint16_t pixel value. +/// +/// Each pixel format in this list is documented with an example on how to get +/// the color components from the pixel. +/// - for packed formats, p is the pixel value as a word. For example, you can +/// get the pixel value for a RGB565 formatted buffer like this: +/// uint16_t p = ((const uint16_t*) allocation)[row_bytes * y / bpp + x]; +/// (with bpp being the bytes per pixel, so 2 for RGB565) +/// +/// - for array formats, p is a pointer to the pixel value. For example, you +/// can get the p for a RGBA8888 formatted buffer like this: +/// const uint8_t *p = ((const uint8_t*) allocation) + row_bytes*y + x*4; +typedef enum { + /// pixel with 8 bit grayscale value. + /// The grayscale value is the luma value calculated from r, g, b + /// according to BT.709. (gray = r*0.2126 + g*0.7152 + b*0.0722) + kFlutterSoftwarePixelFormatGray8, + + /// pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word. + /// r = p & 0x3F; g = (p>>5) & 0x3F; b = p>>11; + kFlutterSoftwarePixelFormatRGB565, + + /// pixel with 4 bits for alpha, red, green, blue; in 16-bit word. + /// r = p & 0xF; g = (p>>4) & 0xF; b = (p>>8) & 0xF; a = p>>12; + kFlutterSoftwarePixelFormatRGBA4444, + + /// pixel with 8 bits for red, green, blue, alpha. + /// r = p[0]; g = p[1]; b = p[2]; a = p[3]; + kFlutterSoftwarePixelFormatRGBA8888, + + /// pixel with 8 bits for red, green and blue and 8 unused bits. + /// r = p[0]; g = p[1]; b = p[2]; + kFlutterSoftwarePixelFormatRGBX8888, + + /// pixel with 8 bits for blue, green, red and alpha. + /// r = p[2]; g = p[1]; b = p[0]; a = p[3]; + kFlutterSoftwarePixelFormatBGRA8888, + + /// either kFlutterSoftwarePixelFormatBGRA8888 or + /// kFlutterSoftwarePixelFormatRGBA8888 depending on CPU endianess and OS + kFlutterSoftwarePixelFormatNative32, +} FlutterSoftwarePixelFormat; + +typedef struct { + /// Target texture of the active texture unit (example GL_TEXTURE_2D or + /// GL_TEXTURE_RECTANGLE). + uint32_t target; + /// The name of the texture. + uint32_t name; + /// The texture format (example GL_RGBA8). + uint32_t format; + /// User data to be returned on the invocation of the destruction callback. + void* user_data; + /// Callback invoked (on an engine managed thread) that asks the embedder to + /// collect the texture. + VoidCallback destruction_callback; + /// Optional parameters for texture height/width, default is 0, non-zero means + /// the texture has the specified width/height. Usually, when the texture type + /// is GL_TEXTURE_RECTANGLE, we need to specify the texture width/height to + /// tell the embedder to scale when rendering. + /// Width of the texture. + size_t width; + /// Height of the texture. + size_t height; +} FlutterOpenGLTexture; + +typedef struct { + /// The target of the color attachment of the frame-buffer. For example, + /// GL_TEXTURE_2D or GL_RENDERBUFFER. In case of ambiguity when dealing with + /// Window bound frame-buffers, 0 may be used. + uint32_t target; + + /// The name of the framebuffer. + uint32_t name; + + /// User data to be returned on the invocation of the destruction callback. + void* user_data; + + /// Callback invoked (on an engine managed thread) that asks the embedder to + /// collect the framebuffer. + VoidCallback destruction_callback; +} FlutterOpenGLFramebuffer; + +typedef bool (*BoolCallback)(void* /* user data */); +typedef FlutterTransformation (*TransformationCallback)(void* /* user data */); +typedef uint32_t (*UIntCallback)(void* /* user data */); +typedef bool (*SoftwareSurfacePresentCallback)(void* /* user data */, + const void* /* allocation */, + size_t /* row bytes */, + size_t /* height */); +typedef void* (*ProcResolver)(void* /* user data */, const char* /* name */); +typedef bool (*TextureFrameCallback)(void* /* user data */, + int64_t /* texture identifier */, + size_t /* width */, + size_t /* height */, + FlutterOpenGLTexture* /* texture out */); +typedef void (*VsyncCallback)(void* /* user data */, intptr_t /* baton */); +typedef void (*OnPreEngineRestartCallback)(void* /* user data */); + +/// A structure to represent the width and height. +typedef struct { + double width; + double height; +} FlutterSize; + +/// A structure to represent the width and height. +/// +/// See: \ref FlutterSize when the value are not integers. +typedef struct { + uint32_t width; + uint32_t height; +} FlutterUIntSize; + +/// A structure to represent a rectangle. +typedef struct { + double left; + double top; + double right; + double bottom; +} FlutterRect; + +/// A structure to represent a 2D point. +typedef struct { + double x; + double y; +} FlutterPoint; + +/// A structure to represent a rounded rectangle. +typedef struct { + FlutterRect rect; + FlutterSize upper_left_corner_radius; + FlutterSize upper_right_corner_radius; + FlutterSize lower_right_corner_radius; + FlutterSize lower_left_corner_radius; +} FlutterRoundedRect; + +/// A structure to represent a damage region. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDamage). + size_t struct_size; + /// The number of rectangles within the damage region. + size_t num_rects; + /// The actual damage region(s) in question. + FlutterRect* damage; +} FlutterDamage; + +/// This information is passed to the embedder when requesting a frame buffer +/// object. +/// +/// See: \ref FlutterOpenGLRendererConfig.fbo_with_frame_info_callback, +/// \ref FlutterMetalRendererConfig.get_next_drawable_callback, +/// and \ref FlutterVulkanRendererConfig.get_next_image_callback. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterFrameInfo). + size_t struct_size; + /// The size of the surface that will be backed by the fbo. + FlutterUIntSize size; +} FlutterFrameInfo; + +/// Callback for when a frame buffer object is requested. +typedef uint32_t (*UIntFrameInfoCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a frame buffer object is requested with necessary +/// information for partial repaint. +typedef void (*FlutterFrameBufferWithDamageCallback)( + void* /* user data */, + const intptr_t /* fbo id */, + FlutterDamage* /* existing damage */); + +/// This information is passed to the embedder when a surface is presented. +/// +/// See: \ref FlutterOpenGLRendererConfig.present_with_info. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterPresentInfo). + size_t struct_size; + /// Id of the fbo backing the surface that was presented. + uint32_t fbo_id; + /// Damage representing the area that the compositor needs to render. + FlutterDamage frame_damage; + /// Damage used to set the buffer's damage region. + FlutterDamage buffer_damage; +} FlutterPresentInfo; + +/// Callback for when a surface is presented. +typedef bool (*BoolPresentInfoCallback)( + void* /* user data */, + const FlutterPresentInfo* /* present info */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterOpenGLRendererConfig). + size_t struct_size; + BoolCallback make_current; + BoolCallback clear_current; + /// Specifying one (and only one) of `present` or `present_with_info` is + /// required. Specifying both is an error and engine initialization will be + /// terminated. The return value indicates success of the present call. If + /// the intent is to use dirty region management, present_with_info must be + /// defined as present will not succeed in communicating information about + /// damage. + BoolCallback present; + /// Specifying one (and only one) of the `fbo_callback` or + /// `fbo_with_frame_info_callback` is required. Specifying both is an error + /// and engine intialization will be terminated. The return value indicates + /// the id of the frame buffer object that flutter will obtain the gl surface + /// from. + UIntCallback fbo_callback; + /// This is an optional callback. Flutter will ask the emebdder to create a GL + /// context current on a background thread. If the embedder is able to do so, + /// Flutter will assume that this context is in the same sharegroup as the + /// main rendering context and use this context for asynchronous texture + /// uploads. Though optional, it is recommended that all embedders set this + /// callback as it will lead to better performance in texture handling. + BoolCallback make_resource_current; + /// By default, the renderer config assumes that the FBO does not change for + /// the duration of the engine run. If this argument is true, the + /// engine will ask the embedder for an updated FBO target (via an + /// fbo_callback invocation) after a present call. + bool fbo_reset_after_present; + /// The transformation to apply to the render target before any rendering + /// operations. This callback is optional. + /// @attention When using a custom compositor, the layer offset and sizes + /// will be affected by this transformation. It will be + /// embedder responsibility to render contents at the + /// transformed offset and size. This is useful for embedders + /// that want to render transformed contents directly into + /// hardware overlay planes without having to apply extra + /// transformations to layer contents (which may necessitate + /// an expensive off-screen render pass). + TransformationCallback surface_transformation; + ProcResolver gl_proc_resolver; + /// When the embedder specifies that a texture has a frame available, the + /// engine will call this method (on an internal engine managed thread) so + /// that external texture details can be supplied to the engine for subsequent + /// composition. + TextureFrameCallback gl_external_texture_frame_callback; + /// Specifying one (and only one) of the `fbo_callback` or + /// `fbo_with_frame_info_callback` is required. Specifying both is an error + /// and engine intialization will be terminated. The return value indicates + /// the id of the frame buffer object (fbo) that flutter will obtain the gl + /// surface from. When using this variant, the embedder is passed a + /// `FlutterFrameInfo` struct that indicates the properties of the surface + /// that flutter will acquire from the returned fbo. + UIntFrameInfoCallback fbo_with_frame_info_callback; + /// Specifying one (and only one) of `present` or `present_with_info` is + /// required. Specifying both is an error and engine initialization will be + /// terminated. When using this variant, the embedder is passed a + /// `FlutterPresentInfo` struct that the embedder can use to release any + /// resources. The return value indicates success of the present call. This + /// callback is essential for dirty region management. If not defined, all the + /// pixels on the screen will be rendered at every frame (regardless of + /// whether damage is actually being computed or not). This is because the + /// information that is passed along to the callback contains the frame and + /// buffer damage that are essential for dirty region management. + BoolPresentInfoCallback present_with_info; + /// Specifying this callback is a requirement for dirty region management. + /// Dirty region management will only render the areas of the screen that have + /// changed in between frames, greatly reducing rendering times and energy + /// consumption. To take advantage of these benefits, it is necessary to + /// define populate_existing_damage as a callback that takes user + /// data, an FBO ID, and an existing damage FlutterDamage. The callback should + /// use the given FBO ID to identify the FBO's exisiting damage (i.e. areas + /// that have changed since the FBO was last used) and use it to populate the + /// given existing damage variable. This callback is dependent on either + /// fbo_callback or fbo_with_frame_info_callback being defined as they are + /// responsible for providing populate_existing_damage with the FBO's + /// ID. Not specifying populate_existing_damage will result in full + /// repaint (i.e. rendering all the pixels on the screen at every frame). + FlutterFrameBufferWithDamageCallback populate_existing_damage; +} FlutterOpenGLRendererConfig; + +/// Alias for id. +typedef const void* FlutterMetalDeviceHandle; + +/// Alias for id. +typedef const void* FlutterMetalCommandQueueHandle; + +/// Alias for id. +typedef const void* FlutterMetalTextureHandle; + +/// Pixel format for the external texture. +typedef enum { + kYUVA, + kRGBA, +} FlutterMetalExternalTexturePixelFormat; + +/// YUV color space for the YUV external texture. +typedef enum { + kBT601FullRange, + kBT601LimitedRange, +} FlutterMetalExternalTextureYUVColorSpace; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalExternalTexture). + size_t struct_size; + /// Height of the texture. + size_t width; + /// Height of the texture. + size_t height; + /// The pixel format type of the external. + FlutterMetalExternalTexturePixelFormat pixel_format; + /// Represents the size of the `textures` array. + size_t num_textures; + /// Supported textures are YUVA and RGBA, in case of YUVA we expect 2 texture + /// handles to be provided by the embedder, Y first and UV next. In case of + /// RGBA only one should be passed. + /// These are individually aliases for id. These textures are + /// retained by the engine for the period of the composition. Once these + /// textures have been unregistered via the + /// `FlutterEngineUnregisterExternalTexture`, the embedder has to release + /// these textures. + FlutterMetalTextureHandle* textures; + /// The YUV color space of the YUV external texture. + FlutterMetalExternalTextureYUVColorSpace yuv_color_space; +} FlutterMetalExternalTexture; + +/// Callback to provide an external texture for a given texture_id. +/// See: external_texture_frame_callback. +typedef bool (*FlutterMetalTextureFrameCallback)( + void* /* user data */, + int64_t /* texture identifier */, + size_t /* width */, + size_t /* height */, + FlutterMetalExternalTexture* /* texture out */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalTexture). + size_t struct_size; + /// Embedder provided unique identifier to the texture buffer. Given that the + /// `texture` handle is passed to the engine to render to, the texture buffer + /// is itself owned by the embedder. This `texture_id` is then also given to + /// the embedder in the present callback. + int64_t texture_id; + /// Handle to the MTLTexture that is owned by the embedder. Engine will render + /// the frame into this texture. + /// + /// A NULL texture is considered invalid. + FlutterMetalTextureHandle texture; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; +} FlutterMetalTexture; + +/// Callback for when a metal texture is requested. +typedef FlutterMetalTexture (*FlutterMetalTextureCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a metal texture is presented. The texture_id here +/// corresponds to the texture_id provided by the embedder in the +/// `FlutterMetalTextureCallback` callback. +typedef bool (*FlutterMetalPresentCallback)( + void* /* user data */, + const FlutterMetalTexture* /* texture */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalRendererConfig). + size_t struct_size; + /// Alias for id. + FlutterMetalDeviceHandle device; + /// Alias for id. + FlutterMetalCommandQueueHandle present_command_queue; + /// The callback that gets invoked when the engine requests the embedder for a + /// texture to render to. + FlutterMetalTextureCallback get_next_drawable_callback; + /// The callback presented to the embedder to present a fully populated metal + /// texture to the user. + FlutterMetalPresentCallback present_drawable_callback; + /// When the embedder specifies that a texture has a frame available, the + /// engine will call this method (on an internal engine managed thread) so + /// that external texture details can be supplied to the engine for subsequent + /// composition. + FlutterMetalTextureFrameCallback external_texture_frame_callback; +} FlutterMetalRendererConfig; + +/// Alias for VkInstance. +typedef void* FlutterVulkanInstanceHandle; + +/// Alias for VkPhysicalDevice. +typedef void* FlutterVulkanPhysicalDeviceHandle; + +/// Alias for VkDevice. +typedef void* FlutterVulkanDeviceHandle; + +/// Alias for VkQueue. +typedef void* FlutterVulkanQueueHandle; + +/// Alias for VkImage. +typedef uint64_t FlutterVulkanImageHandle; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanImage). + size_t struct_size; + /// Handle to the VkImage that is owned by the embedder. The engine will + /// bind this image for writing the frame. + FlutterVulkanImageHandle image; + /// The VkFormat of the image (for example: VK_FORMAT_R8G8B8A8_UNORM). + uint32_t format; +} FlutterVulkanImage; + +/// Callback to fetch a Vulkan function pointer for a given instance. Normally, +/// this should return the results of vkGetInstanceProcAddr. +typedef void* (*FlutterVulkanInstanceProcAddressCallback)( + void* /* user data */, + FlutterVulkanInstanceHandle /* instance */, + const char* /* name */); + +/// Callback for when a VkImage is requested. +typedef FlutterVulkanImage (*FlutterVulkanImageCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a VkImage has been written to and is ready for use by the +/// embedder. +typedef bool (*FlutterVulkanPresentCallback)( + void* /* user data */, + const FlutterVulkanImage* /* image */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanRendererConfig). + size_t struct_size; + + /// The Vulkan API version. This should match the value set in + /// VkApplicationInfo::apiVersion when the VkInstance was created. + uint32_t version; + /// VkInstance handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanInstanceHandle instance; + /// VkPhysicalDevice handle. + FlutterVulkanPhysicalDeviceHandle physical_device; + /// VkDevice handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanDeviceHandle device; + /// The queue family index of the VkQueue supplied in the next field. + uint32_t queue_family_index; + /// VkQueue handle. + FlutterVulkanQueueHandle queue; + /// The number of instance extensions available for enumerating in the next + /// field. + size_t enabled_instance_extension_count; + /// Array of enabled instance extension names. This should match the names + /// passed to `VkInstanceCreateInfo.ppEnabledExtensionNames` when the instance + /// was created, but any subset of enabled instance extensions may be + /// specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + const char** enabled_instance_extensions; + /// The number of device extensions available for enumerating in the next + /// field. + size_t enabled_device_extension_count; + /// Array of enabled logical device extension names. This should match the + /// names passed to `VkDeviceCreateInfo.ppEnabledExtensionNames` when the + /// logical device was created, but any subset of enabled logical device + /// extensions may be specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + /// For example: VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME + const char** enabled_device_extensions; + /// The callback invoked when resolving Vulkan function pointers. + FlutterVulkanInstanceProcAddressCallback get_instance_proc_address_callback; + /// The callback invoked when the engine requests a VkImage from the embedder + /// for rendering the next frame. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanImageCallback get_next_image_callback; + /// The callback invoked when a VkImage has been written to and is ready for + /// use by the embedder. Prior to calling this callback, the engine performs + /// a host sync, and so the VkImage can be used in a pipeline by the embedder + /// without any additional synchronization. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanPresentCallback present_image_callback; + +} FlutterVulkanRendererConfig; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSoftwareRendererConfig). + size_t struct_size; + /// The callback presented to the embedder to present a fully populated buffer + /// to the user. The pixel format of the buffer is the native 32-bit RGBA + /// format. The buffer is owned by the Flutter engine and must be copied in + /// this callback if needed. + SoftwareSurfacePresentCallback surface_present_callback; +} FlutterSoftwareRendererConfig; + +typedef struct { + FlutterRendererType type; + union { + FlutterOpenGLRendererConfig open_gl; + FlutterSoftwareRendererConfig software; + FlutterMetalRendererConfig metal; + FlutterVulkanRendererConfig vulkan; + }; +} FlutterRendererConfig; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). + size_t struct_size; + /// Physical width of the window. + size_t width; + /// Physical height of the window. + size_t height; + /// Scale factor for the physical screen. + double pixel_ratio; + /// Horizontal physical location of the left side of the window on the screen. + size_t left; + /// Vertical physical location of the top of the window on the screen. + size_t top; + /// Top inset of window. + double physical_view_inset_top; + /// Right inset of window. + double physical_view_inset_right; + /// Bottom inset of window. + double physical_view_inset_bottom; + /// Left inset of window. + double physical_view_inset_left; +} FlutterWindowMetricsEvent; + +/// The phase of the pointer event. +typedef enum { + kCancel, + /// The pointer, which must have been down (see kDown), is now up. + /// + /// For touch, this means that the pointer is no longer in contact with the + /// screen. For a mouse, it means the last button was released. Note that if + /// any other buttons are still pressed when one button is released, that + /// should be sent as a kMove rather than a kUp. + kUp, + /// The pointer, which must have been up, is now down. + /// + /// For touch, this means that the pointer has come into contact with the + /// screen. For a mouse, it means a button is now pressed. Note that if any + /// other buttons are already pressed when a new button is pressed, that + /// should be sent as a kMove rather than a kDown. + kDown, + /// The pointer moved while down. + /// + /// This is also used for changes in button state that don't cause a kDown or + /// kUp, such as releasing one of two pressed buttons. + kMove, + /// The pointer is now sending input to Flutter. For instance, a mouse has + /// entered the area where the Flutter content is displayed. + /// + /// A pointer should always be added before sending any other events. + kAdd, + /// The pointer is no longer sending input to Flutter. For instance, a mouse + /// has left the area where the Flutter content is displayed. + /// + /// A removed pointer should no longer send events until sending a new kAdd. + kRemove, + /// The pointer moved while up. + kHover, + /// A pan/zoom started on this pointer. + kPanZoomStart, + /// The pan/zoom updated. + kPanZoomUpdate, + /// The pan/zoom ended. + kPanZoomEnd, +} FlutterPointerPhase; + +/// The device type that created a pointer event. +typedef enum { + kFlutterPointerDeviceKindMouse = 1, + kFlutterPointerDeviceKindTouch, + kFlutterPointerDeviceKindStylus, + kFlutterPointerDeviceKindTrackpad, +} FlutterPointerDeviceKind; + +/// Flags for the `buttons` field of `FlutterPointerEvent` when `device_kind` +/// is `kFlutterPointerDeviceKindMouse`. +typedef enum { + kFlutterPointerButtonMousePrimary = 1 << 0, + kFlutterPointerButtonMouseSecondary = 1 << 1, + kFlutterPointerButtonMouseMiddle = 1 << 2, + kFlutterPointerButtonMouseBack = 1 << 3, + kFlutterPointerButtonMouseForward = 1 << 4, + /// If a mouse has more than five buttons, send higher bit shifted values + /// corresponding to the button number: 1 << 5 for the 6th, etc. +} FlutterPointerMouseButtons; + +/// The type of a pointer signal. +typedef enum { + kFlutterPointerSignalKindNone, + kFlutterPointerSignalKindScroll, + kFlutterPointerSignalKindScrollInertiaCancel, + kFlutterPointerSignalKindScale, + kFlutterPointerSignalKindStylusAuxiliaryAction, +} FlutterPointerSignalKind; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterPointerEvent). + size_t struct_size; + FlutterPointerPhase phase; + /// The timestamp at which the pointer event was generated. The timestamp + /// should be specified in microseconds and the clock should be the same as + /// that used by `FlutterEngineGetCurrentTime`. + size_t timestamp; + /// The x coordinate of the pointer event in physical pixels. + double x; + /// The y coordinate of the pointer event in physical pixels. + double y; + /// An optional device identifier. If this is not specified, it is assumed + /// that the embedder has no multi-touch capability. + int32_t device; + FlutterPointerSignalKind signal_kind; + /// The x offset of the scroll in physical pixels. + double scroll_delta_x; + /// The y offset of the scroll in physical pixels. + double scroll_delta_y; + /// The type of the device generating this event. + /// Backwards compatibility note: If this is not set, the device will be + /// treated as a mouse, with the primary button set for `kDown` and `kMove`. + /// If set explicitly to `kFlutterPointerDeviceKindMouse`, you must set the + /// correct buttons. + FlutterPointerDeviceKind device_kind; + /// The buttons currently pressed, if any. + int64_t buttons; + /// The x offset of the pan/zoom in physical pixels. + double pan_x; + /// The y offset of the pan/zoom in physical pixels. + double pan_y; + /// The scale of the pan/zoom, where 1.0 is the initial scale. + double scale; + /// The rotation of the pan/zoom in radians, where 0.0 is the initial angle. + double rotation; +} FlutterPointerEvent; + +typedef enum { + kFlutterKeyEventTypeUp = 1, + kFlutterKeyEventTypeDown, + kFlutterKeyEventTypeRepeat, +} FlutterKeyEventType; + +/// A structure to represent a key event. +/// +/// Sending `FlutterKeyEvent` via `FlutterEngineSendKeyEvent` results in a +/// corresponding `FlutterKeyEvent` to be dispatched in the framework. It is +/// embedder's responsibility to ensure the regularity of sent events, since the +/// framework only performs simple one-to-one mapping. The events must conform +/// the following rules: +/// +/// * Each key press sequence shall consist of one key down event (`kind` being +/// `kFlutterKeyEventTypeDown`), zero or more repeat events, and one key up +/// event, representing a physical key button being pressed, held, and +/// released. +/// * All events throughout a key press sequence shall have the same `physical` +/// and `logical`. Having different `character`s is allowed. +/// +/// A `FlutterKeyEvent` with `physical` 0 and `logical` 0 is an empty event. +/// This is the only case either `physical` or `logical` can be 0. An empty +/// event must be sent if a key message should be converted to no +/// `FlutterKeyEvent`s, for example, when a key down message is received for a +/// key that has already been pressed according to the record. This is to ensure +/// some `FlutterKeyEvent` arrives at the framework before raw key message. +/// See https://github.com/flutter/flutter/issues/87230. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterKeyEvent). + size_t struct_size; + /// The timestamp at which the key event was generated. The timestamp should + /// be specified in microseconds and the clock should be the same as that used + /// by `FlutterEngineGetCurrentTime`. + double timestamp; + /// The event kind. + FlutterKeyEventType type; + /// The USB HID code for the physical key of the event. + /// + /// For the full definition and list of pre-defined physical keys, see + /// `PhysicalKeyboardKey` from the framework. + /// + /// The only case that `physical` might be 0 is when this is an empty event. + /// See `FlutterKeyEvent` for introduction. + uint64_t physical; + /// The key ID for the logical key of this event. + /// + /// For the full definition and a list of pre-defined logical keys, see + /// `LogicalKeyboardKey` from the framework. + /// + /// The only case that `logical` might be 0 is when this is an empty event. + /// See `FlutterKeyEvent` for introduction. + uint64_t logical; + /// Null-terminated character input from the event. Can be null. Ignored for + /// up events. + const char* character; + /// True if this event does not correspond to a native event. + /// + /// The embedder is likely to skip events and/or construct new events that do + /// not correspond to any native events in order to conform the regularity + /// of events (as documented in `FlutterKeyEvent`). An example is when a key + /// up is missed due to loss of window focus, on a platform that provides + /// query to key pressing status, the embedder might realize that the key has + /// been released at the next key event, and should construct a synthesized up + /// event immediately before the actual event. + /// + /// An event being synthesized means that the `timestamp` might greatly + /// deviate from the actual time when the event occurs physically. + bool synthesized; +} FlutterKeyEvent; + +typedef void (*FlutterKeyEventCallback)(bool /* handled */, + void* /* user_data */); + +struct _FlutterPlatformMessageResponseHandle; +typedef struct _FlutterPlatformMessageResponseHandle + FlutterPlatformMessageResponseHandle; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterPlatformMessage). + size_t struct_size; + const char* channel; + const uint8_t* message; + size_t message_size; + /// The response handle on which to invoke + /// `FlutterEngineSendPlatformMessageResponse` when the response is ready. + /// `FlutterEngineSendPlatformMessageResponse` must be called for all messages + /// received by the embedder. Failure to call + /// `FlutterEngineSendPlatformMessageResponse` will cause a memory leak. It is + /// not safe to send multiple responses on a single response object. + const FlutterPlatformMessageResponseHandle* response_handle; +} FlutterPlatformMessage; + +typedef void (*FlutterPlatformMessageCallback)( + const FlutterPlatformMessage* /* message*/, + void* /* user data */); + +typedef void (*FlutterDataCallback)(const uint8_t* /* data */, + size_t /* size */, + void* /* user data */); + +/// The identifier of the platform view. This identifier is specified by the +/// application when a platform view is added to the scene via the +/// `SceneBuilder.addPlatformView` call. +typedef int64_t FlutterPlatformViewIdentifier; + +/// `FlutterSemanticsNode` ID used as a sentinel to signal the end of a batch of +/// semantics node updates. This is unused if using +/// `FlutterUpdateSemanticsCallback2`. +FLUTTER_EXPORT +extern const int32_t kFlutterSemanticsNodeIdBatchEnd; + +/// A node that represents some semantic data. +/// +/// The semantics tree is maintained during the semantics phase of the pipeline +/// (i.e., during PipelineOwner.flushSemantics), which happens after +/// compositing. Updates are then pushed to embedders via the registered +/// `FlutterUpdateSemanticsCallback`. +/// +/// @deprecated Use `FlutterSemanticsNode2` instead. In order to preserve +/// ABI compatibility for existing users, no new fields will be +/// added to this struct. New fields will continue to be added +/// to `FlutterSemanticsNode2`. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSemanticsNode). + size_t struct_size; + /// The unique identifier for this node. + int32_t id; + /// The set of semantics flags associated with this node. + FlutterSemanticsFlag flags; + /// The set of semantics actions applicable to this node. + FlutterSemanticsAction actions; + /// The position at which the text selection originates. + int32_t text_selection_base; + /// The position at which the text selection terminates. + int32_t text_selection_extent; + /// The total number of scrollable children that contribute to semantics. + int32_t scroll_child_count; + /// The index of the first visible semantic child of a scroll node. + int32_t scroll_index; + /// The current scrolling position in logical pixels if the node is + /// scrollable. + double scroll_position; + /// The maximum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_max; + /// The minimum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_min; + /// The elevation along the z-axis at which the rect of this semantics node is + /// located above its parent. + double elevation; + /// Describes how much space the semantics node takes up along the z-axis. + double thickness; + /// A textual description of the node. + const char* label; + /// A brief description of the result of performing an action on the node. + const char* hint; + /// A textual description of the current value of the node. + const char* value; + /// A value that `value` will have after a kFlutterSemanticsActionIncrease` + /// action has been performed. + const char* increased_value; + /// A value that `value` will have after a kFlutterSemanticsActionDecrease` + /// action has been performed. + const char* decreased_value; + /// The reading direction for `label`, `value`, `hint`, `increasedValue`, + /// `decreasedValue`, and `tooltip`. + FlutterTextDirection text_direction; + /// The bounding box for this node in its coordinate system. + FlutterRect rect; + /// The transform from this node's coordinate system to its parent's + /// coordinate system. + FlutterTransformation transform; + /// The number of children this node has. + size_t child_count; + /// Array of child node IDs in traversal order. Has length `child_count`. + const int32_t* children_in_traversal_order; + /// Array of child node IDs in hit test order. Has length `child_count`. + const int32_t* children_in_hit_test_order; + /// The number of custom accessibility action associated with this node. + size_t custom_accessibility_actions_count; + /// Array of `FlutterSemanticsCustomAction` IDs associated with this node. + /// Has length `custom_accessibility_actions_count`. + const int32_t* custom_accessibility_actions; + /// Identifier of the platform view associated with this semantics node, or + /// -1 if none. + FlutterPlatformViewIdentifier platform_view_id; + /// A textual tooltip attached to the node. + const char* tooltip; +} FlutterSemanticsNode; + +/// A node in the Flutter semantics tree. +/// +/// The semantics tree is maintained during the semantics phase of the pipeline +/// (i.e., during PipelineOwner.flushSemantics), which happens after +/// compositing. Updates are then pushed to embedders via the registered +/// `FlutterUpdateSemanticsCallback2`. +/// +/// @see https://api.flutter.dev/flutter/semantics/SemanticsNode-class.html +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSemanticsNode). + size_t struct_size; + /// The unique identifier for this node. + int32_t id; + /// The set of semantics flags associated with this node. + FlutterSemanticsFlag flags; + /// The set of semantics actions applicable to this node. + FlutterSemanticsAction actions; + /// The position at which the text selection originates. + int32_t text_selection_base; + /// The position at which the text selection terminates. + int32_t text_selection_extent; + /// The total number of scrollable children that contribute to semantics. + int32_t scroll_child_count; + /// The index of the first visible semantic child of a scroll node. + int32_t scroll_index; + /// The current scrolling position in logical pixels if the node is + /// scrollable. + double scroll_position; + /// The maximum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_max; + /// The minimum in-range value for `scrollPosition` if the node is scrollable. + double scroll_extent_min; + /// The elevation along the z-axis at which the rect of this semantics node is + /// located above its parent. + double elevation; + /// Describes how much space the semantics node takes up along the z-axis. + double thickness; + /// A textual description of the node. + const char* label; + /// A brief description of the result of performing an action on the node. + const char* hint; + /// A textual description of the current value of the node. + const char* value; + /// A value that `value` will have after a kFlutterSemanticsActionIncrease` + /// action has been performed. + const char* increased_value; + /// A value that `value` will have after a kFlutterSemanticsActionDecrease` + /// action has been performed. + const char* decreased_value; + /// The reading direction for `label`, `value`, `hint`, `increasedValue`, + /// `decreasedValue`, and `tooltip`. + FlutterTextDirection text_direction; + /// The bounding box for this node in its coordinate system. + FlutterRect rect; + /// The transform from this node's coordinate system to its parent's + /// coordinate system. + FlutterTransformation transform; + /// The number of children this node has. + size_t child_count; + /// Array of child node IDs in traversal order. Has length `child_count`. + const int32_t* children_in_traversal_order; + /// Array of child node IDs in hit test order. Has length `child_count`. + const int32_t* children_in_hit_test_order; + /// The number of custom accessibility action associated with this node. + size_t custom_accessibility_actions_count; + /// Array of `FlutterSemanticsCustomAction` IDs associated with this node. + /// Has length `custom_accessibility_actions_count`. + const int32_t* custom_accessibility_actions; + /// Identifier of the platform view associated with this semantics node, or + /// -1 if none. + FlutterPlatformViewIdentifier platform_view_id; + /// A textual tooltip attached to the node. + const char* tooltip; +} FlutterSemanticsNode2; + +/// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a +/// batch of semantics custom action updates. This is unused if using +/// `FlutterUpdateSemanticsCallback2`. +FLUTTER_EXPORT +extern const int32_t kFlutterSemanticsCustomActionIdBatchEnd; + +/// A custom semantics action, or action override. +/// +/// Custom actions can be registered by applications in order to provide +/// semantic actions other than the standard actions available through the +/// `FlutterSemanticsAction` enum. +/// +/// Action overrides are custom actions that the application developer requests +/// to be used in place of the standard actions in the `FlutterSemanticsAction` +/// enum. +/// +/// @deprecated Use `FlutterSemanticsCustomAction2` instead. In order to +/// preserve ABI compatility for existing users, no new fields +/// will be added to this struct. New fields will continue to +/// be added to `FlutterSemanticsCustomAction2`. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsCustomAction). + size_t struct_size; + /// The unique custom action or action override ID. + int32_t id; + /// For overridden standard actions, corresponds to the + /// `FlutterSemanticsAction` to override. + FlutterSemanticsAction override_action; + /// The user-readable name of this custom semantics action. + const char* label; + /// The hint description of this custom semantics action. + const char* hint; +} FlutterSemanticsCustomAction; + +/// A custom semantics action, or action override. +/// +/// Custom actions can be registered by applications in order to provide +/// semantic actions other than the standard actions available through the +/// `FlutterSemanticsAction` enum. +/// +/// Action overrides are custom actions that the application developer requests +/// to be used in place of the standard actions in the `FlutterSemanticsAction` +/// enum. +/// +/// @see +/// https://api.flutter.dev/flutter/semantics/CustomSemanticsAction-class.html +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsCustomAction). + size_t struct_size; + /// The unique custom action or action override ID. + int32_t id; + /// For overridden standard actions, corresponds to the + /// `FlutterSemanticsAction` to override. + FlutterSemanticsAction override_action; + /// The user-readable name of this custom semantics action. + const char* label; + /// The hint description of this custom semantics action. + const char* hint; +} FlutterSemanticsCustomAction2; + +/// A batch of updates to semantics nodes and custom actions. +/// +/// @deprecated Use `FlutterSemanticsUpdate2` instead. Adding members +/// to `FlutterSemanticsNode` or `FlutterSemanticsCustomAction` +/// breaks the ABI of this struct. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsUpdate). + size_t struct_size; + /// The number of semantics node updates. + size_t nodes_count; + // Array of semantics nodes. Has length `nodes_count`. + FlutterSemanticsNode* nodes; + /// The number of semantics custom action updates. + size_t custom_actions_count; + /// Array of semantics custom actions. Has length `custom_actions_count`. + FlutterSemanticsCustomAction* custom_actions; +} FlutterSemanticsUpdate; + +/// A batch of updates to semantics nodes and custom actions. +typedef struct { + /// The size of the struct. Must be sizeof(FlutterSemanticsUpdate2). + size_t struct_size; + /// The number of semantics node updates. + size_t node_count; + // Array of semantics node pointers. Has length `node_count`. + FlutterSemanticsNode2** nodes; + /// The number of semantics custom action updates. + size_t custom_action_count; + /// Array of semantics custom action pointers. Has length + /// `custom_action_count`. + FlutterSemanticsCustomAction2** custom_actions; +} FlutterSemanticsUpdate2; + +typedef void (*FlutterUpdateSemanticsNodeCallback)( + const FlutterSemanticsNode* /* semantics node */, + void* /* user data */); + +typedef void (*FlutterUpdateSemanticsCustomActionCallback)( + const FlutterSemanticsCustomAction* /* semantics custom action */, + void* /* user data */); + +typedef void (*FlutterUpdateSemanticsCallback)( + const FlutterSemanticsUpdate* /* semantics update */, + void* /* user data*/); + +typedef void (*FlutterUpdateSemanticsCallback2)( + const FlutterSemanticsUpdate2* /* semantics update */, + void* /* user data*/); + +typedef struct _FlutterTaskRunner* FlutterTaskRunner; + +typedef struct { + FlutterTaskRunner runner; + uint64_t task; +} FlutterTask; + +typedef void (*FlutterTaskRunnerPostTaskCallback)( + FlutterTask /* task */, + uint64_t /* target time nanos */, + void* /* user data */); + +/// An interface used by the Flutter engine to execute tasks at the target time +/// on a specified thread. There should be a 1-1 relationship between a thread +/// and a task runner. It is undefined behavior to run a task on a thread that +/// is not associated with its task runner. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterTaskRunnerDescription). + size_t struct_size; + void* user_data; + /// May be called from any thread. Should return true if tasks posted on the + /// calling thread will be run on that same thread. + /// + /// @attention This field is required. + BoolCallback runs_task_on_current_thread_callback; + /// May be called from any thread. The given task should be executed by the + /// embedder on the thread associated with that task runner by calling + /// `FlutterEngineRunTask` at the given target time. The system monotonic + /// clock should be used for the target time. The target time is the absolute + /// time from epoch (NOT a delta) at which the task must be returned back to + /// the engine on the correct thread. If the embedder needs to calculate a + /// delta, `FlutterEngineGetCurrentTime` may be called and the difference used + /// as the delta. + /// + /// @attention This field is required. + FlutterTaskRunnerPostTaskCallback post_task_callback; + /// A unique identifier for the task runner. If multiple task runners service + /// tasks on the same thread, their identifiers must match. + size_t identifier; +} FlutterTaskRunnerDescription; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterCustomTaskRunners). + size_t struct_size; + /// Specify the task runner for the thread on which the `FlutterEngineRun` + /// call is made. The same task runner description can be specified for both + /// the render and platform task runners. This makes the Flutter engine use + /// the same thread for both task runners. + const FlutterTaskRunnerDescription* platform_task_runner; + /// Specify the task runner for the thread on which the render tasks will be + /// run. The same task runner description can be specified for both the render + /// and platform task runners. This makes the Flutter engine use the same + /// thread for both task runners. + const FlutterTaskRunnerDescription* render_task_runner; + /// Specify a callback that is used to set the thread priority for embedder + /// task runners. + void (*thread_priority_setter)(FlutterThreadPriority); +} FlutterCustomTaskRunners; + +typedef struct { + /// The type of the OpenGL backing store. Currently, it can either be a + /// texture or a framebuffer. + FlutterOpenGLTargetType type; + union { + /// A texture for Flutter to render into. + FlutterOpenGLTexture texture; + /// A framebuffer for Flutter to render into. The embedder must ensure that + /// the framebuffer is complete. + FlutterOpenGLFramebuffer framebuffer; + }; +} FlutterOpenGLBackingStore; + +typedef struct { + /// A pointer to the raw bytes of the allocation described by this software + /// backing store. + const void* allocation; + /// The number of bytes in a single row of the allocation. + size_t row_bytes; + /// The number of rows in the allocation. + size_t height; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; +} FlutterSoftwareBackingStore; + +typedef struct { + size_t struct_size; + /// A pointer to the raw bytes of the allocation described by this software + /// backing store. + const void* allocation; + /// The number of bytes in a single row of the allocation. + size_t row_bytes; + /// The number of rows in the allocation. + size_t height; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; + /// The pixel format that the engine should use to render into the allocation. + /// In most cases, kR + FlutterSoftwarePixelFormat pixel_format; +} FlutterSoftwareBackingStore2; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalBackingStore). + size_t struct_size; + union { + // A Metal texture for Flutter to render into. Ownership is not transferred + // to Flutter; the texture is CFRetained on successfully being passed in and + // CFReleased when no longer used. + FlutterMetalTexture texture; + }; +} FlutterMetalBackingStore; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanBackingStore). + size_t struct_size; + /// The image that the layer will be rendered to. This image must already be + /// available for the engine to bind for writing when it's given to the engine + /// via the backing store creation callback. The engine will perform a host + /// sync for all layers prior to calling the compositor present callback, and + /// so the written layer images can be freely bound by the embedder without + /// any additional synchronization. + const FlutterVulkanImage* image; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; +} FlutterVulkanBackingStore; + +typedef enum { + /// Indicates that the Flutter application requested that an opacity be + /// applied to the platform view. + kFlutterPlatformViewMutationTypeOpacity, + /// Indicates that the Flutter application requested that the platform view be + /// clipped using a rectangle. + kFlutterPlatformViewMutationTypeClipRect, + /// Indicates that the Flutter application requested that the platform view be + /// clipped using a rounded rectangle. + kFlutterPlatformViewMutationTypeClipRoundedRect, + /// Indicates that the Flutter application requested that the platform view be + /// transformed before composition. + kFlutterPlatformViewMutationTypeTransformation, +} FlutterPlatformViewMutationType; + +typedef struct { + /// The type of the mutation described by the subsequent union. + FlutterPlatformViewMutationType type; + union { + double opacity; + FlutterRect clip_rect; + FlutterRoundedRect clip_rounded_rect; + FlutterTransformation transformation; + }; +} FlutterPlatformViewMutation; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterPlatformView). + size_t struct_size; + /// The identifier of this platform view. This identifier is specified by the + /// application when a platform view is added to the scene via the + /// `SceneBuilder.addPlatformView` call. + FlutterPlatformViewIdentifier identifier; + /// The number of mutations to be applied to the platform view by the embedder + /// before on-screen composition. + size_t mutations_count; + /// The mutations to be applied by this platform view before it is composited + /// on-screen. The Flutter application may transform the platform view but + /// these transformations cannot be affected by the Flutter compositor because + /// it does not render platform views. Since the embedder is responsible for + /// composition of these views, it is also the embedder's responsibility to + /// affect the appropriate transformation. + /// + /// The mutations must be applied in order. The mutations done in the + /// collection don't take into account the device pixel ratio or the root + /// surface transformation. If these exist, the first mutation in the list + /// will be a transformation mutation to make sure subsequent mutations are in + /// the correct coordinate space. + const FlutterPlatformViewMutation** mutations; +} FlutterPlatformView; + +typedef enum { + /// Specifies an OpenGL backing store. Can either be an OpenGL texture or + /// framebuffer. + kFlutterBackingStoreTypeOpenGL, + /// Specified an software allocation for Flutter to render into using the CPU. + kFlutterBackingStoreTypeSoftware, + /// Specifies a Metal backing store. This is backed by a Metal texture. + kFlutterBackingStoreTypeMetal, + /// Specifies a Vulkan backing store. This is backed by a Vulkan VkImage. + kFlutterBackingStoreTypeVulkan, + /// Specifies a allocation that the engine should render into using + /// software rendering. + kFlutterBackingStoreTypeSoftware2, +} FlutterBackingStoreType; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStore). + size_t struct_size; + /// A baton that is not interpreted by the engine in any way. The embedder may + /// use this to associate resources that are tied to the lifecycle of the + /// `FlutterBackingStore`. + void* user_data; + /// Specifies the type of backing store. + FlutterBackingStoreType type; + /// Indicates if this backing store was updated since the last time it was + /// associated with a presented layer. + bool did_update; + union { + /// The description of the OpenGL backing store. + FlutterOpenGLBackingStore open_gl; + /// The description of the software backing store. + FlutterSoftwareBackingStore software; + /// The description of the software backing store. + FlutterSoftwareBackingStore2 software2; + // The description of the Metal backing store. + FlutterMetalBackingStore metal; + // The description of the Vulkan backing store. + FlutterVulkanBackingStore vulkan; + }; +} FlutterBackingStore; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStoreConfig). + size_t struct_size; + /// The size of the render target the engine expects to render into. + FlutterSize size; +} FlutterBackingStoreConfig; + +typedef enum { + /// Indicates that the contents of this layer are rendered by Flutter into a + /// backing store. + kFlutterLayerContentTypeBackingStore, + /// Indicates that the contents of this layer are determined by the embedder. + kFlutterLayerContentTypePlatformView, +} FlutterLayerContentType; + +typedef struct { + /// This size of this struct. Must be sizeof(FlutterLayer). + size_t struct_size; + /// Each layer displays contents in one way or another. The type indicates + /// whether those contents are specified by Flutter or the embedder. + FlutterLayerContentType type; + union { + /// Indicates that the contents of this layer are rendered by Flutter into a + /// backing store. + const FlutterBackingStore* backing_store; + /// Indicates that the contents of this layer are determined by the + /// embedder. + const FlutterPlatformView* platform_view; + }; + /// The offset of this layer (in physical pixels) relative to the top left of + /// the root surface used by the engine. + FlutterPoint offset; + /// The size of the layer (in physical pixels). + FlutterSize size; +} FlutterLayer; + +typedef bool (*FlutterBackingStoreCreateCallback)( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out, + void* user_data); + +typedef bool (*FlutterBackingStoreCollectCallback)( + const FlutterBackingStore* renderer, + void* user_data); + +typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, + size_t layers_count, + void* user_data); + +typedef struct { + /// This size of this struct. Must be sizeof(FlutterCompositor). + size_t struct_size; + /// A baton that in not interpreted by the engine in any way. If it passed + /// back to the embedder in `FlutterCompositor.create_backing_store_callback`, + /// `FlutterCompositor.collect_backing_store_callback` and + /// `FlutterCompositor.present_layers_callback` + void* user_data; + /// A callback invoked by the engine to obtain a backing store for a specific + /// `FlutterLayer`. + /// + /// On ABI stability: Callers must take care to restrict access within + /// `FlutterBackingStore::struct_size` when specifying a new backing store to + /// the engine. This only matters if the embedder expects to be used with + /// engines older than the version whose headers it used during compilation. + FlutterBackingStoreCreateCallback create_backing_store_callback; + /// A callback invoked by the engine to release the backing store. The + /// embedder may collect any resources associated with the backing store. + FlutterBackingStoreCollectCallback collect_backing_store_callback; + /// Callback invoked by the engine to composite the contents of each layer + /// onto the screen. + FlutterLayersPresentCallback present_layers_callback; + /// Avoid caching backing stores provided by this compositor. + bool avoid_backing_store_cache; +} FlutterCompositor; + +typedef struct { + /// This size of this struct. Must be sizeof(FlutterLocale). + size_t struct_size; + /// The language code of the locale. For example, "en". This is a required + /// field. The string must be null terminated. It may be collected after the + /// call to `FlutterEngineUpdateLocales`. + const char* language_code; + /// The country code of the locale. For example, "US". This is a an optional + /// field. The string must be null terminated if present. It may be collected + /// after the call to `FlutterEngineUpdateLocales`. If not present, a + /// `nullptr` may be specified. + const char* country_code; + /// The script code of the locale. This is a an optional field. The string + /// must be null terminated if present. It may be collected after the call to + /// `FlutterEngineUpdateLocales`. If not present, a `nullptr` may be + /// specified. + const char* script_code; + /// The variant code of the locale. This is a an optional field. The string + /// must be null terminated if present. It may be collected after the call to + /// `FlutterEngineUpdateLocales`. If not present, a `nullptr` may be + /// specified. + const char* variant_code; +} FlutterLocale; + +/// Callback that returns the system locale. +/// +/// Embedders that implement this callback should return the `FlutterLocale` +/// from the `supported_locales` list that most closely matches the +/// user/device's preferred locale. +/// +/// This callback does not currently provide the user_data baton. +/// https://github.com/flutter/flutter/issues/79826 +typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( + const FlutterLocale** /* supported_locales*/, + size_t /* Number of locales*/); + +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + +typedef struct { + /// This size of this struct. Must be sizeof(FlutterDisplay). + size_t struct_size; + + FlutterEngineDisplayId display_id; + + /// This is set to true if the embedder only has one display. In cases where + /// this is set to true, the value of display_id is ignored. In cases where + /// this is not set to true, it is expected that a valid display_id be + /// provided. + bool single_display; + + /// This represents the refresh period in frames per second. This value may be + /// zero if the device is not running or unavailable or unknown. + double refresh_rate; +} FlutterEngineDisplay; + +/// The update type parameter that is passed to +/// `FlutterEngineNotifyDisplayUpdate`. +typedef enum { + /// `FlutterEngineDisplay`s that were active during start-up. A display is + /// considered active if: + /// 1. The frame buffer hardware is connected. + /// 2. The display is drawable, e.g. it isn't being mirrored from another + /// connected display or sleeping. + kFlutterEngineDisplaysUpdateTypeStartup, + kFlutterEngineDisplaysUpdateTypeCount, +} FlutterEngineDisplaysUpdateType; + +typedef int64_t FlutterEngineDartPort; + +typedef enum { + kFlutterEngineDartObjectTypeNull, + kFlutterEngineDartObjectTypeBool, + kFlutterEngineDartObjectTypeInt32, + kFlutterEngineDartObjectTypeInt64, + kFlutterEngineDartObjectTypeDouble, + kFlutterEngineDartObjectTypeString, + /// The object will be made available to Dart code as an instance of + /// Uint8List. + kFlutterEngineDartObjectTypeBuffer, +} FlutterEngineDartObjectType; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterEngineDartBuffer). + size_t struct_size; + /// An opaque baton passed back to the embedder when the + /// buffer_collect_callback is invoked. The engine does not interpret this + /// field in any way. + void* user_data; + /// This is an optional field. + /// + /// When specified, the engine will assume that the buffer is owned by the + /// embedder. When the data is no longer needed by any isolate, this callback + /// will be made on an internal engine managed thread. The embedder is free to + /// collect the buffer here. When this field is specified, it is the embedders + /// responsibility to keep the buffer alive and not modify it till this + /// callback is invoked by the engine. The user data specified in the callback + /// is the value of `user_data` field in this struct. + /// + /// When NOT specified, the VM creates an internal copy of the buffer. The + /// caller is free to modify the buffer as necessary or collect it immediately + /// after the call to `FlutterEnginePostDartObject`. + /// + /// @attention The buffer_collect_callback is will only be invoked by the + /// engine when the `FlutterEnginePostDartObject` method + /// returns kSuccess. In case of non-successful calls to this + /// method, it is the embedders responsibility to collect the + /// buffer. + VoidCallback buffer_collect_callback; + /// A pointer to the bytes of the buffer. When the buffer is owned by the + /// embedder (by specifying the `buffer_collect_callback`), Dart code may + /// modify that embedder owned buffer. For this reason, it is important that + /// this buffer not have page protections that restrict writing to this + /// buffer. + uint8_t* buffer; + /// The size of the buffer. + size_t buffer_size; +} FlutterEngineDartBuffer; + +/// This struct specifies the native representation of a Dart object that can be +/// sent via a send port to any isolate in the VM that has the corresponding +/// receive port. +/// +/// All fields in this struct are copied out in the call to +/// `FlutterEnginePostDartObject` and the caller is free to reuse or collect +/// this struct after that call. +typedef struct { + FlutterEngineDartObjectType type; + union { + bool bool_value; + int32_t int32_value; + int64_t int64_value; + double double_value; + /// A null terminated string. This string will be copied by the VM in the + /// call to `FlutterEnginePostDartObject` and must be collected by the + /// embedder after that call is made. + const char* string_value; + const FlutterEngineDartBuffer* buffer_value; + }; +} FlutterEngineDartObject; + +/// This enum allows embedders to determine the type of the engine thread in the +/// FlutterNativeThreadCallback. Based on the thread type, the embedder may be +/// able to tweak the thread priorities for optimum performance. +typedef enum { + /// The Flutter Engine considers the thread on which the FlutterEngineRun call + /// is made to be the platform thread. There is only one such thread per + /// engine instance. + kFlutterNativeThreadTypePlatform, + /// This is the thread the Flutter Engine uses to execute rendering commands + /// based on the selected client rendering API. There is only one such thread + /// per engine instance. + kFlutterNativeThreadTypeRender, + /// This is a dedicated thread on which the root Dart isolate is serviced. + /// There is only one such thread per engine instance. + kFlutterNativeThreadTypeUI, + /// Multiple threads are used by the Flutter engine to perform long running + /// background tasks. + kFlutterNativeThreadTypeWorker, +} FlutterNativeThreadType; + +/// A callback made by the engine in response to +/// `FlutterEnginePostCallbackOnAllNativeThreads` on all internal thread. +typedef void (*FlutterNativeThreadCallback)(FlutterNativeThreadType type, + void* user_data); + +/// AOT data source type. +typedef enum { + kFlutterEngineAOTDataSourceTypeElfPath +} FlutterEngineAOTDataSourceType; + +/// This struct specifies one of the various locations the engine can look for +/// AOT data sources. +typedef struct { + FlutterEngineAOTDataSourceType type; + union { + /// Absolute path to an ELF library file. + const char* elf_path; + }; +} FlutterEngineAOTDataSource; + +// Logging callback for Dart application messages. +// +// The `tag` parameter contains a null-terminated string containing a logging +// tag or component name that can be used to identify system log messages from +// the app. The `message` parameter contains a null-terminated string +// containing the message to be logged. `user_data` is a user data baton passed +// in `FlutterEngineRun`. +typedef void (*FlutterLogMessageCallback)(const char* /* tag */, + const char* /* message */, + void* /* user_data */); + +/// An opaque object that describes the AOT data that can be used to launch a +/// FlutterEngine instance in AOT mode. +typedef struct _FlutterEngineAOTData* FlutterEngineAOTData; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterProjectArgs). + size_t struct_size; + /// The path to the Flutter assets directory containing project assets. The + /// string can be collected after the call to `FlutterEngineRun` returns. The + /// string must be NULL terminated. + const char* assets_path; + /// The path to the Dart file containing the `main` entry point. + /// The string can be collected after the call to `FlutterEngineRun` returns. + /// The string must be NULL terminated. + /// + /// @deprecated As of Dart 2, running from Dart source is no longer + /// supported. Dart code should now be compiled to kernel form + /// and will be loaded by from `kernel_blob.bin` in the assets + /// directory. This struct member is retained for ABI + /// stability. + const char* main_path__unused__; + /// The path to the `.packages` file for the project. The string can be + /// collected after the call to `FlutterEngineRun` returns. The string must be + /// NULL terminated. + /// + /// @deprecated As of Dart 2, running from Dart source is no longer + /// supported. Dart code should now be compiled to kernel form + /// and will be loaded by from `kernel_blob.bin` in the assets + /// directory. This struct member is retained for ABI + /// stability. + const char* packages_path__unused__; + /// The path to the `icudtl.dat` file for the project. The string can be + /// collected after the call to `FlutterEngineRun` returns. The string must + /// be NULL terminated. + const char* icu_data_path; + /// The command line argument count used to initialize the project. + int command_line_argc; + /// The command line arguments used to initialize the project. The strings can + /// be collected after the call to `FlutterEngineRun` returns. The strings + /// must be `NULL` terminated. + /// + /// @attention The first item in the command line (if specified at all) is + /// interpreted as the executable name. So if an engine flag + /// needs to be passed into the same, it needs to not be the + /// very first item in the list. + /// + /// The set of engine flags are only meant to control + /// unstable features in the engine. Deployed applications should not pass any + /// command line arguments at all as they may affect engine stability at + /// runtime in the presence of un-sanitized input. The list of currently + /// recognized engine flags and their descriptions can be retrieved from the + /// `switches.h` engine source file. + const char* const* command_line_argv; + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to platform messages from the Dart application. + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. The second parameter, `user_data`, is supplied when + /// `FlutterEngineRun` or `FlutterEngineInitialize` is called. + FlutterPlatformMessageCallback platform_message_callback; + /// The VM snapshot data buffer used in AOT operation. This buffer must be + /// mapped in as read-only. For more information refer to the documentation on + /// the Wiki at + /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode + const uint8_t* vm_snapshot_data; + /// The size of the VM snapshot data buffer. If vm_snapshot_data is a symbol + /// reference, 0 may be passed here. + size_t vm_snapshot_data_size; + /// The VM snapshot instructions buffer used in AOT operation. This buffer + /// must be mapped in as read-execute. For more information refer to the + /// documentation on the Wiki at + /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode + const uint8_t* vm_snapshot_instructions; + /// The size of the VM snapshot instructions buffer. If + /// vm_snapshot_instructions is a symbol reference, 0 may be passed here. + size_t vm_snapshot_instructions_size; + /// The isolate snapshot data buffer used in AOT operation. This buffer must + /// be mapped in as read-only. For more information refer to the documentation + /// on the Wiki at + /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode + const uint8_t* isolate_snapshot_data; + /// The size of the isolate snapshot data buffer. If isolate_snapshot_data is + /// a symbol reference, 0 may be passed here. + size_t isolate_snapshot_data_size; + /// The isolate snapshot instructions buffer used in AOT operation. This + /// buffer must be mapped in as read-execute. For more information refer to + /// the documentation on the Wiki at + /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode + const uint8_t* isolate_snapshot_instructions; + /// The size of the isolate snapshot instructions buffer. If + /// isolate_snapshot_instructions is a symbol reference, 0 may be passed here. + size_t isolate_snapshot_instructions_size; + /// The callback invoked by the engine in root isolate scope. Called + /// immediately after the root isolate has been created and marked runnable. + VoidCallback root_isolate_create_callback; + /// The legacy callback invoked by the engine in order to give the embedder + /// the chance to respond to semantics node updates from the Dart application. + /// Semantics node updates are sent in batches terminated by a 'batch end' + /// callback that is passed a sentinel `FlutterSemanticsNode` whose `id` field + /// has the value `kFlutterSemanticsNodeIdBatchEnd`. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// should be set to null. + FlutterUpdateSemanticsNodeCallback update_semantics_node_callback; + /// The legacy callback invoked by the engine in order to give the embedder + /// the chance to respond to updates to semantics custom actions from the Dart + /// application. Custom action updates are sent in batches terminated by a + /// 'batch end' callback that is passed a sentinel + /// `FlutterSemanticsCustomAction` whose `id` field has the value + /// `kFlutterSemanticsCustomActionIdBatchEnd`. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// should be set to null. + FlutterUpdateSemanticsCustomActionCallback + update_semantics_custom_action_callback; + /// Path to a directory used to store data that is cached across runs of a + /// Flutter application (such as compiled shader programs used by Skia). + /// This is optional. The string must be NULL terminated. + /// + // This is different from the cache-path-dir argument defined in switches.h, + // which is used in `flutter::Settings` as `temp_directory_path`. + const char* persistent_cache_path; + + /// If true, the engine would only read the existing cache, but not write new + /// ones. + bool is_persistent_cache_read_only; + + /// A callback that gets invoked by the engine when it attempts to wait for a + /// platform vsync event. The engine will give the platform a baton that needs + /// to be returned back to the engine via `FlutterEngineOnVsync`. All batons + /// must be retured to the engine before initializing a + /// `FlutterEngineShutdown`. Not doing the same will result in a memory leak. + /// While the call to `FlutterEngineOnVsync` must occur on the thread that + /// made the call to `FlutterEngineRun`, the engine will make this callback on + /// an internal engine-managed thread. If the components accessed on the + /// embedder are not thread safe, the appropriate re-threading must be done. + VsyncCallback vsync_callback; + + /// The name of a custom Dart entrypoint. This is optional and specifying a + /// null or empty entrypoint makes the engine look for a method named "main" + /// in the root library of the application. + /// + /// Care must be taken to ensure that the custom entrypoint is not tree-shaken + /// away. Usually, this is done using the `@pragma('vm:entry-point')` + /// decoration. + const char* custom_dart_entrypoint; + + /// Typically the Flutter engine create and manages its internal threads. This + /// optional argument allows for the specification of task runner interfaces + /// to event loops managed by the embedder on threads it creates. + const FlutterCustomTaskRunners* custom_task_runners; + + /// All `FlutterEngine` instances in the process share the same Dart VM. When + /// the first engine is launched, it starts the Dart VM as well. It used to be + /// the case that it was not possible to shutdown the Dart VM cleanly and + /// start it back up in the process in a safe manner. This issue has since + /// been patched. Unfortunately, applications already began to make use of the + /// fact that shutting down the Flutter engine instance left a running VM in + /// the process. Since a Flutter engine could be launched on any thread, + /// applications would "warm up" the VM on another thread by launching + /// an engine with no isolates and then shutting it down immediately. The main + /// Flutter application could then be started on the main thread without + /// having to incur the Dart VM startup costs at that time. With the new + /// behavior, this "optimization" immediately becomes massive performance + /// pessimization as the VM would be started up in the "warm up" phase, shut + /// down there and then started again on the main thread. Changing this + /// behavior was deemed to be an unacceptable breaking change. Embedders that + /// wish to shutdown the Dart VM when the last engine is terminated in the + /// process should opt into this behavior by setting this flag to true. + bool shutdown_dart_vm_when_done; + + /// Typically, Flutter renders the layer hierarchy into a single root surface. + /// However, when embedders need to interleave their own contents within the + /// Flutter layer hierarchy, their applications can push platform views within + /// the Flutter scene. This is done using the `SceneBuilder.addPlatformView` + /// call. When this happens, the Flutter rasterizer divides the effective view + /// hierarchy into multiple layers. Each layer gets its own backing store and + /// Flutter renders into the same. Once the layers contents have been + /// fulfilled, the embedder is asked to composite these layers on-screen. At + /// this point, it can interleave its own contents within the effective + /// hierarchy. The interface for the specification of these layer backing + /// stores and the hooks to listen for the composition of layers on-screen can + /// be controlled using this field. This field is completely optional. In its + /// absence, platforms views in the scene are ignored and Flutter renders to + /// the root surface as normal. + const FlutterCompositor* compositor; + + /// Max size of the old gen heap for the Dart VM in MB, or 0 for unlimited, -1 + /// for default value. + /// + /// See also: + /// https://github.com/dart-lang/sdk/blob/ca64509108b3e7219c50d6c52877c85ab6a35ff2/runtime/vm/flag_list.h#L150 + int64_t dart_old_gen_heap_size; + + /// The AOT data to be used in AOT operation. + /// + /// Embedders should instantiate and destroy this object via the + /// FlutterEngineCreateAOTData and FlutterEngineCollectAOTData methods. + /// + /// Embedders can provide either snapshot buffers or aot_data, but not both. + FlutterEngineAOTData aot_data; + + /// A callback that computes the locale the platform would natively resolve + /// to. + /// + /// The input parameter is an array of FlutterLocales which represent the + /// locales supported by the app. One of the input supported locales should + /// be selected and returned to best match with the user/device's preferred + /// locale. The implementation should produce a result that as closely + /// matches what the platform would natively resolve to as possible. + FlutterComputePlatformResolvedLocaleCallback + compute_platform_resolved_locale_callback; + + /// The command line argument count for arguments passed through to the Dart + /// entrypoint. + int dart_entrypoint_argc; + + /// The command line arguments passed through to the Dart entrypoint. The + /// strings must be `NULL` terminated. + /// + /// The strings will be copied out and so any strings passed in here can + /// be safely collected after initializing the engine with + /// `FlutterProjectArgs`. + const char* const* dart_entrypoint_argv; + + // Logging callback for Dart application messages. + // + // This callback is used by embedder to log print messages from the running + // Flutter application. This callback is made on an internal engine managed + // thread and embedders must re-thread if necessary. Performing blocking calls + // in this callback may introduce application jank. + FlutterLogMessageCallback log_message_callback; + + // A tag string associated with application log messages. + // + // A log message tag string that can be used convey application, subsystem, + // or component name to embedder's logger. This string will be passed to to + // callbacks on `log_message_callback`. Defaults to "flutter" if unspecified. + const char* log_tag; + + // A callback that is invoked right before the engine is restarted. + // + // This optional callback is typically used to reset states to as if the + // engine has just been started, and usually indicates the user has requested + // a hot restart (Shift-R in the Flutter CLI.) It is not called the first time + // the engine starts. + // + // The first argument is the `user_data` from `FlutterEngineInitialize`. + OnPreEngineRestartCallback on_pre_engine_restart_callback; + + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to updates to semantics nodes and custom actions from + /// the Dart application. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// @deprecated Use `update_semantics_callback2` instead. Only one of + /// `update_semantics_node_callback`, + /// `update_semantics_callback`, and + /// `update_semantics_callback2` may be provided; the others + /// must be set to null. + FlutterUpdateSemanticsCallback update_semantics_callback; + + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to updates to semantics nodes and custom actions from + /// the Dart application. + /// + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made. + /// + /// Only one of `update_semantics_node_callback`, `update_semantics_callback`, + /// and `update_semantics_callback2` may be provided; the others must be set + /// to null. + FlutterUpdateSemanticsCallback2 update_semantics_callback2; +} FlutterProjectArgs; + +#ifndef FLUTTER_ENGINE_NO_PROTOTYPES + +//------------------------------------------------------------------------------ +/// @brief Creates the necessary data structures to launch a Flutter Dart +/// application in AOT mode. The data may only be collected after +/// all FlutterEngine instances launched using this data have been +/// terminated. +/// +/// @param[in] source The source of the AOT data. +/// @param[out] data_out The AOT data on success. Unchanged on failure. +/// +/// @return Returns if the AOT data could be successfully resolved. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineCreateAOTData( + const FlutterEngineAOTDataSource* source, + FlutterEngineAOTData* data_out); + +//------------------------------------------------------------------------------ +/// @brief Collects the AOT data. +/// +/// @warning The embedder must ensure that this call is made only after all +/// FlutterEngine instances launched using this data have been +/// terminated, and that all of those instances were launched with +/// the FlutterProjectArgs::shutdown_dart_vm_when_done flag set to +/// true. +/// +/// @param[in] data The data to collect. +/// +/// @return Returns if the AOT data was successfully collected. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data); + +//------------------------------------------------------------------------------ +/// @brief Initialize and run a Flutter engine instance and return a handle +/// to it. This is a convenience method for the pair of calls to +/// `FlutterEngineInitialize` and `FlutterEngineRunInitialized`. +/// +/// @note This method of running a Flutter engine works well except in +/// cases where the embedder specifies custom task runners via +/// `FlutterProjectArgs::custom_task_runners`. In such cases, the +/// engine may need the embedder to post tasks back to it before +/// `FlutterEngineRun` has returned. Embedders can only post tasks +/// to the engine if they have a handle to the engine. In such +/// cases, embedders are advised to get the engine handle via the +/// `FlutterInitializeCall`. Then they can call +/// `FlutterEngineRunInitialized` knowing that they will be able to +/// service custom tasks on other threads with the engine handle. +/// +/// @param[in] version The Flutter embedder API version. Must be +/// FLUTTER_ENGINE_VERSION. +/// @param[in] config The renderer configuration. +/// @param[in] args The Flutter project arguments. +/// @param user_data A user data baton passed back to embedders in +/// callbacks. +/// @param[out] engine_out The engine handle on successful engine creation. +/// +/// @return The result of the call to run the Flutter engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRun(size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * + engine_out); + +//------------------------------------------------------------------------------ +/// @brief Shuts down a Flutter engine instance. The engine handle is no +/// longer valid for any calls in the embedder API after this point. +/// Making additional calls with this handle is undefined behavior. +/// +/// @note This de-initializes the Flutter engine instance (via an implicit +/// call to `FlutterEngineDeinitialize`) if necessary. +/// +/// @param[in] engine The Flutter engine instance to collect. +/// +/// @return The result of the call to shutdown the Flutter engine instance. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) + engine); + +//------------------------------------------------------------------------------ +/// @brief Initialize a Flutter engine instance. This does not run the +/// Flutter application code till the `FlutterEngineRunInitialized` +/// call is made. Besides Flutter application code, no tasks are +/// scheduled on embedder managed task runners either. This allows +/// embedders providing custom task runners to the Flutter engine to +/// obtain a handle to the Flutter engine before the engine can post +/// tasks on these task runners. +/// +/// @param[in] version The Flutter embedder API version. Must be +/// FLUTTER_ENGINE_VERSION. +/// @param[in] config The renderer configuration. +/// @param[in] args The Flutter project arguments. +/// @param user_data A user data baton passed back to embedders in +/// callbacks. +/// @param[out] engine_out The engine handle on successful engine creation. +/// +/// @return The result of the call to initialize the Flutter engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineInitialize(size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * + engine_out); + +//------------------------------------------------------------------------------ +/// @brief Stops running the Flutter engine instance. After this call, the +/// embedder is also guaranteed that no more calls to post tasks +/// onto custom task runners specified by the embedder are made. The +/// Flutter engine handle still needs to be collected via a call to +/// `FlutterEngineShutdown`. +/// +/// @param[in] engine The running engine instance to de-initialize. +/// +/// @return The result of the call to de-initialize the Flutter engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine) + engine); + +//------------------------------------------------------------------------------ +/// @brief Runs an initialized engine instance. An engine can be +/// initialized via `FlutterEngineInitialize`. An initialized +/// instance can only be run once. During and after this call, +/// custom task runners supplied by the embedder are expected to +/// start servicing tasks. +/// +/// @param[in] engine An initialized engine instance that has not previously +/// been run. +/// +/// @return The result of the call to run the initialized Flutter +/// engine instance. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRunInitialized( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendWindowMetricsEvent( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterWindowMetricsEvent* event); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendPointerEvent( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPointerEvent* events, + size_t events_count); + +//------------------------------------------------------------------------------ +/// @brief Sends a key event to the engine. The framework will decide +/// whether to handle this event in a synchronous fashion, although +/// due to technical limitation, the result is always reported +/// asynchronously. The `callback` is guaranteed to be called +/// exactly once. +/// +/// @param[in] engine A running engine instance. +/// @param[in] event The event data to be sent. This function will no +/// longer access `event` after returning. +/// @param[in] callback The callback invoked by the engine when the +/// Flutter application has decided whether it +/// handles this event. Accepts nullptr. +/// @param[in] user_data The context associated with the callback. The +/// exact same value will used to invoke `callback`. +/// Accepts nullptr. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, + void* user_data); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendPlatformMessage( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message); + +//------------------------------------------------------------------------------ +/// @brief Creates a platform message response handle that allows the +/// embedder to set a native callback for a response to a message. +/// This handle may be set on the `response_handle` field of any +/// `FlutterPlatformMessage` sent to the engine. +/// +/// The handle must be collected via a call to +/// `FlutterPlatformMessageReleaseResponseHandle`. This may be done +/// immediately after a call to `FlutterEngineSendPlatformMessage` +/// with a platform message whose response handle contains the handle +/// created using this call. In case a handle is created but never +/// sent in a message, the release call must still be made. Not +/// calling release on the handle results in a small memory leak. +/// +/// The user data baton passed to the data callback is the one +/// specified in this call as the third argument. +/// +/// @see FlutterPlatformMessageReleaseResponseHandle() +/// +/// @param[in] engine A running engine instance. +/// @param[in] data_callback The callback invoked by the engine when the +/// Flutter application send a response on the +/// handle. +/// @param[in] user_data The user data associated with the data callback. +/// @param[out] response_out The response handle created when this call is +/// successful. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterDataCallback data_callback, + void* user_data, + FlutterPlatformMessageResponseHandle** response_out); + +//------------------------------------------------------------------------------ +/// @brief Collects the handle created using +/// `FlutterPlatformMessageCreateResponseHandle`. +/// +/// @see FlutterPlatformMessageCreateResponseHandle() +/// +/// @param[in] engine A running engine instance. +/// @param[in] response The platform message response handle to collect. +/// These handles are created using +/// `FlutterPlatformMessageCreateResponseHandle()`. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterPlatformMessageResponseHandle* response); + +//------------------------------------------------------------------------------ +/// @brief Send a response from the native side to a platform message from +/// the Dart Flutter application. +/// +/// @param[in] engine The running engine instance. +/// @param[in] handle The platform message response handle. +/// @param[in] data The data to associate with the platform message +/// response. +/// @param[in] data_length The length of the platform message response data. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendPlatformMessageResponse( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessageResponseHandle* handle, + const uint8_t* data, + size_t data_length); + +//------------------------------------------------------------------------------ +/// @brief This API is only meant to be used by platforms that need to +/// flush tasks on a message loop not controlled by the Flutter +/// engine. +/// +/// @deprecated This API will be deprecated and is not part of the stable API. +/// Please use the custom task runners API by setting an +/// appropriate `FlutterProjectArgs::custom_task_runners` +/// interface. This will yield better performance and the +/// interface is stable. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult __FlutterEngineFlushPendingTasksNow(); + +//------------------------------------------------------------------------------ +/// @brief Register an external texture with a unique (per engine) +/// identifier. Only rendering backends that support external +/// textures accept external texture registrations. After the +/// external texture is registered, the application can mark that a +/// frame is available by calling +/// `FlutterEngineMarkExternalTextureFrameAvailable`. +/// +/// @see FlutterEngineUnregisterExternalTexture() +/// @see FlutterEngineMarkExternalTextureFrameAvailable() +/// +/// @param[in] engine A running engine instance. +/// @param[in] texture_identifier The identifier of the texture to register +/// with the engine. The embedder may supply new +/// frames to this texture using the same +/// identifier. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRegisterExternalTexture( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); + +//------------------------------------------------------------------------------ +/// @brief Unregister a previous texture registration. +/// +/// @see FlutterEngineRegisterExternalTexture() +/// @see FlutterEngineMarkExternalTextureFrameAvailable() +/// +/// @param[in] engine A running engine instance. +/// @param[in] texture_identifier The identifier of the texture for which new +/// frame will not be available. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineUnregisterExternalTexture( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); + +//------------------------------------------------------------------------------ +/// @brief Mark that a new texture frame is available for a given texture +/// identifier. +/// +/// @see FlutterEngineRegisterExternalTexture() +/// @see FlutterEngineUnregisterExternalTexture() +/// +/// @param[in] engine A running engine instance. +/// @param[in] texture_identifier The identifier of the texture whose frame +/// has been updated. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); + +//------------------------------------------------------------------------------ +/// @brief Enable or disable accessibility semantics. +/// +/// @param[in] engine A running engine instance. +/// @param[in] enabled When enabled, changes to the semantic contents of the +/// window are sent via the +/// `FlutterUpdateSemanticsCallback2` registered to +/// `update_semantics_callback2` in +/// `FlutterProjectArgs`. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineUpdateSemanticsEnabled( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + bool enabled); + +//------------------------------------------------------------------------------ +/// @brief Sets additional accessibility features. +/// +/// @param[in] engine A running engine instance +/// @param[in] features The accessibility features to set. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterAccessibilityFeature features); + +//------------------------------------------------------------------------------ +/// @brief Dispatch a semantics action to the specified semantics node. +/// +/// @param[in] engine A running engine instance. +/// @param[in] node_id The semantics node identifier. +/// @param[in] action The semantics action. +/// @param[in] data Data associated with the action. +/// @param[in] data_length The data length. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineDispatchSemanticsAction( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + uint64_t node_id, + FlutterSemanticsAction action, + const uint8_t* data, + size_t data_length); + +//------------------------------------------------------------------------------ +/// @brief Notify the engine that a vsync event occurred. A baton passed to +/// the platform via the vsync callback must be returned. This call +/// must be made on the thread on which the call to +/// `FlutterEngineRun` was made. +/// +/// @see FlutterEngineGetCurrentTime() +/// +/// @attention That frame timepoints are in nanoseconds. +/// +/// @attention The system monotonic clock is used as the timebase. +/// +/// @param[in] engine. A running engine instance. +/// @param[in] baton The baton supplied by the engine. +/// @param[in] frame_start_time_nanos The point at which the vsync event +/// occurred or will occur. If the time +/// point is in the future, the engine will +/// wait till that point to begin its frame +/// workload. +/// @param[in] frame_target_time_nanos The point at which the embedder +/// anticipates the next vsync to occur. +/// This is a hint the engine uses to +/// schedule Dart VM garbage collection in +/// periods in which the various threads +/// are most likely to be idle. For +/// example, for a 60Hz display, embedders +/// should add 16.6 * 1e6 to the frame time +/// field. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineOnVsync(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + intptr_t baton, + uint64_t frame_start_time_nanos, + uint64_t frame_target_time_nanos); + +//------------------------------------------------------------------------------ +/// @brief Reloads the system fonts in engine. +/// +/// @param[in] engine. A running engine instance. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineReloadSystemFonts( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +//------------------------------------------------------------------------------ +/// @brief A profiling utility. Logs a trace duration begin event to the +/// timeline. If the timeline is unavailable or disabled, this has +/// no effect. Must be balanced with an duration end event (via +/// `FlutterEngineTraceEventDurationEnd`) with the same name on the +/// same thread. Can be called on any thread. Strings passed into +/// the function will NOT be copied when added to the timeline. Only +/// string literals may be passed in. +/// +/// @param[in] name The name of the trace event. +/// +FLUTTER_EXPORT +void FlutterEngineTraceEventDurationBegin(const char* name); + +//----------------------------------------------------------------------------- +/// @brief A profiling utility. Logs a trace duration end event to the +/// timeline. If the timeline is unavailable or disabled, this has +/// no effect. This call must be preceded by a trace duration begin +/// call (via `FlutterEngineTraceEventDurationBegin`) with the same +/// name on the same thread. Can be called on any thread. Strings +/// passed into the function will NOT be copied when added to the +/// timeline. Only string literals may be passed in. +/// +/// @param[in] name The name of the trace event. +/// +FLUTTER_EXPORT +void FlutterEngineTraceEventDurationEnd(const char* name); + +//----------------------------------------------------------------------------- +/// @brief A profiling utility. Logs a trace duration instant event to the +/// timeline. If the timeline is unavailable or disabled, this has +/// no effect. Can be called on any thread. Strings passed into the +/// function will NOT be copied when added to the timeline. Only +/// string literals may be passed in. +/// +/// @param[in] name The name of the trace event. +/// +FLUTTER_EXPORT +void FlutterEngineTraceEventInstant(const char* name); + +//------------------------------------------------------------------------------ +/// @brief Posts a task onto the Flutter render thread. Typically, this may +/// be called from any thread as long as a `FlutterEngineShutdown` +/// on the specific engine has not already been initiated. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback to execute on the render thread. +/// @param callback_data The callback context. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEnginePostRenderThreadTask( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* callback_data); + +//------------------------------------------------------------------------------ +/// @brief Get the current time in nanoseconds from the clock used by the +/// flutter engine. This is the system monotonic clock. +/// +/// @return The current time in nanoseconds. +/// +FLUTTER_EXPORT +uint64_t FlutterEngineGetCurrentTime(); + +//------------------------------------------------------------------------------ +/// @brief Inform the engine to run the specified task. This task has been +/// given to the engine via the +/// `FlutterTaskRunnerDescription.post_task_callback`. This call +/// must only be made at the target time specified in that callback. +/// Running the task before that time is undefined behavior. +/// +/// @param[in] engine A running engine instance. +/// @param[in] task the task handle. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRunTask(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterTask* task); + +//------------------------------------------------------------------------------ +/// @brief Notify a running engine instance that the locale has been +/// updated. The preferred locale must be the first item in the list +/// of locales supplied. The other entries will be used as a +/// fallback. +/// +/// @param[in] engine A running engine instance. +/// @param[in] locales The updated locales in the order of preference. +/// @param[in] locales_count The count of locales supplied. +/// +/// @return Whether the locale updates were applied. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineUpdateLocales(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterLocale** locales, + size_t locales_count); + +//------------------------------------------------------------------------------ +/// @brief Returns if the Flutter engine instance will run AOT compiled +/// Dart code. This call has no threading restrictions. +/// +/// For embedder code that is configured for both AOT and JIT mode +/// Dart execution based on the Flutter engine being linked to, this +/// runtime check may be used to appropriately configure the +/// `FlutterProjectArgs`. In JIT mode execution, the kernel +/// snapshots must be present in the Flutter assets directory +/// specified in the `FlutterProjectArgs`. For AOT execution, the +/// fields `vm_snapshot_data`, `vm_snapshot_instructions`, +/// `isolate_snapshot_data` and `isolate_snapshot_instructions` +/// (along with their size fields) must be specified in +/// `FlutterProjectArgs`. +/// +/// @return True, if AOT Dart code is run. JIT otherwise. +/// +FLUTTER_EXPORT +bool FlutterEngineRunsAOTCompiledDartCode(void); + +//------------------------------------------------------------------------------ +/// @brief Posts a Dart object to specified send port. The corresponding +/// receive port for send port can be in any isolate running in the +/// VM. This isolate can also be the root isolate for an +/// unrelated engine. The engine parameter is necessary only to +/// ensure the call is not made when no engine (and hence no VM) is +/// running. +/// +/// Unlike the platform messages mechanism, there are no threading +/// restrictions when using this API. Message can be posted on any +/// thread and they will be made available to isolate on which the +/// corresponding send port is listening. +/// +/// However, it is the embedders responsibility to ensure that the +/// call is not made during an ongoing call the +/// `FlutterEngineDeinitialize` or `FlutterEngineShutdown` on +/// another thread. +/// +/// @param[in] engine A running engine instance. +/// @param[in] port The send port to send the object to. +/// @param[in] object The object to send to the isolate with the +/// corresponding receive port. +/// +/// @return If the message was posted to the send port. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEnginePostDartObject( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineDartPort port, + const FlutterEngineDartObject* object); + +//------------------------------------------------------------------------------ +/// @brief Posts a low memory notification to a running engine instance. +/// The engine will do its best to release non-critical resources in +/// response. It is not guaranteed that the resource would have been +/// collected by the time this call returns however. The +/// notification is posted to engine subsystems that may be +/// operating on other threads. +/// +/// Flutter applications can respond to these notifications by +/// setting `WidgetsBindingObserver.didHaveMemoryPressure` +/// observers. +/// +/// @param[in] engine A running engine instance. +/// +/// @return If the low memory notification was sent to the running engine +/// instance. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyLowMemoryWarning( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +//------------------------------------------------------------------------------ +/// @brief Schedule a callback to be run on all engine managed threads. +/// The engine will attempt to service this callback the next time +/// the message loop for each managed thread is idle. Since the +/// engine manages the entire lifecycle of multiple threads, there +/// is no opportunity for the embedders to finely tune the +/// priorities of threads directly, or, perform other thread +/// specific configuration (for example, setting thread names for +/// tracing). This callback gives embedders a chance to affect such +/// tuning. +/// +/// @attention This call is expensive and must be made as few times as +/// possible. The callback must also return immediately as not doing +/// so may risk performance issues (especially for callbacks of type +/// kFlutterNativeThreadTypeUI and kFlutterNativeThreadTypeRender). +/// +/// @attention Some callbacks (especially the ones of type +/// kFlutterNativeThreadTypeWorker) may be called after the +/// FlutterEngine instance has shut down. Embedders must be careful +/// in handling the lifecycle of objects associated with the user +/// data baton. +/// +/// @attention In case there are multiple running Flutter engine instances, +/// their workers are shared. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback that will get called multiple times on +/// each engine managed thread. +/// @param[in] user_data A baton passed by the engine to the callback. This +/// baton is not interpreted by the engine in any way. +/// +/// @return Returns if the callback was successfully posted to all threads. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEnginePostCallbackOnAllNativeThreads( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterNativeThreadCallback callback, + void* user_data); + +//------------------------------------------------------------------------------ +/// @brief Posts updates corresponding to display changes to a running engine +/// instance. +/// +/// @param[in] update_type The type of update pushed to the engine. +/// @param[in] displays The displays affected by this update. +/// @param[in] display_count Size of the displays array, must be at least 1. +/// +/// @return the result of the call made to the engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyDisplayUpdate( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* displays, + size_t display_count); + +//------------------------------------------------------------------------------ +/// @brief Schedule a new frame to redraw the content. +/// +/// @param[in] engine A running engine instance. +/// +/// @return the result of the call made to the engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) + engine); + +//------------------------------------------------------------------------------ +/// @brief Schedule a callback to be called after the next frame is drawn. +/// This must be called from the platform thread. The callback is +/// executed only once from the raster thread; embedders must +/// re-thread if necessary. Performing blocking calls +/// in this callback may introduce application jank. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback to execute. +/// @param[in] user_data A baton passed by the engine to the callback. This +/// baton is not interpreted by the engine in any way. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); + +#endif // !FLUTTER_ENGINE_NO_PROTOTYPES + +// Typedefs for the function pointers in FlutterEngineProcTable. +typedef FlutterEngineResult (*FlutterEngineCreateAOTDataFnPtr)( + const FlutterEngineAOTDataSource* source, + FlutterEngineAOTData* data_out); +typedef FlutterEngineResult (*FlutterEngineCollectAOTDataFnPtr)( + FlutterEngineAOTData data); +typedef FlutterEngineResult (*FlutterEngineRunFnPtr)( + size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * engine_out); +typedef FlutterEngineResult (*FlutterEngineShutdownFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineInitializeFnPtr)( + size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * engine_out); +typedef FlutterEngineResult (*FlutterEngineDeinitializeFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineRunInitializedFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineSendWindowMetricsEventFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterWindowMetricsEvent* event); +typedef FlutterEngineResult (*FlutterEngineSendPointerEventFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPointerEvent* events, + size_t events_count); +typedef FlutterEngineResult (*FlutterEngineSendKeyEventFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, + void* user_data); +typedef FlutterEngineResult (*FlutterEngineSendPlatformMessageFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message); +typedef FlutterEngineResult ( + *FlutterEnginePlatformMessageCreateResponseHandleFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterDataCallback data_callback, + void* user_data, + FlutterPlatformMessageResponseHandle** response_out); +typedef FlutterEngineResult ( + *FlutterEnginePlatformMessageReleaseResponseHandleFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterPlatformMessageResponseHandle* response); +typedef FlutterEngineResult (*FlutterEngineSendPlatformMessageResponseFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessageResponseHandle* handle, + const uint8_t* data, + size_t data_length); +typedef FlutterEngineResult (*FlutterEngineRegisterExternalTextureFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); +typedef FlutterEngineResult (*FlutterEngineUnregisterExternalTextureFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); +typedef FlutterEngineResult ( + *FlutterEngineMarkExternalTextureFrameAvailableFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + int64_t texture_identifier); +typedef FlutterEngineResult (*FlutterEngineUpdateSemanticsEnabledFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + bool enabled); +typedef FlutterEngineResult (*FlutterEngineUpdateAccessibilityFeaturesFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterAccessibilityFeature features); +typedef FlutterEngineResult (*FlutterEngineDispatchSemanticsActionFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + uint64_t id, + FlutterSemanticsAction action, + const uint8_t* data, + size_t data_length); +typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + intptr_t baton, + uint64_t frame_start_time_nanos, + uint64_t frame_target_time_nanos); +typedef FlutterEngineResult (*FlutterEngineReloadSystemFontsFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef void (*FlutterEngineTraceEventDurationBeginFnPtr)(const char* name); +typedef void (*FlutterEngineTraceEventDurationEndFnPtr)(const char* name); +typedef void (*FlutterEngineTraceEventInstantFnPtr)(const char* name); +typedef FlutterEngineResult (*FlutterEnginePostRenderThreadTaskFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* callback_data); +typedef uint64_t (*FlutterEngineGetCurrentTimeFnPtr)(); +typedef FlutterEngineResult (*FlutterEngineRunTaskFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterTask* task); +typedef FlutterEngineResult (*FlutterEngineUpdateLocalesFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterLocale** locales, + size_t locales_count); +typedef bool (*FlutterEngineRunsAOTCompiledDartCodeFnPtr)(void); +typedef FlutterEngineResult (*FlutterEnginePostDartObjectFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineDartPort port, + const FlutterEngineDartObject* object); +typedef FlutterEngineResult (*FlutterEngineNotifyLowMemoryWarningFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEnginePostCallbackOnAllNativeThreadsFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterNativeThreadCallback callback, + void* user_data); +typedef FlutterEngineResult (*FlutterEngineNotifyDisplayUpdateFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* displays, + size_t display_count); +typedef FlutterEngineResult (*FlutterEngineScheduleFrameFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); + +/// Function-pointer-based versions of the APIs above. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterEngineProcs). + size_t struct_size; + + FlutterEngineCreateAOTDataFnPtr CreateAOTData; + FlutterEngineCollectAOTDataFnPtr CollectAOTData; + FlutterEngineRunFnPtr Run; + FlutterEngineShutdownFnPtr Shutdown; + FlutterEngineInitializeFnPtr Initialize; + FlutterEngineDeinitializeFnPtr Deinitialize; + FlutterEngineRunInitializedFnPtr RunInitialized; + FlutterEngineSendWindowMetricsEventFnPtr SendWindowMetricsEvent; + FlutterEngineSendPointerEventFnPtr SendPointerEvent; + FlutterEngineSendKeyEventFnPtr SendKeyEvent; + FlutterEngineSendPlatformMessageFnPtr SendPlatformMessage; + FlutterEnginePlatformMessageCreateResponseHandleFnPtr + PlatformMessageCreateResponseHandle; + FlutterEnginePlatformMessageReleaseResponseHandleFnPtr + PlatformMessageReleaseResponseHandle; + FlutterEngineSendPlatformMessageResponseFnPtr SendPlatformMessageResponse; + FlutterEngineRegisterExternalTextureFnPtr RegisterExternalTexture; + FlutterEngineUnregisterExternalTextureFnPtr UnregisterExternalTexture; + FlutterEngineMarkExternalTextureFrameAvailableFnPtr + MarkExternalTextureFrameAvailable; + FlutterEngineUpdateSemanticsEnabledFnPtr UpdateSemanticsEnabled; + FlutterEngineUpdateAccessibilityFeaturesFnPtr UpdateAccessibilityFeatures; + FlutterEngineDispatchSemanticsActionFnPtr DispatchSemanticsAction; + FlutterEngineOnVsyncFnPtr OnVsync; + FlutterEngineReloadSystemFontsFnPtr ReloadSystemFonts; + FlutterEngineTraceEventDurationBeginFnPtr TraceEventDurationBegin; + FlutterEngineTraceEventDurationEndFnPtr TraceEventDurationEnd; + FlutterEngineTraceEventInstantFnPtr TraceEventInstant; + FlutterEnginePostRenderThreadTaskFnPtr PostRenderThreadTask; + FlutterEngineGetCurrentTimeFnPtr GetCurrentTime; + FlutterEngineRunTaskFnPtr RunTask; + FlutterEngineUpdateLocalesFnPtr UpdateLocales; + FlutterEngineRunsAOTCompiledDartCodeFnPtr RunsAOTCompiledDartCode; + FlutterEnginePostDartObjectFnPtr PostDartObject; + FlutterEngineNotifyLowMemoryWarningFnPtr NotifyLowMemoryWarning; + FlutterEnginePostCallbackOnAllNativeThreadsFnPtr + PostCallbackOnAllNativeThreads; + FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; + FlutterEngineScheduleFrameFnPtr ScheduleFrame; + FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; +} FlutterEngineProcTable; + +//------------------------------------------------------------------------------ +/// @brief Gets the table of engine function pointers. +/// +/// @param[out] table The table to fill with pointers. This should be +/// zero-initialized, except for struct_size. +/// +/// @return Returns whether the table was successfully populated. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineGetProcAddresses( + FlutterEngineProcTable* table); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_EMBEDDER_H_