mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 10:43:39 -04:00
Compare commits
42 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e8cb3d97a | ||
|
|
d25eacdf83 | ||
|
|
e3b04fe22e | ||
|
|
7a436ca7e8 | ||
|
|
a63d6a3834 | ||
|
|
d246f5b436 | ||
|
|
9a926e44c1 | ||
|
|
f906242a23 | ||
|
|
33e7ed5c5c | ||
|
|
f911af4197 | ||
|
|
37dd01fd51 | ||
|
|
ba5215242f | ||
|
|
8e4d4efce4 | ||
|
|
14dccb3c47 | ||
|
|
5431705e79 | ||
|
|
633ae42fbb | ||
|
|
0ed6b7558e | ||
|
|
bf1a95f438 | ||
|
|
01dc2bb776 | ||
|
|
4ac3cab012 | ||
|
|
897447e9bd | ||
|
|
724576cc61 | ||
|
|
3e1b86548a | ||
|
|
246e06d1a4 | ||
|
|
a607e3dd22 | ||
|
|
a65114eabf | ||
|
|
6ac0733002 | ||
|
|
8519e92eae | ||
|
|
7f2ac35870 | ||
|
|
1e2dd5ea78 | ||
|
|
beba099fed | ||
|
|
c888c40b3e | ||
|
|
57995cd89c | ||
|
|
29a77b342b | ||
|
|
3ef5bc0bfe | ||
|
|
d94657a44d | ||
|
|
ee58988897 | ||
|
|
ec7f00c9a4 | ||
|
|
164b9329c7 | ||
|
|
2292f3ab1b | ||
|
|
3ab6a304cd | ||
|
|
2e3d926dd5 |
60 changed files with 1140 additions and 240 deletions
|
|
@ -9,8 +9,14 @@ fi
|
||||||
|
|
||||||
cd src/android
|
cd src/android
|
||||||
chmod +x ./gradlew
|
chmod +x ./gradlew
|
||||||
./gradlew assembleRelease
|
|
||||||
./gradlew bundleRelease
|
if [[ "$TARGET" == "googleplay" ]]; then
|
||||||
|
./gradlew assembleGooglePlayRelease
|
||||||
|
./gradlew bundleGooglePlayRelease
|
||||||
|
else
|
||||||
|
./gradlew assembleVanillaRelease
|
||||||
|
./gradlew bundleVanillaRelease
|
||||||
|
fi
|
||||||
|
|
||||||
ccache -s -v
|
ccache -s -v
|
||||||
|
|
||||||
|
|
|
||||||
12
.ci/linux.sh
12
.ci/linux.sh
|
|
@ -1,14 +1,16 @@
|
||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
if [ "$TARGET" = "appimage" ]; then
|
if [[ "$TARGET" == "appimage"* ]]; then
|
||||||
# Compile the AppImage we distribute with Clang.
|
# Compile the AppImage we distribute with Clang.
|
||||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||||
-DCMAKE_C_COMPILER=clang
|
-DCMAKE_C_COMPILER=clang
|
||||||
-DCMAKE_LINKER=/etc/bin/ld.lld
|
-DCMAKE_LINKER=/etc/bin/ld.lld
|
||||||
-DENABLE_ROOM_STANDALONE=OFF)
|
-DENABLE_ROOM_STANDALONE=OFF)
|
||||||
# Bundle required QT wayland libraries
|
if [ "$TARGET" = "appimage-wayland" ]; then
|
||||||
export EXTRA_QT_PLUGINS="waylandcompositor"
|
# Bundle required QT wayland libraries
|
||||||
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
|
export EXTRA_QT_PLUGINS="waylandcompositor"
|
||||||
|
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# For the linux-fresh verification target, verify compilation without PCH as well.
|
# For the linux-fresh verification target, verify compilation without PCH as well.
|
||||||
export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF)
|
export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF)
|
||||||
|
|
@ -30,7 +32,7 @@ cmake .. -G Ninja \
|
||||||
ninja
|
ninja
|
||||||
strip -s bin/Release/*
|
strip -s bin/Release/*
|
||||||
|
|
||||||
if [ "$TARGET" = "appimage" ]; then
|
if [[ "$TARGET" == "appimage"* ]]; then
|
||||||
ninja bundle
|
ninja bundle
|
||||||
# TODO: Our AppImage environment currently uses an older ccache version without the verbose flag.
|
# TODO: Our AppImage environment currently uses an older ccache version without the verbose flag.
|
||||||
ccache -s
|
ccache -s
|
||||||
|
|
|
||||||
35
.github/workflows/build.yml
vendored
35
.github/workflows/build.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target: ["appimage", "fresh"]
|
target: ["appimage", "appimage-wayland", "fresh"]
|
||||||
container:
|
container:
|
||||||
image: opensauce04/azahar-build-environment:latest
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
|
|
@ -51,18 +51,22 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./.ci/linux.sh
|
run: ./.ci/linux.sh
|
||||||
- name: Move AppImage to artifacts directory
|
- name: Move AppImage to artifacts directory
|
||||||
if: ${{ matrix.target == 'appimage' }}
|
if: ${{ contains(matrix.target, 'appimage') }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
mv build/bundle/*.AppImage artifacts/
|
mv build/bundle/*.AppImage artifacts/
|
||||||
|
- name: Rename AppImage
|
||||||
|
if: ${{ matrix.target == 'appimage-wayland' }}
|
||||||
|
run: |
|
||||||
|
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||||
- name: Upload
|
- name: Upload
|
||||||
if: ${{ matrix.target == 'appimage' }}
|
if: ${{ contains(matrix.target, 'appimage') }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
macos:
|
macos:
|
||||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-13') || 'macos-14' }}
|
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-15' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -103,7 +107,7 @@ jobs:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
macos-universal:
|
macos-universal:
|
||||||
runs-on: macos-14
|
runs-on: macos-15
|
||||||
needs: macos
|
needs: macos
|
||||||
env:
|
env:
|
||||||
OS: macos
|
OS: macos
|
||||||
|
|
@ -212,51 +216,64 @@ jobs:
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
android:
|
android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target: ["vanilla", "googleplay"]
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
CCACHE_COMPILERCHECK: content
|
CCACHE_COMPILERCHECK: content
|
||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: android
|
OS: android
|
||||||
TARGET: universal
|
TARGET: ${{ matrix.target }}
|
||||||
|
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
${{ env.CCACHE_DIR }}
|
${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-android-${{ github.sha }}
|
key: ${{ runner.os }}-${{ env.OS }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-android-
|
${{ runner.os }}-${{ env.OS }}-${{ matrix.target }}-
|
||||||
- name: Set tag name
|
- name: Set tag name
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
||||||
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: Deps
|
- name: Install tools
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install ccache apksigner -y
|
sudo apt-get install ccache apksigner -y
|
||||||
- name: Update Android SDK CMake version
|
- name: Update Android SDK CMake version
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
|
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
|
||||||
- name: Build
|
- name: Build
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
||||||
env:
|
env:
|
||||||
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
|
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
|
||||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
|
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
|
||||||
- name: Pack
|
- name: Pack
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: ../../../.ci/pack.sh
|
run: ../../../.ci/pack.sh
|
||||||
working-directory: src/android/app
|
working-directory: src/android/app
|
||||||
env:
|
env:
|
||||||
UNPACKED: 1
|
UNPACKED: 1
|
||||||
- name: Upload
|
- name: Upload
|
||||||
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,15 @@ if (APPLE)
|
||||||
else()
|
else()
|
||||||
# Minimum macOS 13
|
# Minimum macOS 13
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
|
||||||
|
|
||||||
|
# Catch compiler issue on AppleClang versions below 15.0
|
||||||
|
# TODO: Remove this check when we drop macOS 13 Ventura
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND
|
||||||
|
CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15.0)
|
||||||
|
message(FATAL_ERROR "AppleClang 15.0 or later is required due to a compiler bug in earlier versions.\n"
|
||||||
|
"Current version: ${CMAKE_CXX_COMPILER_VERSION}\n"
|
||||||
|
"After updating, delete 'CMakeCache.txt' in the build directory.")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -299,7 +308,7 @@ find_package(Threads REQUIRED)
|
||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (NOT USE_SYSTEM_QT)
|
if (NOT USE_SYSTEM_QT)
|
||||||
download_qt(6.7.2)
|
download_qt(6.9.2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||||
|
|
|
||||||
|
|
@ -130,25 +130,10 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
--icon-file "${CMAKE_BINARY_DIR}/dist/org.azahar_emu.Azahar.svg"
|
--icon-file "${CMAKE_BINARY_DIR}/dist/org.azahar_emu.Azahar.svg"
|
||||||
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
||||||
--appdir "${appdir_path}"
|
--appdir "${appdir_path}"
|
||||||
RESULT_VARIABLE linuxdeploy_appdir_result)
|
RESULT_VARIABLE linuxdeploy_appdir_result
|
||||||
|
ERROR_VARIABLE linuxdeploy_appdir_error)
|
||||||
if (NOT linuxdeploy_appdir_result EQUAL "0")
|
if (NOT linuxdeploy_appdir_result EQUAL "0")
|
||||||
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
|
message(FATAL_ERROR "linuxdeploy failed to create AppDir w/ exit code ${linuxdeploy_appdir_result}:\n${linuxdeploy_appdir_error}")
|
||||||
endif()
|
|
||||||
|
|
||||||
if (enable_qt)
|
|
||||||
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
|
|
||||||
file(READ "${qt_hook_file}" qt_hook_contents)
|
|
||||||
# Add Cinnamon to list of DEs for GTK3 theming.
|
|
||||||
string(REPLACE
|
|
||||||
"*XFCE*"
|
|
||||||
"*X-Cinnamon*|*XFCE*"
|
|
||||||
qt_hook_contents "${qt_hook_contents}")
|
|
||||||
# Wayland backend crashes due to changed schemas in Gnome 40.
|
|
||||||
string(REPLACE
|
|
||||||
"export QT_QPA_PLATFORMTHEME=gtk3"
|
|
||||||
"export QT_QPA_PLATFORMTHEME=gtk3; export GDK_BACKEND=x11"
|
|
||||||
qt_hook_contents "${qt_hook_contents}")
|
|
||||||
file(WRITE "${qt_hook_file}" "${qt_hook_contents}")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Creating AppImage for executable ${executable_path}")
|
message(STATUS "Creating AppImage for executable ${executable_path}")
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
|
||||||
set(arch_path "mingw_64")
|
set(arch_path "mingw_64")
|
||||||
elseif (MSVC)
|
elseif (MSVC)
|
||||||
if ("arm64" IN_LIST ARCHITECTURE)
|
if ("arm64" IN_LIST ARCHITECTURE)
|
||||||
set(arch_path "msvc2019_arm64")
|
set(arch_path "msvc2022_arm64")
|
||||||
elseif ("x86_64" IN_LIST ARCHITECTURE)
|
elseif ("x86_64" IN_LIST ARCHITECTURE)
|
||||||
set(arch_path "msvc2019_64")
|
set(arch_path "msvc2022_64")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -30,12 +30,13 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
|
||||||
|
|
||||||
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
|
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
|
||||||
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
||||||
set(host_arch_path "msvc2019_64")
|
set(host_arch_path "msvc2022_64")
|
||||||
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
|
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
|
||||||
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
|
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
|
||||||
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
|
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
|
||||||
|
# TODO: ^ Is this still true with msvc2022?
|
||||||
# set(host_arch_path "msvc2019_arm64")
|
# set(host_arch_path "msvc2019_arm64")
|
||||||
set(host_arch_path "msvc2019_64")
|
set(host_arch_path "msvc2022_64")
|
||||||
endif()
|
endif()
|
||||||
set(host_arch "win64_${host_arch_path}")
|
set(host_arch "win64_${host_arch_path}")
|
||||||
else()
|
else()
|
||||||
|
|
@ -105,7 +106,7 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
|
||||||
|
|
||||||
if (NOT EXISTS "${prefix}")
|
if (NOT EXISTS "${prefix}")
|
||||||
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
||||||
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.18")
|
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0")
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(aqt_path "${base_path}/aqt.exe")
|
set(aqt_path "${base_path}/aqt.exe")
|
||||||
if (NOT EXISTS "${aqt_path}")
|
if (NOT EXISTS "${aqt_path}")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ Download the latest release from [Releases](https://github.com/azahar-emu/azahar
|
||||||
If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2.
|
If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
|
|
||||||
To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
||||||
|
|
@ -55,6 +54,11 @@ The recommended format for using Azahar on Linux is the Flatpak available on Fla
|
||||||
|
|
||||||
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
||||||
|
|
||||||
|
If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)).
|
||||||
|
|
||||||
|
Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended.
|
||||||
|
|
||||||
|
If you are using the Flatpak and run into issues with Wayland, you can disable Wayland support for the Azahar Flatpak using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal).
|
||||||
|
|
||||||
# Build instructions
|
# Build instructions
|
||||||
|
|
||||||
|
|
|
||||||
3
dist/apple/Info.plist.in
vendored
3
dist/apple/Info.plist.in
vendored
|
|
@ -75,6 +75,9 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<string>True</string>
|
<string>True</string>
|
||||||
|
<key>UIDesignRequiresCompatibility</key>
|
||||||
|
<true/> <!-- Remove when Qt Liquid Glass issues are fixed upstream:
|
||||||
|
https://bugreports.qt.io/browse/QTBUG-138942 -->
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|
|
||||||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4f39041699412873d0afcec89a9313148a192647
|
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
|
||||||
54
dist/languages/ca_ES_valencia.ts
vendored
54
dist/languages/ca_ES_valencia.ts
vendored
|
|
@ -27,7 +27,7 @@
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/aboutdialog.ui" line="30"/>
|
<location filename="../../src/citra_qt/aboutdialog.ui" line="30"/>
|
||||||
<source><html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html></source>
|
<source><html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html></source>
|
||||||
<translation type="unfinished"/>
|
<translation><html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/aboutdialog.ui" line="60"/>
|
<location filename="../../src/citra_qt/aboutdialog.ui" line="60"/>
|
||||||
|
|
@ -725,7 +725,7 @@ Desitja ignorar l'error i continuar?</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="107"/>
|
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="107"/>
|
||||||
<source>Show log output in console</source>
|
<source>Show log output in console</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Mostrar l'eixida del registre en la consola</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="114"/>
|
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="114"/>
|
||||||
|
|
@ -2275,7 +2275,7 @@ Desitja ignorar l'error i continuar?</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="102"/>
|
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="102"/>
|
||||||
<source><a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a></source>
|
<source><a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a></source>
|
||||||
<translation type="unfinished"/>
|
<translation><a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Més Informació</span></a></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="209"/>
|
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="209"/>
|
||||||
|
|
@ -2494,12 +2494,12 @@ Desitja ignorar l'error i continuar?</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="187"/>
|
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="187"/>
|
||||||
<source>Compress installed CIA content</source>
|
<source>Compress installed CIA content</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Comprimir el contingut de CIAs instal·lats</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="190"/>
|
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="190"/>
|
||||||
<source>Compresses the content of CIA files when installed to the emulated SD card. Only affects CIA content which is installed while the setting is enabled.</source>
|
<source>Compresses the content of CIA files when installed to the emulated SD card. Only affects CIA content which is installed while the setting is enabled.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_storage.cpp" line="26"/>
|
<location filename="../../src/citra_qt/configuration/configure_storage.cpp" line="26"/>
|
||||||
|
|
@ -4431,7 +4431,7 @@ Vols reinstal·lar els arxius de totes maneres?</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
||||||
<source>3DS Installation File (*.cia *.zcia)</source>
|
<source>3DS Installation File (*.cia *.zcia)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Fitxers d'Instalació de 3DS (*.cia *.zcia)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
||||||
|
|
@ -4510,24 +4510,24 @@ Vols reinstal·lar els arxius de totes maneres?</translation>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3107"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3107"/>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3113"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3113"/>
|
||||||
<source>Error compressing file</source>
|
<source>Error compressing file</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Error al comprimir el fitxer</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
||||||
<source>File compress operation failed, check log for details.</source>
|
<source>File compress operation failed, check log for details.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Operació de compressió fallida, mira el registre per a més detalls.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
||||||
<source>Error decompressing file</source>
|
<source>Error decompressing file</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Error de descompressió del fitxer</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||||
<source>File decompress operation failed, check log for details.</source>
|
<source>File decompress operation failed, check log for details.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Operació de descompressió fallida, mira el registre per a més detalls.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
||||||
|
|
@ -4663,62 +4663,62 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3063"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3063"/>
|
||||||
<source>Load 3DS ROM File</source>
|
<source>Load 3DS ROM File</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Carregar ROM de 3DS</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3064"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3064"/>
|
||||||
<source>3DS ROM Files (*.cia *cci *3dsx *cxi)</source>
|
<source>3DS ROM Files (*.cia *cci *3dsx *cxi)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Fitxers ROM 3DS (*.cia *cci *3dsx *cxi)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3108"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3108"/>
|
||||||
<source>The selected file is not a compatible 3DS ROM format. Make sure you have chosen the right file, and that it is not encrypted.</source>
|
<source>The selected file is not a compatible 3DS ROM format. Make sure you have chosen the right file, and that it is not encrypted.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>El fitxer seleccionat no és un ROM de 3DS compatible. Assegura't que has triat el fitxer correcte i que no estiga xifrat.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3114"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3114"/>
|
||||||
<source>The selected file is already compressed.</source>
|
<source>The selected file is already compressed.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>El fitxer seleccionat ja està comprimit.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3119"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3119"/>
|
||||||
<source>3DS Compressed ROM File (*.%1)</source>
|
<source>3DS Compressed ROM File (*.%1)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Fitxer ROM 3DS comprimit (*.%1)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3127"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3127"/>
|
||||||
<source>Save 3DS Compressed ROM File</source>
|
<source>Save 3DS Compressed ROM File</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Desar fitxer 3DS comprimit</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3152"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3152"/>
|
||||||
<source>Load 3DS Compressed ROM File</source>
|
<source>Load 3DS Compressed ROM File</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Carregar fitxer 3DS comprimit</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3153"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3153"/>
|
||||||
<source>3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi)</source>
|
<source>3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Fitxer ROM 3DS comprimit (*.zcia *zcci *z3dsx *zcxi)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3182"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3182"/>
|
||||||
<source>The selected file is not a compatible compressed 3DS ROM format. Make sure you have chosen the right file.</source>
|
<source>The selected file is not a compatible compressed 3DS ROM format. Make sure you have chosen the right file.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>El fitxer seleccionat no és un format de ROM 3DS comprimit compatible. Assegura't d'haver triat l'arxiu correcte.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3189"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3189"/>
|
||||||
<source>The selected file is already decompressed.</source>
|
<source>The selected file is already decompressed.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>El fitxer seleccionat ja està descomprimit.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3194"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3194"/>
|
||||||
<source>3DS ROM File (*.%1)</source>
|
<source>3DS ROM File (*.%1)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Fitxer ROM 3DS (*.%1)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3202"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3202"/>
|
||||||
<source>Save 3DS ROM File</source>
|
<source>Save 3DS ROM File</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Desar fitxer ROM 3DS</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3227"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3227"/>
|
||||||
|
|
@ -4818,7 +4818,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3431"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3431"/>
|
||||||
<source>Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)</source>
|
<source>Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3440"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3440"/>
|
||||||
|
|
@ -4839,7 +4839,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3600"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3600"/>
|
||||||
<source>%1 is missing. Please <a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>dump your system archives</a>.<br/>Continuing emulation may result in crashes and bugs.</source>
|
<source>%1 is missing. Please <a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>dump your system archives</a>.<br/>Continuing emulation may result in crashes and bugs.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Falta %1 . Per favor,<a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>bolca els teus arxius de sistema</a>.<br/>Continuar l'emulació pot resultar en penges i errors.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3608"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3608"/>
|
||||||
|
|
@ -4869,7 +4869,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3627"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3627"/>
|
||||||
<source>A fatal error occurred. <a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Check the log</a> for details.<br/>Continuing emulation may result in crashes and bugs.</source>
|
<source>A fatal error occurred. <a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Check the log</a> for details.<br/>Continuing emulation may result in crashes and bugs.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Error fatal.<a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Mira el log</a>per a més detalls.<br/>Continuar l'emulació pot resultar en penges i errors.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3632"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3632"/>
|
||||||
|
|
@ -6387,12 +6387,12 @@ Missatge de depuració:</translation>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/main.ui" line="466"/>
|
<location filename="../../src/citra_qt/main.ui" line="466"/>
|
||||||
<source>Compress ROM File...</source>
|
<source>Compress ROM File...</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Comprimir fitxer ROM...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/main.ui" line="471"/>
|
<location filename="../../src/citra_qt/main.ui" line="471"/>
|
||||||
<source>Decompress ROM File...</source>
|
<source>Decompress ROM File...</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Descomprimir fitxer ROM...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/main.ui" line="479"/>
|
<location filename="../../src/citra_qt/main.ui" line="479"/>
|
||||||
|
|
|
||||||
4
dist/languages/es_ES.ts
vendored
4
dist/languages/es_ES.ts
vendored
|
|
@ -4517,7 +4517,7 @@ Reinstall the files anyway?</source>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
||||||
<source>File compress operation failed, check log for details.</source>
|
<source>File compress operation failed, check log for details.</source>
|
||||||
<translation>Operación de comprensión fallida, mira el registro para más detalles.</translation>
|
<translation>Operación de compresión fallida, mira el registro para más detalles.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
||||||
|
|
@ -4529,7 +4529,7 @@ Reinstall the files anyway?</source>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||||
<source>File decompress operation failed, check log for details.</source>
|
<source>File decompress operation failed, check log for details.</source>
|
||||||
<translation>Operación de descomprensión fallida, mira el registro para más detalles.</translation>
|
<translation>Operación de descompresión fallida, mira el registro para más detalles.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
||||||
|
|
|
||||||
15
dist/languages/ru_RU.ts
vendored
15
dist/languages/ru_RU.ts
vendored
|
|
@ -3894,7 +3894,7 @@ Drag points to change position, or double-click table cells to edit values.</sou
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/configuration/configure_web.ui" line="26"/>
|
<location filename="../../src/citra_qt/configuration/configure_web.ui" line="26"/>
|
||||||
<source>Show current application in your Discord status</source>
|
<source>Show current application in your Discord status</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Показывать текущее приложение в статусе Discord</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
|
@ -4027,7 +4027,7 @@ Drag points to change position, or double-click table cells to edit values.</sou
|
||||||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
||||||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="85"/>
|
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="85"/>
|
||||||
<source>Azahar</source>
|
<source>Azahar</source>
|
||||||
<translation type="unfinished">Azahar</translation>
|
<translation>Azahar</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
||||||
|
|
@ -4170,7 +4170,7 @@ Please check your FFmpeg installation used for compilation.</source>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="1299"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="1299"/>
|
||||||
<source>GBA Virtual Console is not supported by Azahar.</source>
|
<source>GBA Virtual Console is not supported by Azahar.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Azahar не поддерживает GBA Virtual Console.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="1304"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="1304"/>
|
||||||
|
|
@ -4315,7 +4315,7 @@ Please check your FFmpeg installation used for compilation.</source>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3682"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3682"/>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3775"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3775"/>
|
||||||
<source>Azahar</source>
|
<source>Azahar</source>
|
||||||
<translation type="unfinished">Azahar</translation>
|
<translation>Azahar</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2066"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2066"/>
|
||||||
|
|
@ -4523,7 +4523,7 @@ Reinstall the files anyway?</source>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
||||||
<source>Error decompressing file</source>
|
<source>Error decompressing file</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Ошибра при разжатии файла</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||||
|
|
@ -5036,7 +5036,7 @@ Would you like to download it?</source>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/game_list.cpp" line="366"/>
|
<location filename="../../src/citra_qt/game_list.cpp" line="366"/>
|
||||||
<source>Don't show again</source>
|
<source>Don't show again</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Не показывать снова</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/game_list.cpp" line="564"/>
|
<location filename="../../src/citra_qt/game_list.cpp" line="564"/>
|
||||||
|
|
@ -7383,7 +7383,8 @@ They may have left the room.</source>
|
||||||
<source>Azahar has detected user data for Citra.
|
<source>Azahar has detected user data for Citra.
|
||||||
|
|
||||||
</source>
|
</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Azahar обнаружил файлы пользователя Citra.
|
||||||
|
</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../src/citra_qt/user_data_migration.cpp" line="77"/>
|
<location filename="../../src/citra_qt/user_data_migration.cpp" line="77"/>
|
||||||
|
|
|
||||||
67
externals/CMakeLists.txt
vendored
67
externals/CMakeLists.txt
vendored
|
|
@ -1,24 +1,37 @@
|
||||||
# Definitions for all external bundled libraries
|
# Definitions for all external bundled libraries
|
||||||
|
|
||||||
# Suppress warnings from external libraries
|
# Suppress warnings from external libraries
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
if (MSVC)
|
||||||
add_compile_options(/W0)
|
add_compile_options(/W0)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-w)
|
add_compile_options(-w)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
function(target_disable_warnings target)
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(${target} INTERFACE /W0)
|
||||||
|
else()
|
||||||
|
target_compile_options(${target} INTERFACE -w)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
||||||
include(DownloadExternals)
|
include(DownloadExternals)
|
||||||
include(ExternalProject)
|
include(ExternalProject)
|
||||||
|
|
||||||
# Boost
|
# Boost
|
||||||
if (NOT USE_SYSTEM_BOOST)
|
if (USE_SYSTEM_BOOST)
|
||||||
|
unset(BOOST_ROOT CACHE)
|
||||||
|
unset(Boost_INCLUDE_DIR CACHE)
|
||||||
|
set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE)
|
||||||
|
else()
|
||||||
message(STATUS "Including vendored Boost library")
|
message(STATUS "Including vendored Boost library")
|
||||||
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||||
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||||
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
|
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
|
||||||
add_library(boost INTERFACE)
|
add_library(boost INTERFACE)
|
||||||
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
|
target_include_directories(boost INTERFACE ${Boost_INCLUDE_DIR})
|
||||||
|
target_disable_warnings(boost)
|
||||||
|
|
||||||
# Boost::serialization
|
# Boost::serialization
|
||||||
file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
|
file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
|
||||||
|
|
@ -33,11 +46,7 @@ if (NOT USE_SYSTEM_BOOST)
|
||||||
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp
|
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(boost_iostreams PUBLIC boost)
|
target_link_libraries(boost_iostreams PUBLIC boost)
|
||||||
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
||||||
else()
|
|
||||||
unset(BOOST_ROOT CACHE)
|
|
||||||
unset(Boost_INCLUDE_DIR CACHE)
|
|
||||||
set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
|
|
@ -73,6 +82,7 @@ endif()
|
||||||
# dds-ktx
|
# dds-ktx
|
||||||
add_library(dds-ktx INTERFACE)
|
add_library(dds-ktx INTERFACE)
|
||||||
target_include_directories(dds-ktx INTERFACE ./dds-ktx)
|
target_include_directories(dds-ktx INTERFACE ./dds-ktx)
|
||||||
|
target_disable_warnings(dds-ktx)
|
||||||
|
|
||||||
# fmt and Xbyak need to be added before dynarmic
|
# fmt and Xbyak need to be added before dynarmic
|
||||||
# libfmt
|
# libfmt
|
||||||
|
|
@ -137,7 +147,8 @@ endif()
|
||||||
|
|
||||||
# MicroProfile
|
# MicroProfile
|
||||||
add_library(microprofile INTERFACE)
|
add_library(microprofile INTERFACE)
|
||||||
target_include_directories(microprofile SYSTEM INTERFACE ./microprofile)
|
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||||
|
target_disable_warnings(microprofile)
|
||||||
if (ENABLE_MICROPROFILE)
|
if (ENABLE_MICROPROFILE)
|
||||||
target_compile_definitions(microprofile INTERFACE MICROPROFILE_ENABLED=1)
|
target_compile_definitions(microprofile INTERFACE MICROPROFILE_ENABLED=1)
|
||||||
else()
|
else()
|
||||||
|
|
@ -146,10 +157,11 @@ endif()
|
||||||
|
|
||||||
# Nihstro
|
# Nihstro
|
||||||
add_library(nihstro-headers INTERFACE)
|
add_library(nihstro-headers INTERFACE)
|
||||||
target_include_directories(nihstro-headers SYSTEM INTERFACE ./nihstro/include)
|
target_include_directories(nihstro-headers INTERFACE ./nihstro/include)
|
||||||
if (MSVC)
|
target_disable_warnings(nihstro-headers)
|
||||||
# TODO: For some reason MSVC still applies this warning even with /W0 for externals.
|
if (NOT MSVC)
|
||||||
target_compile_options(nihstro-headers INTERFACE /wd4715)
|
# TODO: For some reason MSYS2 still applied this warnin even with -w
|
||||||
|
target_compile_options(nihstro-headers INTERFACE -Wno-invalid-specialization)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Open Source Archives
|
# Open Source Archives
|
||||||
|
|
@ -173,7 +185,8 @@ if (USE_SYSTEM_FFMPEG_HEADERS)
|
||||||
endif()
|
endif()
|
||||||
if (NOT FOUND_FFMPEG_HEADERS)
|
if (NOT FOUND_FFMPEG_HEADERS)
|
||||||
message(STATUS "Using bundled ffmpeg headers.")
|
message(STATUS "Using bundled ffmpeg headers.")
|
||||||
target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/ffmpeg/include)
|
target_include_directories(library-headers INTERFACE ./library-headers/ffmpeg/include)
|
||||||
|
target_disable_warnings(library-headers)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# SoundTouch
|
# SoundTouch
|
||||||
|
|
@ -294,7 +307,8 @@ if (USE_SYSTEM_JSON)
|
||||||
# Citra uses "#include <json.hpp>" so we have to add this manually
|
# Citra uses "#include <json.hpp>" so we have to add this manually
|
||||||
target_include_directories(json-headers SYSTEM INTERFACE "${NLOHMANN_PREFIX}/nlohmann")
|
target_include_directories(json-headers SYSTEM INTERFACE "${NLOHMANN_PREFIX}/nlohmann")
|
||||||
else()
|
else()
|
||||||
target_include_directories(json-headers SYSTEM INTERFACE ./json)
|
target_include_directories(json-headers INTERFACE ./json)
|
||||||
|
target_disable_warnings(json-headers)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# OpenSSL
|
# OpenSSL
|
||||||
|
|
@ -310,7 +324,8 @@ if (NOT OPENSSL_FOUND)
|
||||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||||
set(OPENSSLDIR "/etc/ssl/")
|
set(OPENSSLDIR "/etc/ssl/")
|
||||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(ssl SYSTEM INTERFACE ./libressl/include)
|
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||||
|
target_disable_warnings(ssl)
|
||||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||||
get_directory_property(OPENSSL_LIBRARIES
|
get_directory_property(OPENSSL_LIBRARIES
|
||||||
DIRECTORY libressl
|
DIRECTORY libressl
|
||||||
|
|
@ -327,17 +342,20 @@ if(USE_SYSTEM_CPP_HTTPLIB)
|
||||||
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
||||||
if(HTTP_LIBS)
|
if(HTTP_LIBS)
|
||||||
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
||||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
target_include_directories(httplib INTERFACE ./httplib)
|
||||||
|
target_disable_warnings(httplib)
|
||||||
else()
|
else()
|
||||||
if(CppHttp_FOUND)
|
if(CppHttp_FOUND)
|
||||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||||
else()
|
else()
|
||||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
target_include_directories(httplib INTERFACE ./httplib)
|
||||||
endif()
|
target_disable_warnings(httplib)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
target_include_directories(httplib INTERFACE ./httplib)
|
||||||
|
target_disable_warnings(httplib)
|
||||||
endif()
|
endif()
|
||||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||||
|
|
@ -354,7 +372,8 @@ if (ENABLE_WEB_SERVICE)
|
||||||
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
||||||
else()
|
else()
|
||||||
add_library(cpp-jwt INTERFACE)
|
add_library(cpp-jwt INTERFACE)
|
||||||
target_include_directories(cpp-jwt SYSTEM INTERFACE ./cpp-jwt/include)
|
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||||
|
target_disable_warnings(cpp-jwt)
|
||||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -453,7 +472,8 @@ if (ENABLE_VULKAN)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
add_library(vma INTERFACE)
|
add_library(vma INTERFACE)
|
||||||
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
|
target_include_directories(vma INTERFACE ./vma/include)
|
||||||
|
target_disable_warnings(vma)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# vulkan-headers
|
# vulkan-headers
|
||||||
|
|
@ -465,7 +485,8 @@ if (ENABLE_VULKAN)
|
||||||
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
|
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
|
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||||
|
target_disable_warnings(vulkan-headers)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# adrenotools
|
# adrenotools
|
||||||
|
|
|
||||||
2
externals/boost
vendored
2
externals/boost
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3c27c785ad0f8a742af02e620dc225673f3a12d8
|
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||||
2
externals/fmt
vendored
2
externals/fmt
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a
|
Subproject commit e424e3f2e607da02742f73db84873b8084fc714c
|
||||||
|
|
@ -125,7 +125,7 @@ android {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
versionNameSuffix = "-debug"
|
versionNameSuffix = "-debug"
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
isShrinkResources = true
|
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isJniDebuggable = true
|
isJniDebuggable = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
|
|
@ -135,6 +135,22 @@ android {
|
||||||
isDefault = true
|
isDefault = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
|
||||||
|
register("relWithDebInfoLite") {
|
||||||
|
initWith(getByName("relWithDebInfo"))
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
isDebuggable = false
|
||||||
|
installation {
|
||||||
|
enableBaselineProfile = false // Disabled by default when isDebuggable is true
|
||||||
|
}
|
||||||
|
lint {
|
||||||
|
checkReleaseBuilds = false // Ditto
|
||||||
|
// The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Signed by debug key disallowing distribution on Play Store.
|
// Signed by debug key disallowing distribution on Play Store.
|
||||||
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
||||||
debug {
|
debug {
|
||||||
|
|
@ -148,6 +164,18 @@ android {
|
||||||
|
|
||||||
flavorDimensions.add("version")
|
flavorDimensions.add("version")
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
register("vanilla") {
|
||||||
|
isDefault = true
|
||||||
|
dimension = "version"
|
||||||
|
versionNameSuffix = "-vanilla"
|
||||||
|
}
|
||||||
|
register("googlePlay") {
|
||||||
|
dimension = "version"
|
||||||
|
versionNameSuffix = "-googleplay"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
version = "3.25.0+"
|
version = "3.25.0+"
|
||||||
|
|
@ -186,7 +214,7 @@ dependencies {
|
||||||
|
|
||||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||||
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
||||||
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.304.1/android-binaries-1.4.304.1.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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
src/android/app/src/googlePlay/AndroidManifest.xml
Normal file
8
src/android/app/src/googlePlay/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- These permissions aren't allowed by Google. We asked, and they declined. -->
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="org.citra.citra_emu.CitraApplication"
|
android:name="org.citra.citra_emu.CitraApplication"
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ package org.citra.citra_emu
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
|
|
@ -18,11 +20,14 @@ import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
import org.citra.citra_emu.activities.EmulationActivity
|
||||||
import org.citra.citra_emu.utils.FileUtil
|
import org.citra.citra_emu.utils.FileUtil
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
|
import org.citra.citra_emu.utils.RemovableStorageHelper
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
@ -96,6 +101,24 @@ object NativeLibrary {
|
||||||
*/
|
*/
|
||||||
external fun onTouchMoved(xAxis: Float, yAxis: Float)
|
external fun onTouchMoved(xAxis: Float, yAxis: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch events on the secondary display.
|
||||||
|
*
|
||||||
|
* @param xAxis The value of the x-axis.
|
||||||
|
* @param yAxis The value of the y-axis.
|
||||||
|
* @param pressed To identify if the touch held down or released.
|
||||||
|
* @return true if the pointer is within the touchscreen
|
||||||
|
*/
|
||||||
|
external fun onSecondaryTouchEvent(xAxis: Float, yAxis: Float, pressed: Boolean): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch movement on the secondary display.
|
||||||
|
*
|
||||||
|
* @param xAxis The value of the instantaneous x-axis.
|
||||||
|
* @param yAxis The value of the instantaneous y-axis.
|
||||||
|
*/
|
||||||
|
external fun onSecondaryTouchMoved(xAxis: Float, yAxis: Float)
|
||||||
|
|
||||||
external fun reloadSettings()
|
external fun reloadSettings()
|
||||||
|
|
||||||
external fun getTitleId(filename: String): Long
|
external fun getTitleId(filename: String): Long
|
||||||
|
|
@ -611,6 +634,36 @@ object NativeLibrary {
|
||||||
FileUtil.getFilesName(path)
|
FileUtil.getFilesName(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun getUserDirectory(uriOverride: Uri? = null): String {
|
||||||
|
val preferences: SharedPreferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
|
||||||
|
val dirSep = "/"
|
||||||
|
val udUri = uriOverride ?:
|
||||||
|
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||||
|
val udPathSegment = udUri.lastPathSegment!!
|
||||||
|
val udVirtualPath = udPathSegment.substringAfter(":")
|
||||||
|
|
||||||
|
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
|
||||||
|
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
|
||||||
|
return primaryStoragePath + dirSep + udVirtualPath + dirSep
|
||||||
|
} else { // User directory probably located on a removable storage device
|
||||||
|
val storageIdString = udPathSegment.substringBefore(":")
|
||||||
|
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
|
||||||
|
|
||||||
|
if (udRemovablePath == null) {
|
||||||
|
android.util.Log.e("NativeLibrary",
|
||||||
|
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
|
||||||
|
)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return udRemovablePath + dirSep + udVirtualPath + dirSep
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getSize(path: String): Long =
|
fun getSize(path: String): Long =
|
||||||
|
|
@ -620,6 +673,10 @@ object NativeLibrary {
|
||||||
FileUtil.getFileSize(path)
|
FileUtil.getFileSize(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun getBuildFlavor(): String = BuildConfig.FLAVOR
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fileExists(path: String): Boolean =
|
fun fileExists(path: String): Boolean =
|
||||||
|
|
@ -671,6 +728,24 @@ object NativeLibrary {
|
||||||
FileUtil.renameFile(path, destinationFilename)
|
FileUtil.renameFile(path, destinationFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun updateDocumentLocation(sourcePath: String, destinationPath: String): Boolean =
|
||||||
|
CitraApplication.documentsTree.updateDocumentLocation(sourcePath, destinationPath)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun moveFile(filename: String, sourceDirPath: String, destinationDirPath: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(sourceDirPath)) {
|
||||||
|
try {
|
||||||
|
CitraApplication.documentsTree.moveFile(filename, sourceDirPath, destinationDirPath)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FileUtil.moveFile(filename, sourceDirPath, destinationDirPath)
|
||||||
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deleteDocument(path: String): Boolean =
|
fun deleteDocument(path: String): Boolean =
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,15 @@ 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;
|
private lateinit var secondaryDisplay: SecondaryDisplay
|
||||||
|
|
||||||
|
private val onShutdown = Runnable {
|
||||||
|
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||||
|
finishAffinity()
|
||||||
|
} else {
|
||||||
|
this.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val emulationFragment: EmulationFragment
|
private val emulationFragment: EmulationFragment
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -77,8 +85,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
settingsViewModel.settings.loadSettings()
|
settingsViewModel.settings.loadSettings()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
secondaryDisplay = SecondaryDisplay(this);
|
secondaryDisplay = SecondaryDisplay(this)
|
||||||
secondaryDisplay.updateDisplay();
|
secondaryDisplay.updateDisplay()
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||||
|
|
@ -101,13 +109,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
windowManager.defaultDisplay.rotation
|
windowManager.defaultDisplay.rotation
|
||||||
)
|
)
|
||||||
|
|
||||||
EmulationLifecycleUtil.addShutdownHook(hook = {
|
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
|
||||||
finishAffinity()
|
|
||||||
} else {
|
|
||||||
this.finish()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
isEmulationRunning = true
|
isEmulationRunning = true
|
||||||
instance = this
|
instance = this
|
||||||
|
|
@ -165,12 +167,12 @@ class EmulationActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
EmulationLifecycleUtil.clear()
|
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||||
NativeLibrary.playTimeManagerStop()
|
NativeLibrary.playTimeManagerStop()
|
||||||
isEmulationRunning = false
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
secondaryDisplay.releasePresentation()
|
secondaryDisplay.releasePresentation()
|
||||||
secondaryDisplay.releaseVD();
|
secondaryDisplay.releaseVD()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,29 +11,30 @@ import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import android.view.WindowManager
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
|
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
|
|
||||||
class SecondaryDisplay(val context: Context) {
|
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
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val st = SurfaceTexture(0)
|
|
||||||
st.setDefaultBufferSize(1920, 1080)
|
|
||||||
val vdSurface = Surface(st)
|
|
||||||
vd = displayManager.createVirtualDisplay(
|
vd = displayManager.createVirtualDisplay(
|
||||||
"HiddenDisplay",
|
"HiddenDisplay",
|
||||||
1920,
|
1920,
|
||||||
1080,
|
1080,
|
||||||
320,
|
320,
|
||||||
vdSurface,
|
null,
|
||||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
|
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
|
||||||
)
|
)
|
||||||
|
displayManager.registerDisplayListener(this, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSurface() {
|
fun updateSurface() {
|
||||||
|
|
@ -44,16 +45,23 @@ class SecondaryDisplay(val context: Context) {
|
||||||
NativeLibrary.secondarySurfaceDestroyed()
|
NativeLibrary.secondarySurfaceDestroyed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getExternalDisplay(context: Context): Display? {
|
||||||
|
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY
|
||||||
|
val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||||
|
return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" }
|
||||||
|
}
|
||||||
|
|
||||||
fun updateDisplay() {
|
fun updateDisplay() {
|
||||||
// decide if we are going to the external display or the internal one
|
// decide if we are going to the external display or the internal one
|
||||||
var display = getCustomerDisplay()
|
var display = getExternalDisplay(context)
|
||||||
if (display == null ||
|
if (display == null ||
|
||||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||||
display = vd.display
|
display = vd.display
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 == display) return
|
||||||
|
|
||||||
// otherwise, make a new presentation
|
// otherwise, make a new presentation
|
||||||
releasePresentation()
|
releasePresentation()
|
||||||
|
|
@ -61,29 +69,41 @@ class SecondaryDisplay(val context: Context) {
|
||||||
pres?.show()
|
pres?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCustomerDisplay(): Display? {
|
|
||||||
val displays = displayManager.displays
|
|
||||||
// code taken from MelonDS dual screen - should fix odin 2 detection bug
|
|
||||||
return displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
|
||||||
.firstOrNull { it.displayId != Display.DEFAULT_DISPLAY && it.name != "Built-in Screen" && it.name != "HiddenDisplay"}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun releasePresentation() {
|
fun releasePresentation() {
|
||||||
pres?.dismiss()
|
pres?.dismiss()
|
||||||
pres = null
|
pres = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseVD() {
|
fun releaseVD() {
|
||||||
|
displayManager.unregisterDisplayListener(this)
|
||||||
vd.release()
|
vd.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDisplayAdded(displayId: Int) {
|
||||||
|
updateDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisplayRemoved(displayId: Int) {
|
||||||
|
updateDisplay()
|
||||||
|
}
|
||||||
|
override fun onDisplayChanged(displayId: Int) {
|
||||||
|
updateDisplay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
window?.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||||
|
)
|
||||||
|
|
||||||
// Initialize SurfaceView
|
// Initialize SurfaceView
|
||||||
surfaceView = SurfaceView(context)
|
surfaceView = SurfaceView(context)
|
||||||
|
|
@ -103,6 +123,42 @@ class SecondaryDisplayPresentation(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.surfaceView.setOnTouchListener { _, event ->
|
||||||
|
|
||||||
|
val pointerIndex = event.actionIndex
|
||||||
|
val pointerId = event.getPointerId(pointerIndex)
|
||||||
|
when (event.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||||
|
if (touchscreenPointerId == -1) {
|
||||||
|
touchscreenPointerId = pointerId
|
||||||
|
NativeLibrary.onSecondaryTouchEvent(
|
||||||
|
event.getX(pointerIndex),
|
||||||
|
event.getY(pointerIndex),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val index = event.findPointerIndex(touchscreenPointerId)
|
||||||
|
if (index != -1) {
|
||||||
|
NativeLibrary.onSecondaryTouchMoved(
|
||||||
|
event.getX(index),
|
||||||
|
event.getY(index)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
if (pointerId == touchscreenPointerId) {
|
||||||
|
NativeLibrary.onSecondaryTouchEvent(0f, 0f, false)
|
||||||
|
touchscreenPointerId = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(surfaceView) // Set SurfaceView as content
|
setContentView(surfaceView) // Set SurfaceView as content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright Citra Emulator Project / Lime3DS 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.
|
||||||
|
|
||||||
|
|
@ -10,7 +10,6 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class SliderSetting(
|
class SliderSetting(
|
||||||
setting: AbstractSetting?,
|
setting: AbstractSetting?,
|
||||||
|
|
@ -27,7 +26,8 @@ class SliderSetting(
|
||||||
val selectedFloat: Float
|
val selectedFloat: Float
|
||||||
get() {
|
get() {
|
||||||
val setting = setting ?: return defaultValue!!.toFloat()
|
val setting = setting ?: return defaultValue!!.toFloat()
|
||||||
return 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
|
||||||
|
|
@ -36,8 +36,8 @@ class SliderSetting(
|
||||||
-1f
|
-1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,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.
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ class CitraDirectoryDialogFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
||||||
if (!PermissionsHandler.hasWriteAccess(requireContext())) {
|
if (!PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||||
(requireActivity() as MainActivity)?.openCitraDirectory?.launch(null)
|
PermissionsHandler.compatibleSelectDirectory((requireActivity() as MainActivity).openCitraDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
|
private val onPause = Runnable{ togglePause() }
|
||||||
|
private val onShutdown = Runnable{ emulationState.stop() }
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (context is EmulationActivity) {
|
if (context is EmulationActivity) {
|
||||||
|
|
@ -156,8 +159,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
emulationState = EmulationState(game.path)
|
emulationState = EmulationState(game.path)
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
||||||
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
|
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
||||||
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
|
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
|
@ -507,6 +510,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
EmulationLifecycleUtil.removeHook(onPause)
|
||||||
|
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupCitraDirectoriesThenStartEmulation() {
|
private fun setupCitraDirectoriesThenStartEmulation() {
|
||||||
val directoryInitializationState = DirectoryInitialization.start()
|
val directoryInitializationState = DirectoryInitialization.start()
|
||||||
if (directoryInitializationState ===
|
if (directoryInitializationState ===
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.fragments
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import org.citra.citra_emu.ui.main.MainActivity
|
||||||
|
import org.citra.citra_emu.utils.BuildUtil
|
||||||
|
|
||||||
|
class GrantMissingFilesystemPermissionFragment : DialogFragment() {
|
||||||
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
BuildUtil.assertNotGooglePlay()
|
||||||
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
|
isCancelable = false
|
||||||
|
|
||||||
|
val requestPermissionFunction =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
{
|
||||||
|
manageExternalStoragePermissionLauncher.launch(
|
||||||
|
Intent(
|
||||||
|
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||||
|
Uri.fromParts("package", mainActivity.packageName, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{ permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.filesystem_permission_warning)
|
||||||
|
.setMessage(R.string.filesystem_permission_lost)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
|
requestPermissionFunction()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
private val manageExternalStoragePermissionLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (Environment.isExternalStorageManager()) {
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val permissionLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (isGranted) {
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "GrantMissingFilesystemPermissionFragment"
|
||||||
|
|
||||||
|
fun newInstance(): GrantMissingFilesystemPermissionFragment {
|
||||||
|
BuildUtil.assertNotGooglePlay()
|
||||||
|
return GrantMissingFilesystemPermissionFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -159,7 +159,7 @@ class HomeSettingsFragment : Fragment() {
|
||||||
R.string.select_citra_user_folder,
|
R.string.select_citra_user_folder,
|
||||||
R.string.select_citra_user_folder_home_description,
|
R.string.select_citra_user_folder_home_description,
|
||||||
R.drawable.ic_home,
|
R.drawable.ic_home,
|
||||||
{ mainActivity?.openCitraDirectory?.launch(null) },
|
{ PermissionsHandler.compatibleSelectDirectory(mainActivity.openCitraDirectory) },
|
||||||
details = homeViewModel.userDir
|
details = homeViewModel.userDir
|
||||||
),
|
),
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
|
|
|
||||||
|
|
@ -13,21 +13,25 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.ui.main.MainActivity
|
import org.citra.citra_emu.ui.main.MainActivity
|
||||||
|
import org.citra.citra_emu.utils.PermissionsHandler
|
||||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||||
|
|
||||||
class SelectUserDirectoryDialogFragment : DialogFragment() {
|
class SelectUserDirectoryDialogFragment(titleOverride: Int? = null, descriptionOverride: Int? = null) : DialogFragment() {
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
|
private val title = titleOverride ?: R.string.select_citra_user_folder
|
||||||
|
private val description = descriptionOverride ?: R.string.selecting_user_directory_without_write_permissions
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
mainActivity = requireActivity() as MainActivity
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
isCancelable = false
|
isCancelable = false
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.select_citra_user_folder)
|
.setTitle(title)
|
||||||
.setMessage(R.string.selecting_user_directory_without_write_permissions)
|
.setMessage(description)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
mainActivity?.openCitraDirectoryLostPermission?.launch(null)
|
PermissionsHandler.compatibleSelectDirectory(mainActivity.openCitraDirectoryLostPermission)
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
@ -35,9 +39,10 @@ class SelectUserDirectoryDialogFragment : DialogFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "SelectUserDirectoryDialogFragment"
|
const val TAG = "SelectUserDirectoryDialogFragment"
|
||||||
|
|
||||||
fun newInstance(activity: FragmentActivity): SelectUserDirectoryDialogFragment {
|
fun newInstance(activity: FragmentActivity, titleOverride: Int? = null, descriptionOverride: Int? = null):
|
||||||
|
SelectUserDirectoryDialogFragment {
|
||||||
ViewModelProvider(activity)[HomeViewModel::class.java].setPickingUserDir(true)
|
ViewModelProvider(activity)[HomeViewModel::class.java].setPickingUserDir(true)
|
||||||
return SelectUserDirectoryDialogFragment()
|
return SelectUserDirectoryDialogFragment(titleOverride, descriptionOverride)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
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 androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
@ -30,7 +32,9 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.transition.MaterialFadeThrough
|
import com.google.android.material.transition.MaterialFadeThrough
|
||||||
|
import org.citra.citra_emu.BuildConfig
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.adapters.SetupAdapter
|
import org.citra.citra_emu.adapters.SetupAdapter
|
||||||
import org.citra.citra_emu.databinding.FragmentSetupBinding
|
import org.citra.citra_emu.databinding.FragmentSetupBinding
|
||||||
|
|
@ -41,6 +45,7 @@ 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.ui.main.MainActivity
|
import org.citra.citra_emu.ui.main.MainActivity
|
||||||
|
import org.citra.citra_emu.utils.BuildUtil
|
||||||
import org.citra.citra_emu.utils.CitraDirectoryHelper
|
import org.citra.citra_emu.utils.CitraDirectoryHelper
|
||||||
import org.citra.citra_emu.utils.GameHelper
|
import org.citra.citra_emu.utils.GameHelper
|
||||||
import org.citra.citra_emu.utils.PermissionsHandler
|
import org.citra.citra_emu.utils.PermissionsHandler
|
||||||
|
|
@ -142,7 +147,57 @@ class SetupFragment : Fragment() {
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
pageButtons = mutableListOf<PageButton>().apply {
|
pageButtons = mutableListOf<PageButton>().apply {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
|
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||||
|
add(
|
||||||
|
PageButton(
|
||||||
|
R.drawable.ic_folder,
|
||||||
|
R.string.filesystem_permission,
|
||||||
|
R.string.filesystem_permission_description,
|
||||||
|
buttonAction = {
|
||||||
|
pageButtonCallback = it
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
manageExternalStoragePermissionLauncher.launch(
|
||||||
|
Intent(
|
||||||
|
android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||||
|
Uri.fromParts(
|
||||||
|
"package",
|
||||||
|
requireActivity().packageName,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonState = {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
if (Environment.isExternalStorageManager()) {
|
||||||
|
ButtonState.BUTTON_ACTION_COMPLETE
|
||||||
|
} else {
|
||||||
|
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ButtonState.BUTTON_ACTION_COMPLETE
|
||||||
|
} else {
|
||||||
|
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isUnskippable = true,
|
||||||
|
hasWarning = true,
|
||||||
|
R.string.filesystem_permission_warning,
|
||||||
|
R.string.filesystem_permission_warning_description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
add(
|
add(
|
||||||
PageButton(
|
PageButton(
|
||||||
R.drawable.ic_notification,
|
R.drawable.ic_notification,
|
||||||
|
|
@ -214,18 +269,36 @@ class SetupFragment : Fragment() {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (
|
var permissionsComplete =
|
||||||
|
// Microphone
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
Manifest.permission.RECORD_AUDIO
|
Manifest.permission.RECORD_AUDIO
|
||||||
) == PackageManager.PERMISSION_GRANTED &&
|
) == PackageManager.PERMISSION_GRANTED &&
|
||||||
|
// Camera
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
Manifest.permission.CAMERA
|
Manifest.permission.CAMERA
|
||||||
) == PackageManager.PERMISSION_GRANTED &&
|
) == PackageManager.PERMISSION_GRANTED &&
|
||||||
|
// Notifications
|
||||||
NotificationManagerCompat.from(requireContext())
|
NotificationManagerCompat.from(requireContext())
|
||||||
.areNotificationsEnabled()
|
.areNotificationsEnabled()
|
||||||
) {
|
// External Storage
|
||||||
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
|
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
permissionsComplete =
|
||||||
|
(permissionsComplete && Environment.isExternalStorageManager())
|
||||||
|
} else {
|
||||||
|
permissionsComplete =
|
||||||
|
(permissionsComplete && ContextCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionsComplete) {
|
||||||
PageState.PAGE_STEPS_COMPLETE
|
PageState.PAGE_STEPS_COMPLETE
|
||||||
} else {
|
} else {
|
||||||
PageState.PAGE_STEPS_INCOMPLETE
|
PageState.PAGE_STEPS_INCOMPLETE
|
||||||
|
|
@ -249,7 +322,7 @@ class SetupFragment : Fragment() {
|
||||||
R.string.select_citra_user_folder_description,
|
R.string.select_citra_user_folder_description,
|
||||||
buttonAction = {
|
buttonAction = {
|
||||||
pageButtonCallback = it
|
pageButtonCallback = it
|
||||||
openCitraDirectory.launch(null)
|
PermissionsHandler.compatibleSelectDirectory(openCitraDirectory)
|
||||||
},
|
},
|
||||||
buttonState = {
|
buttonState = {
|
||||||
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||||
|
|
@ -452,6 +525,19 @@ class SetupFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showPermissionDeniedSnackbar() {
|
||||||
|
Snackbar.make(binding.root, R.string.permission_denied, Snackbar.LENGTH_LONG)
|
||||||
|
.setAnchorView(binding.buttonNext)
|
||||||
|
.setAction(R.string.grid_menu_core_settings) {
|
||||||
|
val intent =
|
||||||
|
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
||||||
|
intent.data = uri
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private val permissionLauncher =
|
private val permissionLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
if (isGranted) {
|
if (isGranted) {
|
||||||
|
|
@ -459,16 +545,20 @@ class SetupFragment : Fragment() {
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
Snackbar.make(binding.root, R.string.permission_denied, Snackbar.LENGTH_LONG)
|
showPermissionDeniedSnackbar()
|
||||||
.setAnchorView(binding.buttonNext)
|
}
|
||||||
.setAction(R.string.grid_menu_core_settings) {
|
|
||||||
val intent =
|
// We can't use permissionLauncher because MANAGE_EXTERNAL_STORAGE is a special snowflake
|
||||||
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
private val manageExternalStoragePermissionLauncher =
|
||||||
intent.data = uri
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
startActivity(intent)
|
BuildUtil.assertNotGooglePlay()
|
||||||
}
|
if (Environment.isExternalStorageManager()) {
|
||||||
.show()
|
checkForButtonState.invoke()
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
showPermissionDeniedSnackbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
|
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
|
||||||
|
|
@ -478,6 +568,15 @@ class SetupFragment : Fragment() {
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||||
|
SelectUserDirectoryDialogFragment.newInstance(
|
||||||
|
mainActivity,
|
||||||
|
R.string.invalid_selection,
|
||||||
|
R.string.invalid_user_directory
|
||||||
|
).show(mainActivity.supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
|
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||||
if (isActionMove) {
|
if (isActionMove) {
|
||||||
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
|
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
|
||||||
continue
|
continue
|
||||||
}
|
} else if (isActionUp) {
|
||||||
else if (isActionUp) {
|
|
||||||
NativeLibrary.onTouchEvent(0f, 0f, false)
|
NativeLibrary.onTouchEvent(0f, 0f, false)
|
||||||
break // Up and down actions shouldn't loop
|
break // Up and down actions shouldn't loop
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@
|
||||||
|
|
||||||
package org.citra.citra_emu.ui.main
|
package org.citra.citra_emu.ui.main
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
|
@ -36,6 +40,8 @@ import androidx.work.WorkManager
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.citra.citra_emu.BuildConfig
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||||
import org.citra.citra_emu.databinding.ActivityMainBinding
|
import org.citra.citra_emu.databinding.ActivityMainBinding
|
||||||
|
|
@ -43,6 +49,7 @@ import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
|
import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment
|
||||||
import org.citra.citra_emu.fragments.SelectUserDirectoryDialogFragment
|
import org.citra.citra_emu.fragments.SelectUserDirectoryDialogFragment
|
||||||
import org.citra.citra_emu.fragments.UpdateUserDirectoryDialogFragment
|
import org.citra.citra_emu.fragments.UpdateUserDirectoryDialogFragment
|
||||||
import org.citra.citra_emu.utils.CiaInstallWorker
|
import org.citra.citra_emu.utils.CiaInstallWorker
|
||||||
|
|
@ -185,14 +192,55 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||||
|
|
||||||
if (!firstTimeSetup && !PermissionsHandler.hasWriteAccess(this) &&
|
if (firstTimeSetup) {
|
||||||
!homeViewModel.isPickingUserDir.value
|
return
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
|
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||||
|
fun requestMissingFilesystemPermission() =
|
||||||
|
GrantMissingFilesystemPermissionFragment.newInstance()
|
||||||
|
.show(supportFragmentManager, GrantMissingFilesystemPermissionFragment.TAG)
|
||||||
|
|
||||||
|
if (supportFragmentManager.findFragmentByTag(GrantMissingFilesystemPermissionFragment.TAG) == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
if (!Environment.isExternalStorageManager()) {
|
||||||
|
requestMissingFilesystemPermission()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
requestMissingFilesystemPermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (homeViewModel.isPickingUserDir.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PermissionsHandler.hasWriteAccess(this)) {
|
||||||
SelectUserDirectoryDialogFragment.newInstance(this)
|
SelectUserDirectoryDialogFragment.newInstance(this)
|
||||||
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||||
} else if (!firstTimeSetup && !homeViewModel.isPickingUserDir.value && CitraDirectoryUtils.needToUpdateManually()) {
|
return
|
||||||
|
} else if (CitraDirectoryUtils.needToUpdateManually()) {
|
||||||
UpdateUserDirectoryDialogFragment.newInstance(this)
|
UpdateUserDirectoryDialogFragment.newInstance(this)
|
||||||
.show(supportFragmentManager,UpdateUserDirectoryDialogFragment.TAG)
|
.show(supportFragmentManager,UpdateUserDirectoryDialogFragment.TAG)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
|
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||||
|
if (supportFragmentManager.findFragmentByTag(SelectUserDirectoryDialogFragment.TAG) == null) {
|
||||||
|
if (NativeLibrary.getUserDirectory() == "") {
|
||||||
|
SelectUserDirectoryDialogFragment.newInstance(this)
|
||||||
|
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,6 +364,15 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||||
|
SelectUserDirectoryDialogFragment.newInstance(
|
||||||
|
this,
|
||||||
|
R.string.invalid_selection,
|
||||||
|
R.string.invalid_user_directory
|
||||||
|
).show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
CitraDirectoryHelper(this@MainActivity, permissionsLost)
|
CitraDirectoryHelper(this@MainActivity, permissionsLost)
|
||||||
.showCitraDirectoryDialog(result, buttonState = {})
|
.showCitraDirectoryDialog(result, buttonState = {})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.utils
|
||||||
|
|
||||||
|
import org.citra.citra_emu.BuildConfig
|
||||||
|
|
||||||
|
object BuildUtil {
|
||||||
|
fun assertNotGooglePlay() {
|
||||||
|
|
||||||
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
|
if (BuildConfig.FLAVOR == "googlePlay") {
|
||||||
|
error("Non-GooglePlay code being called in GooglePlay build")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,12 @@ package org.citra.citra_emu.utils
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.model.CheapDocument
|
import org.citra.citra_emu.model.CheapDocument
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
import java.nio.file.Paths
|
||||||
import java.util.StringTokenizer
|
import java.util.StringTokenizer
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
|
@ -191,7 +193,7 @@ class DocumentsTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun renameFile(filepath: String, destinationFilename: String?): Boolean {
|
fun renameFile(filepath: String, destinationFilename: String): Boolean {
|
||||||
val node = resolvePath(filepath) ?: return false
|
val node = resolvePath(filepath) ?: return false
|
||||||
try {
|
try {
|
||||||
val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD)
|
val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD)
|
||||||
|
|
@ -203,6 +205,20 @@ class DocumentsTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun moveFile(filename: String, sourceDirPath: String, destDirPath: String): Boolean {
|
||||||
|
val sourceFileNode = resolvePath(sourceDirPath + "/" + filename) ?: return false
|
||||||
|
val sourceDirNode = resolvePath(sourceDirPath) ?: return false
|
||||||
|
val destDirNode = resolvePath(destDirPath) ?: return false
|
||||||
|
try {
|
||||||
|
val newUri = DocumentsContract.moveDocument(context.contentResolver, sourceFileNode.uri!!, sourceDirNode.uri!!, destDirNode.uri!!)
|
||||||
|
updateDocumentLocation("$sourceDirPath/$filename", "$destDirPath/$filename")
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
error("[DocumentsTree]: Cannot move file, error: " + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun deleteDocument(filepath: String): Boolean {
|
fun deleteDocument(filepath: String): Boolean {
|
||||||
val node = resolvePath(filepath) ?: return false
|
val node = resolvePath(filepath) ?: return false
|
||||||
|
|
@ -219,6 +235,29 @@ class DocumentsTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun updateDocumentLocation(sourcePath: String, destinationPath: String): Boolean {
|
||||||
|
val sourceNode = resolvePath(sourcePath)
|
||||||
|
val newName = Paths.get(destinationPath).fileName.toString()
|
||||||
|
val parentPath = Paths.get(destinationPath).parent.toString()
|
||||||
|
val newParent = resolvePath(parentPath)
|
||||||
|
val newUri = (getUri(parentPath).toString() + "%2F$newName").toUri() // <- Is there a better way?
|
||||||
|
|
||||||
|
if (sourceNode == null || newParent == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceNode.parent!!.removeChild(sourceNode)
|
||||||
|
|
||||||
|
sourceNode.name = newName
|
||||||
|
sourceNode.parent = newParent
|
||||||
|
sourceNode.uri = newUri
|
||||||
|
|
||||||
|
newParent.addChild(sourceNode)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun resolvePath(filepath: String): DocumentsNode? {
|
private fun resolvePath(filepath: String): DocumentsNode? {
|
||||||
root ?: return null
|
root ?: return null
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -18,15 +18,27 @@ object EmulationLifecycleUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addShutdownHook(hook: Runnable) {
|
fun addShutdownHook(hook: Runnable) {
|
||||||
shutdownHooks.add(hook)
|
if (shutdownHooks.contains(hook)) {
|
||||||
|
Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook for function that already existed. Skipping.")
|
||||||
|
} else {
|
||||||
|
shutdownHooks.add(hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addPauseResumeHook(hook: Runnable) {
|
fun addPauseResumeHook(hook: Runnable) {
|
||||||
pauseResumeHooks.add(hook)
|
if (pauseResumeHooks.contains(hook)) {
|
||||||
|
Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook for function that already existed. Skipping.")
|
||||||
|
} else {
|
||||||
|
pauseResumeHooks.add(hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun removeHook(hook: Runnable) {
|
||||||
pauseResumeHooks.clear()
|
if (pauseResumeHooks.contains(hook)) {
|
||||||
shutdownHooks.clear()
|
pauseResumeHooks.remove(hook)
|
||||||
|
}
|
||||||
|
if (shutdownHooks.contains(hook)) {
|
||||||
|
shutdownHooks.remove(hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.model.CheapDocument
|
import org.citra.citra_emu.model.CheapDocument
|
||||||
|
|
@ -434,6 +435,20 @@ object FileUtil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun moveFile(filename: String, sourceDirUriString: String, destDirUriString: String): Boolean {
|
||||||
|
try {
|
||||||
|
val sourceFileUri = ("$sourceDirUriString%2F$filename").toUri()
|
||||||
|
val sourceDirUri = sourceDirUriString.toUri()
|
||||||
|
val destDirUri = destDirUriString.toUri()
|
||||||
|
DocumentsContract.moveDocument(context.contentResolver, sourceFileUri, sourceDirUri, destDirUri)
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.error("[FileUtil]: Cannot move file, error: " + e.message)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deleteDocument(path: String): Boolean {
|
fun deleteDocument(path: String): Boolean {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
|
@ -48,4 +51,17 @@ object PermissionsHandler {
|
||||||
|
|
||||||
fun setCitraDirectory(uriString: String?) =
|
fun setCitraDirectory(uriString: String?) =
|
||||||
preferences.edit().putString(CITRA_DIRECTORY, uriString).apply()
|
preferences.edit().putString(CITRA_DIRECTORY, uriString).apply()
|
||||||
|
|
||||||
|
fun compatibleSelectDirectory(activityLauncher: ActivityResultLauncher<Uri?>) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
activityLauncher.launch(null)
|
||||||
|
} else {
|
||||||
|
val initialUri = DocumentsContract.buildRootUri(
|
||||||
|
"com.android.externalstorage.documents",
|
||||||
|
"primary"
|
||||||
|
)
|
||||||
|
activityLauncher.launch(initialUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.utils
|
||||||
|
|
||||||
|
import org.citra.citra_emu.utils.BuildUtil
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object RemovableStorageHelper {
|
||||||
|
// This really shouldn't be necessary, but the Android API seemingly
|
||||||
|
// doesn't have a way of doing this?
|
||||||
|
fun getRemovableStoragePath(idString: String): String? {
|
||||||
|
BuildUtil.assertNotGooglePlay()
|
||||||
|
|
||||||
|
// On certain Android flavours the external storage mount location can
|
||||||
|
// vary, so add extra cases here if we discover them.
|
||||||
|
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||||
|
|
||||||
|
for (mountPath in possibleMountPaths) {
|
||||||
|
val pathFile = File(mountPath);
|
||||||
|
if (pathFile.exists()) {
|
||||||
|
// TODO: Cache which mount location is being used for the remainder of the
|
||||||
|
// session, as it should never change. -OS
|
||||||
|
return pathFile.absolutePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -657,6 +657,25 @@ void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEn
|
||||||
window->OnTouchMoved((int)x, (int)y);
|
window->OnTouchMoved((int)x, (int)y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchEvent([[maybe_unused]] JNIEnv* env,
|
||||||
|
[[maybe_unused]] jobject obj,
|
||||||
|
jfloat x, jfloat y,
|
||||||
|
jboolean pressed) {
|
||||||
|
if (!secondary_window) {
|
||||||
|
return JNI_FALSE;
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(secondary_window->OnTouchEvent(
|
||||||
|
static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), pressed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||||
|
[[maybe_unused]] jobject obj,
|
||||||
|
jfloat x, jfloat y) {
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->OnTouchMoved((int)x, (int)y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj,
|
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj,
|
||||||
jstring j_filename) {
|
jstring j_filename) {
|
||||||
std::string filepath = GetJString(env, j_filename);
|
std::string filepath = GetJString(env, j_filename);
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,10 @@
|
||||||
<string name="region_mismatch">Advertiment Regió No Vàlida</string>
|
<string name="region_mismatch">Advertiment Regió No Vàlida</string>
|
||||||
<string name="region_mismatch_emulated">La configuració del país no és vàlida per a la regió emulada seleccionada.</string>
|
<string name="region_mismatch_emulated">La configuració del país no és vàlida per a la regió emulada seleccionada.</string>
|
||||||
<string name="region_mismatch_console">La configuració del país no és vàlida per a la consola vinculada actual.</string>
|
<string name="region_mismatch_console">La configuració del país no és vàlida per a la consola vinculada actual.</string>
|
||||||
|
<string name="storage">Emmagatzematge</string>
|
||||||
|
<string name="compress_cia_installs">Comprimir el contingut de CIAs instal·lats</string>
|
||||||
|
<string name="compress_cia_installs_description">Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada.</string>
|
||||||
|
|
||||||
<!-- Camera settings strings -->
|
<!-- Camera settings strings -->
|
||||||
<string name="inner_camera">Càmera interior</string>
|
<string name="inner_camera">Càmera interior</string>
|
||||||
<string name="outer_left_camera">Càmera esquerra externa</string>
|
<string name="outer_left_camera">Càmera esquerra externa</string>
|
||||||
|
|
@ -397,6 +401,10 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
||||||
<string name="emulation_configure_controls">Configurar Controls</string>
|
<string name="emulation_configure_controls">Configurar Controls</string>
|
||||||
<string name="emulation_edit_layout">Editar Estil</string>
|
<string name="emulation_edit_layout">Editar Estil</string>
|
||||||
<string name="emulation_done">Fet</string>
|
<string name="emulation_done">Fet</string>
|
||||||
|
<string name="emulation_button_sliding">Lliscament de botons</string>
|
||||||
|
<string name="emulation_button_sliding_disabled">Mantindre el botó pressionat originalment</string>
|
||||||
|
<string name="emulation_button_sliding_enabled">Mantindre el botó pressionat actualment</string>
|
||||||
|
<string name="emulation_button_sliding_alternative">Mantindre el botó original i actualment pressionat</string>
|
||||||
<string name="emulation_toggle_controls">Activar Controls</string>
|
<string name="emulation_toggle_controls">Activar Controls</string>
|
||||||
<string name="emulation_control_scale">Ajustar Escala</string>
|
<string name="emulation_control_scale">Ajustar Escala</string>
|
||||||
<string name="emulation_control_scale_global">Escala Global</string>
|
<string name="emulation_control_scale_global">Escala Global</string>
|
||||||
|
|
@ -409,6 +417,8 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
||||||
<string name="emulation_aspect_ratio">Relació d\'Aspecte</string>
|
<string name="emulation_aspect_ratio">Relació d\'Aspecte</string>
|
||||||
<string name="emulation_switch_screen_layout">Estil de Pantalla Apaïsada</string>
|
<string name="emulation_switch_screen_layout">Estil de Pantalla Apaïsada</string>
|
||||||
<string name="emulation_switch_portrait_layout">Estil de Pantalla de Perfil</string>
|
<string name="emulation_switch_portrait_layout">Estil de Pantalla de Perfil</string>
|
||||||
|
<string name="emulation_switch_secondary_layout">Estil de Pantalla Secundària</string>
|
||||||
|
<string name="emulation_switch_secondary_layout_description">La disposició de la pantalla secundària connectada, amb cable o sense fil (Chromecast, Miracast)</string>
|
||||||
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
||||||
<string name="emulation_screen_layout_portrait">Vertical</string>
|
<string name="emulation_screen_layout_portrait">Vertical</string>
|
||||||
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
||||||
|
|
@ -416,6 +426,7 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
||||||
<string name="emulation_screen_layout_hybrid">Pantalles híbrides</string>
|
<string name="emulation_screen_layout_hybrid">Pantalles híbrides</string>
|
||||||
<string name="emulation_screen_layout_original">Original</string>
|
<string name="emulation_screen_layout_original">Original</string>
|
||||||
<string name="emulation_portrait_layout_top_full">Per omissió</string>
|
<string name="emulation_portrait_layout_top_full">Per omissió</string>
|
||||||
|
<string name="emulation_secondary_display_default">Per defecte del sistema (espill)</string>
|
||||||
<string name="emulation_screen_layout_custom">Estil Personalitzat</string>
|
<string name="emulation_screen_layout_custom">Estil Personalitzat</string>
|
||||||
<string name="emulation_small_screen_position">Posició de Pantalla Xicoteta</string>
|
<string name="emulation_small_screen_position">Posició de Pantalla Xicoteta</string>
|
||||||
<string name="small_screen_position_description">On hauria d\'aparéixer la pantalla xicoteta en relació amb la gran en Proporció de Pantalla Gran?</string>
|
<string name="small_screen_position_description">On hauria d\'aparéixer la pantalla xicoteta en relació amb la gran en Proporció de Pantalla Gran?</string>
|
||||||
|
|
@ -536,6 +547,10 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
||||||
<string name="create_shortcut">Crear drecera</string>
|
<string name="create_shortcut">Crear drecera</string>
|
||||||
<string name="shortcut_name_empty">El nom de la drecera no pot estar buit</string>
|
<string name="shortcut_name_empty">El nom de la drecera no pot estar buit</string>
|
||||||
<string name="shortcut_image_stretch_toggle">Allargar per a ajustar la imatge</string>
|
<string name="shortcut_image_stretch_toggle">Allargar per a ajustar la imatge</string>
|
||||||
|
<string name="game_context_id">ID:</string>
|
||||||
|
<string name="game_context_file">Fitxer:</string>
|
||||||
|
<string name="game_context_type">Tipus:</string>
|
||||||
|
|
||||||
<!-- Performance Overlay settings -->
|
<!-- Performance Overlay settings -->
|
||||||
<string name="performance_overlay_show">Mostrar informació de rendiment</string>
|
<string name="performance_overlay_show">Mostrar informació de rendiment</string>
|
||||||
<string name="performance_overlay_options">Informació de rendiment</string>
|
<string name="performance_overlay_options">Informació de rendiment</string>
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@
|
||||||
<string name="region_mismatch_emulated">La configuración del país no es válida para la región emulada seleccionada.</string>
|
<string name="region_mismatch_emulated">La configuración del país no es válida para la región emulada seleccionada.</string>
|
||||||
<string name="region_mismatch_console">La configuración del país no es válida para la consola vinculada actual.</string>
|
<string name="region_mismatch_console">La configuración del país no es válida para la consola vinculada actual.</string>
|
||||||
<string name="storage">Almacenamiento</string>
|
<string name="storage">Almacenamiento</string>
|
||||||
<string name="compress_cia_installs">Comprimir el contenido CIA instalado</string>
|
<string name="compress_cia_installs">Comprimir el contenido de CIAs instalados</string>
|
||||||
<string name="compress_cia_installs_description">Comprime el contenido de archivos CIA cuando son instalados a la SD emulada. Solo afecta contenido CIA instalado con esta opción activada.</string>
|
<string name="compress_cia_installs_description">Comprime el contenido de archivos CIA cuando son instalados a la SD emulada. Solo afecta contenido CIA instalado con esta opción activada.</string>
|
||||||
|
|
||||||
<!-- Camera settings strings -->
|
<!-- Camera settings strings -->
|
||||||
|
|
@ -402,9 +402,9 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
||||||
<string name="emulation_edit_layout">Editar Estilo</string>
|
<string name="emulation_edit_layout">Editar Estilo</string>
|
||||||
<string name="emulation_done">Hecho</string>
|
<string name="emulation_done">Hecho</string>
|
||||||
<string name="emulation_button_sliding">Deslizamiento de botones</string>
|
<string name="emulation_button_sliding">Deslizamiento de botones</string>
|
||||||
<string name="emulation_button_sliding_disabled">Mantenga el botón presionado originalmente</string>
|
<string name="emulation_button_sliding_disabled">Mantener el botón presionado originalmente</string>
|
||||||
<string name="emulation_button_sliding_enabled">Mantenga el botón presionado actualmente</string>
|
<string name="emulation_button_sliding_enabled">Mantener el botón presionado actualmente</string>
|
||||||
<string name="emulation_button_sliding_alternative">Mantenga el botón original y actualmente presionado</string>
|
<string name="emulation_button_sliding_alternative">Mantener el botón original y actualmente presionado</string>
|
||||||
<string name="emulation_toggle_controls">Activar Controles</string>
|
<string name="emulation_toggle_controls">Activar Controles</string>
|
||||||
<string name="emulation_control_scale">Ajustar Escala</string>
|
<string name="emulation_control_scale">Ajustar Escala</string>
|
||||||
<string name="emulation_control_scale_global">Escala Global</string>
|
<string name="emulation_control_scale_global">Escala Global</string>
|
||||||
|
|
@ -417,6 +417,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
||||||
<string name="emulation_aspect_ratio">Relación de Aspecto</string>
|
<string name="emulation_aspect_ratio">Relación de Aspecto</string>
|
||||||
<string name="emulation_switch_screen_layout">Estilo de Pantalla Apaisada</string>
|
<string name="emulation_switch_screen_layout">Estilo de Pantalla Apaisada</string>
|
||||||
<string name="emulation_switch_portrait_layout">Estilo de Pantalla de Perfil</string>
|
<string name="emulation_switch_portrait_layout">Estilo de Pantalla de Perfil</string>
|
||||||
|
<string name="emulation_switch_secondary_layout">Estilo de Pantalla Secundaria</string>
|
||||||
|
<string name="emulation_switch_secondary_layout_description">El estilo de la pantalla secundaria conectada, con cable o inalámbrica (Chromecast, Miracast)</string>
|
||||||
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
||||||
<string name="emulation_screen_layout_portrait">Retrato</string>
|
<string name="emulation_screen_layout_portrait">Retrato</string>
|
||||||
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
||||||
|
|
@ -424,6 +426,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
||||||
<string name="emulation_screen_layout_hybrid">Pantallas híbridas</string>
|
<string name="emulation_screen_layout_hybrid">Pantallas híbridas</string>
|
||||||
<string name="emulation_screen_layout_original">Original</string>
|
<string name="emulation_screen_layout_original">Original</string>
|
||||||
<string name="emulation_portrait_layout_top_full">Por defecto</string>
|
<string name="emulation_portrait_layout_top_full">Por defecto</string>
|
||||||
|
<string name="emulation_secondary_display_default">Por defecto del sistema (espejo)</string>
|
||||||
<string name="emulation_screen_layout_custom">Estilo personalizado</string>
|
<string name="emulation_screen_layout_custom">Estilo personalizado</string>
|
||||||
<string name="emulation_small_screen_position">Posición Pantalla Pequeña</string>
|
<string name="emulation_small_screen_position">Posición Pantalla Pequeña</string>
|
||||||
<string name="small_screen_position_description">¿Dónde debería aparecer la pantalla pequeña en relación con la grande en Disposicion de Pantalla Grande?</string>
|
<string name="small_screen_position_description">¿Dónde debería aparecer la pantalla pequeña en relación con la grande en Disposicion de Pantalla Grande?</string>
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,7 @@
|
||||||
<string name="emulation_switch_screen_layout">Geometryczny Układ Ekranu</string>
|
<string name="emulation_switch_screen_layout">Geometryczny Układ Ekranu</string>
|
||||||
<string name="emulation_switch_portrait_layout">Pionowy Układ Ekranu</string>
|
<string name="emulation_switch_portrait_layout">Pionowy Układ Ekranu</string>
|
||||||
<string name="emulation_switch_secondary_layout">Układ ekranu wyświetlacza dodatkowego</string>
|
<string name="emulation_switch_secondary_layout">Układ ekranu wyświetlacza dodatkowego</string>
|
||||||
|
<string name="emulation_switch_secondary_layout_description">Układ używany przez podłączony dodatkowy ekran, przewodowy lub bezprzewodowy (Chromecast, Miracast)</string>
|
||||||
<string name="emulation_screen_layout_largescreen">Duży Ekran</string>
|
<string name="emulation_screen_layout_largescreen">Duży Ekran</string>
|
||||||
<string name="emulation_screen_layout_portrait">Ekran</string>
|
<string name="emulation_screen_layout_portrait">Ekran</string>
|
||||||
<string name="emulation_screen_layout_single">Pojedynczy ekran</string>
|
<string name="emulation_screen_layout_single">Pojedynczy ekran</string>
|
||||||
|
|
@ -424,6 +425,7 @@
|
||||||
<string name="emulation_screen_layout_hybrid">Hybrydowy Ekran</string>
|
<string name="emulation_screen_layout_hybrid">Hybrydowy Ekran</string>
|
||||||
<string name="emulation_screen_layout_original">Oryginalny</string>
|
<string name="emulation_screen_layout_original">Oryginalny</string>
|
||||||
<string name="emulation_portrait_layout_top_full">Domyślny</string>
|
<string name="emulation_portrait_layout_top_full">Domyślny</string>
|
||||||
|
<string name="emulation_secondary_display_default">Ustawienia domyślne systemu (mirror)</string>
|
||||||
<string name="emulation_screen_layout_custom">Niestandardowy Układ</string>
|
<string name="emulation_screen_layout_custom">Niestandardowy Układ</string>
|
||||||
<string name="emulation_small_screen_position">Pozycja małego ekranu</string>
|
<string name="emulation_small_screen_position">Pozycja małego ekranu</string>
|
||||||
<string name="small_screen_position_description">Gdzie powinien być wyświetlany mały ekran względem dużego w układzie dużego ekranu?</string>
|
<string name="small_screen_position_description">Gdzie powinien być wyświetlany mały ekran względem dużego w układzie dużego ekranu?</string>
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,14 @@
|
||||||
<string name="give_permission">Grant permission</string>
|
<string name="give_permission">Grant permission</string>
|
||||||
<string name="notification_warning">Skip granting the notification permission?</string>
|
<string name="notification_warning">Skip granting the notification permission?</string>
|
||||||
<string name="notification_warning_description">Azahar won\'t be able to notify you of important information.</string>
|
<string name="notification_warning_description">Azahar won\'t be able to notify you of important information.</string>
|
||||||
|
<string name="filesystem_permission_warning">Missing Permissions</string>
|
||||||
|
<string name="filesystem_permission_warning_description">Azahar requires permission to manage files on this device in order to store and manage its data.\n\nPlease grant the \"Filesystem\" permission before continuing.</string>
|
||||||
<string name="camera_permission">Camera</string>
|
<string name="camera_permission">Camera</string>
|
||||||
<string name="camera_permission_description">Grant the camera permission below to emulate the 3DS camera.</string>
|
<string name="camera_permission_description">Grant the camera permission below to emulate the 3DS camera.</string>
|
||||||
<string name="microphone_permission">Microphone</string>
|
<string name="microphone_permission">Microphone</string>
|
||||||
<string name="microphone_permission_description">Grant the microphone permission below to emulate the 3DS microphone.</string>
|
<string name="microphone_permission_description">Grant the microphone permission below to emulate the 3DS microphone.</string>
|
||||||
|
<string name="filesystem_permission">Filesystem</string>
|
||||||
|
<string name="filesystem_permission_description">Grant the filesystem permission below to allow Azahar to store files.</string>
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
<string name="add_games_warning">Skip selecting applications folder?</string>
|
<string name="add_games_warning">Skip selecting applications folder?</string>
|
||||||
<string name="add_games_warning_description">Software won\'t be displayed in the Applications list if a folder isn\'t selected.</string>
|
<string name="add_games_warning_description">Software won\'t be displayed in the Applications list if a folder isn\'t selected.</string>
|
||||||
|
|
@ -100,6 +104,9 @@
|
||||||
<string name="cannot_skip">You can\'t skip setting up the user folder</string>
|
<string name="cannot_skip">You can\'t skip setting up the user folder</string>
|
||||||
<string name="cannot_skip_directory_description">This step is required to allow Azahar to work. Please select a directory and then you can continue.</string>
|
<string name="cannot_skip_directory_description">This step is required to allow Azahar to work. Please select a directory and then you can continue.</string>
|
||||||
<string name="selecting_user_directory_without_write_permissions">You have lost write permissions on your <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">user data</a> directory, where saves and other information are stored. This can happen after some app or Android updates. Please re-select the directory to regain permissions so you can continue.</string>
|
<string name="selecting_user_directory_without_write_permissions">You have lost write permissions on your <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">user data</a> directory, where saves and other information are stored. This can happen after some app or Android updates. Please re-select the directory to regain permissions so you can continue.</string>
|
||||||
|
<string name="invalid_selection">Invalid Selection</string>
|
||||||
|
<string name="invalid_user_directory">The user directory selection was invalid.\nPlease re-select the user directory, ensuring that you navigate to it from the root of your device\'s storage.</string>
|
||||||
|
<string name="filesystem_permission_lost">Azahar has lost permission to manage files on this device. This can happen after some app or Android updates. Please re-grant this permission on the next screen to continue using the app.</string>
|
||||||
<string name="cannot_skip_directory_help" translatable="false">https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage</string>
|
<string name="cannot_skip_directory_help" translatable="false">https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage</string>
|
||||||
<string name="set_up_theme_settings">Theme Settings</string>
|
<string name="set_up_theme_settings">Theme Settings</string>
|
||||||
<string name="setup_theme_settings_description">Configure your theme preferences for Azahar.</string>
|
<string name="setup_theme_settings_description">Configure your theme preferences for Azahar.</string>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.11.1" apply false
|
id("com.android.application") version "8.13.1" apply false
|
||||||
id("com.android.library") version "8.11.1" apply false
|
id("com.android.library") version "8.13.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
|
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
|
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,10 @@ if (ENABLE_QT AND UNIX AND NOT APPLE)
|
||||||
target_link_libraries(citra_meta PRIVATE Qt6::DBus gamemode)
|
target_link_libraries(citra_meta PRIVATE Qt6::DBus gamemode)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_QT AND APPLE)
|
||||||
|
target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (ENABLE_QT AND USE_DISCORD_PRESENCE)
|
if (ENABLE_QT AND USE_DISCORD_PRESENCE)
|
||||||
target_link_libraries(citra_meta PRIVATE discord-rpc)
|
target_link_libraries(citra_meta PRIVATE discord-rpc)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -172,12 +172,12 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
||||||
multiplayer/state.h
|
multiplayer/state.h
|
||||||
multiplayer/validation.h
|
multiplayer/validation.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
|
qt_image_interface.cpp
|
||||||
|
qt_image_interface.h
|
||||||
uisettings.cpp
|
uisettings.cpp
|
||||||
uisettings.h
|
uisettings.h
|
||||||
user_data_migration.cpp
|
user_data_migration.cpp
|
||||||
user_data_migration.h
|
user_data_migration.h
|
||||||
qt_image_interface.cpp
|
|
||||||
qt_image_interface.h
|
|
||||||
util/clickable_label.cpp
|
util/clickable_label.cpp
|
||||||
util/clickable_label.h
|
util/clickable_label.h
|
||||||
util/graphics_device_info.cpp
|
util/graphics_device_info.cpp
|
||||||
|
|
@ -190,6 +190,13 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
||||||
util/util.h
|
util/util.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
target_sources(citra_qt PUBLIC
|
||||||
|
qt_swizzle.h
|
||||||
|
qt_swizzle.mm
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
file(GLOB COMPAT_LIST
|
file(GLOB COMPAT_LIST
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||||
|
|
@ -275,7 +282,13 @@ if (NOT WIN32)
|
||||||
target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (APPLE)
|
||||||
|
target_link_libraries(citra_qt PRIVATE Qt6::GuiPrivate)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
target_link_libraries(citra_qt PRIVATE Qt6::QDarwinCameraPermissionPlugin)
|
||||||
|
elseif (UNIX)
|
||||||
target_link_libraries(citra_qt PRIVATE Qt6::DBus gamemode)
|
target_link_libraries(citra_qt PRIVATE Qt6::DBus gamemode)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@
|
||||||
#include "citra_qt/movie/movie_record_dialog.h"
|
#include "citra_qt/movie/movie_record_dialog.h"
|
||||||
#include "citra_qt/multiplayer/state.h"
|
#include "citra_qt/multiplayer/state.h"
|
||||||
#include "citra_qt/qt_image_interface.h"
|
#include "citra_qt/qt_image_interface.h"
|
||||||
|
#include "citra_qt/qt_swizzle.h"
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
#include "common/play_time_manager.h"
|
#include "common/play_time_manager.h"
|
||||||
#ifdef ENABLE_QT_UPDATE_CHECKER
|
#ifdef ENABLE_QT_UPDATE_CHECKER
|
||||||
|
|
@ -114,6 +115,7 @@
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include "common/apple_authorization.h"
|
#include "common/apple_authorization.h"
|
||||||
|
Q_IMPORT_PLUGIN(QDarwinCameraPermissionPlugin);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
|
@ -4112,6 +4114,11 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchQtFrontend(int argc, char* argv[]) {
|
void LaunchQtFrontend(int argc, char* argv[]) {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// Ensure that the linker doesn't optimize qt_swizzle.mm out of existence.
|
||||||
|
QtSwizzle::Dummy();
|
||||||
|
#endif
|
||||||
|
|
||||||
Common::DetachedTasks detached_tasks;
|
Common::DetachedTasks detached_tasks;
|
||||||
|
|
||||||
#if MICROPROFILE_ENABLED
|
#if MICROPROFILE_ENABLED
|
||||||
|
|
|
||||||
9
src/citra_qt/qt_swizzle.h
Normal file
9
src/citra_qt/qt_swizzle.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
namespace QtSwizzle {
|
||||||
|
|
||||||
|
void Dummy();
|
||||||
|
|
||||||
|
} // namespace QtSwizzle
|
||||||
48
src/citra_qt/qt_swizzle.mm
Normal file
48
src/citra_qt/qt_swizzle.mm
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#import <QtGui/private/qmetallayer_p.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
|
namespace QtSwizzle {
|
||||||
|
|
||||||
|
void Dummy() {
|
||||||
|
// Call this anywhere to make sure that qt_swizzle.mm is linked.
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QtSwizzle
|
||||||
|
|
||||||
|
@implementation QMetalLayer (AzaharPatch)
|
||||||
|
|
||||||
|
+ (void)load {
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
Class targetClass = [self class];
|
||||||
|
|
||||||
|
// Get the original and swizzled methods
|
||||||
|
Method originalMethod =
|
||||||
|
class_getInstanceMethod(targetClass, @selector(setNeedsDisplayInRect:));
|
||||||
|
Method swizzledMethod =
|
||||||
|
class_getInstanceMethod(targetClass, @selector(swizzled_setNeedsDisplayInRect:));
|
||||||
|
|
||||||
|
// Swap the implementations
|
||||||
|
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)swizzled_setNeedsDisplayInRect:(CGRect)rect {
|
||||||
|
constexpr auto tooBig = 1e10; // Arbitrary large number
|
||||||
|
|
||||||
|
// Check for problematic huge rectangles
|
||||||
|
if ((!self.needsDisplay) && (rect.size.width > tooBig || rect.size.height > tooBig ||
|
||||||
|
rect.origin.x < -tooBig || rect.origin.y < -tooBig)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original implementation
|
||||||
|
[self swizzled_setNeedsDisplayInRect:rect];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
// 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.
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
|
#include <boost/uuid/uuid_generators.hpp>
|
||||||
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
#include "common/android_storage.h"
|
#include "common/android_storage.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
namespace AndroidStorage {
|
namespace AndroidStorage {
|
||||||
JNIEnv* GetEnvForThread() {
|
JNIEnv* GetEnvForThread() {
|
||||||
|
|
@ -80,8 +84,9 @@ void CleanupJNI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreateFile(const std::string& directory, const std::string& filename) {
|
bool CreateFile(const std::string& directory, const std::string& filename) {
|
||||||
if (create_file == nullptr)
|
if (create_file == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
auto env = GetEnvForThread();
|
auto env = GetEnvForThread();
|
||||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||||
jstring j_filename = env->NewStringUTF(filename.c_str());
|
jstring j_filename = env->NewStringUTF(filename.c_str());
|
||||||
|
|
@ -89,8 +94,9 @@ bool CreateFile(const std::string& directory, const std::string& filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreateDir(const std::string& directory, const std::string& filename) {
|
bool CreateDir(const std::string& directory, const std::string& filename) {
|
||||||
if (create_dir == nullptr)
|
if (create_dir == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
auto env = GetEnvForThread();
|
auto env = GetEnvForThread();
|
||||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||||
jstring j_directory_name = env->NewStringUTF(filename.c_str());
|
jstring j_directory_name = env->NewStringUTF(filename.c_str());
|
||||||
|
|
@ -98,8 +104,9 @@ bool CreateDir(const std::string& directory, const std::string& filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
||||||
if (open_content_uri == nullptr)
|
if (open_content_uri == nullptr) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
const char* mode = "";
|
const char* mode = "";
|
||||||
switch (openmode) {
|
switch (openmode) {
|
||||||
|
|
@ -135,8 +142,9 @@ int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
||||||
|
|
||||||
std::vector<std::string> GetFilesName(const std::string& filepath) {
|
std::vector<std::string> GetFilesName(const std::string& filepath) {
|
||||||
auto vector = std::vector<std::string>();
|
auto vector = std::vector<std::string>();
|
||||||
if (get_files_name == nullptr)
|
if (get_files_name == nullptr) {
|
||||||
return vector;
|
return vector;
|
||||||
|
}
|
||||||
auto env = GetEnvForThread();
|
auto env = GetEnvForThread();
|
||||||
jstring j_filepath = env->NewStringUTF(filepath.c_str());
|
jstring j_filepath = env->NewStringUTF(filepath.c_str());
|
||||||
auto j_object =
|
auto j_object =
|
||||||
|
|
@ -150,10 +158,37 @@ std::vector<std::string> GetFilesName(const std::string& filepath) {
|
||||||
return vector;
|
return vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> GetUserDirectory() {
|
||||||
|
if (get_user_directory == nullptr) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Unable to locate user directory: Function with ID 'get_user_directory' is missing");
|
||||||
|
}
|
||||||
|
auto env = GetEnvForThread();
|
||||||
|
auto j_user_directory =
|
||||||
|
(jstring)(env->CallStaticObjectMethod(native_library, get_user_directory, nullptr));
|
||||||
|
auto result = env->GetStringUTFChars(j_user_directory, nullptr);
|
||||||
|
if (result == "") {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetBuildFlavor() {
|
||||||
|
if (get_build_flavor == nullptr) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Unable get build flavor: Function with ID 'get_build_flavor' is missing");
|
||||||
|
}
|
||||||
|
auto env = GetEnvForThread();
|
||||||
|
const auto jflavor =
|
||||||
|
(jstring)(env->CallStaticObjectMethod(native_library, get_build_flavor, nullptr));
|
||||||
|
return env->GetStringUTFChars(jflavor, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
bool CopyFile(const std::string& source, const std::string& destination_path,
|
bool CopyFile(const std::string& source, const std::string& destination_path,
|
||||||
const std::string& destination_filename) {
|
const std::string& destination_filename) {
|
||||||
if (copy_file == nullptr)
|
if (copy_file == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
auto env = GetEnvForThread();
|
auto env = GetEnvForThread();
|
||||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||||
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
||||||
|
|
@ -163,8 +198,14 @@ bool CopyFile(const std::string& source, const std::string& destination_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenameFile(const std::string& source, const std::string& filename) {
|
bool RenameFile(const std::string& source, const std::string& filename) {
|
||||||
if (rename_file == nullptr)
|
if (rename_file == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
if (std::string(FileUtil::GetFilename(source)) ==
|
||||||
|
std::string(FileUtil::GetFilename(filename))) {
|
||||||
|
// TODO: Should this be treated as a success or failure?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto env = GetEnvForThread();
|
auto env = GetEnvForThread();
|
||||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||||
jstring j_destination_path = env->NewStringUTF(filename.c_str());
|
jstring j_destination_path = env->NewStringUTF(filename.c_str());
|
||||||
|
|
@ -172,6 +213,86 @@ bool RenameFile(const std::string& source, const std::string& filename) {
|
||||||
j_destination_path);
|
j_destination_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UpdateDocumentLocation(const std::string& source_path, const std::string& destination_path) {
|
||||||
|
if (update_document_location == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto env = GetEnvForThread();
|
||||||
|
jstring j_source_path = env->NewStringUTF(source_path.c_str());
|
||||||
|
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
||||||
|
return env->CallStaticBooleanMethod(native_library, update_document_location, j_source_path,
|
||||||
|
j_destination_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoveFile(const std::string& filename, const std::string& source_dir_path,
|
||||||
|
const std::string& destination_dir_path) {
|
||||||
|
if (move_file == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (source_dir_path == destination_dir_path) {
|
||||||
|
// TODO: Should this be treated as a success or failure?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto env = GetEnvForThread();
|
||||||
|
jstring j_filename = env->NewStringUTF(filename.c_str());
|
||||||
|
jstring j_source_dir_path = env->NewStringUTF(source_dir_path.c_str());
|
||||||
|
jstring j_destination_dir_path = env->NewStringUTF(destination_dir_path.c_str());
|
||||||
|
return env->CallStaticBooleanMethod(native_library, move_file, j_filename, j_source_dir_path,
|
||||||
|
j_destination_dir_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoveAndRenameFile(const std::string& src_full_path, const std::string& dest_full_path) {
|
||||||
|
if (src_full_path == dest_full_path) {
|
||||||
|
// TODO: Should this be treated as a success or failure?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto src_filename = std::string(FileUtil::GetFilename(src_full_path));
|
||||||
|
const auto src_parent_path = std::string(FileUtil::GetParentPath(src_full_path));
|
||||||
|
const auto dest_filename = std::string(FileUtil::GetFilename(dest_full_path));
|
||||||
|
const auto dest_parent_path = std::string(FileUtil::GetParentPath(dest_full_path));
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
const std::string tmp_path = "/tmp";
|
||||||
|
AndroidStorage::CreateDir("/", "tmp");
|
||||||
|
|
||||||
|
// If a simultaneous move and rename are not necessary, use individual methods
|
||||||
|
if (src_filename == dest_filename || src_parent_path == dest_parent_path) {
|
||||||
|
if (src_filename != dest_filename) {
|
||||||
|
return AndroidStorage::RenameFile(src_full_path, dest_filename);
|
||||||
|
} else if (src_parent_path != dest_parent_path) {
|
||||||
|
return AndroidStorage::MoveFile(src_filename, src_parent_path, dest_parent_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Create directory named after UUID inside /tmp to house the moved file.
|
||||||
|
// This prevents clashes if files with the same name are moved simultaneously.
|
||||||
|
const auto uuid = boost::uuids::to_string(boost::uuids::time_generator_v7()());
|
||||||
|
const auto allocated_tmp_path = tmp_path + "/" + uuid;
|
||||||
|
AndroidStorage::CreateDir(tmp_path, uuid);
|
||||||
|
|
||||||
|
// Step 2: Attempt to move to allocated temporary directory.
|
||||||
|
// If this step fails, skip everything except the cleanup.
|
||||||
|
result = AndroidStorage::MoveFile(src_filename, src_parent_path, allocated_tmp_path);
|
||||||
|
if (result == true) {
|
||||||
|
// Step 3: Rename to desired file name.
|
||||||
|
AndroidStorage::RenameFile((allocated_tmp_path + "/" + src_filename), dest_filename);
|
||||||
|
|
||||||
|
// Step 4: If a file with the desired name in the destination exists, remove it.
|
||||||
|
AndroidStorage::DeleteDocument(dest_full_path);
|
||||||
|
|
||||||
|
// Step 5: Attempt to move file to desired location.
|
||||||
|
// If this step fails, move the file back to where it came from.
|
||||||
|
result = AndroidStorage::MoveFile(dest_filename, allocated_tmp_path, dest_parent_path);
|
||||||
|
if (result == false) {
|
||||||
|
AndroidStorage::MoveAndRenameFile((allocated_tmp_path + "/" + dest_filename),
|
||||||
|
src_full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Step 6: Clean up the allocated temp directory.
|
||||||
|
AndroidStorage::DeleteDocument(allocated_tmp_path);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||||
F(FunctionName, ReturnValue, JMethodID, Caller)
|
F(FunctionName, ReturnValue, JMethodID, Caller)
|
||||||
#define F(FunctionName, ReturnValue, JMethodID, Caller) \
|
#define F(FunctionName, ReturnValue, JMethodID, Caller) \
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -19,12 +19,23 @@
|
||||||
open_content_uri, "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") \
|
open_content_uri, "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") \
|
||||||
V(GetFilesName, std::vector<std::string>, (const std::string& filepath), get_files_name, \
|
V(GetFilesName, std::vector<std::string>, (const std::string& filepath), get_files_name, \
|
||||||
"getFilesName", "(Ljava/lang/String;)[Ljava/lang/String;") \
|
"getFilesName", "(Ljava/lang/String;)[Ljava/lang/String;") \
|
||||||
|
V(GetUserDirectory, std::optional<std::string>, (), get_user_directory, "getUserDirectory", \
|
||||||
|
"(Landroid/net/Uri;)Ljava/lang/String;") \
|
||||||
V(CopyFile, bool, \
|
V(CopyFile, bool, \
|
||||||
(const std::string& source, const std::string& destination_path, \
|
(const std::string& source, const std::string& destination_path, \
|
||||||
const std::string& destination_filename), \
|
const std::string& destination_filename), \
|
||||||
copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") \
|
copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||||
V(RenameFile, bool, (const std::string& source, const std::string& filename), rename_file, \
|
V(RenameFile, bool, (const std::string& source, const std::string& filename), rename_file, \
|
||||||
"renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z")
|
"renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||||
|
V(UpdateDocumentLocation, bool, \
|
||||||
|
(const std::string& source_path, const std::string& destination_path), \
|
||||||
|
update_document_location, "updateDocumentLocation", \
|
||||||
|
"(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||||
|
V(GetBuildFlavor, std::string, (), get_build_flavor, "getBuildFlavor", "()Ljava/lang/String;") \
|
||||||
|
V(MoveFile, bool, \
|
||||||
|
(const std::string& filename, const std::string& source_dir_path, \
|
||||||
|
const std::string& destination_dir_path), \
|
||||||
|
move_file, "moveFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z")
|
||||||
#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
|
#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
|
||||||
V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
|
V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
|
||||||
"(Ljava/lang/String;)Z") \
|
"(Ljava/lang/String;)Z") \
|
||||||
|
|
@ -44,6 +55,7 @@ ANDROID_STORAGE_FUNCTIONS(FS)
|
||||||
#undef F
|
#undef F
|
||||||
#undef FS
|
#undef FS
|
||||||
#undef FR
|
#undef FR
|
||||||
|
bool MoveAndRenameFile(const std::string& src_full_path, const std::string& dest_full_path);
|
||||||
// Reference:
|
// Reference:
|
||||||
// https://developer.android.com/reference/android/os/ParcelFileDescriptor#parseMode(java.lang.String)
|
// https://developer.android.com/reference/android/os/ParcelFileDescriptor#parseMode(java.lang.String)
|
||||||
enum class AndroidOpenMode {
|
enum class AndroidOpenMode {
|
||||||
|
|
|
||||||
|
|
@ -304,20 +304,31 @@ bool DeleteDir(const std::string& filename) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
|
||||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFullPath, destFullPath);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
if (_wrename(Common::UTF8ToUTF16W(srcFullPath).c_str(),
|
||||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
Common::UTF8ToUTF16W(destFullPath).c_str()) == 0)
|
||||||
return true;
|
return true;
|
||||||
#elif ANDROID
|
#elif ANDROID
|
||||||
if (AndroidStorage::RenameFile(srcFilename, std::string(GetFilename(destFilename))))
|
// srcFullPath and destFullPath are relative to the user directory
|
||||||
return true;
|
if (AndroidStorage::GetBuildFlavor() == "googlePlay") {
|
||||||
|
if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath))
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
std::optional<std::string> userDirLocation = AndroidStorage::GetUserDirectory();
|
||||||
|
if (userDirLocation && rename((*userDirLocation + srcFullPath).c_str(),
|
||||||
|
(*userDirLocation + destFullPath).c_str()) == 0) {
|
||||||
|
AndroidStorage::UpdateDocumentLocation(srcFullPath, destFullPath);
|
||||||
|
// ^ TODO: This shouldn't fail, but what should we do if it somehow does?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
if (rename(srcFullPath.c_str(), destFullPath.c_str()) == 0)
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFullPath, destFullPath,
|
||||||
GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,13 +136,13 @@ bool Delete(const std::string& filename);
|
||||||
// Deletes a directory filename, returns true on success
|
// Deletes a directory filename, returns true on success
|
||||||
bool DeleteDir(const std::string& filename);
|
bool DeleteDir(const std::string& filename);
|
||||||
|
|
||||||
// renames file srcFilename to destFilename, returns true on success
|
// Renames file srcFullPath to destFullPath, returns true on success
|
||||||
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
bool Rename(const std::string& srcFullPath, const std::string& destFullPath);
|
||||||
|
|
||||||
// copies file srcFilename to destFilename, returns true on success
|
// Copies file srcFilename to destFilename, returns true on success
|
||||||
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
||||||
|
|
||||||
// creates an empty file filename, returns true on success
|
// Creates an empty file filename, returns true on success
|
||||||
bool CreateEmptyFile(const std::string& filename);
|
bool CreateEmptyFile(const std::string& filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,10 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!layout.bottom_screen_enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||||
|
|
||||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||||
|
|
|
||||||
|
|
@ -864,8 +864,10 @@ bool CIAFile::Close() {
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install...");
|
LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install...");
|
||||||
if (!is_additional_content) {
|
if (!is_additional_content) {
|
||||||
FileUtil::DeleteDirRecursively(
|
// Only delete the content folder as there may be user save data in the title folder.
|
||||||
GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()));
|
const std::string title_content_path =
|
||||||
|
GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()) + "content/";
|
||||||
|
FileUtil::DeleteDirRecursively(title_content_path);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,31 +61,6 @@ constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS
|
||||||
namespace {
|
namespace {
|
||||||
static bool IsLowRefreshRate() {
|
static bool IsLowRefreshRate() {
|
||||||
#if defined(__APPLE__) || defined(ENABLE_SDL2)
|
#if defined(__APPLE__) || defined(ENABLE_SDL2)
|
||||||
#ifdef __APPLE__ // Need a special implementation because MacOS kills itself in disgust if the
|
|
||||||
// input thread calls SDL_PumpEvents at the same time as we're in SDL_Init here.
|
|
||||||
const auto cur_refresh_rate = AppleUtils::GetRefreshRate();
|
|
||||||
#elif defined(ENABLE_SDL2)
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
||||||
LOG_ERROR(Render_Vulkan, "SDL video failed to initialize, unable to check refresh rate");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayMode cur_display_mode;
|
|
||||||
SDL_GetCurrentDisplayMode(0, &cur_display_mode); // TODO: Multimonitor handling. -OS
|
|
||||||
const auto cur_refresh_rate = cur_display_mode.refresh_rate;
|
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
#endif // __APPLE__
|
|
||||||
|
|
||||||
if (cur_refresh_rate < SCREEN_REFRESH_RATE) {
|
|
||||||
LOG_WARNING(Render_Vulkan,
|
|
||||||
"Detected refresh rate lower than the emulated 3DS screen: {}hz. FIFO will "
|
|
||||||
"be disabled",
|
|
||||||
cur_refresh_rate);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif // defined(__APPLE__) || defined(ENABLE_SDL2)
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
// Apple's low power mode sometimes limits applications to 30fps without changing the refresh
|
// Apple's low power mode sometimes limits applications to 30fps without changing the refresh
|
||||||
// rate, meaning the above code doesn't catch it.
|
// rate, meaning the above code doesn't catch it.
|
||||||
|
|
@ -94,8 +69,34 @@ static bool IsLowRefreshRate() {
|
||||||
"framerate. FIFO will be disabled");
|
"framerate. FIFO will be disabled");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
const auto cur_refresh_rate = AppleUtils::GetRefreshRate();
|
||||||
|
#elif defined(ENABLE_SDL2)
|
||||||
|
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Attempted to check refresh rate via SDL, but failed because "
|
||||||
|
"SDL_INIT_VIDEO wasn't initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_DisplayMode cur_display_mode;
|
||||||
|
SDL_GetCurrentDisplayMode(0, &cur_display_mode); // TODO: Multimonitor handling. -OS
|
||||||
|
|
||||||
|
const auto cur_refresh_rate = cur_display_mode.refresh_rate;
|
||||||
|
#endif // ENABLE_SDL2
|
||||||
|
|
||||||
|
if (cur_refresh_rate < SCREEN_REFRESH_RATE) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Detected refresh rate lower than the emulated 3DS screen: {}hz. FIFO will "
|
||||||
|
"be disabled",
|
||||||
|
cur_refresh_rate);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Render_Vulkan, "Refresh rate is above emulated 3DS screen: {}hz. Good.",
|
||||||
|
cur_refresh_rate);
|
||||||
|
}
|
||||||
|
#endif // defined(__APPLE__) || defined(ENABLE_SDL2)
|
||||||
|
|
||||||
|
// We have no available method of checking refresh rate. Just assume that everything is fine :)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2022 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.
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the maximum supported elements in a texel buffer
|
/// Returns the maximum supported elements in a texel buffer
|
||||||
u32 MaxTexelBufferElements() const {
|
u64 MaxTexelBufferElements() const {
|
||||||
return properties.limits.maxTexelBufferElements;
|
return properties.limits.maxTexelBufferElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -556,12 +556,15 @@ bool PipelineCache::EnsureDirectories() const {
|
||||||
};
|
};
|
||||||
|
|
||||||
return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
|
return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
|
||||||
create_dir(GetPipelineCacheDir());
|
create_dir(GetVulkanDir()) && create_dir(GetPipelineCacheDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PipelineCache::GetVulkanDir() const {
|
||||||
|
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PipelineCache::GetPipelineCacheDir() const {
|
std::string PipelineCache::GetPipelineCacheDir() const {
|
||||||
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP + "pipeline" +
|
return GetVulkanDir() + "pipeline" + DIR_SEP;
|
||||||
DIR_SEP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading,
|
void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ private:
|
||||||
/// Create pipeline cache directories. Returns true on success.
|
/// Create pipeline cache directories. Returns true on success.
|
||||||
bool EnsureDirectories() const;
|
bool EnsureDirectories() const;
|
||||||
|
|
||||||
|
/// Returns the Vulkan shader directory
|
||||||
|
std::string GetVulkanDir() const;
|
||||||
|
|
||||||
/// Returns the pipeline cache storage dir
|
/// Returns the pipeline cache storage dir
|
||||||
std::string GetPipelineCacheDir() const;
|
std::string GetPipelineCacheDir() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014 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.
|
||||||
|
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
#endif
|
#endif
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
#include <SDL.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
|
||||||
std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
|
std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
|
||||||
|
|
@ -29,6 +33,12 @@ std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
case Settings::GraphicsAPI::Vulkan:
|
case Settings::GraphicsAPI::Vulkan:
|
||||||
|
#if defined(ENABLE_SDL2) && !defined(__APPLE__)
|
||||||
|
// TODO: When we migrate to SDL3, refactor so that we don't need to init here.
|
||||||
|
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
|
||||||
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
}
|
||||||
|
#endif // ENABLE_SDL2
|
||||||
return std::make_unique<Vulkan::RendererVulkan>(system, pica, emu_window, secondary_window);
|
return std::make_unique<Vulkan::RendererVulkan>(system, pica, emu_window, secondary_window);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ The scripts in this directory assume that your current working directory is the
|
||||||
## Pre-release checklist
|
## Pre-release checklist
|
||||||
|
|
||||||
- [ ] Update compatibility list
|
- [ ] Update compatibility list
|
||||||
- [ ] Update translations
|
- [ ] If this is a major release (2123.1 -> major.minor), update translations
|
||||||
|
|
||||||
### Note:
|
### Note:
|
||||||
|
|
||||||
|
|
|
||||||
6
tools/purge-github-cache.sh
Executable file
6
tools/purge-github-cache.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
# This script assumes that the Github CLI is installed and that
|
||||||
|
# the authenticated user has appropriate authorization.
|
||||||
|
|
||||||
|
gh cache delete --all
|
||||||
Loading…
Add table
Reference in a new issue