Compare commits

..

No commits in common. "master" and "2125.0" have entirely different histories.

393 changed files with 12287 additions and 29392 deletions

View file

@ -12,9 +12,6 @@ fi
echo "Tag name is: $TAG_NAME" echo "Tag name is: $TAG_NAME"
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME . docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
mkdir -p build mkdir -p build
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage" docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
docker save azahar-room:$TAG_NAME > build/$FILENAME
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV

View file

@ -6,7 +6,8 @@ cmake .. -GNinja \
-DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON
ninja ninja
ccache -s -v ccache -s -v

View file

@ -25,8 +25,9 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \ -DENABLE_ROOM_STANDALONE=OFF \
-DENABLE_DISCORD_RPC=ON \ -DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}" "${EXTRA_CMAKE_FLAGS[@]}"
ninja ninja
strip -s bin/Release/* strip -s bin/Release/*

View file

@ -2,14 +2,13 @@
ARTIFACTS_LIST=($ARTIFACTS) ARTIFACTS_LIST=($ARTIFACTS)
BUILD_DIR=build BUNDLE_DIR=build/bundle
UNIVERSAL_DIR=$BUILD_DIR/universal mkdir build
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
# Set up the base bundle to combine into. # Set up the base artifact to combine into.
mkdir $UNIVERSAL_DIR BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
mv $BASE_ARTIFACT $BUNDLE_DIR
# Executable binary paths that need to be combined. # Executable binary paths that need to be combined.
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar) BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
@ -20,18 +19,21 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
unset IFS unset IFS
# Combine all of the executable binaries and dylibs. # Combine all of the executable binaries and dylibs.
for BIN_PATH in "${BIN_PATHS[@]}"; do for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
done
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do for BIN_PATH in "${BIN_PATHS[@]}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
done
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
# Only merge if the libraries do not have conflicting arches, otherwise it will fail. # Only merge if the libraries do not have conflicting arches, otherwise it will fail.
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH` DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH` if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
fi fi
done
done done
# Remove leftover libs so that they aren't distributed # Remove leftover libs so that they aren't distributed

View file

@ -4,19 +4,23 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON) export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi fi
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH mkdir build && cd build
cmake ../.. -GNinja \ cmake .. -GNinja \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \ -DCMAKE_OSX_ARCHITECTURES="$TARGET" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \ -DENABLE_ROOM_STANDALONE=OFF \
-DENABLE_DISCORD_RPC=ON \ -DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}" "${EXTRA_CMAKE_FLAGS[@]}"
ninja ninja
ninja bundle ninja bundle
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake? mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
ccache -s -v
CURRENT_ARCH=`arch` CURRENT_ARCH=`arch`
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then if [ "$TARGET" = "$CURRENT_ARCH" ]; then
ctest -VV -C Release ctest -VV -C Release
fi fi

View file

@ -1,25 +0,0 @@
#!/bin/bash -ex
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
# It works fine when using the image locally.
export PATH="/mxe/usr/bin:${PATH}"
mkdir build && cd build
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
x86_64-w64-mingw32.shared-cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_DISCORD_RPC=ON \
-DUSE_SYSTEM_BOOST=ON \
-DUSE_SYSTEM_CRYPTOPP=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
make bundle
ccache -s -v

View file

@ -3,21 +3,20 @@
# Determine the full revision name. # Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`" GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
# Archive and upload the artifacts. # Archive and upload the artifacts.
mkdir -p artifacts mkdir -p artifacts
function pack_artifacts() { function pack_artifacts() {
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
ARTIFACTS_PATH="$1" ARTIFACTS_PATH="$1"
# Set up root directory for archive. # Set up root directory for archive.
@ -36,10 +35,10 @@ function pack_artifacts() {
fi fi
# Create .zip/.tar.gz # Create .zip/.tar.gz
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then if [ "$OS" = "windows" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip" ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME" powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip" ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME" zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
else else
@ -57,23 +56,11 @@ if [ -n "$UNPACKED" ]; then
FILENAME=$(basename "$ARTIFACT") FILENAME=$(basename "$ARTIFACT")
EXTENSION="${FILENAME##*.}" EXTENSION="${FILENAME##*.}"
# TODO: Deduplicate
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION" mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
done done
elif [ -n "$PACK_INDIVIDUALLY" ]; then elif [ -n "$PACK_INDIVIDUALLY" ]; then
# Pack and upload the artifacts one-by-one. # Pack and upload the artifacts one-by-one.
for ARTIFACT in build/bundle/*; do for ARTIFACT in build/bundle/*; do
TARGET=$(basename "$ARTIFACT")
pack_artifacts "$ARTIFACT" pack_artifacts "$ARTIFACT"
done done
else else

View file

@ -6,7 +6,7 @@ gcc -v
tx --version tx --version
mkdir build && cd build mkdir build && cd build
cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
make translation make translation
cd .. cd ..

View file

@ -10,7 +10,8 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_DISCORD_RPC=ON \ -DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}" "${EXTRA_CMAKE_FLAGS[@]}"
ninja ninja
ninja bundle ninja bundle

View file

@ -1,5 +1,4 @@
- [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms. - [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms.
---------
--- ---
<!-- <!--

View file

@ -7,41 +7,21 @@ on:
pull_request: pull_request:
branches: [ master ] branches: [ master ]
permissions:
id-token: write
contents: read
attestations: write
jobs: jobs:
source: source:
if: ${{ !github.head_ref }} if: ${{ !github.head_ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Pack - name: Pack
run: ./.ci/source.sh run: ./.ci/source.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: ./
format: spdx-json
output-file: artifacts/source.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: source name: source
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.tar.xz
sbom-path: artifacts/source.spdx.json
linux-x86_64: linux-x86_64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -59,15 +39,14 @@ jobs:
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }} SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }} key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
@ -85,27 +64,12 @@ jobs:
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }} if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
run: | run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Generate SBOM
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }} if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ github.job }}-${{ matrix.target }} name: ${{ github.job }}-${{ matrix.target }}
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.AppImage
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
linux-arm64: linux-arm64:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
@ -123,11 +87,11 @@ jobs:
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }} key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
@ -137,80 +101,85 @@ jobs:
run: ./.ci/linux.sh run: ./.ci/linux.sh
macos: macos:
runs-on: 'macos-26' runs-on: ${{ (matrix.target == 'x86_64' && 'macos-26-intel') || 'macos-26' }}
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env: env:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos OS: macos
TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }} uses: actions/cache@v4
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Install tools - name: Install tools
run: brew install ccache ninja spirv-tools run: brew install ccache ninja spirv-tools
- name: Build (x86_64) - name: Build
run: BUILD_ARCH=x86_64 ./.ci/macos.sh run: ./.ci/macos.sh
- name: Build (arm64) - name: Prepare outputs for caching
run: BUILD_ARCH=arm64 ./.ci/macos.sh run: cp -R build/bundle $OS-$TARGET
- name: Cache outputs for universal build
uses: actions/cache/save@v4
with:
path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos-universal:
runs-on: macos-26
needs: macos
env:
OS: macos
TARGET: universal
steps:
- uses: actions/checkout@v4
- name: Download x86_64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Download ARM64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Create universal app - name: Create universal app
run: ./.ci/macos-universal.sh run: ./.ci/macos-universal.sh
- name: Prepare for packing
run: |
mkdir build/bundle
cp -r build/x86_64/bundle build/bundle/x86_64
cp -r build/arm64/bundle build/bundle/arm64
cp -r build/universal/bundle build/bundle/universal
- name: Pack
env: env:
PACK_INDIVIDUALLY: 1 ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
- name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/macos.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
sbom-path: artifacts/macos.spdx.json
windows: windows:
runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: target: ["msvc", "msys2"]
- target: msvc
os: windows-latest
- target: msys2
os: windows-latest
- target: mxe
os: ubuntu-latest
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
defaults: defaults:
run: run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0} shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -218,16 +187,14 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: windows OS: windows
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }} uses: actions/cache@v4
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -235,7 +202,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC - name: Set up MSVC
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
uses: azahar-emu/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
- name: Install extra tools (MSVC) - name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget run: choco install ccache ninja ptime wget
@ -256,62 +223,34 @@ jobs:
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2) - name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }} if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v4 uses: crazy-max/ghaction-chocolatey@v3
with: with:
args: install ptime wget args: install ptime wget
- name: Install NSIS - name: Install NSIS
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }} if: ${{ github.ref_type == 'tag' }}
run: | run: |
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
ptime D:/a/_temp/nsis-setup.exe /S ptime D:/a/_temp/nsis-setup.exe /S
shell: pwsh shell: pwsh
- name: Disable line ending translation - name: Disable line ending translation
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Build (Native) - name: Build
if: ${{ matrix.target != 'mxe' }}
run: ./.ci/windows.sh run: ./.ci/windows.sh
- name: Build (MXE) - name: Generate installer
if: ${{ matrix.target == 'mxe' }} if: ${{ github.ref_type == 'tag' }}
run: ./.ci/mxe.sh
- name: Generate installer (Native)
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: | run: |
cd src\installer cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi "C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir ..\..\artifacts 2> NUL mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\ move /y *.exe ..\..\artifacts\
shell: cmd shell: cmd
- name: Generate installer (MXE)
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
run: |
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
cd src/installer
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir -p ../../artifacts
mv ./*.exe ../../artifacts/
- name: Pack - name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
artifacts/*.exe
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
android: android:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -323,18 +262,17 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: android OS: android
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }} SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -372,48 +310,23 @@ jobs:
working-directory: src/android/app working-directory: src/android/app
env: env:
UNPACKED: 1 UNPACKED: 1
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: src/android
format: spdx-json
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/ path: src/android/app/artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
src/android/app/artifacts/*.apk
src/android/app/artifacts/*.aab
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
docker: docker:
strategy: runs-on: ubuntu-latest
fail-fast: false
matrix:
include:
- target: x86_64
os: ubuntu-24.04
- target: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
container: container:
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739 image: docker:dind
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
env:
TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install tools
run: apk add bash
- name: Fix git ownership - name: Fix git ownership
run: git config --global --add safe.directory . run: git config --global --add safe.directory .
- name: Build Docker image - name: Build Docker image
@ -422,23 +335,8 @@ jobs:
run: | run: |
mkdir -p artifacts mkdir -p artifacts
mv build/*.dockerimage artifacts/ mv build/*.dockerimage artifacts/
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
image: ${{ env.DOCKER_IMAGE_PATH }}
format: spdx-json
output-file: artifacts/docker-room.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: docker-${{ env.TARGET }} name: docker
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.dockerimage
sbom-path: artifacts/docker-room.spdx.json

View file

@ -20,7 +20,7 @@ jobs:
(github.event.pull_request.author_association != 'OWNER') (github.event.pull_request.author_association != 'OWNER')
steps: steps:
- name: Detect PR if author is first-time contributor - name: Detect PR if author is first-time contributor
uses: actions/github-script@v9 uses: actions/github-script@v7
with: with:
script: | script: |
const { owner, repo } = context.repo; const { owner, repo } = context.repo;

View file

@ -14,7 +14,7 @@ jobs:
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification') if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
steps: steps:
- name: Verify and reopen PR - name: Verify and reopen PR
uses: actions/github-script@v9 uses: actions/github-script@v7
with: with:
script: | script: |
const { owner, repo } = context.repo; const { owner, repo } = context.repo;

View file

@ -13,22 +13,10 @@ jobs:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Build - name: Build
env: env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
run: ./.ci/clang-format.sh run: ./.ci/clang-format.sh
ktlint:
runs-on: ubuntu-latest
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build
run: ./tools/check-kotlin-formatting.sh

View file

@ -11,13 +11,8 @@ on:
env: env:
CORE_ARGS: -DENABLE_LIBRETRO=ON CORE_ARGS: -DENABLE_LIBRETRO=ON
permissions:
id-token: write
contents: read
attestations: write
jobs: jobs:
libretro-android: android:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
OS: android OS: android
@ -28,7 +23,7 @@ jobs:
BUILD_DIR: build/android-arm64-v8a BUILD_DIR: build/android-arm64-v8a
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set tag name - name: Set tag name
@ -37,10 +32,6 @@ jobs:
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
fi fi
echo $GIT_TAG_NAME echo $GIT_TAG_NAME
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Update Android SDK CMake version - name: Update Android SDK CMake version
run: | run: |
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION" echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
@ -50,33 +41,14 @@ jobs:
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR ${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) ${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-android.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip linux:
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-android.spdx.json
libretro-linux:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
OS: linux OS: linux
@ -85,44 +57,21 @@ jobs:
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Build - name: Build
run: | run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-linux.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip windows:
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-linux.spdx.json
libretro-windows:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
OS: windows OS: windows
@ -130,10 +79,10 @@ jobs:
BUILD_DIR: build/windows-x86_64 BUILD_DIR: build/windows-x86_64
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
CMAKE: x86_64-w64-mingw32.static-cmake CMAKE: x86_64-w64-mingw32.static-cmake
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12 IMAGE: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Build in cross-container - name: Build in cross-container
@ -145,36 +94,17 @@ jobs:
$IMAGE \ $IMAGE \
bash -lc "\ bash -lc "\
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \ ${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \ ${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)"
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-windows.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip macos:
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-windows.spdx.json
libretro-macos:
runs-on: macos-26 runs-on: macos-26
strategy: strategy:
fail-fast: false
matrix: matrix:
target: ["x86_64", "arm64"] target: ["x86_64", "arm64"]
env: env:
@ -184,7 +114,7 @@ jobs:
BUILD_DIR: build/osx-${{ matrix.target }} BUILD_DIR: build/osx-${{ matrix.target }}
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install tools - name: Install tools
@ -193,33 +123,14 @@ jobs:
run: | run: |
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-macos-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip ios:
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
libretro-ios:
runs-on: macos-26 runs-on: macos-26
env: env:
OS: ios OS: ios
@ -228,40 +139,21 @@ jobs:
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Build - name: Build
run: | run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-ios.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip tvos:
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-ios.spdx.json
libretro-tvos:
runs-on: macos-26 runs-on: macos-26
env: env:
OS: tvos OS: tvos
@ -270,35 +162,17 @@ jobs:
EXTRA_PATH: bin/Release EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Build - name: Build
run: | run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack - name: Pack
run: ./.ci/libretro-pack.sh run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-tvos.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: | path: ./*.zip
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-tvos.spdx.json

View file

@ -11,7 +11,7 @@ jobs:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Fetch master branch - name: Fetch master branch

View file

@ -10,7 +10,7 @@ jobs:
permissions: permissions:
issues: write issues: write
steps: steps:
- uses: actions/stale@v10.2.0 - uses: actions/stale@v9.1.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 90 days-before-issue-stale: 90

View file

@ -7,10 +7,10 @@ on:
jobs: jobs:
transifex: transifex:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: opensauce04/azahar-build-environment:latest container: opensauce04/azahar-build-environment:transifex
if: ${{ github.repository == 'azahar-emu/azahar' }} if: ${{ github.repository == 'azahar-emu/azahar' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0

6
.gitignore vendored
View file

@ -17,8 +17,7 @@ src/common/scm_rev.cpp
*.swp *.swp
*.kdev4 *.kdev4
.markdown-preview.html .markdown-preview.html
.idea/* .idea/
!.idea/inspectionProfiles
.vs/ .vs/
.vscode/ .vscode/
.cache/ .cache/
@ -61,6 +60,3 @@ VULKAN_SDK/
# Version info files # Version info files
GIT-COMMIT GIT-COMMIT
GIT-TAG GIT-TAG
# verify-release.sh downloads
verify/

14
.gitmodules vendored
View file

@ -55,12 +55,18 @@
[submodule "sdl2"] [submodule "sdl2"]
path = externals/sdl2/SDL path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL url = https://github.com/libsdl-org/SDL
[submodule "cryptopp-cmake"]
path = externals/cryptopp-cmake
url = https://github.com/abdes/cryptopp-cmake.git
[submodule "cryptopp"]
path = externals/cryptopp
url = https://github.com/weidai11/cryptopp.git
[submodule "dds-ktx"] [submodule "dds-ktx"]
path = externals/dds-ktx path = externals/dds-ktx
url = https://github.com/septag/dds-ktx url = https://github.com/septag/dds-ktx
[submodule "openal-soft"] [submodule "openal-soft"]
path = externals/openal-soft path = externals/openal-soft
url = https://github.com/azahar-emu/openal-soft url = https://github.com/kcat/openal-soft
[submodule "glslang"] [submodule "glslang"]
path = externals/glslang path = externals/glslang
url = https://github.com/KhronosGroup/glslang url = https://github.com/KhronosGroup/glslang
@ -100,9 +106,3 @@
[submodule "externals/libretro-common"] [submodule "externals/libretro-common"]
path = externals/libretro-common/libretro-common path = externals/libretro-common/libretro-common
url = https://github.com/libretro/libretro-common.git url = https://github.com/libretro/libretro-common.git
[submodule "dllwalker"]
path = externals/dllwalker
url = https://github.com/azahar-emu/dllwalker
[submodule "externals/cryptopp"]
path = externals/cryptopp
url = https://github.com/cryptopp-modern/cryptopp-modern.git

View file

@ -13,9 +13,6 @@ cmake_policy(SET CMP0063 NEW)
cmake_policy(SET CMP0127 NEW) cmake_policy(SET CMP0127 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
# Prefer building bundled dependencies as static instead of shared
set(BUILD_SHARED_LIBS OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals) include(DownloadExternals)
@ -28,15 +25,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
endif() endif()
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
endif()
if (BSD STREQUAL "OpenBSD")
add_link_options(-z wxneeded)
endif()
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF) option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
# Some submodules like to pick their own default build type if not specified. # Some submodules like to pick their own default build type if not specified.
@ -107,7 +95,7 @@ endif()
# Track which options were explicitly set by the user (for libretro conflict detection) # Track which options were explicitly set by the user (for libretro conflict detection)
set(_LIBRETRO_INCOMPATIBLE_OPTIONS set(_LIBRETRO_INCOMPATIBLE_OPTIONS
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB) ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
set(_USER_SET_OPTIONS "") set(_USER_SET_OPTIONS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS) foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
@ -121,7 +109,7 @@ option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OF
# Set bundled qt as dependent options. # Set bundled qt as dependent options.
option(ENABLE_QT "Enable the Qt frontend" ON) option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON) option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF) option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
@ -130,7 +118,6 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON) option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON) option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
@ -139,10 +126,9 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
# NetBSD doesn't support Vulkan yet, remove this check when it does. option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF) option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF) option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
@ -150,8 +136,6 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF) option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON)
# Compile options # Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -411,21 +395,13 @@ if (APPLE)
endif() endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED) find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED) find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY}) set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO) if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
if (USE_SYSTEM_MOLTENVK) if (NOT USE_SYSTEM_MOLTENVK)
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
else()
download_moltenvk() download_moltenvk()
if (IOS)
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
else()
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
endif() endif()
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}") find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
endif()
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY}) set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif() endif()

View file

@ -198,10 +198,6 @@ if (BUNDLE_TARGET_EXECUTE)
# On Linux, always bundle an AppImage. # On Linux, always bundle an AppImage.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IS_MINGW)
return()
endif()
if (IN_PLACE) if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.") message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif() endif()
@ -277,13 +273,6 @@ else()
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs. # On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (MINGW)
add_custom_command(
TARGET bundle
# The target here is arbitrary
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
POST_BUILD)
else()
add_custom_command( add_custom_command(
TARGET bundle TARGET bundle
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
@ -294,7 +283,6 @@ else()
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
POST_BUILD) POST_BUILD)
endif() endif()
endif()
endfunction() endfunction()
# Adds a target to the bundle target, packing in required libraries. # Adds a target to the bundle target, packing in required libraries.
@ -305,11 +293,6 @@ else()
create_base_bundle_target() create_base_bundle_target()
endif() endif()
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
# We don't really need to "bundle" MXE builds, so don't do anything
return()
endif()
set(bundle_executable_path "$<TARGET_FILE:${target_name}>") set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (bundle_qt AND APPLE) if (bundle_qt AND APPLE)
# For Qt targets on Apple, expect an app bundle. # For Qt targets on Apple, expect an app bundle.
@ -348,7 +331,6 @@ else()
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\"" "-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}" "-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}" "-DIN_PLACE=${in_place}"
"-DIS_MINGW=${MINGW}"
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun" "-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

View file

@ -1,11 +0,0 @@
function(disable_pax_mprotect target)
if (BSD STREQUAL "NetBSD")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
VERBATIM
)
else()
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
endif()
endfunction()

View file

@ -171,16 +171,27 @@ function(download_qt target)
endfunction() endfunction()
function(download_moltenvk) function(download_moltenvk)
if (IOS)
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
else()
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
endif()
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK") if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS ${MOLTENVK_TAR}) if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
${MOLTENVK_TAR} SHOW_PROGRESS) ${MOLTENVK_TAR} SHOW_PROGRESS)
endif() endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif() endif()
# Add the MoltenVK library path to the prefix so find_library can locate it.
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction() endfunction()
function(get_external_prefix lib_name prefix_var) function(get_external_prefix lib_name prefix_var)

View file

@ -17,7 +17,6 @@ foreach(KEY IN ITEMS
"use_virtual_sd" "use_virtual_sd"
"use_custom_storage" "use_custom_storage"
"compress_cia_installs" "compress_cia_installs"
"async_fs_operations"
"region_value" "region_value"
"init_clock" "init_clock"
"init_time" "init_time"
@ -49,7 +48,6 @@ foreach(KEY IN ITEMS
"texture_filter" "texture_filter"
"texture_sampling" "texture_sampling"
"delay_game_render_thread_us" "delay_game_render_thread_us"
"simulate_3ds_gpu_timings"
"layout_option" "layout_option"
"swap_screen" "swap_screen"
"upright_screen" "upright_screen"
@ -109,7 +107,6 @@ foreach(KEY IN ITEMS
"output_device" "output_device"
"input_type" "input_type"
"input_device" "input_device"
"simulate_headphones_plugged"
"delay_start_for_lle_modules" "delay_start_for_lle_modules"
"use_gdbstub" "use_gdbstub"
"gdbstub_port" "gdbstub_port"
@ -118,7 +115,6 @@ foreach(KEY IN ITEMS
"log_filter" "log_filter"
"log_regex_filter" "log_regex_filter"
"toggle_unique_data_console_type" "toggle_unique_data_console_type"
"break_on_unmapped_memory_access"
"use_integer_scaling" "use_integer_scaling"
"layouts_to_cycle" "layouts_to_cycle"
"camera_inner_flip" "camera_inner_flip"
@ -238,7 +234,6 @@ if (ANDROID)
"android_hide_images" "android_hide_images"
"screen_orientation" "screen_orientation"
"performance_overlay_position" "performance_overlay_position"
"enable_secondary_display"
) )
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY}) string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",") set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")

1
CONTRIBUTING.md Normal file
View file

@ -0,0 +1 @@
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**

View file

@ -12,4 +12,4 @@ lang_map = ca@valencia:ca_ES_valencia
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID type = ANDROID
lang_map = ca@valencia:b+ca+ES+valencia, es_419:b+es+419, pt_BR:b+pt+BR, zh_CN:b+zh+CN, zh_TW:b+zh+TW lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1070
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

848
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

7918
dist/languages/es_419.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

828
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

846
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

828
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

856
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1270
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

832
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

846
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,19 +4,23 @@
# --- Builder ---------------- # --- Builder ----------------
FROM opensauce04/azahar-build-environment:latest AS builder FROM opensauce04/azahar-build-environment:latest AS builder
RUN mkdir /var/azahar-src/ COPY . /var/azahar-src
COPY ./ /var/azahar-src/
RUN mkdir ./builddir/ RUN mkdir builddir && cd builddir && \
WORKDIR ./builddir/ cmake /var/azahar-src -G Ninja \
RUN cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \ -DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \ -DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \ -DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON -DENABLE_ROOM_STANDALONE=ON \
RUN ninja -DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
RUN mv ./bin/Release/azahar-room /usr/local/bin/ -DENABLE_VULKAN=OFF \
-DENABLE_SDL2=OFF \
-DENABLE_LIBUSB=OFF \
-DENABLE_CUBEB=OFF \
-DENABLE_OPENAL=OFF && \
ninja && \
mv bin/Release/azahar-room /usr/local/bin/ && \
cd .. && rm -rf builddir
# --- Final ------------------ # --- Final ------------------
FROM debian:trixie AS final FROM debian:trixie AS final

View file

@ -57,10 +57,9 @@ if (ENABLE_TESTS)
else() else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "") set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "") set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2 EXCLUDE_FROM_ALL) add_subdirectory(catch2)
endif() endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain) target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
endif() endif()
# Crypto++ # Crypto++
@ -69,9 +68,17 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE) add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else() else()
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
endif()
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "") set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
add_subdirectory(cryptopp EXCLUDE_FROM_ALL) set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake)
endif() endif()
# dds-ktx # dds-ktx
@ -104,13 +111,7 @@ endif()
# Oaknut # Oaknut
if ("arm64" IN_LIST ARCHITECTURE) if ("arm64" IN_LIST ARCHITECTURE)
if(USE_SYSTEM_OAKNUT)
find_package(oaknut REQUIRED)
add_library(oaknut INTERFACE)
target_link_libraries(oaknut INTERFACE merry::oaknut)
else()
add_subdirectory(oaknut EXCLUDE_FROM_ALL) add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif()
endif() endif()
# Dynarmic # Dynarmic
@ -134,7 +135,7 @@ endif()
# getopt # getopt
if (MSVC) if (MSVC)
add_subdirectory(getopt EXCLUDE_FROM_ALL) add_subdirectory(getopt)
endif() endif()
# inih # inih
@ -143,7 +144,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE) add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir) target_link_libraries(inih INTERFACE inih::inih inih::inir)
else() else()
add_subdirectory(inih EXCLUDE_FROM_ALL) add_subdirectory(inih)
endif() endif()
# MicroProfile # MicroProfile
@ -166,7 +167,7 @@ if (NOT MSVC)
endif() endif()
# Open Source Archives # Open Source Archives
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL) add_subdirectory(open_source_archives)
# faad2 # faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL) add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -205,12 +206,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2 # SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2) if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2 EXCLUDE_FROM_ALL) add_subdirectory(sdl2)
endif() endif()
# libusb # libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB) if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
add_subdirectory(libusb EXCLUDE_FROM_ALL) add_subdirectory(libusb)
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE) set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE) set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif() endif()
@ -254,7 +255,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE) add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet) target_link_libraries(enet INTERFACE libenet::libenet)
else() else()
add_subdirectory(enet EXCLUDE_FROM_ALL) add_subdirectory(enet)
target_include_directories(enet INTERFACE ./enet/include) target_include_directories(enet INTERFACE ./enet/include)
endif() endif()
@ -276,7 +277,7 @@ if (ENABLE_CUBEB)
endif() endif()
# DiscordRPC # DiscordRPC
if (ENABLE_DISCORD_RPC) if (USE_DISCORD_PRESENCE)
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms. # rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
include(TestBigEndian) include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN) test_big_endian(RAPIDJSON_BIG_ENDIAN)
@ -319,8 +320,12 @@ endif()
# OpenSSL # OpenSSL
if (USE_SYSTEM_OPENSSL) if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1) find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
else() endif()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL # LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/") set(OPENSSLDIR "/etc/ssl/")
@ -362,7 +367,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
add_subdirectory(gamemode EXCLUDE_FROM_ALL) add_subdirectory(gamemode)
endif() endif()
# cpp-jwt # cpp-jwt
@ -385,13 +390,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED) find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng) target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else() else()
add_subdirectory(lodepng EXCLUDE_FROM_ALL) add_subdirectory(lodepng)
endif() endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG # (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID) if(ANDROID)
# libyuv # libyuv
add_subdirectory(libyuv EXCLUDE_FROM_ALL) add_subdirectory(libyuv)
target_include_directories(yuv INTERFACE ./libyuv/include) target_include_directories(yuv INTERFACE ./libyuv/include)
endif() endif()
@ -402,9 +407,6 @@ if (ENABLE_OPENAL)
find_package(OpenAL REQUIRED) find_package(OpenAL REQUIRED)
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL) target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
else() else()
if (BSD STREQUAL "OpenBSD")
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
endif()
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
set(ALSOFT_EXAMPLES OFF CACHE BOOL "") set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
set(ALSOFT_INSTALL OFF CACHE BOOL "") set(ALSOFT_INSTALL OFF CACHE BOOL "")
@ -420,7 +422,7 @@ endif()
# OpenGL dependencies # OpenGL dependencies
if (ENABLE_OPENGL) if (ENABLE_OPENGL)
# Glad # Glad
add_subdirectory(glad EXCLUDE_FROM_ALL) add_subdirectory(glad)
endif() endif()
# Vulkan dependencies # Vulkan dependencies
@ -460,7 +462,7 @@ if (ENABLE_VULKAN)
set(ENABLE_CTEST OFF CACHE BOOL "") set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "") set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "") set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang EXCLUDE_FROM_ALL) add_subdirectory(glslang)
endif() endif()
# sirit # sirit
@ -491,24 +493,11 @@ if (ENABLE_VULKAN)
else() else()
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include) target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
target_disable_warnings(vulkan-headers) target_disable_warnings(vulkan-headers)
if (BSD STREQUAL "NetBSD")
# There may be a better way to do this with
# find_package(X11), but I couldn't get
# CMake to do it, so we're depending on
# the x11-links package and assuming the
# prefix location. -OS
target_include_directories(vulkan-headers INTERFACE
/usr/pkg/share/x11-links/include)
elseif (BSD STREQUAL "OpenBSD")
# This is fine to hardcode because it'll never change
target_include_directories(vulkan-headers INTERFACE
/usr/X11R6/include)
endif()
endif() endif()
# adrenotools # adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE) if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL) add_subdirectory(libadrenotools)
endif() endif()
endif() endif()

2
externals/boost vendored

@ -1 +1 @@
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61 Subproject commit f9b15f673a688982f78a5f63a49a27275b318e5f

View file

@ -14,7 +14,6 @@ option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF) option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF) option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF) option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF) option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF) option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF) option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
@ -41,7 +40,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
@ -68,7 +66,6 @@ set(LIB_VAR_LIST
DYNARMIC DYNARMIC
FMT FMT
XBYAK XBYAK
OAKNUT
INIH INIH
FFMPEG_HEADERS FFMPEG_HEADERS
GLSLANG GLSLANG

2
externals/cryptopp vendored

@ -1 +1 @@
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e

1
externals/cryptopp-cmake vendored Submodule

@ -0,0 +1 @@
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c

@ -1 +1 @@
Subproject commit 049826c3f42875c2f9599552f425e76435789cc7 Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a

1
externals/dllwalker vendored

@ -1 +0,0 @@
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e

@ -1 +1 @@
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906 Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727

View file

@ -183,9 +183,6 @@ endif()
if(ENABLE_DEVELOPER_OPTIONS) if(ENABLE_DEVELOPER_OPTIONS)
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS) add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
endif() endif()
if(ENABLE_BUILTIN_KEYBLOB)
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
endif()
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(core) add_subdirectory(core)
@ -203,7 +200,6 @@ if (ENABLE_QT)
endif() endif()
if (ENABLE_QT) # Or any other hypothetical future frontends if (ENABLE_QT) # Or any other hypothetical future frontends
add_subdirectory(citra_cli)
add_subdirectory(citra_meta) add_subdirectory(citra_meta)
endif() endif()

View file

@ -1,20 +0,0 @@
root = true
[*.{kt,kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ktlint_code_style = android_studio
# Disable wildcard imports
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
# Style configuration
indent_size = 4
indent_style = space
ktlint_standard_comment-wrapping = disabled
ktlint_standard_no-unused-imports = enabled
# ^- Reportedly finicky, use for now but maybe delete later if there are issues.
# See https://github.com/ktlint/ktlint/issues/3038
ktlint_standard_package-name = disabled
max_line_length = 100

View file

@ -34,8 +34,7 @@ captures/
# IntelliJ # IntelliJ
*.iml *.iml
.idea/* .idea/
!.idea/inspectionProfiles
# Keystore files # Keystore files
# Uncomment the following line if you do not want to check your keystore files in. # Uncomment the following line if you do not want to check your keystore files in.

View file

@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View file

@ -12,7 +12,6 @@ plugins {
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "2.0.20" kotlin("plugin.serialization") version "2.0.20"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint")
} }
/** /**
@ -25,11 +24,12 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs" val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
@Suppress("UnstableApiUsage")
android { android {
namespace = "org.citra.citra_emu" namespace = "org.citra.citra_emu"
compileSdkVersion = "android-35" compileSdkVersion = "android-35"
ndkVersion = "27.3.13750724" ndkVersion = "27.1.12297006"
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
@ -80,9 +80,7 @@ android {
"-DENABLE_QT=0", // Don't use QT "-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL "-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
// sizes
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
) )
} }
} }
@ -127,8 +125,7 @@ android {
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
versionNameSuffix = "-debug" versionNameSuffix = "-debug"
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
isShrinkResources = true isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
// TODO: ^- Does this actually do anything when isDebuggable is enabled? -OS
isDebuggable = true isDebuggable = true
isJniDebuggable = true isJniDebuggable = true
proguardFiles( proguardFiles(
@ -139,10 +136,8 @@ android {
} }
// Same as above, but with isDebuggable disabled. // Same as above, but with isDebuggable disabled.
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS) // Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS) without constantly tripping over years-old and seemingly harmless memory bugs.
// without constantly tripping over years-old and seemingly harmless memory bugs. // We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
// We should fix those bugs eventually, but for now this exists as a workaround to
// allow other work to be done on these devices.
register("relWithDebInfoLite") { register("relWithDebInfoLite") {
initWith(getByName("relWithDebInfo")) initWith(getByName("relWithDebInfo"))
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
@ -152,7 +147,7 @@ android {
} }
lint { lint {
checkReleaseBuilds = false // Ditto checkReleaseBuilds = false // Ditto
// ^- The name of this property is misleading, this doesn't actually disable linting for the `release` build. // The name of this property is misleading, this doesn't actually disable linting for the `release` build.
} }
} }
@ -220,9 +215,7 @@ dependencies {
// Download Vulkan Validation Layers from the KhronosGroup GitHub. // Download Vulkan Validation Layers from the KhronosGroup GitHub.
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") { val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
src( src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip")
"https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip"
)
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip")) dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
onlyIfModified(true) onlyIfModified(true)
} }
@ -242,11 +235,6 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
tasks.named("preBuild") { tasks.named("preBuild") {
dependsOn(unzipVulkanValidationLayers) dependsOn(unzipVulkanValidationLayers)
dependsOn("ktlintCheck")
}
ktlint {
version = "1.8.0"
} }
fun getGitVersion(): String { fun getGitVersion(): String {
@ -278,7 +266,7 @@ fun getGitHash(): String =
fun getBranch(): String = fun getBranch(): String =
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch" runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
fun runGitCommand(command: ProcessBuilder): String? { fun runGitCommand(command: ProcessBuilder) : String? {
try { try {
command.directory(project.rootDir) command.directory(project.rootDir)
val process = command.start() val process = command.start()
@ -304,7 +292,7 @@ android.applicationVariants.configureEach {
val variant = this val variant = this
val capitalizedName = variant.name.capitalizeUS() val capitalizedName = variant.name.capitalizeUS()
val copyTask = tasks.register("copyBundle$capitalizedName") { val copyTask = tasks.register("copyBundle${capitalizedName}") {
doLast { doLast {
project.copy { project.copy {
from(variant.outputs.first().outputFile.parentFile) from(variant.outputs.first().outputFile.parentFile)
@ -318,5 +306,5 @@ android.applicationVariants.configureEach {
} }
} }
} }
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) } tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
} }

View file

@ -12,10 +12,10 @@ import android.content.Context
import android.os.Build import android.os.Build
import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GraphicsUtil import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil import org.citra.citra_emu.utils.MemoryUtil
import org.citra.citra_emu.utils.PermissionsHandler
class CitraApplication : Application() { class CitraApplication : Application() {
private fun createNotificationChannel() { private fun createNotificationChannel() {
@ -69,7 +69,6 @@ class CitraApplication : Application() {
Log.info("SoC Model - ${Build.SOC_MODEL}") Log.info("SoC Model - ${Build.SOC_MODEL}")
} }
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}") Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
Log.info("OpenGL ES Renderer - ${GraphicsUtil.openGLRendererString}")
} }
companion object { companion object {

View file

@ -19,22 +19,20 @@ import android.view.Surface
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import java.util.Date
import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GraphicsUtil
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RemovableStorageHelper import org.citra.citra_emu.utils.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference
import java.util.Date
/** /**
* Class which contains methods that interact * Class which contains methods that interact
@ -44,7 +42,7 @@ object NativeLibrary {
/** /**
* Default touchscreen device * Default touchscreen device
*/ */
const val TOUCHSCREEN_DEVICE = "Touchscreen" const val TouchScreenDevice = "Touchscreen"
@JvmField @JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null) var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@ -136,7 +134,10 @@ object NativeLibrary {
*/ */
external fun setUserDirectory(directory: String) external fun setUserDirectory(directory: String)
data class InstalledGame(val path: String, val mediaType: Game.MediaType) data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
fun getInstalledGamePaths(): Array<InstalledGame> { fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl() val games = getInstalledGamePathsImpl()
@ -251,14 +252,12 @@ object NativeLibrary {
external fun playTimeManagerGetCurrentTitleId(): Long external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean = fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
uninstallTitle(titleId, mediaType.value) return uninstallTitle(titleId, mediaType.value)
}
external fun nativeFileExists(path: String): Boolean external fun nativeFileExists(path: String): Boolean
external fun deleteOpenGLShaderCache(titleId: Long)
external fun deleteVulkanShaderCache(titleId: Long)
private var coreErrorAlertResult = false private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object() private val coreErrorAlertLock = Object()
@ -315,12 +314,6 @@ object NativeLibrary {
canContinue = false canContinue = false
} }
CoreError.ErrorCoreExceptionRaised -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = false
}
CoreError.ErrorUnknown -> { CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error) title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message) message = emulationActivity.getString(R.string.fatal_error_message)
@ -345,12 +338,11 @@ object NativeLibrary {
return coreErrorAlertResult return coreErrorAlertResult
} }
@Keep @get:Keep
@JvmStatic @get:JvmStatic
fun isPortraitMode(): Boolean = ( val isPortraitMode: Boolean
CitraApplication.appContext.resources.configuration.orientation == get() = CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
)
@Keep @Keep
@JvmStatic @JvmStatic
@ -444,7 +436,7 @@ object NativeLibrary {
return return
} }
if (resultCode == CoreError.ShutdownRequested.value) { if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
emulationActivity.finish() emulationActivity.finish()
return return
} }
@ -463,57 +455,23 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE)) var captionId = R.string.loader_error_invalid_format
val title: String val result = requireArguments().getInt(RESULT_CODE)
val message: String if (result == ErrorLoader_ErrorEncrypted) {
when (coreError) { captionId = R.string.loader_error_encrypted
CoreError.ErrorGetLoader,
CoreError.ErrorLoaderErrorInvalidFormat,
CoreError.ErrorSystemMode -> {
title = getString(R.string.loader_error_invalid_format)
message = getString(R.string.loader_error_invalid_format_description)
}
CoreError.ErrorLoaderErrorEncrypted -> {
title = getString(R.string.loader_error_encrypted)
message = getString(R.string.loader_error_encrypted_description)
}
CoreError.ErrorArticDisconnected -> {
title = getString(R.string.artic_base)
message = getString(R.string.artic_server_comm_error)
}
CoreError.ErrorN3DSApplication -> {
title = getString(R.string.loader_error_invalid_system_mode)
message = getString(R.string.loader_error_invalid_system_mode_description)
}
CoreError.ErrorLoaderErrorPatches -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_applying_patches_description)
}
CoreError.ErrorLoaderErrorPatchesInvalidTitle -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_patch_wrong_application)
}
else -> {
title = getString(R.string.loader_error_generic_title)
message = getString(
R.string.loader_error_generic,
getString(coreError.stringRes),
coreError.value
)
} }
if (result == ErrorArticDisconnected) {
captionId = R.string.artic_base
} }
val alert = MaterialAlertDialogBuilder(requireContext()) val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(title) .setTitle(captionId)
.setMessage( .setMessage(
Html.fromHtml( Html.fromHtml(
message, if (result == ErrorArticDisconnected)
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
else
CitraApplication.appContext.resources.getString(R.string.redump_games),
Html.FROM_HTML_MODE_LEGACY Html.FROM_HTML_MODE_LEGACY
) )
) )
@ -535,6 +493,20 @@ object NativeLibrary {
const val RESULT_CODE = "resultcode" const val RESULT_CODE = "resultcode"
const val Success = 0
const val ErrorNotInitialized = 1
const val ErrorGetLoader = 2
const val ErrorSystemMode = 3
const val ErrorLoader = 4
const val ErrorLoader_ErrorEncrypted = 5
const val ErrorLoader_ErrorInvalidFormat = 6
const val ErrorLoader_ErrorGBATitle = 7
const val ErrorSystemFiles = 8
const val ErrorSavestate = 9
const val ErrorArticDisconnected = 10
const val ShutdownRequested = 11
const val ErrorUnknown = 12
fun newInstance(resultCode: Int): EmulationErrorDialogFragment { fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
val args = Bundle() val args = Bundle()
args.putInt(RESULT_CODE, resultCode) args.putInt(RESULT_CODE, resultCode)
@ -645,7 +617,7 @@ object NativeLibrary {
fun loadStateIfAvailable(slot: Int): Boolean { fun loadStateIfAvailable(slot: Int): Boolean {
var available = false var available = false
getSavestateInfo()?.forEach { getSavestateInfo()?.forEach {
if (it.slot == slot) { if (it.slot == slot){
available = true available = true
return@forEach return@forEach
} }
@ -683,17 +655,19 @@ object NativeLibrary {
// Compression / Decompression // Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus = fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
CompressStatus.fromValue( return CompressStatus.fromValue(
compressFileNative(inputPath, outputPath) compressFileNative(inputPath, outputPath)
) )
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus = fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
CompressStatus.fromValue( return CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath) decompressFileNative(inputPath, outputPath)
) )
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@ -727,7 +701,8 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) { fun openContentUri(path: String, openMode: String): Int =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.openContentUri(path, openMode) CitraApplication.documentsTree.openContentUri(path, openMode)
} else { } else {
FileUtil.openContentUri(path, openMode) FileUtil.openContentUri(path, openMode)
@ -735,7 +710,8 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) { fun getFilesName(path: String): Array<String?> =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFilesName(path) CitraApplication.documentsTree.getFilesName(path)
} else { } else {
FileUtil.getFilesName(path) FileUtil.getFilesName(path)
@ -765,14 +741,10 @@ object NativeLibrary {
return primaryStoragePath + dirSep + virtualPath return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device } else { // User directory probably located on a removable storage device
val storageIdString = pathSegment.substringBefore(":") val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath( val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
CitraApplication.appContext,
storageIdString
)
if (removablePath == null) { if (removablePath == null) {
android.util.Log.e( android.util.Log.e("NativeLibrary",
"NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)" "Unknown mount location for storage device '$storageIdString' (URI: $uri)"
) )
return "" return ""
@ -792,7 +764,8 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) { fun getSize(path: String): Long =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFileSize(path) CitraApplication.documentsTree.getFileSize(path)
} else { } else {
FileUtil.getFileSize(path) FileUtil.getFileSize(path)
@ -804,11 +777,8 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL() fun fileExists(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
@Keep
@JvmStatic
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path) CitraApplication.documentsTree.exists(path)
} else { } else {
FileUtil.exists(path) FileUtil.exists(path)
@ -816,7 +786,8 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) { fun isDirectory(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path) CitraApplication.documentsTree.isDirectory(path)
} else { } else {
FileUtil.isDirectory(path) FileUtil.isDirectory(path)
@ -828,7 +799,8 @@ object NativeLibrary {
sourcePath: String, sourcePath: String,
destinationParentPath: String, destinationParentPath: String,
destinationFilename: String destinationFilename: String
): Boolean = if (FileUtil.isNativePath(sourcePath) && ): Boolean =
if (FileUtil.isNativePath(sourcePath) &&
FileUtil.isNativePath(destinationParentPath) FileUtil.isNativePath(destinationParentPath)
) { ) {
CitraApplication.documentsTree CitraApplication.documentsTree
@ -874,35 +846,19 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) { fun deleteDocument(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path) CitraApplication.documentsTree.deleteDocument(path)
} else { } else {
FileUtil.deleteDocument(path) FileUtil.deleteDocument(path)
} }
enum class CoreError(val value: Int, @StringRes val stringRes: Int) { enum class CoreError {
Success(0, R.string.core_error_success), ErrorSystemFiles,
ErrorNotInitialized(1, R.string.core_error_not_initialized), ErrorSavestate,
ErrorGetLoader(2, R.string.core_error_get_loader), ErrorArticDisconnected,
ErrorSystemMode(3, R.string.core_error_system_mode), ErrorN3DSApplication,
ErrorLoader(4, R.string.core_error_loader), ErrorUnknown
ErrorLoaderErrorEncrypted(5, R.string.core_error_loader_encrypted),
ErrorLoaderErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
ErrorLoaderErrorGBATitle(7, R.string.core_error_loader_gba_title),
ErrorLoaderErrorPatches(8, R.string.core_error_loader_error_patches),
ErrorLoaderErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
ErrorSystemFiles(10, R.string.core_error_system_files),
ErrorSavestate(11, R.string.core_error_savestate),
ErrorArticDisconnected(12, R.string.core_error_artic_disconnected),
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
ShutdownRequested(16, R.string.core_error_shutdown_requested),
ErrorUnknown(17, R.string.core_error_unknown);
companion object {
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
}
} }
enum class InstallStatus { enum class InstallStatus {
@ -953,11 +909,7 @@ object NativeLibrary {
const val MESSAGE = "message" const val MESSAGE = "message"
const val CAN_CONTINUE = "canContinue" const val CAN_CONTINUE = "canContinue"
fun newInstance( fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
title: String,
message: String,
canContinue: Boolean
): CoreErrorDialogFragment {
val frag = CoreErrorDialogFragment() val frag = CoreErrorDialogFragment()
val args = Bundle() val args = Bundle()
args.putString(TITLE, title) args.putString(TITLE, title)

View file

@ -8,7 +8,6 @@ import android.Manifest.permission
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -31,7 +30,7 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.camera.StillImageCameraHelper.onFilePickerResult import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
import org.citra.citra_emu.contracts.OpenFileResultContract import org.citra.citra_emu.contracts.OpenFileResultContract
import org.citra.citra_emu.databinding.ActivityEmulationBinding import org.citra.citra_emu.databinding.ActivityEmulationBinding
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
@ -46,9 +45,9 @@ import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.utils.ThemeUtil
@ -64,7 +63,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility private lateinit var hotkeyUtility: HotkeyUtility
lateinit var secondaryDisplayManager: SecondaryDisplay private lateinit var secondaryDisplay: SecondaryDisplay
private val onShutdown = Runnable { private val onShutdown = Runnable {
if (intent.getBooleanExtra("launched_from_shortcut", false)) { if (intent.getBooleanExtra("launched_from_shortcut", false)) {
@ -81,9 +80,7 @@ class EmulationActivity : AppCompatActivity() {
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
} }
private var isRotationBlocked: Boolean = true
private var isEmulationRunning: Boolean = false private var isEmulationRunning: Boolean = false
private var isEmulationReady: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -92,20 +89,12 @@ class EmulationActivity : AppCompatActivity() {
ThemeUtil.setTheme(this) ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings() settingsViewModel.settings.loadSettings()
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
// Block orientation until emulation is ready to prevent unneccesary
// surface recreation until the renderer is ready.
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
secondaryDisplay = SecondaryDisplay(this)
secondaryDisplayManager = SecondaryDisplay(this) secondaryDisplay.updateDisplay()
secondaryDisplayManager.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root) setContentView(binding.root)
@ -130,6 +119,8 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true isEmulationRunning = true
instance = this instance = this
applyOrientationSettings() // Check for orientation settings at startup
val game = try { val game = try {
intent.extras?.let { extras -> intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java) BundleCompat.getParcelable(extras, "game", Game::class.java)
@ -145,76 +136,39 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStart(game.titleId) NativeLibrary.playTimeManagerStart(game.titleId)
} }
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
NativeLibrary.stopEmulation()
NativeLibrary.playTimeManagerStop()
isEmulationReady = false
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
emulationViewModel.setEmulationStarted(false)
val game = intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
}
if (game != null) {
NativeLibrary.playTimeManagerStart(game.titleId)
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
}
// On some devices, the system bars will not disappear on first boot or after some // On some devices, the system bars will not disappear on first boot or after some
// rotations. Here we set full screen immersive repeatedly in onResume and in // rotations. Here we set full screen immersive repeatedly in onResume and in
// onWindowFocusChanged to prevent the unwanted status bar state. // onWindowFocusChanged to prevent the unwanted status bar state.
override fun onResume() { override fun onResume() {
enableFullscreenImmersive()
if (isEmulationReady) {
// If emulation is ready then unblock rotation
isRotationBlocked = false
applyOrientationSettings()
emulationViewModel.setEmulationStarted(true)
} else {
if (!isRotationBlocked) {
applyOrientationSettings()
}
}
super.onResume() super.onResume()
enableFullscreenImmersive()
applyOrientationSettings() // Check for orientation settings changes on runtime
} }
override fun onStop() { override fun onStop() {
secondaryDisplayManager.releasePresentation() secondaryDisplay.releasePresentation()
super.onStop() super.onStop()
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
enableFullscreenImmersive()
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
enableFullscreenImmersive()
} }
public override fun onRestart() { public override fun onRestart() {
super.onRestart() super.onRestart()
secondaryDisplayManager.updateDisplay() secondaryDisplay.updateDisplay()
NativeLibrary.reloadCameraDevices() NativeLibrary.reloadCameraDevices()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean("isEmulationRunning", isEmulationRunning) outState.putBoolean("isEmulationRunning", isEmulationRunning)
outState.putBoolean("isEmulationReady", isEmulationReady)
outState.putBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false) isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onDestroy() { override fun onDestroy() {
@ -222,8 +176,8 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStop() NativeLibrary.playTimeManagerStop()
isEmulationRunning = false isEmulationRunning = false
instance = null instance = null
secondaryDisplayManager.releasePresentation() secondaryDisplay.releasePresentation()
secondaryDisplayManager.releaseVD() secondaryDisplay.releaseVD()
super.onDestroy() super.onDestroy()
} }
@ -268,11 +222,6 @@ class EmulationActivity : AppCompatActivity() {
fun onEmulationStarted() { fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true) emulationViewModel.setEmulationStarted(true)
isEmulationReady = true
if (isRotationBlocked) {
isRotationBlocked = false
applyOrientationSettings()
}
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
getString(R.string.emulation_menu_help), getString(R.string.emulation_menu_help),
@ -334,13 +283,11 @@ class EmulationActivity : AppCompatActivity() {
} }
return hotkeyUtility.handleKeyPress(event) return hotkeyUtility.handleKeyPress(event)
} }
KeyEvent.ACTION_UP -> { KeyEvent.ACTION_UP -> {
return hotkeyUtility.handleKeyRelease(event) return hotkeyUtility.handleKeyRelease(event)
} }
else -> { else -> {
return false return false;
} }
} }
} }
@ -360,8 +307,7 @@ class EmulationActivity : AppCompatActivity() {
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation // TODO: Move this check into native code - prevents crash if input pressed before starting emulation
if (!NativeLibrary.isRunning() || if (!NativeLibrary.isRunning() ||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) || (event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
emulationFragment.isDrawerOpen() emulationFragment.isDrawerOpen()) {
) {
return super.dispatchGenericMotionEvent(event) return super.dispatchGenericMotionEvent(event)
} }
@ -390,19 +336,16 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation = val guestOrientation =
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
val inverted = preferences.getBoolean( val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
InputBindingSetting.getInputAxisInvertedKey(axis),
false
)
if (nextMapping == -1 || guestOrientation == -1) { if (nextMapping == -1 || guestOrientation == -1) {
// Axis is unmapped // Axis is unmapped
continue continue
} }
if ((value > 0f && value < 0.1f) || (value < 0f && value > -0.1f)) { if (value > 0f && value < 0.1f || value < 0f && value > -0.1f) {
// Skip joystick wobble // Skip joystick wobble
value = 0f value = 0f
} }
if (inverted) value = -value if (inverted) value = -value;
when (nextMapping) { when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> { NativeLibrary.ButtonType.STICK_LEFT -> {
@ -456,7 +399,7 @@ class EmulationActivity : AppCompatActivity() {
// Triggers L/R and ZL/ZR // Triggers L/R and ZL/ZR
if (isTriggerPressedLMapped) { if (isTriggerPressedLMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.TRIGGER_L, NativeLibrary.ButtonType.TRIGGER_L,
if (isTriggerPressedL) { if (isTriggerPressedL) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -467,7 +410,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedRMapped) { if (isTriggerPressedRMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.TRIGGER_R, NativeLibrary.ButtonType.TRIGGER_R,
if (isTriggerPressedR) { if (isTriggerPressedR) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -478,7 +421,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedZLMapped) { if (isTriggerPressedZLMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.BUTTON_ZL, NativeLibrary.ButtonType.BUTTON_ZL,
if (isTriggerPressedZL) { if (isTriggerPressedZL) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -489,7 +432,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedZRMapped) { if (isTriggerPressedZRMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.BUTTON_ZR, NativeLibrary.ButtonType.BUTTON_ZR,
if (isTriggerPressedZR) { if (isTriggerPressedZR) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -502,72 +445,72 @@ class EmulationActivity : AppCompatActivity() {
// Work-around to allow D-pad axis to be bound to emulated buttons // Work-around to allow D-pad axis to be bound to emulated buttons
if (axisValuesDPad[0] == 0f) { if (axisValuesDPad[0] == 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[0] < 0f) { if (axisValuesDPad[0] < 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[0] > 0f) { if (axisValuesDPad[0] > 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
} }
if (axisValuesDPad[1] == 0f) { if (axisValuesDPad[1] == 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[1] < 0f) { if (axisValuesDPad[1] < 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[1] > 0f) { if (axisValuesDPad[1] > 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE, NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
@ -579,9 +522,7 @@ class EmulationActivity : AppCompatActivity() {
registerForActivityResult(OpenFileResultContract()) { result: Intent? -> registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles( val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, result, applicationContext, listOf<String>("bin")
applicationContext,
listOf<String>("bin")
) ?: return@registerForActivityResult ) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) { if (BuildUtil.isGooglePlayBuild) {
onAmiiboSelected(selectedFiles[0]) onAmiiboSelected(selectedFiles[0])
@ -598,12 +539,14 @@ class EmulationActivity : AppCompatActivity() {
return@registerForActivityResult return@registerForActivityResult
} }
onFilePickerResult(result.toString()) OnFilePickerResult(result.toString())
} }
companion object { companion object {
private var instance: EmulationActivity? = null private var instance: EmulationActivity? = null
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false fun isRunning(): Boolean {
return instance?.isEmulationRunning ?: false
}
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardDriverOptionBinding import org.citra.citra_emu.databinding.CardDriverOptionBinding
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.GpuDriverMetadata import org.citra.citra_emu.utils.GpuDriverMetadata
import org.citra.citra_emu.viewmodel.DriverViewModel import org.citra.citra_emu.viewmodel.DriverViewModel
import org.citra.citra_emu.utils.GpuDriverHelper
class DriverAdapter(private val driverViewModel: DriverViewModel) : class DriverAdapter(private val driverViewModel: DriverViewModel) :
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>( ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
@ -105,11 +105,15 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>, oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata> newItem: Pair<Uri, GpuDriverMetadata>
): Boolean = oldItem.first == newItem.first ): Boolean {
return oldItem.first == newItem.first
}
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>, oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata> newItem: Pair<Uri, GpuDriverMetadata>
): Boolean = oldItem.second == newItem.second ): Boolean {
return oldItem.second == newItem.second
}
} }
} }

View file

@ -4,25 +4,24 @@
package org.citra.citra_emu.adapters package org.citra.citra_emu.adapters
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.content.Context
import android.widget.PopupMenu import android.content.SharedPreferences
import android.widget.TextView import android.widget.TextView
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.BitmapFactory
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
@ -30,23 +29,23 @@ import androidx.core.graphics.scale
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import org.citra.citra_emu.HomeNavigationDirections import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
@ -65,12 +64,10 @@ class GameAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val inflater: LayoutInflater, private val inflater: LayoutInflater,
private val openImageLauncher: ActivityResultLauncher<String>?, private val openImageLauncher: ActivityResultLauncher<String>?,
private val onRequestCompressOrDecompress: ( private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit ) :
)? = null ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), View.OnClickListener, View.OnLongClickListener {
View.OnClickListener,
View.OnLongClickListener {
private var lastClickTime = 0L private var lastClickTime = 0L
private var imagePath: String? = null private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null private var dialogShortcutBinding: DialogShortcutBinding? = null
@ -214,8 +211,7 @@ class GameAdapter(
binding.textGameTitle.text = game.title binding.textGameTitle.text = game.title
binding.textCompany.text = game.company binding.textCompany.text = game.company
binding.textGameRegion.text = game.regions binding.textGameRegion.text = game.regions
binding.imageCartridge.visibility = binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
if (preferences.getString("insertedCartridge", "") != game.path) {
View.GONE View.GONE
} else { } else {
View.VISIBLE View.VISIBLE
@ -223,9 +219,7 @@ class GameAdapter(
val backgroundColorId = val backgroundColorId =
if ( if (
isValidGame( isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
)
) { ) {
R.attr.colorSurface R.attr.colorSurface
} else { } else {
@ -265,45 +259,16 @@ class GameAdapter(
val extraDir: String val extraDir: String
) )
private fun getGameDirectories(game: Game): GameDirectories { private fun getGameDirectories(game: Game): GameDirectories {
val basePath = val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
return GameDirectories( return GameDirectories(
gameDir = game.path.substringBeforeLast("/"), gameDir = game.path.substringBeforeLast("/"),
saveDir = saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
basePath +
"/title/${String.format(
"%016x",
game.titleId
).lowercase().substring(
0,
8
)}/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/data/00000001",
modsDir = "load/mods/${String.format("%016X", game.titleId)}", modsDir = "load/mods/${String.format("%016X", game.titleId)}",
texturesDir = "load/textures/${String.format("%016X", game.titleId)}", texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
appDir = game.path.substringBeforeLast("/").split("/").filter { appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
it.isNotEmpty() dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
}.joinToString("/"), updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
dlcDir = extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
basePath +
"/title/0004008c/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
updatesDir =
basePath +
"/title/0004000e/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
extraDir =
basePath +
"/extdata/00000000/${String.format(
"%016X",
game.titleId
).substring(8, 14).padStart(8, '0')}"
) )
} }
@ -333,36 +298,13 @@ class GameAdapter(
.setType("*/*") .setType("*/*")
val uri = when (menuItem.itemId) { val uri = when (menuItem.itemId) {
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper( R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
dirs.appDir R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
) R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper( R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
dirs.saveDir R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
) R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
dirs.updatesDir
)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
dirs.dlcDir
)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
dirs.extraDir
)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
dirs.texturesDir,
true
)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
dirs.modsDir,
true
)
else -> null else -> null
} }
@ -376,11 +318,7 @@ class GameAdapter(
popup.show() popup.show()
} }
private fun showUninstallContextMenu( private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
view: View,
game: Game,
bottomSheetDialog: BottomSheetDialog
) {
val dirs = getGameDirectories(game) val dirs = getGameDirectories(game)
val popup = PopupMenu(view.context, view).apply { val popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu) menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
@ -403,38 +341,16 @@ class GameAdapter(
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = { val uninstallAction: () -> Unit = {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle( R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
titleId, R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
game.mediaType R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(
dlcTitleId,
Game.MediaType.SDMC
)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(
updateTitleId,
Game.MediaType.SDMC
)
} }
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
if (menuItem.itemId in if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
listOf( IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
R.id.game_context_uninstall,
R.id.game_context_uninstall_dlc,
R.id.game_context_uninstall_updates
)
) {
IndeterminateProgressDialogFragment.newInstance(
activity,
R.string.uninstalling,
false,
uninstallAction
)
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG) .show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
true true
} else { } else {
@ -445,12 +361,7 @@ class GameAdapter(
popup.show() popup.show()
} }
private fun showAboutGameDialog( private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
context: Context,
game: Game,
holder: GameViewHolder,
view: View
) {
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null) val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
val bottomSheetDialog = BottomSheetDialog(context) val bottomSheetDialog = BottomSheetDialog(context)
@ -462,22 +373,12 @@ class GameAdapter(
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId) bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
context.getString(R.string.game_context_file) + " " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text =
context.getString(R.string.game_context_type) + " " + game.fileType
val insertButton = bottomSheetView.findViewById<MaterialButton>( val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
R.id.insert_cartridge_button insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
)
insertButton.text =
if (inserted) {
context.getString(R.string.game_context_eject)
} else {
context.getString(R.string.game_context_insert)
}
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
insertButton.setOnClickListener { insertButton.setOnClickListener {
if (inserted) { if (inserted) {
@ -518,7 +419,7 @@ class GameAdapter(
val preferences = PreferenceManager.getDefaultSharedPreferences(context) val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Default to false for zoomed in shortcut icons // Default to false for zoomed in shortcut icons
preferences.edit { preferences.edit() {
putBoolean( putBoolean(
"shouldStretchIcon", "shouldStretchIcon",
false false
@ -550,15 +451,10 @@ class GameAdapter(
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString() val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
if (shortcutName.isEmpty()) { if (shortcutName.isEmpty()) {
Toast.makeText( Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
context,
R.string.shortcut_name_empty,
Toast.LENGTH_LONG
).show()
return@setPositiveButton return@setPositiveButton
} }
val iconBitmap = val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
(dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
val shortcutManager = activity.getSystemService(ShortcutManager::class.java) val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@ -566,11 +462,9 @@ class GameAdapter(
val shortcut = ShortcutInfo.Builder(context, shortcutName) val shortcut = ShortcutInfo.Builder(context, shortcutName)
.setShortLabel(shortcutName) .setShortLabel(shortcutName)
.setIcon(icon) .setIcon(icon)
.setIntent( .setIntent(game.launchIntent.apply {
game.launchIntent.apply {
putExtra("launchedFromShortcut", true) putExtra("launchedFromShortcut", true)
} })
)
.build() .build()
shortcutManager?.requestPinShortcut(shortcut, null) shortcutManager?.requestPinShortcut(shortcut, null)
@ -591,9 +485,7 @@ class GameAdapter(
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>( val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
R.id.compress_decompress
)
if (game.isInstalled) { if (game.isInstalled) {
compressDecompressButton.setOnClickListener { compressDecompressButton.setOnClickListener {
Toast.makeText( Toast.makeText(
@ -606,90 +498,22 @@ class GameAdapter(
} else { } else {
compressDecompressButton.setOnClickListener { compressDecompressButton.setOnClickListener {
val shouldCompress = !game.isCompressed val shouldCompress = !game.isCompressed
val recommendedExt = NativeLibrary.getRecommendedExtension( val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
holder.game.path,
shouldCompress
)
val baseName = holder.game.filename.substringBeforeLast('.') val baseName = holder.game.filename.substringBeforeLast('.')
onRequestCompressOrDecompress?.invoke( onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
holder.game.path,
"$baseName.$recommendedExt",
shouldCompress
)
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
} }
compressDecompressButton.text = compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game) showOpenContextMenu(it, game)
} }
bottomSheetView.findViewById<MaterialButton>( bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener {
R.id.menu_button_uninstall
).setOnClickListener {
showUninstallContextMenu(it, game, bottomSheetDialog) showUninstallContextMenu(it, game, bottomSheetDialog)
} }
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
val options =
arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
var selectedIndex = -1
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_cache_select_backend)
.setSingleChoiceItems(options, -1) { dialog, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
Toast.LENGTH_LONG
)
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
}
activity.runOnUiThread {
progToast.cancel()
Toast.makeText(
CitraApplication.appContext,
R.string.shader_cache_deleted,
Toast.LENGTH_SHORT
).show()
}
}
}
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.create()
dialog.setOnShowListener {
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
positiveButton.isEnabled = false
val listView = dialog.listView
listView.setOnItemClickListener { _, _, position, _ ->
selectedIndex = position
positiveButton.isEnabled = true
}
}
dialog.show()
}
val bottomSheetBehavior = bottomSheetDialog.getBehavior() val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -738,8 +562,10 @@ class GameAdapter(
} }
} }
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream() private fun isValidGame(extension: String): Boolean {
return Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() } .noneMatch { extension == it.lowercase() }
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() { private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
@ -748,6 +574,8 @@ class GameAdapter(
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
} }
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
}
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -9,24 +9,27 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardHomeOptionBinding import org.citra.citra_emu.databinding.CardHomeOptionBinding
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.HomeSetting import org.citra.citra_emu.model.HomeSetting
import org.citra.citra_emu.viewmodel.GamesViewModel
class HomeSettingAdapter( class HomeSettingAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner, private val viewLifecycle: LifecycleOwner,
var options: List<HomeSetting> var options: List<HomeSetting>
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), ) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
val binding = val binding =
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -34,7 +37,9 @@ class HomeSettingAdapter(
return HomeOptionViewHolder(binding) return HomeOptionViewHolder(binding)
} }
override fun getItemCount(): Int = options.size override fun getItemCount(): Int {
return options.size
}
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) { override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position]) holder.bind(options[position])

View file

@ -8,17 +8,18 @@ import android.content.res.ColorStateList
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.PageSetupBinding import org.citra.citra_emu.databinding.PageSetupBinding
import org.citra.citra_emu.model.ButtonState import org.citra.citra_emu.model.ButtonState
import org.citra.citra_emu.model.PageState import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.ViewUtils import org.citra.citra_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
@ -34,8 +35,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position]) holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) : inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root), RecyclerView.ViewHolder(binding.root), SetupCallback {
SetupCallback {
lateinit var page: SetupPage lateinit var page: SetupPage
init { init {
@ -49,9 +49,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
onStepCompleted(0, pageFullyCompleted = true) onStepCompleted(0, pageFullyCompleted = true)
} }
if (page.pageButtons != null && if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE
) {
for (pageButton in page.pageButtons) { for (pageButton in page.pageButtons) {
val pageButtonView = LayoutInflater.from(activity) val pageButtonView = LayoutInflater.from(activity)
.inflate( .inflate(
@ -110,17 +108,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
.alpha(0.38f) .alpha(0.38f)
.setDuration(200) .setDuration(200)
.start() .start()
button.setTextColor( button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
button.iconTint = button.iconTint =
ColorStateList.valueOf( ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
} }
} }
} }

View file

@ -1,20 +1,20 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.applets package org.citra.citra_emu.applets
import androidx.annotation.Keep import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
import java.io.Serializable
@Keep @Keep
object MiiSelector { object MiiSelector {
lateinit var data: MiiSelectorData lateinit var data: MiiSelectorData
val finishLock = Object() val finishLock = Object()
private fun executeImpl(config: MiiSelectorConfig) { private fun ExecuteImpl(config: MiiSelectorConfig) {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
data = MiiSelectorData(0, 0) data = MiiSelectorData(0, 0)
val fragment = MiiSelectorDialogFragment.newInstance(config) val fragment = MiiSelectorDialogFragment.newInstance(config)
@ -22,8 +22,8 @@ object MiiSelector {
} }
@JvmStatic @JvmStatic
fun execute(config: MiiSelectorConfig): MiiSelectorData { fun Execute(config: MiiSelectorConfig): MiiSelectorData {
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) } NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
synchronized(finishLock) { synchronized(finishLock) {
try { try {
finishLock.wait() finishLock.wait()
@ -43,5 +43,5 @@ object MiiSelector {
lateinit var miiNames: Array<String> lateinit var miiNames: Array<String>
} }
class MiiSelectorData(var returnCode: Long, var index: Int) class MiiSelectorData (var returnCode: Long, var index: Int)
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -7,27 +7,27 @@ package org.citra.citra_emu.applets
import android.text.InputFilter import android.text.InputFilter
import android.text.Spanned import android.text.Spanned
import androidx.annotation.Keep import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.CitraApplication.Companion.appContext import org.citra.citra_emu.CitraApplication.Companion.appContext
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.fragments.KeyboardDialogFragment import org.citra.citra_emu.fragments.KeyboardDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import java.io.Serializable
@Keep @Keep
object SoftwareKeyboard { object SoftwareKeyboard {
lateinit var data: KeyboardData lateinit var data: KeyboardData
val finishLock = Object() val finishLock = Object()
private fun executeImpl(config: KeyboardConfig) { private fun ExecuteImpl(config: KeyboardConfig) {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
data = KeyboardData(0, "") data = KeyboardData(0, "")
KeyboardDialogFragment.newInstance(config) KeyboardDialogFragment.newInstance(config)
.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG) .show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
} }
fun handleValidationError(config: KeyboardConfig, error: ValidationError) { fun HandleValidationError(config: KeyboardConfig, error: ValidationError) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()!! val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
val message: String = when (error) { val message: String = when (error) {
ValidationError.FixedLengthRequired -> emulationActivity.getString( ValidationError.FixedLengthRequired -> emulationActivity.getString(
@ -54,12 +54,12 @@ object SoftwareKeyboard {
} }
@JvmStatic @JvmStatic
fun execute(config: KeyboardConfig): KeyboardData { fun Execute(config: KeyboardConfig): KeyboardData {
if (config.buttonConfig == ButtonConfig.NONE) { if (config.buttonConfig == ButtonConfig.None) {
Log.error("Unexpected button config None") Log.error("Unexpected button config None")
return KeyboardData(0, "") return KeyboardData(0, "")
} }
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) } NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
synchronized(finishLock) { synchronized(finishLock) {
try { try {
finishLock.wait() finishLock.wait()
@ -69,9 +69,8 @@ object SoftwareKeyboard {
return data return data
} }
@Suppress("unused")
@JvmStatic @JvmStatic
fun showError(error: String) { fun ShowError(error: String) {
NativeLibrary.displayAlertMsg( NativeLibrary.displayAlertMsg(
appContext.resources.getString(R.string.software_keyboard), appContext.resources.getString(R.string.software_keyboard),
error, error,
@ -79,23 +78,20 @@ object SoftwareKeyboard {
) )
} }
@Suppress("FunctionName")
private external fun ValidateFilters(text: String): ValidationError private external fun ValidateFilters(text: String): ValidationError
@Suppress("FunctionName")
external fun ValidateInput(text: String): ValidationError external fun ValidateInput(text: String): ValidationError
// / Corresponds to Frontend::ButtonConfig /// Corresponds to Frontend::ButtonConfig
interface ButtonConfig { interface ButtonConfig {
companion object { companion object {
const val SINGLE = 0 // / Ok button const val Single = 0 /// Ok button
const val DUAL = 1 // / Cancel | Ok buttons const val Dual = 1 /// Cancel | Ok buttons
const val TRIPLE = 2 // / Cancel | I Forgot | Ok buttons const val Triple = 2 /// Cancel | I Forgot | Ok buttons
const val NONE = 3 // / No button (returned by swkbdInputText in special cases) const val None = 3 /// No button (returned by swkbdInputText in special cases)
} }
} }
// / Corresponds to Frontend::ValidationError /// Corresponds to Frontend::ValidationError
enum class ValidationError { enum class ValidationError {
None, None,
@ -132,7 +128,7 @@ object SoftwareKeyboard {
lateinit var buttonText: Array<String> lateinit var buttonText: Array<String>
} }
// / Corresponds to Frontend::KeyboardData /// Corresponds to Frontend::KeyboardData
class KeyboardData(var button: Int, var text: String) class KeyboardData(var button: Int, var text: String)
class Filter : InputFilter { class Filter : InputFilter {
override fun filter( override fun filter(

View file

@ -21,10 +21,9 @@ object StillImageCameraHelper {
private var filePickerPath: String? = null private var filePickerPath: String? = null
// Opens file picker for camera. // Opens file picker for camera.
@Suppress("unused")
@Keep @Keep
@JvmStatic @JvmStatic
fun openFilePicker(): String? { fun OpenFilePicker(): String? {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
// At this point, we are assuming that we already have permissions as they are // At this point, we are assuming that we already have permissions as they are
@ -45,16 +44,15 @@ object StillImageCameraHelper {
// Called from EmulationActivity. // Called from EmulationActivity.
@JvmStatic @JvmStatic
fun onFilePickerResult(result: String) { fun OnFilePickerResult(result: String) {
filePickerPath = result filePickerPath = result
synchronized(filePickerLock) { filePickerLock.notifyAll() } synchronized(filePickerLock) { filePickerLock.notifyAll() }
} }
// Blocking call. Load image from file and crop/resize it to fit in width x height. // Blocking call. Load image from file and crop/resize it to fit in width x height.
@Suppress("unused")
@Keep @Keep
@JvmStatic @JvmStatic
fun loadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? { fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
val context = CitraApplication.appContext val context = CitraApplication.appContext
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.data(uri) .data(uri)

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -9,10 +9,11 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() { class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
override fun createIntent(context: Context, input: Boolean?): Intent = override fun createIntent(context: Context, input: Boolean?): Intent {
Intent(Intent.ACTION_OPEN_DOCUMENT) return Intent(Intent.ACTION_OPEN_DOCUMENT)
.setType("application/octet-stream") .setType("application/octet-stream")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input) .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
} }

View file

@ -4,14 +4,15 @@
package org.citra.citra_emu.display package org.citra.citra_emu.display
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.ActivityInfo
import android.app.Activity
import android.view.WindowManager import android.view.WindowManager
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
@ -19,7 +20,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
class ScreenAdjustmentUtil( class ScreenAdjustmentUtil(
private val context: Context, private val context: Context,
private val windowManager: WindowManager, private val windowManager: WindowManager,
private val settings: Settings private val settings: Settings,
) { ) {
fun swapScreen() { fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens val isEnabled = !EmulationMenuSettings.swapScreens
@ -33,18 +34,17 @@ class ScreenAdjustmentUtil(
} }
fun cycleLayouts() { fun cycleLayouts() {
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeValues = val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty()) { if (landscapeLayoutsToCycle.isNotEmpty())
landscapeLayoutsToCycle.toIntArray() landscapeLayoutsToCycle.toIntArray()
} else { else context.resources.getIntArray(
context.resources.getIntArray(
R.array.landscapeValues R.array.landscapeValues
) )
}
val portraitValues = context.resources.getIntArray(R.array.portraitValues) val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode()) { if (NativeLibrary.isPortraitMode) {
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
val pos = portraitValues.indexOf(currentLayout) val pos = portraitValues.indexOf(currentLayout)
val layoutOption = portraitValues[(pos + 1) % portraitValues.size] val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
@ -61,32 +61,14 @@ class ScreenAdjustmentUtil(
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode()) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
fun changeScreenOrientation(layoutOption: Int) { fun changeScreenOrientation(layoutOption: Int) {
IntSetting.SCREEN_LAYOUT.int = layoutOption IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode()) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeSecondaryOrientation(layoutOption: Int) {
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
}
fun enableSecondaryDisplay(layoutOption: Int) {
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = true
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
changeSecondaryOrientation(layoutOption)
}
fun disableSecondaryDisplay() {
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
} }
fun changeActivityOrientation(orientationOption: Int) { fun changeActivityOrientation(orientationOption: Int) {
@ -101,6 +83,7 @@ class ScreenAdjustmentUtil(
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode()) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
} }

View file

@ -13,8 +13,11 @@ enum class ScreenLayout(val int: Int) {
HYBRID_SCREEN(4), HYBRID_SCREEN(4),
CUSTOM_LAYOUT(5); CUSTOM_LAYOUT(5);
companion object { companion object {
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
} }
} }
@ -29,7 +32,9 @@ enum class SmallScreenPosition(val int: Int) {
BELOW(7); BELOW(7);
companion object { companion object {
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT fun from(int: Int): SmallScreenPosition {
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
} }
} }
@ -40,27 +45,23 @@ enum class PortraitScreenLayout(val int: Int) {
ORIGINAL(2); ORIGINAL(2);
companion object { companion object {
fun from(int: Int): PortraitScreenLayout = fun from(int: Int): PortraitScreenLayout {
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
} }
} }
enum class SecondaryDisplayLayout(val int: Int) { enum class SecondaryDisplayLayout(val int: Int) {
// These must match what is defined in src/common/settings.h // These must match what is defined in src/common/settings.h
// NONE is no longer selectable in the interface, having been replaced with
// the boolean ENABLE_SECONDARY_DISPLAY setting, but is left here for backwards compatibility
NONE(0), NONE(0),
TOP_SCREEN(1), TOP_SCREEN(1),
BOTTOM_SCREEN(2), BOTTOM_SCREEN(2),
SIDE_BY_SIDE(3), SIDE_BY_SIDE(3);
REVERSE_PRIMARY(4),
ORIGINAL(5),
HYBRID(6),
LARGE_SCREEN(7)
;
companion object { companion object {
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE fun from(int: Int): SecondaryDisplayLayout {
return entries.firstOrNull { it.int == int } ?: NONE
}
} }
} }
@ -73,7 +74,9 @@ enum class StereoWhichDisplay(val int: Int) {
SECONDARY_ONLY(3); SECONDARY_ONLY(3);
companion object { companion object {
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE fun from(int: Int): StereoWhichDisplay {
return entries.firstOrNull { it.int == int } ?: NONE
}
} }
} }
@ -85,10 +88,12 @@ enum class StereoMode(val int: Int) {
SIDE_BY_SIDE_FULL(2), SIDE_BY_SIDE_FULL(2),
ANAGLYPH(3), ANAGLYPH(3),
INTERLACED(4), INTERLACED(4),
REVERSE_INTERLACED(5), REVERSE_INTERLACED (5),
CARDBOARD_VR(6); CARDBOARD_VR (6);
companion object { companion object {
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
} }
} }

View file

@ -8,27 +8,19 @@ import android.app.Presentation
import android.content.Context import android.content.Context
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Display import android.view.Display
import android.view.MotionEvent import android.view.MotionEvent
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.WindowManager import android.view.WindowManager
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.NativeLibrary
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
private var pres: SecondaryDisplayPresentation? = null private var pres: SecondaryDisplayPresentation? = null
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val vd: VirtualDisplay private val vd: VirtualDisplay
var preferredDisplayId = -1
var currentDisplayId = -1
val availableDisplays: List<Display>
get() = getSecondaryDisplays()
init { init {
vd = displayManager.createVirtualDisplay( vd = displayManager.createVirtualDisplay(
@ -43,40 +35,31 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
} }
fun updateSurface() { fun updateSurface() {
val surface = pres?.getSurfaceHolder()?.surface NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
if (surface != null && surface.isValid) {
NativeLibrary.secondarySurfaceChanged(surface)
} else {
Log.warning("SecondaryDisplay Attempted to update null or invalid surface")
}
} }
fun destroySurface() { fun destroySurface() {
NativeLibrary.secondarySurfaceDestroyed() NativeLibrary.secondarySurfaceDestroyed()
} }
private fun getSecondaryDisplays(): List<Display> { private fun getExternalDisplay(context: Context): Display? {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val currentDisplayId = context.display.displayId
context.display.displayId
} else {
@Suppress("DEPRECATION")
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.displayId
}
val displays = dm.displays val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION) val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
return displays.filter { val extDisplays = displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId } val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
isNotDefaultOrPresentable && isNotDefaultOrPresentable &&
it.displayId != currentDisplayId && it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" && it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF && it.state != Display.STATE_OFF &&
it.isValid it.isValid
} }
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
?: extDisplays.firstOrNull()
return selected
} }
fun updateDisplay() { fun updateDisplay() {
@ -85,40 +68,21 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
return return
} }
val displayToUse = if (availableDisplays.isEmpty() || // decide if we are going to the external display or the internal one
// Theoretically, the NONE option is no longer selectable, but var display = getExternalDisplay(context)
// I am leaving this in for backwards compatibility if (display == null ||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int || IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean display = vd.display
) {
currentDisplayId = -1
vd.display
} else if (preferredDisplayId >= 0 &&
availableDisplays.any { it.displayId == preferredDisplayId }
) {
currentDisplayId = preferredDisplayId
availableDisplays.first { it.displayId == preferredDisplayId }
} else {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val default = dm.displays.first { it.displayId == Display.DEFAULT_DISPLAY }
// prioritize displays that have a different name from the default display, as
// some devices such as the Odin 2 create a permanent virtual display with the same
// name as the default display that should be skipped in most cases
currentDisplayId = availableDisplays.firstOrNull {
it.name != default.name && !it.name.contains("Built", true)
}?.displayId
?: availableDisplays[0].displayId
availableDisplays.first { it.displayId == currentDisplayId }
} }
// if our presentation is already on the right display, ignore // if our presentation is already on the right display, ignore
if (pres?.display == displayToUse) return if (pres?.display == display) return
// otherwise, make a new presentation // otherwise, make a new presentation
releasePresentation() releasePresentation()
try { try {
pres = SecondaryDisplayPresentation(context, displayToUse!!, this) pres = SecondaryDisplayPresentation(context, display!!, this)
pres?.show() pres?.show()
} }
// catch BadTokenException and InvalidDisplayException, // catch BadTokenException and InvalidDisplayException,
@ -155,9 +119,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
} }
} }
class SecondaryDisplayPresentation( class SecondaryDisplayPresentation(
context: Context, context: Context, display: Display, val parent: SecondaryDisplay
display: Display,
val parent: SecondaryDisplay
) : Presentation(context, display) { ) : Presentation(context, display) {
private lateinit var surfaceView: SurfaceView private lateinit var surfaceView: SurfaceView
private var touchscreenPointerId = -1 private var touchscreenPointerId = -1
@ -175,21 +137,16 @@ class SecondaryDisplayPresentation(
surfaceView = SurfaceView(context) surfaceView = SurfaceView(context)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback { surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) { override fun surfaceCreated(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface created")
} }
override fun surfaceChanged( override fun surfaceChanged(
holder: SurfaceHolder, holder: SurfaceHolder, format: Int, width: Int, height: Int
format: Int,
width: Int,
height: Int
) { ) {
Log.debug("SecondaryDisplay Surface changed: ${width}x$height")
parent.updateSurface() parent.updateSurface()
} }
override fun surfaceDestroyed(holder: SurfaceHolder) { override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface destroyed")
parent.destroySurface() parent.destroySurface()
} }
}) })
@ -234,5 +191,7 @@ class SecondaryDisplayPresentation(
} }
// Publicly accessible method to get the SurfaceHolder // Publicly accessible method to get the SurfaceHolder
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder fun getSurfaceHolder(): SurfaceHolder {
return surfaceView.holder
}
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
private var selectedCheatPosition = -1 private var selectedCheatPosition = -1
fun initialize(titleId_: Long) { fun initialize(titleId_: Long) {
titleId = titleId_ titleId = titleId_;
load() load()
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -170,7 +170,8 @@ class CheatDetailsFragment : Fragment() {
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
} }
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener( private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root binding.root
) { _: View?, windowInsets: WindowInsetsCompat -> ) { _: View?, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -41,8 +41,7 @@ class CheatsAdapter(
} }
inner class CheatViewHolder(private val binding: ListItemCheatBinding) : inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
RecyclerView.ViewHolder(binding.root), RecyclerView.ViewHolder(binding.root), View.OnClickListener,
View.OnClickListener,
CompoundButton.OnCheckedChangeListener { CompoundButton.OnCheckedChangeListener {
private lateinit var viewModel: CheatsViewModel private lateinit var viewModel: CheatsViewModel
private lateinit var cheat: Cheat private lateinit var cheat: Cheat

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -32,9 +32,7 @@ import org.citra.citra_emu.ui.TwoPaneOnBackPressedCallback
import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.viewmodel.HomeViewModel import org.citra.citra_emu.viewmodel.HomeViewModel
class CheatsFragment : class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
Fragment(),
SlidingPaneLayout.PanelSlideListener {
private var cheatListLastFocus: View? = null private var cheatListLastFocus: View? = null
private var cheatDetailsLastFocus: View? = null private var cheatDetailsLastFocus: View? = null
@ -159,15 +157,12 @@ class CheatsFragment :
} }
private fun onSelectedCheatChanged(selectedCheat: Cheat?) { private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value!!
if (!cheatSelected && binding.slidingPaneLayout.isOpen) { if (!cheatSelected && binding.slidingPaneLayout.isOpen) {
binding.slidingPaneLayout.close() binding.slidingPaneLayout.close()
} }
binding.slidingPaneLayout.lockMode = if (cheatSelected) { binding.slidingPaneLayout.lockMode =
SlidingPaneLayout.LOCK_MODE_UNLOCKED if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
} else {
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
}
} }
fun onListViewFocusChange(hasFocus: Boolean) { fun onListViewFocusChange(hasFocus: Boolean) {
@ -208,8 +203,7 @@ class CheatsFragment :
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
// Set keyboard insets if the system supports smooth keyboard animations // Set keyboard insets if the system supports smooth keyboard animations
val mlpDetails = binding.cheatDetailsContainer.layoutParams val mlpDetails = binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
as ViewGroup.MarginLayoutParams
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (keyboardInsets.bottom > 0) { if (keyboardInsets.bottom > 0) {
mlpDetails.bottomMargin = keyboardInsets.bottom mlpDetails.bottomMargin = keyboardInsets.bottom
@ -237,16 +231,14 @@ class CheatsFragment :
runningAnimations: List<WindowInsetsAnimationCompat> runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat { ): WindowInsetsCompat {
val mlpDetails = val mlpDetails =
binding.cheatDetailsContainer.layoutParams binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
as ViewGroup.MarginLayoutParams
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets) mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
binding.cheatDetailsContainer.layoutParams = mlpDetails binding.cheatDetailsContainer.layoutParams = mlpDetails
return insets return insets
} }
} })
)
} }
} }
} }

View file

@ -12,5 +12,5 @@ enum class Hotkey(val button: Int) {
QUICKSAVE(10005), QUICKSAVE(10005),
QUICKLOAD(10006), QUICKLOAD(10006),
TURBO_LIMIT(10007), TURBO_LIMIT(10007),
ENABLE(10008) ENABLE(10008);
} }

View file

@ -11,11 +11,11 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.Settings
class HotkeyUtility( class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val screenAdjustmentUtil: ScreenAdjustmentUtil,
@ -41,7 +41,7 @@ class HotkeyUtility(
// Now process all internal buttons associated with this keypress // Now process all internal buttons associated with this keypress
for (button in buttonSet) { for (button in buttonSet) {
currentlyPressedButtons.add(button) currentlyPressedButtons.add(button)
// option 1 - this is the enable command, which was already handled //option 1 - this is the enable command, which was already handled
if (button == Hotkey.ENABLE.button) { if (button == Hotkey.ENABLE.button) {
handled = true handled = true
} }
@ -74,8 +74,7 @@ class HotkeyUtility(
val thisKeyIsHotkey = val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) } !thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) { if (thisKeyIsEnableButton) {
handled = true handled = true; hotkeyIsEnabled = false
hotkeyIsEnabled = false
} }
for (button in buttonSet) { for (button in buttonSet) {
@ -110,15 +109,10 @@ class HotkeyUtility(
fun handleHotkey(bindedButton: Int): Boolean { fun handleHotkey(bindedButton: Int): Boolean {
when (bindedButton) { when (bindedButton) {
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen() Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true) Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
Hotkey.QUICKSAVE.button -> { Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText( Toast.makeText(

View file

@ -18,7 +18,6 @@ object SettingKeys {
external fun enable_required_online_lle_modules(): String external fun enable_required_online_lle_modules(): String
external fun use_virtual_sd(): String external fun use_virtual_sd(): String
external fun compress_cia_installs(): String external fun compress_cia_installs(): String
external fun async_fs_operations(): String
external fun region_value(): String external fun region_value(): String
external fun init_clock(): String external fun init_clock(): String
external fun init_time(): String external fun init_time(): String
@ -46,7 +45,6 @@ object SettingKeys {
external fun texture_filter(): String external fun texture_filter(): String
external fun texture_sampling(): String external fun texture_sampling(): String
external fun delay_game_render_thread_us(): String external fun delay_game_render_thread_us(): String
external fun simulate_3ds_gpu_timings(): String
external fun layout_option(): String external fun layout_option(): String
external fun swap_screen(): String external fun swap_screen(): String
external fun upright_screen(): String external fun upright_screen(): String
@ -94,7 +92,6 @@ object SettingKeys {
external fun audio_emulation(): String external fun audio_emulation(): String
external fun enable_audio_stretching(): String external fun enable_audio_stretching(): String
external fun enable_realtime_audio(): String external fun enable_realtime_audio(): String
external fun simulate_headphones_plugged(): String
external fun volume(): String external fun volume(): String
external fun output_type(): String external fun output_type(): String
external fun output_device(): String external fun output_device(): String
@ -141,5 +138,4 @@ object SettingKeys {
external fun android_hide_images(): String external fun android_hide_images(): String
external fun screen_orientation(): String external fun screen_orientation(): String
external fun performance_overlay_position(): String external fun performance_overlay_position(): String
external fun enable_secondary_display(): String
} }

View file

@ -20,63 +20,19 @@ enum class BooleanSetting(
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false), SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false), INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false), ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE( TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
SettingKeys.toggle_unique_data_console_type(), SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
Settings.SECTION_DEBUG,
false
),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(), Settings.SECTION_RENDERER, false),
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false), PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS( PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
SettingKeys.performance_overlay_show_fps(), PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
Settings.SECTION_LAYOUT, PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
true PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
), PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FRAMETIME( PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
SettingKeys.performance_overlay_show_frame_time(), PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
Settings.SECTION_LAYOUT, DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
false DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
), REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
PERF_OVERLAY_SHOW_SPEED(
SettingKeys.performance_overlay_show_speed(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(
SettingKeys.performance_overlay_show_app_ram_usage(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(
SettingKeys.performance_overlay_show_available_ram(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_BATTERY_TEMP(
SettingKeys.performance_overlay_show_battery_temp(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_BACKGROUND(
SettingKeys.performance_overlay_background(),
Settings.SECTION_LAYOUT,
false
),
DELAY_START_LLE_MODULES(
SettingKeys.delay_start_for_lle_modules(),
Settings.SECTION_DEBUG,
true
),
DETERMINISTIC_ASYNC_OPERATIONS(
SettingKeys.deterministic_async_operations(),
Settings.SECTION_DEBUG,
false
),
REQUIRED_ONLINE_LLE_MODULES(
SettingKeys.enable_required_online_lle_modules(),
Settings.SECTION_SYSTEM,
false
),
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false), LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true), NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true), LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
@ -88,43 +44,19 @@ enum class BooleanSetting(
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false), PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true), ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false), ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(
SettingKeys.simulate_headphones_plugged(),
Settings.SECTION_AUDIO,
false
),
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true), CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true), HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true), SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false), VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true), USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false), DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER( DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
SettingKeys.disable_right_eye_render(), USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
Settings.SECTION_RENDERER,
false
),
USE_ARTIC_BASE_CONTROLLER(
SettingKeys.use_artic_base_controller(),
Settings.SECTION_CONTROLS,
false
),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false), UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT( COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
SettingKeys.compress_cia_installs(),
Settings.SECTION_STORAGE,
false
),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false), ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true), APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false), USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false);
ENABLE_SECONDARY_DISPLAY(SettingKeys.enable_secondary_display(), Settings.SECTION_LAYOUT, true),
SIMULATE_3DS_GPU_TIMINGS(
SettingKeys.simulate_3ds_gpu_timings(),
Settings.SECTION_RENDERER,
true
);
override var boolean: Boolean = defaultValue override var boolean: Boolean = defaultValue
@ -159,7 +91,6 @@ enum class BooleanSetting(
SHADERS_ACCURATE_MUL, SHADERS_ACCURATE_MUL,
USE_ARTIC_BASE_CONTROLLER, USE_ARTIC_BASE_CONTROLLER,
COMPRESS_INSTALLED_CIA_CONTENT, COMPRESS_INSTALLED_CIA_CONTENT,
ASYNC_FS_OPERATIONS,
ANDROID_HIDE_IMAGES, ANDROID_HIDE_IMAGES,
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
APPLY_REGION_FREE_PATCH APPLY_REGION_FREE_PATCH

View file

@ -11,12 +11,8 @@ enum class FloatSetting(
override val section: String, override val section: String,
override val defaultValue: Float override val defaultValue: Float
) : AbstractFloatSetting { ) : AbstractFloatSetting {
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(), Settings.SECTION_LAYOUT, 2.25f), LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY( SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
SettingKeys.custom_second_layer_opacity(),
Settings.SECTION_RENDERER,
100f
),
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f), BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f), BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f); BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);

View file

@ -4,8 +4,6 @@
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntListSetting( enum class IntListSetting(
override val key: String, override val key: String,
override val section: String, override val section: String,
@ -13,15 +11,10 @@ enum class IntListSetting(
val canBeEmpty: Boolean = true val canBeEmpty: Boolean = true
) : AbstractListSetting<Int> { ) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE( LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
SettingKeys.layouts_to_cycle(),
Settings.SECTION_LAYOUT,
listOf(0, 1, 2, 3, 4, 5),
canBeEmpty = false
);
private var backingList: List<Int> = defaultValue private var backingList: List<Int> = defaultValue
private var lastValidList: List<Int> = defaultValue private var lastValidList : List<Int> = defaultValue
override var list: List<Int> override var list: List<Int>
get() = backingList get() = backingList
@ -37,6 +30,7 @@ enum class IntListSetting(
override val valueAsString: String override val valueAsString: String
get() = list.joinToString() get() = list.joinToString()
override val isRuntimeEditable: Boolean override val isRuntimeEditable: Boolean
get() { get() {
for (setting in NOT_RUNTIME_EDITABLE) { for (setting in NOT_RUNTIME_EDITABLE) {
@ -50,7 +44,8 @@ enum class IntListSetting(
companion object { companion object {
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList() private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key } fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue } fun clear() = values().forEach { it.list = it.defaultValue }
} }

View file

@ -17,7 +17,7 @@ enum class IntSetting(
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0), CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2), GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1), RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2), STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0), STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
@ -26,30 +26,26 @@ enum class IntSetting(
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0), CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0), CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0), SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(), Settings.SECTION_LAYOUT, 0), SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(), Settings.SECTION_LAYOUT, 0), LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(), Settings.SECTION_LAYOUT, 0), LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(), Settings.SECTION_LAYOUT, 800), LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(), Settings.SECTION_LAYOUT, 480), LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(), Settings.SECTION_LAYOUT, 80), LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(), Settings.SECTION_LAYOUT, 480), LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(), Settings.SECTION_LAYOUT, 640), LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(), Settings.SECTION_LAYOUT, 480), LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
SCREEN_GAP(SettingKeys.screen_gap(), Settings.SECTION_LAYOUT, 0), SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(), Settings.SECTION_LAYOUT, 0), PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 4), SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(), Settings.SECTION_LAYOUT, 0), PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(), Settings.SECTION_LAYOUT, 0), PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(), Settings.SECTION_LAYOUT, 800), PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(), Settings.SECTION_LAYOUT, 480), PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(), Settings.SECTION_LAYOUT, 80), PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(), Settings.SECTION_LAYOUT, 480), PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(), Settings.SECTION_LAYOUT, 640), PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT( PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
SettingKeys.custom_portrait_bottom_height(),
Settings.SECTION_LAYOUT,
480
),
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0), AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100), CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0), TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
@ -58,12 +54,8 @@ enum class IntSetting(
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0), DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2), ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200), TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION( PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
SettingKeys.performance_overlay_position(), RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
Settings.SECTION_LAYOUT,
0
),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(), Settings.SECTION_RENDERER, 0),
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0); ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue override var int: Int = defaultValue
@ -86,7 +78,7 @@ enum class IntSetting(
EMULATED_REGION, EMULATED_REGION,
INIT_CLOCK, INIT_CLOCK,
GRAPHICS_API, GRAPHICS_API,
AUDIO_INPUT_TYPE AUDIO_INPUT_TYPE,
) )
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -26,7 +26,9 @@ class SettingSection(val name: String) {
* @param key Used to retrieve the Setting. * @param key Used to retrieve the Setting.
* @return A Setting object (you should probably cast this before using) * @return A Setting object (you should probably cast this before using)
*/ */
fun getSetting(key: String): AbstractSetting? = settings[key] fun getSetting(key: String): AbstractSetting? {
return settings[key]
}
fun mergeSection(settingSection: SettingSection) { fun mergeSection(settingSection: SettingSection) {
for (setting in settingSection.settings.values) { for (setting in settingSection.settings.values) {

View file

@ -5,11 +5,11 @@
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import android.text.TextUtils import android.text.TextUtils
import java.util.TreeMap
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.ui.SettingsActivityView import org.citra.citra_emu.features.settings.ui.SettingsActivityView
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
import java.util.TreeMap
class Settings { class Settings {
private var gameId: String? = null private var gameId: String? = null
@ -33,7 +33,9 @@ class Settings {
var sections: HashMap<String, SettingSection?> = SettingsSectionMap() var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
fun getSection(sectionName: String): SettingSection? = sections[sectionName] fun getSection(sectionName: String): SettingSection? {
return sections[sectionName]
}
val isEmpty: Boolean val isEmpty: Boolean
get() = sections.isEmpty() get() = sections.isEmpty()
@ -140,7 +142,7 @@ class Settings {
const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game" const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload" const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKLOAD = "hotkey_quickpause" const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit" const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
val buttonKeys = listOf( val buttonKeys = listOf(
@ -208,7 +210,7 @@ class Settings {
HOTKEY_CLOSE_GAME, HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME, HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE, HOTKEY_QUICKSAVE,
HOTKEY_QUICKLOAD, HOTKEY_QUICKlOAD,
HOTKEY_TURBO_LIMIT HOTKEY_TURBO_LIMIT
) )
val hotkeyTitles = listOf( val hotkeyTitles = listOf(

View file

@ -15,17 +15,9 @@ enum class StringSetting(
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"), CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"), CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"), CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_LEFT_CONFIG( CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
SettingKeys.camera_outer_left_config(),
Settings.SECTION_CAMERA,
"_back"
),
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"), CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_RIGHT_CONFIG( CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
SettingKeys.camera_outer_right_config(),
Settings.SECTION_CAMERA,
"_back"
);
override var string: String = defaultValue override var string: String = defaultValue

View file

@ -1,11 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.AbstractStringSetting
@ -15,9 +13,7 @@ class DateTimeSetting(
descriptionId: Int, descriptionId: Int,
val key: String? = null, val key: String? = null,
private val defaultValue: String? = null, private val defaultValue: String? = null,
override var isEnabled: Boolean = true, override var isEnabled: Boolean = true
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING override val type = TYPE_DATETIME_SETTING

View file

@ -1,9 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view package org.citra.citra_emu.features.settings.model.view
class HeaderSetting(titleId: Int, descId: Int = 0) : SettingsItem(null, titleId, descId) { class HeaderSetting(titleId: Int,descId: Int = 0) : SettingsItem(null, titleId, descId) {
override val type = TYPE_HEADER override val type = TYPE_HEADER
} }

View file

@ -20,8 +20,10 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) : class InputBindingSetting(
SettingsItem(abstractSetting, titleId, 0) { val abstractSetting: AbstractSetting,
titleId: Int
) : SettingsItem(abstractSetting, titleId, 0) {
private val context: Context get() = CitraApplication.appContext private val context: Context get() = CitraApplication.appContext
private val preferences: SharedPreferences private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(context) get() = PreferenceManager.getDefaultSharedPreferences(context)
@ -37,7 +39,8 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
/** /**
* Returns true if this key is for the 3DS Circle Pad * Returns true if this key is for the 3DS Circle Pad
*/ */
fun isCirclePad(): Boolean = when (abstractSetting.key) { fun isCirclePad(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
@ -47,7 +50,8 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
/** /**
* Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad * Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad
*/ */
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) { fun isHorizontalOrientation(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_HORIZONTAL, Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
@ -58,7 +62,8 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
/** /**
* Returns true if this key is for the 3DS C-Stick * Returns true if this key is for the 3DS C-Stick
*/ */
fun isCStick(): Boolean = when (abstractSetting.key) { fun isCStick(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CSTICK_AXIS_HORIZONTAL, Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_VERTICAL -> true Settings.KEY_CSTICK_AXIS_VERTICAL -> true
@ -68,18 +73,19 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
/** /**
* Returns true if this key is for the 3DS D-Pad * Returns true if this key is for the 3DS D-Pad
*/ */
fun isDPad(): Boolean = when (abstractSetting.key) { fun isDPad(): Boolean =
when (abstractSetting.key) {
Settings.KEY_DPAD_AXIS_HORIZONTAL, Settings.KEY_DPAD_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_VERTICAL -> true Settings.KEY_DPAD_AXIS_VERTICAL -> true
else -> false else -> false
} }
/** /**
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real * Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
* triggers on the 3DS, but we support them as such on a physical gamepad. * triggers on the 3DS, but we support them as such on a physical gamepad.
*/ */
fun isTrigger(): Boolean = when (abstractSetting.key) { fun isTrigger(): Boolean =
when (abstractSetting.key) {
Settings.KEY_BUTTON_L, Settings.KEY_BUTTON_L,
Settings.KEY_BUTTON_R, Settings.KEY_BUTTON_R,
Settings.KEY_BUTTON_ZL, Settings.KEY_BUTTON_ZL,
@ -91,12 +97,16 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
/** /**
* Returns true if a gamepad axis can be used to map this key. * Returns true if a gamepad axis can be used to map this key.
*/ */
fun isAxisMappingSupported(): Boolean = isCirclePad() || isCStick() || isDPad() || isTrigger() fun isAxisMappingSupported(): Boolean {
return isCirclePad() || isCStick() || isDPad() || isTrigger()
}
/** /**
* Returns true if a gamepad button can be used to map this key. * Returns true if a gamepad button can be used to map this key.
*/ */
fun isButtonMappingSupported(): Boolean = !isAxisMappingSupported() || isTrigger() fun isButtonMappingSupported(): Boolean {
return !isAxisMappingSupported() || isTrigger()
}
/** /**
* Returns the Citra button code for the settings key. * Returns the Citra button code for the settings key.
@ -125,7 +135,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
else -> -1 else -> -1
} }
@ -167,10 +177,10 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
// if this is an int pref, either old button or an axis, so just remove it // if this is an int pref, either old button or an axis, so just remove it
preferences.edit().remove(oldKey).apply() preferences.edit().remove(oldKey).apply()
return return;
} }
buttonCodes.remove(buttonCode.toString()) buttonCodes.remove(buttonCode.toString());
preferences.edit().putStringSet(oldKey, buttonCodes).apply() preferences.edit().putStringSet(oldKey,buttonCodes).apply()
} }
} }
@ -187,7 +197,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
// Cleanup old mapping for this setting // Cleanup old mapping for this setting
removeOldMapping() removeOldMapping()
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) { it.toString() }) editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
// Write next reverse mapping for future cleanup // Write next reverse mapping for future cleanup
editor.putString(reverseKey, key) editor.putString(reverseKey, key)
@ -207,7 +217,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
preferences.edit() preferences.edit()
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1) .putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
.putInt(getInputAxisButtonKey(axis), value) .putInt(getInputAxisButtonKey(axis), value)
.putBoolean(getInputAxisInvertedKey(axis), inverted) .putBoolean(getInputAxisInvertedKey(axis),inverted)
// Write next reverse mapping for future cleanup // Write next reverse mapping for future cleanup
.putString(reverseKey, getInputAxisKey(axis)) .putString(reverseKey, getInputAxisKey(axis))
.apply() .apply()
@ -261,7 +271,8 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
companion object { companion object {
private const val INPUT_MAPPING_PREFIX = "InputMapping" private const val INPUT_MAPPING_PREFIX = "InputMapping"
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase() private fun toTitleCase(raw: String): String =
raw.replace("_", " ").lowercase()
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } } .split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
private const val BUTTON_NAME_L3 = "Button L3" private const val BUTTON_NAME_L3 = "Button L3"
@ -276,7 +287,8 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
LINUX_BTN_DPAD_RIGHT to "Dpad Right" LINUX_BTN_DPAD_RIGHT to "Dpad Right"
) )
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode] fun getButtonName(keyCode: Int): String =
buttonNameOverrides[keyCode]
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_")) ?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
private data class DefaultButtonMapping( private data class DefaultButtonMapping(
@ -284,7 +296,6 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
val hostKeyCode: Int, val hostKeyCode: Int,
val guestButtonCode: Int val guestButtonCode: Int
) )
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually. // Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
private data class DefaultAxisMapping( private data class DefaultAxisMapping(
val settingKey: String, val settingKey: String,
@ -295,153 +306,45 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
) )
private val xboxFaceButtonMappings = listOf( private val xboxFaceButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
Settings.KEY_BUTTON_A, DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
KeyEvent.KEYCODE_BUTTON_B, DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
NativeLibrary.ButtonType.BUTTON_A DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_Y
)
) )
private val nintendoFaceButtonMappings = listOf( private val nintendoFaceButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
Settings.KEY_BUTTON_A, DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
KeyEvent.KEYCODE_BUTTON_A, DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
NativeLibrary.ButtonType.BUTTON_A DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
) )
private val commonButtonMappings = listOf( private val commonButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
Settings.KEY_BUTTON_L, DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
KeyEvent.KEYCODE_BUTTON_L1, DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
NativeLibrary.ButtonType.TRIGGER_L DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
), DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
Settings.KEY_BUTTON_R,
KeyEvent.KEYCODE_BUTTON_R1,
NativeLibrary.ButtonType.TRIGGER_R
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZL,
KeyEvent.KEYCODE_BUTTON_L2,
NativeLibrary.ButtonType.BUTTON_ZL
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZR,
KeyEvent.KEYCODE_BUTTON_R2,
NativeLibrary.ButtonType.BUTTON_ZR
),
DefaultButtonMapping(
Settings.KEY_BUTTON_SELECT,
KeyEvent.KEYCODE_BUTTON_SELECT,
NativeLibrary.ButtonType.BUTTON_SELECT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_START,
NativeLibrary.ButtonType.BUTTON_START
)
) )
private val dpadButtonMappings = listOf( private val dpadButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
Settings.KEY_BUTTON_UP, DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
KeyEvent.KEYCODE_DPAD_UP, DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
NativeLibrary.ButtonType.DPAD_UP DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
KeyEvent.KEYCODE_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
KeyEvent.KEYCODE_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
) )
private val stickAxisMappings = listOf( private val stickAxisMappings = listOf(
DefaultAxisMapping( DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
MotionEvent.AXIS_X, DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
NativeLibrary.ButtonType.STICK_LEFT, DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_Z,
NativeLibrary.ButtonType.STICK_C,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RZ,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
) )
private val dpadAxisMappings = listOf( private val dpadAxisMappings = listOf(
DefaultAxisMapping( DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
Settings.KEY_DPAD_AXIS_HORIZONTAL, DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
MotionEvent.AXIS_HAT_X,
NativeLibrary.ButtonType.DPAD,
0,
false
),
DefaultAxisMapping(
Settings.KEY_DPAD_AXIS_VERTICAL,
MotionEvent.AXIS_HAT_Y,
NativeLibrary.ButtonType.DPAD,
1,
false
)
) )
// Nintendo Switch Joy-Con specific mappings. // Nintendo Switch Joy-Con specific mappings.
@ -466,84 +369,28 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
// This is different from both the standard Xbox table (full swap) and the // This is different from both the standard Xbox table (full swap) and the
// Nintendo table (no swap). // Nintendo table (no swap).
private val joyconFaceButtonMappings = listOf( private val joyconFaceButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
Settings.KEY_BUTTON_A, DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
KeyEvent.KEYCODE_BUTTON_B, DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
NativeLibrary.ButtonType.BUTTON_A DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
) )
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN // Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
private val joyconDpadButtonMappings = listOf( private val joyconDpadButtonMappings = listOf(
DefaultButtonMapping( DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
Settings.KEY_BUTTON_UP, DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
LINUX_BTN_DPAD_UP, DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
NativeLibrary.ButtonType.DPAD_UP DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
LINUX_BTN_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
LINUX_BTN_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
LINUX_BTN_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
) )
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY // Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to // (not Z/RZ like most controllers). The horizontal axis is inverted relative to
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth. // the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
private val joyconStickAxisMappings = listOf( private val joyconStickAxisMappings = listOf(
DefaultAxisMapping( DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
MotionEvent.AXIS_X, DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
NativeLibrary.ButtonType.STICK_LEFT, DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_RX,
NativeLibrary.ButtonType.STICK_C,
0,
true
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RY,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
) )
/** /**
@ -570,11 +417,9 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
} }
private val allBindingKeys: Set<String> by lazy { private val allBindingKeys: Set<String> by lazy {
( (Settings.buttonKeys + Settings.triggerKeys +
Settings.buttonKeys + Settings.triggerKeys +
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys + Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
Settings.dPadButtonKeys Settings.dPadButtonKeys).toSet()
).toSet()
} }
fun clearAllBindings() { fun clearAllBindings() {
@ -620,11 +465,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
* false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT) * false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT)
*/ */
fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) { fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) {
val faceButtons = if (isNintendoLayout) { val faceButtons = if (isNintendoLayout) nintendoFaceButtonMappings else xboxFaceButtonMappings
nintendoFaceButtonMappings
} else {
xboxFaceButtonMappings
}
val buttonMappings = if (useAxisDpad) { val buttonMappings = if (useAxisDpad) {
faceButtons + commonButtonMappings faceButtons + commonButtonMappings
} else { } else {
@ -661,16 +502,15 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted) editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted)
val dir = if (mapping.orientation == 0) '+' else '-' val dir = if (mapping.orientation == 0) '+' else '-'
editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir") editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir")
@Suppress("ktlint:standard:max-line-length") val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
val reverseKey =
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
editor.putString(reverseKey, axisKey) editor.putString(reverseKey, axisKey)
} }
/** /**
* Returns the settings key for the specified Citra button code. * Returns the settings key for the specified Citra button code.
*/ */
private fun getButtonKey(buttonCode: Int): String = when (buttonCode) { private fun getButtonKey(buttonCode: Int): String =
when (buttonCode) {
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
@ -688,19 +528,16 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
else -> "" else -> ""
} }
/** /**
* Get the mutable set of int button values this key should map to given an event * Get the mutable set of int button values this key should map to given an event
*/ */
fun getButtonSet(keyCode: KeyEvent): MutableSet<Int> { fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
val key = getInputButtonKey(keyCode) val key = getInputButtonKey(keyCode)
val preferences = PreferenceManager.getDefaultSharedPreferences( val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
CitraApplication.appContext
)
var buttonCodes = try { var buttonCodes = try {
preferences.getStringSet(key, mutableSetOf<String>()) preferences.getStringSet(key, mutableSetOf<String>())
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
val prefInt = preferences.getInt(key, -1) val prefInt = preferences.getInt(key, -1);
val migratedSet = if (prefInt != -1) { val migratedSet = if (prefInt != -1) {
mutableSetOf(prefInt.toString()) mutableSetOf(prefInt.toString())
} else { } else {
@ -712,17 +549,15 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet() return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
} }
private fun getInputButtonKey(keyId: Int): String = private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
"${INPUT_MAPPING_PREFIX}_HostAxis_$keyId"
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */ /** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
fun getInputButtonKey(event: KeyEvent): String = fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
getInputButtonKey(translateEventToKeyId(event))
/** /**
* Helper function to get the settings key for an gamepad axis. * Helper function to get the settings key for an gamepad axis.
*/ */
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_$axis" fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${axis}"
/** /**
* Helper function to get the settings key for an gamepad axis button (stick or trigger). * Helper function to get the settings key for an gamepad axis button (stick or trigger).
@ -740,6 +575,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
fun getInputAxisOrientationKey(axis: Int): String = fun getInputAxisOrientationKey(axis: Int): String =
"${getInputAxisKey(axis)}_GuestOrientation" "${getInputAxisKey(axis)}_GuestOrientation"
/** /**
* This function translates a keyEvent into an "keyid" * This function translates a keyEvent into an "keyid"
* This key id is either the keyCode from the event, or * This key id is either the keyCode from the event, or
@ -749,10 +585,12 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
* This handles keys like the media-keys on google statia-controllers * This handles keys like the media-keys on google statia-controllers
* that don't have a conventional "mapping" and report as "unknown" * that don't have a conventional "mapping" and report as "unknown"
*/ */
fun translateEventToKeyId(event: KeyEvent): Int = if (event.keyCode == 0) { fun translateEventToKeyId(event: KeyEvent): Int {
return if (event.keyCode == 0) {
event.scanCode event.scanCode
} else { } else {
event.keyCode event.keyCode
} }
} }
}
} }

View file

@ -3,8 +3,6 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.IntListSetting
class MultiChoiceSetting( class MultiChoiceSetting(
@ -15,9 +13,7 @@ class MultiChoiceSetting(
val valuesId: Int, val valuesId: Int,
val key: String? = null, val key: String? = null,
val defaultValue: List<Int>? = null, val defaultValue: List<Int>? = null,
override var isEnabled: Boolean = true, override var isEnabled: Boolean = true
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_MULTI_CHOICE override val type = TYPE_MULTI_CHOICE
@ -29,7 +25,7 @@ class MultiChoiceSetting(
try { try {
val setting = setting as IntListSetting val setting = setting as IntListSetting
return setting.list return setting.list
} catch (_: ClassCastException) { }catch (_: ClassCastException) {
} }
return defaultValue!! return defaultValue!!
} }
@ -46,4 +42,5 @@ class MultiChoiceSetting(
intSetting.list = selection intSetting.list = selection
return intSetting return intSetting
} }
} }

View file

@ -4,8 +4,7 @@
package org.citra.citra_emu.features.settings.model.view package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractSetting
@ -31,9 +30,6 @@ abstract class SettingsItem(
open var isEnabled: Boolean = true open var isEnabled: Boolean = true
@StringRes open var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
val isActive: Boolean val isActive: Boolean
get() { get() {
return this.isEditable && this.isEnabled return this.isEditable && this.isEnabled

Some files were not shown because too many files have changed in this diff Show more