summaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/build.gradle.kts5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt73
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt172
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt118
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt59
-rw-r--r--src/android/app/src/main/jni/native.cpp21
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml55
-rw-r--r--src/android/app/src/main/res/layout/list_item_settings_header.xml28
-rw-r--r--src/android/app/src/main/res/values/arrays.xml11
-rw-r--r--src/android/app/src/main/res/values/strings.xml42
20 files changed, 531 insertions, 188 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 7ae538cf9..bab4f4d0f 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -163,13 +163,14 @@ android {
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
- version.set("0.47.0")
+ version.set("0.47.1")
android.set(true)
ignoreFailures.set(false)
disabledRules.set(
setOf(
"no-wildcard-imports",
- "package-name"
+ "package-name",
+ "import-ordering"
)
)
reporters {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index f0a6753a9..b1771b424 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -27,13 +27,13 @@ import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.fragment.NavHostFragment
-import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler
+import org.yuzu.yuzu_emu.utils.MemoryUtil
import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper
+import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding
@@ -102,6 +104,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
inputHandler = InputHandler()
inputHandler.initialize()
+ val memoryUtil = MemoryUtil(this)
+ if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.device_memory_inadequate,
+ memoryUtil.getDeviceRAM(),
+ "8 ${getString(R.string.memory_gigabyte)}"
+ ),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
// Start a foreground service to prevent the app from getting killed in the background
val startIntent = Intent(this, ForegroundService::class.java)
startForegroundService(startIntent)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 63b4df273..d41933766 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -8,6 +8,9 @@ enum class BooleanSetting(
override val section: String,
override val defaultValue: Boolean
) : AbstractBooleanSetting {
+ CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
+ FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
+ FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 63f95690c..6621289fd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -8,6 +8,7 @@ enum class StringSetting(
override val section: String,
override val defaultValue: String
) : AbstractStringSetting {
+ AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
override var string: String = defaultValue
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index 0f8edbfb0..a67001311 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -3,12 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-
class HeaderSetting(
- setting: AbstractSetting?,
- titleId: Int,
- descriptionId: Int
-) : SettingsItem(setting, titleId, descriptionId) {
+ titleId: Int
+) : SettingsItem(null, titleId, 0) {
override val type = TYPE_HEADER
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index bad34fd88..3b6731dcd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -7,20 +7,20 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class StringSingleChoiceSetting(
- val key: String? = null,
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
- val choicesId: Array<String>,
- private val valuesId: Array<String>?,
+ val choices: Array<String>,
+ val values: Array<String>?,
+ val key: String? = null,
private val defaultValue: String? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE
fun getValueAt(index: Int): String? {
- if (valuesId == null) return null
- return if (index >= 0 && index < valuesId.size) {
- valuesId[index]
+ if (values == null) return null
+ return if (index >= 0 && index < values.size) {
+ values[index]
} else {
""
}
@@ -36,8 +36,8 @@ class StringSingleChoiceSetting(
val selectValueIndex: Int
get() {
val selectedValue = selectedValue
- for (i in valuesId!!.indices) {
- if (valuesId[i] == selectedValue) {
+ for (i in values!!.indices) {
+ if (values[i] == selectedValue) {
return i
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index eac6a134b..ce0b92c90 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -138,7 +138,7 @@ class SettingsAdapter(
clickedItem = item
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
- .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this)
+ .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
.show()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index c8c85dd7a..59c1d9d54 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -42,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
fun putSetting(setting: AbstractSetting) {
- if (setting.section == null) {
+ if (setting.section == null || setting.key == null) {
return
}
@@ -353,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
- sl.add(
- SliderSetting(
- IntSetting.AUDIO_VOLUME,
- R.string.audio_volume,
- R.string.audio_volume_description,
- 0,
- 100,
- "%",
- IntSetting.AUDIO_VOLUME.key,
- IntSetting.AUDIO_VOLUME.defaultValue
- )
- )
+ sl.apply {
+ add(
+ StringSingleChoiceSetting(
+ StringSetting.AUDIO_OUTPUT_ENGINE,
+ R.string.audio_output_engine,
+ 0,
+ settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
+ settingsActivity.resources.getStringArray(R.array.outputEngineValues),
+ StringSetting.AUDIO_OUTPUT_ENGINE.key,
+ StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
+ )
+ )
+ add(
+ SliderSetting(
+ IntSetting.AUDIO_VOLUME,
+ R.string.audio_volume,
+ R.string.audio_volume_description,
+ 0,
+ 100,
+ "%",
+ IntSetting.AUDIO_VOLUME.key,
+ IntSetting.AUDIO_VOLUME.defaultValue
+ )
+ )
+ }
}
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
@@ -467,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
sl.apply {
+ add(HeaderSetting(R.string.gpu))
add(
SingleChoiceSetting(
IntSetting.RENDERER_BACKEND,
@@ -487,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.RENDERER_DEBUG.defaultValue
)
)
+
+ add(HeaderSetting(R.string.cpu))
+ add(
+ SwitchSetting(
+ BooleanSetting.CPU_DEBUG_MODE,
+ R.string.cpu_debug_mode,
+ R.string.cpu_debug_mode_description,
+ BooleanSetting.CPU_DEBUG_MODE.key,
+ BooleanSetting.CPU_DEBUG_MODE.defaultValue
+ )
+ )
+
+ val fastmem = object : AbstractBooleanSetting {
+ override var boolean: Boolean
+ get() =
+ BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
+ set(value) {
+ BooleanSetting.FASTMEM.boolean = value
+ BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
+ }
+ override val key: String? = null
+ override val section: String = Settings.SECTION_CPU
+ override val isRuntimeEditable: Boolean = false
+ override val valueAsString: String = ""
+ override val defaultValue: Any = true
+ }
+ add(
+ SwitchSetting(
+ fastmem,
+ R.string.fastmem,
+ 0
+ )
+ )
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index de764a27f..e4e321bd3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
for (i in values.indices) {
if (values[i] == item.selectedValue) {
binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
+ return
+ }
+ }
+ } else if (item is StringSingleChoiceSetting) {
+ for (i in item.values!!.indices) {
+ if (item.values[i] == item.selectedValue) {
+ binding.textSettingDescription.text = item.choices[i]
+ return
}
}
} else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 20a0636df..70a52df5d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -244,5 +244,21 @@ object SettingsFile {
val setting = settings[key]
parser.put(header, setting!!.key, setting.valueAsString)
}
+
+ BooleanSetting.values().forEach {
+ if (!keySet.contains(it.key)) {
+ parser.put(header, it.key, it.valueAsString)
+ }
+ }
+ IntSetting.values().forEach {
+ if (!keySet.contains(it.key)) {
+ parser.put(header, it.key, it.valueAsString)
+ }
+ }
+ StringSetting.values().forEach {
+ if (!keySet.contains(it.key)) {
+ parser.put(header, it.key, it.valueAsString)
+ }
+ }
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 6f8adbba5..5a36ffad4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -68,79 +68,109 @@ class HomeSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
- val optionsList: MutableList<HomeSetting> = mutableListOf(
- HomeSetting(
- R.string.advanced_settings,
- R.string.settings_description,
- R.drawable.ic_settings
- ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
- HomeSetting(
- R.string.open_user_folder,
- R.string.open_user_folder_description,
- R.drawable.ic_folder_open
- ) { openFileManager() },
- HomeSetting(
- R.string.preferences_theme,
- R.string.theme_and_color_description,
- R.drawable.ic_palette
- ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
- HomeSetting(
- R.string.install_gpu_driver,
- R.string.install_gpu_driver_description,
- R.drawable.ic_exit
- ) { driverInstaller() },
- HomeSetting(
- R.string.install_amiibo_keys,
- R.string.install_amiibo_keys_description,
- R.drawable.ic_nfc
- ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
- HomeSetting(
- R.string.install_game_content,
- R.string.install_game_content_description,
- R.drawable.ic_system_update_alt
- ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
- HomeSetting(
- R.string.select_games_folder,
- R.string.select_games_folder_description,
- R.drawable.ic_add
- ) {
- mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
- },
- HomeSetting(
- R.string.manage_save_data,
- R.string.import_export_saves_description,
- R.drawable.ic_save
- ) {
- ImportExportSavesFragment().show(
- parentFragmentManager,
- ImportExportSavesFragment.TAG
+ val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
+ add(
+ HomeSetting(
+ R.string.advanced_settings,
+ R.string.settings_description,
+ R.drawable.ic_settings
+ ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+ )
+ add(
+ HomeSetting(
+ R.string.open_user_folder,
+ R.string.open_user_folder_description,
+ R.drawable.ic_folder_open
+ ) { openFileManager() }
+ )
+ add(
+ HomeSetting(
+ R.string.preferences_theme,
+ R.string.theme_and_color_description,
+ R.drawable.ic_palette
+ ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+ )
+
+ if (GpuDriverHelper.supportsCustomDriverLoading()) {
+ add(
+ HomeSetting(
+ R.string.install_gpu_driver,
+ R.string.install_gpu_driver_description,
+ R.drawable.ic_exit
+ ) { driverInstaller() }
)
- },
- HomeSetting(
- R.string.install_prod_keys,
- R.string.install_prod_keys_description,
- R.drawable.ic_unlock
- ) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
- HomeSetting(
- R.string.install_firmware,
- R.string.install_firmware_description,
- R.drawable.ic_firmware
- ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
- HomeSetting(
- R.string.share_log,
- R.string.share_log_description,
- R.drawable.ic_log
- ) { shareLog() },
- HomeSetting(
- R.string.about,
- R.string.about_description,
- R.drawable.ic_info_outline
- ) {
- exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
- parentFragmentManager.primaryNavigationFragment?.findNavController()
- ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
}
- )
+
+ add(
+ HomeSetting(
+ R.string.install_amiibo_keys,
+ R.string.install_amiibo_keys_description,
+ R.drawable.ic_nfc
+ ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
+ )
+ add(
+ HomeSetting(
+ R.string.install_game_content,
+ R.string.install_game_content_description,
+ R.drawable.ic_system_update_alt
+ ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
+ )
+ add(
+ HomeSetting(
+ R.string.select_games_folder,
+ R.string.select_games_folder_description,
+ R.drawable.ic_add
+ ) {
+ mainActivity.getGamesDirectory.launch(
+ Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
+ )
+ }
+ )
+ add(
+ HomeSetting(
+ R.string.manage_save_data,
+ R.string.import_export_saves_description,
+ R.drawable.ic_save
+ ) {
+ ImportExportSavesFragment().show(
+ parentFragmentManager,
+ ImportExportSavesFragment.TAG
+ )
+ }
+ )
+ add(
+ HomeSetting(
+ R.string.install_prod_keys,
+ R.string.install_prod_keys_description,
+ R.drawable.ic_unlock
+ ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
+ )
+ add(
+ HomeSetting(
+ R.string.install_firmware,
+ R.string.install_firmware_description,
+ R.drawable.ic_firmware
+ ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
+ )
+ add(
+ HomeSetting(
+ R.string.share_log,
+ R.string.share_log_description,
+ R.drawable.ic_log
+ ) { shareLog() }
+ )
+ add(
+ HomeSetting(
+ R.string.about,
+ R.string.about_description,
+ R.drawable.ic_info_outline
+ ) {
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ parentFragmentManager.primaryNavigationFragment?.findNavController()
+ ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
+ }
+ )
+ }
if (!BuildConfig.PREMIUM) {
optionsList.add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
new file mode 100644
index 000000000..b29b627e9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
@@ -0,0 +1,62 @@
+// 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
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index cc1d87f1b..3086cfad3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.ui.main
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
@@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
+import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- val installGameUpdate =
- registerForActivityResult(ActivityResultContracts.OpenDocument()) {
- if (it == null) {
- return@registerForActivityResult
- }
-
+ val installGameUpdate = registerForActivityResult(
+ ActivityResultContracts.OpenMultipleDocuments()
+ ) { documents: List<Uri> ->
+ if (documents.isNotEmpty()) {
IndeterminateProgressDialogFragment.newInstance(
this@MainActivity,
R.string.install_game_content
) {
- val result = NativeLibrary.installFileToNand(it.toString())
+ var installSuccess = 0
+ var installOverwrite = 0
+ var errorBaseGame = 0
+ var errorExtension = 0
+ var errorOther = 0
+ var errorTotal = 0
lifecycleScope.launch {
- withContext(Dispatchers.Main) {
- when (result) {
+ documents.forEach {
+ when (NativeLibrary.installFileToNand(it.toString())) {
NativeLibrary.InstallFileToNandResult.Success -> {
- Toast.makeText(
- applicationContext,
- R.string.install_game_content_success,
- Toast.LENGTH_SHORT
- ).show()
+ installSuccess += 1
}
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
- Toast.makeText(
- applicationContext,
- R.string.install_game_content_success_overwrite,
- Toast.LENGTH_SHORT
- ).show()
+ installOverwrite += 1
}
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_base
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorBaseGame += 1
}
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_file_extension,
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorExtension += 1
}
else -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_description,
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorOther += 1
}
}
}
+ withContext(Dispatchers.Main) {
+ val separator = System.getProperty("line.separator") ?: "\n"
+ val installResult = StringBuilder()
+ if (installSuccess > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_install,
+ installSuccess
+ )
+ )
+ installResult.append(separator)
+ }
+ if (installOverwrite > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_overwrite,
+ installOverwrite
+ )
+ )
+ installResult.append(separator)
+ }
+ errorTotal = errorBaseGame + errorExtension + errorOther
+ if (errorTotal > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(
+ R.string.install_game_content_failed_count,
+ errorTotal
+ )
+ )
+ installResult.append(separator)
+ if (errorBaseGame > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_base)
+ )
+ installResult.append(separator)
+ }
+ if (errorExtension > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_file_extension)
+ )
+ installResult.append(separator)
+ }
+ if (errorOther > 0) {
+ installResult.append(
+ getString(R.string.install_game_content_failure_description)
+ )
+ installResult.append(separator)
+ }
+ LongMessageDialogFragment.newInstance(
+ R.string.install_game_content_failure,
+ installResult.toString().trim(),
+ R.string.install_game_content_help_link
+ ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+ } else {
+ LongMessageDialogFragment.newInstance(
+ R.string.install_game_content_success,
+ installResult.toString().trim()
+ ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+ }
+ }
}
- return@newInstance result
+ return@newInstance installSuccess + installOverwrite + errorTotal
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index dad159481..1d4695a2a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -113,6 +113,8 @@ object GpuDriverHelper {
initializeDriverParameters(context)
}
+ external fun supportsCustomDriverLoading(): Boolean
+
// Parse the custom driver metadata to retrieve the name.
val customDriverName: String?
get() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
new file mode 100644
index 000000000..18e5fa0b0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.app.ActivityManager
+import android.content.Context
+import org.yuzu.yuzu_emu.R
+import java.util.Locale
+
+class MemoryUtil(val context: Context) {
+
+ private val Long.floatForm: String
+ get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
+
+ private fun bytesToSizeUnit(size: Long): String {
+ return when {
+ size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
+ size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
+ size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
+ size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
+ size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
+ size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
+ else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
+ }
+ }
+
+ private val totalMemory =
+ with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
+ val memInfo = ActivityManager.MemoryInfo()
+ getMemoryInfo(memInfo)
+ memInfo.totalMem
+ }
+
+ fun isLessThan(minimum: Int, size: Long): Boolean {
+ return when (size) {
+ Kb -> totalMemory < Mb && totalMemory < minimum
+ Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
+ Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
+ Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
+ Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
+ Eb -> totalMemory / Eb < minimum
+ else -> totalMemory < Kb && totalMemory < minimum
+ }
+ }
+
+ fun getDeviceRAM(): String {
+ return bytesToSizeUnit(totalMemory)
+ }
+
+ companion object {
+ const val Kb: Long = 1024
+ const val Mb = Kb * 1024
+ const val Gb = Mb * 1024
+ const val Tb = Gb * 1024
+ const val Pb = Tb * 1024
+ const val Eb = Pb * 1024
+ }
+}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index f9617202b..f4fed0886 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -237,6 +237,7 @@ public:
m_software_keyboard = android_keyboard.get();
m_system.SetShuttingDown(false);
m_system.ApplySettings();
+ Settings::LogSettings();
m_system.HIDCore().ReloadInputDevices();
m_system.SetAppletFrontendSet({
nullptr, // Amiibo Settings
@@ -560,6 +561,26 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
}
+[[maybe_unused]] static bool CheckKgslPresent() {
+ constexpr auto KgslPath{"/dev/kgsl-3d0"};
+
+ return access(KgslPath, F_OK) == 0;
+}
+
+[[maybe_unused]] bool SupportsCustomDriver() {
+ return android_get_device_api_level() >= 28 && CheckKgslPresent();
+}
+
+jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
+ [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject instance) {
+#ifdef ARCHITECTURE_arm64
+ // If the KGSL device exists custom drivers can be loaded using adrenotools
+ return SupportsCustomDriver();
+#else
+ return false;
+#endif
+}
+
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
[[maybe_unused]] jclass clazz) {
Core::Crypto::KeyManager::Instance().ReloadKeys();
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
index 599d845ad..a5767adee 100644
--- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:minHeight="72dp"
+ android:paddingVertical="@dimen/spacing_large"
android:paddingStart="@dimen/spacing_large"
- android:paddingEnd="24dp"
- android:paddingVertical="@dimen/spacing_large">
+ android:paddingEnd="24dp">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_widget"
@@ -19,32 +19,35 @@
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
- <com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.BodySmall"
- android:id="@+id/text_setting_description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_alignStart="@+id/text_setting_name"
- android:layout_below="@+id/text_setting_name"
- android:layout_marginEnd="@dimen/spacing_large"
- android:layout_marginTop="@dimen/spacing_small"
- android:layout_toStartOf="@+id/switch_widget"
- android:textAlignment="viewStart"
- tools:text="@string/frame_limit_enable_description" />
-
- <com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.HeadlineMedium"
- android:id="@+id/text_setting_name"
- android:layout_width="0dp"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
+ android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/spacing_large"
android:layout_toStartOf="@+id/switch_widget"
- android:textSize="16sp"
- android:textAlignment="viewStart"
- app:lineHeight="28dp"
- tools:text="@string/frame_limit_enable" />
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="16sp"
+ app:lineHeight="28dp"
+ tools:text="@string/frame_limit_enable" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_description"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ tools:text="@string/frame_limit_enable_description" />
+
+ </LinearLayout>
</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml
index abd24df6f..cf85bc0da 100644
--- a/src/android/app/src/main/res/layout/list_item_settings_header.xml
+++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml
@@ -1,20 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/text_header_name"
+ style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="match_parent"
- android:layout_height="48dp"
- android:paddingVertical="4dp"
- android:paddingHorizontal="@dimen/spacing_large">
-
- <com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.TitleSmall"
- android:id="@+id/text_header_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="start|center_vertical"
- android:textColor="?attr/colorPrimary"
- android:textAlignment="viewStart"
- android:textStyle="bold"
- tools:text="CPU Settings" />
-
-</FrameLayout>
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:paddingHorizontal="@dimen/spacing_large"
+ android:paddingVertical="16dp"
+ android:textAlignment="viewStart"
+ android:textColor="?attr/colorPrimary"
+ android:textStyle="bold"
+ tools:text="CPU Settings" />
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 7f7b1938c..6d092f7a9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -236,4 +236,15 @@
<item>2</item>
</integer-array>
+ <string-array name="outputEngineEntries">
+ <item>@string/auto</item>
+ <item>@string/cubeb</item>
+ <item>@string/string_null</item>
+ </string-array>
+ <string-array name="outputEngineValues">
+ <item>auto</item>
+ <item>cubeb</item>
+ <item>null</item>
+ </string-array>
+
</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 2f2059d42..21805d274 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -104,12 +104,14 @@
<string name="share_log_missing">No log file found</string>
<string name="install_game_content">Install game content</string>
<string name="install_game_content_description">Install game updates or DLC</string>
- <string name="install_game_content_failure">Error installing file to NAND</string>
- <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
- <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
- <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
- <string name="install_game_content_success">Game content installed successfully</string>
- <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
+ <string name="install_game_content_failure">Error installing file(s) to NAND</string>
+ <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
+ <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
+ <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
+ <string name="install_game_content_failed_count">%1$d installation error(s)</string>
+ <string name="install_game_content_success">Game content(s) installed successfully</string>
+ <string name="install_game_content_success_install">%1$d installed successfully</string>
+ <string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
@@ -158,7 +160,6 @@
<string name="set_custom_rtc">Set custom RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Accuracy level</string>
<string name="renderer_resolution">Resolution (Handheld/Docked)</string>
<string name="renderer_vsync">VSync mode</string>
@@ -172,12 +173,21 @@
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string>
<string name="renderer_reactive_flushing">Use reactive flushing</string>
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
- <string name="renderer_debug">Graphics debugging</string>
- <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
<string name="use_disk_shader_cache">Disk shader cache</string>
<string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU Debugging</string>
+ <string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Graphics debugging</string>
+ <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
+ <string name="fastmem">Fastmem</string>
+
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Output engine</string>
<string name="audio_volume">Volume</string>
<string name="audio_volume_description">Specifies the volume of audio output.</string>
@@ -196,6 +206,7 @@
<string name="learn_more">Learn more</string>
<string name="auto">Auto</string>
<string name="submit">Submit</string>
+ <string name="string_null">Null</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -261,6 +272,7 @@
<string name="fatal_error">Fatal Error</string>
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
+ <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
<!-- Region Names -->
<string name="region_japan">Japan</string>
@@ -291,6 +303,15 @@
<string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
<string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">None</string>
@@ -366,6 +387,9 @@
<string name="theme_mode_light">Light</string>
<string name="theme_mode_dark">Dark</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Black backgrounds</string>
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>