mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-12-16 12:08:49 +00:00
Merge remote-tracking branch 'origin/master' into warmenhoven/dev/azahar_libretro
This commit is contained in:
commit
eb6295354d
@ -318,7 +318,7 @@ find_package(Threads REQUIRED)
|
||||
|
||||
if (ENABLE_QT)
|
||||
if (NOT USE_SYSTEM_QT)
|
||||
download_qt(6.7.2)
|
||||
download_qt(6.9.2)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
|
||||
@ -20,9 +20,9 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
|
||||
set(arch_path "mingw_64")
|
||||
elseif (MSVC)
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
set(arch_path "msvc2019_arm64")
|
||||
set(arch_path "msvc2022_arm64")
|
||||
elseif ("x86_64" IN_LIST ARCHITECTURE)
|
||||
set(arch_path "msvc2019_64")
|
||||
set(arch_path "msvc2022_64")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
||||
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.
|
||||
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")
|
||||
# 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: ^ Is this still true with msvc2022?
|
||||
# set(host_arch_path "msvc2019_arm64")
|
||||
set(host_arch_path "msvc2019_64")
|
||||
set(host_arch_path "msvc2022_64")
|
||||
endif()
|
||||
set(host_arch "win64_${host_arch_path}")
|
||||
else()
|
||||
@ -105,7 +106,7 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
|
||||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
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)
|
||||
set(aqt_path "${base_path}/aqt.exe")
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
|
||||
3
dist/apple/Info.plist.in
vendored
3
dist/apple/Info.plist.in
vendored
@ -75,6 +75,9 @@
|
||||
<true/>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<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>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
@ -1 +1 @@
|
||||
Subproject commit 4f39041699412873d0afcec89a9313148a192647
|
||||
Subproject commit a36decbe43d0e5a570ac3d3ba9a0b226dc832a17
|
||||
28
externals/CMakeLists.txt
vendored
28
externals/CMakeLists.txt
vendored
@ -64,7 +64,7 @@ target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
if(USE_SYSTEM_CRYPTOPP)
|
||||
find_package(cryptopp REQUIRED)
|
||||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp SYSTEM INTERFACE cryptopp::cryptopp)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||
@ -89,7 +89,7 @@ target_disable_warnings(dds-ktx)
|
||||
if(USE_SYSTEM_FMT)
|
||||
add_library(fmt INTERFACE)
|
||||
find_package(fmt REQUIRED)
|
||||
target_link_libraries(fmt SYSTEM INTERFACE fmt::fmt)
|
||||
target_link_libraries(fmt INTERFACE fmt::fmt)
|
||||
else()
|
||||
option(FMT_INSTALL "" ON)
|
||||
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||
@ -101,7 +101,7 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
if(USE_SYSTEM_XBYAK)
|
||||
find_package(xbyak REQUIRED)
|
||||
add_library(xbyak INTERFACE)
|
||||
target_link_libraries(xbyak SYSTEM INTERFACE xbyak::xbyak)
|
||||
target_link_libraries(xbyak INTERFACE xbyak::xbyak)
|
||||
else()
|
||||
add_subdirectory(xbyak EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
@ -117,7 +117,7 @@ if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||
if(USE_SYSTEM_DYNARMIC)
|
||||
find_package(dynarmic REQUIRED)
|
||||
add_library(dynarmic INTERFACE)
|
||||
target_link_libraries(dynarmic SYSTEM INTERFACE dynarmic::dynarmic)
|
||||
target_link_libraries(dynarmic INTERFACE dynarmic::dynarmic)
|
||||
# The dynarmic package's cmake files are helpfully completely silent
|
||||
# so we have to inform the user of its status ourselves
|
||||
if(TARGET dynarmic::dynarmic)
|
||||
@ -140,7 +140,7 @@ endif()
|
||||
if(USE_SYSTEM_INIH)
|
||||
find_package(inih REQUIRED COMPONENTS inih inir)
|
||||
add_library(inih INTERFACE)
|
||||
target_link_libraries(inih SYSTEM INTERFACE inih::inih inih::inir)
|
||||
target_link_libraries(inih INTERFACE inih::inih inih::inir)
|
||||
else()
|
||||
add_subdirectory(inih)
|
||||
endif()
|
||||
@ -221,7 +221,7 @@ if(USE_SYSTEM_ZSTD)
|
||||
if(TARGET zstd::libzstd_shared)
|
||||
message(STATUS "Found system Zstandard")
|
||||
endif()
|
||||
target_link_libraries(zstd SYSTEM INTERFACE zstd::libzstd_shared)
|
||||
target_link_libraries(zstd INTERFACE zstd::libzstd_shared)
|
||||
else()
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
@ -256,7 +256,7 @@ endif()
|
||||
if(USE_SYSTEM_ENET)
|
||||
find_package(libenet REQUIRED)
|
||||
add_library(enet INTERFACE)
|
||||
target_link_libraries(enet SYSTEM INTERFACE libenet::libenet)
|
||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||
else()
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
@ -267,7 +267,7 @@ if (ENABLE_CUBEB)
|
||||
if(USE_SYSTEM_CUBEB)
|
||||
find_package(cubeb REQUIRED)
|
||||
add_library(cubeb INTERFACE)
|
||||
target_link_libraries(cubeb SYSTEM INTERFACE cubeb::cubeb)
|
||||
target_link_libraries(cubeb INTERFACE cubeb::cubeb)
|
||||
if(TARGET cubeb::cubeb)
|
||||
message(STATUS "Found system cubeb")
|
||||
endif()
|
||||
@ -367,7 +367,7 @@ if(USE_SYSTEM_CPP_HTTPLIB)
|
||||
target_disable_warnings(httplib)
|
||||
else()
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib SYSTEM INTERFACE httplib::httplib)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
@ -390,7 +390,7 @@ if (ENABLE_WEB_SERVICE)
|
||||
if (USE_SYSTEM_CPP_JWT)
|
||||
find_package(cpp-jwt REQUIRED)
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_link_libraries(cpp-jwt SYSTEM INTERFACE cpp-jwt::cpp-jwt)
|
||||
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
||||
else()
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||
@ -403,7 +403,7 @@ endif()
|
||||
if(USE_SYSTEM_LODEPNG)
|
||||
add_library(lodepng INTERFACE)
|
||||
find_package(lodepng REQUIRED)
|
||||
target_link_libraries(lodepng SYSTEM INTERFACE lodepng::lodepng)
|
||||
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
|
||||
else()
|
||||
add_subdirectory(lodepng)
|
||||
endif()
|
||||
@ -420,7 +420,7 @@ if (ENABLE_OPENAL)
|
||||
if(USE_SYSTEM_OPENAL)
|
||||
add_library(OpenAL INTERFACE)
|
||||
find_package(OpenAL REQUIRED)
|
||||
target_link_libraries(OpenAL SYSTEM INTERFACE OpenAL::OpenAL)
|
||||
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
|
||||
else()
|
||||
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
||||
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
||||
@ -489,7 +489,7 @@ if (ENABLE_VULKAN)
|
||||
find_package(VulkanMemoryAllocator REQUIRED)
|
||||
if(TARGET GPUOpen::VulkanMemoryAllocator)
|
||||
message(STATUS "Found VulkanMemoryAllocator")
|
||||
target_link_libraries(vma SYSTEM INTERFACE GPUOpen::VulkanMemoryAllocator)
|
||||
target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator)
|
||||
endif()
|
||||
else()
|
||||
add_library(vma INTERFACE)
|
||||
@ -503,7 +503,7 @@ if (ENABLE_VULKAN)
|
||||
find_package(Vulkan REQUIRED)
|
||||
if(TARGET Vulkan::Headers)
|
||||
message(STATUS "Found Vulkan headers")
|
||||
target_link_libraries(vulkan-headers SYSTEM INTERFACE Vulkan::Headers)
|
||||
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
|
||||
endif()
|
||||
else()
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
|
||||
@ -64,7 +64,7 @@ android {
|
||||
// The application ID refers to Lime3DS to allow for
|
||||
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
|
||||
applicationId = "io.github.lime3ds.android"
|
||||
minSdk = 28
|
||||
minSdk = 29
|
||||
targetSdk = 35
|
||||
versionCode = autoVersion
|
||||
versionName = getGitVersion()
|
||||
@ -186,7 +186,7 @@ dependencies {
|
||||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
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"))
|
||||
onlyIfModified(true)
|
||||
}
|
||||
|
||||
@ -60,7 +60,15 @@ class EmulationActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
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
|
||||
get() {
|
||||
@ -77,8 +85,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
ThemeUtil.setTheme(this)
|
||||
settingsViewModel.settings.loadSettings()
|
||||
super.onCreate(savedInstanceState)
|
||||
secondaryDisplay = SecondaryDisplay(this);
|
||||
secondaryDisplay.updateDisplay();
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
@ -101,13 +109,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
windowManager.defaultDisplay.rotation
|
||||
)
|
||||
|
||||
EmulationLifecycleUtil.addShutdownHook(hook = {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
finishAffinity()
|
||||
} else {
|
||||
this.finish()
|
||||
}
|
||||
})
|
||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||
|
||||
isEmulationRunning = true
|
||||
instance = this
|
||||
@ -165,12 +167,12 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationLifecycleUtil.clear()
|
||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
isEmulationRunning = false
|
||||
instance = null
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD();
|
||||
secondaryDisplay.releaseVD()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@ -23,15 +23,12 @@ class SecondaryDisplay(val context: Context) {
|
||||
private val vd: VirtualDisplay
|
||||
|
||||
init {
|
||||
val st = SurfaceTexture(0)
|
||||
st.setDefaultBufferSize(1920, 1080)
|
||||
val vdSurface = Surface(st)
|
||||
vd = displayManager.createVirtualDisplay(
|
||||
"HiddenDisplay",
|
||||
1920,
|
||||
1080,
|
||||
320,
|
||||
vdSurface,
|
||||
null,
|
||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
|
||||
)
|
||||
}
|
||||
|
||||
@ -19,13 +19,14 @@ enum class BooleanSetting(
|
||||
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
|
||||
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
|
||||
OVERLAY_SHOW_FPS("overlay_show_fps", Settings.SECTION_LAYOUT, true),
|
||||
OVERLAY_SHOW_FRAMETIME("overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
|
||||
OVERLAY_SHOW_SPEED("overlay_show_speed", Settings.SECTION_LAYOUT, false),
|
||||
OVERLAY_SHOW_APP_RAM_USAGE("overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
|
||||
OVERLAY_SHOW_AVAILABLE_RAM("overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
|
||||
OVERLAY_SHOW_BATTERY_TEMP("overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
|
||||
OVERLAY_BACKGROUND("overlay_background", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED("performance_overlay_show_speed", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE("performance_overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM("performance_overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND("performance_overlay_background", Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
|
||||
@ -49,7 +50,8 @@ enum class BooleanSetting(
|
||||
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
|
||||
UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false);
|
||||
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false),
|
||||
ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
||||
@ -83,6 +85,8 @@ enum class BooleanSetting(
|
||||
SHADERS_ACCURATE_MUL,
|
||||
USE_ARTIC_BASE_CONTROLLER,
|
||||
COMPRESS_INSTALLED_CIA_CONTENT,
|
||||
ANDROID_HIDE_IMAGES,
|
||||
PERF_OVERLAY_ENABLE // Works in overlay options, but not from the settings menu
|
||||
)
|
||||
|
||||
fun from(key: String): BooleanSetting? =
|
||||
|
||||
@ -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
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -10,6 +10,10 @@ enum class FloatSetting(
|
||||
override val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f),
|
||||
EMPTY_SETTING("", "", 0.0f);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
|
||||
@ -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
|
||||
// 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.ScaledFloatSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting?,
|
||||
@ -27,7 +26,8 @@ class SliderSetting(
|
||||
val selectedFloat: Float
|
||||
get() {
|
||||
val setting = setting ?: return defaultValue!!.toFloat()
|
||||
return when (setting) {
|
||||
|
||||
val ret = when (setting) {
|
||||
is AbstractIntSetting -> setting.int.toFloat()
|
||||
is FloatSetting -> setting.float
|
||||
is ScaledFloatSetting -> setting.float
|
||||
@ -36,8 +36,8 @@ class SliderSetting(
|
||||
-1f
|
||||
}
|
||||
}
|
||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@ -4,14 +4,19 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.ui
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
@ -60,6 +65,32 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
loadSettingsUI()
|
||||
}
|
||||
|
||||
private fun updateAndroidImageVisibility() {
|
||||
val dataDirTreeUri: Uri
|
||||
val dataDirDocument: DocumentFile
|
||||
val nomediaFileDocument: DocumentFile?
|
||||
val nomediaFileExists: Boolean
|
||||
try {
|
||||
dataDirTreeUri = PermissionsHandler.citraDirectory
|
||||
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
|
||||
nomediaFileExists = (nomediaFileDocument != null)
|
||||
} catch (e: Exception) {
|
||||
Log.error("[SettingsActivity]: Error occurred while trying to find .nomedia, error: " + e.message)
|
||||
return
|
||||
}
|
||||
|
||||
if (BooleanSetting.ANDROID_HIDE_IMAGES.boolean) {
|
||||
if (!nomediaFileExists) {
|
||||
Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory")
|
||||
FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia")
|
||||
}
|
||||
} else if (nomediaFileExists) {
|
||||
Log.info("[SettingsActivity]: Attempting to delete .nomedia in user data directory")
|
||||
nomediaFileDocument!!.delete()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop(finishing: Boolean) {
|
||||
if (finishing && shouldSave) {
|
||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
@ -67,6 +98,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
//added to ensure that layout changes take effect as soon as settings window closes
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
updateAndroidImageVisibility()
|
||||
TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS
|
||||
}
|
||||
NativeLibrary.reloadSettings()
|
||||
|
||||
@ -248,6 +248,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.TURBO_LIMIT.defaultValue.toFloat()
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.ANDROID_HIDE_IMAGES,
|
||||
R.string.android_hide_images,
|
||||
R.string.android_hide_images_description,
|
||||
BooleanSetting.ANDROID_HIDE_IMAGES.key,
|
||||
BooleanSetting.ANDROID_HIDE_IMAGES.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1166,6 +1175,89 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
FloatSetting.LARGE_SCREEN_PROPORTION.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SliderSetting(
|
||||
FloatSetting.SECOND_SCREEN_OPACITY,
|
||||
R.string.second_screen_opacity,
|
||||
R.string.second_screen_opacity_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
FloatSetting.SECOND_SCREEN_OPACITY.key,
|
||||
FloatSetting.SECOND_SCREEN_OPACITY.defaultValue,
|
||||
isEnabled = IntSetting.SCREEN_LAYOUT.int == 5
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.bg_color, R.string.bg_color_description))
|
||||
val bgRedSetting = object : AbstractIntSetting {
|
||||
override var int: Int
|
||||
get() = (FloatSetting.BACKGROUND_RED.float * 255).toInt()
|
||||
set(value) {
|
||||
FloatSetting.BACKGROUND_RED.float = value.toFloat() / 255
|
||||
settings.saveSetting(FloatSetting.BACKGROUND_RED, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
override val key = null
|
||||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
bgRedSetting,
|
||||
R.string.bg_red,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
""
|
||||
)
|
||||
)
|
||||
val bgGreenSetting = object : AbstractIntSetting {
|
||||
override var int: Int
|
||||
get() = (FloatSetting.BACKGROUND_GREEN.float * 255).toInt()
|
||||
set(value) {
|
||||
FloatSetting.BACKGROUND_GREEN.float = value.toFloat() / 255
|
||||
settings.saveSetting(FloatSetting.BACKGROUND_GREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
override val key = null
|
||||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
bgGreenSetting,
|
||||
R.string.bg_green,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
""
|
||||
)
|
||||
)
|
||||
val bgBlueSetting = object : AbstractIntSetting {
|
||||
override var int: Int
|
||||
get() = (FloatSetting.BACKGROUND_BLUE.float * 255).toInt()
|
||||
set(value) {
|
||||
FloatSetting.BACKGROUND_BLUE.float = value.toFloat() / 255
|
||||
settings.saveSetting(FloatSetting.BACKGROUND_BLUE, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
override val key = null
|
||||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
bgBlueSetting,
|
||||
R.string.bg_blue,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
""
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.performance_overlay_options,
|
||||
@ -1201,38 +1293,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
object : AbstractBooleanSetting {
|
||||
override val key = "EmulationMenuSettings_showPerfPerformanceOverlay"
|
||||
override val section = Settings.SECTION_LAYOUT
|
||||
override val defaultValue = false
|
||||
override var boolean: Boolean
|
||||
get() = EmulationMenuSettings.showPerformanceOverlay
|
||||
set(value) { EmulationMenuSettings.showPerformanceOverlay = value }
|
||||
override val isRuntimeEditable = true
|
||||
override val valueAsString: String get() = boolean.toString()
|
||||
},
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE,
|
||||
R.string.performance_overlay_enable,
|
||||
0,
|
||||
"EmulationMenuSettings_showPerfPerformanceOverlay",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.key,
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_BACKGROUND,
|
||||
R.string.overlay_background,
|
||||
R.string.overlay_background_description,
|
||||
"overlay_background",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_BACKGROUND,
|
||||
R.string.performance_overlay_background,
|
||||
R.string.performance_overlay_background_description,
|
||||
BooleanSetting.PERF_OVERLAY_BACKGROUND.key,
|
||||
BooleanSetting.PERF_OVERLAY_BACKGROUND.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.PERFORMANCE_OVERLAY_POSITION,
|
||||
R.string.overlay_position,
|
||||
R.string.overlay_position_description,
|
||||
R.string.performance_overlay_position,
|
||||
R.string.performance_overlay_position_description,
|
||||
R.array.statsPosition,
|
||||
R.array.statsPositionValues,
|
||||
)
|
||||
@ -1243,61 +1326,61 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_FPS,
|
||||
R.string.overlay_show_fps,
|
||||
R.string.overlay_show_fps_description,
|
||||
"overlay_show_fps",
|
||||
true
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FPS,
|
||||
R.string.performance_overlay_show_fps,
|
||||
R.string.performance_overlay_show_fps_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FPS.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FPS.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_FRAMETIME,
|
||||
R.string.overlay_show_frametime,
|
||||
R.string.overlay_show_frametime_description,
|
||||
"overlay_show_frame_time",
|
||||
true
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME,
|
||||
R.string.performance_overlay_show_frametime,
|
||||
R.string.performance_overlay_show_frametime_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_SPEED,
|
||||
R.string.overlay_show_speed,
|
||||
R.string.overlay_show_speed_description,
|
||||
"overlay_show_speed",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_SPEED,
|
||||
R.string.performance_overlay_show_speed,
|
||||
R.string.performance_overlay_show_speed_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_SPEED.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_SPEED.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE,
|
||||
R.string.overlay_show_app_ram_usage,
|
||||
R.string.overlay_show_app_ram_usage_description,
|
||||
"overlay_show_app_ram_usage",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE,
|
||||
R.string.performance_overlay_show_app_ram_usage,
|
||||
R.string.performance_overlay_show_app_ram_usage_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM,
|
||||
R.string.overlay_show_available_ram,
|
||||
R.string.overlay_show_available_ram_description,
|
||||
"overlay_show_available_ram",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM,
|
||||
R.string.performance_overlay_show_available_ram,
|
||||
R.string.performance_overlay_show_available_ram_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP,
|
||||
R.string.overlay_show_battery_temp,
|
||||
R.string.overlay_show_battery_temp_description,
|
||||
"overlay_show_battery_temp",
|
||||
false
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP,
|
||||
R.string.performance_overlay_show_battery_temp,
|
||||
R.string.performance_overlay_show_battery_temp_description,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.key,
|
||||
BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.display.ScreenLayout
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
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.utils.SettingsFile
|
||||
@ -100,6 +101,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
|
||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
private val settings get() = settingsViewModel.settings
|
||||
|
||||
private val onPause = Runnable{ togglePause() }
|
||||
private val onShutdown = Runnable{ emulationState.stop() }
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
@ -155,9 +160,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
retainInstance = true
|
||||
emulationState = EmulationState(game.path)
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
||||
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
|
||||
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings)
|
||||
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@ -507,6 +512,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationLifecycleUtil.removeHook(onPause)
|
||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupCitraDirectoriesThenStartEmulation() {
|
||||
val directoryInitializationState = DirectoryInitialization.start()
|
||||
if (directoryInitializationState ===
|
||||
@ -662,7 +673,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
popupMenu.menu.apply {
|
||||
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
||||
findItem(R.id.menu_performance_overlay_show).isChecked =
|
||||
EmulationMenuSettings.showPerformanceOverlay
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.boolean
|
||||
findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback
|
||||
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
|
||||
EmulationMenuSettings.joystickRelCenter
|
||||
@ -679,7 +690,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
}
|
||||
|
||||
R.id.menu_performance_overlay_show -> {
|
||||
EmulationMenuSettings.showPerformanceOverlay = !EmulationMenuSettings.showPerformanceOverlay
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean
|
||||
settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG)
|
||||
updateShowPerformanceOverlay()
|
||||
true
|
||||
}
|
||||
@ -1202,7 +1214,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
|
||||
if (EmulationMenuSettings.showPerformanceOverlay) {
|
||||
if (BooleanSetting.PERF_OVERLAY_ENABLE.boolean) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val SPEED = 2
|
||||
@ -1217,11 +1229,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
val perfStats = NativeLibrary.getPerfStats()
|
||||
val dividerString = "\u00A0\u2502 "
|
||||
if (perfStats[FPS] > 0) {
|
||||
if (BooleanSetting.OVERLAY_SHOW_FPS.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_FPS.boolean) {
|
||||
sb.append(String.format("FPS:\u00A0%d", (perfStats[FPS] + 0.5).toInt()))
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_SHOW_FRAMETIME.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.boolean) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
sb.append(
|
||||
String.format(
|
||||
@ -1236,7 +1248,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
)
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_SHOW_SPEED.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_SPEED.boolean) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
sb.append(
|
||||
String.format(
|
||||
@ -1246,14 +1258,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
)
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.boolean) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
val appRamUsage =
|
||||
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
|
||||
sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB")
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.boolean) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
context?.let { ctx ->
|
||||
val activityManager =
|
||||
@ -1266,14 +1278,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
}
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.boolean) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
val batteryTemp = getBatteryTemperature()
|
||||
val tempF = celsiusToFahrenheit(batteryTemp)
|
||||
sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF))
|
||||
}
|
||||
|
||||
if (BooleanSetting.OVERLAY_BACKGROUND.boolean) {
|
||||
if (BooleanSetting.PERF_OVERLAY_BACKGROUND.boolean) {
|
||||
binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black)
|
||||
} else {
|
||||
binding.performanceOverlayShowText.setBackgroundResource(0)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -18,15 +18,27 @@ object EmulationLifecycleUtil {
|
||||
}
|
||||
|
||||
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) {
|
||||
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() {
|
||||
pauseResumeHooks.clear()
|
||||
shutdownHooks.clear()
|
||||
fun removeHook(hook: Runnable) {
|
||||
if (pauseResumeHooks.contains(hook)) {
|
||||
pauseResumeHooks.remove(hook)
|
||||
}
|
||||
if (shutdownHooks.contains(hook)) {
|
||||
shutdownHooks.remove(hook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,13 +35,6 @@ object EmulationMenuSettings {
|
||||
.apply()
|
||||
}
|
||||
|
||||
var showPerformanceOverlay: Boolean
|
||||
get() = preferences.getBoolean("EmulationMenuSettings_showPerformanceOverlay", false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean("EmulationMenuSettings_showPerformanceOverlay", value)
|
||||
.apply()
|
||||
}
|
||||
var hapticFeedback: Boolean
|
||||
get() = preferences.getBoolean("EmulationMenuSettings_HapticFeedback", true)
|
||||
set(value) {
|
||||
|
||||
@ -172,6 +172,7 @@ void Config::ReadValues() {
|
||||
ReadSetting("Renderer", Settings::values.bg_red);
|
||||
ReadSetting("Renderer", Settings::values.bg_green);
|
||||
ReadSetting("Renderer", Settings::values.bg_blue);
|
||||
ReadSetting("Renderer", Settings::values.custom_second_layer_opacity);
|
||||
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
|
||||
ReadSetting("Renderer", Settings::values.disable_right_eye_render);
|
||||
|
||||
|
||||
@ -170,6 +170,9 @@ bg_red =
|
||||
bg_blue =
|
||||
bg_green =
|
||||
|
||||
# Opacity of second layer when using custom layout option (bottom screen unless swapped). Useful if positioning on top of the first layer.
|
||||
custom_second_layer_opacity =
|
||||
|
||||
# Whether and how Stereoscopic 3D should be rendered
|
||||
# 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
|
||||
render_3d =
|
||||
|
||||
@ -148,12 +148,12 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="statsPosition">
|
||||
<item>@string/overlay_position_top_left</item>
|
||||
<item>@string/overlay_position_center_top</item>
|
||||
<item>@string/overlay_position_top_right</item>
|
||||
<item>@string/overlay_position_bottom_left</item>
|
||||
<item>@string/overlay_position_center_bottom</item>
|
||||
<item>@string/overlay_position_bottom_right</item>
|
||||
<item>@string/performance_overlay_position_top_left</item>
|
||||
<item>@string/performance_overlay_position_center_top</item>
|
||||
<item>@string/performance_overlay_position_top_right</item>
|
||||
<item>@string/performance_overlay_position_bottom_left</item>
|
||||
<item>@string/performance_overlay_position_center_bottom</item>
|
||||
<item>@string/performance_overlay_position_bottom_right</item>
|
||||
</string-array>
|
||||
<integer-array name="statsPositionValues">
|
||||
<item>0</item>
|
||||
|
||||
@ -257,6 +257,8 @@
|
||||
<string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed. If disabled, emulation speed will be uncapped and the turbo speed hotkey will not work.</string>
|
||||
<string name="frame_limit_slider">Limit Speed Percent</string>
|
||||
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. With the default of 100% emulation will be limited to normal speed. Values higher or lower will increase or decrease the speed limit.</string>
|
||||
<string name="android_hide_images">Hide 3DS Images from Android</string>
|
||||
<string name="android_hide_images_description">Prevent 3DS camera, screenshot, and custom texture images from being indexed by Android and displayed in the gallery. Your device may need to be rebooted after changing this setting to take effect.</string>
|
||||
<string name="turbo_limit">Turbo Speed Limit</string>
|
||||
<string name="turbo_limit_description">Emulation speed limit used while the turbo hotkey is active.</string>
|
||||
<string name="expand_to_cutout_area">Expand to Cutout Area</string>
|
||||
@ -448,6 +450,13 @@
|
||||
<string name="emulation_portrait_layout_top_full">Default</string>
|
||||
<string name="emulation_secondary_display_default">System Default (mirror)</string>
|
||||
<string name="emulation_screen_layout_custom">Custom Layout</string>
|
||||
<string name="bg_color">Background Color</string>
|
||||
<string name="bg_color_description">The color which appears behind the screens during emulation, represented as an RGB value.</string>
|
||||
<string name="bg_red">Red</string>
|
||||
<string name="bg_green">Green</string>
|
||||
<string name="bg_blue">Blue</string>
|
||||
<string name="second_screen_opacity">Custom Layout Second Screen Opacity</string>
|
||||
<string name="second_screen_opacity_description">The opacity of the second 3DS screen when using a custom screen layout. Useful if the second screen is to be positioned on top of the first screen.</string>
|
||||
<string name="emulation_small_screen_position">Small Screen Position</string>
|
||||
<string name="small_screen_position_description">Where should the small screen appear relative to the large one in Large Screen Layout?</string>
|
||||
<string name="small_screen_position_top_right">Top Right</string>
|
||||
@ -576,28 +585,28 @@
|
||||
<string name="performance_overlay_options">Performance Overlay</string>
|
||||
<string name="performance_overlay_enable">Enable Performance Overlay</string>
|
||||
<string name="performance_overlay_options_description">Configure whether the performance overlay is shown and what information is displayed.</string>
|
||||
<string name="overlay_show_fps">Show FPS</string>
|
||||
<string name="overlay_show_fps_description">Display current frames per second.</string>
|
||||
<string name="overlay_show_frametime">Show Frametime</string>
|
||||
<string name="overlay_show_frametime_description">Display current frametime.</string>
|
||||
<string name="overlay_show_speed">Show Speed</string>
|
||||
<string name="overlay_show_speed_description">Display current emulation speed percentage.</string>
|
||||
<string name="overlay_show_app_ram_usage">Show App Memory Usage</string>
|
||||
<string name="overlay_show_app_ram_usage_description">Display the amount of RAM getting used by the emulator.</string>
|
||||
<string name="overlay_show_available_ram">Show Available Memory</string>
|
||||
<string name="overlay_show_available_ram_description">Display the amount of RAM which is available.</string>
|
||||
<string name="overlay_show_battery_temp">Show Battery Temperature</string>
|
||||
<string name="overlay_show_battery_temp_description">Display current Battery temperature in Celsius and Fahrenheit.</string>
|
||||
<string name="overlay_position">Overlay Position</string>
|
||||
<string name="overlay_position_description">Choose where the performance overlay is displayed on the screen.</string>
|
||||
<string name="overlay_position_top_left">Top Left</string>
|
||||
<string name="overlay_position_top_right">Top Right</string>
|
||||
<string name="overlay_position_bottom_left">Bottom Left</string>
|
||||
<string name="overlay_position_bottom_right">Bottom Right</string>
|
||||
<string name="overlay_position_center_top">Center Top</string>
|
||||
<string name="overlay_position_center_bottom">Center Bottom</string>
|
||||
<string name="overlay_background">Overlay Background</string>
|
||||
<string name="overlay_background_description">Adds a background behind the overlay for easier reading.</string>
|
||||
<string name="performance_overlay_show_fps">Show FPS</string>
|
||||
<string name="performance_overlay_show_fps_description">Display current frames per second.</string>
|
||||
<string name="performance_overlay_show_frametime">Show Frametime</string>
|
||||
<string name="performance_overlay_show_frametime_description">Display current frametime.</string>
|
||||
<string name="performance_overlay_show_speed">Show Speed</string>
|
||||
<string name="performance_overlay_show_speed_description">Display current emulation speed percentage.</string>
|
||||
<string name="performance_overlay_show_app_ram_usage">Show App Memory Usage</string>
|
||||
<string name="performance_overlay_show_app_ram_usage_description">Display the amount of RAM getting used by the emulator.</string>
|
||||
<string name="performance_overlay_show_available_ram">Show Available Memory</string>
|
||||
<string name="performance_overlay_show_available_ram_description">Display the amount of RAM which is available.</string>
|
||||
<string name="performance_overlay_show_battery_temp">Show Battery Temperature</string>
|
||||
<string name="performance_overlay_show_battery_temp_description">Display current Battery temperature in Celsius and Fahrenheit.</string>
|
||||
<string name="performance_overlay_position">Overlay Position</string>
|
||||
<string name="performance_overlay_position_description">Choose where the performance overlay is displayed on the screen.</string>
|
||||
<string name="performance_overlay_position_top_left">Top Left</string>
|
||||
<string name="performance_overlay_position_top_right">Top Right</string>
|
||||
<string name="performance_overlay_position_bottom_left">Bottom Left</string>
|
||||
<string name="performance_overlay_position_bottom_right">Bottom Right</string>
|
||||
<string name="performance_overlay_position_center_top">Center Top</string>
|
||||
<string name="performance_overlay_position_center_bottom">Center Bottom</string>
|
||||
<string name="performance_overlay_background">Overlay Background</string>
|
||||
<string name="performance_overlay_background_description">Adds a background behind the overlay for easier reading.</string>
|
||||
|
||||
<!-- Cheats -->
|
||||
<string name="cheats">Cheats</string>
|
||||
|
||||
@ -60,6 +60,10 @@ if (ENABLE_QT AND UNIX AND NOT APPLE)
|
||||
target_link_libraries(citra_meta PRIVATE Qt6::DBus gamemode)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT AND APPLE)
|
||||
target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT AND USE_DISCORD_PRESENCE)
|
||||
target_link_libraries(citra_meta PRIVATE discord-rpc)
|
||||
endif()
|
||||
|
||||
@ -172,12 +172,12 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
||||
multiplayer/state.h
|
||||
multiplayer/validation.h
|
||||
precompiled_headers.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
user_data_migration.cpp
|
||||
user_data_migration.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
util/clickable_label.cpp
|
||||
util/clickable_label.h
|
||||
util/graphics_device_info.cpp
|
||||
@ -190,6 +190,13 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
||||
util/util.h
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_sources(citra_qt PUBLIC
|
||||
qt_swizzle.h
|
||||
qt_swizzle.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMPAT_LIST
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||
@ -275,6 +282,10 @@ if (NOT WIN32)
|
||||
target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(citra_qt PRIVATE Qt6::GuiPrivate)
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(citra_qt PRIVATE Qt6::DBus gamemode)
|
||||
endif()
|
||||
|
||||
@ -69,6 +69,7 @@
|
||||
#include "citra_qt/movie/movie_record_dialog.h"
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "citra_qt/qt_image_interface.h"
|
||||
#include "citra_qt/qt_swizzle.h"
|
||||
#include "citra_qt/uisettings.h"
|
||||
#include "common/play_time_manager.h"
|
||||
#ifdef ENABLE_QT_UPDATE_CHECKER
|
||||
@ -4112,6 +4113,11 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
|
||||
@ -531,7 +531,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="lb_opacity_second_layer">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Bottom Screen Opacity % (OpenGL Only)</p></body></html></string>
|
||||
<string><html><head/><body><p>Bottom Screen Opacity %</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
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
|
||||
@ -718,10 +718,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
||||
}
|
||||
|
||||
void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
|
||||
// TODO: Allow for second layer opacity in portrait mode android
|
||||
|
||||
if (!isPortrait &&
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
|
||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout &&
|
||||
Settings::values.custom_second_layer_opacity.GetValue() < 100) {
|
||||
state.blend.src_rgb_func = GL_CONSTANT_ALPHA;
|
||||
state.blend.src_a_func = GL_CONSTANT_ALPHA;
|
||||
@ -732,8 +729,7 @@ void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
|
||||
}
|
||||
|
||||
void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) {
|
||||
if (!isPortrait &&
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
|
||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout &&
|
||||
Settings::values.custom_second_layer_opacity.GetValue() < 100) {
|
||||
state.blend.src_rgb_func = GL_ONE;
|
||||
state.blend.dst_rgb_func = GL_ZERO;
|
||||
|
||||
@ -376,7 +376,13 @@ void RendererVulkan::BuildPipelines() {
|
||||
};
|
||||
|
||||
const vk::PipelineColorBlendAttachmentState colorblend_attachment = {
|
||||
.blendEnable = false,
|
||||
.blendEnable = true,
|
||||
.srcColorBlendFactor = vk::BlendFactor::eConstantAlpha,
|
||||
.dstColorBlendFactor = vk::BlendFactor::eOneMinusConstantAlpha,
|
||||
.colorBlendOp = vk::BlendOp::eAdd,
|
||||
.srcAlphaBlendFactor = vk::BlendFactor::eConstantAlpha,
|
||||
.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusConstantAlpha,
|
||||
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
|
||||
};
|
||||
@ -385,7 +391,6 @@ void RendererVulkan::BuildPipelines() {
|
||||
.logicOpEnable = false,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &colorblend_attachment,
|
||||
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
|
||||
};
|
||||
|
||||
const vk::Viewport placeholder_viewport = vk::Viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
@ -398,6 +403,7 @@ void RendererVulkan::BuildPipelines() {
|
||||
};
|
||||
|
||||
const std::array dynamic_states = {
|
||||
vk::DynamicState::eBlendConstants,
|
||||
vk::DynamicState::eViewport,
|
||||
vk::DynamicState::eScissor,
|
||||
};
|
||||
@ -729,6 +735,13 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl
|
||||
});
|
||||
}
|
||||
|
||||
void RendererVulkan::ApplySecondLayerOpacity(float alpha) {
|
||||
scheduler.Record([alpha](vk::CommandBuffer cmdbuf) {
|
||||
const std::array<float, 4> blend_constants = {0.0f, 0.0f, 0.0f, alpha};
|
||||
cmdbuf.setBlendConstants(blend_constants.data());
|
||||
});
|
||||
}
|
||||
|
||||
void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
const Common::Rectangle<u32>& top_screen) {
|
||||
if (!layout.top_screen_enabled) {
|
||||
@ -867,13 +880,30 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
|
||||
draw_info.modelview = MakeOrthographicMatrix(layout.width, layout.height);
|
||||
|
||||
draw_info.layer = 0;
|
||||
|
||||
// Apply the initial default opacity value; Needed to avoid flickering
|
||||
ApplySecondLayerOpacity(1.0f);
|
||||
|
||||
bool use_custom_opacity =
|
||||
Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout &&
|
||||
Settings::values.custom_second_layer_opacity.GetValue() < 100;
|
||||
float second_alpha = use_custom_opacity
|
||||
? Settings::values.custom_second_layer_opacity.GetValue() / 100.0f
|
||||
: 1.0f;
|
||||
|
||||
if (!Settings::values.swap_screen.GetValue()) {
|
||||
DrawTopScreen(layout, top_screen);
|
||||
draw_info.layer = 0;
|
||||
if (use_custom_opacity) {
|
||||
ApplySecondLayerOpacity(second_alpha);
|
||||
}
|
||||
DrawBottomScreen(layout, bottom_screen);
|
||||
} else {
|
||||
DrawBottomScreen(layout, bottom_screen);
|
||||
draw_info.layer = 0;
|
||||
if (use_custom_opacity) {
|
||||
ApplySecondLayerOpacity(second_alpha);
|
||||
}
|
||||
DrawTopScreen(layout, top_screen);
|
||||
}
|
||||
|
||||
|
||||
@ -102,12 +102,16 @@ private:
|
||||
void DrawScreens(Frame* frame, const Layout::FramebufferLayout& layout, bool flipped);
|
||||
void DrawBottomScreen(const Layout::FramebufferLayout& layout,
|
||||
const Common::Rectangle<u32>& bottom_screen);
|
||||
|
||||
void DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
const Common::Rectangle<u32>& top_screen);
|
||||
void DrawSingleScreen(u32 screen_id, float x, float y, float w, float h,
|
||||
Layout::DisplayOrientation orientation);
|
||||
void DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, float x, float y, float w,
|
||||
float h, Layout::DisplayOrientation orientation);
|
||||
|
||||
void ApplySecondLayerOpacity(float alpha);
|
||||
|
||||
void LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer, ScreenInfo& screen_info,
|
||||
bool right_eye);
|
||||
void FillScreen(Common::Vec3<u8> color, const TextureInfo& texture);
|
||||
|
||||
@ -556,12 +556,15 @@ bool PipelineCache::EnsureDirectories() const {
|
||||
};
|
||||
|
||||
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 {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP + "pipeline" +
|
||||
DIR_SEP;
|
||||
return GetVulkanDir() + "pipeline" + DIR_SEP;
|
||||
}
|
||||
|
||||
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.
|
||||
bool EnsureDirectories() const;
|
||||
|
||||
/// Returns the Vulkan shader directory
|
||||
std::string GetVulkanDir() const;
|
||||
|
||||
/// Returns the pipeline cache storage dir
|
||||
std::string GetPipelineCacheDir() const;
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ The scripts in this directory assume that your current working directory is the
|
||||
## Pre-release checklist
|
||||
|
||||
- [ ] Update compatibility list
|
||||
- [ ] Update translations
|
||||
- [ ] If this is a major release (2123.1 -> major.minor), update translations
|
||||
|
||||
### 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…
Reference in New Issue
Block a user