android: Expose interface for getting settings from native code

Completely removes code related to parsing the settings file on the java side. Now all settings are accessed via NativeConfig.kt and config.cpp has been modified to be closer to the core counterpart. Since the core currently uses QSettings, we can't remove reliance from Wini yet. This also includes simplifications to each settings interface to get closer to native code and prepare for per-game settings.
This commit is contained in:
Charles Lombardo 2023-08-16 02:36:56 -04:00
parent 3d5ecc1f08
commit 6c8f2b355a
49 changed files with 866 additions and 966 deletions

View file

@ -219,10 +219,6 @@ object NativeLibrary {
external fun reloadSettings()
external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
external fun initGameIni(gameID: String?)
/**
@ -413,14 +409,17 @@ object NativeLibrary {
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
)
}
CoreError.ErrorSavestate -> {
title = emulationActivity.getString(R.string.save_load_error)
message = details
}
CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
}
else -> {
return true
}
@ -454,6 +453,7 @@ object NativeLibrary {
captionId = R.string.loader_error_video_core
descriptionId = R.string.loader_error_video_core_description
}
else -> {
captionId = R.string.loader_error_encrypted
descriptionId = R.string.loader_error_encrypted_roms_description

View file

@ -28,7 +28,6 @@ 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
@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
private val settingsViewModel: SettingsViewModel by viewModels()
override fun onDestroy() {
stopForegroundService(this)
super.onDestroy()
@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
settingsViewModel.settings.loadSettings()
super.onCreate(savedInstanceState)
binding = ActivityEmulationBinding.inflate(layoutInflater)

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting {
var boolean: Boolean
val boolean: Boolean
fun setBoolean(value: Boolean)
}

View file

@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.model
import androidx.lifecycle.ViewModel
interface AbstractByteSetting : AbstractSetting {
val byte: Byte
class SettingsViewModel : ViewModel() {
val settings = Settings()
fun setByte(value: Byte)
}

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting {
var float: Float
val float: Float
fun setFloat(value: Float)
}

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting {
var int: Int
val int: Int
fun setInt(value: Int)
}

View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractLongSetting : AbstractSetting {
val long: Long
fun setLong(value: Long)
}

View file

@ -3,10 +3,17 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
interface AbstractSetting {
val key: String?
val section: String?
val isRuntimeEditable: Boolean
val valueAsString: String
val category: Settings.Category
val defaultValue: Any
val valueAsString: String
get() = ""
val isRuntimeModifiable: Boolean
get() = NativeConfig.getIsRuntimeModifiable(key!!)
fun reset() = run { }
}

View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractShortSetting : AbstractSetting {
val short: Short
fun setShort(value: Short)
}

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting {
var string: String
val string: String
fun setString(value: String)
}

View file

@ -3,41 +3,34 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class BooleanSetting(
override val key: String,
override val section: String,
override val defaultValue: Boolean
override val category: Settings.Category
) : 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);
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
USE_DOCKED_MODE("use_docked_mode", Settings.Category.System),
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer),
RENDERER_DEBUG("debug", Settings.Category.Renderer),
PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
override var boolean: Boolean = defaultValue
override val boolean: Boolean
get() = NativeConfig.getBoolean(key, false)
override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
override val defaultValue: Boolean by lazy { NativeConfig.getBoolean(key, true) }
override val valueAsString: String
get() = boolean.toString()
get() = if (boolean) "1" else "0"
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
PICTURE_IN_PICTURE,
USE_CUSTOM_RTC
)
fun from(key: String): BooleanSetting? =
BooleanSetting.values().firstOrNull { it.key == key }
fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
}
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ByteSetting(
override val key: String,
override val category: Settings.Category
) : AbstractByteSetting {
AUDIO_VOLUME("volume", Settings.Category.Audio);
override val byte: Byte
get() = NativeConfig.getByte(key, false)
override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
override val valueAsString: String
get() = byte.toString()
override fun reset() = NativeConfig.setByte(key, defaultValue)
}

View file

@ -3,34 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class FloatSetting(
override val key: String,
override val section: String,
override val defaultValue: Float
override val category: Settings.Category
) : AbstractFloatSetting {
// No float settings currently exist
EMPTY_SETTING("", "", 0f);
EMPTY_SETTING("", Settings.Category.UiGeneral);
override var float: Float = defaultValue
override val float: Float
get() = NativeConfig.getFloat(key, false)
override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
override val valueAsString: String
get() = float.toString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
}
override fun reset() = NativeConfig.setFloat(key, defaultValue)
}

View file

@ -3,139 +3,34 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class IntSetting(
override val key: String,
override val section: String,
override val defaultValue: Int
override val category: Settings.Category
) : AbstractIntSetting {
RENDERER_USE_SPEED_LIMIT(
"use_speed_limit",
Settings.SECTION_RENDERER,
1
),
USE_DOCKED_MODE(
"use_docked_mode",
Settings.SECTION_SYSTEM,
0
),
RENDERER_USE_DISK_SHADER_CACHE(
"use_disk_shader_cache",
Settings.SECTION_RENDERER,
1
),
RENDERER_FORCE_MAX_CLOCK(
"force_max_clock",
Settings.SECTION_RENDERER,
0
),
RENDERER_ASYNCHRONOUS_SHADERS(
"use_asynchronous_shaders",
Settings.SECTION_RENDERER,
0
),
RENDERER_REACTIVE_FLUSHING(
"use_reactive_flushing",
Settings.SECTION_RENDERER,
0
),
RENDERER_DEBUG(
"debug",
Settings.SECTION_RENDERER,
0
),
RENDERER_SPEED_LIMIT(
"speed_limit",
Settings.SECTION_RENDERER,
100
),
CPU_ACCURACY(
"cpu_accuracy",
Settings.SECTION_CPU,
0
),
REGION_INDEX(
"region_index",
Settings.SECTION_SYSTEM,
-1
),
LANGUAGE_INDEX(
"language_index",
Settings.SECTION_SYSTEM,
1
),
RENDERER_BACKEND(
"backend",
Settings.SECTION_RENDERER,
1
),
RENDERER_ACCURACY(
"gpu_accuracy",
Settings.SECTION_RENDERER,
0
),
RENDERER_RESOLUTION(
"resolution_setup",
Settings.SECTION_RENDERER,
2
),
RENDERER_VSYNC(
"use_vsync",
Settings.SECTION_RENDERER,
0
),
RENDERER_SCALING_FILTER(
"scaling_filter",
Settings.SECTION_RENDERER,
1
),
RENDERER_ANTI_ALIASING(
"anti_aliasing",
Settings.SECTION_RENDERER,
0
),
RENDERER_SCREEN_LAYOUT(
"screen_layout",
Settings.SECTION_RENDERER,
Settings.LayoutOption_MobileLandscape
),
RENDERER_ASPECT_RATIO(
"aspect_ratio",
Settings.SECTION_RENDERER,
0
),
AUDIO_VOLUME(
"volume",
Settings.SECTION_AUDIO,
100
);
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
REGION_INDEX("region_index", Settings.Category.System),
LANGUAGE_INDEX("language_index", Settings.Category.System),
RENDERER_BACKEND("backend", Settings.Category.Renderer),
RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer),
RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
override var int: Int = defaultValue
override val int: Int
get() = NativeConfig.getInt(key, false)
override fun setInt(value: Int) = NativeConfig.setInt(key, value)
override val defaultValue: Int by lazy { NativeConfig.getInt(key, true) }
override val valueAsString: String
get() = int.toString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
RENDERER_USE_DISK_SHADER_CACHE,
RENDERER_ASYNCHRONOUS_SHADERS,
RENDERER_DEBUG,
RENDERER_BACKEND,
RENDERER_RESOLUTION,
RENDERER_VSYNC
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
}
override fun reset() = NativeConfig.setInt(key, defaultValue)
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class LongSetting(
override val key: String,
override val category: Settings.Category
) : AbstractLongSetting {
CUSTOM_RTC("custom_rtc", Settings.Category.System);
override val long: Long
get() = NativeConfig.getLong(key, false)
override fun setLong(value: Long) = NativeConfig.setLong(key, value)
override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
override val valueAsString: String
get() = long.toString()
override fun reset() = NativeConfig.setLong(key, defaultValue)
}

View file

@ -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)
}
}
}

View file

@ -4,195 +4,151 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
import java.util.*
import android.widget.Toast
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
class Settings {
private var gameId: String? = null
object Settings {
private val context get() = YuzuApplication.appContext
var isLoaded = false
/**
* A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
* when getting a key not already in the map
*/
class SettingsSectionMap : HashMap<String, SettingSection?>() {
override operator fun get(key: String): SettingSection? {
if (!super.containsKey(key)) {
val section = SettingSection(key)
super.put(key, section)
return section
}
return super.get(key)
}
}
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
fun getSection(sectionName: String): SettingSection? {
return sections[sectionName]
}
val isEmpty: Boolean
get() = sections.isEmpty()
fun loadSettings(view: SettingsActivityView? = null) {
sections = SettingsSectionMap()
loadYuzuSettings(view)
if (!TextUtils.isEmpty(gameId)) {
loadCustomGameSettings(gameId!!, view)
}
isLoaded = true
}
private fun loadYuzuSettings(view: SettingsActivityView?) {
for ((fileName) in configFileSectionsMap) {
sections.putAll(SettingsFile.readFile(fileName, view))
}
}
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
// Custom game settings
mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
}
private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
for ((key, updatedSection) in updatedSections) {
if (sections.containsKey(key)) {
val originalSection = sections[key]
originalSection!!.mergeSection(updatedSection!!)
} else {
sections[key] = updatedSection
}
}
}
fun loadSettings(gameId: String, view: SettingsActivityView) {
this.gameId = gameId
loadSettings(view)
}
fun saveSettings(view: SettingsActivityView) {
fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
view.showToastMessage(
YuzuApplication.appContext.getString(R.string.ini_saved),
false
)
for ((fileName, sectionNames) in configFileSectionsMap) {
val iniSections = TreeMap<String, SettingSection>()
for (section in sectionNames) {
iniSections[section] = sections[section]!!
}
SettingsFile.saveFile(fileName, iniSections, view)
}
Toast.makeText(
context,
context.getString(R.string.ini_saved),
Toast.LENGTH_SHORT
).show()
SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
} else {
// Custom game settings
view.showToastMessage(
YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
false
)
SettingsFile.saveCustomGameSettings(gameId, sections)
// TODO: Save custom game settings
Toast.makeText(
context,
context.getString(R.string.gameid_saved, gameId),
Toast.LENGTH_SHORT
).show()
}
}
companion object {
const val SECTION_GENERAL = "General"
const val SECTION_SYSTEM = "System"
const val SECTION_RENDERER = "Renderer"
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion"
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
val overlayLayoutPrefs = listOf(
PREF_LANDSCAPE_OVERLAY_VERSION,
PREF_PORTRAIT_OVERLAY_VERSION,
PREF_FOLDABLE_OVERLAY_VERSION
)
const val PREF_CONTROL_SCALE = "controlScale"
const val PREF_CONTROL_OPACITY = "controlOpacity"
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
const val PREF_BUTTON_A = "buttonToggle0"
const val PREF_BUTTON_B = "buttonToggle1"
const val PREF_BUTTON_X = "buttonToggle2"
const val PREF_BUTTON_Y = "buttonToggle3"
const val PREF_BUTTON_L = "buttonToggle4"
const val PREF_BUTTON_R = "buttonToggle5"
const val PREF_BUTTON_ZL = "buttonToggle6"
const val PREF_BUTTON_ZR = "buttonToggle7"
const val PREF_BUTTON_PLUS = "buttonToggle8"
const val PREF_BUTTON_MINUS = "buttonToggle9"
const val PREF_BUTTON_DPAD = "buttonToggle10"
const val PREF_STICK_L = "buttonToggle11"
const val PREF_STICK_R = "buttonToggle12"
const val PREF_BUTTON_STICK_L = "buttonToggle13"
const val PREF_BUTTON_STICK_R = "buttonToggle14"
const val PREF_BUTTON_HOME = "buttonToggle15"
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_THEME = "Theme"
const val PREF_THEME_MODE = "ThemeMode"
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
val overlayPreferences = listOf(
PREF_OVERLAY_VERSION,
PREF_CONTROL_SCALE,
PREF_CONTROL_OPACITY,
PREF_TOUCH_ENABLED,
PREF_BUTTON_A,
PREF_BUTTON_B,
PREF_BUTTON_X,
PREF_BUTTON_Y,
PREF_BUTTON_L,
PREF_BUTTON_R,
PREF_BUTTON_ZL,
PREF_BUTTON_ZR,
PREF_BUTTON_PLUS,
PREF_BUTTON_MINUS,
PREF_BUTTON_DPAD,
PREF_STICK_L,
PREF_STICK_R,
PREF_BUTTON_HOME,
PREF_BUTTON_SCREENSHOT,
PREF_BUTTON_STICK_L,
PREF_BUTTON_STICK_R
)
const val LayoutOption_Unspecified = 0
const val LayoutOption_MobilePortrait = 4
const val LayoutOption_MobileLandscape = 5
init {
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
listOf(
SECTION_GENERAL,
SECTION_SYSTEM,
SECTION_RENDERER,
SECTION_AUDIO,
SECTION_CPU
)
}
enum class Category {
Android,
Audio,
Core,
Cpu,
CpuDebug,
CpuUnsafe,
Renderer,
RendererAdvanced,
RendererDebug,
System,
SystemAudio,
DataStorage,
Debugging,
DebuggingGraphics,
Miscellaneous,
Network,
WebService,
AddOns,
Controls,
Ui,
UiGeneral,
UiLayout,
UiGameList,
Screenshots,
Shortcuts,
Multiplayer,
Services,
Paths,
MaxEnum
}
val settingsList = listOf<AbstractSetting>(
*BooleanSetting.values(),
*ByteSetting.values(),
*ShortSetting.values(),
*IntSetting.values(),
*FloatSetting.values(),
*LongSetting.values(),
*StringSetting.values()
)
const val SECTION_GENERAL = "General"
const val SECTION_SYSTEM = "System"
const val SECTION_RENDERER = "Renderer"
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion"
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
val overlayLayoutPrefs = listOf(
PREF_LANDSCAPE_OVERLAY_VERSION,
PREF_PORTRAIT_OVERLAY_VERSION,
PREF_FOLDABLE_OVERLAY_VERSION
)
const val PREF_CONTROL_SCALE = "controlScale"
const val PREF_CONTROL_OPACITY = "controlOpacity"
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
const val PREF_BUTTON_A = "buttonToggle0"
const val PREF_BUTTON_B = "buttonToggle1"
const val PREF_BUTTON_X = "buttonToggle2"
const val PREF_BUTTON_Y = "buttonToggle3"
const val PREF_BUTTON_L = "buttonToggle4"
const val PREF_BUTTON_R = "buttonToggle5"
const val PREF_BUTTON_ZL = "buttonToggle6"
const val PREF_BUTTON_ZR = "buttonToggle7"
const val PREF_BUTTON_PLUS = "buttonToggle8"
const val PREF_BUTTON_MINUS = "buttonToggle9"
const val PREF_BUTTON_DPAD = "buttonToggle10"
const val PREF_STICK_L = "buttonToggle11"
const val PREF_STICK_R = "buttonToggle12"
const val PREF_BUTTON_STICK_L = "buttonToggle13"
const val PREF_BUTTON_STICK_R = "buttonToggle14"
const val PREF_BUTTON_HOME = "buttonToggle15"
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_THEME = "Theme"
const val PREF_THEME_MODE = "ThemeMode"
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
val overlayPreferences = listOf(
PREF_OVERLAY_VERSION,
PREF_CONTROL_SCALE,
PREF_CONTROL_OPACITY,
PREF_TOUCH_ENABLED,
PREF_BUTTON_A,
PREF_BUTTON_B,
PREF_BUTTON_X,
PREF_BUTTON_Y,
PREF_BUTTON_L,
PREF_BUTTON_R,
PREF_BUTTON_ZL,
PREF_BUTTON_ZR,
PREF_BUTTON_PLUS,
PREF_BUTTON_MINUS,
PREF_BUTTON_DPAD,
PREF_STICK_L,
PREF_STICK_R,
PREF_BUTTON_HOME,
PREF_BUTTON_SCREENSHOT,
PREF_BUTTON_STICK_L,
PREF_BUTTON_STICK_R
)
const val LayoutOption_Unspecified = 0
const val LayoutOption_MobilePortrait = 4
const val LayoutOption_MobileLandscape = 5
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(
override val key: String,
override val category: Settings.Category
) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
override val short: Short
get() = NativeConfig.getShort(key, false)
override fun setShort(value: Short) = NativeConfig.setShort(key, value)
override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
override val valueAsString: String
get() = short.toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}

View file

@ -3,36 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(
override val key: String,
override val section: String,
override val defaultValue: String
override val category: Settings.Category
) : AbstractStringSetting {
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
// No string settings currently exist
EMPTY_SETTING("", Settings.Category.UiGeneral);
override var string: String = defaultValue
override val string: String
get() = NativeConfig.getString(key, false)
override fun setString(value: String) = NativeConfig.setString(key, value)
override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
override val valueAsString: String
get() = string
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
CUSTOM_RTC
)
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
}
override fun reset() = NativeConfig.setString(key, defaultValue)
}

View file

@ -3,29 +3,29 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class DateTimeSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val key: String? = null,
private val defaultValue: String? = null
private val defaultValue: Long? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING
val value: String
val value: Long
get() = if (setting != null) {
val setting = setting as AbstractStringSetting
setting.string
val setting = setting as AbstractLongSetting
setting.long
} else {
defaultValue!!
}
fun setSelectedValue(datetime: String): AbstractStringSetting {
val stringSetting = setting as AbstractStringSetting
stringSetting.string = datetime
return stringSetting
fun setSelectedValue(datetime: Long): AbstractLongSetting {
val longSetting = setting as AbstractLongSetting
longSetting.setLong(datetime)
return longSetting
}
}

View file

@ -23,7 +23,7 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
if (!NativeLibrary.isRunning()) return true
return setting?.isRuntimeEditable ?: false
return setting?.isRuntimeModifiable ?: false
}
companion object {

View file

@ -33,7 +33,7 @@ class SingleChoiceSetting(
*/
fun setSelectedValue(selection: Int): AbstractIntSetting {
val intSetting = setting as AbstractIntSetting
intSetting.int = selection
intSetting.setInt(selection)
return intSetting
}
}

View file

@ -3,10 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
import org.yuzu.yuzu_emu.utils.Log
class SliderSetting(
@ -17,14 +19,16 @@ class SliderSetting(
val max: Int,
val units: String,
val key: String? = null,
val defaultValue: Int? = null
val defaultValue: Any? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
val selectedValue: Int
val selectedValue: Any
get() {
val setting = setting ?: return defaultValue!!
return when (setting) {
is AbstractByteSetting -> setting.byte.toInt()
is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt()
else -> {
@ -43,7 +47,7 @@ class SliderSetting(
*/
fun setSelectedValue(selection: Int): AbstractIntSetting {
val intSetting = setting as AbstractIntSetting
intSetting.int = selection
intSetting.setInt(selection)
return intSetting
}
@ -56,7 +60,19 @@ class SliderSetting(
*/
fun setSelectedValue(selection: Float): AbstractFloatSetting {
val floatSetting = setting as AbstractFloatSetting
floatSetting.float = selection
floatSetting.setFloat(selection)
return floatSetting
}
fun setSelectedValue(selection: Short): AbstractShortSetting {
val shortSetting = setting as AbstractShortSetting
shortSetting.setShort(selection)
return shortSetting
}
fun setSelectedValue(selection: Byte): AbstractByteSetting {
val byteSetting = setting as AbstractByteSetting
byteSetting.setByte(selection)
return byteSetting
}
}

View file

@ -53,7 +53,7 @@ class StringSingleChoiceSetting(
*/
fun setSelectedValue(selection: String): AbstractStringSetting {
val stringSetting = setting as AbstractStringSetting
stringSetting.string = selection
stringSetting.setString(selection)
return stringSetting
}
}

View file

@ -49,14 +49,14 @@ class SwitchSetting(
// Try integer setting
try {
val setting = setting as AbstractIntSetting
setting.int = if (checked) 1 else 0
setting.setInt(if (checked) 1 else 0)
return setting
} catch (_: ClassCastException) {
}
// Try boolean setting
val setting = setting as AbstractBooleanSetting
setting.boolean = checked
setting.setBoolean(checked)
return setting
}
}

View file

@ -21,12 +21,7 @@ import com.google.android.material.color.MaterialColors
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.*
@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private lateinit var binding: ActivitySettingsBinding
private val settingsViewModel: SettingsViewModel by viewModels()
override val settings: Settings get() = settingsViewModel.settings
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
fragment?.loadSettingsList()
}
override fun showToastMessage(message: String, is_long: Boolean) {
Toast.makeText(
this,
message,
if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
).show()
}
override fun onSettingChanged() {
presenter.onSettingChanged()
}
@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
// Prevents saving to a non-existent settings file
presenter.onSettingsReset()
// Reset the static memory representation of each setting
BooleanSetting.clear()
FloatSetting.clear()
IntSetting.clear()
StringSetting.clear()
// Delete settings file because the user may have changed values that do not exist in the UI
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
Settings.settingsList.forEach { it.reset() }
showToastMessage(getString(R.string.settings_reset), true)
Toast.makeText(
applicationContext,
getString(R.string.settings_reset),
Toast.LENGTH_LONG
).show()
finish()
}

View file

@ -5,7 +5,6 @@ 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
@ -14,8 +13,6 @@ 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
@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
}
private fun loadSettingsUI() {
if (!settings.isLoaded) {
if (!TextUtils.isEmpty(gameId)) {
settings.loadSettings(gameId, activityView)
} else {
settings.loadSettings(activityView)
}
}
// TODO: Load custom settings contextually
activityView.showSettingsFragment(menuTag, false, gameId)
activityView.onSettingsFileLoaded()
}
@ -67,9 +58,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
fun onStop(finishing: Boolean) {
if (finishing && shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView)
Settings.saveSettings()
NativeLibrary.reloadSettings()
}
NativeLibrary.reloadSettings()
}
fun onSettingChanged() {

View file

@ -3,8 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui
import org.yuzu.yuzu_emu.features.settings.model.Settings
/**
* Abstraction for the Activity that manages SettingsFragments.
*/
@ -17,15 +15,6 @@ interface SettingsActivityView {
*/
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.
*/
@ -36,14 +25,6 @@ interface SettingsActivityView {
*/
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.
*/

View file

@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
@ -115,8 +113,7 @@ class SettingsAdapter(
}
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
val setting = item.setChecked(checked)
fragmentView.putSetting(setting)
item.setChecked(checked)
fragmentView.onSettingChanged()
}
@ -150,7 +147,7 @@ class SettingsAdapter(
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
clickedItem = item
clickedPosition = position
val storedTime = java.lang.Long.decode(item.value) * 1000
val storedTime = item.value * 1000
// Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance()
@ -183,13 +180,11 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
val rtcString = epochTime.toString()
if (item.value != rtcString) {
if (item.value != epochTime) {
fragmentView.onSettingChanged()
notifyItemChanged(clickedPosition)
item.setSelectedValue(epochTime)
}
notifyItemChanged(clickedPosition)
val setting = item.setSelectedValue(rtcString)
fragmentView.putSetting(setting)
clickedItem = null
}
datePicker.show(
@ -201,7 +196,7 @@ class SettingsAdapter(
fun onSliderClick(item: SliderSetting, position: Int) {
clickedItem = item
clickedPosition = position
sliderProgress = item.selectedValue
sliderProgress = item.selectedValue as Int
val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater)
@ -249,8 +244,7 @@ class SettingsAdapter(
}
// Get the backing Setting, which may be null (if for example it was missing from the file)
val setting = scSetting.setSelectedValue(value)
fragmentView.putSetting(setting)
scSetting.setSelectedValue(value)
closeDialog()
}
@ -258,8 +252,7 @@ class SettingsAdapter(
val scSetting = clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which)
if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
val setting = scSetting.setSelectedValue(value!!)
fragmentView.putSetting(setting)
scSetting.setSelectedValue(value!!)
closeDialog()
}
@ -268,13 +261,25 @@ class SettingsAdapter(
if (sliderSetting.selectedValue != sliderProgress) {
fragmentView.onSettingChanged()
}
if (sliderSetting.setting is FloatSetting) {
val value = sliderProgress.toFloat()
val setting = sliderSetting.setSelectedValue(value)
fragmentView.putSetting(setting)
} else {
val setting = sliderSetting.setSelectedValue(sliderProgress)
fragmentView.putSetting(setting)
when (sliderSetting.setting) {
is ByteSetting -> {
val value = sliderProgress.toByte()
sliderSetting.setSelectedValue(value)
}
is ShortSetting -> {
val value = sliderProgress.toShort()
sliderSetting.setSelectedValue(value)
}
is FloatSetting -> {
val value = sliderProgress.toFloat()
sliderSetting.setSelectedValue(value)
}
else -> {
sliderSetting.setSelectedValue(sliderProgress)
}
}
closeDialog()
}
@ -286,13 +291,8 @@ class SettingsAdapter(
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
when (setting) {
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
is AbstractStringSetting -> setting.string = setting.defaultValue as String
}
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
setting.reset()
notifyItemChanged(position)
fragmentView.onSettingChanged()
}

View file

@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsFragment : Fragment(), SettingsFragmentView {
@ -89,14 +88,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
)
}
override fun showToastMessage(message: String?, is_long: Boolean) {
activityView!!.showToastMessage(message!!, is_long)
}
override fun putSetting(setting: AbstractSetting) {
fragmentPresenter.putSetting(setting)
}
override fun onSettingChanged() {
activityView!!.onSettingChanged()
}

View file

@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.SharedPreferences
import android.os.Build
import android.text.TextUtils
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
@ -27,7 +29,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private var settingsList: ArrayList<SettingsItem>? = null
private val settingsActivity get() = fragmentView.activityView as SettingsActivity
private val settings get() = fragmentView.activityView!!.settings
private lateinit var preferences: SharedPreferences
@ -41,17 +42,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
loadSettingsList()
}
fun putSetting(setting: AbstractSetting) {
if (setting.section == null || setting.key == null) {
return
}
val section = settings.getSection(setting.section!!)!!
if (section.getSetting(setting.key!!) == null) {
section.putSetting(setting)
}
}
fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
settingsActivity.setToolbarTitle("Game Settings: $gameId")
@ -69,7 +59,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
fragmentView.showToastMessage("Unimplemented menu", false)
val context = YuzuApplication.appContext
Toast.makeText(
context,
context.getString(R.string.unimplemented_menu),
Toast.LENGTH_SHORT
).show()
return
}
}
@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
sl.apply {
add(
SwitchSetting(
IntSetting.RENDERER_USE_SPEED_LIMIT,
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
R.string.frame_limit_enable,
R.string.frame_limit_enable_description,
IntSetting.RENDERER_USE_SPEED_LIMIT.key,
IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
)
)
add(
SliderSetting(
IntSetting.RENDERER_SPEED_LIMIT,
ShortSetting.RENDERER_SPEED_LIMIT,
R.string.frame_limit_slider,
R.string.frame_limit_slider_description,
1,
200,
"%",
IntSetting.RENDERER_SPEED_LIMIT.key,
IntSetting.RENDERER_SPEED_LIMIT.defaultValue
ShortSetting.RENDERER_SPEED_LIMIT.key,
ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
)
)
add(
@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
sl.apply {
add(
SwitchSetting(
IntSetting.USE_DOCKED_MODE,
BooleanSetting.USE_DOCKED_MODE,
R.string.use_docked_mode,
R.string.use_docked_mode_description,
IntSetting.USE_DOCKED_MODE.key,
IntSetting.USE_DOCKED_MODE.defaultValue
BooleanSetting.USE_DOCKED_MODE.key,
BooleanSetting.USE_DOCKED_MODE.defaultValue
)
)
add(
@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
DateTimeSetting(
StringSetting.CUSTOM_RTC,
LongSetting.CUSTOM_RTC,
R.string.set_custom_rtc,
0,
StringSetting.CUSTOM_RTC.key,
StringSetting.CUSTOM_RTC.defaultValue
LongSetting.CUSTOM_RTC.key,
LongSetting.CUSTOM_RTC.defaultValue
)
)
}
@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
R.string.use_disk_shader_cache,
R.string.use_disk_shader_cache_description,
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
)
)
add(
SwitchSetting(
IntSetting.RENDERER_FORCE_MAX_CLOCK,
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
R.string.renderer_force_max_clock,
R.string.renderer_force_max_clock_description,
IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
)
)
add(
SwitchSetting(
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
R.string.renderer_asynchronous_shaders,
R.string.renderer_asynchronous_shaders_description,
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
)
)
add(
SwitchSetting(
IntSetting.RENDERER_REACTIVE_FLUSHING,
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
R.string.renderer_reactive_flushing,
R.string.renderer_reactive_flushing_description,
IntSetting.RENDERER_REACTIVE_FLUSHING.key,
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
)
)
}
@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply {
add(
StringSingleChoiceSetting(
StringSetting.AUDIO_OUTPUT_ENGINE,
SingleChoiceSetting(
IntSetting.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
R.array.outputEngineEntries,
R.array.outputEngineValues,
IntSetting.AUDIO_OUTPUT_ENGINE.key,
IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
)
)
add(
SliderSetting(
IntSetting.AUDIO_VOLUME,
ByteSetting.AUDIO_VOLUME,
R.string.audio_volume,
R.string.audio_volume_description,
0,
100,
"%",
IntSetting.AUDIO_VOLUME.key,
IntSetting.AUDIO_VOLUME.defaultValue
ByteSetting.AUDIO_VOLUME.key,
ByteSetting.AUDIO_VOLUME.defaultValue
)
)
}
@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
override var int: Int
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0)
set(value) {
preferences.edit()
.putInt(Settings.PREF_THEME, value)
.apply()
settingsActivity.recreate()
}
override fun setInt(value: Int) {
preferences.edit()
.putInt(Settings.PREF_THEME, value)
.apply()
settingsActivity.recreate()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Any = 0
}
@ -423,19 +418,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override var int: Int
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
set(value) {
preferences.edit()
.putInt(Settings.PREF_THEME_MODE, value)
.apply()
ThemeHelper.setThemeMode(settingsActivity)
}
override fun setInt(value: Int) {
preferences.edit()
.putInt(Settings.PREF_THEME_MODE, value)
.apply()
ThemeHelper.setThemeMode(settingsActivity)
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Any = -1
}
@ -450,20 +445,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
override var boolean: Boolean
override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
.apply()
settingsActivity.recreate()
}
override fun setBoolean(value: Boolean) {
preferences.edit()
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
.apply()
settingsActivity.recreate()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
.toString()
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Any = false
}
@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.RENDERER_DEBUG,
BooleanSetting.RENDERER_DEBUG,
R.string.renderer_debug,
R.string.renderer_debug_description,
IntSetting.RENDERER_DEBUG.key,
IntSetting.RENDERER_DEBUG.defaultValue
BooleanSetting.RENDERER_DEBUG.key,
BooleanSetting.RENDERER_DEBUG.defaultValue
)
)
@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
val fastmem = object : AbstractBooleanSetting {
override var boolean: Boolean
override val boolean: Boolean
get() =
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
set(value) {
BooleanSetting.FASTMEM.boolean = value
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
}
override fun setBoolean(value: Boolean) {
BooleanSetting.FASTMEM.setBoolean(value)
BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
}
override val key: String? = null
override val section: String = Settings.SECTION_CPU
override val isRuntimeEditable: Boolean = false
override val valueAsString: String = ""
override val category = Settings.Category.Cpu
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Any = true
}
add(

View file

@ -3,7 +3,6 @@
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
/**
@ -36,21 +35,6 @@ interface SettingsFragmentView {
*/
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.
*/

View file

@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
binding.textSettingValue.visibility = View.VISIBLE
val epochTime = setting.value.toLong()
val epochTime = setting.value
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)

View file

@ -3,18 +3,15 @@
package org.yuzu.yuzu_emu.features.settings.utils
import android.widget.Toast
import java.io.*
import java.util.*
import org.ini4j.Wini
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.*
import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.utils.BiMap
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
object SettingsFile {
const val FILE_NAME_CONFIG = "config"
private var sectionsMap = BiMap<String?, String?>()
/**
* Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
* failed.
*
* @param ini The ini file to load the settings from
* @param isCustomGame
* @param view The current view.
* @return An Observable that emits a HashMap of the file's contents, then completes.
*/
private fun readFile(
ini: File?,
isCustomGame: Boolean,
view: SettingsActivityView? = null
): HashMap<String, SettingSection?> {
val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
var reader: BufferedReader? = null
try {
reader = BufferedReader(FileReader(ini))
var current: SettingSection? = null
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("[") && line!!.endsWith("]")) {
current = sectionFromLine(line!!, isCustomGame)
sections[current.name] = current
} else if (current != null) {
val setting = settingFromLine(line!!)
if (setting != null) {
current.putSetting(setting)
}
}
}
} catch (e: FileNotFoundException) {
Log.error("[SettingsFile] File not found: " + e.message)
view?.onSettingsFileNotFound()
} catch (e: IOException) {
Log.error("[SettingsFile] Error reading from: " + e.message)
view?.onSettingsFileNotFound()
} finally {
if (reader != null) {
try {
reader.close()
} catch (e: IOException) {
Log.error("[SettingsFile] Error closing: " + e.message)
}
}
}
return sections
}
fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
return readFile(getSettingsFile(fileName), false, view)
}
fun readFile(fileName: String): HashMap<String, SettingSection?> =
readFile(getSettingsFile(fileName), false)
/**
* Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
* failed.
*
* @param gameId the id of the game to load it's settings.
* @param view The current view.
*/
fun readCustomGameSettings(
gameId: String,
view: SettingsActivityView?
): HashMap<String, SettingSection?> {
return readFile(getCustomGameSettingsFile(gameId), true, view)
}
/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
* telling why it failed.
*
* @param fileName The target filename without a path or extension.
* @param sections The HashMap containing the Settings we want to serialize.
* @param view The current view.
*/
fun saveFile(
fileName: String,
sections: TreeMap<String, SettingSection>,
view: SettingsActivityView
) {
fun saveFile(fileName: String) {
val ini = getSettingsFile(fileName)
try {
val writer = Wini(ini)
val keySet: Set<String> = sections.keys
for (key in keySet) {
val section = sections[key]
writeSection(writer, section!!)
val wini = Wini(ini)
for (specificCategory in Settings.Category.values()) {
val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
for (setting in Settings.settingsList) {
if (setting.key!!.isEmpty()) continue
val settingCategoryHeader =
NativeConfig.getConfigHeader(setting.category.ordinal)
val iniSetting: String? = wini.get(categoryHeader, setting.key)
if (iniSetting != null || settingCategoryHeader == categoryHeader) {
wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
}
}
}
writer.store()
wini.store()
} catch (e: IOException) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
view.showToastMessage(
YuzuApplication.appContext
.getString(R.string.error_saving, fileName, e.message),
false
)
val context = YuzuApplication.appContext
Toast.makeText(
context,
context.getString(R.string.error_saving, fileName, e.message),
Toast.LENGTH_SHORT
).show()
}
}
fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
val sortedSections: Set<String> = TreeSet(sections.keys)
for (sectionKey in sortedSections) {
val section = sections[sectionKey]
val settings = section!!.settings
val sortedKeySet: Set<String> = TreeSet(settings.keys)
for (settingKey in sortedKeySet) {
val setting = settings[settingKey]
NativeLibrary.setUserSetting(
gameId,
mapSectionNameFromIni(
section.name
),
setting!!.key,
setting.valueAsString
)
}
}
}
private fun mapSectionNameFromIni(generalSectionName: String): String? {
return if (sectionsMap.getForward(generalSectionName) != null) {
sectionsMap.getForward(generalSectionName)
} else {
generalSectionName
}
}
private fun mapSectionNameToIni(generalSectionName: String): String {
return if (sectionsMap.getBackward(generalSectionName) != null) {
sectionsMap.getBackward(generalSectionName).toString()
} else {
generalSectionName
}
}
fun getSettingsFile(fileName: String): File {
return File(
DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
)
}
private fun getCustomGameSettingsFile(gameId: String): File {
return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
}
private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
var sectionName: String = line.substring(1, line.length - 1)
if (isCustomGame) {
sectionName = mapSectionNameToIni(sectionName)
}
return SettingSection(sectionName)
}
/**
* For a line of text, determines what type of data is being represented, and returns
* a Setting object containing this data.
*
* @param line The line of text being parsed.
* @return A typed Setting containing the key/value contained in the line.
*/
private fun settingFromLine(line: String): AbstractSetting? {
val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (splitLine.size != 2) {
return null
}
val key = splitLine[0].trim { it <= ' ' }
val value = splitLine[1].trim { it <= ' ' }
if (value.isEmpty()) {
return null
}
val booleanSetting = BooleanSetting.from(key)
if (booleanSetting != null) {
booleanSetting.boolean = value.toBoolean()
return booleanSetting
}
val intSetting = IntSetting.from(key)
if (intSetting != null) {
intSetting.int = value.toInt()
return intSetting
}
val floatSetting = FloatSetting.from(key)
if (floatSetting != null) {
floatSetting.float = value.toFloat()
return floatSetting
}
val stringSetting = StringSetting.from(key)
if (stringSetting != null) {
stringSetting.string = value
return stringSetting
}
return null
}
/**
* Writes the contents of a Section HashMap to disk.
*
* @param parser A Wini pointed at a file on disk.
* @param section A section containing settings to be written to the file.
*/
private fun writeSection(parser: Wini, section: SettingSection) {
// Write the section header.
val header = section.name
// Write this section's values.
val settings = section.settings
val keySet: Set<String> = settings.keys
for (key in keySet) {
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)
}
}
}
fun getSettingsFile(fileName: String): File =
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
}

View file

@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
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
@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
override var themeId: Int = 0
@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
settingsViewModel.settings.loadSettings()
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)

View file

@ -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]
}
}

View file

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
object NativeConfig {
external fun getBoolean(key: String, getDefault: Boolean): Boolean
external fun setBoolean(key: String, value: Boolean)
external fun getByte(key: String, getDefault: Boolean): Byte
external fun setByte(key: String, value: Byte)
external fun getShort(key: String, getDefault: Boolean): Short
external fun setShort(key: String, value: Short)
external fun getInt(key: String, getDefault: Boolean): Int
external fun setInt(key: String, value: Int)
external fun getFloat(key: String, getDefault: Boolean): Float
external fun setFloat(key: String, value: Float)
external fun getLong(key: String, getDefault: Boolean): Long
external fun setLong(key: String, value: Long)
external fun getString(key: String, getDefault: Boolean): String
external fun setString(key: String, value: String)
external fun getIsRuntimeModifiable(key: String): Boolean
external fun getConfigHeader(category: Int): String
}

View file

@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
id_cache.cpp
id_cache.h
native.cpp
native_config.cpp
uisettings.cpp
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})

View file

@ -16,18 +16,20 @@
#include "input_common/main.h"
#include "jni/config.h"
#include "jni/default_ini.h"
#include "uisettings.h"
namespace FS = Common::FS;
Config::Config(std::optional<std::filesystem::path> config_path)
: config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
Reload();
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) {
@ -301,9 +303,28 @@ void Config::ReadValues() {
// Network
ReadSetting("Network", Settings::values.network_interface);
// Android
ReadSetting("Android", AndroidSettings::values.picture_in_picture);
ReadSetting("Android", AndroidSettings::values.screen_layout);
}
void Config::Reload() {
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();
}

View file

@ -13,25 +13,35 @@
class INIReader;
class Config {
std::filesystem::path config_loc;
std::unique_ptr<INIReader> config;
bool LoadINI(const std::string& default_contents = "", bool retry = true);
void ReadValues();
public:
explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
enum class ConfigType {
GlobalConfig,
PerGameConfig,
InputProfile,
};
explicit Config(const std::string& config_name = "config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
void Initialize(const std::string& config_name);
private:
/**
* Applies a value read from the sdl2_config to a Setting.
* 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;
};

View file

@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
Config{};
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
jstring j_game_id, jstring j_section,
jstring j_key) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
std::string_view section = env->GetStringUTFChars(j_section, 0);
std::string_view key = env->GetStringUTFChars(j_key, 0);
env->ReleaseStringUTFChars(j_game_id, game_id.data());
env->ReleaseStringUTFChars(j_section, section.data());
env->ReleaseStringUTFChars(j_key, key.data());
return env->NewStringUTF("");
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
jstring j_game_id, jstring j_section,
jstring j_key, jstring j_value) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
std::string_view section = env->GetStringUTFChars(j_section, 0);
std::string_view key = env->GetStringUTFChars(j_key, 0);
std::string_view value = env->GetStringUTFChars(j_value, 0);
env->ReleaseStringUTFChars(j_game_id, game_id.data());
env->ReleaseStringUTFChars(j_section, section.data());
env->ReleaseStringUTFChars(j_key, key.data());
env->ReleaseStringUTFChars(j_value, value.data());
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);

View file

@ -0,0 +1,224 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <jni.h>
#include "common/logging/log.h"
#include "common/settings.h"
#include "jni/android_common/android_common.h"
#include "jni/config.h"
#include "uisettings.h"
template <typename T>
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
auto key = GetJString(env, jkey);
auto basicSetting = Settings::values.linkage.by_key[key];
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
if (basicSetting != 0) {
return static_cast<Settings::Setting<T>*>(basicSetting);
}
if (basicAndroidSetting != 0) {
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
}
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
return nullptr;
}
extern "C" {
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
jstring jkey, jboolean getDefault) {
auto setting = getSetting<bool>(env, jkey);
if (setting == nullptr) {
return false;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
jboolean value) {
auto setting = getSetting<bool>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(static_cast<bool>(value));
}
jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<u8>(env, jkey);
if (setting == nullptr) {
return -1;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
jbyte value) {
auto setting = getSetting<u8>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(value);
}
jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
jshort value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(value);
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<int>(env, jkey);
if (setting == nullptr) {
return -1;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
jint value) {
auto setting = getSetting<int>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(value);
}
jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<float>(env, jkey);
if (setting == nullptr) {
return -1;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
jfloat value) {
auto setting = getSetting<float>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(value);
}
jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<long>(env, jkey);
if (setting == nullptr) {
return -1;
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return setting->GetDefault();
}
return setting->GetValue();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
jlong value) {
auto setting = getSetting<long>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(value);
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
jboolean getDefault) {
auto setting = getSetting<std::string>(env, jkey);
if (setting == nullptr) {
return ToJString(env, "");
}
setting->SetGlobal(true);
if (static_cast<bool>(getDefault)) {
return ToJString(env, setting->GetDefault());
}
return ToJString(env, setting->GetValue());
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
jstring value) {
auto setting = getSetting<std::string>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetGlobal(true);
setting->SetValue(GetJString(env, value));
}
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
jstring jkey) {
auto key = GetJString(env, jkey);
auto setting = Settings::values.linkage.by_key[key];
if (setting != 0) {
return setting->RuntimeModfiable();
}
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
return true;
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
jint jcategory) {
auto category = static_cast<Settings::Category>(jcategory);
return ToJString(env, Settings::TranslateCategory(category));
}
} // extern "C"

View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "uisettings.h"
namespace AndroidSettings {
Values values;
} // namespace AndroidSettings

View file

@ -0,0 +1,29 @@
// 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

View file

@ -243,10 +243,10 @@
<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>
<integer-array name="outputEngineValues">
<item>0</item>
<item>1</item>
<item>3</item>
</integer-array>
</resources>

View file

@ -200,6 +200,7 @@
<string name="ini_saved">Saved settings</string>
<string name="gameid_saved">Saved settings for %1$s</string>
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
<string name="unimplemented_menu">Unimplemented Menu</string>
<string name="loading">Loading…</string>
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="reset_to_default">Reset to default</string>

View file

@ -159,6 +159,8 @@ float Volume() {
const char* TranslateCategory(Category category) {
switch (category) {
case Category::Android:
return "Android";
case Category::Audio:
return "Audio";
case Category::Core:

View file

@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
: label{name}, category{category_}, id{linkage.count}, save{save_},
runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
other_setting{other_setting_} {
linkage.by_key.insert({name, this});
linkage.by_category[category].push_back(this);
linkage.count++;
}

View file

@ -12,6 +12,7 @@
namespace Settings {
enum class Category : u32 {
Android,
Audio,
Core,
Cpu,
@ -68,6 +69,7 @@ public:
explicit Linkage(u32 initial_count = 0);
~Linkage();
std::map<Category, std::vector<BasicSetting*>> by_category{};
std::map<std::string, Settings::BasicSetting*> by_key{};
std::vector<std::function<void()>> restore_functions{};
u32 count;
};