Added initial Android source code
This commit is contained in:
parent
79f5383fec
commit
3cf1eb5f9d
|
@ -0,0 +1,3 @@
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
[Bb]uild
|
|
@ -17,7 +17,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(package_repo "ext-linux-bin/raw/main/")
|
set(package_repo "ext-linux-bin/raw/main/")
|
||||||
set(package_extension ".tar.xz")
|
set(package_extension ".tar.xz")
|
||||||
elseif (ANDROID)
|
elseif (ANDROID)
|
||||||
set(package_repo "ext-android-bin/raw/main/")
|
set(package_repo "android-binaries/raw/main/")
|
||||||
set(package_extension ".tar.xz")
|
set(package_extension ".tar.xz")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "No package available for this platform")
|
message(FATAL_ERROR "No package available for this platform")
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 19b940e864bd3a5afb3c79e3c6788869d01a19eb
|
Subproject commit 2f382df218d7e8516dee3b3caccb819a62b571a2
|
|
@ -1 +1 @@
|
||||||
Subproject commit deec5f75ee1a8ccbe32c8780b1d17284fc87b0f1
|
Subproject commit 5cd3f5c5ceea6d9e9d435ccdd922d9b99e55d10b
|
|
@ -1,31 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.disk_shader_cache
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class ShaderProgressViewModel : ViewModel() {
|
|
||||||
private val _progress = MutableLiveData(0)
|
|
||||||
val progress: LiveData<Int> get() = _progress
|
|
||||||
|
|
||||||
private val _max = MutableLiveData(0)
|
|
||||||
val max: LiveData<Int> get() = _max
|
|
||||||
|
|
||||||
private val _message = MutableLiveData("")
|
|
||||||
val message: LiveData<String> get() = _message
|
|
||||||
|
|
||||||
fun setProgress(progress: Int) {
|
|
||||||
_progress.postValue(progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMax(max: Int) {
|
|
||||||
_max.postValue(max)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMessage(msg: String) {
|
|
||||||
_message.postValue(msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.disk_shader_cache.ui
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
|
||||||
import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
|
|
||||||
import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
|
|
||||||
|
|
||||||
class ShaderProgressDialogFragment : DialogFragment() {
|
|
||||||
private var _binding: DialogProgressBarBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
private lateinit var alertDialog: AlertDialog
|
|
||||||
|
|
||||||
private lateinit var shaderProgressViewModel: ShaderProgressViewModel
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
_binding = DialogProgressBarBinding.inflate(layoutInflater)
|
|
||||||
shaderProgressViewModel =
|
|
||||||
ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
|
|
||||||
|
|
||||||
val title = requireArguments().getString(TITLE)
|
|
||||||
val message = requireArguments().getString(MESSAGE)
|
|
||||||
|
|
||||||
isCancelable = false
|
|
||||||
alertDialog = MaterialAlertDialogBuilder(requireActivity())
|
|
||||||
.setView(binding.root)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.create()
|
|
||||||
return alertDialog
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
|
|
||||||
binding.progressBar.progress = progress
|
|
||||||
setUpdateText()
|
|
||||||
}
|
|
||||||
shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
|
|
||||||
binding.progressBar.max = max
|
|
||||||
setUpdateText()
|
|
||||||
}
|
|
||||||
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
|
|
||||||
alertDialog.setMessage(msg)
|
|
||||||
}
|
|
||||||
synchronized(DiskShaderCacheProgress.finishLock) {
|
|
||||||
DiskShaderCacheProgress.finishLock.notifyAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onUpdateProgress(msg: String, progress: Int, max: Int) {
|
|
||||||
shaderProgressViewModel.setProgress(progress)
|
|
||||||
shaderProgressViewModel.setMax(max)
|
|
||||||
shaderProgressViewModel.setMessage(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpdateText() {
|
|
||||||
binding.progressText.text = String.format(
|
|
||||||
"%d/%d",
|
|
||||||
shaderProgressViewModel.progress.value,
|
|
||||||
shaderProgressViewModel.max.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "ProgressDialogFragment"
|
|
||||||
const val TITLE = "title"
|
|
||||||
const val MESSAGE = "message"
|
|
||||||
|
|
||||||
fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
|
|
||||||
val frag = ShaderProgressDialogFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(TITLE, title)
|
|
||||||
args.putString(MESSAGE, message)
|
|
||||||
frag.arguments = args
|
|
||||||
return frag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A semantically-related group of Settings objects. These Settings are
|
|
||||||
* internally stored as a HashMap.
|
|
||||||
*/
|
|
||||||
class SettingSection(val name: String) {
|
|
||||||
val settings = HashMap<String, AbstractSetting>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method; inserts a value directly into the backing HashMap.
|
|
||||||
*
|
|
||||||
* @param setting The Setting to be inserted.
|
|
||||||
*/
|
|
||||||
fun putSetting(setting: AbstractSetting) {
|
|
||||||
settings[setting.key!!] = setting
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method; gets a value directly from the backing HashMap.
|
|
||||||
*
|
|
||||||
* @param key Used to retrieve the Setting.
|
|
||||||
* @return A Setting object (you should probably cast this before using)
|
|
||||||
*/
|
|
||||||
fun getSetting(key: String): AbstractSetting? {
|
|
||||||
return settings[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mergeSection(settingSection: SettingSection) {
|
|
||||||
for (setting in settingSection.settings.values) {
|
|
||||||
putSetting(setting)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class SettingsViewModel : ViewModel() {
|
|
||||||
val settings = Settings()
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.TextUtils
|
|
||||||
import java.io.File
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
|
||||||
|
|
||||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
|
||||||
val settings: Settings get() = activityView.settings
|
|
||||||
|
|
||||||
private var shouldSave = false
|
|
||||||
private lateinit var menuTag: String
|
|
||||||
private lateinit var gameId: String
|
|
||||||
|
|
||||||
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
|
|
||||||
this.menuTag = menuTag
|
|
||||||
this.gameId = gameId
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onStart() {
|
|
||||||
prepareDirectoriesIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadSettingsUI() {
|
|
||||||
if (!settings.isLoaded) {
|
|
||||||
if (!TextUtils.isEmpty(gameId)) {
|
|
||||||
settings.loadSettings(gameId, activityView)
|
|
||||||
} else {
|
|
||||||
settings.loadSettings(activityView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activityView.showSettingsFragment(menuTag, false, gameId)
|
|
||||||
activityView.onSettingsFileLoaded()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun prepareDirectoriesIfNeeded() {
|
|
||||||
val configFile =
|
|
||||||
File(
|
|
||||||
"${DirectoryInitialization.userDirectory}/config/" +
|
|
||||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
|
||||||
)
|
|
||||||
if (!configFile.exists()) {
|
|
||||||
Log.error(
|
|
||||||
"${DirectoryInitialization.userDirectory}/config/" +
|
|
||||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
|
||||||
)
|
|
||||||
Log.error("yuzu config file could not be found!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
|
||||||
DirectoryInitialization.start(activityView as Context)
|
|
||||||
}
|
|
||||||
loadSettingsUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onStop(finishing: Boolean) {
|
|
||||||
if (finishing && shouldSave) {
|
|
||||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
|
||||||
settings.saveSettings(activityView)
|
|
||||||
}
|
|
||||||
NativeLibrary.reloadSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingChanged() {
|
|
||||||
shouldSave = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingsReset() {
|
|
||||||
shouldSave = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveState(outState: Bundle) {
|
|
||||||
outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val KEY_SHOULD_SAVE = "should_save"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for the Activity that manages SettingsFragments.
|
|
||||||
*/
|
|
||||||
interface SettingsActivityView {
|
|
||||||
/**
|
|
||||||
* Show a new SettingsFragment.
|
|
||||||
*
|
|
||||||
* @param menuTag Identifier for the settings group that should be displayed.
|
|
||||||
* @param addToStack Whether or not this fragment should replace a previous one.
|
|
||||||
*/
|
|
||||||
fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a contained Fragment to get access to the Setting HashMap
|
|
||||||
* loaded from disk, so that each Fragment doesn't need to perform its own
|
|
||||||
* read operation.
|
|
||||||
*
|
|
||||||
* @return A HashMap of Settings.
|
|
||||||
*/
|
|
||||||
val settings: Settings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a load operation completes.
|
|
||||||
*/
|
|
||||||
fun onSettingsFileLoaded()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a load operation fails.
|
|
||||||
*/
|
|
||||||
fun onSettingsFileNotFound()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a popup text message on screen.
|
|
||||||
*
|
|
||||||
* @param message The contents of the onscreen message.
|
|
||||||
* @param is_long Whether this should be a long Toast or short one.
|
|
||||||
*/
|
|
||||||
fun showToastMessage(message: String, is_long: Boolean)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End the activity.
|
|
||||||
*/
|
|
||||||
fun finish()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a containing Fragment to tell the Activity that a setting was changed;
|
|
||||||
* unless this has been called, the Activity will not save to disk.
|
|
||||||
*/
|
|
||||||
fun onSettingChanged()
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for a screen showing a list of settings. Instances of
|
|
||||||
* this type of view will each display a layer of the setting hierarchy.
|
|
||||||
*/
|
|
||||||
interface SettingsFragmentView {
|
|
||||||
/**
|
|
||||||
* Pass an ArrayList to the View so that it can be displayed on screen.
|
|
||||||
*
|
|
||||||
* @param settingsList The result of converting the HashMap to an ArrayList
|
|
||||||
*/
|
|
||||||
fun showSettingsList(settingsList: ArrayList<SettingsItem>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructs the Fragment to load the settings screen.
|
|
||||||
*/
|
|
||||||
fun loadSettingsList()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The Fragment's containing activity.
|
|
||||||
*/
|
|
||||||
val activityView: SettingsActivityView?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the Fragment to tell the containing Activity to show a new
|
|
||||||
* Fragment containing a submenu of settings.
|
|
||||||
*
|
|
||||||
* @param menuKey Identifier for the settings group that should be shown.
|
|
||||||
*/
|
|
||||||
fun loadSubMenu(menuKey: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the Fragment to tell the containing activity to display a toast message.
|
|
||||||
*
|
|
||||||
* @param message Text to be shown in the Toast
|
|
||||||
* @param is_long Whether this should be a long Toast or short one.
|
|
||||||
*/
|
|
||||||
fun showToastMessage(message: String?, is_long: Boolean)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Have the fragment add a setting to the HashMap.
|
|
||||||
*
|
|
||||||
* @param setting The (possibly previously missing) new setting.
|
|
||||||
*/
|
|
||||||
fun putSetting(setting: AbstractSetting)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Have the fragment tell the containing Activity that a setting was modified.
|
|
||||||
*/
|
|
||||||
fun onSettingChanged()
|
|
||||||
}
|
|
|
@ -1,214 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.FilenameFilter
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
|
||||||
import org.yuzu.yuzu_emu.getPublicFilesDir
|
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
|
||||||
|
|
||||||
class ImportExportSavesFragment : DialogFragment() {
|
|
||||||
private val context = YuzuApplication.appContext
|
|
||||||
private val savesFolder =
|
|
||||||
"${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
|
|
||||||
|
|
||||||
// Get first subfolder in saves folder (should be the user folder)
|
|
||||||
private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
|
|
||||||
private var lastZipCreated: File? = null
|
|
||||||
|
|
||||||
private lateinit var startForResultExportSave: ActivityResultLauncher<Intent>
|
|
||||||
private lateinit var documentPicker: ActivityResultLauncher<Array<String>>
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val activity = requireActivity() as AppCompatActivity
|
|
||||||
|
|
||||||
val activityResultRegistry = requireActivity().activityResultRegistry
|
|
||||||
startForResultExportSave = activityResultRegistry.register(
|
|
||||||
"startForResultExportSaveKey",
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) {
|
|
||||||
File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
|
|
||||||
}
|
|
||||||
documentPicker = activityResultRegistry.register(
|
|
||||||
"documentPickerKey",
|
|
||||||
ActivityResultContracts.OpenDocument()
|
|
||||||
) {
|
|
||||||
it?.let { uri -> importSave(uri, activity) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
return if (savesFolderRoot == "") {
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.manage_save_data)
|
|
||||||
.setMessage(R.string.import_export_saves_no_profile)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.manage_save_data)
|
|
||||||
.setMessage(R.string.manage_save_data_description)
|
|
||||||
.setNegativeButton(R.string.export_saves) { _, _ ->
|
|
||||||
exportSave()
|
|
||||||
}
|
|
||||||
.setPositiveButton(R.string.import_saves) { _, _ ->
|
|
||||||
documentPicker.launch(arrayOf("application/zip"))
|
|
||||||
}
|
|
||||||
.setNeutralButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zips the save files located in the given folder path and creates a new zip file with the current date and time.
|
|
||||||
* @return true if the zip file is successfully created, false otherwise.
|
|
||||||
*/
|
|
||||||
private fun zipSave(): Boolean {
|
|
||||||
try {
|
|
||||||
val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp")
|
|
||||||
tempFolder.mkdirs()
|
|
||||||
val saveFolder = File(savesFolderRoot)
|
|
||||||
val outputZipFile = File(
|
|
||||||
tempFolder,
|
|
||||||
"yuzu saves - ${
|
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
|
||||||
}.zip"
|
|
||||||
)
|
|
||||||
outputZipFile.createNewFile()
|
|
||||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
|
||||||
saveFolder.walkTopDown().forEach { file ->
|
|
||||||
val zipFileName =
|
|
||||||
file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
|
|
||||||
if (zipFileName == "") {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
|
|
||||||
zos.putNextEntry(entry)
|
|
||||||
if (file.isFile) {
|
|
||||||
file.inputStream().use { fis -> fis.copyTo(zos) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastZipCreated = outputZipFile
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
|
|
||||||
*/
|
|
||||||
private fun exportSave() {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val wasZipCreated = zipSave()
|
|
||||||
val lastZipFile = lastZipCreated
|
|
||||||
if (!wasZipCreated || lastZipFile == null) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val file = DocumentFile.fromSingleUri(
|
|
||||||
context,
|
|
||||||
DocumentsContract.buildDocumentUri(
|
|
||||||
DocumentProvider.AUTHORITY,
|
|
||||||
"${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
|
|
||||||
)
|
|
||||||
)!!
|
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
|
||||||
.setDataAndType(file.uri, "application/zip")
|
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
.putExtra(Intent.EXTRA_STREAM, file.uri)
|
|
||||||
startForResultExportSave.launch(Intent.createChooser(intent, "Share save file"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports the save files contained in the zip file, and replaces any existing ones with the new save file.
|
|
||||||
* @param zipUri The Uri of the zip file containing the save file(s) to import.
|
|
||||||
*/
|
|
||||||
private fun importSave(zipUri: Uri, activity: AppCompatActivity) {
|
|
||||||
val inputZip = context.contentResolver.openInputStream(zipUri)
|
|
||||||
// A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
|
|
||||||
var validZip = false
|
|
||||||
val savesFolder = File(savesFolderRoot)
|
|
||||||
val cacheSaveDir = File("${context.cacheDir.path}/saves/")
|
|
||||||
cacheSaveDir.mkdir()
|
|
||||||
|
|
||||||
if (inputZip == null) {
|
|
||||||
Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
|
|
||||||
.show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val filterTitleId =
|
|
||||||
FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
|
|
||||||
|
|
||||||
try {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
FileUtil.unzip(inputZip, cacheSaveDir)
|
|
||||||
cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
|
|
||||||
File(savesFolder, savePath).deleteRecursively()
|
|
||||||
File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
|
|
||||||
validZip = true
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
if (!validZip) {
|
|
||||||
MessageDialogFragment.newInstance(
|
|
||||||
requireActivity(),
|
|
||||||
titleId = R.string.save_file_invalid_zip_structure,
|
|
||||||
descriptionId = R.string.save_file_invalid_zip_structure_description
|
|
||||||
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.save_file_imported_success),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheSaveDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "ImportExportSavesFragment"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
|
||||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
|
||||||
|
|
||||||
class IndeterminateProgressDialogFragment : DialogFragment() {
|
|
||||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private lateinit var binding: DialogProgressBarBinding
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val titleId = requireArguments().getInt(TITLE)
|
|
||||||
val cancellable = requireArguments().getBoolean(CANCELLABLE)
|
|
||||||
|
|
||||||
binding = DialogProgressBarBinding.inflate(layoutInflater)
|
|
||||||
binding.progressBar.isIndeterminate = true
|
|
||||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(titleId)
|
|
||||||
.setView(binding.root)
|
|
||||||
|
|
||||||
if (cancellable) {
|
|
||||||
dialog.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val alertDialog = dialog.create()
|
|
||||||
alertDialog.setCanceledOnTouchOutside(false)
|
|
||||||
|
|
||||||
if (!taskViewModel.isRunning.value) {
|
|
||||||
taskViewModel.runTask()
|
|
||||||
}
|
|
||||||
return alertDialog
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
viewLifecycleOwner.lifecycleScope.apply {
|
|
||||||
launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
taskViewModel.isComplete.collect {
|
|
||||||
if (it) {
|
|
||||||
dismiss()
|
|
||||||
when (val result = taskViewModel.result.value) {
|
|
||||||
is String -> Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
result,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
|
|
||||||
is MessageDialogFragment -> result.show(
|
|
||||||
requireActivity().supportFragmentManager,
|
|
||||||
MessageDialogFragment.TAG
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
taskViewModel.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
taskViewModel.cancelled.collect {
|
|
||||||
if (it) {
|
|
||||||
dialog?.setTitle(R.string.cancelling)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
|
|
||||||
// Setting the OnClickListener again after the dialog is shown overrides this behavior.
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
val alertDialog = dialog as AlertDialog
|
|
||||||
val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
|
|
||||||
negativeButton.setOnClickListener {
|
|
||||||
alertDialog.setTitle(getString(R.string.cancelling))
|
|
||||||
taskViewModel.setCancelled(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "IndeterminateProgressDialogFragment"
|
|
||||||
|
|
||||||
private const val TITLE = "Title"
|
|
||||||
private const val CANCELLABLE = "Cancellable"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
activity: FragmentActivity,
|
|
||||||
titleId: Int,
|
|
||||||
cancellable: Boolean = false,
|
|
||||||
task: suspend () -> Any
|
|
||||||
): IndeterminateProgressDialogFragment {
|
|
||||||
val dialog = IndeterminateProgressDialogFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
|
|
||||||
args.putInt(TITLE, titleId)
|
|
||||||
args.putBoolean(CANCELLABLE, cancellable)
|
|
||||||
dialog.arguments = args
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
class LongMessageDialogFragment : DialogFragment() {
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val titleId = requireArguments().getInt(TITLE)
|
|
||||||
val description = requireArguments().getString(DESCRIPTION)
|
|
||||||
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
|
||||||
|
|
||||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setPositiveButton(R.string.close, null)
|
|
||||||
.setTitle(titleId)
|
|
||||||
.setMessage(description)
|
|
||||||
|
|
||||||
if (helpLinkId != 0) {
|
|
||||||
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
|
|
||||||
openLink(getString(helpLinkId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialog.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openLink(link: String) {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "LongMessageDialogFragment"
|
|
||||||
|
|
||||||
private const val TITLE = "Title"
|
|
||||||
private const val DESCRIPTION = "Description"
|
|
||||||
private const val HELP_LINK = "Link"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
titleId: Int,
|
|
||||||
description: String,
|
|
||||||
helpLinkId: Int = 0
|
|
||||||
): LongMessageDialogFragment {
|
|
||||||
val dialog = LongMessageDialogFragment()
|
|
||||||
val bundle = Bundle()
|
|
||||||
bundle.apply {
|
|
||||||
putInt(TITLE, titleId)
|
|
||||||
putString(DESCRIPTION, description)
|
|
||||||
putInt(HELP_LINK, helpLinkId)
|
|
||||||
}
|
|
||||||
dialog.arguments = bundle
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,227 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.slider.Slider
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
|
||||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
|
||||||
|
|
||||||
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
|
||||||
private var type = 0
|
|
||||||
private var position = 0
|
|
||||||
|
|
||||||
private var defaultCancelListener =
|
|
||||||
DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private lateinit var sliderBinding: DialogSliderBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
type = requireArguments().getInt(TYPE)
|
|
||||||
position = requireArguments().getInt(POSITION)
|
|
||||||
|
|
||||||
if (settingsViewModel.clickedItem == null) dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
return when (type) {
|
|
||||||
TYPE_RESET_SETTING -> {
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setMessage(R.string.reset_setting_confirmation)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
|
||||||
settingsViewModel.clickedItem!!.setting.reset()
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
|
||||||
val item = settingsViewModel.clickedItem as SingleChoiceSetting
|
|
||||||
val value = getSelectionForSingleChoiceValue(item)
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setSingleChoiceItems(item.choicesId, value, this)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_SLIDER -> {
|
|
||||||
sliderBinding = DialogSliderBinding.inflate(layoutInflater)
|
|
||||||
val item = settingsViewModel.clickedItem as SliderSetting
|
|
||||||
|
|
||||||
settingsViewModel.setSliderTextValue(item.getSelectedValue().toFloat(), item.units)
|
|
||||||
sliderBinding.slider.apply {
|
|
||||||
valueFrom = item.min.toFloat()
|
|
||||||
valueTo = item.max.toFloat()
|
|
||||||
value = settingsViewModel.sliderProgress.value.toFloat()
|
|
||||||
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
|
||||||
settingsViewModel.setSliderTextValue(value, item.units)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setView(sliderBinding.root)
|
|
||||||
.setPositiveButton(android.R.string.ok, this)
|
|
||||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
|
||||||
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onCreateDialog(savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
return when (type) {
|
|
||||||
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
|
||||||
else -> super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
when (type) {
|
|
||||||
SettingsItem.TYPE_SLIDER -> {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
settingsViewModel.sliderTextValue.collect {
|
|
||||||
sliderBinding.textValue.text = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
settingsViewModel.sliderProgress.collect {
|
|
||||||
sliderBinding.slider.value = it.toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
|
||||||
when (settingsViewModel.clickedItem) {
|
|
||||||
is SingleChoiceSetting -> {
|
|
||||||
val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
|
|
||||||
val value = getValueForSingleChoiceSelection(scSetting, which)
|
|
||||||
scSetting.setSelectedValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
is StringSingleChoiceSetting -> {
|
|
||||||
val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
|
||||||
val value = scSetting.getValueAt(which)
|
|
||||||
scSetting.setSelectedValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
is SliderSetting -> {
|
|
||||||
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
|
|
||||||
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun closeDialog() {
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
settingsViewModel.clickedItem = null
|
|
||||||
settingsViewModel.setSliderProgress(-1f)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
|
||||||
val valuesId = item.valuesId
|
|
||||||
return if (valuesId > 0) {
|
|
||||||
val valuesArray = requireContext().resources.getIntArray(valuesId)
|
|
||||||
valuesArray[which]
|
|
||||||
} else {
|
|
||||||
which
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
|
||||||
val value = item.getSelectedValue()
|
|
||||||
val valuesId = item.valuesId
|
|
||||||
if (valuesId > 0) {
|
|
||||||
val valuesArray = requireContext().resources.getIntArray(valuesId)
|
|
||||||
for (index in valuesArray.indices) {
|
|
||||||
val current = valuesArray[index]
|
|
||||||
if (current == value) {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "SettingsDialogFragment"
|
|
||||||
|
|
||||||
const val TYPE_RESET_SETTING = -1
|
|
||||||
|
|
||||||
const val TITLE = "Title"
|
|
||||||
const val TYPE = "Type"
|
|
||||||
const val POSITION = "Position"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
settingsViewModel: SettingsViewModel,
|
|
||||||
clickedItem: SettingsItem,
|
|
||||||
type: Int,
|
|
||||||
position: Int
|
|
||||||
): SettingsDialogFragment {
|
|
||||||
when (type) {
|
|
||||||
SettingsItem.TYPE_HEADER,
|
|
||||||
SettingsItem.TYPE_SWITCH,
|
|
||||||
SettingsItem.TYPE_SUBMENU,
|
|
||||||
SettingsItem.TYPE_DATETIME_SETTING,
|
|
||||||
SettingsItem.TYPE_RUNNABLE ->
|
|
||||||
throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
|
|
||||||
|
|
||||||
SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
|
|
||||||
(clickedItem as SliderSetting).getSelectedValue().toFloat()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
settingsViewModel.clickedItem = clickedItem
|
|
||||||
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(TYPE, type)
|
|
||||||
args.putInt(POSITION, position)
|
|
||||||
val fragment = SettingsDialogFragment()
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
|
||||||
import info.debatty.java.stringsimilarity.Cosine
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
|
||||||
|
|
||||||
class SettingsSearchFragment : Fragment() {
|
|
||||||
private var _binding: FragmentSettingsSearchBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
private var settingsAdapter: SettingsAdapter? = null
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
|
||||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
|
||||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
|
||||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
_binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
|
||||||
|
|
||||||
val dividerDecoration = MaterialDividerItemDecoration(
|
|
||||||
requireContext(),
|
|
||||||
LinearLayoutManager.VERTICAL
|
|
||||||
)
|
|
||||||
dividerDecoration.isLastItemDecorated = false
|
|
||||||
binding.settingsList.apply {
|
|
||||||
adapter = settingsAdapter
|
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
addItemDecoration(dividerDecoration)
|
|
||||||
}
|
|
||||||
|
|
||||||
focusSearch()
|
|
||||||
|
|
||||||
binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
|
|
||||||
binding.searchBackground.setOnClickListener { focusSearch() }
|
|
||||||
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
|
||||||
binding.searchText.doOnTextChanged { _, _, _, _ ->
|
|
||||||
search()
|
|
||||||
binding.settingsList.smoothScrollToPosition(0)
|
|
||||||
}
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
settingsViewModel.shouldReloadSettingsList.collect {
|
|
||||||
if (it) {
|
|
||||||
settingsViewModel.setShouldReloadSettingsList(false)
|
|
||||||
search()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
search()
|
|
||||||
|
|
||||||
setInsets()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun search() {
|
|
||||||
val searchTerm = binding.searchText.text.toString().lowercase()
|
|
||||||
binding.clearButton.visibility =
|
|
||||||
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
|
||||||
if (searchTerm.isEmpty()) {
|
|
||||||
binding.noResultsView.visibility = View.VISIBLE
|
|
||||||
settingsAdapter?.submitList(emptyList())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseList = SettingsItem.settingsItems
|
|
||||||
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
|
|
||||||
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
|
|
||||||
val title = getString(item.value.nameId).lowercase()
|
|
||||||
val similarity = similarityAlgorithm.similarity(searchTerm, title)
|
|
||||||
if (similarity > 0.08) {
|
|
||||||
Pair(similarity, item)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.sortedByDescending { it.first }.mapNotNull {
|
|
||||||
val item = it.second.value
|
|
||||||
val pairedSettingKey = item.setting.pairedSettingKey
|
|
||||||
val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
|
|
||||||
val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
|
|
||||||
if (pairedSettingValue) it.second.value else null
|
|
||||||
} else {
|
|
||||||
it.second.value
|
|
||||||
}
|
|
||||||
optionalSetting
|
|
||||||
}
|
|
||||||
settingsAdapter?.submitList(sortedList)
|
|
||||||
binding.noResultsView.visibility =
|
|
||||||
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun focusSearch() {
|
|
||||||
binding.searchText.requestFocus()
|
|
||||||
val imm = requireActivity()
|
|
||||||
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
|
||||||
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setInsets() =
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
|
||||||
binding.root
|
|
||||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
|
||||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
|
|
||||||
val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
|
|
||||||
val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
|
|
||||||
|
|
||||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
|
||||||
|
|
||||||
val leftInsets = barInsets.left + cutoutInsets.left
|
|
||||||
val rightInsets = barInsets.right + cutoutInsets.right
|
|
||||||
|
|
||||||
binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
|
|
||||||
binding.frameSearch.updatePadding(
|
|
||||||
left = leftInsets + sideMargin,
|
|
||||||
top = barInsets.top + topMargin,
|
|
||||||
right = rightInsets + sideMargin
|
|
||||||
)
|
|
||||||
binding.noResultsView.updatePadding(
|
|
||||||
left = leftInsets,
|
|
||||||
right = rightInsets,
|
|
||||||
bottom = barInsets.bottom
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.settingsList.updateMargins(
|
|
||||||
left = leftInsets + sideMargin,
|
|
||||||
right = rightInsets + sideMargin
|
|
||||||
)
|
|
||||||
binding.divider.updateMargins(
|
|
||||||
left = leftInsets + sideMargin,
|
|
||||||
right = rightInsets + sideMargin
|
|
||||||
)
|
|
||||||
|
|
||||||
windowInsets
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SEARCH_TEXT = "SearchText"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.model
|
|
||||||
|
|
||||||
data class Addon(
|
|
||||||
var enabled: Boolean,
|
|
||||||
val title: String,
|
|
||||||
val version: String
|
|
||||||
)
|
|
|
@ -1,71 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.model
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
|
|
||||||
class SettingsViewModel : ViewModel() {
|
|
||||||
var game: Game? = null
|
|
||||||
|
|
||||||
var clickedItem: SettingsItem? = null
|
|
||||||
|
|
||||||
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
|
||||||
private val _shouldRecreate = MutableStateFlow(false)
|
|
||||||
|
|
||||||
val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
|
|
||||||
private val _shouldNavigateBack = MutableStateFlow(false)
|
|
||||||
|
|
||||||
val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
|
|
||||||
private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
|
|
||||||
|
|
||||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
|
||||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
|
||||||
|
|
||||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
|
||||||
private val _sliderProgress = MutableStateFlow(-1)
|
|
||||||
|
|
||||||
val sliderTextValue: StateFlow<String> get() = _sliderTextValue
|
|
||||||
private val _sliderTextValue = MutableStateFlow("")
|
|
||||||
|
|
||||||
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
|
||||||
private val _adapterItemChanged = MutableStateFlow(-1)
|
|
||||||
|
|
||||||
fun setShouldRecreate(value: Boolean) {
|
|
||||||
_shouldRecreate.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShouldNavigateBack(value: Boolean) {
|
|
||||||
_shouldNavigateBack.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShouldShowResetSettingsDialog(value: Boolean) {
|
|
||||||
_shouldShowResetSettingsDialog.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShouldReloadSettingsList(value: Boolean) {
|
|
||||||
_shouldReloadSettingsList.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSliderTextValue(value: Float, units: String) {
|
|
||||||
_sliderProgress.value = value.toInt()
|
|
||||||
_sliderTextValue.value = String.format(
|
|
||||||
YuzuApplication.appContext.getString(R.string.value_with_units),
|
|
||||||
value.toInt().toString(),
|
|
||||||
units
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSliderProgress(value: Float) {
|
|
||||||
_sliderProgress.value = value.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAdapterItemChanged(value: Int) {
|
|
||||||
_adapterItemChanged.value = value
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
class BiMap<K, V> {
|
|
||||||
private val forward: MutableMap<K, V> = HashMap()
|
|
||||||
private val backward: MutableMap<V, K> = HashMap()
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun add(key: K, value: V) {
|
|
||||||
forward[key] = value
|
|
||||||
backward[value] = key
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun getForward(key: K): V? {
|
|
||||||
return forward[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun getBackward(key: V): K? {
|
|
||||||
return backward[key]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
import android.view.InputDevice
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some controllers have incorrect mappings. This class has special-case fixes for them.
|
|
||||||
*/
|
|
||||||
class ControllerMappingHelper {
|
|
||||||
/**
|
|
||||||
* Some controllers report extra button presses that can be ignored.
|
|
||||||
*/
|
|
||||||
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
|
|
||||||
return if (isDualShock4(inputDevice)) {
|
|
||||||
// The two analog triggers generate analog motion events as well as a keycode.
|
|
||||||
// We always prefer to use the analog values, so throw away the button press
|
|
||||||
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scale an axis to be zero-centered with a proper range.
|
|
||||||
*/
|
|
||||||
fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
|
|
||||||
if (isDualShock4(inputDevice)) {
|
|
||||||
// Android doesn't have correct mappings for this controller's triggers. It reports them
|
|
||||||
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
|
|
||||||
// Scale them to properly zero-centered with a range of [0.0, 1.0].
|
|
||||||
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
|
|
||||||
return (value + 1) / 2.0f
|
|
||||||
}
|
|
||||||
} else if (isXboxOneWireless(inputDevice)) {
|
|
||||||
// Same as the DualShock 4, the mappings are missing.
|
|
||||||
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
|
|
||||||
return (value + 1) / 2.0f
|
|
||||||
}
|
|
||||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
|
||||||
// This axis is stuck at ~.5. Ignore it.
|
|
||||||
return 0.0f
|
|
||||||
}
|
|
||||||
} else if (isMogaPro2Hid(inputDevice)) {
|
|
||||||
// This controller has a broken axis that reports a constant value. Ignore it.
|
|
||||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
|
||||||
return 0.0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sony DualShock 4 controller
|
|
||||||
private fun isDualShock4(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft Xbox One controller
|
|
||||||
private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moga Pro 2 HID
|
|
||||||
private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|
||||||
|
|
||||||
object EmulationMenuSettings {
|
|
||||||
private val preferences =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
|
||||||
|
|
||||||
var joystickRelCenter: Boolean
|
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
var dpadSlide: Boolean
|
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
var hapticFeedback: Boolean
|
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
var showFps: Boolean
|
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
var showOverlay: Boolean
|
|
||||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.IBinder
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service that shows a permanent notification in the background to avoid the app getting
|
|
||||||
* cleared from memory by the system.
|
|
||||||
*/
|
|
||||||
class ForegroundService : Service() {
|
|
||||||
companion object {
|
|
||||||
const val EMULATION_RUNNING_NOTIFICATION = 0x1000
|
|
||||||
|
|
||||||
const val ACTION_STOP = "stop"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showRunningNotification() {
|
|
||||||
// Intent is used to resume emulation if the notification is clicked
|
|
||||||
val contentIntent = PendingIntent.getActivity(
|
|
||||||
this,
|
|
||||||
0,
|
|
||||||
Intent(this, EmulationActivity::class.java),
|
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
val builder =
|
|
||||||
NotificationCompat.Builder(this, getString(R.string.emulation_notification_channel_id))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_notification_logo)
|
|
||||||
.setContentTitle(getString(R.string.app_name))
|
|
||||||
.setContentText(getString(R.string.emulation_notification_running))
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setVibrate(null)
|
|
||||||
.setSound(null)
|
|
||||||
.setContentIntent(contentIntent)
|
|
||||||
startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
showRunningNotification()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
if (intent == null) {
|
|
||||||
return START_NOT_STICKY
|
|
||||||
}
|
|
||||||
if (intent.action == ACTION_STOP) {
|
|
||||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
||||||
stopSelfResult(startId)
|
|
||||||
}
|
|
||||||
return START_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "jni/android_common/android_common.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "jni/id_cache.h"
|
|
||||||
|
|
||||||
std::string GetJString(JNIEnv* env, jstring jstr) {
|
|
||||||
if (!jstr) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const jchar* jchars = env->GetStringChars(jstr, nullptr);
|
|
||||||
const jsize length = env->GetStringLength(jstr);
|
|
||||||
const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length);
|
|
||||||
const std::string converted_string = Common::UTF16ToUTF8(string_view);
|
|
||||||
env->ReleaseStringChars(jstr, jchars);
|
|
||||||
|
|
||||||
return converted_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring ToJString(JNIEnv* env, std::string_view str) {
|
|
||||||
const std::u16string converted_string = Common::UTF8ToUTF16(str);
|
|
||||||
return env->NewString(reinterpret_cast<const jchar*>(converted_string.data()),
|
|
||||||
static_cast<jint>(converted_string.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring ToJString(JNIEnv* env, std::u16string_view str) {
|
|
||||||
return ToJString(env, Common::UTF16ToUTF8(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
double GetJDouble(JNIEnv* env, jobject jdouble) {
|
|
||||||
return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject ToJDouble(JNIEnv* env, double value) {
|
|
||||||
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 GetJInteger(JNIEnv* env, jobject jinteger) {
|
|
||||||
return env->GetIntField(jinteger, IDCache::GetIntegerValueField());
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject ToJInteger(JNIEnv* env, s32 value) {
|
|
||||||
return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetJBoolean(JNIEnv* env, jobject jboolean) {
|
|
||||||
return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField());
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject ToJBoolean(JNIEnv* env, bool value) {
|
|
||||||
return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value);
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
std::string GetJString(JNIEnv* env, jstring jstr);
|
|
||||||
jstring ToJString(JNIEnv* env, std::string_view str);
|
|
||||||
jstring ToJString(JNIEnv* env, std::u16string_view str);
|
|
||||||
|
|
||||||
double GetJDouble(JNIEnv* env, jobject jdouble);
|
|
||||||
jobject ToJDouble(JNIEnv* env, double value);
|
|
||||||
|
|
||||||
s32 GetJInteger(JNIEnv* env, jobject jinteger);
|
|
||||||
jobject ToJInteger(JNIEnv* env, s32 value);
|
|
||||||
|
|
||||||
bool GetJBoolean(JNIEnv* env, jobject jboolean);
|
|
||||||
jobject ToJBoolean(JNIEnv* env, bool value);
|
|
|
@ -1,277 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "jni/android_common/android_common.h"
|
|
||||||
#include "jni/applets/software_keyboard.h"
|
|
||||||
#include "jni/id_cache.h"
|
|
||||||
|
|
||||||
static jclass s_software_keyboard_class;
|
|
||||||
static jclass s_keyboard_config_class;
|
|
||||||
static jclass s_keyboard_data_class;
|
|
||||||
static jmethodID s_swkbd_execute_normal;
|
|
||||||
static jmethodID s_swkbd_execute_inline;
|
|
||||||
|
|
||||||
namespace SoftwareKeyboard {
|
|
||||||
|
|
||||||
static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) {
|
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
|
||||||
jobject object = env->AllocObject(s_keyboard_config_class);
|
|
||||||
|
|
||||||
env->SetObjectField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"),
|
|
||||||
ToJString(env, config.ok_text));
|
|
||||||
env->SetObjectField(
|
|
||||||
object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"),
|
|
||||||
ToJString(env, config.header_text));
|
|
||||||
env->SetObjectField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"),
|
|
||||||
ToJString(env, config.sub_text));
|
|
||||||
env->SetObjectField(
|
|
||||||
object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"),
|
|
||||||
ToJString(env, config.guide_text));
|
|
||||||
env->SetObjectField(
|
|
||||||
object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"),
|
|
||||||
ToJString(env, config.initial_text));
|
|
||||||
env->SetShortField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"),
|
|
||||||
static_cast<jshort>(config.left_optional_symbol_key));
|
|
||||||
env->SetShortField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"),
|
|
||||||
static_cast<jshort>(config.right_optional_symbol_key));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"),
|
|
||||||
static_cast<jint>(config.max_text_length));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"),
|
|
||||||
static_cast<jint>(config.min_text_length));
|
|
||||||
env->SetIntField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"),
|
|
||||||
static_cast<jint>(config.initial_cursor_position));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"),
|
|
||||||
static_cast<jint>(config.type));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"),
|
|
||||||
static_cast<jint>(config.password_mode));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"),
|
|
||||||
static_cast<jint>(config.text_draw_type));
|
|
||||||
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"),
|
|
||||||
static_cast<jint>(config.key_disable_flags.raw));
|
|
||||||
env->SetBooleanField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"),
|
|
||||||
static_cast<jboolean>(config.use_blur_background));
|
|
||||||
env->SetBooleanField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"),
|
|
||||||
static_cast<jboolean>(config.enable_backspace_button));
|
|
||||||
env->SetBooleanField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"),
|
|
||||||
static_cast<jboolean>(config.enable_return_button));
|
|
||||||
env->SetBooleanField(object,
|
|
||||||
env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"),
|
|
||||||
static_cast<jboolean>(config.disable_cancel_button));
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) {
|
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
|
||||||
const jstring string = reinterpret_cast<jstring>(env->GetObjectField(
|
|
||||||
object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;")));
|
|
||||||
return ResultData{GetJString(env, string),
|
|
||||||
static_cast<Service::AM::Frontend::SwkbdResult>(env->GetIntField(
|
|
||||||
object, env->GetFieldID(s_keyboard_data_class, "result", "I")))};
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidKeyboard::~AndroidKeyboard() = default;
|
|
||||||
|
|
||||||
void AndroidKeyboard::InitializeKeyboard(
|
|
||||||
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
|
|
||||||
SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) {
|
|
||||||
if (is_inline) {
|
|
||||||
LOG_WARNING(
|
|
||||||
Frontend,
|
|
||||||
"(STUBBED) called, backend requested to initialize the inline software keyboard.");
|
|
||||||
|
|
||||||
submit_inline_callback = std::move(submit_inline_callback_);
|
|
||||||
} else {
|
|
||||||
LOG_WARNING(
|
|
||||||
Frontend,
|
|
||||||
"(STUBBED) called, backend requested to initialize the normal software keyboard.");
|
|
||||||
|
|
||||||
submit_normal_callback = std::move(submit_normal_callback_);
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters = std::move(initialize_parameters);
|
|
||||||
|
|
||||||
LOG_INFO(Frontend,
|
|
||||||
"\nKeyboardInitializeParameters:"
|
|
||||||
"\nok_text={}"
|
|
||||||
"\nheader_text={}"
|
|
||||||
"\nsub_text={}"
|
|
||||||
"\nguide_text={}"
|
|
||||||
"\ninitial_text={}"
|
|
||||||
"\nmax_text_length={}"
|
|
||||||
"\nmin_text_length={}"
|
|
||||||
"\ninitial_cursor_position={}"
|
|
||||||
"\ntype={}"
|
|
||||||
"\npassword_mode={}"
|
|
||||||
"\ntext_draw_type={}"
|
|
||||||
"\nkey_disable_flags={}"
|
|
||||||
"\nuse_blur_background={}"
|
|
||||||
"\nenable_backspace_button={}"
|
|
||||||
"\nenable_return_button={}"
|
|
||||||
"\ndisable_cancel_button={}",
|
|
||||||
Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text),
|
|
||||||
Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text),
|
|
||||||
Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length,
|
|
||||||
parameters.min_text_length, parameters.initial_cursor_position, parameters.type,
|
|
||||||
parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw,
|
|
||||||
parameters.use_blur_background, parameters.enable_backspace_button,
|
|
||||||
parameters.enable_return_button, parameters.disable_cancel_button);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::ShowNormalKeyboard() const {
|
|
||||||
LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard.");
|
|
||||||
|
|
||||||
ResultData data{};
|
|
||||||
|
|
||||||
// Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
|
|
||||||
std::thread([&] {
|
|
||||||
data = ResultData::CreateFromFrontend(IDCache::GetEnvForThread()->CallStaticObjectMethod(
|
|
||||||
s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters)));
|
|
||||||
}).join();
|
|
||||||
|
|
||||||
SubmitNormalText(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::ShowTextCheckDialog(
|
|
||||||
Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
|
|
||||||
std::u16string text_check_message) const {
|
|
||||||
LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::ShowInlineKeyboard(
|
|
||||||
Core::Frontend::InlineAppearParameters appear_parameters) const {
|
|
||||||
LOG_WARNING(Frontend,
|
|
||||||
"(STUBBED) called, backend requested to show the inline software keyboard.");
|
|
||||||
|
|
||||||
LOG_INFO(Frontend,
|
|
||||||
"\nInlineAppearParameters:"
|
|
||||||
"\nmax_text_length={}"
|
|
||||||
"\nmin_text_length={}"
|
|
||||||
"\nkey_top_scale_x={}"
|
|
||||||
"\nkey_top_scale_y={}"
|
|
||||||
"\nkey_top_translate_x={}"
|
|
||||||
"\nkey_top_translate_y={}"
|
|
||||||
"\ntype={}"
|
|
||||||
"\nkey_disable_flags={}"
|
|
||||||
"\nkey_top_as_floating={}"
|
|
||||||
"\nenable_backspace_button={}"
|
|
||||||
"\nenable_return_button={}"
|
|
||||||
"\ndisable_cancel_button={}",
|
|
||||||
appear_parameters.max_text_length, appear_parameters.min_text_length,
|
|
||||||
appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y,
|
|
||||||
appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y,
|
|
||||||
appear_parameters.type, appear_parameters.key_disable_flags.raw,
|
|
||||||
appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button,
|
|
||||||
appear_parameters.enable_return_button, appear_parameters.disable_cancel_button);
|
|
||||||
|
|
||||||
// Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
|
|
||||||
m_is_inline_active = true;
|
|
||||||
std::thread([&] {
|
|
||||||
IDCache::GetEnvForThread()->CallStaticVoidMethod(
|
|
||||||
s_software_keyboard_class, s_swkbd_execute_inline, ToJKeyboardParams(parameters));
|
|
||||||
}).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::HideInlineKeyboard() const {
|
|
||||||
LOG_WARNING(Frontend,
|
|
||||||
"(STUBBED) called, backend requested to hide the inline software keyboard.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::InlineTextChanged(
|
|
||||||
Core::Frontend::InlineTextParameters text_parameters) const {
|
|
||||||
LOG_WARNING(Frontend,
|
|
||||||
"(STUBBED) called, backend requested to change the inline keyboard text.");
|
|
||||||
|
|
||||||
LOG_INFO(Frontend,
|
|
||||||
"\nInlineTextParameters:"
|
|
||||||
"\ninput_text={}"
|
|
||||||
"\ncursor_position={}",
|
|
||||||
Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position);
|
|
||||||
|
|
||||||
submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString,
|
|
||||||
text_parameters.input_text, text_parameters.cursor_position);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::ExitKeyboard() const {
|
|
||||||
LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) {
|
|
||||||
if (!m_is_inline_active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_current_text += submitted_text;
|
|
||||||
|
|
||||||
submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text,
|
|
||||||
m_current_text.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) {
|
|
||||||
static constexpr int KEYCODE_BACK = 4;
|
|
||||||
static constexpr int KEYCODE_ENTER = 66;
|
|
||||||
static constexpr int KEYCODE_DEL = 67;
|
|
||||||
|
|
||||||
if (!m_is_inline_active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (key_code) {
|
|
||||||
case KEYCODE_BACK:
|
|
||||||
case KEYCODE_ENTER:
|
|
||||||
m_is_inline_active = false;
|
|
||||||
submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::DecidedEnter, m_current_text,
|
|
||||||
static_cast<s32>(m_current_text.size()));
|
|
||||||
break;
|
|
||||||
case KEYCODE_DEL:
|
|
||||||
m_current_text.pop_back();
|
|
||||||
submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text,
|
|
||||||
m_current_text.size());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidKeyboard::SubmitNormalText(const ResultData& data) const {
|
|
||||||
submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitJNI(JNIEnv* env) {
|
|
||||||
s_software_keyboard_class = reinterpret_cast<jclass>(
|
|
||||||
env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard")));
|
|
||||||
s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig")));
|
|
||||||
s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData")));
|
|
||||||
|
|
||||||
s_swkbd_execute_normal = env->GetStaticMethodID(
|
|
||||||
s_software_keyboard_class, "executeNormal",
|
|
||||||
"(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/"
|
|
||||||
"applets/keyboard/SoftwareKeyboard$KeyboardData;");
|
|
||||||
s_swkbd_execute_inline = env->GetStaticMethodID(
|
|
||||||
s_software_keyboard_class, "executeInline",
|
|
||||||
"(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanupJNI(JNIEnv* env) {
|
|
||||||
env->DeleteGlobalRef(s_software_keyboard_class);
|
|
||||||
env->DeleteGlobalRef(s_keyboard_config_class);
|
|
||||||
env->DeleteGlobalRef(s_keyboard_data_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace SoftwareKeyboard
|
|
|
@ -1,78 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include "core/frontend/applets/software_keyboard.h"
|
|
||||||
|
|
||||||
namespace SoftwareKeyboard {
|
|
||||||
|
|
||||||
class AndroidKeyboard final : public Core::Frontend::SoftwareKeyboardApplet {
|
|
||||||
public:
|
|
||||||
~AndroidKeyboard() override;
|
|
||||||
|
|
||||||
void Close() const override {
|
|
||||||
ExitKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeKeyboard(bool is_inline,
|
|
||||||
Core::Frontend::KeyboardInitializeParameters initialize_parameters,
|
|
||||||
SubmitNormalCallback submit_normal_callback_,
|
|
||||||
SubmitInlineCallback submit_inline_callback_) override;
|
|
||||||
|
|
||||||
void ShowNormalKeyboard() const override;
|
|
||||||
|
|
||||||
void ShowTextCheckDialog(Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
|
|
||||||
std::u16string text_check_message) const override;
|
|
||||||
|
|
||||||
void ShowInlineKeyboard(
|
|
||||||
Core::Frontend::InlineAppearParameters appear_parameters) const override;
|
|
||||||
|
|
||||||
void HideInlineKeyboard() const override;
|
|
||||||
|
|
||||||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override;
|
|
||||||
|
|
||||||
void ExitKeyboard() const override;
|
|
||||||
|
|
||||||
void SubmitInlineKeyboardText(std::u16string submitted_text);
|
|
||||||
|
|
||||||
void SubmitInlineKeyboardInput(int key_code);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ResultData {
|
|
||||||
static ResultData CreateFromFrontend(jobject object);
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
Service::AM::Frontend::SwkbdResult result{};
|
|
||||||
};
|
|
||||||
|
|
||||||
void SubmitNormalText(const ResultData& result) const;
|
|
||||||
|
|
||||||
Core::Frontend::KeyboardInitializeParameters parameters{};
|
|
||||||
|
|
||||||
mutable SubmitNormalCallback submit_normal_callback;
|
|
||||||
mutable SubmitInlineCallback submit_inline_callback;
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable bool m_is_inline_active{};
|
|
||||||
std::u16string m_current_text;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Should be called in JNI_Load
|
|
||||||
void InitJNI(JNIEnv* env);
|
|
||||||
|
|
||||||
// Should be called in JNI_Unload
|
|
||||||
void CleanupJNI(JNIEnv* env);
|
|
||||||
|
|
||||||
} // namespace SoftwareKeyboard
|
|
||||||
|
|
||||||
// Native function calls
|
|
||||||
extern "C" {
|
|
||||||
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters(
|
|
||||||
JNIEnv* env, jclass clazz, jstring text);
|
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput(
|
|
||||||
JNIEnv* env, jclass clazz, jstring text);
|
|
||||||
}
|
|
|
@ -1,330 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <INIReader.h>
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/fs/fs.h"
|
|
||||||
#include "common/fs/path_util.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "common/settings_enums.h"
|
|
||||||
#include "core/hle/service/acc/profile_manager.h"
|
|
||||||
#include "input_common/main.h"
|
|
||||||
#include "jni/config.h"
|
|
||||||
#include "jni/default_ini.h"
|
|
||||||
#include "uisettings.h"
|
|
||||||
|
|
||||||
namespace FS = Common::FS;
|
|
||||||
|
|
||||||
Config::Config(const std::string& config_name, ConfigType config_type)
|
|
||||||
: type(config_type), global{config_type == ConfigType::GlobalConfig} {
|
|
||||||
Initialize(config_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Config::~Config() = default;
|
|
||||||
|
|
||||||
bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
|
||||||
void(FS::CreateParentDir(config_loc));
|
|
||||||
config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
|
|
||||||
const auto config_loc_str = FS::PathToUTF8String(config_loc);
|
|
||||||
if (config->ParseError() < 0) {
|
|
||||||
if (retry) {
|
|
||||||
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
|
|
||||||
config_loc_str);
|
|
||||||
|
|
||||||
void(FS::CreateParentDir(config_loc));
|
|
||||||
void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents));
|
|
||||||
|
|
||||||
config = std::make_unique<INIReader>(config_loc_str);
|
|
||||||
|
|
||||||
return LoadINI(default_contents, false);
|
|
||||||
}
|
|
||||||
LOG_ERROR(Config, "Failed.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
|
||||||
std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault());
|
|
||||||
if (setting_value.empty()) {
|
|
||||||
setting_value = setting.GetDefault();
|
|
||||||
}
|
|
||||||
setting = std::move(setting_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
|
||||||
setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Type, bool ranged>
|
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
|
||||||
setting = static_cast<Type>(
|
|
||||||
config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Config::ReadValues() {
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.touch_device);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
|
|
||||||
Settings::values.touchscreen.enabled =
|
|
||||||
config->GetBoolean("ControlsGeneral", "touch_enabled", true);
|
|
||||||
Settings::values.touchscreen.rotation_angle =
|
|
||||||
config->GetInteger("ControlsGeneral", "touch_angle", 0);
|
|
||||||
Settings::values.touchscreen.diameter_x =
|
|
||||||
config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
|
|
||||||
Settings::values.touchscreen.diameter_y =
|
|
||||||
config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
|
|
||||||
|
|
||||||
int num_touch_from_button_maps =
|
|
||||||
config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
|
|
||||||
if (num_touch_from_button_maps > 0) {
|
|
||||||
for (int i = 0; i < num_touch_from_button_maps; ++i) {
|
|
||||||
Settings::TouchFromButtonMap map;
|
|
||||||
map.name = config->Get("ControlsGeneral",
|
|
||||||
std::string("touch_from_button_maps_") + std::to_string(i) +
|
|
||||||
std::string("_name"),
|
|
||||||
"default");
|
|
||||||
const int num_touch_maps = config->GetInteger(
|
|
||||||
"ControlsGeneral",
|
|
||||||
std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
|
|
||||||
0);
|
|
||||||
map.buttons.reserve(num_touch_maps);
|
|
||||||
|
|
||||||
for (int j = 0; j < num_touch_maps; ++j) {
|
|
||||||
std::string touch_mapping =
|
|
||||||
config->Get("ControlsGeneral",
|
|
||||||
std::string("touch_from_button_maps_") + std::to_string(i) +
|
|
||||||
std::string("_bind_") + std::to_string(j),
|
|
||||||
"");
|
|
||||||
map.buttons.emplace_back(std::move(touch_mapping));
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings::values.touch_from_button_maps.emplace_back(std::move(map));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Settings::values.touch_from_button_maps.emplace_back(
|
|
||||||
Settings::TouchFromButtonMap{"default", {}});
|
|
||||||
num_touch_from_button_maps = 1;
|
|
||||||
}
|
|
||||||
Settings::values.touch_from_button_map_index = std::clamp(
|
|
||||||
Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
|
|
||||||
|
|
||||||
ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
|
|
||||||
|
|
||||||
// Data Storage
|
|
||||||
ReadSetting("Data Storage", Settings::values.use_virtual_sd);
|
|
||||||
FS::SetYuzuPath(FS::YuzuPath::NANDDir,
|
|
||||||
config->Get("Data Storage", "nand_directory",
|
|
||||||
FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
|
|
||||||
FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
|
|
||||||
config->Get("Data Storage", "sdmc_directory",
|
|
||||||
FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
|
|
||||||
FS::SetYuzuPath(FS::YuzuPath::LoadDir,
|
|
||||||
config->Get("Data Storage", "load_directory",
|
|
||||||
FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
|
|
||||||
FS::SetYuzuPath(FS::YuzuPath::DumpDir,
|
|
||||||
config->Get("Data Storage", "dump_directory",
|
|
||||||
FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
|
|
||||||
ReadSetting("Data Storage", Settings::values.gamecard_inserted);
|
|
||||||
ReadSetting("Data Storage", Settings::values.gamecard_current_game);
|
|
||||||
ReadSetting("Data Storage", Settings::values.gamecard_path);
|
|
||||||
|
|
||||||
// System
|
|
||||||
ReadSetting("System", Settings::values.current_user);
|
|
||||||
Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
|
|
||||||
Service::Account::MAX_USERS - 1);
|
|
||||||
|
|
||||||
// Disable docked mode by default on Android
|
|
||||||
Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
|
|
||||||
? Settings::ConsoleMode::Docked
|
|
||||||
: Settings::ConsoleMode::Handheld);
|
|
||||||
|
|
||||||
const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
|
|
||||||
if (rng_seed_enabled) {
|
|
||||||
Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
|
|
||||||
} else {
|
|
||||||
Settings::values.rng_seed.SetValue(0);
|
|
||||||
}
|
|
||||||
Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
|
|
||||||
|
|
||||||
const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
|
|
||||||
if (custom_rtc_enabled) {
|
|
||||||
Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
|
|
||||||
} else {
|
|
||||||
Settings::values.custom_rtc = 0;
|
|
||||||
}
|
|
||||||
Settings::values.custom_rtc_enabled = custom_rtc_enabled;
|
|
||||||
|
|
||||||
ReadSetting("System", Settings::values.language_index);
|
|
||||||
ReadSetting("System", Settings::values.region_index);
|
|
||||||
ReadSetting("System", Settings::values.time_zone_index);
|
|
||||||
ReadSetting("System", Settings::values.sound_index);
|
|
||||||
|
|
||||||
// Core
|
|
||||||
ReadSetting("Core", Settings::values.use_multi_core);
|
|
||||||
ReadSetting("Core", Settings::values.memory_layout_mode);
|
|
||||||
|
|
||||||
// Cpu
|
|
||||||
ReadSetting("Cpu", Settings::values.cpu_accuracy);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpu_debug_mode);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
|
|
||||||
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
|
|
||||||
|
|
||||||
// Renderer
|
|
||||||
ReadSetting("Renderer", Settings::values.renderer_backend);
|
|
||||||
ReadSetting("Renderer", Settings::values.renderer_debug);
|
|
||||||
ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
|
|
||||||
ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
|
|
||||||
ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
|
|
||||||
ReadSetting("Renderer", Settings::values.vulkan_device);
|
|
||||||
|
|
||||||
ReadSetting("Renderer", Settings::values.resolution_setup);
|
|
||||||
ReadSetting("Renderer", Settings::values.scaling_filter);
|
|
||||||
ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
|
|
||||||
ReadSetting("Renderer", Settings::values.anti_aliasing);
|
|
||||||
ReadSetting("Renderer", Settings::values.fullscreen_mode);
|
|
||||||
ReadSetting("Renderer", Settings::values.aspect_ratio);
|
|
||||||
ReadSetting("Renderer", Settings::values.max_anisotropy);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_speed_limit);
|
|
||||||
ReadSetting("Renderer", Settings::values.speed_limit);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
|
|
||||||
ReadSetting("Renderer", Settings::values.vsync_mode);
|
|
||||||
ReadSetting("Renderer", Settings::values.shader_backend);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
|
|
||||||
ReadSetting("Renderer", Settings::values.nvdec_emulation);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
|
|
||||||
ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
|
|
||||||
|
|
||||||
ReadSetting("Renderer", Settings::values.bg_red);
|
|
||||||
ReadSetting("Renderer", Settings::values.bg_green);
|
|
||||||
ReadSetting("Renderer", Settings::values.bg_blue);
|
|
||||||
|
|
||||||
// Use GPU accuracy normal by default on Android
|
|
||||||
Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger(
|
|
||||||
"Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal)));
|
|
||||||
|
|
||||||
// Use GPU default anisotropic filtering on Android
|
|
||||||
Settings::values.max_anisotropy =
|
|
||||||
static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
|
|
||||||
|
|
||||||
// Disable ASTC compute by default on Android
|
|
||||||
Settings::values.accelerate_astc.SetValue(
|
|
||||||
config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
|
|
||||||
: Settings::AstcDecodeMode::Cpu);
|
|
||||||
|
|
||||||
// Enable asynchronous presentation by default on Android
|
|
||||||
Settings::values.async_presentation =
|
|
||||||
config->GetBoolean("Renderer", "async_presentation", true);
|
|
||||||
|
|
||||||
// Disable force_max_clock by default on Android
|
|
||||||
Settings::values.renderer_force_max_clock =
|
|
||||||
config->GetBoolean("Renderer", "force_max_clock", false);
|
|
||||||
|
|
||||||
// Disable use_reactive_flushing by default on Android
|
|
||||||
Settings::values.use_reactive_flushing =
|
|
||||||
config->GetBoolean("Renderer", "use_reactive_flushing", false);
|
|
||||||
|
|
||||||
// Audio
|
|
||||||
ReadSetting("Audio", Settings::values.sink_id);
|
|
||||||
ReadSetting("Audio", Settings::values.audio_output_device_id);
|
|
||||||
ReadSetting("Audio", Settings::values.volume);
|
|
||||||
|
|
||||||
// Miscellaneous
|
|
||||||
// log_filter has a different default here than from common
|
|
||||||
Settings::values.log_filter = "*:Info";
|
|
||||||
ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
|
|
||||||
|
|
||||||
// Debugging
|
|
||||||
Settings::values.record_frame_times =
|
|
||||||
config->GetBoolean("Debugging", "record_frame_times", false);
|
|
||||||
ReadSetting("Debugging", Settings::values.dump_exefs);
|
|
||||||
ReadSetting("Debugging", Settings::values.dump_nso);
|
|
||||||
ReadSetting("Debugging", Settings::values.enable_fs_access_log);
|
|
||||||
ReadSetting("Debugging", Settings::values.reporting_services);
|
|
||||||
ReadSetting("Debugging", Settings::values.quest_flag);
|
|
||||||
ReadSetting("Debugging", Settings::values.use_debug_asserts);
|
|
||||||
ReadSetting("Debugging", Settings::values.use_auto_stub);
|
|
||||||
ReadSetting("Debugging", Settings::values.disable_macro_jit);
|
|
||||||
ReadSetting("Debugging", Settings::values.disable_macro_hle);
|
|
||||||
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
|
||||||
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
|
||||||
|
|
||||||
const auto title_list = config->Get("AddOns", "title_ids", "");
|
|
||||||
std::stringstream ss(title_list);
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(ss, line, '|')) {
|
|
||||||
const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
|
|
||||||
const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
|
|
||||||
|
|
||||||
std::stringstream inner_ss(disabled_list);
|
|
||||||
std::string inner_line;
|
|
||||||
std::vector<std::string> out;
|
|
||||||
while (std::getline(inner_ss, inner_line, '|')) {
|
|
||||||
out.push_back(inner_line);
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings::values.disabled_addons.insert_or_assign(title_id, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web Service
|
|
||||||
ReadSetting("WebService", Settings::values.enable_telemetry);
|
|
||||||
ReadSetting("WebService", Settings::values.web_api_url);
|
|
||||||
ReadSetting("WebService", Settings::values.yuzu_username);
|
|
||||||
ReadSetting("WebService", Settings::values.yuzu_token);
|
|
||||||
|
|
||||||
// Network
|
|
||||||
ReadSetting("Network", Settings::values.network_interface);
|
|
||||||
|
|
||||||
// Android
|
|
||||||
ReadSetting("Android", AndroidSettings::values.picture_in_picture);
|
|
||||||
ReadSetting("Android", AndroidSettings::values.screen_layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Config::Initialize(const std::string& config_name) {
|
|
||||||
const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
|
|
||||||
const auto config_file = fmt::format("{}.ini", config_name);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ConfigType::GlobalConfig:
|
|
||||||
config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
|
|
||||||
break;
|
|
||||||
case ConfigType::PerGameConfig:
|
|
||||||
config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
|
|
||||||
break;
|
|
||||||
case ConfigType::InputProfile:
|
|
||||||
config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
|
|
||||||
LoadINI(DefaultINI::android_config_file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LoadINI(DefaultINI::android_config_file);
|
|
||||||
ReadValues();
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "common/settings.h"
|
|
||||||
|
|
||||||
class INIReader;
|
|
||||||
|
|
||||||
class Config {
|
|
||||||
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum class ConfigType {
|
|
||||||
GlobalConfig,
|
|
||||||
PerGameConfig,
|
|
||||||
InputProfile,
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit Config(const std::string& config_name = "config",
|
|
||||||
ConfigType config_type = ConfigType::GlobalConfig);
|
|
||||||
~Config();
|
|
||||||
|
|
||||||
void Initialize(const std::string& config_name);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Applies a value read from the config to a Setting.
|
|
||||||
*
|
|
||||||
* @param group The name of the INI group
|
|
||||||
* @param setting The yuzu setting to modify
|
|
||||||
*/
|
|
||||||
template <typename Type, bool ranged>
|
|
||||||
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
|
||||||
|
|
||||||
void ReadValues();
|
|
||||||
|
|
||||||
const ConfigType type;
|
|
||||||
std::unique_ptr<INIReader> config;
|
|
||||||
std::string config_loc;
|
|
||||||
const bool global;
|
|
||||||
};
|
|
|
@ -1,511 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace DefaultINI {
|
|
||||||
|
|
||||||
const char* android_config_file = R"(
|
|
||||||
|
|
||||||
[ControlsP0]
|
|
||||||
# The input devices and parameters for each Switch native input
|
|
||||||
# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
|
|
||||||
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
|
|
||||||
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
|
|
||||||
|
|
||||||
# Indicates if this player should be connected at boot
|
|
||||||
connected=
|
|
||||||
|
|
||||||
# for button input, the following devices are available:
|
|
||||||
# - "keyboard" (default) for keyboard input. Required parameters:
|
|
||||||
# - "code": the code of the key to bind
|
|
||||||
# - "sdl" for joystick input using SDL. Required parameters:
|
|
||||||
# - "guid": SDL identification GUID of the joystick
|
|
||||||
# - "port": the index of the joystick to bind
|
|
||||||
# - "button"(optional): the index of the button to bind
|
|
||||||
# - "hat"(optional): the index of the hat to bind as direction buttons
|
|
||||||
# - "axis"(optional): the index of the axis to bind
|
|
||||||
# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
|
|
||||||
# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
|
|
||||||
# triggered if the axis value crosses
|
|
||||||
# - "direction"(only used for axis): "+" means the button is triggered when the axis value
|
|
||||||
# is greater than the threshold; "-" means the button is triggered when the axis value
|
|
||||||
# is smaller than the threshold
|
|
||||||
button_a=
|
|
||||||
button_b=
|
|
||||||
button_x=
|
|
||||||
button_y=
|
|
||||||
button_lstick=
|
|
||||||
button_rstick=
|
|
||||||
button_l=
|
|
||||||
button_r=
|
|
||||||
button_zl=
|
|
||||||
button_zr=
|
|
||||||
button_plus=
|
|
||||||
button_minus=
|
|
||||||
button_dleft=
|
|
||||||
button_dup=
|
|
||||||
button_dright=
|
|
||||||
button_ddown=
|
|
||||||
button_lstick_left=
|
|
||||||
button_lstick_up=
|
|
||||||
button_lstick_right=
|
|
||||||
button_lstick_down=
|
|
||||||
button_sl=
|
|
||||||
button_sr=
|
|
||||||
button_home=
|
|
||||||
button_screenshot=
|
|
||||||
|
|
||||||
# for analog input, the following devices are available:
|
|
||||||
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
|
|
||||||
# - "up", "down", "left", "right": sub-devices for each direction.
|
|
||||||
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
|
|
||||||
# - "modifier": sub-devices as a modifier.
|
|
||||||
# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
|
|
||||||
# Must be in range of 0.0-1.0. Defaults to 0.5
|
|
||||||
# - "sdl" for joystick input using SDL. Required parameters:
|
|
||||||
# - "guid": SDL identification GUID of the joystick
|
|
||||||
# - "port": the index of the joystick to bind
|
|
||||||
# - "axis_x": the index of the axis to bind as x-axis (default to 0)
|
|
||||||
# - "axis_y": the index of the axis to bind as y-axis (default to 1)
|
|
||||||
lstick=
|
|
||||||
rstick=
|
|
||||||
|
|
||||||
# for motion input, the following devices are available:
|
|
||||||
# - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
|
|
||||||
# - "code": the code of the key to bind
|
|
||||||
# - "sdl" for motion input using SDL. Required parameters:
|
|
||||||
# - "guid": SDL identification GUID of the joystick
|
|
||||||
# - "port": the index of the joystick to bind
|
|
||||||
# - "motion": the index of the motion sensor to bind
|
|
||||||
# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
|
|
||||||
# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
|
|
||||||
# - "port": the port of the cemu hook server
|
|
||||||
# - "pad": the index of the joystick
|
|
||||||
# - "motion": the index of the motion sensor of the joystick to bind
|
|
||||||
motionleft=
|
|
||||||
motionright=
|
|
||||||
|
|
||||||
[ControlsGeneral]
|
|
||||||
# To use the debug_pad, prepend `debug_pad_` before each button setting above.
|
|
||||||
# i.e. debug_pad_button_a=
|
|
||||||
|
|
||||||
# Enable debug pad inputs to the guest
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
debug_pad_enabled =
|
|
||||||
|
|
||||||
# Whether to enable or disable vibration
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
vibration_enabled=
|
|
||||||
|
|
||||||
# Whether to enable or disable accurate vibrations
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
enable_accurate_vibrations=
|
|
||||||
|
|
||||||
# Enables controller motion inputs
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
motion_enabled =
|
|
||||||
|
|
||||||
# Defines the udp device's touch screen coordinate system for cemuhookudp devices
|
|
||||||
# - "min_x", "min_y", "max_x", "max_y"
|
|
||||||
touch_device=
|
|
||||||
|
|
||||||
# for mapping buttons to touch inputs.
|
|
||||||
#touch_from_button_map=1
|
|
||||||
#touch_from_button_maps_0_name=default
|
|
||||||
#touch_from_button_maps_0_count=2
|
|
||||||
#touch_from_button_maps_0_bind_0=foo
|
|
||||||
#touch_from_button_maps_0_bind_1=bar
|
|
||||||
# etc.
|
|
||||||
|
|
||||||
# List of Cemuhook UDP servers, delimited by ','.
|
|
||||||
# Default: 127.0.0.1:26760
|
|
||||||
# Example: 127.0.0.1:26760,123.4.5.67:26761
|
|
||||||
udp_input_servers =
|
|
||||||
|
|
||||||
# Enable controlling an axis via a mouse input.
|
|
||||||
# 0 (default): Off, 1: On
|
|
||||||
mouse_panning =
|
|
||||||
|
|
||||||
# Set mouse sensitivity.
|
|
||||||
# Default: 1.0
|
|
||||||
mouse_panning_sensitivity =
|
|
||||||
|
|
||||||
# Emulate an analog control stick from keyboard inputs.
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
emulate_analog_keyboard =
|
|
||||||
|
|
||||||
# Enable mouse inputs to the guest
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
mouse_enabled =
|
|
||||||
|
|
||||||
# Enable keyboard inputs to the guest
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
keyboard_enabled =
|
|
||||||
|
|
||||||
[Core]
|
|
||||||
# Whether to use multi-core for CPU emulation
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
use_multi_core =
|
|
||||||
|
|
||||||
# Enable unsafe extended guest system memory layout (8GB DRAM)
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
use_unsafe_extended_memory_layout =
|
|
||||||
|
|
||||||
[Cpu]
|
|
||||||
# Adjusts various optimizations.
|
|
||||||
# Auto-select mode enables choice unsafe optimizations.
|
|
||||||
# Accurate enables only safe optimizations.
|
|
||||||
# Unsafe allows any unsafe optimizations.
|
|
||||||
# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
|
|
||||||
cpu_accuracy =
|
|
||||||
|
|
||||||
# Allow disabling safe optimizations.
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
cpu_debug_mode =
|
|
||||||
|
|
||||||
# Enable inline page tables optimization (faster guest memory access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_page_tables =
|
|
||||||
|
|
||||||
# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_block_linking =
|
|
||||||
|
|
||||||
# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_return_stack_buffer =
|
|
||||||
|
|
||||||
# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_fast_dispatcher =
|
|
||||||
|
|
||||||
# Enable context elimination CPU Optimization (reduce host memory use for guest context)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_context_elimination =
|
|
||||||
|
|
||||||
# Enable constant propagation CPU optimization (basic IR optimization)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_const_prop =
|
|
||||||
|
|
||||||
# Enable miscellaneous CPU optimizations (basic IR optimization)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_misc_ir =
|
|
||||||
|
|
||||||
# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_reduce_misalign_checks =
|
|
||||||
|
|
||||||
# Enable Host MMU Emulation (faster guest memory access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_fastmem =
|
|
||||||
|
|
||||||
# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_fastmem_exclusives =
|
|
||||||
|
|
||||||
# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_recompile_exclusives =
|
|
||||||
|
|
||||||
# Enable optimization to ignore invalid memory accesses (faster guest memory access)
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_ignore_memory_aborts =
|
|
||||||
|
|
||||||
# Enable unfuse FMA (improve performance on CPUs without FMA)
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_unfuse_fma =
|
|
||||||
|
|
||||||
# Enable faster FRSQRTE and FRECPE
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_reduce_fp_error =
|
|
||||||
|
|
||||||
# Enable faster ASIMD instructions (32 bits only)
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_ignore_standard_fpcr =
|
|
||||||
|
|
||||||
# Enable inaccurate NaN handling
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_inaccurate_nan =
|
|
||||||
|
|
||||||
# Disable address space checks (64 bits only)
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_fastmem_check =
|
|
||||||
|
|
||||||
# Enable faster exclusive instructions
|
|
||||||
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
|
|
||||||
# 0: Disabled, 1 (default): Enabled
|
|
||||||
cpuopt_unsafe_ignore_global_monitor =
|
|
||||||
|
|
||||||
[Renderer]
|
|
||||||
# Which backend API to use.
|
|
||||||
# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null
|
|
||||||
backend =
|
|
||||||
|
|
||||||
# Whether to enable asynchronous presentation (Vulkan only)
|
|
||||||
# 0: Off, 1 (default): On
|
|
||||||
async_presentation =
|
|
||||||
|
|
||||||
# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
force_max_clock =
|
|
||||||
|
|
||||||
# Enable graphics API debugging mode.
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
debug =
|
|
||||||
|
|
||||||
# Enable shader feedback.
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
renderer_shader_feedback =
|
|
||||||
|
|
||||||
# Enable Nsight Aftermath crash dumps
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
nsight_aftermath =
|
|
||||||
|
|
||||||
# Disable shader loop safety checks, executing the shader without loop logic changes
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
disable_shader_loop_safety_checks =
|
|
||||||
|
|
||||||
# Which Vulkan physical device to use (defaults to 0)
|
|
||||||
vulkan_device =
|
|
||||||
|
|
||||||
# 0: 0.5x (360p/540p) [EXPERIMENTAL]
|
|
||||||
# 1: 0.75x (540p/810p) [EXPERIMENTAL]
|
|
||||||
# 2 (default): 1x (720p/1080p)
|
|
||||||
# 3: 2x (1440p/2160p)
|
|
||||||
# 4: 3x (2160p/3240p)
|
|
||||||
# 5: 4x (2880p/4320p)
|
|
||||||
# 6: 5x (3600p/5400p)
|
|
||||||
# 7: 6x (4320p/6480p)
|
|
||||||
resolution_setup =
|
|
||||||
|
|
||||||
# Pixel filter to use when up- or down-sampling rendered frames.
|
|
||||||
# 0: Nearest Neighbor
|
|
||||||
# 1 (default): Bilinear
|
|
||||||
# 2: Bicubic
|
|
||||||
# 3: Gaussian
|
|
||||||
# 4: ScaleForce
|
|
||||||
# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only]
|
|
||||||
scaling_filter =
|
|
||||||
|
|
||||||
# Anti-Aliasing (AA)
|
|
||||||
# 0 (default): None, 1: FXAA
|
|
||||||
anti_aliasing =
|
|
||||||
|
|
||||||
# Whether to use fullscreen or borderless window mode
|
|
||||||
# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
|
|
||||||
fullscreen_mode =
|
|
||||||
|
|
||||||
# Aspect ratio
|
|
||||||
# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
|
|
||||||
aspect_ratio =
|
|
||||||
|
|
||||||
# Anisotropic filtering
|
|
||||||
# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
|
|
||||||
max_anisotropy =
|
|
||||||
|
|
||||||
# Whether to enable VSync or not.
|
|
||||||
# OpenGL: Values other than 0 enable VSync
|
|
||||||
# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
|
|
||||||
# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
|
|
||||||
# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
|
|
||||||
# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
|
|
||||||
# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
|
|
||||||
# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed
|
|
||||||
use_vsync =
|
|
||||||
|
|
||||||
# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
|
|
||||||
# not available and GLASM is selected, GLSL will be used.
|
|
||||||
# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
|
|
||||||
shader_backend =
|
|
||||||
|
|
||||||
# Whether to allow asynchronous shader building.
|
|
||||||
# 0 (default): Off, 1: On
|
|
||||||
use_asynchronous_shaders =
|
|
||||||
|
|
||||||
# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
|
|
||||||
# 0 (default): Off, 1: On
|
|
||||||
use_reactive_flushing =
|
|
||||||
|
|
||||||
# NVDEC emulation.
|
|
||||||
# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
|
|
||||||
nvdec_emulation =
|
|
||||||
|
|
||||||
# Accelerate ASTC texture decoding.
|
|
||||||
# 0 (default): Off, 1: On
|
|
||||||
accelerate_astc =
|
|
||||||
|
|
||||||
# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
|
|
||||||
# 0: Off, 1: On (default)
|
|
||||||
use_speed_limit =
|
|
||||||
|
|
||||||
# Limits the speed of the game to run no faster than this value as a percentage of target speed
|
|
||||||
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
|
|
||||||
speed_limit =
|
|
||||||
|
|
||||||
# Whether to use disk based shader cache
|
|
||||||
# 0: Off, 1 (default): On
|
|
||||||
use_disk_shader_cache =
|
|
||||||
|
|
||||||
# Which gpu accuracy level to use
|
|
||||||
# 0 (default): Normal, 1: High, 2: Extreme (Very slow)
|
|
||||||
gpu_accuracy =
|
|
||||||
|
|
||||||
# Whether to use asynchronous GPU emulation
|
|
||||||
# 0 : Off (slow), 1 (default): On (fast)
|
|
||||||
use_asynchronous_gpu_emulation =
|
|
||||||
|
|
||||||
# Inform the guest that GPU operations completed more quickly than they did.
|
|
||||||
# 0: Off, 1 (default): On
|
|
||||||
use_fast_gpu_time =
|
|
||||||
|
|
||||||
# Force unmodified buffers to be flushed, which can cost performance.
|
|
||||||
# 0: Off (default), 1: On
|
|
||||||
use_pessimistic_flushes =
|
|
||||||
|
|
||||||
# Whether to use garbage collection or not for GPU caches.
|
|
||||||
# 0 (default): Off, 1: On
|
|
||||||
use_caches_gc =
|
|
||||||
|
|
||||||
# The clear color for the renderer. What shows up on the sides of the bottom screen.
|
|
||||||
# Must be in range of 0-255. Defaults to 0 for all.
|
|
||||||
bg_red =
|
|
||||||
bg_blue =
|
|
||||||
bg_green =
|
|
||||||
|
|
||||||
[Audio]
|
|
||||||
# Which audio output engine to use.
|
|
||||||
# auto (default): Auto-select
|
|
||||||
# cubeb: Cubeb audio engine (if available)
|
|
||||||
# sdl2: SDL2 audio engine (if available)
|
|
||||||
# null: No audio output
|
|
||||||
output_engine =
|
|
||||||
|
|
||||||
# Which audio device to use.
|
|
||||||
# auto (default): Auto-select
|
|
||||||
output_device =
|
|
||||||
|
|
||||||
# Output volume.
|
|
||||||
# 100 (default): 100%, 0; mute
|
|
||||||
volume =
|
|
||||||
|
|
||||||
[Data Storage]
|
|
||||||
# Whether to create a virtual SD card.
|
|
||||||
# 1: Yes, 0 (default): No
|
|
||||||
use_virtual_sd =
|
|
||||||
|
|
||||||
# Whether or not to enable gamecard emulation
|
|
||||||
# 1: Yes, 0 (default): No
|
|
||||||
gamecard_inserted =
|
|
||||||
|
|
||||||
# Whether or not the gamecard should be emulated as the current game
|
|
||||||
# If 'gamecard_inserted' is 0 this setting is irrelevant
|
|
||||||
# 1: Yes, 0 (default): No
|
|
||||||
gamecard_current_game =
|
|
||||||
|
|
||||||
# Path to an XCI file to use as the gamecard
|
|
||||||
# If 'gamecard_inserted' is 0 this setting is irrelevant
|
|
||||||
# If 'gamecard_current_game' is 1 this setting is irrelevant
|
|
||||||
gamecard_path =
|
|
||||||
|
|
||||||
[System]
|
|
||||||
# Whether the system is docked
|
|
||||||
# 1 (default): Yes, 0: No
|
|
||||||
use_docked_mode =
|
|
||||||
|
|
||||||
# Sets the seed for the RNG generator built into the switch
|
|
||||||
# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
|
|
||||||
rng_seed_enabled =
|
|
||||||
rng_seed =
|
|
||||||
|
|
||||||
# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
|
|
||||||
# This will auto-increment, with the time set being the time the game is started
|
|
||||||
# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
|
|
||||||
custom_rtc_enabled =
|
|
||||||
custom_rtc =
|
|
||||||
|
|
||||||
# Sets the systems language index
|
|
||||||
# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
|
|
||||||
# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
|
|
||||||
# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
|
|
||||||
language_index =
|
|
||||||
|
|
||||||
# The system region that yuzu will use during emulation
|
|
||||||
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
|
||||||
region_index =
|
|
||||||
|
|
||||||
# The system time zone that yuzu will use during emulation
|
|
||||||
# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
|
|
||||||
time_zone_index =
|
|
||||||
|
|
||||||
# Sets the sound output mode.
|
|
||||||
# 0: Mono, 1 (default): Stereo, 2: Surround
|
|
||||||
sound_index =
|
|
||||||
|
|
||||||
[Miscellaneous]
|
|
||||||
# A filter which removes logs below a certain logging level.
|
|
||||||
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
|
|
||||||
log_filter = *:Trace
|
|
||||||
|
|
||||||
# Use developer keys
|
|
||||||
# 0 (default): Disabled, 1: Enabled
|
|
||||||
use_dev_keys =
|
|
||||||
|
|
||||||
[Debugging]
|
|
||||||
# Record frame time data, can be found in the log directory. Boolean value
|
|
||||||
record_frame_times =
|
|
||||||
# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
|
|
||||||
dump_exefs=false
|
|
||||||
# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
|
|
||||||
dump_nso=false
|
|
||||||
# Determines whether or not yuzu will save the filesystem access log.
|
|
||||||
enable_fs_access_log=false
|
|
||||||
# Enables verbose reporting services
|
|
||||||
reporting_services =
|
|
||||||
# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
|
|
||||||
# false: Retail/Normal Mode (default), true: Kiosk Mode
|
|
||||||
quest_flag =
|
|
||||||
# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
|
|
||||||
# false: Disabled (default), true: Enabled
|
|
||||||
use_debug_asserts =
|
|
||||||
# Determines whether unimplemented HLE service calls should be automatically stubbed.
|
|
||||||
# false: Disabled (default), true: Enabled
|
|
||||||
use_auto_stub =
|
|
||||||
# Enables/Disables the macro JIT compiler
|
|
||||||
disable_macro_jit=false
|
|
||||||
# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
|
|
||||||
# false: Disabled (default), true: Enabled
|
|
||||||
use_gdbstub=false
|
|
||||||
# The port to use for the GDB server, if it is enabled.
|
|
||||||
gdbstub_port=6543
|
|
||||||
|
|
||||||
[WebService]
|
|
||||||
# Whether or not to enable telemetry
|
|
||||||
# 0: No, 1 (default): Yes
|
|
||||||
enable_telemetry =
|
|
||||||
# URL for Web API
|
|
||||||
web_api_url = https://api.yuzu-emu.org
|
|
||||||
# Username and token for yuzu Web Service
|
|
||||||
# See https://profile.yuzu-emu.org/ for more info
|
|
||||||
yuzu_username =
|
|
||||||
yuzu_token =
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
# Name of the network interface device to use with yuzu LAN play.
|
|
||||||
# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
|
|
||||||
# e.g. On Windows: 'Ethernet', 'Wi-Fi'
|
|
||||||
network_interface =
|
|
||||||
|
|
||||||
[AddOns]
|
|
||||||
# Used to disable add-ons
|
|
||||||
# List of title IDs of games that will have add-ons disabled (separated by '|'):
|
|
||||||
title_ids =
|
|
||||||
# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
|
|
||||||
# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
|
|
||||||
)";
|
|
||||||
} // namespace DefaultINI
|
|
|
@ -1,428 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/fs/fs_android.h"
|
|
||||||
#include "jni/applets/software_keyboard.h"
|
|
||||||
#include "jni/id_cache.h"
|
|
||||||
#include "video_core/rasterizer_interface.h"
|
|
||||||
|
|
||||||
static JavaVM* s_java_vm;
|
|
||||||
static jclass s_native_library_class;
|
|
||||||
static jclass s_disk_cache_progress_class;
|
|
||||||
static jclass s_load_callback_stage_class;
|
|
||||||
static jclass s_game_dir_class;
|
|
||||||
static jmethodID s_game_dir_constructor;
|
|
||||||
static jmethodID s_exit_emulation_activity;
|
|
||||||
static jmethodID s_disk_cache_load_progress;
|
|
||||||
static jmethodID s_on_emulation_started;
|
|
||||||
static jmethodID s_on_emulation_stopped;
|
|
||||||
static jmethodID s_on_program_changed;
|
|
||||||
|
|
||||||
static jclass s_game_class;
|
|
||||||
static jmethodID s_game_constructor;
|
|
||||||
static jfieldID s_game_title_field;
|
|
||||||
static jfieldID s_game_path_field;
|
|
||||||
static jfieldID s_game_program_id_field;
|
|
||||||
static jfieldID s_game_developer_field;
|
|
||||||
static jfieldID s_game_version_field;
|
|
||||||
static jfieldID s_game_is_homebrew_field;
|
|
||||||
|
|
||||||
static jclass s_string_class;
|
|
||||||
static jclass s_pair_class;
|
|
||||||
static jmethodID s_pair_constructor;
|
|
||||||
static jfieldID s_pair_first_field;
|
|
||||||
static jfieldID s_pair_second_field;
|
|
||||||
|
|
||||||
static jclass s_overlay_control_data_class;
|
|
||||||
static jmethodID s_overlay_control_data_constructor;
|
|
||||||
static jfieldID s_overlay_control_data_id_field;
|
|
||||||
static jfieldID s_overlay_control_data_enabled_field;
|
|
||||||
static jfieldID s_overlay_control_data_landscape_position_field;
|
|
||||||
static jfieldID s_overlay_control_data_portrait_position_field;
|
|
||||||
static jfieldID s_overlay_control_data_foldable_position_field;
|
|
||||||
|
|
||||||
static jclass s_patch_class;
|
|
||||||
static jmethodID s_patch_constructor;
|
|
||||||
static jfieldID s_patch_enabled_field;
|
|
||||||
static jfieldID s_patch_name_field;
|
|
||||||
static jfieldID s_patch_version_field;
|
|
||||||
static jfieldID s_patch_type_field;
|
|
||||||
static jfieldID s_patch_program_id_field;
|
|
||||||
static jfieldID s_patch_title_id_field;
|
|
||||||
|
|
||||||
static jclass s_double_class;
|
|
||||||
static jmethodID s_double_constructor;
|
|
||||||
static jfieldID s_double_value_field;
|
|
||||||
|
|
||||||
static jclass s_integer_class;
|
|
||||||
static jmethodID s_integer_constructor;
|
|
||||||
static jfieldID s_integer_value_field;
|
|
||||||
|
|
||||||
static jclass s_boolean_class;
|
|
||||||
static jmethodID s_boolean_constructor;
|
|
||||||
static jfieldID s_boolean_value_field;
|
|
||||||
|
|
||||||
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
|
||||||
|
|
||||||
namespace IDCache {
|
|
||||||
|
|
||||||
JNIEnv* GetEnvForThread() {
|
|
||||||
thread_local static struct OwnedEnv {
|
|
||||||
OwnedEnv() {
|
|
||||||
status = s_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
|
||||||
if (status == JNI_EDETACHED)
|
|
||||||
s_java_vm->AttachCurrentThread(&env, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
~OwnedEnv() {
|
|
||||||
if (status == JNI_EDETACHED)
|
|
||||||
s_java_vm->DetachCurrentThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
int status;
|
|
||||||
JNIEnv* env = nullptr;
|
|
||||||
} owned;
|
|
||||||
return owned.env;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetNativeLibraryClass() {
|
|
||||||
return s_native_library_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetDiskCacheProgressClass() {
|
|
||||||
return s_disk_cache_progress_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetDiskCacheLoadCallbackStageClass() {
|
|
||||||
return s_load_callback_stage_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetGameDirClass() {
|
|
||||||
return s_game_dir_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetGameDirConstructor() {
|
|
||||||
return s_game_dir_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetExitEmulationActivity() {
|
|
||||||
return s_exit_emulation_activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetDiskCacheLoadProgress() {
|
|
||||||
return s_disk_cache_load_progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetOnEmulationStarted() {
|
|
||||||
return s_on_emulation_started;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetOnEmulationStopped() {
|
|
||||||
return s_on_emulation_stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetOnProgramChanged() {
|
|
||||||
return s_on_program_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetGameClass() {
|
|
||||||
return s_game_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetGameConstructor() {
|
|
||||||
return s_game_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGameTitleField() {
|
|
||||||
return s_game_title_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGamePathField() {
|
|
||||||
return s_game_path_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGameProgramIdField() {
|
|
||||||
return s_game_program_id_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGameDeveloperField() {
|
|
||||||
return s_game_developer_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGameVersionField() {
|
|
||||||
return s_game_version_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetGameIsHomebrewField() {
|
|
||||||
return s_game_is_homebrew_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetStringClass() {
|
|
||||||
return s_string_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetPairClass() {
|
|
||||||
return s_pair_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetPairConstructor() {
|
|
||||||
return s_pair_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPairFirstField() {
|
|
||||||
return s_pair_first_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPairSecondField() {
|
|
||||||
return s_pair_second_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetOverlayControlDataClass() {
|
|
||||||
return s_overlay_control_data_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetOverlayControlDataConstructor() {
|
|
||||||
return s_overlay_control_data_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataIdField() {
|
|
||||||
return s_overlay_control_data_id_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataEnabledField() {
|
|
||||||
return s_overlay_control_data_enabled_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField() {
|
|
||||||
return s_overlay_control_data_landscape_position_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataPortraitPositionField() {
|
|
||||||
return s_overlay_control_data_portrait_position_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataFoldablePositionField() {
|
|
||||||
return s_overlay_control_data_foldable_position_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetPatchClass() {
|
|
||||||
return s_patch_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetPatchConstructor() {
|
|
||||||
return s_patch_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchEnabledField() {
|
|
||||||
return s_patch_enabled_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchNameField() {
|
|
||||||
return s_patch_name_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchVersionField() {
|
|
||||||
return s_patch_version_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchTypeField() {
|
|
||||||
return s_patch_type_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchProgramIdField() {
|
|
||||||
return s_patch_program_id_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetPatchTitleIdField() {
|
|
||||||
return s_patch_title_id_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetDoubleClass() {
|
|
||||||
return s_double_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetDoubleConstructor() {
|
|
||||||
return s_double_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetDoubleValueField() {
|
|
||||||
return s_double_value_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetIntegerClass() {
|
|
||||||
return s_integer_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetIntegerConstructor() {
|
|
||||||
return s_integer_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetIntegerValueField() {
|
|
||||||
return s_integer_value_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass GetBooleanClass() {
|
|
||||||
return s_boolean_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID GetBooleanConstructor() {
|
|
||||||
return s_boolean_constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfieldID GetBooleanValueField() {
|
|
||||||
return s_boolean_value_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace IDCache
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|
||||||
s_java_vm = vm;
|
|
||||||
|
|
||||||
JNIEnv* env;
|
|
||||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK)
|
|
||||||
return JNI_ERR;
|
|
||||||
|
|
||||||
// Initialize Java classes
|
|
||||||
const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary");
|
|
||||||
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
|
||||||
s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress")));
|
|
||||||
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
|
|
||||||
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
|
|
||||||
|
|
||||||
const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
|
|
||||||
s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
|
|
||||||
s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V");
|
|
||||||
env->DeleteLocalRef(game_dir_class);
|
|
||||||
|
|
||||||
// Initialize methods
|
|
||||||
s_exit_emulation_activity =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
|
||||||
s_disk_cache_load_progress =
|
|
||||||
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
|
|
||||||
s_on_emulation_started =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
|
|
||||||
s_on_emulation_stopped =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
|
|
||||||
s_on_program_changed =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
|
|
||||||
|
|
||||||
const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
|
|
||||||
s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
|
|
||||||
s_game_constructor = env->GetMethodID(game_class, "<init>",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
|
|
||||||
"String;Ljava/lang/String;Ljava/lang/String;Z)V");
|
|
||||||
s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;");
|
|
||||||
s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;");
|
|
||||||
s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;");
|
|
||||||
s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;");
|
|
||||||
s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;");
|
|
||||||
s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z");
|
|
||||||
env->DeleteLocalRef(game_class);
|
|
||||||
|
|
||||||
const jclass string_class = env->FindClass("java/lang/String");
|
|
||||||
s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class));
|
|
||||||
env->DeleteLocalRef(string_class);
|
|
||||||
|
|
||||||
const jclass pair_class = env->FindClass("kotlin/Pair");
|
|
||||||
s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class));
|
|
||||||
s_pair_constructor =
|
|
||||||
env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V");
|
|
||||||
s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;");
|
|
||||||
s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
|
|
||||||
env->DeleteLocalRef(pair_class);
|
|
||||||
|
|
||||||
const jclass overlay_control_data_class =
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
|
|
||||||
s_overlay_control_data_class =
|
|
||||||
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
|
||||||
s_overlay_control_data_constructor =
|
|
||||||
env->GetMethodID(overlay_control_data_class, "<init>",
|
|
||||||
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
|
|
||||||
s_overlay_control_data_id_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
|
||||||
s_overlay_control_data_enabled_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "enabled", "Z");
|
|
||||||
s_overlay_control_data_landscape_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
|
|
||||||
s_overlay_control_data_portrait_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
|
||||||
s_overlay_control_data_foldable_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
|
||||||
env->DeleteLocalRef(overlay_control_data_class);
|
|
||||||
|
|
||||||
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
|
||||||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
|
||||||
s_patch_constructor = env->GetMethodID(
|
|
||||||
patch_class, "<init>",
|
|
||||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
|
|
||||||
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
|
||||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
|
||||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
|
||||||
s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
|
|
||||||
s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
|
|
||||||
s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
|
|
||||||
env->DeleteLocalRef(patch_class);
|
|
||||||
|
|
||||||
const jclass double_class = env->FindClass("java/lang/Double");
|
|
||||||
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
|
|
||||||
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
|
|
||||||
s_double_value_field = env->GetFieldID(double_class, "value", "D");
|
|
||||||
env->DeleteLocalRef(double_class);
|
|
||||||
|
|
||||||
const jclass int_class = env->FindClass("java/lang/Integer");
|
|
||||||
s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
|
|
||||||
s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
|
|
||||||
s_integer_value_field = env->GetFieldID(int_class, "value", "I");
|
|
||||||
env->DeleteLocalRef(int_class);
|
|
||||||
|
|
||||||
const jclass boolean_class = env->FindClass("java/lang/Boolean");
|
|
||||||
s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
|
|
||||||
s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
|
|
||||||
s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
|
|
||||||
env->DeleteLocalRef(boolean_class);
|
|
||||||
|
|
||||||
// Initialize Android Storage
|
|
||||||
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
|
||||||
|
|
||||||
// Initialize applets
|
|
||||||
SoftwareKeyboard::InitJNI(env);
|
|
||||||
|
|
||||||
return JNI_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
|
||||||
JNIEnv* env;
|
|
||||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnInitialize Android Storage
|
|
||||||
Common::FS::Android::UnRegisterCallbacks();
|
|
||||||
env->DeleteGlobalRef(s_native_library_class);
|
|
||||||
env->DeleteGlobalRef(s_disk_cache_progress_class);
|
|
||||||
env->DeleteGlobalRef(s_load_callback_stage_class);
|
|
||||||
env->DeleteGlobalRef(s_game_dir_class);
|
|
||||||
env->DeleteGlobalRef(s_game_class);
|
|
||||||
env->DeleteGlobalRef(s_string_class);
|
|
||||||
env->DeleteGlobalRef(s_pair_class);
|
|
||||||
env->DeleteGlobalRef(s_overlay_control_data_class);
|
|
||||||
env->DeleteGlobalRef(s_patch_class);
|
|
||||||
env->DeleteGlobalRef(s_double_class);
|
|
||||||
env->DeleteGlobalRef(s_integer_class);
|
|
||||||
env->DeleteGlobalRef(s_boolean_class);
|
|
||||||
|
|
||||||
// UnInitialize applets
|
|
||||||
SoftwareKeyboard::CleanupJNI(env);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,68 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include "video_core/rasterizer_interface.h"
|
|
||||||
|
|
||||||
namespace IDCache {
|
|
||||||
|
|
||||||
JNIEnv* GetEnvForThread();
|
|
||||||
jclass GetNativeLibraryClass();
|
|
||||||
jclass GetDiskCacheProgressClass();
|
|
||||||
jclass GetDiskCacheLoadCallbackStageClass();
|
|
||||||
jclass GetGameDirClass();
|
|
||||||
jmethodID GetGameDirConstructor();
|
|
||||||
jmethodID GetExitEmulationActivity();
|
|
||||||
jmethodID GetDiskCacheLoadProgress();
|
|
||||||
jmethodID GetOnEmulationStarted();
|
|
||||||
jmethodID GetOnEmulationStopped();
|
|
||||||
jmethodID GetOnProgramChanged();
|
|
||||||
|
|
||||||
jclass GetGameClass();
|
|
||||||
jmethodID GetGameConstructor();
|
|
||||||
jfieldID GetGameTitleField();
|
|
||||||
jfieldID GetGamePathField();
|
|
||||||
jfieldID GetGameProgramIdField();
|
|
||||||
jfieldID GetGameDeveloperField();
|
|
||||||
jfieldID GetGameVersionField();
|
|
||||||
jfieldID GetGameIsHomebrewField();
|
|
||||||
|
|
||||||
jclass GetStringClass();
|
|
||||||
jclass GetPairClass();
|
|
||||||
jmethodID GetPairConstructor();
|
|
||||||
jfieldID GetPairFirstField();
|
|
||||||
jfieldID GetPairSecondField();
|
|
||||||
|
|
||||||
jclass GetOverlayControlDataClass();
|
|
||||||
jmethodID GetOverlayControlDataConstructor();
|
|
||||||
jfieldID GetOverlayControlDataIdField();
|
|
||||||
jfieldID GetOverlayControlDataEnabledField();
|
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField();
|
|
||||||
jfieldID GetOverlayControlDataPortraitPositionField();
|
|
||||||
jfieldID GetOverlayControlDataFoldablePositionField();
|
|
||||||
|
|
||||||
jclass GetPatchClass();
|
|
||||||
jmethodID GetPatchConstructor();
|
|
||||||
jfieldID GetPatchEnabledField();
|
|
||||||
jfieldID GetPatchNameField();
|
|
||||||
jfieldID GetPatchVersionField();
|
|
||||||
jfieldID GetPatchTypeField();
|
|
||||||
jfieldID GetPatchProgramIdField();
|
|
||||||
jfieldID GetPatchTitleIdField();
|
|
||||||
|
|
||||||
jclass GetDoubleClass();
|
|
||||||
jmethodID GetDoubleConstructor();
|
|
||||||
jfieldID GetDoubleValueField();
|
|
||||||
|
|
||||||
jclass GetIntegerClass();
|
|
||||||
jmethodID GetIntegerConstructor();
|
|
||||||
jfieldID GetIntegerValueField();
|
|
||||||
|
|
||||||
jclass GetBooleanClass();
|
|
||||||
jmethodID GetBooleanConstructor();
|
|
||||||
jfieldID GetBooleanValueField();
|
|
||||||
|
|
||||||
} // namespace IDCache
|
|
|
@ -491,7 +491,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
|
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jobject clazz,
|
||||||
jstring hook_lib_dir,
|
jstring hook_lib_dir,
|
||||||
jstring custom_driver_dir,
|
jstring custom_driver_dir,
|
||||||
jstring custom_driver_name,
|
jstring custom_driver_name,
|
||||||
|
@ -555,32 +555,32 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getSystemDriverInfo(
|
||||||
return j_driver_info;
|
return j_driver_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jobject clazz) {
|
||||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||||
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jobject clazz) {
|
||||||
EmulationSession::GetInstance().UnPauseEmulation();
|
EmulationSession::GetInstance().UnPauseEmulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jobject clazz) {
|
||||||
EmulationSession::GetInstance().PauseEmulation();
|
EmulationSession::GetInstance().PauseEmulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jobject clazz) {
|
||||||
EmulationSession::GetInstance().HaltEmulation();
|
EmulationSession::GetInstance().HaltEmulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jobject clazz) {
|
||||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jobject clazz) {
|
||||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jobject clazz,
|
||||||
jboolean reload) {
|
jboolean reload) {
|
||||||
// Initialize the emulated system.
|
// Initialize the emulated system.
|
||||||
if (!reload) {
|
if (!reload) {
|
||||||
|
@ -589,7 +589,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass
|
||||||
EmulationSession::GetInstance().InitializeSystem(reload);
|
EmulationSession::GetInstance().InitializeSystem(reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
|
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jobject clazz) {
|
||||||
jdoubleArray j_stats = env->NewDoubleArray(4);
|
jdoubleArray j_stats = env->NewDoubleArray(4);
|
||||||
|
|
||||||
if (EmulationSession::GetInstance().IsRunning()) {
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
@ -605,7 +605,7 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jcl
|
||||||
return j_stats;
|
return j_stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass clazz) {
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jobject clazz) {
|
||||||
if (Settings::IsNceEnabled()) {
|
if (Settings::IsNceEnabled()) {
|
||||||
return Common::Android::ToJString(env, "NCE");
|
return Common::Android::ToJString(env, "NCE");
|
||||||
}
|
}
|
||||||
|
@ -641,18 +641,18 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstrin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jobject clazz) {
|
||||||
LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
|
LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
|
||||||
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardText(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardText(JNIEnv* env, jobject clazz,
|
||||||
jstring j_text) {
|
jstring j_text) {
|
||||||
const std::u16string input = Common::UTF8ToUTF16(Common::Android::GetJString(env, j_text));
|
const std::u16string input = Common::UTF8ToUTF16(Common::Android::GetJString(env, j_text));
|
||||||
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardText(input);
|
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardText(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env, jobject clazz,
|
||||||
jint j_key_code) {
|
jint j_key_code) {
|
||||||
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
|
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
|
||||||
}
|
}
|
||||||
|
@ -677,7 +677,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jobject clazz,
|
||||||
jlong jid) {
|
jlong jid) {
|
||||||
auto bis_system =
|
auto bis_system =
|
||||||
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
||||||
|
@ -694,18 +694,18 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, j
|
||||||
return Common::Android::ToJString(env, applet_nca->GetFullPath());
|
return Common::Android::ToJString(env, applet_nca->GetFullPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jobject clazz,
|
||||||
jint jappletId) {
|
jint jappletId) {
|
||||||
EmulationSession::GetInstance().SetAppletId(jappletId);
|
EmulationSession::GetInstance().SetAppletId(jappletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jobject clazz,
|
||||||
jint jcabinetMode) {
|
jint jcabinetMode) {
|
||||||
EmulationSession::GetInstance().System().GetFrontendAppletHolder().SetCabinetMode(
|
EmulationSession::GetInstance().System().GetFrontendAppletHolder().SetCabinetMode(
|
||||||
static_cast<Service::NFP::CabinetMode>(jcabinetMode));
|
static_cast<Service::NFP::CabinetMode>(jcabinetMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jobject clazz) {
|
||||||
auto bis_system =
|
auto bis_system =
|
||||||
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
||||||
if (!bis_system) {
|
if (!bis_system) {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "uisettings.h"
|
|
||||||
|
|
||||||
namespace AndroidSettings {
|
|
||||||
|
|
||||||
Values values;
|
|
||||||
|
|
||||||
} // namespace AndroidSettings
|
|
|
@ -1,29 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <common/settings_common.h>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "common/settings_setting.h"
|
|
||||||
|
|
||||||
namespace AndroidSettings {
|
|
||||||
|
|
||||||
struct Values {
|
|
||||||
Settings::Linkage linkage;
|
|
||||||
|
|
||||||
// Android
|
|
||||||
Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
|
|
||||||
Settings::Category::Android};
|
|
||||||
Settings::Setting<s32> screen_layout{linkage,
|
|
||||||
5,
|
|
||||||
"screen_layout",
|
|
||||||
Settings::Category::Android,
|
|
||||||
Settings::Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Values values;
|
|
||||||
|
|
||||||
} // namespace AndroidSettings
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="0"
|
|
||||||
android:toXDelta="-75" />
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="0"
|
|
||||||
android:toAlpha="1" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="-200"
|
|
||||||
android:toXDelta="0" />
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="0"
|
|
||||||
android:toXDelta="75" />
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="0"
|
|
||||||
android:toAlpha="1" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="200"
|
|
||||||
android:toXDelta="0" />
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="translationX"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="-1280dp"
|
|
||||||
android:valueTo="0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="300"/>
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="alpha"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="1"
|
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
|
||||||
android:duration="300"/>
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!-- This animation is used ONLY when a submenu is replaced. -->
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="translationX"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="-1280dp"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="200"/>
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="alpha"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="1"
|
|
||||||
android:valueTo="0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="200"/>
|
|
||||||
|
|
||||||
</set>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
style="?attr/materialCardViewOutlinedStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginVertical="12dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:padding="24dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/icon"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
app:tint="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_gravity="center_vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
tools:text="@string/applets" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/description"
|
|
||||||
style="@style/TextAppearance.Material3.BodyMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
tools:text="@string/applets_description" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<locale android:name="en" /> <!-- English (default) -->
|
|
||||||
<locale android:name="de" /> <!-- German -->
|
|
||||||
<locale android:name="es" /> <!-- Spanish -->
|
|
||||||
<locale android:name="fr" /> <!-- French -->
|
|
||||||
<locale android:name="it" /> <!-- Italian -->
|
|
||||||
<locale android:name="ja" /> <!-- Japanese -->
|
|
||||||
<locale android:name="nb" /> <!-- Norwegian Bokmal -->
|
|
||||||
<locale android:name="pl" /> <!-- Polish -->
|
|
||||||
<locale android:name="pt-rBR" /> <!-- Portuguese (Brazil) -->
|
|
||||||
<locale android:name="pt-RPT" /> <!-- Portuguese (Portugal) -->
|
|
||||||
<locale android:name="ru" /> <!-- Russian -->
|
|
||||||
<locale android:name="uk" /> <!-- Ukranian -->
|
|
||||||
<locale android:name="zh-rCN" /> <!-- Chinese (China) -->
|
|
||||||
<locale android:name="zh-rTW" /> <!-- Chinese (Taiwan) -->
|
|
||||||
</locale-config>
|
|
|
@ -3,8 +3,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.1.2" apply false
|
id("com.android.application") version "8.3.0" apply false
|
||||||
id("com.android.library") version "8.1.2" apply false
|
id("com.android.library") version "8.3.0" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
|
|
Loading…
Reference in New Issue