mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-19 17:49:24 -04:00
Compare commits
89 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e43e451d1 | ||
|
|
0a269688a3 | ||
|
|
3a77813821 | ||
|
|
fbeb53c25f | ||
|
|
1137d530b0 | ||
|
|
04f3a93854 | ||
|
|
28960bf166 | ||
|
|
176cd5cf14 | ||
|
|
735c63174c | ||
|
|
f015a208e5 | ||
|
|
deb130cdb7 | ||
|
|
4687226fa5 | ||
|
|
bd733284a3 | ||
|
|
cd1ef53569 | ||
|
|
7786dae94d | ||
|
|
5ea4bb2511 | ||
|
|
22d7be4e3c | ||
|
|
60e44ff049 | ||
|
|
d70099278a | ||
|
|
1afa24387f | ||
|
|
bfaaaee10d | ||
|
|
2940069fe2 | ||
|
|
a2a20fcc65 | ||
|
|
b4bda9d7f5 | ||
|
|
609ed7caf3 | ||
|
|
9ffb39eab2 | ||
|
|
792cde35ca | ||
|
|
398b13abc2 | ||
|
|
823901a364 | ||
|
|
8a6d597dec | ||
|
|
3dc357a8c9 | ||
|
|
379649dbce | ||
|
|
c03248f158 | ||
|
|
4867bb2e2b | ||
|
|
4e4c7e687b | ||
|
|
56f738eb06 | ||
|
|
b1e537a485 | ||
|
|
59da460177 | ||
|
|
8bdb60a6e1 | ||
|
|
383a28795e | ||
|
|
725544f3b4 | ||
|
|
135f10320a | ||
|
|
0b7114cbf8 | ||
|
|
95d42cb40a | ||
|
|
f0bc64d967 | ||
|
|
ad8526c4cf | ||
|
|
ae7d7dca1f | ||
|
|
4a4b75b0de | ||
|
|
b186b04995 | ||
|
|
ab6896a2ca | ||
|
|
e11f3da493 | ||
|
|
8ffb94b06c | ||
|
|
267887d7a9 | ||
|
|
778ca369cd | ||
|
|
dbe7fd979f | ||
|
|
1c7c7a5f1b | ||
|
|
bf59d26c48 | ||
|
|
652fc02175 | ||
|
|
854e198196 | ||
|
|
5ecd402811 | ||
|
|
0ce2a30d20 | ||
|
|
644a181aff | ||
|
|
422c7865a3 | ||
|
|
ca99574700 | ||
|
|
f902010f04 | ||
|
|
929a51afc6 | ||
|
|
260f08c497 | ||
|
|
921ea178b9 | ||
|
|
b540725090 | ||
|
|
5ddbaeae23 | ||
|
|
b081f800a4 | ||
|
|
76a71d76d4 | ||
|
|
83eef0012e | ||
|
|
ec6a0dd1c8 | ||
|
|
eb498e5ecd | ||
|
|
4fa793b945 | ||
|
|
5d84dfed91 | ||
|
|
eee7f076ee | ||
|
|
996abd1eaf | ||
|
|
91128d6625 | ||
|
|
37b6c91de6 | ||
|
|
b6c54ac8c7 | ||
|
|
b3ee2d8ac5 | ||
|
|
9701a3d874 | ||
|
|
a276623dbb | ||
|
|
2fff086e81 | ||
|
|
5bc58c78ed | ||
|
|
0fe6a8c7df | ||
|
|
d4b5633cf0 |
348 changed files with 19309 additions and 11994 deletions
|
|
@ -12,6 +12,9 @@ fi
|
||||||
|
|
||||||
echo "Tag name is: $TAG_NAME"
|
echo "Tag name is: $TAG_NAME"
|
||||||
|
|
||||||
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
|
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
||||||
|
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
||||||
|
|
||||||
|
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,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_QT_TRANSLATION=ON \
|
|
||||||
-DENABLE_ROOM_STANDALONE=OFF \
|
-DENABLE_ROOM_STANDALONE=OFF \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
-DENABLE_DISCORD_RPC=ON \
|
||||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||||
ninja
|
ninja
|
||||||
strip -s bin/Release/*
|
strip -s bin/Release/*
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
||||||
cmake ../.. -GNinja \
|
cmake ../.. -GNinja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
||||||
-DENABLE_QT_TRANSLATION=ON \
|
|
||||||
-DENABLE_ROOM_STANDALONE=OFF \
|
-DENABLE_ROOM_STANDALONE=OFF \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
-DENABLE_DISCORD_RPC=ON \
|
||||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||||
ninja
|
ninja
|
||||||
ninja bundle
|
ninja bundle
|
||||||
|
|
|
||||||
25
.ci/mxe.sh
Executable file
25
.ci/mxe.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/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
|
||||||
|
|
@ -36,10 +36,10 @@ function pack_artifacts() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create .zip/.tar.gz
|
# Create .zip/.tar.gz
|
||||||
if [ "$OS" = "windows" ]; then
|
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; 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" ]; then
|
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; 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
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ gcc -v
|
||||||
tx --version
|
tx --version
|
||||||
|
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
||||||
make translation
|
make translation
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ 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_DISCORD_RPC=ON \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
|
||||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||||
ninja
|
ninja
|
||||||
ninja bundle
|
ninja bundle
|
||||||
|
|
|
||||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
- [ ] 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.
|
||||||
|
---------
|
||||||
|
|
||||||
---
|
---
|
||||||
<!--
|
<!--
|
||||||
|
|
|
||||||
202
.github/workflows/build.yml
vendored
202
.github/workflows/build.yml
vendored
|
|
@ -7,21 +7,41 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
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
|
||||||
|
|
@ -39,14 +59,15 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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' }}
|
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
|
|
@ -64,12 +85,27 @@ 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
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
|
||||||
|
|
@ -87,11 +123,11 @@ jobs:
|
||||||
OS: linux
|
OS: linux
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
|
|
@ -106,13 +142,15 @@ 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: macos
|
OS: macos
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v4
|
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||||
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ github.sha }}
|
||||||
|
|
@ -136,18 +174,43 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PACK_INDIVIDUALLY: 1
|
PACK_INDIVIDUALLY: 1
|
||||||
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}
|
name: ${{ env.OS }}
|
||||||
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:
|
||||||
target: ["msvc", "msys2"]
|
include:
|
||||||
|
- 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}
|
||||||
|
|
@ -155,14 +218,16 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v4
|
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||||
|
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 }}
|
||||||
|
|
@ -170,7 +235,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: ilammy/msvc-dev-cmd@v1
|
uses: azahar-emu/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
|
||||||
|
|
@ -191,34 +256,62 @@ 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@v3
|
uses: crazy-max/ghaction-chocolatey@v4
|
||||||
with:
|
with:
|
||||||
args: install ptime wget
|
args: install ptime wget
|
||||||
- name: Install NSIS
|
- name: Install NSIS
|
||||||
if: ${{ github.ref_type == 'tag' }}
|
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||||
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
|
- name: Build (Native)
|
||||||
|
if: ${{ matrix.target != 'mxe' }}
|
||||||
run: ./.ci/windows.sh
|
run: ./.ci/windows.sh
|
||||||
- name: Generate installer
|
- name: Build (MXE)
|
||||||
if: ${{ github.ref_type == 'tag' }}
|
if: ${{ matrix.target == 'mxe' }}
|
||||||
|
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
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
|
||||||
|
|
@ -230,17 +323,18 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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' }}
|
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
|
|
@ -278,23 +372,48 @@ 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
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:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
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:
|
||||||
image: docker:dind
|
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
||||||
|
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
||||||
|
env:
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
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
|
||||||
|
|
@ -303,8 +422,23 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
mv build/*.dockerimage artifacts/
|
mv build/*.dockerimage artifacts/
|
||||||
- name: Upload
|
- name: Generate SBOM
|
||||||
uses: actions/upload-artifact@v4
|
if: ${{ github.ref_type == 'tag' }}
|
||||||
|
uses: anchore/sbom-action@v0
|
||||||
with:
|
with:
|
||||||
name: docker
|
image: ${{ env.DOCKER_IMAGE_PATH }}
|
||||||
|
format: spdx-json
|
||||||
|
output-file: artifacts/docker-room.spdx.json
|
||||||
|
upload-artifact: false
|
||||||
|
- name: Upload
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: docker-${{ env.TARGET }}
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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@v7
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
|
|
|
||||||
|
|
@ -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@v7
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
|
|
|
||||||
14
.github/workflows/format.yml
vendored
14
.github/workflows/format.yml
vendored
|
|
@ -13,10 +13,22 @@ jobs:
|
||||||
image: opensauce04/azahar-build-environment:latest
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
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
|
||||||
178
.github/workflows/libretro.yml
vendored
178
.github/workflows/libretro.yml
vendored
|
|
@ -11,8 +11,13 @@ on:
|
||||||
env:
|
env:
|
||||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
attestations: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
android:
|
libretro-android:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
OS: android
|
OS: android
|
||||||
|
|
@ -23,7 +28,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@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set tag name
|
- name: Set tag name
|
||||||
|
|
@ -32,6 +37,10 @@ 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"
|
||||||
|
|
@ -41,14 +50,33 @@ 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
linux:
|
./*.zip
|
||||||
|
./*.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
|
||||||
|
|
@ -57,21 +85,44 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
windows:
|
./*.zip
|
||||||
|
./*.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
|
||||||
|
|
@ -79,10 +130,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: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
|
||||||
EXTRA_PATH: bin/Release
|
EXTRA_PATH: bin/Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build in cross-container
|
- name: Build in cross-container
|
||||||
|
|
@ -94,17 +145,36 @@ 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
macos:
|
./*.zip
|
||||||
|
./*.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:
|
||||||
|
|
@ -114,7 +184,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@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
|
|
@ -123,14 +193,33 @@ 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
ios:
|
./*.zip
|
||||||
|
./*.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
|
||||||
|
|
@ -139,21 +228,40 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
tvos:
|
./*.zip
|
||||||
|
./*.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
|
||||||
|
|
@ -162,17 +270,35 @@ 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@v4
|
- uses: actions/checkout@v6
|
||||||
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@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: ./*.zip
|
path: |
|
||||||
|
./*.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
|
||||||
|
|
|
||||||
2
.github/workflows/license-header.yml
vendored
2
.github/workflows/license-header.yml
vendored
|
|
@ -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@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Fetch master branch
|
- name: Fetch master branch
|
||||||
|
|
|
||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9.1.0
|
- uses: actions/stale@v10.2.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-issue-stale: 90
|
days-before-issue-stale: 90
|
||||||
|
|
|
||||||
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
|
|
@ -7,10 +7,10 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
transifex:
|
transifex:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: opensauce04/azahar-build-environment:transifex
|
container: opensauce04/azahar-build-environment:latest
|
||||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -17,7 +17,8 @@ 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/
|
||||||
|
|
@ -60,3 +61,6 @@ VULKAN_SDK/
|
||||||
# Version info files
|
# Version info files
|
||||||
GIT-COMMIT
|
GIT-COMMIT
|
||||||
GIT-TAG
|
GIT-TAG
|
||||||
|
|
||||||
|
# verify-release.sh downloads
|
||||||
|
verify/
|
||||||
14
.gitmodules
vendored
14
.gitmodules
vendored
|
|
@ -55,18 +55,12 @@
|
||||||
[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/kcat/openal-soft
|
url = https://github.com/azahar-emu/openal-soft
|
||||||
[submodule "glslang"]
|
[submodule "glslang"]
|
||||||
path = externals/glslang
|
path = externals/glslang
|
||||||
url = https://github.com/KhronosGroup/glslang
|
url = https://github.com/KhronosGroup/glslang
|
||||||
|
|
@ -106,3 +100,9 @@
|
||||||
[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
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ 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)
|
||||||
|
|
@ -25,6 +28,15 @@ 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.
|
||||||
|
|
@ -95,7 +107,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_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
||||||
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)
|
||||||
|
|
@ -109,7 +121,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" OFF)
|
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON)
|
||||||
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)
|
||||||
|
|
@ -118,6 +130,7 @@ 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)
|
||||||
|
|
@ -129,7 +142,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENA
|
||||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
||||||
|
|
||||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF)
|
||||||
|
|
||||||
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
|
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
|
||||||
|
|
||||||
|
|
@ -398,13 +411,21 @@ 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} ${MOLTENVK_LIBRARY})
|
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
|
||||||
|
|
||||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||||
if (NOT USE_SYSTEM_MOLTENVK)
|
if (USE_SYSTEM_MOLTENVK)
|
||||||
download_moltenvk()
|
|
||||||
endif()
|
|
||||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||||
|
else()
|
||||||
|
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()
|
||||||
|
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
|
||||||
|
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()
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,10 @@ 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()
|
||||||
|
|
@ -273,6 +277,13 @@ 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}
|
||||||
|
|
@ -283,6 +294,7 @@ 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.
|
||||||
|
|
@ -293,6 +305,11 @@ 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.
|
||||||
|
|
@ -331,6 +348,7 @@ 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}")
|
||||||
|
|
|
||||||
|
|
@ -171,27 +171,16 @@ 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 ${MOLTENVK_DIR})
|
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||||
if (NOT EXISTS ${MOLTENVK_TAR})
|
if (NOT EXISTS ${MOLTENVK_TAR})
|
||||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/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)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ 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"
|
||||||
|
|
@ -48,6 +49,7 @@ 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"
|
||||||
|
|
@ -107,6 +109,7 @@ 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"
|
||||||
|
|
@ -115,6 +118,7 @@ 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"
|
||||||
|
|
@ -234,6 +238,7 @@ 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 +0,0 @@
|
||||||
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**
|
|
||||||
2
dist/languages/.tx/config
vendored
2
dist/languages/.tx/config
vendored
|
|
@ -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 = 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, es_419:b+es+419
|
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
|
||||||
|
|
|
||||||
825
dist/languages/ca_ES_valencia.ts
vendored
825
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load diff
813
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
813
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load diff
831
dist/languages/de.ts
vendored
831
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
827
dist/languages/el.ts
vendored
827
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load diff
827
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
827
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load diff
1273
dist/languages/es_419.ts
vendored
1273
dist/languages/es_419.ts
vendored
File diff suppressed because it is too large
Load diff
811
dist/languages/fi.ts
vendored
811
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load diff
825
dist/languages/fr.ts
vendored
825
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
813
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
813
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load diff
811
dist/languages/id.ts
vendored
811
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load diff
833
dist/languages/it.ts
vendored
833
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
833
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
833
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
File diff suppressed because it is too large
Load diff
815
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
815
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
File diff suppressed because it is too large
Load diff
813
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
813
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
File diff suppressed because it is too large
Load diff
837
dist/languages/nb.ts
vendored
837
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
813
dist/languages/nl.ts
vendored
813
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
833
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
833
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load diff
847
dist/languages/pt_BR.ts
vendored
847
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
815
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
815
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
File diff suppressed because it is too large
Load diff
819
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
819
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
File diff suppressed because it is too large
Load diff
829
dist/languages/sv.ts
vendored
829
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load diff
821
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
821
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
File diff suppressed because it is too large
Load diff
813
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
813
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load diff
825
dist/languages/zh_CN.ts
vendored
825
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
811
dist/languages/zh_TW.ts
vendored
811
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -4,23 +4,19 @@
|
||||||
# --- Builder ----------------
|
# --- Builder ----------------
|
||||||
FROM opensauce04/azahar-build-environment:latest AS builder
|
FROM opensauce04/azahar-build-environment:latest AS builder
|
||||||
|
|
||||||
COPY . /var/azahar-src
|
RUN mkdir /var/azahar-src/
|
||||||
|
COPY ./ /var/azahar-src/
|
||||||
|
|
||||||
RUN mkdir builddir && cd builddir && \
|
RUN mkdir ./builddir/
|
||||||
cmake /var/azahar-src -G Ninja \
|
WORKDIR ./builddir/
|
||||||
|
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
|
||||||
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
|
RUN ninja
|
||||||
-DENABLE_VULKAN=OFF \
|
RUN mv ./bin/Release/azahar-room /usr/local/bin/
|
||||||
-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
|
||||||
|
|
|
||||||
51
externals/CMakeLists.txt
vendored
51
externals/CMakeLists.txt
vendored
|
|
@ -57,7 +57,7 @@ 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)
|
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||||
include(Catch)
|
include(Catch)
|
||||||
|
|
@ -69,17 +69,9 @@ 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 "")
|
||||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
|
||||||
add_subdirectory(cryptopp-cmake)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# dds-ktx
|
# dds-ktx
|
||||||
|
|
@ -142,7 +134,7 @@ endif()
|
||||||
|
|
||||||
# getopt
|
# getopt
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
add_subdirectory(getopt)
|
add_subdirectory(getopt EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# inih
|
# inih
|
||||||
|
|
@ -151,7 +143,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)
|
add_subdirectory(inih EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# MicroProfile
|
# MicroProfile
|
||||||
|
|
@ -174,7 +166,7 @@ if (NOT MSVC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Open Source Archives
|
# Open Source Archives
|
||||||
add_subdirectory(open_source_archives)
|
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
# faad2
|
# faad2
|
||||||
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||||
|
|
@ -213,12 +205,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)
|
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# libusb
|
# libusb
|
||||||
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
||||||
add_subdirectory(libusb)
|
add_subdirectory(libusb EXCLUDE_FROM_ALL)
|
||||||
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()
|
||||||
|
|
@ -262,7 +254,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)
|
add_subdirectory(enet EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(enet INTERFACE ./enet/include)
|
target_include_directories(enet INTERFACE ./enet/include)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -284,7 +276,7 @@ if (ENABLE_CUBEB)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
if (USE_DISCORD_PRESENCE)
|
if (ENABLE_DISCORD_RPC)
|
||||||
# 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)
|
||||||
|
|
@ -327,12 +319,8 @@ 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)
|
||||||
endif()
|
else()
|
||||||
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/")
|
||||||
|
|
@ -374,7 +362,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)
|
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# cpp-jwt
|
# cpp-jwt
|
||||||
|
|
@ -397,13 +385,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)
|
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
|
||||||
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)
|
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -414,6 +402,9 @@ 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 "")
|
||||||
|
|
@ -429,7 +420,7 @@ endif()
|
||||||
# OpenGL dependencies
|
# OpenGL dependencies
|
||||||
if (ENABLE_OPENGL)
|
if (ENABLE_OPENGL)
|
||||||
# Glad
|
# Glad
|
||||||
add_subdirectory(glad)
|
add_subdirectory(glad EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Vulkan dependencies
|
# Vulkan dependencies
|
||||||
|
|
@ -469,7 +460,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)
|
add_subdirectory(glslang EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# sirit
|
# sirit
|
||||||
|
|
@ -508,12 +499,16 @@ if (ENABLE_VULKAN)
|
||||||
# prefix location. -OS
|
# prefix location. -OS
|
||||||
target_include_directories(vulkan-headers INTERFACE
|
target_include_directories(vulkan-headers INTERFACE
|
||||||
/usr/pkg/share/x11-links/include)
|
/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()
|
endif()
|
||||||
|
|
||||||
# adrenotools
|
# adrenotools
|
||||||
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
||||||
add_subdirectory(libadrenotools)
|
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
2
externals/cryptopp
vendored
2
externals/cryptopp
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e
|
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab
|
||||||
1
externals/cryptopp-cmake
vendored
1
externals/cryptopp-cmake
vendored
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c
|
|
||||||
2
externals/discord-rpc
vendored
2
externals/discord-rpc
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a
|
Subproject commit 049826c3f42875c2f9599552f425e76435789cc7
|
||||||
1
externals/dllwalker
vendored
Submodule
1
externals/dllwalker
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e
|
||||||
2
externals/openal-soft
vendored
2
externals/openal-soft
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727
|
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906
|
||||||
|
|
@ -203,6 +203,7 @@ 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()
|
||||||
|
|
||||||
|
|
|
||||||
20
src/android/.editorconfig
Normal file
20
src/android/.editorconfig
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
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
|
||||||
3
src/android/.gitignore
vendored
3
src/android/.gitignore
vendored
|
|
@ -34,7 +34,8 @@ 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.
|
||||||
|
|
|
||||||
14
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
14
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<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>
|
||||||
|
|
@ -12,6 +12,7 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,12 +25,11 @@ 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.1.12297006"
|
ndkVersion = "27.3.13750724"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
@ -80,7 +80,9 @@ 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 sizes
|
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page
|
||||||
|
// sizes
|
||||||
|
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +127,8 @@ android {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
versionNameSuffix = "-debug"
|
versionNameSuffix = "-debug"
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
|
isShrinkResources = true
|
||||||
|
// TODO: ^- Does this actually do anything when isDebuggable is enabled? -OS
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isJniDebuggable = true
|
isJniDebuggable = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
|
|
@ -136,8 +139,10 @@ 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) without constantly tripping over years-old and seemingly harmless memory bugs.
|
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS)
|
||||||
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
|
// 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 on these devices.
|
||||||
register("relWithDebInfoLite") {
|
register("relWithDebInfoLite") {
|
||||||
initWith(getByName("relWithDebInfo"))
|
initWith(getByName("relWithDebInfo"))
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
|
@ -147,7 +152,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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +220,9 @@ 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("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip")
|
src(
|
||||||
|
"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)
|
||||||
}
|
}
|
||||||
|
|
@ -235,6 +242,11 @@ 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 {
|
||||||
|
|
@ -292,7 +304,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)
|
||||||
|
|
@ -306,5 +318,5 @@ android.applicationVariants.configureEach {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
|
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.GpuDriverHelper
|
import org.citra.citra_emu.utils.GraphicsUtil
|
||||||
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,6 +69,7 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,22 @@ 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
|
||||||
|
|
@ -42,7 +44,7 @@ object NativeLibrary {
|
||||||
/**
|
/**
|
||||||
* Default touchscreen device
|
* Default touchscreen device
|
||||||
*/
|
*/
|
||||||
const val TouchScreenDevice = "Touchscreen"
|
const val TOUCHSCREEN_DEVICE = "Touchscreen"
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||||
|
|
@ -134,10 +136,7 @@ object NativeLibrary {
|
||||||
*/
|
*/
|
||||||
external fun setUserDirectory(directory: String)
|
external fun setUserDirectory(directory: String)
|
||||||
|
|
||||||
data class InstalledGame(
|
data class InstalledGame(val path: String, val mediaType: Game.MediaType)
|
||||||
val path: String,
|
|
||||||
val mediaType: Game.MediaType
|
|
||||||
)
|
|
||||||
fun getInstalledGamePaths(): Array<InstalledGame> {
|
fun getInstalledGamePaths(): Array<InstalledGame> {
|
||||||
val games = getInstalledGamePathsImpl()
|
val games = getInstalledGamePathsImpl()
|
||||||
|
|
||||||
|
|
@ -252,9 +251,8 @@ 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 =
|
||||||
return uninstallTitle(titleId, mediaType.value)
|
uninstallTitle(titleId, mediaType.value)
|
||||||
}
|
|
||||||
|
|
||||||
external fun nativeFileExists(path: String): Boolean
|
external fun nativeFileExists(path: String): Boolean
|
||||||
|
|
||||||
|
|
@ -317,6 +315,12 @@ 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)
|
||||||
|
|
@ -341,11 +345,12 @@ object NativeLibrary {
|
||||||
return coreErrorAlertResult
|
return coreErrorAlertResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Keep
|
@Keep
|
||||||
@get:JvmStatic
|
@JvmStatic
|
||||||
val isPortraitMode: Boolean
|
fun isPortraitMode(): Boolean = (
|
||||||
get() = CitraApplication.appContext.resources.configuration.orientation ==
|
CitraApplication.appContext.resources.configuration.orientation ==
|
||||||
Configuration.ORIENTATION_PORTRAIT
|
Configuration.ORIENTATION_PORTRAIT
|
||||||
|
)
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
@ -439,7 +444,7 @@ object NativeLibrary {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
if (resultCode == CoreError.ShutdownRequested.value) {
|
||||||
emulationActivity.finish()
|
emulationActivity.finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -458,23 +463,57 @@ object NativeLibrary {
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
|
|
||||||
var captionId = R.string.loader_error_invalid_format
|
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||||
val result = requireArguments().getInt(RESULT_CODE)
|
val title: String
|
||||||
if (result == ErrorLoader_ErrorEncrypted) {
|
val message: String
|
||||||
captionId = R.string.loader_error_encrypted
|
when (coreError) {
|
||||||
|
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(captionId)
|
.setTitle(title)
|
||||||
.setMessage(
|
.setMessage(
|
||||||
Html.fromHtml(
|
Html.fromHtml(
|
||||||
if (result == ErrorArticDisconnected)
|
message,
|
||||||
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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -496,21 +535,6 @@ 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 ErrorN3DSApplication = 11
|
|
||||||
const val ShutdownRequested = 12
|
|
||||||
const val ErrorUnknown = 13
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -659,19 +683,17 @@ 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 =
|
||||||
return CompressStatus.fromValue(
|
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 =
|
||||||
return CompressStatus.fromValue(
|
CompressStatus.fromValue(
|
||||||
decompressFileNative(inputPath, outputPath)
|
decompressFileNative(inputPath, outputPath)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
|
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
|
||||||
|
|
||||||
|
|
@ -705,8 +727,7 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun openContentUri(path: String, openMode: String): Int =
|
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||||
} else {
|
} else {
|
||||||
FileUtil.openContentUri(path, openMode)
|
FileUtil.openContentUri(path, openMode)
|
||||||
|
|
@ -714,8 +735,7 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getFilesName(path: String): Array<String?> =
|
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
CitraApplication.documentsTree.getFilesName(path)
|
CitraApplication.documentsTree.getFilesName(path)
|
||||||
} else {
|
} else {
|
||||||
FileUtil.getFilesName(path)
|
FileUtil.getFilesName(path)
|
||||||
|
|
@ -745,10 +765,14 @@ 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(CitraApplication.appContext, storageIdString)
|
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
|
||||||
|
CitraApplication.appContext,
|
||||||
|
storageIdString
|
||||||
|
)
|
||||||
|
|
||||||
if (removablePath == null) {
|
if (removablePath == null) {
|
||||||
android.util.Log.e("NativeLibrary",
|
android.util.Log.e(
|
||||||
|
"NativeLibrary",
|
||||||
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -768,8 +792,7 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getSize(path: String): Long =
|
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
CitraApplication.documentsTree.getFileSize(path)
|
CitraApplication.documentsTree.getFileSize(path)
|
||||||
} else {
|
} else {
|
||||||
FileUtil.getFileSize(path)
|
FileUtil.getFileSize(path)
|
||||||
|
|
@ -781,8 +804,11 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fileExists(path: String): Boolean =
|
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
|
||||||
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)
|
||||||
|
|
@ -790,8 +816,7 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isDirectory(path: String): Boolean =
|
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
CitraApplication.documentsTree.isDirectory(path)
|
CitraApplication.documentsTree.isDirectory(path)
|
||||||
} else {
|
} else {
|
||||||
FileUtil.isDirectory(path)
|
FileUtil.isDirectory(path)
|
||||||
|
|
@ -803,8 +828,7 @@ object NativeLibrary {
|
||||||
sourcePath: String,
|
sourcePath: String,
|
||||||
destinationParentPath: String,
|
destinationParentPath: String,
|
||||||
destinationFilename: String
|
destinationFilename: String
|
||||||
): Boolean =
|
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
|
||||||
if (FileUtil.isNativePath(sourcePath) &&
|
|
||||||
FileUtil.isNativePath(destinationParentPath)
|
FileUtil.isNativePath(destinationParentPath)
|
||||||
) {
|
) {
|
||||||
CitraApplication.documentsTree
|
CitraApplication.documentsTree
|
||||||
|
|
@ -850,19 +874,35 @@ object NativeLibrary {
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deleteDocument(path: String): Boolean =
|
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
CitraApplication.documentsTree.deleteDocument(path)
|
CitraApplication.documentsTree.deleteDocument(path)
|
||||||
} else {
|
} else {
|
||||||
FileUtil.deleteDocument(path)
|
FileUtil.deleteDocument(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CoreError {
|
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
|
||||||
ErrorSystemFiles,
|
Success(0, R.string.core_error_success),
|
||||||
ErrorSavestate,
|
ErrorNotInitialized(1, R.string.core_error_not_initialized),
|
||||||
ErrorArticDisconnected,
|
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||||
ErrorN3DSApplication,
|
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||||
ErrorUnknown
|
ErrorLoader(4, R.string.core_error_loader),
|
||||||
|
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 {
|
||||||
|
|
@ -913,7 +953,11 @@ object NativeLibrary {
|
||||||
const val MESSAGE = "message"
|
const val MESSAGE = "message"
|
||||||
const val CAN_CONTINUE = "canContinue"
|
const val CAN_CONTINUE = "canContinue"
|
||||||
|
|
||||||
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
|
fun newInstance(
|
||||||
|
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)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,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 +46,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 +64,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
|
||||||
private lateinit var secondaryDisplay: SecondaryDisplay
|
lateinit var secondaryDisplayManager: SecondaryDisplay
|
||||||
|
|
||||||
private val onShutdown = Runnable {
|
private val onShutdown = Runnable {
|
||||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||||
|
|
@ -102,8 +102,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
secondaryDisplay = SecondaryDisplay(this)
|
secondaryDisplayManager = SecondaryDisplay(this)
|
||||||
secondaryDisplay.updateDisplay()
|
secondaryDisplayManager.updateDisplay()
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||||
|
|
@ -188,7 +188,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
secondaryDisplay.releasePresentation()
|
secondaryDisplayManager.releasePresentation()
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,7 +199,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
|
|
||||||
public override fun onRestart() {
|
public override fun onRestart() {
|
||||||
super.onRestart()
|
super.onRestart()
|
||||||
secondaryDisplay.updateDisplay()
|
secondaryDisplayManager.updateDisplay()
|
||||||
NativeLibrary.reloadCameraDevices()
|
NativeLibrary.reloadCameraDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
NativeLibrary.playTimeManagerStop()
|
NativeLibrary.playTimeManagerStop()
|
||||||
isEmulationRunning = false
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
secondaryDisplay.releasePresentation()
|
secondaryDisplayManager.releasePresentation()
|
||||||
secondaryDisplay.releaseVD()
|
secondaryDisplayManager.releaseVD()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
@ -334,11 +334,13 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +360,8 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,16 +390,19 @@ 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(InputBindingSetting.getInputAxisInvertedKey(axis),false);
|
val inverted = preferences.getBoolean(
|
||||||
|
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 -> {
|
||||||
|
|
@ -450,7 +456,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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.TRIGGER_L,
|
NativeLibrary.ButtonType.TRIGGER_L,
|
||||||
if (isTriggerPressedL) {
|
if (isTriggerPressedL) {
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
|
@ -461,7 +467,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
if (isTriggerPressedRMapped) {
|
if (isTriggerPressedRMapped) {
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.TRIGGER_R,
|
NativeLibrary.ButtonType.TRIGGER_R,
|
||||||
if (isTriggerPressedR) {
|
if (isTriggerPressedR) {
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
|
@ -472,7 +478,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
if (isTriggerPressedZLMapped) {
|
if (isTriggerPressedZLMapped) {
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.BUTTON_ZL,
|
NativeLibrary.ButtonType.BUTTON_ZL,
|
||||||
if (isTriggerPressedZL) {
|
if (isTriggerPressedZL) {
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
|
@ -483,7 +489,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
if (isTriggerPressedZRMapped) {
|
if (isTriggerPressedZRMapped) {
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.BUTTON_ZR,
|
NativeLibrary.ButtonType.BUTTON_ZR,
|
||||||
if (isTriggerPressedZR) {
|
if (isTriggerPressedZR) {
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
|
@ -496,72 +502,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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
NativeLibrary.ButtonState.RELEASED
|
NativeLibrary.ButtonState.RELEASED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
NativeLibrary.ButtonState.RELEASED
|
NativeLibrary.ButtonState.RELEASED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_UP,
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
NativeLibrary.ButtonState.RELEASED
|
NativeLibrary.ButtonState.RELEASED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_UP,
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
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.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_UP,
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
NativeLibrary.ButtonState.RELEASED
|
NativeLibrary.ButtonState.RELEASED
|
||||||
)
|
)
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
NativeLibrary.TouchScreenDevice,
|
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||||
NativeLibrary.ButtonState.PRESSED
|
NativeLibrary.ButtonState.PRESSED
|
||||||
)
|
)
|
||||||
|
|
@ -573,7 +579,9 @@ 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, applicationContext, listOf<String>("bin")
|
result,
|
||||||
|
applicationContext,
|
||||||
|
listOf<String>("bin")
|
||||||
) ?: return@registerForActivityResult
|
) ?: return@registerForActivityResult
|
||||||
if (BuildUtil.isGooglePlayBuild) {
|
if (BuildUtil.isGooglePlayBuild) {
|
||||||
onAmiiboSelected(selectedFiles[0])
|
onAmiiboSelected(selectedFiles[0])
|
||||||
|
|
@ -590,14 +598,12 @@ 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 {
|
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
|
||||||
return instance?.isEmulationRunning ?: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,15 +105,11 @@ 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 {
|
): Boolean = oldItem.first == newItem.first
|
||||||
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 {
|
): Boolean = oldItem.second == newItem.second
|
||||||
return oldItem.second == newItem.second
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,25 @@
|
||||||
|
|
||||||
package org.citra.citra_emu.adapters
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
import android.graphics.drawable.Icon
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.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.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.PopupMenu
|
||||||
|
import android.widget.TextView
|
||||||
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
|
||||||
|
|
@ -29,24 +30,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 androidx.lifecycle.lifecycleScope
|
|
||||||
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.launch
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.citra.citra_emu.HomeNavigationDirections
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.HomeNavigationDirections
|
||||||
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,10 +65,12 @@ 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: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
|
private val onRequestCompressOrDecompress: (
|
||||||
) :
|
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
|
||||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
)? = null
|
||||||
View.OnClickListener, View.OnLongClickListener {
|
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||||
|
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
|
||||||
|
|
@ -212,7 +214,8 @@ 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 = if (preferences.getString("insertedCartridge", "") != game.path) {
|
binding.imageCartridge.visibility =
|
||||||
|
if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||||
View.GONE
|
View.GONE
|
||||||
} else {
|
} else {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
|
|
@ -220,7 +223,9 @@ class GameAdapter(
|
||||||
|
|
||||||
val backgroundColorId =
|
val backgroundColorId =
|
||||||
if (
|
if (
|
||||||
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
|
isValidGame(
|
||||||
|
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
R.attr.colorSurface
|
R.attr.colorSurface
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -260,16 +265,45 @@ class GameAdapter(
|
||||||
val extraDir: String
|
val extraDir: String
|
||||||
)
|
)
|
||||||
private fun getGameDirectories(game: Game): GameDirectories {
|
private fun getGameDirectories(game: Game): GameDirectories {
|
||||||
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
val basePath =
|
||||||
|
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
||||||
return GameDirectories(
|
return GameDirectories(
|
||||||
gameDir = game.path.substringBeforeLast("/"),
|
gameDir = game.path.substringBeforeLast("/"),
|
||||||
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
|
saveDir =
|
||||||
|
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 { it.isNotEmpty() }.joinToString("/"),
|
appDir = game.path.substringBeforeLast("/").split("/").filter {
|
||||||
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
it.isNotEmpty()
|
||||||
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
}.joinToString("/"),
|
||||||
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
|
dlcDir =
|
||||||
|
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')}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,13 +333,36 @@ class GameAdapter(
|
||||||
.setType("*/*")
|
.setType("*/*")
|
||||||
|
|
||||||
val uri = when (menuItem.itemId) {
|
val uri = when (menuItem.itemId) {
|
||||||
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
|
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
|
||||||
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
|
dirs.appDir
|
||||||
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_save_dir -> CitraApplication.documentsTree.folderUriHelper(
|
||||||
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
|
dirs.saveDir
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,7 +376,11 @@ class GameAdapter(
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
|
private fun showUninstallContextMenu(
|
||||||
|
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)
|
||||||
|
|
@ -342,16 +403,38 @@ 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(titleId, game.mediaType)
|
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
|
||||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
|
titleId,
|
||||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
|
game.mediaType
|
||||||
|
)
|
||||||
|
|
||||||
|
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 listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
|
if (menuItem.itemId in
|
||||||
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
|
listOf(
|
||||||
|
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 {
|
||||||
|
|
@ -362,7 +445,12 @@ class GameAdapter(
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
|
private fun showAboutGameDialog(
|
||||||
|
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)
|
||||||
|
|
@ -374,12 +462,22 @@ 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 = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text =
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
|
context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
|
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_filetype).text =
|
||||||
|
context.getString(R.string.game_context_type) + " " + game.fileType
|
||||||
|
|
||||||
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
|
val insertButton = bottomSheetView.findViewById<MaterialButton>(
|
||||||
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
|
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.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) {
|
||||||
|
|
@ -420,7 +518,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
|
||||||
|
|
@ -452,10 +550,15 @@ 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(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.shortcut_name_empty,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
return@setPositiveButton
|
return@setPositiveButton
|
||||||
}
|
}
|
||||||
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
|
val iconBitmap =
|
||||||
|
(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 {
|
||||||
|
|
@ -463,9 +566,11 @@ class GameAdapter(
|
||||||
val shortcut = ShortcutInfo.Builder(context, shortcutName)
|
val shortcut = ShortcutInfo.Builder(context, shortcutName)
|
||||||
.setShortLabel(shortcutName)
|
.setShortLabel(shortcutName)
|
||||||
.setIcon(icon)
|
.setIcon(icon)
|
||||||
.setIntent(game.launchIntent.apply {
|
.setIntent(
|
||||||
|
game.launchIntent.apply {
|
||||||
putExtra("launchedFromShortcut", true)
|
putExtra("launchedFromShortcut", true)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
shortcutManager?.requestPinShortcut(shortcut, null)
|
shortcutManager?.requestPinShortcut(shortcut, null)
|
||||||
|
|
@ -486,7 +591,9 @@ class GameAdapter(
|
||||||
bottomSheetDialog.dismiss()
|
bottomSheetDialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
|
||||||
|
R.id.compress_decompress
|
||||||
|
)
|
||||||
if (game.isInstalled) {
|
if (game.isInstalled) {
|
||||||
compressDecompressButton.setOnClickListener {
|
compressDecompressButton.setOnClickListener {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -499,24 +606,35 @@ class GameAdapter(
|
||||||
} else {
|
} else {
|
||||||
compressDecompressButton.setOnClickListener {
|
compressDecompressButton.setOnClickListener {
|
||||||
val shouldCompress = !game.isCompressed
|
val shouldCompress = !game.isCompressed
|
||||||
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
|
val recommendedExt = NativeLibrary.getRecommendedExtension(
|
||||||
|
holder.game.path,
|
||||||
|
shouldCompress
|
||||||
|
)
|
||||||
val baseName = holder.game.filename.substringBeforeLast('.')
|
val baseName = holder.game.filename.substringBeforeLast('.')
|
||||||
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
|
onRequestCompressOrDecompress?.invoke(
|
||||||
|
holder.game.path,
|
||||||
|
"$baseName.$recommendedExt",
|
||||||
|
shouldCompress
|
||||||
|
)
|
||||||
bottomSheetDialog.dismiss()
|
bottomSheetDialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
compressDecompressButton.text =
|
||||||
|
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>(R.id.menu_button_uninstall).setOnClickListener {
|
bottomSheetView.findViewById<MaterialButton>(
|
||||||
|
R.id.menu_button_uninstall
|
||||||
|
).setOnClickListener {
|
||||||
showUninstallContextMenu(it, game, bottomSheetDialog)
|
showUninstallContextMenu(it, game, bottomSheetDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
|
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
|
||||||
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
|
val options =
|
||||||
|
arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
|
||||||
var selectedIndex = -1
|
var selectedIndex = -1
|
||||||
val dialog = MaterialAlertDialogBuilder(context)
|
val dialog = MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(R.string.delete_cache_select_backend)
|
.setTitle(R.string.delete_cache_select_backend)
|
||||||
|
|
@ -532,11 +650,11 @@ class GameAdapter(
|
||||||
progToast.show()
|
progToast.show()
|
||||||
|
|
||||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
|
||||||
when (selectedIndex) {
|
when (selectedIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
NativeLibrary.deleteVulkanShaderCache(game.titleId)
|
NativeLibrary.deleteVulkanShaderCache(game.titleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
|
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
|
||||||
}
|
}
|
||||||
|
|
@ -620,10 +738,8 @@ class GameAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isValidGame(extension: String): Boolean {
|
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
|
||||||
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 {
|
||||||
|
|
@ -632,8 +748,6 @@ 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 {
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,27 +9,24 @@ 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>(), View.OnClickListener {
|
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
|
||||||
|
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)
|
||||||
|
|
@ -37,9 +34,7 @@ class HomeSettingAdapter(
|
||||||
return HomeOptionViewHolder(binding)
|
return HomeOptionViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int = options.size
|
||||||
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])
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,17 @@ 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>) :
|
||||||
|
|
@ -35,7 +34,8 @@ 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), SetupCallback {
|
RecyclerView.ViewHolder(binding.root),
|
||||||
|
SetupCallback {
|
||||||
lateinit var page: SetupPage
|
lateinit var page: SetupPage
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -49,7 +49,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
||||||
onStepCompleted(0, pageFullyCompleted = true)
|
onStepCompleted(0, pageFullyCompleted = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
|
if (page.pageButtons != null &&
|
||||||
|
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(
|
||||||
|
|
@ -108,9 +110,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
||||||
.alpha(0.38f)
|
.alpha(0.38f)
|
||||||
.setDuration(200)
|
.setDuration(200)
|
||||||
.start()
|
.start()
|
||||||
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
button.setTextColor(
|
||||||
|
button.context.getColor(
|
||||||
|
com.google.android.material.R.color.material_on_surface_disabled
|
||||||
|
)
|
||||||
|
)
|
||||||
button.iconTint =
|
button.iconTint =
|
||||||
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
ColorStateList.valueOf(
|
||||||
|
button.context.getColor(
|
||||||
|
com.google.android.material.R.color.material_on_surface_disabled
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,8 +69,9 @@ 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,
|
||||||
|
|
@ -78,16 +79,19 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ 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
|
||||||
|
|
@ -44,15 +45,16 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,11 +9,10 @@ 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 =
|
||||||
return Intent(Intent.ACTION_OPEN_DOCUMENT)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,14 @@
|
||||||
|
|
||||||
package org.citra.citra_emu.display
|
package org.citra.citra_emu.display
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
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.IntSetting
|
|
||||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
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.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
|
||||||
|
|
@ -20,7 +19,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
|
||||||
|
|
@ -34,17 +33,18 @@ 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 context.resources.getIntArray(
|
} else {
|
||||||
|
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,14 +61,32 @@ 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) {
|
||||||
|
|
@ -83,7 +101,6 @@ 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())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,8 @@ 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 {
|
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
||||||
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
|
||||||
BELOW(7);
|
BELOW(7);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(int: Int): SmallScreenPosition {
|
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
||||||
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,23 +40,27 @@ enum class PortraitScreenLayout(val int: Int) {
|
||||||
ORIGINAL(2);
|
ORIGINAL(2);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(int: Int): PortraitScreenLayout {
|
fun from(int: Int): PortraitScreenLayout =
|
||||||
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
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 {
|
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
|
||||||
return entries.firstOrNull { it.int == int } ?: NONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,9 +73,7 @@ enum class StereoWhichDisplay(val int: Int) {
|
||||||
SECONDARY_ONLY(3);
|
SECONDARY_ONLY(3);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(int: Int): StereoWhichDisplay {
|
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
|
||||||
return entries.firstOrNull { it.int == int } ?: NONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,8 +89,6 @@ enum class StereoMode(val int: Int) {
|
||||||
CARDBOARD_VR(6);
|
CARDBOARD_VR(6);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(int: Int): StereoMode {
|
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
|
||||||
return entries.firstOrNull { it.int == int } ?: OFF
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,19 +8,27 @@ 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.features.settings.model.IntSetting
|
|
||||||
import org.citra.citra_emu.NativeLibrary
|
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.utils.Log
|
||||||
|
|
||||||
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(
|
||||||
|
|
@ -35,31 +43,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSurface() {
|
fun updateSurface() {
|
||||||
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
|
val surface = 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 getExternalDisplay(context: Context): Display? {
|
private fun getSecondaryDisplays(): List<Display> {
|
||||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
val currentDisplayId = context.display.displayId
|
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
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)
|
||||||
val extDisplays = displays.filter {
|
return displays.filter {
|
||||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
val isNotDefaultOrPresentable =
|
||||||
|
(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() {
|
||||||
|
|
@ -68,21 +85,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// decide if we are going to the external display or the internal one
|
val displayToUse = if (availableDisplays.isEmpty() ||
|
||||||
var display = getExternalDisplay(context)
|
// Theoretically, the NONE option is no longer selectable, but
|
||||||
if (display == null ||
|
// I am leaving this in for backwards compatibility
|
||||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
|
||||||
display = vd.display
|
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
|
||||||
|
) {
|
||||||
|
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 == display) return
|
if (pres?.display == displayToUse) return
|
||||||
|
|
||||||
// otherwise, make a new presentation
|
// otherwise, make a new presentation
|
||||||
releasePresentation()
|
releasePresentation()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
|
||||||
pres?.show()
|
pres?.show()
|
||||||
}
|
}
|
||||||
// catch BadTokenException and InvalidDisplayException,
|
// catch BadTokenException and InvalidDisplayException,
|
||||||
|
|
@ -119,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class SecondaryDisplayPresentation(
|
class SecondaryDisplayPresentation(
|
||||||
context: Context, display: Display, val parent: SecondaryDisplay
|
context: Context,
|
||||||
|
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
|
||||||
|
|
@ -137,16 +175,21 @@ 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, format: Int, width: Int, height: Int
|
holder: SurfaceHolder,
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -191,7 +234,5 @@ class SecondaryDisplayPresentation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publicly accessible method to get the SurfaceHolder
|
// Publicly accessible method to get the SurfaceHolder
|
||||||
fun getSurfaceHolder(): SurfaceHolder {
|
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
|
||||||
return surfaceView.holder
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,8 +170,7 @@ 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() =
|
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
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())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,7 +41,8 @@ class CheatsAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
|
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root), View.OnClickListener,
|
RecyclerView.ViewHolder(binding.root),
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,7 +32,9 @@ 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 : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
class CheatsFragment :
|
||||||
|
Fragment(),
|
||||||
|
SlidingPaneLayout.PanelSlideListener {
|
||||||
private var cheatListLastFocus: View? = null
|
private var cheatListLastFocus: View? = null
|
||||||
private var cheatDetailsLastFocus: View? = null
|
private var cheatDetailsLastFocus: View? = null
|
||||||
|
|
||||||
|
|
@ -157,12 +159,15 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 =
|
binding.slidingPaneLayout.lockMode = if (cheatSelected) {
|
||||||
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
SlidingPaneLayout.LOCK_MODE_UNLOCKED
|
||||||
|
} else {
|
||||||
|
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onListViewFocusChange(hasFocus: Boolean) {
|
fun onListViewFocusChange(hasFocus: Boolean) {
|
||||||
|
|
@ -203,7 +208,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||||
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 as ViewGroup.MarginLayoutParams
|
val mlpDetails = binding.cheatDetailsContainer.layoutParams
|
||||||
|
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
|
||||||
|
|
@ -231,14 +237,16 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||||
runningAnimations: List<WindowInsetsAnimationCompat>
|
runningAnimations: List<WindowInsetsAnimationCompat>
|
||||||
): WindowInsetsCompat {
|
): WindowInsetsCompat {
|
||||||
val mlpDetails =
|
val mlpDetails =
|
||||||
binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
|
binding.cheatDetailsContainer.layoutParams
|
||||||
|
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
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -74,7 +74,8 @@ 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; hotkeyIsEnabled = false
|
handled = true
|
||||||
|
hotkeyIsEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (button in buttonSet) {
|
for (button in buttonSet) {
|
||||||
|
|
@ -109,10 +110,15 @@ 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(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ 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
|
||||||
|
|
@ -45,6 +46,7 @@ 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
|
||||||
|
|
@ -92,6 +94,7 @@ 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
|
||||||
|
|
@ -138,4 +141,5 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -20,19 +20,63 @@ 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(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
|
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(
|
||||||
|
SettingKeys.toggle_unique_data_console_type(),
|
||||||
|
Settings.SECTION_DEBUG,
|
||||||
|
false
|
||||||
|
),
|
||||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(), Settings.SECTION_RENDERER, 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(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
|
PERF_OVERLAY_SHOW_FPS(
|
||||||
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
|
SettingKeys.performance_overlay_show_fps(),
|
||||||
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
|
Settings.SECTION_LAYOUT,
|
||||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
|
true
|
||||||
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_SHOW_FRAMETIME(
|
||||||
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
|
SettingKeys.performance_overlay_show_frame_time(),
|
||||||
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
|
Settings.SECTION_LAYOUT,
|
||||||
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
|
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),
|
||||||
|
|
@ -44,19 +88,43 @@ 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(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
|
DISABLE_RIGHT_EYE_RENDER(
|
||||||
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
|
SettingKeys.disable_right_eye_render(),
|
||||||
|
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(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
|
COMPRESS_INSTALLED_CIA_CONTENT(
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -91,6 +159,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ enum class FloatSetting(
|
||||||
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(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
SECOND_SCREEN_OPACITY(
|
||||||
|
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);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
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,
|
||||||
|
|
@ -11,7 +13,12 @@ enum class IntListSetting(
|
||||||
val canBeEmpty: Boolean = true
|
val canBeEmpty: Boolean = true
|
||||||
) : AbstractListSetting<Int> {
|
) : AbstractListSetting<Int> {
|
||||||
|
|
||||||
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
LAYOUTS_TO_CYCLE(
|
||||||
|
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
|
||||||
|
|
@ -30,7 +37,6 @@ 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) {
|
||||||
|
|
@ -44,8 +50,7 @@ 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? =
|
fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key }
|
||||||
values().firstOrNull { it.key == key }
|
|
||||||
|
|
||||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
fun clear() = values().forEach { it.list = it.defaultValue }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ enum class IntSetting(
|
||||||
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,0),
|
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 4),
|
||||||
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),
|
||||||
|
|
@ -45,7 +45,11 @@ enum class IntSetting(
|
||||||
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(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
PORTRAIT_BOTTOM_HEIGHT(
|
||||||
|
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),
|
||||||
|
|
@ -54,7 +58,11 @@ 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(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
|
PERFORMANCE_OVERLAY_POSITION(
|
||||||
|
SettingKeys.performance_overlay_position(),
|
||||||
|
Settings.SECTION_LAYOUT,
|
||||||
|
0
|
||||||
|
),
|
||||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(), Settings.SECTION_RENDERER, 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);
|
||||||
|
|
||||||
|
|
@ -78,7 +86,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 }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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,9 +26,7 @@ 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? {
|
fun getSetting(key: String): AbstractSetting? = settings[key]
|
||||||
return settings[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mergeSection(settingSection: SettingSection) {
|
fun mergeSection(settingSection: SettingSection) {
|
||||||
for (setting in settingSection.settings.values) {
|
for (setting in settingSection.settings.values) {
|
||||||
|
|
|
||||||
|
|
@ -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,9 +33,7 @@ class Settings {
|
||||||
|
|
||||||
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||||
|
|
||||||
fun getSection(sectionName: String): SettingSection? {
|
fun getSection(sectionName: String): SettingSection? = sections[sectionName]
|
||||||
return sections[sectionName]
|
|
||||||
}
|
|
||||||
|
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
get() = sections.isEmpty()
|
get() = sections.isEmpty()
|
||||||
|
|
@ -142,7 +140,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(
|
||||||
|
|
@ -210,7 +208,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(
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,17 @@ 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(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
|
CAMERA_OUTER_LEFT_CONFIG(
|
||||||
|
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(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
CAMERA_OUTER_RIGHT_CONFIG(
|
||||||
|
SettingKeys.camera_outer_right_config(),
|
||||||
|
Settings.SECTION_CAMERA,
|
||||||
|
"_back"
|
||||||
|
);
|
||||||
|
|
||||||
override var string: String = defaultValue
|
override var string: String = defaultValue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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
|
||||||
|
|
||||||
|
|
@ -13,7 +15,9 @@ 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ 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(
|
class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
|
||||||
val abstractSetting: AbstractSetting,
|
SettingsItem(abstractSetting, titleId, 0) {
|
||||||
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)
|
||||||
|
|
@ -39,8 +37,7 @@ class InputBindingSetting(
|
||||||
/**
|
/**
|
||||||
* 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 =
|
fun isCirclePad(): Boolean = when (abstractSetting.key) {
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -50,8 +47,7 @@ class InputBindingSetting(
|
||||||
/**
|
/**
|
||||||
* 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 =
|
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) {
|
||||||
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
|
||||||
|
|
@ -62,8 +58,7 @@ class InputBindingSetting(
|
||||||
/**
|
/**
|
||||||
* 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 =
|
fun isCStick(): Boolean = when (abstractSetting.key) {
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -73,19 +68,18 @@ class InputBindingSetting(
|
||||||
/**
|
/**
|
||||||
* 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 =
|
fun isDPad(): Boolean = when (abstractSetting.key) {
|
||||||
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 =
|
fun isTrigger(): Boolean = when (abstractSetting.key) {
|
||||||
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,
|
||||||
|
|
@ -97,16 +91,12 @@ class InputBindingSetting(
|
||||||
/**
|
/**
|
||||||
* 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 {
|
fun isAxisMappingSupported(): Boolean = isCirclePad() || isCStick() || isDPad() || isTrigger()
|
||||||
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 {
|
fun isButtonMappingSupported(): Boolean = !isAxisMappingSupported() || isTrigger()
|
||||||
return !isAxisMappingSupported() || isTrigger()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Citra button code for the settings key.
|
* Returns the Citra button code for the settings key.
|
||||||
|
|
@ -135,7 +125,7 @@ class InputBindingSetting(
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +167,9 @@ class InputBindingSetting(
|
||||||
} 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,8 +261,7 @@ class InputBindingSetting(
|
||||||
companion object {
|
companion object {
|
||||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
||||||
|
|
||||||
private fun toTitleCase(raw: String): String =
|
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase()
|
||||||
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"
|
||||||
|
|
@ -287,8 +276,7 @@ class InputBindingSetting(
|
||||||
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getButtonName(keyCode: Int): String =
|
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode]
|
||||||
buttonNameOverrides[keyCode]
|
|
||||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||||
|
|
||||||
private data class DefaultButtonMapping(
|
private data class DefaultButtonMapping(
|
||||||
|
|
@ -296,6 +284,7 @@ class InputBindingSetting(
|
||||||
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,
|
||||||
|
|
@ -306,45 +295,153 @@ class InputBindingSetting(
|
||||||
)
|
)
|
||||||
|
|
||||||
private val xboxFaceButtonMappings = listOf(
|
private val xboxFaceButtonMappings = listOf(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
Settings.KEY_BUTTON_A,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
|
NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
),
|
||||||
|
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(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
|
Settings.KEY_BUTTON_A,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
),
|
||||||
|
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(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
|
Settings.KEY_BUTTON_L,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
|
KeyEvent.KEYCODE_BUTTON_L1,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
|
NativeLibrary.ButtonType.TRIGGER_L
|
||||||
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)
|
DefaultButtonMapping(
|
||||||
|
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(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
Settings.KEY_BUTTON_UP,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
NativeLibrary.ButtonType.DPAD_UP
|
||||||
|
),
|
||||||
|
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(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
DefaultAxisMapping(
|
||||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
|
MotionEvent.AXIS_X,
|
||||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
|
NativeLibrary.ButtonType.STICK_LEFT,
|
||||||
|
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(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
|
DefaultAxisMapping(
|
||||||
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
|
Settings.KEY_DPAD_AXIS_HORIZONTAL,
|
||||||
|
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.
|
||||||
|
|
@ -369,28 +466,84 @@ class InputBindingSetting(
|
||||||
// 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(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
Settings.KEY_BUTTON_A,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
),
|
||||||
|
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(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
DefaultButtonMapping(
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
Settings.KEY_BUTTON_UP,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
LINUX_BTN_DPAD_UP,
|
||||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
NativeLibrary.ButtonType.DPAD_UP
|
||||||
|
),
|
||||||
|
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(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
DefaultAxisMapping(
|
||||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
|
MotionEvent.AXIS_X,
|
||||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
|
NativeLibrary.ButtonType.STICK_LEFT,
|
||||||
|
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
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -417,9 +570,11 @@ class InputBindingSetting(
|
||||||
}
|
}
|
||||||
|
|
||||||
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).toSet()
|
Settings.dPadButtonKeys
|
||||||
|
).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearAllBindings() {
|
fun clearAllBindings() {
|
||||||
|
|
@ -465,7 +620,11 @@ class InputBindingSetting(
|
||||||
* 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) nintendoFaceButtonMappings else xboxFaceButtonMappings
|
val faceButtons = if (isNintendoLayout) {
|
||||||
|
nintendoFaceButtonMappings
|
||||||
|
} else {
|
||||||
|
xboxFaceButtonMappings
|
||||||
|
}
|
||||||
val buttonMappings = if (useAxisDpad) {
|
val buttonMappings = if (useAxisDpad) {
|
||||||
faceButtons + commonButtonMappings
|
faceButtons + commonButtonMappings
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -502,15 +661,16 @@ class InputBindingSetting(
|
||||||
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")
|
||||||
val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
|
@Suppress("ktlint:standard:max-line-length")
|
||||||
|
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 =
|
private fun getButtonKey(buttonCode: Int): String = when (buttonCode) {
|
||||||
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
|
||||||
|
|
@ -528,16 +688,19 @@ class InputBindingSetting(
|
||||||
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(CitraApplication.appContext)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
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 {
|
||||||
|
|
@ -549,15 +712,17 @@ class InputBindingSetting(
|
||||||
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
|
private fun getInputButtonKey(keyId: Int): String =
|
||||||
|
"${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 = getInputButtonKey(translateEventToKeyId(event))
|
fun getInputButtonKey(event: KeyEvent): String =
|
||||||
|
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).
|
||||||
|
|
@ -575,7 +740,6 @@ class InputBindingSetting(
|
||||||
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
|
||||||
|
|
@ -585,12 +749,10 @@ class InputBindingSetting(
|
||||||
* 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 {
|
fun translateEventToKeyId(event: KeyEvent): Int = if (event.keyCode == 0) {
|
||||||
return if (event.keyCode == 0) {
|
|
||||||
event.scanCode
|
event.scanCode
|
||||||
} else {
|
} else {
|
||||||
event.keyCode
|
event.keyCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
// 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(
|
||||||
|
|
@ -13,7 +15,9 @@ 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
|
||||||
|
|
||||||
|
|
@ -42,5 +46,4 @@ class MultiChoiceSetting(
|
||||||
intSetting.list = selection
|
intSetting.list = selection
|
||||||
return intSetting
|
return intSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
package org.citra.citra_emu.features.settings.model.view
|
||||||
|
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import androidx.annotation.StringRes
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -30,6 +31,9 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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.AbstractIntSetting
|
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||||
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.AbstractShortSetting
|
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||||
|
|
@ -16,7 +18,9 @@ class SingleChoiceSetting(
|
||||||
val valuesId: Int,
|
val valuesId: Int,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: Int? = null,
|
val defaultValue: 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_SINGLE_CHOICE
|
override val type = TYPE_SINGLE_CHOICE
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
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.AbstractFloatSetting
|
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||||
|
|
@ -20,7 +22,9 @@ class SliderSetting(
|
||||||
val units: String,
|
val units: String,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: Float? = null,
|
val defaultValue: Float? = 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_SLIDER
|
override val type = TYPE_SLIDER
|
||||||
val selectedFloat: Float
|
val selectedFloat: Float
|
||||||
|
|
@ -29,8 +33,11 @@ class SliderSetting(
|
||||||
|
|
||||||
val ret = when (setting) {
|
val ret = when (setting) {
|
||||||
is AbstractIntSetting -> setting.int.toFloat()
|
is AbstractIntSetting -> setting.int.toFloat()
|
||||||
|
|
||||||
is FloatSetting -> setting.float
|
is FloatSetting -> setting.float
|
||||||
|
|
||||||
is ScaledFloatSetting -> setting.float
|
is ScaledFloatSetting -> setting.float
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Log.error("[SliderSetting] Error casting setting type.")
|
Log.error("[SliderSetting] Error casting setting type.")
|
||||||
-1f
|
-1f
|
||||||
|
|
@ -38,6 +45,7 @@ class SliderSetting(
|
||||||
}
|
}
|
||||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a value to the backing int. If that int was previously null,
|
* Write a value to the backing int. If that int was previously null,
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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
|
||||||
|
|
||||||
|
|
@ -13,7 +15,9 @@ class StringInputSetting(
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val defaultValue: String,
|
val defaultValue: String,
|
||||||
val characterLimit: Int = 0,
|
val characterLimit: Int = 0,
|
||||||
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_STRING_INPUT
|
override val type = TYPE_STRING_INPUT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar 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.AbstractShortSetting
|
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||||
|
|
@ -16,7 +18,9 @@ class StringSingleChoiceSetting(
|
||||||
val values: Array<String>?,
|
val values: Array<String>?,
|
||||||
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_STRING_SINGLE_CHOICE
|
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue