diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 8658b29bd..6033f0d8f 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -17,7 +17,7 @@ jobs: matrix: include: - platform: mac-intel - os: macos-13 + os: macos-14 pack: 1 upload: 1 pack_type: Release @@ -26,11 +26,11 @@ jobs: preset: macos-conan-ninja-release conan_profile: macos-intel conan_prebuilts: dependencies-mac-intel - conan_options: --options with_apple_system_libs=True + conan_options: --profile=dependencies/conan_profiles/base/apple-system artifact_platform: intel - platform: mac-arm - os: macos-13 + os: macos-14 pack: 1 upload: 1 pack_type: Release @@ -39,11 +39,11 @@ jobs: preset: macos-arm-conan-ninja-release conan_profile: macos-arm conan_prebuilts: dependencies-mac-arm - conan_options: --options with_apple_system_libs=True + conan_options: --profile=dependencies/conan_profiles/base/apple-system artifact_platform: arm - platform: ios - os: macos-13 + os: macos-14 pack: 1 upload: 1 pack_type: Release @@ -52,7 +52,7 @@ jobs: preset: ios-release-conan-ccache conan_profile: ios-arm64 conan_prebuilts: dependencies-ios - conan_options: --options with_apple_system_libs=True + conan_options: --profile=dependencies/conan_profiles/base/apple-system - platform: msvc-x64 arch: x64 @@ -61,8 +61,10 @@ jobs: upload: 0 pack_type: RelWithDebInfo extension: zip - before_install: msvc.sh preset: windows-msvc-ninja-release + conan_profile: msvc-x64 + conan_prebuilts: dependencies-windows-x64 + conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:target_pre_windows10=True" artifact_platform: x64 cl: Hostx64/x64/cl.exe @@ -73,8 +75,10 @@ jobs: upload: 0 pack_type: RelWithDebInfo extension: zip - before_install: msvc.sh preset: windows-msvc-ninja-release-x86 + conan_profile: msvc-x86 + conan_prebuilts: dependencies-windows-x86 + conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:target_pre_windows10=True" artifact_platform: x86 cl: Hostx64/x86/cl.exe @@ -85,55 +89,43 @@ jobs: upload: 0 pack_type: RelWithDebInfo extension: zip - before_install: msvc.sh preset: windows-msvc-ninja-release-arm64 + conan_profile: msvc-arm64 + conan_prebuilts: dependencies-windows-arm64 + conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:lua_lib=lua" artifact_platform: arm64 cl: HostARM64/ARM64/cl.exe - - platform: mingw_x86_64 - arch: x86_64 - os: ubuntu-24.04 - pack: 1 - pack_type: Release - extension: zip - cmake_args: -G Ninja - before_install: mingw.sh - preset: windows-mingw-conan-linux - conan_profile: mingw64-linux.jinja - conan_prebuilts: dependencies-mingw-x86-64 - - - platform: mingw_x86 - arch: x86 - os: ubuntu-24.04 - pack: 1 - pack_type: Release - extension: zip - cmake_args: -G Ninja - before_install: mingw.sh - preset: windows-mingw-conan-linux - conan_profile: mingw32-linux.jinja - conan_prebuilts: dependencies-mingw-x86 - - platform: android-32 - os: ubuntu-24.04 + os: ubuntu-latest upload: 1 extension: apk preset: android-conan-ninja-release - before_install: android.sh conan_profile: android-32-ndk conan_prebuilts: dependencies-android-armeabi-v7a + conan_options: --profile=dependencies/conan_profiles/base/android-system artifact_platform: armeabi-v7a - platform: android-64 - os: ubuntu-24.04 + os: ubuntu-latest upload: 1 extension: apk preset: android-conan-ninja-release - before_install: android.sh conan_profile: android-64-ndk conan_prebuilts: dependencies-android-arm64-v8a + conan_options: --profile=dependencies/conan_profiles/base/android-system artifact_platform: arm64-v8a + - platform: android-64-intel + os: ubuntu-latest + upload: 1 + extension: apk + preset: android-conan-ninja-release + conan_profile: android-x64-ndk + conan_prebuilts: dependencies-android-x64 + conan_options: --profile=dependencies/conan_profiles/base/android-system + artifact_platform: x64 + runs-on: ${{ matrix.os }} # Allow non-MSVC builds to fail without failing whole job # This keeps pipeline moving so Windows Installer job can still run @@ -148,11 +140,11 @@ jobs: uses: actions/checkout@v5 with: submodules: recursive - + - name: Prepare APT staging dir if: contains(matrix.os, 'ubuntu') run: mkdir -p "$RUNNER_TEMP/apt-cache" - + - name: APT cache restore if: contains(matrix.os, 'ubuntu') id: aptcache @@ -166,7 +158,7 @@ jobs: - name: Prepare CI if: "${{ matrix.before_install != '' }}" run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}' '${{matrix.arch}}' - + # Save only on cache miss, GitHub caches are immutable per key - name: APT cache save if: contains(matrix.os, 'ubuntu') && steps.aptcache.outputs.cache-hit != 'true' @@ -177,11 +169,9 @@ jobs: - name: Install Conan Dependencies if: "${{ matrix.conan_prebuilts != '' }}" - run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}' - - - name: Install vcpkg Dependencies - if: ${{ startsWith(matrix.platform, 'msvc') }} - run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}' 'vcpkg' + run: | + pipx install conan + source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}' - name: Setup MSVC Developer Command Prompt if: ${{ startsWith(matrix.platform, 'msvc') }} @@ -206,7 +196,7 @@ jobs: max-size: '5G' verbose: 2 job-summary: "" # <-- disable built-in summary to avoid duplicate block - + - name: Setup compiler cache for branch builds uses: hendrikmuhs/ccache-action@v1.2 if: ${{ github.event.number == '' }} @@ -220,7 +210,7 @@ jobs: max-size: '5G' verbose: 2 job-summary: "" # <-- disable built-in summary to avoid duplicate block - + - name: CCache tuning (Android) if: ${{ startsWith(matrix.platform, 'android') }} run: | @@ -230,33 +220,36 @@ jobs: ccache --set-config=hash_dir=true ccache --set-config=sloppiness=time_macros - - name: Install Conan - if: "${{ matrix.conan_profile != '' }}" - run: pipx install 'conan<2.0' - - name: Install Conan profile if: "${{ matrix.conan_profile != '' }}" run: | - conan profile new default --detect + conan profile detect conan install . \ - --install-folder=conan-generated \ - --no-imports \ + --output-folder=conan-generated \ --build=never \ - --profile:build=default \ - --profile:host=CI/conan/${{ matrix.conan_profile }} \ + --profile=dependencies/conan_profiles/${{ matrix.conan_profile }} \ ${{ matrix.conan_options }} - env: - GENERATE_ONLY_BUILT_CONFIG: 1 # Can't be set in Gradle project - name: Configure enableUncompressedNativeLibs if: ${{ startsWith(matrix.platform, 'android') }} run: mkdir -p ~/.gradle && echo "android.bundle.enableUncompressedNativeLibs=true" > ~/.gradle/gradle.properties - # Workaround for gradle not discovering SDK that was installed via conan - - name: Find Android NDK + # Workaround for gradle not discovering SDK that was installed via Сonan + - name: Link Android NDK from Conan cache to Android SDK path if: ${{ startsWith(matrix.platform, 'android') }} - run: sudo ln -s -T /home/runner/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/local/lib/android/sdk/ndk/25.2.9519653 + run: | + ndkPackage='android-ndk' + hexRegex='[[:xdigit:]]+' + + ndkPackageRevision=$(conan list "$ndkPackage/*:*" --format=compact \ + | egrep --only-matching "$ndkPackage/\\w+#$hexRegex:$hexRegex") + ndkPackagePath=$(conan cache path "$ndkPackageRevision") + + # format: Pkg.Revision = 25.2.9519653 + ndkPath="$ndkPackagePath/bin" + ndkVersion=$(fgrep 'Pkg.Revision' "$ndkPath/source.properties" | cut -d ' ' -f 3) + ln -s -T "$ndkPath" "$ANDROID_HOME/ndk/$ndkVersion" - name: Install Java uses: actions/setup-java@v5 @@ -266,6 +259,7 @@ jobs: java-version: '17' # a hack to build ID for x64 build in order for Google Play to allow upload of both 32 and 64 bit builds + # TODO: x86_64 - name: Bump Android x64 build ID if: ${{ matrix.platform == 'android-64' }} run: perl -i -pe 's/versionCode (\d+)/$x=$1+1; "versionCode $x"/e' android/vcmi-app/build.gradle @@ -283,27 +277,33 @@ jobs: env: PULL_REQUEST: ${{ github.event.pull_request.number }} - - name: Configure + - name: Configure (non-MSVC) + if: ${{ !startsWith(matrix.platform, 'msvc') }} run: | if [[ ("${{ matrix.preset }}" == "android-conan-ninja-release") && ("${{ github.ref }}" != 'refs/heads/master') ]]; then - cmake -DENABLE_CCACHE:BOOL=ON \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily" \ - --preset ${{ matrix.preset }} - elif ${{ startsWith(matrix.platform, 'msvc') }}; then - CL="$VCToolsInstallDir/bin/${{ matrix.cl }}" - cmake \ - -D CMAKE_C_COMPILER:FILEPATH="$CL" \ - -D CMAKE_CXX_COMPILER:FILEPATH="$CL" \ - --preset ${{ matrix.preset }} - else - cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} + # key1=value1;key2=value2;... + gradleProperties=$(python3 CI/android/gradle_daily_props.py) + androidOptions=("-DANDROID_GRADLE_PROPERTIES=$gradleProperties") + androidOptions+=("-DCMAKE_C_COMPILER_LAUNCHER=ccache" "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache") fi + cmake -DENABLE_CCACHE:BOOL=ON "${androidOptions[@]}" --preset ${{ matrix.preset }} + - name: Configure (MSVC) + if: ${{ startsWith(matrix.platform, 'msvc') }} + run: | + & conan-generated\conanrun.ps1 + + $CL = "$($env:VCToolsInstallDir)/bin/${{ matrix.cl }}" + cmake ` + -D "CMAKE_C_COMPILER:FILEPATH=$CL" ` + -D "CMAKE_CXX_COMPILER:FILEPATH=$CL" ` + --preset ${{ matrix.preset }} + shell: pwsh - name: Build run: | + ${{ startsWith(matrix.platform, 'msvc') && '& conan-generated\conanrun.ps1' }} cmake --build --preset ${{matrix.preset}} + shell: pwsh env: ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} @@ -320,15 +320,10 @@ jobs: id: cpack if: ${{ matrix.pack == 1 }} run: | - cd '${{github.workspace}}/out/build/${{matrix.preset}}' - - # Workaround for CPack bug on macOS 13 - counter=0 - until cpack -C ${{matrix.pack_type}} || ((counter > 20)); do - sleep 3 - ((counter++)) - done - rm -rf _CPack_Packages + ${{ startsWith(matrix.platform, 'msvc') && '& conan-generated\conanrun.ps1' }} + cd "${{github.workspace}}/out/build/${{matrix.preset}}" + cpack -C ${{matrix.pack_type}} + shell: pwsh - name: Find Android package if: ${{ startsWith(matrix.platform, 'android') }} @@ -410,14 +405,14 @@ jobs: run: | source '${{github.workspace}}/CI/get_package_name.sh' echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV - + - name: Create source code archive (including submodules) run: | git archive HEAD -o "release.tar" --worktree-attributes -v git submodule update --init --recursive git submodule --quiet foreach 'cd "$toplevel"; tar -rvf "release.tar" "$sm_path"' gzip release.tar - + - name: Upload source code archive id: upload_source uses: actions/upload-artifact@v4 @@ -434,7 +429,7 @@ jobs: cat > .summary/source.json <> "$GITHUB_OUTPUT" echo "version_timestamp=${version_timestamp}" >> "$GITHUB_OUTPUT" - - name: Install ucrt Dependencies - run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}' 'ucrt' + - name: Download UCRT + run: source '${{github.workspace}}/CI/wininstaller/download_ucrt.sh' '${{matrix.platform}}' - name: Build Number run: | @@ -630,13 +624,11 @@ jobs: path: ${{github.workspace}}/artifact - name: Extract Artifact - shell: bash run: | mkdir artifact/extracted unzip "artifact/${{ env.VCMI_PACKAGE_FILE_NAME }}.zip" -d artifact/extracted - name: Ensure Inno Setup is installed - shell: bash run: | if [ ! -f "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" ] && [ ! -f "/c/ProgramData/Chocolatey/bin/ISCC.exe" ]; then choco install innosetup --no-progress -y @@ -728,7 +720,7 @@ jobs: steps: - name: Checkout (for script path) uses: actions/checkout@v5 - + - name: Download all partial JSON artifacts continue-on-error: true uses: actions/download-artifact@v5 @@ -736,7 +728,7 @@ jobs: pattern: partial-json-* merge-multiple: true path: partials - + - name: Run final summary env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/qt_translation_update.yml b/.github/workflows/qt_translation_update.yml new file mode 100644 index 000000000..4780cf3d0 --- /dev/null +++ b/.github/workflows/qt_translation_update.yml @@ -0,0 +1,44 @@ +name: Weekly Qt translation refresh + +on: + schedule: + - cron: '0 0 * * 0' # Runs every Sunday at 00:00 UTC + workflow_dispatch: + +permissions: + contents: write + +jobs: + refresh: + name: Qt translations refresh + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + submodules: false + + - name: Install Qt tools (lupdate) + run: | + sudo apt install -y --no-install-recommends qttools5-dev-tools + + - name: Update TS files (Launcher) + working-directory: launcher + run: | + lupdate . -ts translation/*.ts + + - name: Update TS files (Map Editor) + working-directory: mapeditor + run: | + lupdate . -ts translation/*.ts + + - name: Commit and push translation updates + uses: EndBug/add-and-commit@v9 + with: + add: "launcher/translation mapeditor/translation" + default_author: github_actions + message: "Auto-update VCMI Qt translation files" + push: true diff --git a/.gitignore b/.gitignore index 334daa1db..b849d2bae 100644 --- a/.gitignore +++ b/.gitignore @@ -56,8 +56,6 @@ fuzzylite.pc /client/RD /launcher/RD /lib/RD -/lib/minizip/RD -/lib/minizip/*/RD /scripting/erm/RD /scripting/erm/*/RD /server/RD diff --git a/.gitmodules b/.gitmodules index d6b206fe1..57ce2608d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ path = launcher/lib/innoextract url = https://github.com/vcmi/innoextract.git branch = vcmi +[submodule "dependencies"] + path = dependencies + url = https://github.com/vcmi/vcmi-dependencies.git diff --git a/AI/CMakeLists.txt b/AI/CMakeLists.txt index 85df664aa..335d1cba3 100644 --- a/AI/CMakeLists.txt +++ b/AI/CMakeLists.txt @@ -19,10 +19,6 @@ else() set(fuzzylite_FOUND FALSE) endif() -if(TARGET fuzzylite::fuzzylite AND MSVC) - install_vcpkg_imported_tgt(fuzzylite::fuzzylite) -endif() - if(NOT fuzzylite_FOUND) set(FL_BUILD_BINARY OFF CACHE BOOL "") set(FL_BUILD_SHARED OFF CACHE BOOL "") diff --git a/CI/android/gradle_daily_props.py b/CI/android/gradle_daily_props.py new file mode 100644 index 000000000..179483997 --- /dev/null +++ b/CI/android/gradle_daily_props.py @@ -0,0 +1,7 @@ +dic = { + "applicationIdSuffix": ".daily", + "applicationLabel": "VCMI daily", + "applicationVariant": "daily", + "signingConfig": "dailySigning", +} +print(";".join([f"{key}={value}" for key, value in dic.items()])) diff --git a/CI/before_install/android.sh b/CI/before_install/android.sh deleted file mode 100644 index 9fba7de9f..000000000 --- a/CI/before_install/android.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -sudo apt-get update -sudo apt-get install ninja-build diff --git a/CI/before_install/linux_qt5.sh b/CI/before_install/linux_qt5.sh index 28ab0614b..a4bf69ff6 100644 --- a/CI/before_install/linux_qt5.sh +++ b/CI/before_install/linux_qt5.sh @@ -24,4 +24,4 @@ sudo eatmydata apt -yq --no-install-recommends \ sudo rm -f "$APT_CACHE/lock" || true sudo rm -rf "$APT_CACHE/partial" || true -sudo chown -R "$USER:$USER" "$APT_CACHE" \ No newline at end of file +sudo chown -R "$USER:$USER" "$APT_CACHE" diff --git a/CI/before_install/linux_qt6.sh b/CI/before_install/linux_qt6.sh index 96cca364b..305ea866d 100644 --- a/CI/before_install/linux_qt6.sh +++ b/CI/before_install/linux_qt6.sh @@ -24,4 +24,4 @@ sudo eatmydata apt -yq --no-install-recommends \ sudo rm -f "$APT_CACHE/lock" || true sudo rm -rf "$APT_CACHE/partial" || true -sudo chown -R "$USER:$USER" "$APT_CACHE" \ No newline at end of file +sudo chown -R "$USER:$USER" "$APT_CACHE" diff --git a/CI/before_install/macos.sh b/CI/before_install/macos.sh index 98e63a30e..afdec709c 100644 --- a/CI/before_install/macos.sh +++ b/CI/before_install/macos.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV +echo DEVELOPER_DIR=/Applications/Xcode_16.2.app >> $GITHUB_ENV diff --git a/CI/before_install/mingw.sh b/CI/before_install/mingw.sh deleted file mode 100644 index e2fcd5e9d..000000000 --- a/CI/before_install/mingw.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -export DEBIAN_FRONTEND=noninteractive - -ARCH="${1:-x86_64}" -case "$ARCH" in - x86) triplet=i686-w64-mingw32 ;; - x86_64) triplet=x86_64-w64-mingw32 ;; - *) echo "Unsupported ARCH '$ARCH' (use: x86 | x86_64)"; exit 2 ;; -esac - -APT_CACHE="${APT_CACHE:-${RUNNER_TEMP:-/tmp}/apt-cache}" -sudo mkdir -p "$APT_CACHE" - -sudo apt -yq -o Acquire::Retries=3 update -sudo apt -yq install eatmydata - -sudo eatmydata apt -yq --no-install-recommends \ - -o Dir::Cache::archives="$APT_CACHE" \ - -o APT::Keep-Downloaded-Packages=true \ - -o Acquire::Retries=3 -o Dpkg::Use-Pty=0 \ - install \ - ninja-build nsis mingw-w64 g++-mingw-w64 - -if [[ -x "/usr/bin/${triplet}-g++-posix" ]]; then - sudo update-alternatives --set "${triplet}-g++" "/usr/bin/${triplet}-g++-posix" -fi -if [[ -x "/usr/bin/${triplet}-gcc-posix" ]]; then - sudo update-alternatives --set "${triplet}-gcc" "/usr/bin/${triplet}-gcc-posix" -fi - -sudo rm -f "$APT_CACHE/lock" || true -sudo rm -rf "$APT_CACHE/partial" || true -sudo chown -R "$USER:$USER" "$APT_CACHE" \ No newline at end of file diff --git a/CI/before_install/msvc.sh b/CI/before_install/msvc.sh deleted file mode 100644 index a0f7687f6..000000000 --- a/CI/before_install/msvc.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath) -echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH" -echo "Installed toolset versions:" -ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC" - -TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC/" | head -1) -DUMPBIN_PATH="$MSVC_INSTALL_PATH/VC/Tools/MSVC/$TOOLS_DIR/bin/Hostx64/x64/dumpbin.exe" - -# This command should work as well, but for some reason it is *extremely* slow on the Github CI (~7 minutes) -#DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1) - -echo "TOOLS_DIR = $TOOLS_DIR" -echo "DUMPBIN_PATH = $DUMPBIN_PATH" - -dirname "$DUMPBIN_PATH" > "$GITHUB_PATH" diff --git a/CI/conan/android-32 b/CI/conan/android-32 deleted file mode 100644 index 623589d71..000000000 --- a/CI/conan/android-32 +++ /dev/null @@ -1,5 +0,0 @@ -include(base/android) - -[settings] -arch=armv7 -os.api_level=19 diff --git a/CI/conan/android-32-ndk b/CI/conan/android-32-ndk deleted file mode 100644 index a70c76db0..000000000 --- a/CI/conan/android-32-ndk +++ /dev/null @@ -1,4 +0,0 @@ -include(android-32) - -[tool_requires] -android-ndk/r25c diff --git a/CI/conan/android-64 b/CI/conan/android-64 deleted file mode 100644 index d9ef364ae..000000000 --- a/CI/conan/android-64 +++ /dev/null @@ -1,5 +0,0 @@ -include(base/android) - -[settings] -arch=armv8 -os.api_level=21 diff --git a/CI/conan/android-64-ndk b/CI/conan/android-64-ndk deleted file mode 100644 index e36c5af3b..000000000 --- a/CI/conan/android-64-ndk +++ /dev/null @@ -1,4 +0,0 @@ -include(android-64) - -[tool_requires] -android-ndk/r25c diff --git a/CI/conan/base/android b/CI/conan/base/android deleted file mode 100644 index 507c18bbc..000000000 --- a/CI/conan/base/android +++ /dev/null @@ -1,10 +0,0 @@ -[settings] -build_type=Release -compiler=clang -compiler.libcxx=c++_shared -compiler.version=14 -os=Android - -[buildenv] -# fixes shared libiconv build -LD=ld diff --git a/CI/conan/base/apple b/CI/conan/base/apple deleted file mode 100644 index ae85bc702..000000000 --- a/CI/conan/base/apple +++ /dev/null @@ -1,12 +0,0 @@ -[settings] -compiler=apple-clang -compiler.version=14 -compiler.libcxx=libc++ -build_type=Release - -# required for Boost.Locale in versions >= 1.81 -compiler.cppstd=11 - -[conf] -tools.apple:enable_bitcode = False -tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/base/cross-macro.j2 b/CI/conan/base/cross-macro.j2 deleted file mode 100644 index ba3c53212..000000000 --- a/CI/conan/base/cross-macro.j2 +++ /dev/null @@ -1,21 +0,0 @@ -{% macro generate_env(target_host) -%} -CONAN_CROSS_COMPILE={{ target_host }}- -CHOST={{ target_host }} -AR={{ target_host }}-ar -AS={{ target_host }}-as -CC={{ target_host }}-gcc -CXX={{ target_host }}-g++ -RANLIB={{ target_host }}-ranlib -STRIP={{ target_host }}-strip -{%- endmacro -%} - -{% macro generate_env_win32(target_host) -%} -CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/13-posix/ -RC={{ target_host }}-windres -{%- endmacro -%} - -{% macro generate_conf(target_host) -%} -tools.build:compiler_executables = {"c": "{{ target_host }}-gcc", "cpp": "{{ target_host }}-g++"} -tools.build:sysroot = /usr/{{ target_host }} -tools.build:defines = ["WINVER=0x0601", "_WIN32_WINNT=0x0601"] -{%- endmacro -%} \ No newline at end of file diff --git a/CI/conan/base/cross-windows b/CI/conan/base/cross-windows deleted file mode 100644 index 5c3c55115..000000000 --- a/CI/conan/base/cross-windows +++ /dev/null @@ -1,10 +0,0 @@ -[settings] -os=Windows -compiler=gcc -compiler.libcxx=libstdc++11 -compiler.version=10 -compiler.cppstd=11 -build_type=Release - -[conf] -tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/base/ios b/CI/conan/base/ios deleted file mode 100644 index 0f48cadf2..000000000 --- a/CI/conan/base/ios +++ /dev/null @@ -1,5 +0,0 @@ -include(apple) - -[settings] -os=iOS -os.sdk=iphoneos diff --git a/CI/conan/base/macos b/CI/conan/base/macos deleted file mode 100644 index db13d6a90..000000000 --- a/CI/conan/base/macos +++ /dev/null @@ -1,4 +0,0 @@ -include(apple) - -[settings] -os=Macos diff --git a/CI/conan/ios-arm64 b/CI/conan/ios-arm64 deleted file mode 100644 index 238bbcf8f..000000000 --- a/CI/conan/ios-arm64 +++ /dev/null @@ -1,5 +0,0 @@ -include(base/ios) - -[settings] -os.version=12.0 -arch=armv8 diff --git a/CI/conan/ios-armv7 b/CI/conan/ios-armv7 deleted file mode 100644 index 54482ce16..000000000 --- a/CI/conan/ios-armv7 +++ /dev/null @@ -1,8 +0,0 @@ -include(base/ios) - -[settings] -os.version=10.0 -arch=armv7 - -# Xcode 13.x is the last version that can build for armv7 -compiler.version=13 diff --git a/CI/conan/macos-arm b/CI/conan/macos-arm deleted file mode 100644 index d3f06e078..000000000 --- a/CI/conan/macos-arm +++ /dev/null @@ -1,5 +0,0 @@ -include(base/macos) - -[settings] -os.version=11.0 -arch=armv8 diff --git a/CI/conan/macos-intel b/CI/conan/macos-intel deleted file mode 100644 index b527e056b..000000000 --- a/CI/conan/macos-intel +++ /dev/null @@ -1,5 +0,0 @@ -include(base/macos) - -[settings] -os.version=10.13 -arch=x86_64 diff --git a/CI/conan/mingw32-linux.jinja b/CI/conan/mingw32-linux.jinja deleted file mode 100644 index 7173a0969..000000000 --- a/CI/conan/mingw32-linux.jinja +++ /dev/null @@ -1,15 +0,0 @@ -{% import 'base/cross-macro.j2' as cross -%} -include(base/cross-windows) -{% set target_host="i686-w64-mingw32" %} - -[settings] -arch=x86 - -[conf] -{{ cross.generate_conf(target_host)}} -tools.build:cflags = ["-msse2"] -tools.build:cxxflags = ["-msse2"] - -[env] -{{ cross.generate_env(target_host) }} -{{ cross.generate_env_win32(target_host) }} \ No newline at end of file diff --git a/CI/conan/mingw64-linux.jinja b/CI/conan/mingw64-linux.jinja deleted file mode 100644 index 4c6b59ef0..000000000 --- a/CI/conan/mingw64-linux.jinja +++ /dev/null @@ -1,13 +0,0 @@ -{% import 'base/cross-macro.j2' as cross -%} -include(base/cross-windows) -{% set target_host="x86_64-w64-mingw32" %} - -[settings] -arch=x86_64 - -[conf] -{{ cross.generate_conf(target_host)}} - -[env] -{{ cross.generate_env(target_host) }} -{{ cross.generate_env_win32(target_host) }} \ No newline at end of file diff --git a/CI/emit_partial.py b/CI/emit_partial.py index f3c291240..de13b7780 100644 --- a/CI/emit_partial.py +++ b/CI/emit_partial.py @@ -83,7 +83,7 @@ def arch_label(platform: str, arch_env: Optional[str]) -> str: return arch_env mapping = { "mac-intel": "Intel", - "mac-arm": "ARM64", + "mac-arm": "Apple Silicon", "ios": "ARM64", "msvc-x64": "x64", "msvc-x86": "x86", @@ -92,6 +92,7 @@ def arch_label(platform: str, arch_env: Optional[str]) -> str: "mingw_x86_64": "x64", "android-32": "ARMv7", "android-64": "ARM64", + "android-64-intel": "x86_64", } return mapping.get(platform, platform) diff --git a/CI/example.markdownlint-cli2.jsonc b/CI/example.markdownlint-cli2.jsonc index 2d1daf4ce..61a176fa6 100644 --- a/CI/example.markdownlint-cli2.jsonc +++ b/CI/example.markdownlint-cli2.jsonc @@ -184,7 +184,7 @@ // MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md "MD040": { // List of languages - "allowed_languages": [ "cpp", "json", "sh", "text", "nix", "powershell", "lua" ], + "allowed_languages": [ "cpp", "json", "sh", "text", "nix", "powershell", "lua", "batchfile" ], // Require language only "language_only": true }, diff --git a/CI/install_conan_dependencies.sh b/CI/install_conan_dependencies.sh index 593f96e30..69c96b1e1 100644 --- a/CI/install_conan_dependencies.sh +++ b/CI/install_conan_dependencies.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -RELEASE_TAG="1.3" -FILENAME="$1" -DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz" +RELEASE_TAG="2025-08-24" +FILENAME="$1.tgz" +DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME" -mkdir ~/.conan -cd ~/.conan -curl -L "$DOWNLOAD_URL" | tar -xf - --xz +downloadedFile="$RUNNER_TEMP/$FILENAME" +curl -Lo "$downloadedFile" "$DOWNLOAD_URL" +conan cache restore "$downloadedFile" diff --git a/CI/install_vcpkg_dependencies.sh b/CI/install_vcpkg_dependencies.sh deleted file mode 100644 index 83d6de888..000000000 --- a/CI/install_vcpkg_dependencies.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -ACCOUNT="vcmi" - -# Fetch latest release tag from GitHub API -# RELEASE_TAG=$(curl -s "https://api.github.com/repos/$ACCOUNT/vcmi-deps-windows/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - -RELEASE_TAG="v1.9" - -# 2. parameter: all | vcpkg | ucrt (default: all) -PART="${2:-all}" - -# --- VCPKG --- -if [[ "$PART" == "all" || "$PART" == "vcpkg" ]]; then - DEP_FILENAME="dependencies-$1" - DEP_URL="https://github.com/$ACCOUNT/vcmi-deps-windows/releases/download/$RELEASE_TAG/$DEP_FILENAME.txz" - curl -L "$DEP_URL" | tar -xf - --xz -fi - -# --- UCRT --- -if [[ "$PART" == "all" || "$PART" == "ucrt" ]]; then - UCRT_FILENAME="ucrtRedist-$1" - UCRT_URL="https://github.com/$ACCOUNT/vcmi-deps-windows/releases/download/$RELEASE_TAG/$UCRT_FILENAME.txz" - mkdir -p ucrt - curl -L "$UCRT_URL" | tar -xf - --xz -C ucrt -fi \ No newline at end of file diff --git a/CI/wininstaller/download_ucrt.sh b/CI/wininstaller/download_ucrt.sh new file mode 100644 index 000000000..30a5c60c6 --- /dev/null +++ b/CI/wininstaller/download_ucrt.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +RELEASE_TAG="v1.9" +UCRT_FILENAME="ucrtRedist-$1" +UCRT_URL="https://github.com/vcmi/vcmi-deps-windows/releases/download/$RELEASE_TAG/$UCRT_FILENAME.txz" + +UCRT_DIR="ucrt" +mkdir -p "$UCRT_DIR" +curl -L "$UCRT_URL" | tar -xf - --xz -C "$UCRT_DIR" diff --git a/CMakeLists.txt b/CMakeLists.txt index b0f887d5a..ff4aabf30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,12 +17,8 @@ project(VCMI) # It's used currently to make sure that 3rd-party dependencies in git submodules get proper FOLDER property # - Make FindFuzzyLite check for the right version and disable FORCE_BUNDLED_FL by default -if(APPLE) - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(APPLE_MACOS 1) - else() - set(APPLE_IOS 1) - endif() +if(APPLE AND NOT IOS) + set(MACOS 1) endif() if(POLICY CMP0142) @@ -77,12 +73,12 @@ else() option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) endif() -if(APPLE_IOS) +if(IOS) set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") endif() -if(APPLE_IOS OR ANDROID) +if(IOS OR ANDROID) set(ENABLE_MONOLITHIC_INSTALL OFF) set(ENABLE_SINGLE_APP_BUILD ON) set(ENABLE_EDITOR OFF) @@ -155,7 +151,7 @@ if(ENABLE_CCACHE) endif() endif() -if(ENABLE_CCACHE AND (CMAKE_GENERATOR STREQUAL "Xcode")) +if(ENABLE_CCACHE AND XCODE) # https://stackoverflow.com/a/36515503/2278742 # Set up wrapper scripts configure_file(xcode/launch-c.in xcode/launch-c) @@ -183,7 +179,7 @@ include(VersionDefinition) vcmi_print_important_variables() # Options to enable folders in CMake generated projects for Visual Studio, Xcode, etc -# Very useful to put 3rd-party libraries such as Minizip, GoogleTest and FuzzyLite in their own folders +# Very useful to put 3rd-party libraries such as GoogleTest and FuzzyLite in their own folders set_property(GLOBAL PROPERTY USE_FOLDERS TRUE) # Make FOLDER property inheritable # So when we set FOLDER property on AI directory all and targets inside will inherit it @@ -287,7 +283,7 @@ if(ENABLE_GOLDMASTER) add_definitions(-DENABLE_GOLDMASTER) endif() -if(APPLE_IOS) +if(IOS) set(CMAKE_MACOSX_RPATH 1) set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0) @@ -303,14 +299,14 @@ if(APPLE_IOS) set(CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") endif() -if(APPLE_MACOS) +if(MACOS) # Not supported by standard library of our current minimal target - macOS 10.14 or newer required set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-aligned-allocation") endif() if(MINGW OR MSVC) # Windows Vista or newer for FuzzyLite 6 to compile - # Except for conan which already has this definition in its preset + # Except for conan which already has this definition in its profile if(NOT USING_CONAN) add_definitions(-D_WIN32_WINNT=0x0600) endif() @@ -373,14 +369,9 @@ if(MINGW OR MSVC) # Reported to Microsoft here: # https://developercommunity.visualstudio.com/content/problem/224597/linker-failing-because-of-multiple-definitions-of.html set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE") - - # Required at least for compatibility with Boost 1.68 on Vcpkg - # psapi required for ARM64 builds - set(SYSTEM_LIBS ${SYSTEM_LIBS} bcrypt psapi) endif(MSVC) if(MINGW) - # Temporary (?) workaround for failing builds on MinGW CI due to bug in TBB set(CMAKE_CXX_EXTENSIONS ON) @@ -446,7 +437,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR NOT WIN32) endif() # Check if some platform-specific libraries are needed for linking -if(NOT WIN32 AND NOT APPLE_IOS) +if(NOT WIN32 AND NOT IOS) include(CheckLibraryExists) # Shared memory functions used by Boost.Interprocess @@ -505,14 +496,7 @@ if(TARGET zlib::zlib) add_library(ZLIB::ZLIB ALIAS zlib::zlib) endif() -option(FORCE_BUNDLED_MINIZIP "Force bundled Minizip library" OFF) -if(NOT FORCE_BUNDLED_MINIZIP) - find_package(minizip) - if(TARGET minizip::minizip) - add_definitions(-DUSE_SYSTEM_MINIZIP) - endif() -endif() - +find_package(minizip REQUIRED) if (ENABLE_CLIENT) if (ENABLE_VIDEO) @@ -608,7 +592,7 @@ elseif(APPLE) set(LIB_DIR "." CACHE STRING "Where to install main library") set(DATA_DIR "." CACHE STRING "Where to install data files") else() - if(APPLE_MACOS) + if(MACOS) set(APP_BUNDLE_DIR "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_CONTENTS_DIR "${APP_BUNDLE_DIR}/Contents") set(APP_BUNDLE_BINARY_DIR "${APP_BUNDLE_CONTENTS_DIR}/MacOS") @@ -626,6 +610,9 @@ elseif(ANDROID) include(GNUInstallDirs) set(LIB_DIR "libs/${ANDROID_ABI}") + file(READ "${CMAKE_ANDROID_NDK}/meta/abis.json" ndkAbiInfo) + string(JSON ANDROID_SYSROOT_LIB_SUBDIR GET "${ndkAbiInfo}" "${ANDROID_ABI}" "triple") + # required by Qt set(androidPackageSourceDir "${CMAKE_SOURCE_DIR}/android") set(androidQtBuildDir "${CMAKE_BINARY_DIR}/android-build") @@ -666,7 +653,7 @@ else() endif() # iOS/Android have flat libs directory structure -if(APPLE_IOS OR ANDROID) +if(IOS OR ANDROID) set(AI_LIB_DIR "${LIB_DIR}") set(SCRIPTING_LIB_DIR "${LIB_DIR}") else() @@ -685,7 +672,7 @@ endif() # Add subdirectories # ####################################### -if(APPLE_IOS) +if(IOS) add_subdirectory(ios) endif() @@ -707,11 +694,6 @@ if(ENABLE_LUA) add_subdirectory(scripting/lua) endif() -if(NOT TARGET minizip::minizip) - add_subdirectory_with_folder("3rdparty" lib/minizip) - add_library(minizip::minizip ALIAS minizip) -endif() - if(ENABLE_LAUNCHER OR ENABLE_EDITOR) add_subdirectory(vcmiqt) endif() @@ -746,6 +728,8 @@ endif() # Installation section # ####################################### +vcmi_install_conan_deps() + if(ANDROID) string(REPLACE ";" "\n" ANDROID_GRADLE_PROPERTIES_MULTILINE "${ANDROID_GRADLE_PROPERTIES}") file(WRITE "${androidPackageSourceDir}/vcmi-app/gradle.properties" "signingRoot=${CMAKE_SOURCE_DIR}/CI/android\n${ANDROID_GRADLE_PROPERTIES_MULTILINE}") @@ -767,66 +751,13 @@ if(ENABLE_LUA) endif() # that script is useless for Windows / iOS / Android -if(NOT WIN32 AND NOT APPLE_IOS AND NOT ANDROID) +if(NOT WIN32 AND NOT IOS AND NOT ANDROID) install(FILES vcmibuilder DESTINATION ${BIN_DIR} PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif() - -if(WIN32) - if(TBB_FOUND AND MSVC) - install_vcpkg_imported_tgt(TBB::tbb) - endif() - - if(USING_CONAN) - #Conan imports enabled - vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}") - file(GLOB dep_files - ${dep_files} - "${CMAKE_SYSROOT}/bin/*.dll" - "${CMAKE_SYSROOT}/lib/*.dll" - "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_dw2-1.dll" # for 32-bit only? - "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" # for 64-bit only? - "${CONAN_SYSTEM_LIBRARY_LOCATION}/libstdc++-6.dll") - else() - file(GLOB dep_files - ${dep_files} - "${CMAKE_FIND_ROOT_PATH}/bin/*.dll") - endif() - - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - # Copy debug versions of libraries if build type is debug - set(debug_postfix d) - endif() - - if(ENABLE_LAUNCHER OR ENABLE_EDITOR) - get_target_property(QtCore_location Qt${QT_VERSION_MAJOR}::Core LOCATION) - get_filename_component(Qtbin_folder ${QtCore_location} PATH) - file(GLOB dep_files - ${dep_files} - ${Qtbin_folder}/Qt5Core${debug_postfix}.dll - ${Qtbin_folder}/Qt5Gui${debug_postfix}.dll - ${Qtbin_folder}/Qt5Widgets${debug_postfix}.dll - ${Qtbin_folder}/Qt5Network${debug_postfix}.dll - ${Qtbin_folder}/icu*.dll) - get_target_property(integration_type Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin TYPE) - if(NOT(integration_type STREQUAL "INTERFACE_LIBRARY")) - get_target_property(integration_loc Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin LOCATION) - install( - FILES ${integration_loc} - DESTINATION ${BIN_DIR}/platforms - ) - install( - FILES "$" - DESTINATION ${BIN_DIR}/styles) - endif() - endif() - - install(FILES ${dep_files} DESTINATION ${BIN_DIR}) -endif(WIN32) - ####################################### # Packaging section # ####################################### @@ -920,7 +851,7 @@ if(WIN32) if(NOT (${CMAKE_CROSSCOMPILING})) add_subdirectory(win) endif() -elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL) +elseif(MACOS AND NOT ENABLE_MONOLITHIC_INSTALL) set(CPACK_MONOLITHIC_INSTALL 1) set(CPACK_GENERATOR "DragNDrop") set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/osx/dmg_background.png") @@ -964,7 +895,7 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL) # Bundle fixing code must be in separate directory to be executed after all other install code add_subdirectory(osx) -elseif(APPLE_IOS) +elseif(IOS) set(CPACK_GENERATOR ZIP) set(CPACK_ARCHIVE_FILE_EXTENSION ipa) set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) diff --git a/CMakePresets.json b/CMakePresets.json index f40bf4641..2d5788ab0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -10,8 +10,7 @@ "name": "build-with-conan", "hidden": true, "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan-generated/conan_toolchain.cmake", - "FORCE_BUNDLED_MINIZIP": "OFF" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan-generated/conan_toolchain.cmake" } }, { @@ -155,28 +154,30 @@ "name": "windows-msvc-release", "displayName": "Windows x64 RelWithDebInfo", "description": "VCMI RelWithDebInfo build", - "inherits": "default-release", + "inherits": [ + "build-with-conan", + "default-release" + ], "generator": "Visual Studio 17 2022", "architecture": { "value": "x64", "strategy": "set" }, "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", - "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", - "FORCE_BUNDLED_MINIZIP": "ON" + "CMAKE_POLICY_DEFAULT_CMP0091": "NEW" } }, { "name": "windows-msvc-release-x86", "displayName": "Windows x86 RelWithDebInfo", "description": "VCMI RelWithDebInfo build", - "inherits": "default-release", + "inherits": [ + "build-with-conan", + "default-release" + ], "generator": "Visual Studio 17 2022", "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", - "FORCE_BUNDLED_MINIZIP": "ON", "CMAKE_GENERATOR_PLATFORM": "WIN32" } }, @@ -184,16 +185,17 @@ "name": "windows-msvc-release-arm64", "displayName": "Windows ARM64 RelWithDebInfo", "description": "VCMI Windows ARM64 build", - "inherits": "default-release", + "inherits": [ + "build-with-conan", + "default-release" + ], "generator": "Visual Studio 17 2022", "architecture": { "value": "ARM64", "strategy": "set" }, "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", - "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", - "FORCE_BUNDLED_MINIZIP": "ON" + "CMAKE_POLICY_DEFAULT_CMP0091": "NEW" } }, { @@ -206,57 +208,38 @@ } }, { - "name": "windows-msvc-ninja-release", - "displayName": "Windows x64 RelWithDebInfo (Ninja)", - "description": "VCMI RelWithDebInfo build using Ninja + sccache", - "inherits": "default-release", - "generator": "Ninja", - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", - "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", - "FORCE_BUNDLED_MINIZIP": "ON", - "ENABLE_CCACHE": "ON", - "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", - "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded", - "ENABLE_MULTI_PROCESS_BUILDS": "OFF", - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "CMAKE_C_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7", - "CMAKE_CXX_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7" - } - }, - { - "name": "windows-msvc-ninja-release-x86", - "displayName": "Windows x86 RelWithDebInfo (Ninja)", - "description": "VCMI RelWithDebInfo build using Ninja + sccache (x86)", - "inherits": "windows-msvc-ninja-release", - "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x86-windows" - } - }, - { - "name": "windows-msvc-ninja-release-arm64", - "displayName": "Windows ARM64 RelWithDebInfo (Ninja)", - "description": "VCMI RelWithDebInfo build using Ninja + sccache (ARM64)", - "inherits": "windows-msvc-ninja-release", - "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "arm64-windows" - } - }, - { - "name": "windows-mingw-conan-linux", - "displayName": "Ninja+Conan release", - "description": "VCMI Windows Ninja using Conan on Linux", + "name": "windows-msvc-ninja-release", + "displayName": "Windows x64 RelWithDebInfo (Ninja)", + "description": "VCMI RelWithDebInfo build using Ninja + sccache", "inherits": [ "build-with-conan", "default-release" ], + "generator": "Ninja", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "FORCE_BUNDLED_FL": "ON", - "ENABLE_TEMPLATE_EDITOR": "OFF" + "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", + "ENABLE_CCACHE": "ON", + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded", + "ENABLE_MULTI_PROCESS_BUILDS": "OFF", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_C_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7", + "CMAKE_CXX_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7" } }, + { + "name": "windows-msvc-ninja-release-x86", + "displayName": "Windows x86 RelWithDebInfo (Ninja)", + "description": "VCMI RelWithDebInfo build using Ninja + sccache (x86)", + "inherits": "windows-msvc-ninja-release" + }, + { + "name": "windows-msvc-ninja-release-arm64", + "displayName": "Windows ARM64 RelWithDebInfo (Ninja)", + "description": "VCMI RelWithDebInfo build using Ninja + sccache (ARM64)", + "inherits": "windows-msvc-ninja-release" + }, { "name": "macos-ninja-release", "displayName": "Ninja release", @@ -272,8 +255,7 @@ "default-release" ], "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "ENABLE_TEMPLATE_EDITOR": "OFF" + "CMAKE_BUILD_TYPE": "Release" } }, { @@ -290,36 +272,20 @@ "generator": "Xcode" }, { - "name": "ios-device", - "displayName": "Base iOS device", - "description": "Base VCMI preset for iOS device", + "name": "ios-device-conan", + "displayName": "Base iOS device using Conan", + "description": "Base VCMI preset for iOS device using Conan", + "inherits": [ + "build-with-conan" + ], "generator": "Xcode", "binaryDir": "../build-${presetName}", "cacheVariables": { "CMAKE_SYSTEM_NAME": "iOS", "FORCE_BUNDLED_FL": "ON", - "FORCE_BUNDLED_MINIZIP": "ON", "ENABLE_EDITOR" : "OFF" } }, - { - "name": "ios-simulator", - "displayName": "Base iOS simulator", - "description": "Base VCMI preset for iOS simulator", - "inherits": "ios-device", - "cacheVariables": { - "CMAKE_OSX_SYSROOT": "iphonesimulator" - } - }, - { - "name": "ios-device-conan", - "displayName": "Base iOS device using Conan", - "description": "Base VCMI preset for iOS device using Conan", - "inherits": [ - "build-with-conan", - "ios-device" - ] - }, { "name": "base-ios-release", "displayName": "Base iOS release", @@ -352,18 +318,6 @@ "ENABLE_CCACHE": "ON" } }, - { - "name": "ios-release-legacy", - "displayName": "iOS release using legacy dependencies", - "description": "VCMI iOS release using legacy dependencies", - "inherits": [ - "base-ios-release", - "ios-device" - ], - "cacheVariables": { - "CMAKE_PREFIX_PATH": "${sourceDir}/build/iphoneos" - } - }, { "name": "android-conan-ninja-release", "displayName": "Android release", @@ -522,12 +476,6 @@ "configurePreset": "windows-msvc-ninja-release-arm64", "inherits": "default-release" }, - { - "name": "windows-mingw-conan-linux", - "configurePreset": "windows-mingw-conan-linux", - "inherits": "default-release", - "configuration": "Release" - }, { "name": "ios-release-conan", "configurePreset": "ios-release-conan", @@ -543,11 +491,6 @@ "configurePreset": "ios-release-conan-ccache", "inherits": "ios-release-conan" }, - { - "name": "ios-release-legacy", - "configurePreset": "ios-release-legacy", - "inherits": "ios-release-conan" - }, { "name": "android-conan-ninja-release", "configurePreset": "android-conan-ninja-release", @@ -613,11 +556,6 @@ "name": "windows-msvc-release", "configurePreset": "windows-msvc-release", "inherits": "default-release" - }, - { - "name": "windows-mingw-conan-linux", - "configurePreset": "windows-mingw-conan-linux", - "inherits": "default-release" } ] } diff --git a/Mods/vcmi/Content/Sprites/adventureLayers.png b/Mods/vcmi/Content/Sprites/adventureLayers.png new file mode 100644 index 000000000..2f113e3f0 Binary files /dev/null and b/Mods/vcmi/Content/Sprites/adventureLayers.png differ diff --git a/Mods/vcmi/Content/config/english.json b/Mods/vcmi/Content/config/english.json index fe878f673..31e3896d2 100644 --- a/Mods/vcmi/Content/config/english.json +++ b/Mods/vcmi/Content/config/english.json @@ -952,6 +952,11 @@ "creatures.core.marksman.bonus.extraAttack" : "{Shoots twice}\nThis unit can shoot twice", "creatures.core.azureDragon.bonus.fearful" : "{Fear}\nEnemy units have a 10% chance of freezing in fear", "creatures.core.azureDragon.bonus.fearless" : "{Fearless}\nImmune to Fear ability", + "creatures.core.halfling.bonus.lucky" : "{Lucky}\nHalfling's luck cannot be decreased below +1", + "creatures.core.nomad.bonus.sandWalker" : "{Sand walker}\nHero ignores terrain penalty on sand", + "creatures.core.rogue.bonus.visionsMonsters" : "{Visions Monsters}\nHero can see detailed informations about neutral monsters", + "creatures.core.rogue.bonus.visionsHeroes" : "{Visions Heroes}\nHero can see detailed informations about enemy heroes", + "creatures.core.rogue.bonus.visionsTowns" : "{Visions Towns}\nHero can see detailed informations about enemy towns", "core.bonus.ADDITIONAL_ATTACK.description" : "{Additional attacks}\nUnit can attack an additional {$val} times", // TODO: alternative descriptions for melee/ranged effect range "core.bonus.ADDITIONAL_RETALIATION.description" : "{Additional retaliations}\nUnit can retaliate ${val} extra times", diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 62eea479c..31de2b2a5 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -46,7 +46,7 @@ android { manifest.srcFile '../AndroidManifest.xml' jniLibs.srcDirs = ['../libs'] - java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + java.srcDirs = [qt5AndroidDir + '/src', 'src', SDL_JAVA_SRC_DIR] aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qt5AndroidDir + '/res', 'src/main/res', '../res'] } @@ -102,24 +102,6 @@ android { } } -def CommandOutput(final cmd, final arguments, final cwd) { - try { - new ByteArrayOutputStream().withStream { final os -> - exec { - executable cmd - args arguments - workingDir cwd - standardOutput os - } - return os.toString().trim() - } - } - catch (final Exception ex) { - print("Broken: " + cmd + " " + arguments + " in " + cwd + " :: " + ex.toString()) - return "" - } -} - def SigningPropertiesPath(final basePath, final signingConfigKey) { return file("${basePath}/${signingConfigKey}.properties") } @@ -142,7 +124,6 @@ def LoadSigningConfig(final signingConfigKey) { && props.containsKey('KEY_ALIAS')) { signingConfig.storeFile = SigningKeystorePath(signingRoot, props['STORE_FILE']) - signingConfig.storePassword = props['STORE_PASSWORD'] signingConfig.keyAlias = props['KEY_ALIAS'] if(props.containsKey('STORE_PASSWORD')) diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java index 1c7f65cff..393e9ce05 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java @@ -77,7 +77,7 @@ public class VcmiSDLActivity extends SDLActivity @Override protected String[] getLibraries() { - // SDL is linked statically, no need to load anything + // app main library and SDL are loaded when launcher starts, no extra work to do return new String[] { }; } diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDevice.java b/android/vcmi-app/src/main/java/org/libsdl/app/HIDDevice.java deleted file mode 100644 index 955df5d14..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDevice.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.libsdl.app; - -import android.hardware.usb.UsbDevice; - -interface HIDDevice -{ - public int getId(); - public int getVendorId(); - public int getProductId(); - public String getSerialNumber(); - public int getVersion(); - public String getManufacturerName(); - public String getProductName(); - public UsbDevice getDevice(); - public boolean open(); - public int sendFeatureReport(byte[] report); - public int sendOutputReport(byte[] report); - public boolean getFeatureReport(byte[] report); - public void setFrozen(boolean frozen); - public void close(); - public void shutdown(); -} diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java deleted file mode 100644 index 65c5a4237..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ /dev/null @@ -1,650 +0,0 @@ -package org.libsdl.app; - -import android.content.Context; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothGattService; -import android.hardware.usb.UsbDevice; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.os.*; - -//import com.android.internal.util.HexDump; - -import java.lang.Runnable; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.UUID; - -class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { - - private static final String TAG = "hidapi"; - private HIDDeviceManager mManager; - private BluetoothDevice mDevice; - private int mDeviceId; - private BluetoothGatt mGatt; - private boolean mIsRegistered = false; - private boolean mIsConnected = false; - private boolean mIsChromebook = false; - private boolean mIsReconnecting = false; - private boolean mFrozen = false; - private LinkedList mOperations; - GattOperation mCurrentOperation = null; - private Handler mHandler; - - private static final int TRANSPORT_AUTO = 0; - private static final int TRANSPORT_BREDR = 1; - private static final int TRANSPORT_LE = 2; - - private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; - - static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); - static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); - static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); - static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; - - static class GattOperation { - private enum Operation { - CHR_READ, - CHR_WRITE, - ENABLE_NOTIFICATION - } - - Operation mOp; - UUID mUuid; - byte[] mValue; - BluetoothGatt mGatt; - boolean mResult = true; - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - } - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - mValue = value; - } - - public void run() { - // This is executed in main thread - BluetoothGattCharacteristic chr; - - switch (mOp) { - case CHR_READ: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Reading characteristic " + chr.getUuid()); - if (!mGatt.readCharacteristic(chr)) { - Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); - mResult = false; - break; - } - mResult = true; - break; - case CHR_WRITE: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); - chr.setValue(mValue); - if (!mGatt.writeCharacteristic(chr)) { - Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); - mResult = false; - break; - } - mResult = true; - break; - case ENABLE_NOTIFICATION: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); - if (chr != null) { - BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); - if (cccd != null) { - int properties = chr.getProperties(); - byte[] value; - if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { - value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; - } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { - value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; - } else { - Log.e(TAG, "Unable to start notifications on input characteristic"); - mResult = false; - return; - } - - mGatt.setCharacteristicNotification(chr, true); - cccd.setValue(value); - if (!mGatt.writeDescriptor(cccd)) { - Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); - mResult = false; - return; - } - mResult = true; - } - } - } - } - - public boolean finish() { - return mResult; - } - - private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { - BluetoothGattService valveService = mGatt.getService(steamControllerService); - if (valveService == null) - return null; - return valveService.getCharacteristic(uuid); - } - - static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { - return new GattOperation(gatt, Operation.CHR_READ, uuid); - } - - static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { - return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); - } - - static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { - return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); - } - } - - public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { - mManager = manager; - mDevice = device; - mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); - mIsRegistered = false; - mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); - mOperations = new LinkedList(); - mHandler = new Handler(Looper.getMainLooper()); - - mGatt = connectGatt(); - // final HIDDeviceBLESteamController finalThis = this; - // mHandler.postDelayed(new Runnable() { - // @Override - // public void run() { - // finalThis.checkConnectionForChromebookIssue(); - // } - // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); - } - - public String getIdentifier() { - return String.format("SteamController.%s", mDevice.getAddress()); - } - - public BluetoothGatt getGatt() { - return mGatt; - } - - // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead - // of TRANSPORT_LE. Let's force ourselves to connect low energy. - private BluetoothGatt connectGatt(boolean managed) { - if (Build.VERSION.SDK_INT >= 23) { - try { - return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); - } catch (Exception e) { - return mDevice.connectGatt(mManager.getContext(), managed, this); - } - } else { - return mDevice.connectGatt(mManager.getContext(), managed, this); - } - } - - private BluetoothGatt connectGatt() { - return connectGatt(false); - } - - protected int getConnectionState() { - - Context context = mManager.getContext(); - if (context == null) { - // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. - return BluetoothProfile.STATE_DISCONNECTED; - } - - BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); - if (btManager == null) { - // This device doesn't support Bluetooth. We should never be here, because how did - // we instantiate a device to start with? - return BluetoothProfile.STATE_DISCONNECTED; - } - - return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); - } - - public void reconnect() { - - if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { - mGatt.disconnect(); - mGatt = connectGatt(); - } - - } - - protected void checkConnectionForChromebookIssue() { - if (!mIsChromebook) { - // We only do this on Chromebooks, because otherwise it's really annoying to just attempt - // over and over. - return; - } - - int connectionState = getConnectionState(); - - switch (connectionState) { - case BluetoothProfile.STATE_CONNECTED: - if (!mIsConnected) { - // We are in the Bad Chromebook Place. We can force a disconnect - // to try to recover. - Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - } - else if (!isRegistered()) { - if (mGatt.getServices().size() > 0) { - Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); - probeService(this); - } - else { - Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - } - } - else { - Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); - return; - } - break; - - case BluetoothProfile.STATE_DISCONNECTED: - Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); - - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - - case BluetoothProfile.STATE_CONNECTING: - Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); - break; - } - - final HIDDeviceBLESteamController finalThis = this; - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - finalThis.checkConnectionForChromebookIssue(); - } - }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); - } - - private boolean isRegistered() { - return mIsRegistered; - } - - private void setRegistered() { - mIsRegistered = true; - } - - private boolean probeService(HIDDeviceBLESteamController controller) { - - if (isRegistered()) { - return true; - } - - if (!mIsConnected) { - return false; - } - - Log.v(TAG, "probeService controller=" + controller); - - for (BluetoothGattService service : mGatt.getServices()) { - if (service.getUuid().equals(steamControllerService)) { - Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); - - for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { - if (chr.getUuid().equals(inputCharacteristic)) { - Log.v(TAG, "Found input characteristic"); - // Start notifications - BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); - if (cccd != null) { - enableNotification(chr.getUuid()); - } - } - } - return true; - } - } - - if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { - Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); - mIsConnected = false; - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - } - - return false; - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private void finishCurrentGattOperation() { - GattOperation op = null; - synchronized (mOperations) { - if (mCurrentOperation != null) { - op = mCurrentOperation; - mCurrentOperation = null; - } - } - if (op != null) { - boolean result = op.finish(); // TODO: Maybe in main thread as well? - - // Our operation failed, let's add it back to the beginning of our queue. - if (!result) { - mOperations.addFirst(op); - } - } - executeNextGattOperation(); - } - - private void executeNextGattOperation() { - synchronized (mOperations) { - if (mCurrentOperation != null) - return; - - if (mOperations.isEmpty()) - return; - - mCurrentOperation = mOperations.removeFirst(); - } - - // Run in main thread - mHandler.post(new Runnable() { - @Override - public void run() { - synchronized (mOperations) { - if (mCurrentOperation == null) { - Log.e(TAG, "Current operation null in executor?"); - return; - } - - mCurrentOperation.run(); - // now wait for the GATT callback and when it comes, finish this operation - } - } - }); - } - - private void queueGattOperation(GattOperation op) { - synchronized (mOperations) { - mOperations.add(op); - } - executeNextGattOperation(); - } - - private void enableNotification(UUID chrUuid) { - GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); - queueGattOperation(op); - } - - public void writeCharacteristic(UUID uuid, byte[] value) { - GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); - queueGattOperation(op); - } - - public void readCharacteristic(UUID uuid) { - GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); - queueGattOperation(op); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////// BluetoothGattCallback overridden methods - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { - //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); - mIsReconnecting = false; - if (newState == 2) { - mIsConnected = true; - // Run directly, without GattOperation - if (!isRegistered()) { - mHandler.post(new Runnable() { - @Override - public void run() { - mGatt.discoverServices(); - } - }); - } - } - else if (newState == 0) { - mIsConnected = false; - } - - // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. - } - - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - //Log.v(TAG, "onServicesDiscovered status=" + status); - if (status == 0) { - if (gatt.getServices().size() == 0) { - Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); - mIsReconnecting = true; - mIsConnected = false; - gatt.disconnect(); - mGatt = connectGatt(false); - } - else { - probeService(this); - } - } - } - - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); - - if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { - mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); - } - - finishCurrentGattOperation(); - } - - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); - - if (characteristic.getUuid().equals(reportCharacteristic)) { - // Only register controller with the native side once it has been fully configured - if (!isRegistered()) { - Log.v(TAG, "Registering Steam Controller with ID: " + getId()); - mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); - setRegistered(); - } - } - - finishCurrentGattOperation(); - } - - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - // Enable this for verbose logging of controller input reports - //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); - - if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { - mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); - } - } - - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - //Log.v(TAG, "onDescriptorRead status=" + status); - } - - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); - //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); - - if (chr.getUuid().equals(inputCharacteristic)) { - boolean hasWrittenInputDescriptor = true; - BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); - if (reportChr != null) { - Log.v(TAG, "Writing report characteristic to enter valve mode"); - reportChr.setValue(enterValveMode); - gatt.writeCharacteristic(reportChr); - } - } - - finishCurrentGattOperation(); - } - - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - //Log.v(TAG, "onReliableWriteCompleted status=" + status); - } - - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { - //Log.v(TAG, "onReadRemoteRssi status=" + status); - } - - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - //Log.v(TAG, "onMtuChanged status=" + status); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - //////// Public API - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public int getId() { - return mDeviceId; - } - - @Override - public int getVendorId() { - // Valve Corporation - final int VALVE_USB_VID = 0x28DE; - return VALVE_USB_VID; - } - - @Override - public int getProductId() { - // We don't have an easy way to query from the Bluetooth device, but we know what it is - final int D0G_BLE2_PID = 0x1106; - return D0G_BLE2_PID; - } - - @Override - public String getSerialNumber() { - // This will be read later via feature report by Steam - return "12345"; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public String getManufacturerName() { - return "Valve Corporation"; - } - - @Override - public String getProductName() { - return "Steam Controller"; - } - - @Override - public UsbDevice getDevice() { - return null; - } - - @Override - public boolean open() { - return true; - } - - @Override - public int sendFeatureReport(byte[] report) { - if (!isRegistered()) { - Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); - if (mIsConnected) { - probeService(this); - } - return -1; - } - - // We need to skip the first byte, as that doesn't go over the air - byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); - //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); - writeCharacteristic(reportCharacteristic, actual_report); - return report.length; - } - - @Override - public int sendOutputReport(byte[] report) { - if (!isRegistered()) { - Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); - if (mIsConnected) { - probeService(this); - } - return -1; - } - - //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); - writeCharacteristic(reportCharacteristic, report); - return report.length; - } - - @Override - public boolean getFeatureReport(byte[] report) { - if (!isRegistered()) { - Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); - if (mIsConnected) { - probeService(this); - } - return false; - } - - //Log.v(TAG, "getFeatureReport"); - readCharacteristic(reportCharacteristic); - return true; - } - - @Override - public void close() { - } - - @Override - public void setFrozen(boolean frozen) { - mFrozen = frozen; - } - - @Override - public void shutdown() { - close(); - - BluetoothGatt g = mGatt; - if (g != null) { - g.disconnect(); - g.close(); - mGatt = null; - } - mManager = null; - mIsRegistered = false; - mIsConnected = false; - mOperations.clear(); - } - -} - diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceManager.java deleted file mode 100644 index cf3c9267f..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ /dev/null @@ -1,679 +0,0 @@ -package org.libsdl.app; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.os.Build; -import android.util.Log; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.hardware.usb.*; -import android.os.Handler; -import android.os.Looper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - -public class HIDDeviceManager { - private static final String TAG = "hidapi"; - private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; - - private static HIDDeviceManager sManager; - private static int sManagerRefCount = 0; - - public static HIDDeviceManager acquire(Context context) { - if (sManagerRefCount == 0) { - sManager = new HIDDeviceManager(context); - } - ++sManagerRefCount; - return sManager; - } - - public static void release(HIDDeviceManager manager) { - if (manager == sManager) { - --sManagerRefCount; - if (sManagerRefCount == 0) { - sManager.close(); - sManager = null; - } - } - } - - private Context mContext; - private HashMap mDevicesById = new HashMap(); - private HashMap mBluetoothDevices = new HashMap(); - private int mNextDeviceId = 0; - private SharedPreferences mSharedPreferences = null; - private boolean mIsChromebook = false; - private UsbManager mUsbManager; - private Handler mHandler; - private BluetoothManager mBluetoothManager; - private List mLastBluetoothDevices; - - private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDeviceAttached(usbDevice); - } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDeviceDetached(usbDevice); - } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); - } - } - }; - - private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - // Bluetooth device was connected. If it was a Steam Controller, handle it - if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - Log.d(TAG, "Bluetooth device connected: " + device); - - if (isSteamController(device)) { - connectBluetoothDevice(device); - } - } - - // Bluetooth device was disconnected, remove from controller manager (if any) - if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - Log.d(TAG, "Bluetooth device disconnected: " + device); - - disconnectBluetoothDevice(device); - } - } - }; - - private HIDDeviceManager(final Context context) { - mContext = context; - - HIDDeviceRegisterCallback(); - - mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); - mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); - -// if (shouldClear) { -// SharedPreferences.Editor spedit = mSharedPreferences.edit(); -// spedit.clear(); -// spedit.commit(); -// } -// else - { - mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); - } - } - - public Context getContext() { - return mContext; - } - - public int getDeviceIDForIdentifier(String identifier) { - SharedPreferences.Editor spedit = mSharedPreferences.edit(); - - int result = mSharedPreferences.getInt(identifier, 0); - if (result == 0) { - result = mNextDeviceId++; - spedit.putInt("next_device_id", mNextDeviceId); - } - - spedit.putInt(identifier, result); - spedit.commit(); - return result; - } - - private void initializeUSB() { - mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); - if (mUsbManager == null) { - return; - } - - /* - // Logging - for (UsbDevice device : mUsbManager.getDeviceList().values()) { - Log.i(TAG,"Path: " + device.getDeviceName()); - Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); - Log.i(TAG,"Product: " + device.getProductName()); - Log.i(TAG,"ID: " + device.getDeviceId()); - Log.i(TAG,"Class: " + device.getDeviceClass()); - Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); - Log.i(TAG,"Vendor ID " + device.getVendorId()); - Log.i(TAG,"Product ID: " + device.getProductId()); - Log.i(TAG,"Interface count: " + device.getInterfaceCount()); - Log.i(TAG,"---------------------------------------"); - - // Get interface details - for (int index = 0; index < device.getInterfaceCount(); index++) { - UsbInterface mUsbInterface = device.getInterface(index); - Log.i(TAG," ***** *****"); - Log.i(TAG," Interface index: " + index); - Log.i(TAG," Interface ID: " + mUsbInterface.getId()); - Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); - Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); - Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); - Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); - - // Get endpoint details - for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) - { - UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); - Log.i(TAG," ++++ ++++ ++++"); - Log.i(TAG," Endpoint index: " + epi); - Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); - Log.i(TAG," Direction: " + mEndpoint.getDirection()); - Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); - Log.i(TAG," Interval: " + mEndpoint.getInterval()); - Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); - Log.i(TAG," Type: " + mEndpoint.getType()); - } - } - } - Log.i(TAG," No more devices connected."); - */ - - // Register for USB broadcasts and permission completions - IntentFilter filter = new IntentFilter(); - filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); - filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); - filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); - mContext.registerReceiver(mUsbBroadcast, filter); - - for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { - handleUsbDeviceAttached(usbDevice); - } - } - - UsbManager getUSBManager() { - return mUsbManager; - } - - private void shutdownUSB() { - try { - mContext.unregisterReceiver(mUsbBroadcast); - } catch (Exception e) { - // We may not have registered, that's okay - } - } - - private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { - return true; - } - if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { - return true; - } - return false; - } - - private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { - final int XB360_IFACE_SUBCLASS = 93; - final int XB360_IFACE_PROTOCOL = 1; // Wired - final int XB360W_IFACE_PROTOCOL = 129; // Wireless - final int[] SUPPORTED_VENDORS = { - 0x0079, // GPD Win 2 - 0x044f, // Thrustmaster - 0x045e, // Microsoft - 0x046d, // Logitech - 0x056e, // Elecom - 0x06a3, // Saitek - 0x0738, // Mad Catz - 0x07ff, // Mad Catz - 0x0e6f, // PDP - 0x0f0d, // Hori - 0x1038, // SteelSeries - 0x11c9, // Nacon - 0x12ab, // Unknown - 0x1430, // RedOctane - 0x146b, // BigBen - 0x1532, // Razer Sabertooth - 0x15e4, // Numark - 0x162e, // Joytech - 0x1689, // Razer Onza - 0x1949, // Lab126, Inc. - 0x1bad, // Harmonix - 0x20d6, // PowerA - 0x24c6, // PowerA - 0x2c22, // Qanba - }; - - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && - usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && - (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || - usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { - int vendor_id = usbDevice.getVendorId(); - for (int supportedVid : SUPPORTED_VENDORS) { - if (vendor_id == supportedVid) { - return true; - } - } - } - return false; - } - - private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { - final int XB1_IFACE_SUBCLASS = 71; - final int XB1_IFACE_PROTOCOL = 208; - final int[] SUPPORTED_VENDORS = { - 0x045e, // Microsoft - 0x0738, // Mad Catz - 0x0e6f, // PDP - 0x0f0d, // Hori - 0x1532, // Razer Wildcat - 0x20d6, // PowerA - 0x24c6, // PowerA - 0x2dc8, /* 8BitDo */ - 0x2e24, // Hyperkin - }; - - if (usbInterface.getId() == 0 && - usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && - usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && - usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { - int vendor_id = usbDevice.getVendorId(); - for (int supportedVid : SUPPORTED_VENDORS) { - if (vendor_id == supportedVid) { - return true; - } - } - } - return false; - } - - private void handleUsbDeviceAttached(UsbDevice usbDevice) { - connectHIDDeviceUSB(usbDevice); - } - - private void handleUsbDeviceDetached(UsbDevice usbDevice) { - List devices = new ArrayList(); - for (HIDDevice device : mDevicesById.values()) { - if (usbDevice.equals(device.getDevice())) { - devices.add(device.getId()); - } - } - for (int id : devices) { - HIDDevice device = mDevicesById.get(id); - mDevicesById.remove(id); - device.shutdown(); - HIDDeviceDisconnected(id); - } - } - - private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { - for (HIDDevice device : mDevicesById.values()) { - if (usbDevice.equals(device.getDevice())) { - boolean opened = false; - if (permission_granted) { - opened = device.open(); - } - HIDDeviceOpenResult(device.getId(), opened); - } - } - } - - private void connectHIDDeviceUSB(UsbDevice usbDevice) { - synchronized (this) { - int interface_mask = 0; - for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { - UsbInterface usbInterface = usbDevice.getInterface(interface_index); - if (isHIDDeviceInterface(usbDevice, usbInterface)) { - // Check to see if we've already added this interface - // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive - int interface_id = usbInterface.getId(); - if ((interface_mask & (1 << interface_id)) != 0) { - continue; - } - interface_mask |= (1 << interface_id); - - HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); - int id = device.getId(); - mDevicesById.put(id, device); - HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); - } - } - } - } - - private void initializeBluetooth() { - Log.d(TAG, "Initializing Bluetooth"); - - if (Build.VERSION.SDK_INT <= 30 && - mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); - return; - } - - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) { - Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); - return; - } - - // Find bonded bluetooth controllers and create SteamControllers for them - mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); - if (mBluetoothManager == null) { - // This device doesn't support Bluetooth. - return; - } - - BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); - if (btAdapter == null) { - // This device has Bluetooth support in the codebase, but has no available adapters. - return; - } - - // Get our bonded devices. - for (BluetoothDevice device : btAdapter.getBondedDevices()) { - - Log.d(TAG, "Bluetooth device available: " + device); - if (isSteamController(device)) { - connectBluetoothDevice(device); - } - - } - - // NOTE: These don't work on Chromebooks, to my undying dismay. - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - mContext.registerReceiver(mBluetoothBroadcast, filter); - - if (mIsChromebook) { - mHandler = new Handler(Looper.getMainLooper()); - mLastBluetoothDevices = new ArrayList(); - - // final HIDDeviceManager finalThis = this; - // mHandler.postDelayed(new Runnable() { - // @Override - // public void run() { - // finalThis.chromebookConnectionHandler(); - // } - // }, 5000); - } - } - - private void shutdownBluetooth() { - try { - mContext.unregisterReceiver(mBluetoothBroadcast); - } catch (Exception e) { - // We may not have registered, that's okay - } - } - - // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. - // This function provides a sort of dummy version of that, watching for changes in the - // connected devices and attempting to add controllers as things change. - public void chromebookConnectionHandler() { - if (!mIsChromebook) { - return; - } - - ArrayList disconnected = new ArrayList(); - ArrayList connected = new ArrayList(); - - List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); - - for (BluetoothDevice bluetoothDevice : currentConnected) { - if (!mLastBluetoothDevices.contains(bluetoothDevice)) { - connected.add(bluetoothDevice); - } - } - for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { - if (!currentConnected.contains(bluetoothDevice)) { - disconnected.add(bluetoothDevice); - } - } - - mLastBluetoothDevices = currentConnected; - - for (BluetoothDevice bluetoothDevice : disconnected) { - disconnectBluetoothDevice(bluetoothDevice); - } - for (BluetoothDevice bluetoothDevice : connected) { - connectBluetoothDevice(bluetoothDevice); - } - - final HIDDeviceManager finalThis = this; - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - finalThis.chromebookConnectionHandler(); - } - }, 10000); - } - - public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { - Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); - synchronized (this) { - if (mBluetoothDevices.containsKey(bluetoothDevice)) { - Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); - - HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); - device.reconnect(); - - return false; - } - HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); - int id = device.getId(); - mBluetoothDevices.put(bluetoothDevice, device); - mDevicesById.put(id, device); - - // The Steam Controller will mark itself connected once initialization is complete - } - return true; - } - - public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { - synchronized (this) { - HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); - if (device == null) - return; - - int id = device.getId(); - mBluetoothDevices.remove(bluetoothDevice); - mDevicesById.remove(id); - device.shutdown(); - HIDDeviceDisconnected(id); - } - } - - public boolean isSteamController(BluetoothDevice bluetoothDevice) { - // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. - if (bluetoothDevice == null) { - return false; - } - - // If the device has no local name, we really don't want to try an equality check against it. - if (bluetoothDevice.getName() == null) { - return false; - } - - return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); - } - - private void close() { - shutdownUSB(); - shutdownBluetooth(); - synchronized (this) { - for (HIDDevice device : mDevicesById.values()) { - device.shutdown(); - } - mDevicesById.clear(); - mBluetoothDevices.clear(); - HIDDeviceReleaseCallback(); - } - } - - public void setFrozen(boolean frozen) { - synchronized (this) { - for (HIDDevice device : mDevicesById.values()) { - device.setFrozen(frozen); - } - } - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private HIDDevice getDevice(int id) { - synchronized (this) { - HIDDevice result = mDevicesById.get(id); - if (result == null) { - Log.v(TAG, "No device for id: " + id); - Log.v(TAG, "Available devices: " + mDevicesById.keySet()); - } - return result; - } - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////// JNI interface functions - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - public boolean initialize(boolean usb, boolean bluetooth) { - Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); - - if (usb) { - initializeUSB(); - } - if (bluetooth) { - initializeBluetooth(); - } - return true; - } - - public boolean openDevice(int deviceID) { - Log.v(TAG, "openDevice deviceID=" + deviceID); - HIDDevice device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return false; - } - - // Look to see if this is a USB device and we have permission to access it - UsbDevice usbDevice = device.getDevice(); - if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { - HIDDeviceOpenPending(deviceID); - try { - final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 - int flags; - if (Build.VERSION.SDK_INT >= 31) { - flags = FLAG_MUTABLE; - } else { - flags = 0; - } - mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); - } catch (Exception e) { - Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); - HIDDeviceOpenResult(deviceID, false); - } - return false; - } - - try { - return device.open(); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return false; - } - - public int sendOutputReport(int deviceID, byte[] report) { - try { - //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return -1; - } - - return device.sendOutputReport(report); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return -1; - } - - public int sendFeatureReport(int deviceID, byte[] report) { - try { - //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return -1; - } - - return device.sendFeatureReport(report); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return -1; - } - - public boolean getFeatureReport(int deviceID, byte[] report) { - try { - //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return false; - } - - return device.getFeatureReport(report); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return false; - } - - public void closeDevice(int deviceID) { - try { - Log.v(TAG, "closeDevice deviceID=" + deviceID); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return; - } - - device.close(); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - } - - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////// Native methods - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private native void HIDDeviceRegisterCallback(); - private native void HIDDeviceReleaseCallback(); - - native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); - native void HIDDeviceOpenPending(int deviceID); - native void HIDDeviceOpenResult(int deviceID, boolean opened); - native void HIDDeviceDisconnected(int deviceID); - - native void HIDDeviceInputReport(int deviceID, byte[] report); - native void HIDDeviceFeatureReport(int deviceID, byte[] report); -} diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceUSB.java deleted file mode 100644 index d20fe80bc..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ /dev/null @@ -1,309 +0,0 @@ -package org.libsdl.app; - -import android.hardware.usb.*; -import android.os.Build; -import android.util.Log; -import java.util.Arrays; - -class HIDDeviceUSB implements HIDDevice { - - private static final String TAG = "hidapi"; - - protected HIDDeviceManager mManager; - protected UsbDevice mDevice; - protected int mInterfaceIndex; - protected int mInterface; - protected int mDeviceId; - protected UsbDeviceConnection mConnection; - protected UsbEndpoint mInputEndpoint; - protected UsbEndpoint mOutputEndpoint; - protected InputThread mInputThread; - protected boolean mRunning; - protected boolean mFrozen; - - public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { - mManager = manager; - mDevice = usbDevice; - mInterfaceIndex = interface_index; - mInterface = mDevice.getInterface(mInterfaceIndex).getId(); - mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); - mRunning = false; - } - - public String getIdentifier() { - return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); - } - - @Override - public int getId() { - return mDeviceId; - } - - @Override - public int getVendorId() { - return mDevice.getVendorId(); - } - - @Override - public int getProductId() { - return mDevice.getProductId(); - } - - @Override - public String getSerialNumber() { - String result = null; - if (Build.VERSION.SDK_INT >= 21) { - try { - result = mDevice.getSerialNumber(); - } - catch (SecurityException exception) { - //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); - } - } - if (result == null) { - result = ""; - } - return result; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public String getManufacturerName() { - String result = null; - if (Build.VERSION.SDK_INT >= 21) { - result = mDevice.getManufacturerName(); - } - if (result == null) { - result = String.format("%x", getVendorId()); - } - return result; - } - - @Override - public String getProductName() { - String result = null; - if (Build.VERSION.SDK_INT >= 21) { - result = mDevice.getProductName(); - } - if (result == null) { - result = String.format("%x", getProductId()); - } - return result; - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - public String getDeviceName() { - return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; - } - - @Override - public boolean open() { - mConnection = mManager.getUSBManager().openDevice(mDevice); - if (mConnection == null) { - Log.w(TAG, "Unable to open USB device " + getDeviceName()); - return false; - } - - // Force claim our interface - UsbInterface iface = mDevice.getInterface(mInterfaceIndex); - if (!mConnection.claimInterface(iface, true)) { - Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); - close(); - return false; - } - - // Find the endpoints - for (int j = 0; j < iface.getEndpointCount(); j++) { - UsbEndpoint endpt = iface.getEndpoint(j); - switch (endpt.getDirection()) { - case UsbConstants.USB_DIR_IN: - if (mInputEndpoint == null) { - mInputEndpoint = endpt; - } - break; - case UsbConstants.USB_DIR_OUT: - if (mOutputEndpoint == null) { - mOutputEndpoint = endpt; - } - break; - } - } - - // Make sure the required endpoints were present - if (mInputEndpoint == null || mOutputEndpoint == null) { - Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); - close(); - return false; - } - - // Start listening for input - mRunning = true; - mInputThread = new InputThread(); - mInputThread.start(); - - return true; - } - - @Override - public int sendFeatureReport(byte[] report) { - int res = -1; - int offset = 0; - int length = report.length; - boolean skipped_report_id = false; - byte report_number = report[0]; - - if (report_number == 0x0) { - ++offset; - --length; - skipped_report_id = true; - } - - res = mConnection.controlTransfer( - UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, - 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - mInterface, - report, offset, length, - 1000/*timeout millis*/); - - if (res < 0) { - Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); - return -1; - } - - if (skipped_report_id) { - ++length; - } - return length; - } - - @Override - public int sendOutputReport(byte[] report) { - int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); - if (r != report.length) { - Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); - } - return r; - } - - @Override - public boolean getFeatureReport(byte[] report) { - int res = -1; - int offset = 0; - int length = report.length; - boolean skipped_report_id = false; - byte report_number = report[0]; - - if (report_number == 0x0) { - /* Offset the return buffer by 1, so that the report ID - will remain in byte 0. */ - ++offset; - --length; - skipped_report_id = true; - } - - res = mConnection.controlTransfer( - UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, - 0x01/*HID get_report*/, - (3/*HID feature*/ << 8) | report_number, - mInterface, - report, offset, length, - 1000/*timeout millis*/); - - if (res < 0) { - Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); - return false; - } - - if (skipped_report_id) { - ++res; - ++length; - } - - byte[] data; - if (res == length) { - data = report; - } else { - data = Arrays.copyOfRange(report, 0, res); - } - mManager.HIDDeviceFeatureReport(mDeviceId, data); - - return true; - } - - @Override - public void close() { - mRunning = false; - if (mInputThread != null) { - while (mInputThread.isAlive()) { - mInputThread.interrupt(); - try { - mInputThread.join(); - } catch (InterruptedException e) { - // Keep trying until we're done - } - } - mInputThread = null; - } - if (mConnection != null) { - UsbInterface iface = mDevice.getInterface(mInterfaceIndex); - mConnection.releaseInterface(iface); - mConnection.close(); - mConnection = null; - } - } - - @Override - public void shutdown() { - close(); - mManager = null; - } - - @Override - public void setFrozen(boolean frozen) { - mFrozen = frozen; - } - - protected class InputThread extends Thread { - @Override - public void run() { - int packetSize = mInputEndpoint.getMaxPacketSize(); - byte[] packet = new byte[packetSize]; - while (mRunning) { - int r; - try - { - r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); - } - catch (Exception e) - { - Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); - break; - } - if (r < 0) { - // Could be a timeout or an I/O error - } - if (r > 0) { - byte[] data; - if (r == packetSize) { - data = packet; - } else { - data = Arrays.copyOfRange(packet, 0, r); - } - - if (!mFrozen) { - mManager.HIDDeviceInputReport(mDeviceId, data); - } - } - } - } - } -} diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/SDL.java b/android/vcmi-app/src/main/java/org/libsdl/app/SDL.java deleted file mode 100644 index dafc0cb87..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/SDL.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.libsdl.app; - -import android.content.Context; - -import java.lang.Class; -import java.lang.reflect.Method; - -/** - SDL library initialization -*/ -public class SDL { - - // This function should be called first and sets up the native code - // so it can call into the Java classes - public static void setupJNI() { - SDLActivity.nativeSetupJNI(); - SDLAudioManager.nativeSetupJNI(); - SDLControllerManager.nativeSetupJNI(); - } - - // This function should be called each time the activity is started - public static void initialize() { - setContext(null); - - SDLActivity.initialize(); - SDLAudioManager.initialize(); - SDLControllerManager.initialize(); - } - - // This function stores the current activity (SDL or not) - public static void setContext(Context context) { - mContext = context; - } - - public static Context getContext() { - return mContext; - } - - public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { - - if (libraryName == null) { - throw new NullPointerException("No library name provided."); - } - - try { - // Let's see if we have ReLinker available in the project. This is necessary for - // some projects that have huge numbers of local libraries bundled, and thus may - // trip a bug in Android's native library loader which ReLinker works around. (If - // loadLibrary works properly, ReLinker will simply use the normal Android method - // internally.) - // - // To use ReLinker, just add it as a dependency. For more information, see - // https://github.com/KeepSafe/ReLinker for ReLinker's repository. - // - Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); - Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); - Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); - Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); - - // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if - // they've changed during updates. - Method forceMethod = relinkClass.getDeclaredMethod("force"); - Object relinkInstance = forceMethod.invoke(null); - Class relinkInstanceClass = relinkInstance.getClass(); - - // Actually load the library! - Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); - loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); - } - catch (final Throwable e) { - // Fall back - try { - System.loadLibrary(libraryName); - } - catch (final UnsatisfiedLinkError ule) { - throw ule; - } - catch (final SecurityException se) { - throw se; - } - } - } - - protected static Context mContext; -} diff --git a/android/vcmi-app/src/main/java/org/libsdl/app/SDLActivity.java b/android/vcmi-app/src/main/java/org/libsdl/app/SDLActivity.java deleted file mode 100644 index 649fb1e74..000000000 --- a/android/vcmi-app/src/main/java/org/libsdl/app/SDLActivity.java +++ /dev/null @@ -1,2102 +0,0 @@ -package org.libsdl.app; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.UiModeManager; -import android.content.ClipboardManager; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.hardware.Sensor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.text.Editable; -import android.text.InputType; -import android.text.Selection; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.SparseArray; -import android.view.Display; -import android.view.Gravity; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.PointerIcon; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; - -import java.util.Hashtable; -import java.util.Locale; - - -/** - SDL Activity -*/ -public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { - private static final String TAG = "SDL"; - private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 26; - private static final int SDL_MICRO_VERSION = 5; -/* - // Display InputType.SOURCE/CLASS of events and devices - // - // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); - // SDLActivity.debugSource(event.getSource(), "event"); - public static void debugSource(int sources, String prefix) { - int s = sources; - int s_copy = sources; - String cls = ""; - String src = ""; - int tst = 0; - int FLAG_TAINTED = 0x80000000; - - if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; - if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; - if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; - if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; - if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; - - - int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits - s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON - | InputDevice.SOURCE_CLASS_JOYSTICK - | InputDevice.SOURCE_CLASS_POINTER - | InputDevice.SOURCE_CLASS_POSITION - | InputDevice.SOURCE_CLASS_TRACKBALL); - - if (s2 != 0) cls += "Some_Unknown"; - - s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; - if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; - s2 &= ~tst; - } - - tst = InputDevice.SOURCE_DPAD; - if ((s & tst) == tst) src += " DPAD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_GAMEPAD; - if ((s & tst) == tst) src += " GAMEPAD"; - s2 &= ~tst; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - tst = InputDevice.SOURCE_HDMI; - if ((s & tst) == tst) src += " HDMI"; - s2 &= ~tst; - } - - tst = InputDevice.SOURCE_JOYSTICK; - if ((s & tst) == tst) src += " JOYSTICK"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_KEYBOARD; - if ((s & tst) == tst) src += " KEYBOARD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_MOUSE; - if ((s & tst) == tst) src += " MOUSE"; - s2 &= ~tst; - - if (Build.VERSION.SDK_INT >= 26) { - tst = InputDevice.SOURCE_MOUSE_RELATIVE; - if ((s & tst) == tst) src += " MOUSE_RELATIVE"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_ROTARY_ENCODER; - if ((s & tst) == tst) src += " ROTARY_ENCODER"; - s2 &= ~tst; - } - tst = InputDevice.SOURCE_STYLUS; - if ((s & tst) == tst) src += " STYLUS"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TOUCHPAD; - if ((s & tst) == tst) src += " TOUCHPAD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TOUCHSCREEN; - if ((s & tst) == tst) src += " TOUCHSCREEN"; - s2 &= ~tst; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - tst = InputDevice.SOURCE_TOUCH_NAVIGATION; - if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; - s2 &= ~tst; - } - - tst = InputDevice.SOURCE_TRACKBALL; - if ((s & tst) == tst) src += " TRACKBALL"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_ANY; - if ((s & tst) == tst) src += " ANY"; - s2 &= ~tst; - - if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; - s2 &= ~FLAG_TAINTED; - - if (s2 != 0) src += " Some_Unknown"; - - Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); - } -*/ - - public static boolean mIsResumedCalled, mHasFocus; - public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); - - // Cursor types - // private static final int SDL_SYSTEM_CURSOR_NONE = -1; - private static final int SDL_SYSTEM_CURSOR_ARROW = 0; - private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; - private static final int SDL_SYSTEM_CURSOR_WAIT = 2; - private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; - private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; - private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; - private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; - private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; - private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; - private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; - private static final int SDL_SYSTEM_CURSOR_NO = 10; - private static final int SDL_SYSTEM_CURSOR_HAND = 11; - - protected static final int SDL_ORIENTATION_UNKNOWN = 0; - protected static final int SDL_ORIENTATION_LANDSCAPE = 1; - protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; - protected static final int SDL_ORIENTATION_PORTRAIT = 3; - protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; - - protected static int mCurrentOrientation; - protected static Locale mCurrentLocale; - - // Handle the state of the native layer - public enum NativeState { - INIT, RESUMED, PAUSED - } - - public static NativeState mNextNativeState; - public static NativeState mCurrentNativeState; - - /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ - public static boolean mBrokenLibraries = true; - - // Main components - protected static SDLActivity mSingleton; - protected static SDLSurface mSurface; - protected static DummyEdit mTextEdit; - protected static boolean mScreenKeyboardShown; - protected static ViewGroup mLayout; - protected static SDLClipboardHandler mClipboardHandler; - protected static Hashtable mCursors; - protected static int mLastCursorID; - protected static SDLGenericMotionListener_API12 mMotionListener; - protected static HIDDeviceManager mHIDDeviceManager; - - // This is what SDL runs in. It invokes SDL_main(), eventually - protected static Thread mSDLThread; - - protected static SDLGenericMotionListener_API12 getMotionListener() { - if (mMotionListener == null) { - if (Build.VERSION.SDK_INT >= 26) { - mMotionListener = new SDLGenericMotionListener_API26(); - } else if (Build.VERSION.SDK_INT >= 24) { - mMotionListener = new SDLGenericMotionListener_API24(); - } else { - mMotionListener = new SDLGenericMotionListener_API12(); - } - } - - return mMotionListener; - } - - /** - * This method returns the name of the shared object with the application entry point - * It can be overridden by derived classes. - */ - protected String getMainSharedObject() { - String library; - String[] libraries = SDLActivity.mSingleton.getLibraries(); - if (libraries.length > 0) { - library = "lib" + libraries[libraries.length - 1] + ".so"; - } else { - library = "libmain.so"; - } - return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; - } - - /** - * This method returns the name of the application entry point - * It can be overridden by derived classes. - */ - protected String getMainFunction() { - return "SDL_main"; - } - - /** - * This method is called by SDL before loading the native shared libraries. - * It can be overridden to provide names of shared libraries to be loaded. - * The default implementation returns the defaults. It never returns null. - * An array returned by a new implementation must at least contain "SDL2". - * Also keep in mind that the order the libraries are loaded may matter. - * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). - */ - protected String[] getLibraries() { - return new String[] { - "SDL2", - // "SDL2_image", - // "SDL2_mixer", - // "SDL2_net", - // "SDL2_ttf", - "main" - }; - } - - // Load the .so - public void loadLibraries() { - for (String lib : getLibraries()) { - SDL.loadLibrary(lib); - } - } - - /** - * This method is called by SDL before starting the native application thread. - * It can be overridden to provide the arguments after the application name. - * The default implementation returns an empty array. It never returns null. - * @return arguments for the native application. - */ - protected String[] getArguments() { - return new String[0]; - } - - public static void initialize() { - // The static nature of the singleton and Android quirkyness force us to initialize everything here - // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values - mSingleton = null; - mSurface = null; - mTextEdit = null; - mLayout = null; - mClipboardHandler = null; - mCursors = new Hashtable(); - mLastCursorID = 0; - mSDLThread = null; - mIsResumedCalled = false; - mHasFocus = true; - mNextNativeState = NativeState.INIT; - mCurrentNativeState = NativeState.INIT; - } - - protected SDLSurface createSDLSurface(Context context) { - return new SDLSurface(context); - } - - // Setup - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.v(TAG, "Device: " + Build.DEVICE); - Log.v(TAG, "Model: " + Build.MODEL); - Log.v(TAG, "onCreate()"); - super.onCreate(savedInstanceState); - - try { - Thread.currentThread().setName("SDLActivity"); - } catch (Exception e) { - Log.v(TAG, "modify thread properties failed " + e.toString()); - } - - // Load shared libraries - String errorMsgBrokenLib = ""; - try { - loadLibraries(); - mBrokenLibraries = false; /* success */ - } catch(UnsatisfiedLinkError e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } catch(Exception e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } - - if (!mBrokenLibraries) { - String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + - String.valueOf(SDL_MINOR_VERSION) + "." + - String.valueOf(SDL_MICRO_VERSION); - String version = nativeGetVersion(); - if (!version.equals(expected_version)) { - mBrokenLibraries = true; - errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; - } - } - - if (mBrokenLibraries) { - mSingleton = this; - AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); - dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." - + System.getProperty("line.separator") - + System.getProperty("line.separator") - + "Error: " + errorMsgBrokenLib); - dlgAlert.setTitle("SDL Error"); - dlgAlert.setPositiveButton("Exit", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog,int id) { - // if this button is clicked, close current activity - SDLActivity.mSingleton.finish(); - } - }); - dlgAlert.setCancelable(false); - dlgAlert.create().show(); - - return; - } - - // Set up JNI - SDL.setupJNI(); - - // Initialize state - SDL.initialize(); - - // So we can call stuff from static callbacks - mSingleton = this; - SDL.setContext(this); - - mClipboardHandler = new SDLClipboardHandler(); - - mHIDDeviceManager = HIDDeviceManager.acquire(this); - - // Set up the surface - mSurface = createSDLSurface(getApplication()); - - mLayout = new RelativeLayout(this); - mLayout.addView(mSurface); - - // Get our current screen orientation and pass it down. - mCurrentOrientation = SDLActivity.getCurrentOrientation(); - // Only record current orientation - SDLActivity.onNativeOrientationChanged(mCurrentOrientation); - - try { - if (Build.VERSION.SDK_INT < 24) { - mCurrentLocale = getContext().getResources().getConfiguration().locale; - } else { - mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); - } - } catch(Exception ignored) { - } - - setContentView(mLayout); - - setWindowStyle(false); - - getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); - - // Get filename from "Open with" of another application - Intent intent = getIntent(); - if (intent != null && intent.getData() != null) { - String filename = intent.getData().getPath(); - if (filename != null) { - Log.v(TAG, "Got filename: " + filename); - SDLActivity.onNativeDropFile(filename); - } - } - } - - protected void pauseNativeThread() { - mNextNativeState = NativeState.PAUSED; - mIsResumedCalled = false; - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.handleNativeState(); - } - - protected void resumeNativeThread() { - mNextNativeState = NativeState.RESUMED; - mIsResumedCalled = true; - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.handleNativeState(); - } - - // Events - @Override - protected void onPause() { - Log.v(TAG, "onPause()"); - super.onPause(); - - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(true); - } - if (!mHasMultiWindow) { - pauseNativeThread(); - } - } - - @Override - protected void onResume() { - Log.v(TAG, "onResume()"); - super.onResume(); - - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(false); - } - if (!mHasMultiWindow) { - resumeNativeThread(); - } - } - - @Override - protected void onStop() { - Log.v(TAG, "onStop()"); - super.onStop(); - if (mHasMultiWindow) { - pauseNativeThread(); - } - } - - @Override - protected void onStart() { - Log.v(TAG, "onStart()"); - super.onStart(); - if (mHasMultiWindow) { - resumeNativeThread(); - } - } - - public static int getCurrentOrientation() { - int result = SDL_ORIENTATION_UNKNOWN; - - Activity activity = (Activity)getContext(); - if (activity == null) { - return result; - } - Display display = activity.getWindowManager().getDefaultDisplay(); - - switch (display.getRotation()) { - case Surface.ROTATION_0: - result = SDL_ORIENTATION_PORTRAIT; - break; - - case Surface.ROTATION_90: - result = SDL_ORIENTATION_LANDSCAPE; - break; - - case Surface.ROTATION_180: - result = SDL_ORIENTATION_PORTRAIT_FLIPPED; - break; - - case Surface.ROTATION_270: - result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - } - - return result; - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); - - if (SDLActivity.mBrokenLibraries) { - return; - } - - mHasFocus = hasFocus; - if (hasFocus) { - mNextNativeState = NativeState.RESUMED; - SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); - - SDLActivity.handleNativeState(); - nativeFocusChanged(true); - - } else { - nativeFocusChanged(false); - if (!mHasMultiWindow) { - mNextNativeState = NativeState.PAUSED; - SDLActivity.handleNativeState(); - } - } - } - - @Override - public void onLowMemory() { - Log.v(TAG, "onLowMemory()"); - super.onLowMemory(); - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.nativeLowMemory(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Log.v(TAG, "onConfigurationChanged()"); - super.onConfigurationChanged(newConfig); - - if (SDLActivity.mBrokenLibraries) { - return; - } - - if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { - mCurrentLocale = newConfig.locale; - SDLActivity.onNativeLocaleChanged(); - } - } - - @Override - protected void onDestroy() { - Log.v(TAG, "onDestroy()"); - - if (mHIDDeviceManager != null) { - HIDDeviceManager.release(mHIDDeviceManager); - mHIDDeviceManager = null; - } - - if (SDLActivity.mBrokenLibraries) { - super.onDestroy(); - return; - } - - if (SDLActivity.mSDLThread != null) { - - // Send Quit event to "SDLThread" thread - SDLActivity.nativeSendQuit(); - - // Wait for "SDLThread" thread to end - try { - SDLActivity.mSDLThread.join(); - } catch(Exception e) { - Log.v(TAG, "Problem stopping SDLThread: " + e); - } - } - - SDLActivity.nativeQuit(); - - super.onDestroy(); - } - - @Override - public void onBackPressed() { - // Check if we want to block the back button in case of mouse right click. - // - // If we do, the normal hardware back button will no longer work and people have to use home, - // but the mouse right click will work. - // - boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); - if (trapBack) { - // Exit and let the mouse handler handle this button (if appropriate) - return; - } - - // Default system back button behavior. - if (!isFinishing()) { - super.onBackPressed(); - } - } - - // Called by JNI from SDL. - public static void manualBackButton() { - mSingleton.pressBackButton(); - } - - // Used to get us onto the activity's main thread - public void pressBackButton() { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (!SDLActivity.this.isFinishing()) { - SDLActivity.this.superOnBackPressed(); - } - } - }); - } - - // Used to access the system back behavior. - public void superOnBackPressed() { - super.onBackPressed(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - - if (SDLActivity.mBrokenLibraries) { - return false; - } - - int keyCode = event.getKeyCode(); - // Ignore certain special keys so they're handled by Android - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || - keyCode == KeyEvent.KEYCODE_VOLUME_UP || - keyCode == KeyEvent.KEYCODE_CAMERA || - keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ - keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ - ) { - return false; - } - return super.dispatchKeyEvent(event); - } - - /* Transition to next state */ - public static void handleNativeState() { - - if (mNextNativeState == mCurrentNativeState) { - // Already in same state, discard. - return; - } - - // Try a transition to init state - if (mNextNativeState == NativeState.INIT) { - - mCurrentNativeState = mNextNativeState; - return; - } - - // Try a transition to paused state - if (mNextNativeState == NativeState.PAUSED) { - if (mSDLThread != null) { - nativePause(); - } - if (mSurface != null) { - mSurface.handlePause(); - } - mCurrentNativeState = mNextNativeState; - return; - } - - // Try a transition to resumed state - if (mNextNativeState == NativeState.RESUMED) { - if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { - if (mSDLThread == null) { - // This is the entry point to the C app. - // Start up the C app thread and enable sensor input for the first time - // FIXME: Why aren't we enabling sensor input at start? - - mSDLThread = new Thread(new SDLMain(), "SDLThread"); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); - mSDLThread.start(); - - // No nativeResume(), don't signal Android_ResumeSem - } else { - nativeResume(); - } - mSurface.handleResume(); - - mCurrentNativeState = mNextNativeState; - } - } - } - - // Messages from the SDLMain thread - static final int COMMAND_CHANGE_TITLE = 1; - static final int COMMAND_CHANGE_WINDOW_STYLE = 2; - static final int COMMAND_TEXTEDIT_HIDE = 3; - static final int COMMAND_SET_KEEP_SCREEN_ON = 5; - - protected static final int COMMAND_USER = 0x8000; - - protected static boolean mFullscreenModeActive; - - /** - * This method is called by SDL if SDL did not handle a message itself. - * This happens if a received message contains an unsupported command. - * Method can be overwritten to handle Messages in a different class. - * @param command the command of the message. - * @param param the parameter of the message. May be null. - * @return if the message was handled in overridden method. - */ - protected boolean onUnhandledMessage(int command, Object param) { - return false; - } - - /** - * A Handler class for Messages from native SDL applications. - * It uses current Activities as target (e.g. for the title). - * static to prevent implicit references to enclosing object. - */ - protected static class SDLCommandHandler extends Handler { - @Override - public void handleMessage(Message msg) { - Context context = SDL.getContext(); - if (context == null) { - Log.e(TAG, "error handling message, getContext() returned null"); - return; - } - switch (msg.arg1) { - case COMMAND_CHANGE_TITLE: - if (context instanceof Activity) { - ((Activity) context).setTitle((String)msg.obj); - } else { - Log.e(TAG, "error handling message, getContext() returned no Activity"); - } - break; - case COMMAND_CHANGE_WINDOW_STYLE: - if (Build.VERSION.SDK_INT >= 19) { - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { - int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - SDLActivity.mFullscreenModeActive = true; - } else { - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - SDLActivity.mFullscreenModeActive = false; - } - } - } else { - Log.e(TAG, "error handling message, getContext() returned no Activity"); - } - } - break; - case COMMAND_TEXTEDIT_HIDE: - if (mTextEdit != null) { - // Note: On some devices setting view to GONE creates a flicker in landscape. - // Setting the View's sizes to 0 is similar to GONE but without the flicker. - // The sizes will be set to useful values when the keyboard is shown again. - mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); - - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); - - mScreenKeyboardShown = false; - - mSurface.requestFocus(); - } - break; - case COMMAND_SET_KEEP_SCREEN_ON: - { - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - } - } - break; - } - default: - if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { - Log.e(TAG, "error handling message, command is " + msg.arg1); - } - } - } - } - - // Handler for the messages - Handler commandHandler = new SDLCommandHandler(); - - // Send a message from the SDLMain thread - boolean sendCommand(int command, Object data) { - Message msg = commandHandler.obtainMessage(); - msg.arg1 = command; - msg.obj = data; - boolean result = commandHandler.sendMessage(msg); - - if (Build.VERSION.SDK_INT >= 19) { - if (command == COMMAND_CHANGE_WINDOW_STYLE) { - // Ensure we don't return until the resize has actually happened, - // or 500ms have passed. - - boolean bShouldWait = false; - - if (data instanceof Integer) { - // Let's figure out if we're already laid out fullscreen or not. - Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - DisplayMetrics realMetrics = new DisplayMetrics(); - display.getRealMetrics(realMetrics); - - boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && - (realMetrics.heightPixels == mSurface.getHeight())); - - if ((Integer) data == 1) { - // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going - // to change size and should wait for surfaceChanged() before we return, so the size - // is right back in native code. If we're already laid out fullscreen, though, we're - // not going to change size even if we change decor modes, so we shouldn't wait for - // surfaceChanged() -- which may not even happen -- and should return immediately. - bShouldWait = !bFullscreenLayout; - } else { - // If we're laid out fullscreen (even if the status bar and nav bar are present), - // or are actively in fullscreen, we're going to change size and should wait for - // surfaceChanged before we return, so the size is right back in native code. - bShouldWait = bFullscreenLayout; - } - } - - if (bShouldWait && (SDLActivity.getContext() != null)) { - // We'll wait for the surfaceChanged() method, which will notify us - // when called. That way, we know our current size is really the - // size we need, instead of grabbing a size that's still got - // the navigation and/or status bars before they're hidden. - // - // We'll wait for up to half a second, because some devices - // take a surprisingly long time for the surface resize, but - // then we'll just give up and return. - // - synchronized (SDLActivity.getContext()) { - try { - SDLActivity.getContext().wait(500); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } - } - } - - return result; - } - - // C functions we call - public static native String nativeGetVersion(); - public static native int nativeSetupJNI(); - public static native int nativeRunMain(String library, String function, Object arguments); - public static native void nativeLowMemory(); - public static native void nativeSendQuit(); - public static native void nativeQuit(); - public static native void nativePause(); - public static native void nativeResume(); - public static native void nativeFocusChanged(boolean hasFocus); - public static native void onNativeDropFile(String filename); - public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); - public static native void onNativeResize(); - public static native void onNativeKeyDown(int keycode); - public static native void onNativeKeyUp(int keycode); - public static native boolean onNativeSoftReturnKey(); - public static native void onNativeKeyboardFocusLost(); - public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); - public static native void onNativeTouch(int touchDevId, int pointerFingerId, - int action, float x, - float y, float p); - public static native void onNativeAccel(float x, float y, float z); - public static native void onNativeClipboardChanged(); - public static native void onNativeSurfaceCreated(); - public static native void onNativeSurfaceChanged(); - public static native void onNativeSurfaceDestroyed(); - public static native String nativeGetHint(String name); - public static native boolean nativeGetHintBoolean(String name, boolean default_value); - public static native void nativeSetenv(String name, String value); - public static native void onNativeOrientationChanged(int orientation); - public static native void nativeAddTouch(int touchId, String name); - public static native void nativePermissionResult(int requestCode, boolean result); - public static native void onNativeLocaleChanged(); - - /** - * This method is called by SDL using JNI. - */ - public static boolean setActivityTitle(String title) { - // Called from SDLMain() thread and can't directly affect the view - return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); - } - - /** - * This method is called by SDL using JNI. - */ - public static void setWindowStyle(boolean fullscreen) { - // Called from SDLMain() thread and can't directly affect the view - mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); - } - - /** - * This method is called by SDL using JNI. - * This is a static method for JNI convenience, it calls a non-static method - * so that is can be overridden - */ - public static void setOrientation(int w, int h, boolean resizable, String hint) - { - if (mSingleton != null) { - mSingleton.setOrientationBis(w, h, resizable, hint); - } - } - - /** - * This can be overridden - */ - public void setOrientationBis(int w, int h, boolean resizable, String hint) - { - int orientation_landscape = -1; - int orientation_portrait = -1; - - /* If set, hint "explicitly controls which UI orientations are allowed". */ - if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; - } else if (hint.contains("LandscapeRight")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - } else if (hint.contains("LandscapeLeft")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - } - - if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; - } else if (hint.contains("Portrait")) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - } else if (hint.contains("PortraitUpsideDown")) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - } - - boolean is_landscape_allowed = (orientation_landscape != -1); - boolean is_portrait_allowed = (orientation_portrait != -1); - int req; /* Requested orientation */ - - /* No valid hint, nothing is explicitly allowed */ - if (!is_portrait_allowed && !is_landscape_allowed) { - if (resizable) { - /* All orientations are allowed */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; - } else { - /* Fixed window and nothing specified. Get orientation from w/h of created window */ - req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); - } - } else { - /* At least one orientation is allowed */ - if (resizable) { - if (is_portrait_allowed && is_landscape_allowed) { - /* hint allows both landscape and portrait, promote to full sensor */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; - } else { - /* Use the only one allowed "orientation" */ - req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); - } - } else { - /* Fixed window and both orientations are allowed. Choose one. */ - if (is_portrait_allowed && is_landscape_allowed) { - req = (w > h ? orientation_landscape : orientation_portrait); - } else { - /* Use the only one allowed "orientation" */ - req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); - } - } - } - - Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); - mSingleton.setRequestedOrientation(req); - } - - /** - * This method is called by SDL using JNI. - */ - public static void minimizeWindow() { - - if (mSingleton == null) { - return; - } - - Intent startMain = new Intent(Intent.ACTION_MAIN); - startMain.addCategory(Intent.CATEGORY_HOME); - startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mSingleton.startActivity(startMain); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean shouldMinimizeOnFocusLoss() { -/* - if (Build.VERSION.SDK_INT >= 24) { - if (mSingleton == null) { - return true; - } - - if (mSingleton.isInMultiWindowMode()) { - return false; - } - - if (mSingleton.isInPictureInPictureMode()) { - return false; - } - } - - return true; -*/ - return false; - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isScreenKeyboardShown() - { - if (mTextEdit == null) { - return false; - } - - if (!mScreenKeyboardShown) { - return false; - } - - InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - return imm.isAcceptingText(); - - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean supportsRelativeMouse() - { - // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under - // Android 7 APIs, and simply returns no data under Android 8 APIs. - // - // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and - // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, - // we should stick to relative mode. - // - if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { - return false; - } - - return SDLActivity.getMotionListener().supportsRelativeMouse(); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean setRelativeMouseEnabled(boolean enabled) - { - if (enabled && !supportsRelativeMouse()) { - return false; - } - - return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean sendMessage(int command, int param) { - if (mSingleton == null) { - return false; - } - return mSingleton.sendCommand(command, param); - } - - /** - * This method is called by SDL using JNI. - */ - public static Context getContext() { - return SDL.getContext(); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isAndroidTV() { - UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); - if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { - return true; - } - if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { - return true; - } - if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { - return true; - } - return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); - } - - public static double getDiagonal() - { - DisplayMetrics metrics = new DisplayMetrics(); - Activity activity = (Activity)getContext(); - if (activity == null) { - return 0.0; - } - activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); - - double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; - double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; - - return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isTablet() { - // If our diagonal size is seven inches or greater, we consider ourselves a tablet. - return (getDiagonal() >= 7.0); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isChromebook() { - if (getContext() == null) { - return false; - } - return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isDeXMode() { - if (Build.VERSION.SDK_INT < 24) { - return false; - } - try { - final Configuration config = getContext().getResources().getConfiguration(); - final Class configClass = config.getClass(); - return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) - == configClass.getField("semDesktopModeEnabled").getInt(config); - } catch(Exception ignored) { - return false; - } - } - - /** - * This method is called by SDL using JNI. - */ - public static DisplayMetrics getDisplayDPI() { - return getContext().getResources().getDisplayMetrics(); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean getManifestEnvironmentVariables() { - try { - if (getContext() == null) { - return false; - } - - ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); - Bundle bundle = applicationInfo.metaData; - if (bundle == null) { - return false; - } - String prefix = "SDL_ENV."; - final int trimLength = prefix.length(); - for (String key : bundle.keySet()) { - if (key.startsWith(prefix)) { - String name = key.substring(trimLength); - String value = bundle.get(key).toString(); - nativeSetenv(name, value); - } - } - /* environment variables set! */ - return true; - } catch (Exception e) { - Log.v(TAG, "exception " + e.toString()); - } - return false; - } - - // This method is called by SDLControllerManager's API 26 Generic Motion Handler. - public static View getContentView() { - return mLayout; - } - - static class ShowTextInputTask implements Runnable { - /* - * This is used to regulate the pan&scan method to have some offset from - * the bottom edge of the input region and the top edge of an input - * method (soft keyboard) - */ - static final int HEIGHT_PADDING = 15; - - public int x, y, w, h; - - public ShowTextInputTask(int x, int y, int w, int h) { - this.x = x; - this.y = y; - this.w = w; - this.h = h; - - /* Minimum size of 1 pixel, so it takes focus. */ - if (this.w <= 0) { - this.w = 1; - } - if (this.h + HEIGHT_PADDING <= 0) { - this.h = 1 - HEIGHT_PADDING; - } - } - - @Override - public void run() { - RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); - params.leftMargin = x; - params.topMargin = y; - - if (mTextEdit == null) { - mTextEdit = new DummyEdit(SDL.getContext()); - - mLayout.addView(mTextEdit, params); - } else { - mTextEdit.setLayoutParams(params); - } - - mTextEdit.setVisibility(View.VISIBLE); - mTextEdit.requestFocus(); - - InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mTextEdit, 0); - - mScreenKeyboardShown = true; - } - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean showTextInput(int x, int y, int w, int h) { - // Transfer the task to the main thread as a Runnable - return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); - } - - public static boolean isTextInputEvent(KeyEvent event) { - - // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT - if (event.isCtrlPressed()) { - return false; - } - - return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; - } - - public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { - int deviceId = event.getDeviceId(); - int source = event.getSource(); - - if (source == InputDevice.SOURCE_UNKNOWN) { - InputDevice device = InputDevice.getDevice(deviceId); - if (device != null) { - source = device.getSources(); - } - } - -// if (event.getAction() == KeyEvent.ACTION_DOWN) { -// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } else if (event.getAction() == KeyEvent.ACTION_UP) { -// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } - - // Dispatch the different events depending on where they come from - // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD - // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD - // - // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and - // SOURCE_JOYSTICK, while its key events arrive from the keyboard source - // So, retrieve the device itself and check all of its sources - if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { - // Note that we process events with specific key codes here - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { - return true; - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { - return true; - } - } - } - - if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (isTextInputEvent(event)) { - if (ic != null) { - ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); - } else { - SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); - } - } - onNativeKeyDown(keyCode); - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - onNativeKeyUp(keyCode); - return true; - } - } - - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { - // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses - // they are ignored here because sending them as mouse input to SDL is messy - if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { - switch (event.getAction()) { - case KeyEvent.ACTION_DOWN: - case KeyEvent.ACTION_UP: - // mark the event as handled or it will be handled by system - // handling KEYCODE_BACK by system will call onBackPressed() - return true; - } - } - } - - return false; - } - - /** - * This method is called by SDL using JNI. - */ - public static Surface getNativeSurface() { - if (SDLActivity.mSurface == null) { - return null; - } - return SDLActivity.mSurface.getNativeSurface(); - } - - // Input - - /** - * This method is called by SDL using JNI. - */ - public static void initTouch() { - int[] ids = InputDevice.getDeviceIds(); - - for (int id : ids) { - InputDevice device = InputDevice.getDevice(id); - /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ - if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN - || device.isVirtual())) { - - int touchDevId = device.getId(); - /* - * Prevent id to be -1, since it's used in SDL internal for synthetic events - * Appears when using Android emulator, eg: - * adb shell input mouse tap 100 100 - * adb shell input touchscreen tap 100 100 - */ - if (touchDevId < 0) { - touchDevId -= 1; - } - nativeAddTouch(touchDevId, device.getName()); - } - } - } - - // Messagebox - - /** Result of current messagebox. Also used for blocking the calling thread. */ - protected final int[] messageboxSelection = new int[1]; - - /** - * This method is called by SDL using JNI. - * Shows the messagebox from UI thread and block calling thread. - * buttonFlags, buttonIds and buttonTexts must have same length. - * @param buttonFlags array containing flags for every button. - * @param buttonIds array containing id for every button. - * @param buttonTexts array containing text for every button. - * @param colors null for default or array of length 5 containing colors. - * @return button id or -1. - */ - public int messageboxShowMessageBox( - final int flags, - final String title, - final String message, - final int[] buttonFlags, - final int[] buttonIds, - final String[] buttonTexts, - final int[] colors) { - - messageboxSelection[0] = -1; - - // sanity checks - - if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { - return -1; // implementation broken - } - - // collect arguments for Dialog - - final Bundle args = new Bundle(); - args.putInt("flags", flags); - args.putString("title", title); - args.putString("message", message); - args.putIntArray("buttonFlags", buttonFlags); - args.putIntArray("buttonIds", buttonIds); - args.putStringArray("buttonTexts", buttonTexts); - args.putIntArray("colors", colors); - - // trigger Dialog creation on UI thread - - runOnUiThread(new Runnable() { - @Override - public void run() { - messageboxCreateAndShow(args); - } - }); - - // block the calling thread - - synchronized (messageboxSelection) { - try { - messageboxSelection.wait(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - return -1; - } - } - - // return selected value - - return messageboxSelection[0]; - } - - protected void messageboxCreateAndShow(Bundle args) { - - // TODO set values from "flags" to messagebox dialog - - // get colors - - int[] colors = args.getIntArray("colors"); - int backgroundColor; - int textColor; - int buttonBorderColor; - int buttonBackgroundColor; - int buttonSelectedColor; - if (colors != null) { - int i = -1; - backgroundColor = colors[++i]; - textColor = colors[++i]; - buttonBorderColor = colors[++i]; - buttonBackgroundColor = colors[++i]; - buttonSelectedColor = colors[++i]; - } else { - backgroundColor = Color.TRANSPARENT; - textColor = Color.TRANSPARENT; - buttonBorderColor = Color.TRANSPARENT; - buttonBackgroundColor = Color.TRANSPARENT; - buttonSelectedColor = Color.TRANSPARENT; - } - - // create dialog with title and a listener to wake up calling thread - - final AlertDialog dialog = new AlertDialog.Builder(this).create(); - dialog.setTitle(args.getString("title")); - dialog.setCancelable(false); - dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface unused) { - synchronized (messageboxSelection) { - messageboxSelection.notify(); - } - } - }); - - // create text - - TextView message = new TextView(this); - message.setGravity(Gravity.CENTER); - message.setText(args.getString("message")); - if (textColor != Color.TRANSPARENT) { - message.setTextColor(textColor); - } - - // create buttons - - int[] buttonFlags = args.getIntArray("buttonFlags"); - int[] buttonIds = args.getIntArray("buttonIds"); - String[] buttonTexts = args.getStringArray("buttonTexts"); - - final SparseArray