diff --git a/CMakeLists.txt b/CMakeLists.txt index c4943729..15e05c2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,8 +94,8 @@ if (ANDROID AND SUDACHI_DOWNLOAD_ANDROID_VVL) WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") endif() - # Copy the arm64 binary to src/android/app/main/jniLibs - set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/") + # Copy the arm64 binary to src/android/sudachi/main/jniLibs + set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/sudachi/src/main/jniLibs/arm64-v8a/") file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so" DESTINATION "${vvl_lib_path}") endif() diff --git a/dist/languages/.tx/config b/dist/languages/.tx/config index e4d947d3..e30a2af2 100644 --- a/dist/languages/.tx/config +++ b/dist/languages/.tx/config @@ -8,7 +8,7 @@ source_lang = en type = QT [o:sudachi-emulator:p:sudachi:r:sudachi-android] -file_filter = ../../src/android/app/src/main/res/values-/strings.xml -source_file = ../../src/android/app/src/main/res/values/strings.xml +file_filter = ../../src/android/sudachi/src/main/res/values-/strings.xml +source_file = ../../src/android/sudachi/src/main/res/values/strings.xml type = ANDROID lang_map = ja_JP:ja, ko_KR:ko, pt_BR:pt-rBR, pt_PT:pt-rPT, ru_RU:ru, vi_VN:vi, zh_CN:zh-rCN, zh_TW:zh-rTW diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e35885a..eb47b691 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,6 +214,6 @@ if (ENABLE_WEB_SERVICE) endif() if (ANDROID) - add_subdirectory(android/app/src/main/jni) - target_include_directories(sudachi-android PRIVATE android/app/src/main) + add_subdirectory(android/sudachi/src/main/jni) + target_include_directories(sudachi-android PRIVATE android/sudachi/src/main) endif() diff --git a/src/android/.gitignore b/src/android/.gitignore index d0860dcd..e1776c76 100644 --- a/src/android/.gitignore +++ b/src/android/.gitignore @@ -47,7 +47,7 @@ captures/ .externalNativeBuild # CXX compile cache -app/.cxx +sudachi/.cxx # Google Services (e.g. APIs or Firebase) google-services.json @@ -67,5 +67,5 @@ fastlane/readme.md # Autogenerated library for vulkan validation layers libVkLayer_khronos_validation.so -app/ea -app/mainline \ No newline at end of file +sudachi/ea +sudachi/mainline \ No newline at end of file diff --git a/src/android/app/proguard-rules.pro b/src/android/app/proguard-rules.pro deleted file mode 100644 index 3c6e4553..00000000 --- a/src/android/app/proguard-rules.pro +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-FileCopyrightText: 2023 sudachi Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -# To get usable stack traces --dontobfuscate - -# Prevents crashing when using Wini --keep class org.ini4j.spi.IniParser --keep class org.ini4j.spi.IniBuilder --keep class org.ini4j.spi.IniFormatter - -# Suppress warnings for R8 --dontwarn org.bouncycastle.jsse.BCSSLParameters --dontwarn org.bouncycastle.jsse.BCSSLSocket --dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider --dontwarn org.conscrypt.Conscrypt$Version --dontwarn org.conscrypt.Conscrypt --dontwarn org.conscrypt.ConscryptHostnameVerifier --dontwarn org.openjsse.javax.net.ssl.SSLParameters --dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE --dontwarn java.beans.Introspector --dontwarn java.beans.VetoableChangeListener --dontwarn java.beans.VetoableChangeSupport diff --git a/src/android/app/src/ea/res/drawable/ic_sudachi.xml b/src/android/app/src/ea/res/drawable/ic_sudachi.xml deleted file mode 100644 index deb8ba53..00000000 --- a/src/android/app/src/ea/res/drawable/ic_sudachi.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/src/android/app/src/ea/res/drawable/ic_sudachi_full.xml b/src/android/app/src/ea/res/drawable/ic_sudachi_full.xml deleted file mode 100644 index 4ef47287..00000000 --- a/src/android/app/src/ea/res/drawable/ic_sudachi_full.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/src/android/app/src/ea/res/drawable/ic_sudachi_title.xml b/src/android/app/src/ea/res/drawable/ic_sudachi_title.xml deleted file mode 100644 index 29d0cfce..00000000 --- a/src/android/app/src/ea/res/drawable/ic_sudachi_title.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 73638c04..00000000 --- a/src/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/SudachiApplication.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/SudachiApplication.kt deleted file mode 100644 index 858b6ef9..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/SudachiApplication.kt +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu - -import android.app.Application -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import org.sudachi.sudachi_emu.features.input.NativeInput -import java.io.File -import org.sudachi.sudachi_emu.utils.DirectoryInitialization -import org.sudachi.sudachi_emu.utils.DocumentsTree -import org.sudachi.sudachi_emu.utils.GpuDriverHelper -import org.sudachi.sudachi_emu.utils.Log - -fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir - -class SudachiApplication : Application() { - private fun createNotificationChannels() { - val noticeChannel = NotificationChannel( - getString(R.string.notice_notification_channel_id), - getString(R.string.notice_notification_channel_name), - NotificationManager.IMPORTANCE_HIGH - ) - noticeChannel.description = getString(R.string.notice_notification_channel_description) - noticeChannel.setSound(null, null) - - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(noticeChannel) - } - - override fun onCreate() { - super.onCreate() - application = this - documentsTree = DocumentsTree() - DirectoryInitialization.start() - GpuDriverHelper.initializeDriverParameters() - NativeInput.reloadInputDevices() - NativeLibrary.logDeviceInfo() - Log.logDeviceInfo() - - createNotificationChannels() - } - - companion object { - var documentsTree: DocumentsTree? = null - lateinit var application: SudachiApplication - - val appContext: Context - get() = application.applicationContext - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/activities/EmulationActivity.kt deleted file mode 100644 index 59a14ea4..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/activities/EmulationActivity.kt +++ /dev/null @@ -1,509 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.activities - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.PictureInPictureParams -import android.app.RemoteAction -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.content.res.Configuration -import android.graphics.Rect -import android.graphics.drawable.Icon -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.os.Build -import android.os.Bundle -import android.util.Rational -import android.view.InputDevice -import android.view.KeyEvent -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 androidx.preference.PreferenceManager -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.databinding.ActivityEmulationBinding -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.settings.model.BooleanSetting -import org.sudachi.sudachi_emu.features.settings.model.IntSetting -import org.sudachi.sudachi_emu.features.settings.model.Settings -import org.sudachi.sudachi_emu.model.EmulationViewModel -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.utils.InputHandler -import org.sudachi.sudachi_emu.utils.Log -import org.sudachi.sudachi_emu.utils.MemoryUtil -import org.sudachi.sudachi_emu.utils.NativeConfig -import org.sudachi.sudachi_emu.utils.NfcReader -import org.sudachi.sudachi_emu.utils.ParamPackage -import org.sudachi.sudachi_emu.utils.ThemeHelper -import java.text.NumberFormat -import kotlin.math.roundToInt - -class EmulationActivity : AppCompatActivity(), SensorEventListener { - private lateinit var binding: ActivityEmulationBinding - - var isActivityRecreated = false - private lateinit var nfcReader: NfcReader - - private val gyro = FloatArray(3) - private val accel = FloatArray(3) - private var motionTimestamp: Long = 0 - private var flipMotionOrientation: Boolean = false - - private val actionPause = "ACTION_EMULATOR_PAUSE" - private val actionPlay = "ACTION_EMULATOR_PLAY" - private val actionMute = "ACTION_EMULATOR_MUTE" - private val actionUnmute = "ACTION_EMULATOR_UNMUTE" - - private val emulationViewModel: EmulationViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - Log.gameLaunched = true - ThemeHelper.setTheme(this) - - super.onCreate(savedInstanceState) - - InputHandler.updateControllerData() - val players = NativeConfig.getInputSettings(true) - var hasConfiguredControllers = false - players.forEach { - if (it.hasMapping()) { - hasConfiguredControllers = true - } - } - if (!hasConfiguredControllers && InputHandler.androidControllers.isNotEmpty()) { - var params: ParamPackage? = null - for (controller in InputHandler.registeredControllers) { - if (controller.get("port", -1) == 0) { - params = controller - break - } - } - - if (params != null) { - NativeInput.updateMappingsWithDefault( - 0, - params, - params.get("display", getString(R.string.unknown)) - ) - NativeConfig.saveGlobalConfig() - } - } - - binding = ActivityEmulationBinding.inflate(layoutInflater) - setContentView(binding.root) - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment - navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras) - - isActivityRecreated = savedInstanceState != null - - // Set these options now so that the SurfaceView the game renders into is the right size. - enableFullscreenImmersive() - - window.decorView.setBackgroundColor(getColor(android.R.color.black)) - - nfcReader = NfcReader(this) - nfcReader.initialize() - - val preferences = PreferenceManager.getDefaultSharedPreferences(SudachiApplication.appContext) - if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { - if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) { - Toast.makeText( - this, - getString( - R.string.device_memory_inadequate, - MemoryUtil.getDeviceRAM(), - getString( - R.string.memory_formatted, - NumberFormat.getInstance().format(MemoryUtil.REQUIRED_MEMORY), - getString(R.string.memory_gigabyte) - ) - ), - Toast.LENGTH_LONG - ).show() - preferences.edit() - .putBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, true) - .apply() - } - } - } - - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { - if (event.action == KeyEvent.ACTION_DOWN) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - // Special case, we do not support multiline input, dismiss the keyboard. - val overlayView: View = - this.findViewById(R.id.surface_input_overlay) - val im = - overlayView.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - im.hideSoftInputFromWindow(overlayView.windowToken, 0) - } else { - val textChar = event.unicodeChar - if (textChar == 0) { - // No text, button input. - NativeLibrary.submitInlineKeyboardInput(keyCode) - } else { - // Text submitted. - NativeLibrary.submitInlineKeyboardText(textChar.toChar().toString()) - } - } - } - return super.onKeyDown(keyCode, event) - } - - override fun onResume() { - super.onResume() - nfcReader.startScanning() - startMotionSensorListener() - InputHandler.updateControllerData() - - buildPictureInPictureParams() - } - - override fun onPause() { - super.onPause() - nfcReader.stopScanning() - stopMotionSensorListener() - } - - override fun onUserLeaveHint() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { - val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() - .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() - enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) - } - } - } - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - setIntent(intent) - nfcReader.onNewIntent(intent) - InputHandler.updateControllerData() - } - - override fun dispatchKeyEvent(event: KeyEvent): Boolean { - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD - ) { - return super.dispatchKeyEvent(event) - } - - if (emulationViewModel.drawerOpen.value) { - return super.dispatchKeyEvent(event) - } - - return InputHandler.dispatchKeyEvent(event) - } - - override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD - ) { - return super.dispatchGenericMotionEvent(event) - } - - if (emulationViewModel.drawerOpen.value) { - return super.dispatchGenericMotionEvent(event) - } - - // Don't attempt to do anything if we are disconnecting a device. - if (event.actionMasked == MotionEvent.ACTION_CANCEL) { - return true - } - - return InputHandler.dispatchGenericMotionEvent(event) - } - - override fun onSensorChanged(event: SensorEvent) { - val rotation = this.display?.rotation - if (rotation == Surface.ROTATION_90) { - flipMotionOrientation = true - } - if (rotation == Surface.ROTATION_270) { - flipMotionOrientation = false - } - - if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { - if (flipMotionOrientation) { - accel[0] = event.values[1] / SensorManager.GRAVITY_EARTH - accel[1] = -event.values[0] / SensorManager.GRAVITY_EARTH - } else { - accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH - accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH - } - accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH - } - if (event.sensor.type == Sensor.TYPE_GYROSCOPE) { - // Investigate why sensor value is off by 6x - if (flipMotionOrientation) { - gyro[0] = -event.values[1] / 6.0f - gyro[1] = event.values[0] / 6.0f - } else { - gyro[0] = event.values[1] / 6.0f - gyro[1] = -event.values[0] / 6.0f - } - gyro[2] = event.values[2] / 6.0f - } - - // Only update state on accelerometer data - if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) { - return - } - val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000 - motionTimestamp = event.timestamp - NativeInput.onDeviceMotionEvent( - NativeInput.Player1Device, - deltaTimestamp, - gyro[0], - gyro[1], - gyro[2], - accel[0], - accel[1], - accel[2] - ) - NativeInput.onDeviceMotionEvent( - NativeInput.ConsoleDevice, - deltaTimestamp, - gyro[0], - gyro[1], - gyro[2], - accel[0], - accel[1], - accel[2] - ) - } - - override fun onAccuracyChanged(sensor: Sensor, i: Int) {} - - private fun enableFullscreenImmersive() { - WindowCompat.setDecorFitsSystemWindows(window, false) - - WindowInsetsControllerCompat(window, window.decorView).let { controller -> - controller.hide(WindowInsetsCompat.Type.systemBars()) - controller.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } - - private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): - PictureInPictureParams.Builder { - val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) { - 0 -> Rational(16, 9) - 1 -> Rational(4, 3) - 2 -> Rational(21, 9) - 3 -> Rational(16, 10) - else -> null // Best fit - } - return this.apply { aspectRatio?.let { setAspectRatio(it) } } - } - - private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): - PictureInPictureParams.Builder { - val pictureInPictureActions: MutableList = mutableListOf() - val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - - if (NativeLibrary.isPaused()) { - val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play) - val playPendingIntent = PendingIntent.getBroadcast( - this@EmulationActivity, - R.drawable.ic_pip_play, - Intent(actionPlay), - pendingFlags - ) - val playRemoteAction = RemoteAction( - playIcon, - getString(R.string.play), - getString(R.string.play), - playPendingIntent - ) - pictureInPictureActions.add(playRemoteAction) - } else { - val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause) - val pausePendingIntent = PendingIntent.getBroadcast( - this@EmulationActivity, - R.drawable.ic_pip_pause, - Intent(actionPause), - pendingFlags - ) - val pauseRemoteAction = RemoteAction( - pauseIcon, - getString(R.string.pause), - getString(R.string.pause), - pausePendingIntent - ) - pictureInPictureActions.add(pauseRemoteAction) - } - - if (BooleanSetting.AUDIO_MUTED.getBoolean()) { - val unmuteIcon = Icon.createWithResource( - this@EmulationActivity, - R.drawable.ic_pip_unmute - ) - val unmutePendingIntent = PendingIntent.getBroadcast( - this@EmulationActivity, - R.drawable.ic_pip_unmute, - Intent(actionUnmute), - pendingFlags - ) - val unmuteRemoteAction = RemoteAction( - unmuteIcon, - getString(R.string.unmute), - getString(R.string.unmute), - unmutePendingIntent - ) - pictureInPictureActions.add(unmuteRemoteAction) - } else { - val muteIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_mute) - val mutePendingIntent = PendingIntent.getBroadcast( - this@EmulationActivity, - R.drawable.ic_pip_mute, - Intent(actionMute), - pendingFlags - ) - val muteRemoteAction = RemoteAction( - muteIcon, - getString(R.string.mute), - getString(R.string.mute), - mutePendingIntent - ) - pictureInPictureActions.add(muteRemoteAction) - } - - return this.apply { setActions(pictureInPictureActions) } - } - - fun buildPictureInPictureParams() { - val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() - .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val isEmulationActive = emulationViewModel.emulationStarted.value && - !emulationViewModel.isEmulationStopping.value - pictureInPictureParamsBuilder.setAutoEnterEnabled( - BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive - ) - } - setPictureInPictureParams(pictureInPictureParamsBuilder.build()) - } - - private var pictureInPictureReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent) { - if (intent.action == actionPlay) { - if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation() - } else if (intent.action == actionPause) { - if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() - } - if (intent.action == actionUnmute) { - if (BooleanSetting.AUDIO_MUTED.getBoolean()) { - BooleanSetting.AUDIO_MUTED.setBoolean(false) - } - } else if (intent.action == actionMute) { - if (!BooleanSetting.AUDIO_MUTED.getBoolean()) { - BooleanSetting.AUDIO_MUTED.setBoolean(true) - } - } - buildPictureInPictureParams() - } - } - - @SuppressLint("UnspecifiedRegisterReceiverFlag") - override fun onPictureInPictureModeChanged( - isInPictureInPictureMode: Boolean, - newConfig: Configuration - ) { - super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - if (isInPictureInPictureMode) { - IntentFilter().apply { - addAction(actionPause) - addAction(actionPlay) - addAction(actionMute) - addAction(actionUnmute) - }.also { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED) - } else { - registerReceiver(pictureInPictureReceiver, it) - } - } - } else { - try { - unregisterReceiver(pictureInPictureReceiver) - } catch (ignored: Exception) { - } - // Always resume audio, since there is no UI button - if (BooleanSetting.AUDIO_MUTED.getBoolean()) { - BooleanSetting.AUDIO_MUTED.setBoolean(false) - } - } - } - - fun onEmulationStarted() { - emulationViewModel.setEmulationStarted(true) - } - - fun onEmulationStopped(status: Int) { - if (status == 0 && emulationViewModel.programChanged.value == -1) { - finish() - } - emulationViewModel.setEmulationStopped(true) - } - - fun onProgramChanged(programIndex: Int) { - emulationViewModel.setProgramChanged(programIndex) - } - - private fun startMotionSensorListener() { - val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager - val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) - val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME) - sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME) - } - - private fun stopMotionSensorListener() { - val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager - val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) - val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - - sensorManager.unregisterListener(this, gyroSensor) - sensorManager.unregisterListener(this, accelSensor) - } - - companion object { - const val EXTRA_SELECTED_GAME = "SelectedGame" - - fun launch(activity: AppCompatActivity, game: Game) { - val launcher = Intent(activity, EmulationActivity::class.java) - launcher.putExtra(EXTRA_SELECTED_GAME, game) - activity.startActivity(launcher) - } - - private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean { - if (view == null) { - return true - } - val viewBounds = Rect() - view.getGlobalVisibleRect(viewBounds) - return !viewBounds.contains(x.roundToInt(), y.roundToInt()) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AbstractDiffAdapter.kt deleted file mode 100644 index b145080d..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AbstractDiffAdapter.kt +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.annotation.SuppressLint -import androidx.recyclerview.widget.AsyncDifferConfig -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder -import androidx.recyclerview.widget.RecyclerView - -/** - * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate - * code used in every [RecyclerView]. - * Type assigned to [Model] must inherit from [Object] in order to be compared properly. - * @param exact Decides whether each item will be compared by reference or by their contents - */ -abstract class AbstractDiffAdapter>( - exact: Boolean = true -) : ListAdapter(AsyncDifferConfig.Builder(DiffCallback(exact)).build()) { - override fun onBindViewHolder(holder: Holder, position: Int) = - holder.bind(currentList[position]) - - private class DiffCallback(val exact: Boolean) : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { - if (exact) { - return oldItem === newItem - } - return oldItem == newItem - } - - @SuppressLint("DiffUtilEquals") - override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { - return oldItem == newItem - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AddonAdapter.kt deleted file mode 100644 index 92824b26..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AddonAdapter.kt +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import org.sudachi.sudachi_emu.databinding.ListItemAddonBinding -import org.sudachi.sudachi_emu.model.Patch -import org.sudachi.sudachi_emu.model.AddonViewModel -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class AddonAdapter(val addonViewModel: AddonViewModel) : - AbstractDiffAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { - ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return AddonViewHolder(it) } - } - - inner class AddonViewHolder(val binding: ListItemAddonBinding) : - AbstractViewHolder(binding) { - override fun bind(model: Patch) { - binding.root.setOnClickListener { - binding.addonCheckbox.isChecked = !binding.addonCheckbox.isChecked - } - binding.title.text = model.name - binding.version.text = model.version - binding.addonCheckbox.setOnCheckedChangeListener { _, checked -> - model.enabled = checked - } - binding.addonCheckbox.isChecked = model.enabled - binding.buttonDelete.setOnClickListener { - addonViewModel.setAddonToDelete(model) - } - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AppletAdapter.kt deleted file mode 100644 index 10db1f7f..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/AppletAdapter.kt +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.Toast -import androidx.core.content.res.ResourcesCompat -import androidx.fragment.app.FragmentActivity -import androidx.navigation.findNavController -import org.sudachi.sudachi_emu.HomeNavigationDirections -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.databinding.CardSimpleOutlinedBinding -import org.sudachi.sudachi_emu.model.Applet -import org.sudachi.sudachi_emu.model.AppletInfo -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class AppletAdapter(val activity: FragmentActivity, applets: List) : - AbstractListAdapter(applets) { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): AppletAdapter.AppletViewHolder { - CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return AppletViewHolder(it) } - } - - inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) : - AbstractViewHolder(binding) { - override fun bind(model: Applet) { - binding.title.setText(model.titleId) - binding.description.setText(model.descriptionId) - binding.icon.setImageDrawable( - ResourcesCompat.getDrawable( - binding.icon.context.resources, - model.iconId, - binding.icon.context.theme - ) - ) - - binding.root.setOnClickListener { onClick(model) } - } - - fun onClick(applet: Applet) { - val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId) - if (appletPath.isEmpty()) { - Toast.makeText( - binding.root.context, - R.string.applets_error_applet, - Toast.LENGTH_SHORT - ).show() - return - } - - if (applet.appletInfo == AppletInfo.Cabinet) { - binding.root.findNavController() - .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment) - return - } - - NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId) - val appletGame = Game( - title = SudachiApplication.appContext.getString(applet.titleId), - path = appletPath - ) - val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) - binding.root.findNavController().navigate(action) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/FolderAdapter.kt deleted file mode 100644 index 703280d4..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/FolderAdapter.kt +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.net.Uri -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.fragment.app.FragmentActivity -import org.sudachi.sudachi_emu.databinding.CardFolderBinding -import org.sudachi.sudachi_emu.fragments.GameFolderPropertiesDialogFragment -import org.sudachi.sudachi_emu.model.GameDir -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.utils.ViewUtils.marquee -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : - AbstractDiffAdapter() { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): FolderAdapter.FolderViewHolder { - CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return FolderViewHolder(it) } - } - - inner class FolderViewHolder(val binding: CardFolderBinding) : - AbstractViewHolder(binding) { - override fun bind(model: GameDir) { - binding.apply { - path.text = Uri.parse(model.uriString).path - path.marquee() - - buttonEdit.setOnClickListener { - GameFolderPropertiesDialogFragment.newInstance(model) - .show( - activity.supportFragmentManager, - GameFolderPropertiesDialogFragment.TAG - ) - } - - buttonDelete.setOnClickListener { - gamesViewModel.removeFolder(model) - } - } - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/GameAdapter.kt deleted file mode 100644 index b3963268..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/GameAdapter.kt +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.net.Uri -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.pm.ShortcutInfoCompat -import androidx.core.content.pm.ShortcutManagerCompat -import androidx.documentfile.provider.DocumentFile -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.navigation.findNavController -import androidx.preference.PreferenceManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.sudachi.sudachi_emu.HomeNavigationDirections -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.databinding.CardGameBinding -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.utils.GameIconUtils -import org.sudachi.sudachi_emu.utils.ViewUtils.marquee -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class GameAdapter(private val activity: AppCompatActivity) : - AbstractDiffAdapter(exact = false) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { - CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return GameViewHolder(it) } - } - - inner class GameViewHolder(val binding: CardGameBinding) : - AbstractViewHolder(binding) { - override fun bind(model: Game) { - binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP - GameIconUtils.loadGameIcon(model, binding.imageGameScreen) - - binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ") - - binding.textGameTitle.marquee() - binding.cardGame.setOnClickListener { onClick(model) } - binding.cardGame.setOnLongClickListener { onLongClick(model) } - } - - fun onClick(game: Game) { - val gameExists = DocumentFile.fromSingleUri( - SudachiApplication.appContext, - Uri.parse(game.path) - )?.exists() == true - if (!gameExists) { - Toast.makeText( - SudachiApplication.appContext, - R.string.loader_error_file_not_found, - Toast.LENGTH_LONG - ).show() - - ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) - return - } - - val preferences = - PreferenceManager.getDefaultSharedPreferences(SudachiApplication.appContext) - preferences.edit() - .putLong( - game.keyLastPlayedTime, - System.currentTimeMillis() - ) - .apply() - - activity.lifecycleScope.launch { - withContext(Dispatchers.IO) { - val shortcut = - ShortcutInfoCompat.Builder(SudachiApplication.appContext, game.path) - .setShortLabel(game.title) - .setIcon(GameIconUtils.getShortcutIcon(activity, game)) - .setIntent(game.launchIntent) - .build() - ShortcutManagerCompat.pushDynamicShortcut(SudachiApplication.appContext, shortcut) - } - } - - val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true) - binding.root.findNavController().navigate(action) - } - - fun onLongClick(game: Game): Boolean { - val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game) - binding.root.findNavController().navigate(action) - return true - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/LicenseAdapter.kt deleted file mode 100644 index dd06d2ee..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/LicenseAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import org.sudachi.sudachi_emu.databinding.ListItemSettingBinding -import org.sudachi.sudachi_emu.fragments.LicenseBottomSheetDialogFragment -import org.sudachi.sudachi_emu.model.License -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class LicenseAdapter(private val activity: AppCompatActivity, licenses: List) : - AbstractListAdapter(licenses) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder { - ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return LicenseViewHolder(it) } - } - - inner class LicenseViewHolder(val binding: ListItemSettingBinding) : - AbstractViewHolder(binding) { - override fun bind(model: License) { - binding.apply { - textSettingName.text = root.context.getString(model.titleId) - textSettingDescription.text = root.context.getString(model.descriptionId) - textSettingValue.setVisible(false) - - root.setOnClickListener { onClick(model) } - } - } - - private fun onClick(license: License) { - LicenseBottomSheetDialogFragment.newInstance(license) - .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/SetupAdapter.kt deleted file mode 100644 index 313ae214..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/adapters/SetupAdapter.kt +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.adapters - -import android.text.Html -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.res.ResourcesCompat -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.button.MaterialButton -import org.sudachi.sudachi_emu.databinding.PageSetupBinding -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.model.SetupCallback -import org.sudachi.sudachi_emu.model.SetupPage -import org.sudachi.sudachi_emu.model.StepState -import org.sudachi.sudachi_emu.utils.ViewUtils -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import org.sudachi.sudachi_emu.viewholder.AbstractViewHolder - -class SetupAdapter(val activity: AppCompatActivity, pages: List) : - AbstractListAdapter(pages) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder { - PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false) - .also { return SetupPageViewHolder(it) } - } - - inner class SetupPageViewHolder(val binding: PageSetupBinding) : - AbstractViewHolder(binding), SetupCallback { - override fun bind(model: SetupPage) { - if (model.stepCompleted.invoke() == StepState.COMPLETE) { - binding.buttonAction.setVisible(visible = false, gone = false) - binding.textConfirmation.setVisible(true) - } - - binding.icon.setImageDrawable( - ResourcesCompat.getDrawable( - activity.resources, - model.iconId, - activity.theme - ) - ) - binding.textTitle.text = activity.resources.getString(model.titleId) - binding.textDescription.text = - Html.fromHtml(activity.resources.getString(model.descriptionId), 0) - - binding.buttonAction.apply { - text = activity.resources.getString(model.buttonTextId) - if (model.buttonIconId != 0) { - icon = ResourcesCompat.getDrawable( - activity.resources, - model.buttonIconId, - activity.theme - ) - } - iconGravity = - if (model.leftAlignedIcon) { - MaterialButton.ICON_GRAVITY_START - } else { - MaterialButton.ICON_GRAVITY_END - } - setOnClickListener { - model.buttonAction.invoke(this@SetupPageViewHolder) - } - } - } - - override fun onStepCompleted() { - ViewUtils.hideView(binding.buttonAction, 200) - ViewUtils.showView(binding.textConfirmation, 200) - ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/disk_shader_cache/DiskShaderCacheProgress.kt deleted file mode 100644 index 580014d7..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/disk_shader_cache/DiskShaderCacheProgress.kt +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.disk_shader_cache - -import androidx.annotation.Keep -import androidx.lifecycle.ViewModelProvider -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.activities.EmulationActivity -import org.sudachi.sudachi_emu.model.EmulationViewModel -import org.sudachi.sudachi_emu.utils.Log - -@Keep -object DiskShaderCacheProgress { - private lateinit var emulationViewModel: EmulationViewModel - - private fun prepareViewModel() { - emulationViewModel = - ViewModelProvider( - NativeLibrary.sEmulationActivity.get() as EmulationActivity - )[EmulationViewModel::class.java] - } - - @JvmStatic - fun loadProgress(stage: Int, progress: Int, max: Int) { - val emulationActivity = NativeLibrary.sEmulationActivity.get() - if (emulationActivity == null) { - Log.error("[DiskShaderCacheProgress] EmulationActivity not present") - return - } - - emulationActivity.runOnUiThread { - when (LoadCallbackStage.values()[stage]) { - LoadCallbackStage.Prepare -> prepareViewModel() - LoadCallbackStage.Build -> emulationViewModel.updateProgress( - emulationActivity.getString(R.string.building_shaders), - progress, - max - ) - - LoadCallbackStage.Complete -> {} - } - } - } - - // Equivalent to VideoCore::LoadCallbackStage - enum class LoadCallbackStage { - Prepare, Build, Complete - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/NativeInput.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/NativeInput.kt deleted file mode 100644 index 52288d94..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/NativeInput.kt +++ /dev/null @@ -1,416 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input - -import org.sudachi.sudachi_emu.features.input.model.NativeButton -import org.sudachi.sudachi_emu.features.input.model.NativeAnalog -import org.sudachi.sudachi_emu.features.input.model.InputType -import org.sudachi.sudachi_emu.features.input.model.ButtonName -import org.sudachi.sudachi_emu.features.input.model.NpadStyleIndex -import org.sudachi.sudachi_emu.utils.NativeConfig -import org.sudachi.sudachi_emu.utils.ParamPackage -import android.view.InputDevice - -object NativeInput { - /** - * Default controller id for each device - */ - const val Player1Device = 0 - const val Player2Device = 1 - const val Player3Device = 2 - const val Player4Device = 3 - const val Player5Device = 4 - const val Player6Device = 5 - const val Player7Device = 6 - const val Player8Device = 7 - const val ConsoleDevice = 8 - - /** - * Button states - */ - object ButtonState { - const val RELEASED = 0 - const val PRESSED = 1 - } - - /** - * Returns true if pro controller isn't available and handheld is. - * Intended to check where the input overlay should direct its inputs. - */ - external fun isHandheldOnly(): Boolean - - /** - * Handles button press events for a gamepad. - * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. - * @param port Port determined by controller connection order. - * @param buttonId The Android Keycode corresponding to this event. - * @param action Mask identifying which action is happening (button pressed down, or button released). - */ - external fun onGamePadButtonEvent( - guid: String, - port: Int, - buttonId: Int, - action: Int - ) - - /** - * Handles axis movement events. - * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. - * @param port Port determined by controller connection order. - * @param axis The axis ID. - * @param value Value along the given axis. - */ - external fun onGamePadAxisEvent(guid: String, port: Int, axis: Int, value: Float) - - /** - * Handles motion events. - * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. - * @param port Port determined by controller connection order. - * @param deltaTimestamp The finger id corresponding to this event. - * @param xGyro The value of the x-axis for the gyroscope. - * @param yGyro The value of the y-axis for the gyroscope. - * @param zGyro The value of the z-axis for the gyroscope. - * @param xAccel The value of the x-axis for the accelerometer. - * @param yAccel The value of the y-axis for the accelerometer. - * @param zAccel The value of the z-axis for the accelerometer. - */ - external fun onGamePadMotionEvent( - guid: String, - port: Int, - deltaTimestamp: Long, - xGyro: Float, - yGyro: Float, - zGyro: Float, - xAccel: Float, - yAccel: Float, - zAccel: Float - ) - - /** - * Signals and load a nfc tag - * @param data Byte array containing all the data from a nfc tag. - */ - external fun onReadNfcTag(data: ByteArray?) - - /** - * Removes current loaded nfc tag. - */ - external fun onRemoveNfcTag() - - /** - * Handles touch press events. - * @param fingerId The finger id corresponding to this event. - * @param xAxis The value of the x-axis on the touchscreen. - * @param yAxis The value of the y-axis on the touchscreen. - */ - external fun onTouchPressed(fingerId: Int, xAxis: Float, yAxis: Float) - - /** - * Handles touch movement. - * @param fingerId The finger id corresponding to this event. - * @param xAxis The value of the x-axis on the touchscreen. - * @param yAxis The value of the y-axis on the touchscreen. - */ - external fun onTouchMoved(fingerId: Int, xAxis: Float, yAxis: Float) - - /** - * Handles touch release events. - * @param fingerId The finger id corresponding to this event - */ - external fun onTouchReleased(fingerId: Int) - - /** - * Sends a button input to the global virtual controllers. - * @param port Port determined by controller connection order. - * @param button The [NativeButton] corresponding to this event. - * @param action Mask identifying which action is happening (button pressed down, or button released). - */ - fun onOverlayButtonEvent(port: Int, button: NativeButton, action: Int) = - onOverlayButtonEventImpl(port, button.int, action) - - private external fun onOverlayButtonEventImpl(port: Int, buttonId: Int, action: Int) - - /** - * Sends a joystick input to the global virtual controllers. - * @param port Port determined by controller connection order. - * @param stick The [NativeAnalog] corresponding to this event. - * @param xAxis Value along the X axis. - * @param yAxis Value along the Y axis. - */ - fun onOverlayJoystickEvent(port: Int, stick: NativeAnalog, xAxis: Float, yAxis: Float) = - onOverlayJoystickEventImpl(port, stick.int, xAxis, yAxis) - - private external fun onOverlayJoystickEventImpl( - port: Int, - stickId: Int, - xAxis: Float, - yAxis: Float - ) - - /** - * Handles motion events for the global virtual controllers. - * @param port Port determined by controller connection order - * @param deltaTimestamp The finger id corresponding to this event. - * @param xGyro The value of the x-axis for the gyroscope. - * @param yGyro The value of the y-axis for the gyroscope. - * @param zGyro The value of the z-axis for the gyroscope. - * @param xAccel The value of the x-axis for the accelerometer. - * @param yAccel The value of the y-axis for the accelerometer. - * @param zAccel The value of the z-axis for the accelerometer. - */ - external fun onDeviceMotionEvent( - port: Int, - deltaTimestamp: Long, - xGyro: Float, - yGyro: Float, - zGyro: Float, - xAccel: Float, - yAccel: Float, - zAccel: Float - ) - - /** - * Reloads all input devices from the currently loaded Settings::values.players into HID Core - */ - external fun reloadInputDevices() - - /** - * Registers a controller to be used with mapping - * @param device An [InputDevice] or the input overlay wrapped with [SudachiInputDevice] - */ - external fun registerController(device: SudachiInputDevice) - - /** - * Gets the names of input devices that have been registered with the input subsystem via [registerController] - */ - external fun getInputDevices(): Array - - /** - * Reads all input profiles from disk. Must be called before creating a profile picker. - */ - external fun loadInputProfiles() - - /** - * Gets the names of each available input profile. - */ - external fun getInputProfileNames(): Array - - /** - * Checks if the user-provided name for an input profile is valid. - * @param name User-provided name for an input profile. - * @return Whether [name] is valid or not. - */ - external fun isProfileNameValid(name: String): Boolean - - /** - * Creates a new input profile. - * @param name The new profile's name. - * @param playerIndex Index of the player that's currently being edited. Used to write the profile - * name to this player's config. - * @return Whether creating the profile was successful or not. - */ - external fun createProfile(name: String, playerIndex: Int): Boolean - - /** - * Deletes an input profile. - * @param name Name of the profile to delete. - * @param playerIndex Index of the player that's currently being edited. Used to remove the profile - * name from this player's config if they have it loaded. - * @return Whether deleting this profile was successful or not. - */ - external fun deleteProfile(name: String, playerIndex: Int): Boolean - - /** - * Loads an input profile. - * @param name Name of the input profile to load. - * @param playerIndex Index of the player that will have this profile loaded. - * @return Whether loading this profile was successful or not. - */ - external fun loadProfile(name: String, playerIndex: Int): Boolean - - /** - * Saves an input profile. - * @param name Name of the profile to save. - * @param playerIndex Index of the player that's currently being edited. Used to write the profile - * name to this player's config. - * @return Whether saving the profile was successful or not. - */ - external fun saveProfile(name: String, playerIndex: Int): Boolean - - /** - * Intended to be used immediately before a call to [NativeConfig.saveControlPlayerValues] - * Must be used while per-game config is loaded. - */ - external fun loadPerGameConfiguration( - playerIndex: Int, - selectedIndex: Int, - selectedProfileName: String - ) - - /** - * Tells the input subsystem to start listening for inputs to map. - * @param type Type of input to map as shown by the int property in each [InputType]. - */ - external fun beginMapping(type: Int) - - /** - * Gets an input's [ParamPackage] as a serialized string. Used for input verification before mapping. - * Must be run after [beginMapping] and before [stopMapping]. - */ - external fun getNextInput(): String - - /** - * Tells the input subsystem to stop listening for inputs to map. - */ - external fun stopMapping() - - /** - * Updates a controller's mappings with auto-mapping params. - * @param playerIndex Index of the player to auto-map. - * @param deviceParams [ParamPackage] representing the device to auto-map as received - * from [getInputDevices]. - * @param displayName Name of the device to auto-map as received from the "display" param in [deviceParams]. - * Intended to be a way to provide a default name for a controller if the "display" param is empty. - */ - fun updateMappingsWithDefault( - playerIndex: Int, - deviceParams: ParamPackage, - displayName: String - ) = updateMappingsWithDefaultImpl(playerIndex, deviceParams.serialize(), displayName) - - private external fun updateMappingsWithDefaultImpl( - playerIndex: Int, - deviceParams: String, - displayName: String - ) - - /** - * Gets the params for a specific button. - * @param playerIndex Index of the player to get params from. - * @param button The [NativeButton] to get params for. - * @return A [ParamPackage] representing a player's specific button. - */ - fun getButtonParam(playerIndex: Int, button: NativeButton): ParamPackage = - ParamPackage(getButtonParamImpl(playerIndex, button.int)) - - private external fun getButtonParamImpl(playerIndex: Int, buttonId: Int): String - - /** - * Sets the params for a specific button. - * @param playerIndex Index of the player to set params for. - * @param button The [NativeButton] to set params for. - * @param param A [ParamPackage] to set. - */ - fun setButtonParam(playerIndex: Int, button: NativeButton, param: ParamPackage) = - setButtonParamImpl(playerIndex, button.int, param.serialize()) - - private external fun setButtonParamImpl(playerIndex: Int, buttonId: Int, param: String) - - /** - * Gets the params for a specific stick. - * @param playerIndex Index of the player to get params from. - * @param stick The [NativeAnalog] to get params for. - * @return A [ParamPackage] representing a player's specific stick. - */ - fun getStickParam(playerIndex: Int, stick: NativeAnalog): ParamPackage = - ParamPackage(getStickParamImpl(playerIndex, stick.int)) - - private external fun getStickParamImpl(playerIndex: Int, stickId: Int): String - - /** - * Sets the params for a specific stick. - * @param playerIndex Index of the player to set params for. - * @param stick The [NativeAnalog] to set params for. - * @param param A [ParamPackage] to set. - */ - fun setStickParam(playerIndex: Int, stick: NativeAnalog, param: ParamPackage) = - setStickParamImpl(playerIndex, stick.int, param.serialize()) - - private external fun setStickParamImpl(playerIndex: Int, stickId: Int, param: String) - - /** - * Gets the int representation of a [ButtonName]. Tells you what to show as the mapped input for - * a button/analog/other. - * @param param A [ParamPackage] that represents a specific button's params. - * @return The [ButtonName] for [param]. - */ - fun getButtonName(param: ParamPackage): ButtonName = - ButtonName.from(getButtonNameImpl(param.serialize())) - - private external fun getButtonNameImpl(param: String): Int - - /** - * Gets each supported [NpadStyleIndex] for a given player. - * @param playerIndex Index of the player to get supported indexes for. - * @return List of each supported [NpadStyleIndex]. - */ - fun getSupportedStyleTags(playerIndex: Int): List = - getSupportedStyleTagsImpl(playerIndex).map { NpadStyleIndex.from(it) } - - private external fun getSupportedStyleTagsImpl(playerIndex: Int): IntArray - - /** - * Gets the [NpadStyleIndex] for a given player. - * @param playerIndex Index of the player to get an [NpadStyleIndex] from. - * @return The [NpadStyleIndex] for a given player. - */ - fun getStyleIndex(playerIndex: Int): NpadStyleIndex = - NpadStyleIndex.from(getStyleIndexImpl(playerIndex)) - - private external fun getStyleIndexImpl(playerIndex: Int): Int - - /** - * Sets the [NpadStyleIndex] for a given player. - * @param playerIndex Index of the player to change. - * @param style The new style to set. - */ - fun setStyleIndex(playerIndex: Int, style: NpadStyleIndex) = - setStyleIndexImpl(playerIndex, style.int) - - private external fun setStyleIndexImpl(playerIndex: Int, styleIndex: Int) - - /** - * Checks if a device is a controller. - * @param params [ParamPackage] for an input device retrieved from [getInputDevices] - * @return Whether the device is a controller or not. - */ - fun isController(params: ParamPackage): Boolean = isControllerImpl(params.serialize()) - - private external fun isControllerImpl(params: String): Boolean - - /** - * Checks if a controller is connected - * @param playerIndex Index of the player to check. - * @return Whether the player is connected or not. - */ - external fun getIsConnected(playerIndex: Int): Boolean - - /** - * Connects/disconnects a controller and ensures that connection order stays in-tact. - * @param playerIndex Index of the player to connect/disconnect. - * @param connected Whether to connect or disconnect this controller. - */ - fun connectControllers(playerIndex: Int, connected: Boolean = true) { - val connectedControllers = mutableListOf().apply { - if (connected) { - for (i in 0 until 8) { - add(i <= playerIndex) - } - } else { - for (i in 0 until 8) { - add(i < playerIndex) - } - } - } - connectControllersImpl(connectedControllers.toBooleanArray()) - } - - private external fun connectControllersImpl(connected: BooleanArray) - - /** - * Resets all of the button and analog mappings for a player. - * @param playerIndex Index of the player that will have its mappings reset. - */ - external fun resetControllerMappings(playerIndex: Int) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/SudachiVibrator.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/SudachiVibrator.kt deleted file mode 100644 index 4f50bb09..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/SudachiVibrator.kt +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input - -import android.content.Context -import android.os.Build -import android.os.CombinedVibration -import android.os.VibrationEffect -import android.os.Vibrator -import android.os.VibratorManager -import android.view.InputDevice -import androidx.annotation.Keep -import androidx.annotation.RequiresApi -import org.sudachi.sudachi_emu.SudachiApplication - -@Keep -@Suppress("DEPRECATION") -interface SudachiVibrator { - fun supportsVibration(): Boolean - - fun vibrate(intensity: Float) - - companion object { - fun getControllerVibrator(device: InputDevice): SudachiVibrator = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - SudachiVibratorManager(device.vibratorManager) - } else { - SudachiVibratorManagerCompat(device.vibrator) - } - - fun getSystemVibrator(): SudachiVibrator = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val vibratorManager = SudachiApplication.appContext - .getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager - SudachiVibratorManager(vibratorManager) - } else { - val vibrator = SudachiApplication.appContext - .getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - SudachiVibratorManagerCompat(vibrator) - } - - fun getVibrationEffect(intensity: Float): VibrationEffect? { - if (intensity > 0f) { - return VibrationEffect.createOneShot( - 50, - (255.0 * intensity).toInt().coerceIn(1, 255) - ) - } - return null - } - } -} - -@RequiresApi(Build.VERSION_CODES.S) -class SudachiVibratorManager(private val vibratorManager: VibratorManager) : SudachiVibrator { - override fun supportsVibration(): Boolean { - return vibratorManager.vibratorIds.isNotEmpty() - } - - override fun vibrate(intensity: Float) { - val vibration = SudachiVibrator.getVibrationEffect(intensity) ?: return - vibratorManager.vibrate(CombinedVibration.createParallel(vibration)) - } -} - -class SudachiVibratorManagerCompat(private val vibrator: Vibrator) : SudachiVibrator { - override fun supportsVibration(): Boolean { - return vibrator.hasVibrator() - } - - override fun vibrate(intensity: Float) { - val vibration = SudachiVibrator.getVibrationEffect(intensity) ?: return - vibrator.vibrate(vibration) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/AnalogDirection.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/AnalogDirection.kt deleted file mode 100644 index 45d92d10..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/AnalogDirection.kt +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input.model - -enum class AnalogDirection(val int: Int, val param: String) { - Up(0, "up"), - Down(1, "down"), - Left(2, "left"), - Right(3, "right") -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/ButtonName.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/ButtonName.kt deleted file mode 100644 index 2d8ff80d..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/ButtonName.kt +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input.model - -// Loosely matches the enum in common/input.h -enum class ButtonName(val int: Int) { - Invalid(1), - - // This will display the engine name instead of the button name - Engine(2), - - // This will display the button by value instead of the button name - Value(3); - - companion object { - fun from(int: Int): ButtonName = entries.firstOrNull { it.int == int } ?: Invalid - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NativeAnalog.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NativeAnalog.kt deleted file mode 100644 index 88ef7927..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NativeAnalog.kt +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input.model - -// Must match enum in src/common/settings_input.h -enum class NativeAnalog(val int: Int) { - LStick(0), - RStick(1); - - companion object { - fun from(int: Int): NativeAnalog = entries.firstOrNull { it.int == int } ?: LStick - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NpadStyleIndex.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NpadStyleIndex.kt deleted file mode 100644 index 4d938fa4..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/NpadStyleIndex.kt +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input.model - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.R - -// Must match enum in src/core/hid/hid_types.h -enum class NpadStyleIndex(val int: Int, @StringRes val nameId: Int = 0) { - None(0), - Fullkey(3, R.string.pro_controller), - Handheld(4, R.string.handheld), - HandheldNES(4), - JoyconDual(5, R.string.dual_joycons), - JoyconLeft(6, R.string.left_joycon), - JoyconRight(7, R.string.right_joycon), - GameCube(8, R.string.gamecube_controller), - Pokeball(9), - NES(10), - SNES(12), - N64(13), - SegaGenesis(14), - SystemExt(32), - System(33); - - companion object { - fun from(int: Int): NpadStyleIndex = entries.firstOrNull { it.int == int } ?: None - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/PlayerInput.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/PlayerInput.kt deleted file mode 100644 index bd1328c3..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/input/model/PlayerInput.kt +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.input.model - -import androidx.annotation.Keep - -@Keep -data class PlayerInput( - var connected: Boolean, - var buttons: Array, - var analogs: Array, - var motions: Array, - - var vibrationEnabled: Boolean, - var vibrationStrength: Int, - - var bodyColorLeft: Long, - var bodyColorRight: Long, - var buttonColorLeft: Long, - var buttonColorRight: Long, - var profileName: String, - - var useSystemVibrator: Boolean -) { - // It's recommended to use the generated equals() and hashCode() methods - // when using arrays in a data class - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PlayerInput - - if (connected != other.connected) return false - if (!buttons.contentEquals(other.buttons)) return false - if (!analogs.contentEquals(other.analogs)) return false - if (!motions.contentEquals(other.motions)) return false - if (vibrationEnabled != other.vibrationEnabled) return false - if (vibrationStrength != other.vibrationStrength) return false - if (bodyColorLeft != other.bodyColorLeft) return false - if (bodyColorRight != other.bodyColorRight) return false - if (buttonColorLeft != other.buttonColorLeft) return false - if (buttonColorRight != other.buttonColorRight) return false - if (profileName != other.profileName) return false - return useSystemVibrator == other.useSystemVibrator - } - - override fun hashCode(): Int { - var result = connected.hashCode() - result = 31 * result + buttons.contentHashCode() - result = 31 * result + analogs.contentHashCode() - result = 31 * result + motions.contentHashCode() - result = 31 * result + vibrationEnabled.hashCode() - result = 31 * result + vibrationStrength - result = 31 * result + bodyColorLeft.hashCode() - result = 31 * result + bodyColorRight.hashCode() - result = 31 * result + buttonColorLeft.hashCode() - result = 31 * result + buttonColorRight.hashCode() - result = 31 * result + profileName.hashCode() - result = 31 * result + useSystemVibrator.hashCode() - return result - } - - fun hasMapping(): Boolean { - var hasMapping = false - buttons.forEach { - if (it != "[empty]" && it.isNotEmpty()) { - hasMapping = true - } - } - analogs.forEach { - if (it != "[empty]" && it.isNotEmpty()) { - hasMapping = true - } - } - motions.forEach { - if (it != "[empty]" && it.isNotEmpty()) { - hasMapping = true - } - } - return hasMapping - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractFloatSetting.kt deleted file mode 100644 index 7dd7c0f6..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractFloatSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -interface AbstractFloatSetting : AbstractSetting { - fun getFloat(needsGlobal: Boolean = false): Float - fun setFloat(value: Float) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractIntSetting.kt deleted file mode 100644 index bc01b750..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractIntSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -interface AbstractIntSetting : AbstractSetting { - fun getInt(needsGlobal: Boolean = false): Int - fun setInt(value: Int) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractStringSetting.kt deleted file mode 100644 index 88213f77..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/AbstractStringSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -interface AbstractStringSetting : AbstractSetting { - fun getString(needsGlobal: Boolean = false): String - fun setString(value: String) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/ByteSetting.kt deleted file mode 100644 index 49b94595..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/ByteSetting.kt +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -import org.sudachi.sudachi_emu.utils.NativeConfig - -enum class ByteSetting(override val key: String) : AbstractByteSetting { - AUDIO_VOLUME("volume"); - - override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal) - - override fun setByte(value: Byte) { - if (NativeConfig.isPerGameConfigLoaded()) { - global = false - } - NativeConfig.setByte(key, value) - } - - override val defaultValue: Byte by lazy { NativeConfig.getDefaultToString(key).toByte() } - - override fun getValueAsString(needsGlobal: Boolean): String = getByte(needsGlobal).toString() - - override fun reset() = NativeConfig.setByte(key, defaultValue) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/FloatSetting.kt deleted file mode 100644 index e7e1c291..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/FloatSetting.kt +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -import org.sudachi.sudachi_emu.utils.NativeConfig - -enum class FloatSetting(override val key: String) : AbstractFloatSetting { - // No float settings currently exist - EMPTY_SETTING(""); - - override fun getFloat(needsGlobal: Boolean): Float = NativeConfig.getFloat(key, false) - - override fun setFloat(value: Float) { - if (NativeConfig.isPerGameConfigLoaded()) { - global = false - } - NativeConfig.setFloat(key, value) - } - - override val defaultValue: Float by lazy { NativeConfig.getDefaultToString(key).toFloat() } - - override fun getValueAsString(needsGlobal: Boolean): String = getFloat(needsGlobal).toString() - - override fun reset() = NativeConfig.setFloat(key, defaultValue) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/LongSetting.kt deleted file mode 100644 index 677d0e98..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/LongSetting.kt +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -import org.sudachi.sudachi_emu.utils.NativeConfig - -enum class LongSetting(override val key: String) : AbstractLongSetting { - CUSTOM_RTC("custom_rtc"); - - override fun getLong(needsGlobal: Boolean): Long = NativeConfig.getLong(key, needsGlobal) - - override fun setLong(value: Long) { - if (NativeConfig.isPerGameConfigLoaded()) { - global = false - } - NativeConfig.setLong(key, value) - } - - override val defaultValue: Long by lazy { NativeConfig.getDefaultToString(key).toLong() } - - override fun getValueAsString(needsGlobal: Boolean): String = getLong(needsGlobal).toString() - - override fun reset() = NativeConfig.setLong(key, defaultValue) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/Settings.kt deleted file mode 100644 index 4965f732..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/Settings.kt +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication - -object Settings { - enum class MenuTag(val titleId: Int = 0) { - SECTION_ROOT(R.string.advanced_settings), - SECTION_SYSTEM(R.string.preferences_system), - SECTION_RENDERER(R.string.preferences_graphics), - SECTION_AUDIO(R.string.preferences_audio), - SECTION_INPUT(R.string.preferences_controls), - SECTION_INPUT_PLAYER_ONE, - SECTION_INPUT_PLAYER_TWO, - SECTION_INPUT_PLAYER_THREE, - SECTION_INPUT_PLAYER_FOUR, - SECTION_INPUT_PLAYER_FIVE, - SECTION_INPUT_PLAYER_SIX, - SECTION_INPUT_PLAYER_SEVEN, - SECTION_INPUT_PLAYER_EIGHT, - SECTION_THEME(R.string.preferences_theme), - SECTION_DEBUG(R.string.preferences_debug); - } - - fun getPlayerString(player: Int): String = - SudachiApplication.appContext.getString(R.string.preferences_player, player) - - const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" - const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" - - // Deprecated input overlay preference keys - 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" - val overlayPreferences = listOf( - 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 - ) - - // Deprecated layout preference keys - const val PREF_LANDSCAPE_SUFFIX = "_Landscape" - const val PREF_PORTRAIT_SUFFIX = "_Portrait" - const val PREF_FOLDABLE_SUFFIX = "_Foldable" - val overlayLayoutSuffixes = listOf( - PREF_LANDSCAPE_SUFFIX, - PREF_PORTRAIT_SUFFIX, - PREF_FOLDABLE_SUFFIX - ) - - // Deprecated theme preference keys - const val PREF_THEME = "Theme" - const val PREF_THEME_MODE = "ThemeMode" - const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" - - enum class EmulationOrientation(val int: Int) { - Unspecified(0), - SensorLandscape(5), - Landscape(1), - ReverseLandscape(2), - SensorPortrait(6), - Portrait(4), - ReversePortrait(3); - - companion object { - fun from(int: Int): EmulationOrientation = - entries.firstOrNull { it.int == int } ?: Unspecified - } - } - - enum class EmulationVerticalAlignment(val int: Int) { - Top(1), - Center(0), - Bottom(2); - - companion object { - fun from(int: Int): EmulationVerticalAlignment = - entries.firstOrNull { it.int == int } ?: Center - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/StringSetting.kt deleted file mode 100644 index 29aea99c..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/StringSetting.kt +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model - -import org.sudachi.sudachi_emu.utils.NativeConfig - -enum class StringSetting(override val key: String) : AbstractStringSetting { - DRIVER_PATH("driver_path"), - DEVICE_NAME("device_name"); - - override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal) - - override fun setString(value: String) { - if (NativeConfig.isPerGameConfigLoaded()) { - global = false - } - NativeConfig.setString(key, value) - } - - override val defaultValue: String by lazy { NativeConfig.getDefaultToString(key) } - - override fun getValueAsString(needsGlobal: Boolean): String = getString(needsGlobal) - - override fun reset() = NativeConfig.setString(key, defaultValue) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/AnalogInputSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/AnalogInputSetting.kt deleted file mode 100644 index 1e5b91d2..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/AnalogInputSetting.kt +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.model.AnalogDirection -import org.sudachi.sudachi_emu.features.input.model.InputType -import org.sudachi.sudachi_emu.features.input.model.NativeAnalog -import org.sudachi.sudachi_emu.utils.ParamPackage - -class AnalogInputSetting( - override val playerIndex: Int, - val nativeAnalog: NativeAnalog, - val analogDirection: AnalogDirection, - @StringRes titleId: Int = 0, - titleString: String = "" -) : InputSetting(titleId, titleString) { - override val type = TYPE_INPUT - override val inputType = InputType.Stick - - override fun getSelectedValue(): String { - val params = NativeInput.getStickParam(playerIndex, nativeAnalog) - val analog = analogToText(params, analogDirection.param) - return getDisplayString(params, analog) - } - - override fun setSelectedValue(param: ParamPackage) = - NativeInput.setStickParam(playerIndex, nativeAnalog, param) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/DateTimeSetting.kt deleted file mode 100644 index 5a533717..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/DateTimeSetting.kt +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.settings.model.AbstractLongSetting - -class DateTimeSetting( - private val longSetting: AbstractLongSetting, - @StringRes titleId: Int = 0, - titleString: String = "", - @StringRes descriptionId: Int = 0, - descriptionString: String = "" -) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) { - override val type = TYPE_DATETIME_SETTING - - fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal) - fun setValue(value: Long) = (setting as AbstractLongSetting).setLong(value) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/InputSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/InputSetting.kt deleted file mode 100644 index 094a00d1..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/InputSetting.kt +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.model.ButtonName -import org.sudachi.sudachi_emu.features.input.model.InputType -import org.sudachi.sudachi_emu.utils.ParamPackage - -sealed class InputSetting( - @StringRes titleId: Int, - titleString: String -) : SettingsItem(emptySetting, titleId, titleString, 0, "") { - override val type = TYPE_INPUT - abstract val inputType: InputType - abstract val playerIndex: Int - - protected val context get() = SudachiApplication.appContext - - abstract fun getSelectedValue(): String - - abstract fun setSelectedValue(param: ParamPackage) - - protected fun getDisplayString(params: ParamPackage, control: String): String { - val deviceName = params.get("display", "") - deviceName.ifEmpty { - return context.getString(R.string.not_set) - } - return "$deviceName: $control" - } - - private fun getDirectionName(direction: String): String = - when (direction) { - "up" -> context.getString(R.string.up) - "down" -> context.getString(R.string.down) - "left" -> context.getString(R.string.left) - "right" -> context.getString(R.string.right) - else -> direction - } - - protected fun buttonToText(param: ParamPackage): String { - if (!param.has("engine")) { - return context.getString(R.string.not_set) - } - - val toggle = if (param.get("toggle", false)) "~" else "" - val inverted = if (param.get("inverted", false)) "!" else "" - val invert = if (param.get("invert", "+") == "-") "-" else "" - val turbo = if (param.get("turbo", false)) "$" else "" - val commonButtonName = NativeInput.getButtonName(param) - - if (commonButtonName == ButtonName.Invalid) { - return context.getString(R.string.invalid) - } - - if (commonButtonName == ButtonName.Engine) { - return param.get("engine", "") - } - - if (commonButtonName == ButtonName.Value) { - if (param.has("hat")) { - val hat = getDirectionName(param.get("direction", "")) - return context.getString(R.string.qualified_hat, turbo, toggle, inverted, hat) - } - if (param.has("axis")) { - val axis = param.get("axis", "") - return context.getString( - R.string.qualified_button_stick_axis, - toggle, - inverted, - invert, - axis - ) - } - if (param.has("button")) { - val button = param.get("button", "") - return context.getString(R.string.qualified_button, turbo, toggle, inverted, button) - } - } - - return context.getString(R.string.unknown) - } - - protected fun analogToText(param: ParamPackage, direction: String): String { - if (!param.has("engine")) { - return context.getString(R.string.not_set) - } - - if (param.get("engine", "") == "analog_from_button") { - return buttonToText(ParamPackage(param.get(direction, ""))) - } - - if (!param.has("axis_x") || !param.has("axis_y")) { - return context.getString(R.string.unknown) - } - - val xAxis = param.get("axis_x", "") - val yAxis = param.get("axis_y", "") - val xInvert = param.get("invert_x", "+") == "-" - val yInvert = param.get("invert_y", "+") == "-" - - if (direction == "modifier") { - return context.getString(R.string.unused) - } - - when (direction) { - "up" -> { - val yInvertString = if (yInvert) "+" else "-" - return context.getString(R.string.qualified_axis, yAxis, yInvertString) - } - - "down" -> { - val yInvertString = if (yInvert) "-" else "+" - return context.getString(R.string.qualified_axis, yAxis, yInvertString) - } - - "left" -> { - val xInvertString = if (xInvert) "+" else "-" - return context.getString(R.string.qualified_axis, xAxis, xInvertString) - } - - "right" -> { - val xInvertString = if (xInvert) "-" else "+" - return context.getString(R.string.qualified_axis, xAxis, xInvertString) - } - } - - return context.getString(R.string.unknown) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/ModifierInputSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/ModifierInputSetting.kt deleted file mode 100644 index 3b77c6a0..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/ModifierInputSetting.kt +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.model.InputType -import org.sudachi.sudachi_emu.features.input.model.NativeAnalog -import org.sudachi.sudachi_emu.utils.ParamPackage - -class ModifierInputSetting( - override val playerIndex: Int, - val nativeAnalog: NativeAnalog, - @StringRes titleId: Int = 0, - titleString: String = "" -) : InputSetting(titleId, titleString) { - override val inputType = InputType.Button - - override fun getSelectedValue(): String { - val analogParam = NativeInput.getStickParam(playerIndex, nativeAnalog) - val modifierParam = ParamPackage(analogParam.get("modifier", "")) - return buttonToText(modifierParam) - } - - override fun setSelectedValue(param: ParamPackage) { - val newParam = NativeInput.getStickParam(playerIndex, nativeAnalog) - newParam.set("modifier", param.serialize()) - NativeInput.setStickParam(playerIndex, nativeAnalog, newParam) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SingleChoiceSetting.kt deleted file mode 100644 index 946f6834..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SingleChoiceSetting.kt +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.ArrayRes -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.settings.model.AbstractIntSetting -import org.sudachi.sudachi_emu.features.settings.model.AbstractSetting - -class SingleChoiceSetting( - setting: AbstractSetting, - @StringRes titleId: Int = 0, - titleString: String = "", - @StringRes descriptionId: Int = 0, - descriptionString: String = "", - @ArrayRes val choicesId: Int, - @ArrayRes val valuesId: Int -) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { - override val type = TYPE_SINGLE_CHOICE - - fun getSelectedValue(needsGlobal: Boolean = false) = - when (setting) { - is AbstractIntSetting -> setting.getInt(needsGlobal) - else -> -1 - } - - fun setSelectedValue(value: Int) = (setting as AbstractIntSetting).setInt(value) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/StringInputSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/StringInputSetting.kt deleted file mode 100644 index f4882903..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/StringInputSetting.kt +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.settings.model.AbstractStringSetting - -class StringInputSetting( - setting: AbstractStringSetting, - @StringRes titleId: Int = 0, - titleString: String = "", - @StringRes descriptionId: Int = 0, - descriptionString: String = "" -) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { - override val type = TYPE_STRING_INPUT - - fun getSelectedValue(needsGlobal: Boolean = false) = setting.getValueAsString(needsGlobal) - - fun setSelectedValue(selection: String) = - (setting as AbstractStringSetting).setString(selection) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SwitchSetting.kt deleted file mode 100644 index 897935e3..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/model/view/SwitchSetting.kt +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.model.view - -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.features.settings.model.AbstractBooleanSetting -import org.sudachi.sudachi_emu.features.settings.model.AbstractIntSetting -import org.sudachi.sudachi_emu.features.settings.model.AbstractSetting - -class SwitchSetting( - setting: AbstractSetting, - @StringRes titleId: Int = 0, - titleString: String = "", - @StringRes descriptionId: Int = 0, - descriptionString: String = "" -) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { - override val type = TYPE_SWITCH - - fun getIsChecked(needsGlobal: Boolean = false): Boolean { - return when (setting) { - is AbstractIntSetting -> setting.getInt(needsGlobal) == 1 - is AbstractBooleanSetting -> setting.getBoolean(needsGlobal) - else -> false - } - } - - fun setChecked(value: Boolean) { - when (setting) { - is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) - is AbstractBooleanSetting -> setting.setBoolean(value) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/InputDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/InputDialogFragment.kt deleted file mode 100644 index 91fc566a..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/InputDialogFragment.kt +++ /dev/null @@ -1,300 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui - -import android.app.Dialog -import android.graphics.drawable.Animatable2 -import android.graphics.drawable.AnimatedVectorDrawable -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.view.InputDevice -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.DialogMappingBinding -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.model.NativeAnalog -import org.sudachi.sudachi_emu.features.input.model.NativeButton -import org.sudachi.sudachi_emu.features.settings.model.view.AnalogInputSetting -import org.sudachi.sudachi_emu.features.settings.model.view.ButtonInputSetting -import org.sudachi.sudachi_emu.features.settings.model.view.InputSetting -import org.sudachi.sudachi_emu.features.settings.model.view.ModifierInputSetting -import org.sudachi.sudachi_emu.utils.InputHandler -import org.sudachi.sudachi_emu.utils.ParamPackage - -class InputDialogFragment : DialogFragment() { - private var inputAccepted = false - - private var position: Int = 0 - - private lateinit var inputSetting: InputSetting - - private lateinit var binding: DialogMappingBinding - - private val settingsViewModel: SettingsViewModel by activityViewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (settingsViewModel.clickedItem == null) dismiss() - - position = requireArguments().getInt(POSITION) - - InputHandler.updateControllerData() - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - inputSetting = settingsViewModel.clickedItem as InputSetting - binding = DialogMappingBinding.inflate(layoutInflater) - - val builder = MaterialAlertDialogBuilder(requireContext()) - .setPositiveButton(android.R.string.cancel) { _, _ -> - NativeInput.stopMapping() - dismiss() - } - .setView(binding.root) - - val playButtonMapAnimation = { twoDirections: Boolean -> - val stickAnimation: AnimatedVectorDrawable - val buttonAnimation: AnimatedVectorDrawable - binding.imageStickAnimation.apply { - val anim = if (twoDirections) { - R.drawable.stick_two_direction_anim - } else { - R.drawable.stick_one_direction_anim - } - setBackgroundResource(anim) - stickAnimation = background as AnimatedVectorDrawable - } - binding.imageButtonAnimation.apply { - setBackgroundResource(R.drawable.button_anim) - buttonAnimation = background as AnimatedVectorDrawable - } - stickAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() { - override fun onAnimationEnd(drawable: Drawable?) { - buttonAnimation.start() - } - }) - buttonAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() { - override fun onAnimationEnd(drawable: Drawable?) { - stickAnimation.start() - } - }) - stickAnimation.start() - } - - when (val setting = inputSetting) { - is AnalogInputSetting -> { - when (setting.nativeAnalog) { - NativeAnalog.LStick -> builder.setTitle( - getString(R.string.map_control, getString(R.string.left_stick)) - ) - - NativeAnalog.RStick -> builder.setTitle( - getString(R.string.map_control, getString(R.string.right_stick)) - ) - } - - builder.setMessage(R.string.stick_map_description) - - playButtonMapAnimation.invoke(true) - } - - is ModifierInputSetting -> { - builder.setTitle(getString(R.string.map_control, setting.title)) - .setMessage(R.string.button_map_description) - playButtonMapAnimation.invoke(false) - } - - is ButtonInputSetting -> { - if (setting.nativeButton == NativeButton.DUp || - setting.nativeButton == NativeButton.DDown || - setting.nativeButton == NativeButton.DLeft || - setting.nativeButton == NativeButton.DRight - ) { - builder.setTitle(getString(R.string.map_dpad_direction, setting.title)) - } else { - builder.setTitle(getString(R.string.map_control, setting.title)) - } - builder.setMessage(R.string.button_map_description) - playButtonMapAnimation.invoke(false) - } - } - - return builder.create() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.requestFocus() - view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() } - dialog?.setOnKeyListener { _, _, keyEvent -> onKeyEvent(keyEvent) } - binding.root.setOnGenericMotionListener { _, motionEvent -> onMotionEvent(motionEvent) } - NativeInput.beginMapping(inputSetting.inputType.int) - } - - private fun onKeyEvent(event: KeyEvent): Boolean { - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD - ) { - return false - } - - val action = when (event.action) { - KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED - KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED - else -> return false - } - val controllerData = - InputHandler.androidControllers[event.device.controllerNumber] ?: return false - NativeInput.onGamePadButtonEvent( - controllerData.getGUID(), - controllerData.getPort(), - event.keyCode, - action - ) - onInputReceived(event.device) - return true - } - - private fun onMotionEvent(event: MotionEvent): Boolean { - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD - ) { - return false - } - - // Temp workaround for DPads that give both axis and button input. The input system can't - // take in a specific axis direction for a binding so you lose half of the directions for a DPad. - - val controllerData = - InputHandler.androidControllers[event.device.controllerNumber] ?: return false - event.device.motionRanges.forEach { - NativeInput.onGamePadAxisEvent( - controllerData.getGUID(), - controllerData.getPort(), - it.axis, - event.getAxisValue(it.axis) - ) - onInputReceived(event.device) - } - return true - } - - private fun onInputReceived(device: InputDevice) { - val params = ParamPackage(NativeInput.getNextInput()) - if (params.has("engine") && isInputAcceptable(params) && !inputAccepted) { - inputAccepted = true - setResult(params, device) - } - } - - private fun setResult(params: ParamPackage, device: InputDevice) { - NativeInput.stopMapping() - params.set("display", "${device.name} ${params.get("port", 0)}") - when (val item = settingsViewModel.clickedItem as InputSetting) { - is ModifierInputSetting, - is ButtonInputSetting -> { - // Invert DPad up and left bindings by default - val tempSetting = inputSetting as? ButtonInputSetting - if (tempSetting != null) { - if (tempSetting.nativeButton == NativeButton.DUp || - tempSetting.nativeButton == NativeButton.DLeft && - params.has("axis") - ) { - params.set("invert", "-") - } - } - - item.setSelectedValue(params) - settingsViewModel.setAdapterItemChanged(position) - } - - is AnalogInputSetting -> { - var analogParam = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) - analogParam = adjustAnalogParam(params, analogParam, item.analogDirection.param) - - // Invert Y-Axis by default - analogParam.set("invert_y", "-") - - item.setSelectedValue(analogParam) - settingsViewModel.setReloadListAndNotifyDataset(true) - } - } - dismiss() - } - - private fun adjustAnalogParam( - inputParam: ParamPackage, - analogParam: ParamPackage, - buttonName: String - ): ParamPackage { - // The poller returned a complete axis, so set all the buttons - if (inputParam.has("axis_x") && inputParam.has("axis_y")) { - return inputParam - } - - // Check if the current configuration has either no engine or an axis binding. - // Clears out the old binding and adds one with analog_from_button. - if (!analogParam.has("engine") || analogParam.has("axis_x") || analogParam.has("axis_y")) { - analogParam.clear() - analogParam.set("engine", "analog_from_button") - } - analogParam.set(buttonName, inputParam.serialize()) - return analogParam - } - - private fun isInputAcceptable(params: ParamPackage): Boolean { - if (InputHandler.registeredControllers.size == 1) { - return true - } - - if (params.has("motion")) { - return true - } - - val currentDevice = settingsViewModel.getCurrentDeviceParams(params) - if (currentDevice.get("engine", "any") == "any") { - return true - } - - val guidMatch = params.get("guid", "") == currentDevice.get("guid", "") || - params.get("guid", "") == currentDevice.get("guid2", "") - return params.get("engine", "") == currentDevice.get("engine", "") && - guidMatch && - params.get("port", 0) == currentDevice.get("port", 0) - } - - companion object { - const val TAG = "InputDialogFragment" - - const val POSITION = "Position" - - fun newInstance( - inputMappingViewModel: SettingsViewModel, - setting: InputSetting, - position: Int - ): InputDialogFragment { - inputMappingViewModel.clickedItem = setting - val args = Bundle() - args.putInt(POSITION, position) - val fragment = InputDialogFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsActivity.kt deleted file mode 100644 index 84f6bd0a..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsActivity.kt +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui - -import android.os.Bundle -import android.view.View -import android.view.ViewGroup.MarginLayoutParams -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.navArgs -import com.google.android.material.color.MaterialColors -import org.sudachi.sudachi_emu.NativeLibrary -import java.io.IOException -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.ActivitySettingsBinding -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.settings.utils.SettingsFile -import org.sudachi.sudachi_emu.fragments.ResetSettingsDialogFragment -import org.sudachi.sudachi_emu.utils.* - -class SettingsActivity : AppCompatActivity() { - private lateinit var binding: ActivitySettingsBinding - - private val args by navArgs() - - private val settingsViewModel: SettingsViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - ThemeHelper.setTheme(this) - - super.onCreate(savedInstanceState) - - binding = ActivitySettingsBinding.inflate(layoutInflater) - setContentView(binding.root) - - if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) { - SettingsFile.loadCustomConfig(args.game!!) - } - settingsViewModel.game = args.game - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment - navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras) - - WindowCompat.setDecorFitsSystemWindows(window, false) - - if (InsetsHelper.getSystemGestureType(applicationContext) != - InsetsHelper.GESTURE_NAVIGATION - ) { - binding.navigationBarShade.setBackgroundColor( - ThemeHelper.getColorWithOpacity( - MaterialColors.getColor( - binding.navigationBarShade, - com.google.android.material.R.attr.colorSurface - ), - ThemeHelper.SYSTEM_BAR_ALPHA - ) - ) - } - - settingsViewModel.shouldRecreate.collect( - this, - resetState = { settingsViewModel.setShouldRecreate(false) } - ) { if (it) recreate() } - settingsViewModel.shouldNavigateBack.collect( - this, - resetState = { settingsViewModel.setShouldNavigateBack(false) } - ) { if (it) navigateBack() } - settingsViewModel.shouldShowResetSettingsDialog.collect( - this, - resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) } - ) { - if (it) { - ResetSettingsDialogFragment().show( - supportFragmentManager, - ResetSettingsDialogFragment.TAG - ) - } - } - - onBackPressedDispatcher.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() = navigateBack() - } - ) - - setInsets() - } - - fun navigateBack() { - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment - if (navHostFragment.childFragmentManager.backStackEntryCount > 0) { - navHostFragment.navController.popBackStack() - } else { - finish() - } - } - - override fun onStart() { - super.onStart() - if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start() - } - } - - override fun onStop() { - super.onStop() - Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...") - if (isFinishing) { - NativeInput.reloadInputDevices() - NativeLibrary.applySettings() - if (args.game == null) { - NativeConfig.saveGlobalConfig() - } else if (NativeConfig.isPerGameConfigLoaded()) { - NativeLibrary.logSettings() - NativeConfig.savePerGameConfig() - NativeConfig.unloadPerGameConfig() - } - } - } - - fun onSettingsReset() { - // Delete settings file because the user may have changed values that do not exist in the UI - if (args.game == null) { - NativeConfig.unloadGlobalConfig() - val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) - if (!settingsFile.delete()) { - throw IOException("Failed to delete $settingsFile") - } - NativeConfig.initializeGlobalConfig() - } else { - NativeConfig.unloadPerGameConfig() - val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!) - if (!settingsFile.delete()) { - throw IOException("Failed to delete $settingsFile") - } - } - - Toast.makeText( - applicationContext, - getString(R.string.settings_reset), - Toast.LENGTH_LONG - ).show() - finish() - } - - private fun setInsets() { - ViewCompat.setOnApplyWindowInsetsListener( - binding.navigationBarShade - ) { _: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - - // The only situation where we care to have a nav bar shade is when it's at the bottom - // of the screen where scrolling list elements can go behind it. - val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams - mlpNavShade.height = barInsets.bottom - binding.navigationBarShade.layoutParams = mlpNavShade - - windowInsets - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsFragmentPresenter.kt deleted file mode 100644 index 725fe74e..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ /dev/null @@ -1,975 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui - -import android.annotation.SuppressLint -import android.os.Build -import android.widget.Toast -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.model.AnalogDirection -import org.sudachi.sudachi_emu.features.input.model.NativeAnalog -import org.sudachi.sudachi_emu.features.input.model.NativeButton -import org.sudachi.sudachi_emu.features.input.model.NpadStyleIndex -import org.sudachi.sudachi_emu.features.settings.model.AbstractBooleanSetting -import org.sudachi.sudachi_emu.features.settings.model.AbstractIntSetting -import org.sudachi.sudachi_emu.features.settings.model.BooleanSetting -import org.sudachi.sudachi_emu.features.settings.model.ByteSetting -import org.sudachi.sudachi_emu.features.settings.model.IntSetting -import org.sudachi.sudachi_emu.features.settings.model.LongSetting -import org.sudachi.sudachi_emu.features.settings.model.Settings -import org.sudachi.sudachi_emu.features.settings.model.Settings.MenuTag -import org.sudachi.sudachi_emu.features.settings.model.ShortSetting -import org.sudachi.sudachi_emu.features.settings.model.StringSetting -import org.sudachi.sudachi_emu.features.settings.model.view.* -import org.sudachi.sudachi_emu.utils.InputHandler -import org.sudachi.sudachi_emu.utils.NativeConfig - -class SettingsFragmentPresenter( - private val settingsViewModel: SettingsViewModel, - private val adapter: SettingsAdapter, - private var menuTag: MenuTag -) { - private var settingsList = ArrayList() - - private val context get() = SudachiApplication.appContext - - // Extension for altering settings list based on each setting's properties - fun ArrayList.add(key: String) { - val item = SettingsItem.settingsItems[key]!! - if (settingsViewModel.game != null && !item.setting.isSwitchable) { - return - } - - if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) { - item.setting.global = true - } - - val pairedSettingKey = item.setting.pairedSettingKey - if (pairedSettingKey.isNotEmpty()) { - val pairedSettingValue = NativeConfig.getBoolean( - pairedSettingKey, - if (NativeLibrary.isRunning() && !NativeConfig.isPerGameConfigLoaded()) { - !NativeConfig.usingGlobal(pairedSettingKey) - } else { - NativeConfig.usingGlobal(pairedSettingKey) - } - ) - if (!pairedSettingValue) return - } - add(item) - } - - // Allows you to show/hide abstract settings based on the paired setting key - fun ArrayList.addAbstract(item: SettingsItem) { - val pairedSettingKey = item.setting.pairedSettingKey - if (pairedSettingKey.isNotEmpty()) { - val pairedSettingsItem = - this.firstOrNull { it.setting.key == pairedSettingKey } ?: return - val pairedSetting = pairedSettingsItem.setting as AbstractBooleanSetting - if (!pairedSetting.getBoolean(!NativeConfig.isPerGameConfigLoaded())) return - } - add(item) - } - - fun onViewCreated() { - loadSettingsList() - } - - @SuppressLint("NotifyDataSetChanged") - fun loadSettingsList(notifyDataSetChanged: Boolean = false) { - val sl = ArrayList() - when (menuTag) { - MenuTag.SECTION_ROOT -> addConfigSettings(sl) - MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) - MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) - MenuTag.SECTION_AUDIO -> addAudioSettings(sl) - MenuTag.SECTION_INPUT -> addInputSettings(sl) - MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0) - MenuTag.SECTION_INPUT_PLAYER_TWO -> addInputPlayer(sl, 1) - MenuTag.SECTION_INPUT_PLAYER_THREE -> addInputPlayer(sl, 2) - MenuTag.SECTION_INPUT_PLAYER_FOUR -> addInputPlayer(sl, 3) - MenuTag.SECTION_INPUT_PLAYER_FIVE -> addInputPlayer(sl, 4) - MenuTag.SECTION_INPUT_PLAYER_SIX -> addInputPlayer(sl, 5) - MenuTag.SECTION_INPUT_PLAYER_SEVEN -> addInputPlayer(sl, 6) - MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7) - MenuTag.SECTION_THEME -> addThemeSettings(sl) - MenuTag.SECTION_DEBUG -> addDebugSettings(sl) - } - settingsList = sl - adapter.submitList(settingsList) { - if (notifyDataSetChanged) { - adapter.notifyDataSetChanged() - } - } - } - - private fun addConfigSettings(sl: ArrayList) { - sl.apply { - add( - SubmenuSetting( - titleId = R.string.preferences_system, - descriptionId = R.string.preferences_system_description, - iconId = R.drawable.ic_system_settings, - menuKey = MenuTag.SECTION_SYSTEM - ) - ) - add( - SubmenuSetting( - titleId = R.string.preferences_graphics, - descriptionId = R.string.preferences_graphics_description, - iconId = R.drawable.ic_graphics, - menuKey = MenuTag.SECTION_RENDERER - ) - ) - add( - SubmenuSetting( - titleId = R.string.preferences_audio, - descriptionId = R.string.preferences_audio_description, - iconId = R.drawable.ic_audio, - menuKey = MenuTag.SECTION_AUDIO - ) - ) - add( - SubmenuSetting( - titleId = R.string.preferences_debug, - descriptionId = R.string.preferences_debug_description, - iconId = R.drawable.ic_code, - menuKey = MenuTag.SECTION_DEBUG - ) - ) - add( - RunnableSetting( - titleId = R.string.reset_to_default, - descriptionId = R.string.reset_to_default_description, - isRunnable = !NativeLibrary.isRunning(), - iconId = R.drawable.ic_restore - ) { settingsViewModel.setShouldShowResetSettingsDialog(true) } - ) - } - } - - private fun addSystemSettings(sl: ArrayList) { - sl.apply { - add(StringSetting.DEVICE_NAME.key) - add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) - add(ShortSetting.RENDERER_SPEED_LIMIT.key) - add(BooleanSetting.USE_DOCKED_MODE.key) - add(IntSetting.REGION_INDEX.key) - add(IntSetting.LANGUAGE_INDEX.key) - add(BooleanSetting.USE_CUSTOM_RTC.key) - add(LongSetting.CUSTOM_RTC.key) - } - } - - private fun addGraphicsSettings(sl: ArrayList) { - sl.apply { - add(IntSetting.RENDERER_ACCURACY.key) - add(IntSetting.RENDERER_RESOLUTION.key) - add(IntSetting.RENDERER_VSYNC.key) - add(IntSetting.RENDERER_SCALING_FILTER.key) - add(IntSetting.FSR_SHARPENING_SLIDER.key) - add(IntSetting.RENDERER_ANTI_ALIASING.key) - add(IntSetting.MAX_ANISOTROPY.key) - add(IntSetting.RENDERER_SCREEN_LAYOUT.key) - add(IntSetting.RENDERER_ASPECT_RATIO.key) - add(IntSetting.VERTICAL_ALIGNMENT.key) - add(BooleanSetting.PICTURE_IN_PICTURE.key) - add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) - add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) - add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) - add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) - } - } - - private fun addAudioSettings(sl: ArrayList) { - sl.apply { - add(IntSetting.AUDIO_OUTPUT_ENGINE.key) - add(ByteSetting.AUDIO_VOLUME.key) - } - } - - private fun addInputSettings(sl: ArrayList) { - settingsViewModel.currentDevice = 0 - - if (NativeConfig.isPerGameConfigLoaded()) { - NativeInput.loadInputProfiles() - val profiles = NativeInput.getInputProfileNames().toMutableList() - profiles.add(0, "") - val prettyProfiles = profiles.toTypedArray() - prettyProfiles[0] = - context.getString(R.string.use_global_input_configuration) - sl.apply { - for (i in 0 until 8) { - add( - IntSingleChoiceSetting( - getPerGameProfileSetting(profiles, i), - titleString = getPlayerProfileString(i + 1), - choices = prettyProfiles, - values = IntArray(profiles.size) { it }.toTypedArray() - ) - ) - } - } - return - } - - val getConnectedIcon: (Int) -> Int = { playerIndex: Int -> - if (NativeInput.getIsConnected(playerIndex)) { - R.drawable.ic_controller - } else { - R.drawable.ic_controller_disconnected - } - } - - val inputSettings = NativeConfig.getInputSettings(true) - sl.apply { - add( - SubmenuSetting( - titleString = Settings.getPlayerString(1), - descriptionString = inputSettings[0].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_ONE, - iconId = getConnectedIcon(0) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(2), - descriptionString = inputSettings[1].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_TWO, - iconId = getConnectedIcon(1) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(3), - descriptionString = inputSettings[2].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_THREE, - iconId = getConnectedIcon(2) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(4), - descriptionString = inputSettings[3].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_FOUR, - iconId = getConnectedIcon(3) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(5), - descriptionString = inputSettings[4].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_FIVE, - iconId = getConnectedIcon(4) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(6), - descriptionString = inputSettings[5].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_SIX, - iconId = getConnectedIcon(5) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(7), - descriptionString = inputSettings[6].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_SEVEN, - iconId = getConnectedIcon(6) - ) - ) - add( - SubmenuSetting( - titleString = Settings.getPlayerString(8), - descriptionString = inputSettings[7].profileName, - menuKey = MenuTag.SECTION_INPUT_PLAYER_EIGHT, - iconId = getConnectedIcon(7) - ) - ) - } - } - - private fun getPlayerProfileString(player: Int): String = - context.getString(R.string.player_num_profile, player) - - private fun getPerGameProfileSetting( - profiles: List, - playerIndex: Int - ): AbstractIntSetting { - return object : AbstractIntSetting { - private val players - get() = NativeConfig.getInputSettings(false) - - override val key = "" - - override fun getInt(needsGlobal: Boolean): Int { - val currentProfile = players[playerIndex].profileName - profiles.forEachIndexed { i, profile -> - if (profile == currentProfile) { - return i - } - } - return 0 - } - - override fun setInt(value: Int) { - NativeInput.loadPerGameConfiguration(playerIndex, value, profiles[value]) - NativeInput.connectControllers(playerIndex) - NativeConfig.saveControlPlayerValues() - } - - override val defaultValue = 0 - - override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() - - override fun reset() = setInt(defaultValue) - - override var global = true - - override val isRuntimeModifiable = true - - override val isSaveable = true - } - } - - private fun addInputPlayer(sl: ArrayList, playerIndex: Int) { - sl.apply { - val connectedSetting = object : AbstractBooleanSetting { - override val key = "connected" - - override fun getBoolean(needsGlobal: Boolean): Boolean = - NativeInput.getIsConnected(playerIndex) - - override fun setBoolean(value: Boolean) = - NativeInput.connectControllers(playerIndex, value) - - override val defaultValue = playerIndex == 0 - - override fun getValueAsString(needsGlobal: Boolean): String = - getBoolean(needsGlobal).toString() - - override fun reset() = setBoolean(defaultValue) - } - add(SwitchSetting(connectedSetting, R.string.connected)) - - val styleTags = NativeInput.getSupportedStyleTags(playerIndex) - val npadType = object : AbstractIntSetting { - override val key = "npad_type" - override fun getInt(needsGlobal: Boolean): Int { - val styleIndex = NativeInput.getStyleIndex(playerIndex) - return styleTags.indexOfFirst { it == styleIndex } - } - - override fun setInt(value: Int) { - NativeInput.setStyleIndex(playerIndex, styleTags[value]) - settingsViewModel.setReloadListAndNotifyDataset(true) - } - - override val defaultValue = NpadStyleIndex.Fullkey.int - override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() - override fun reset() = setInt(defaultValue) - override val pairedSettingKey: String = "connected" - } - addAbstract( - IntSingleChoiceSetting( - npadType, - titleId = R.string.controller_type, - choices = styleTags.map { context.getString(it.nameId) } - .toTypedArray(), - values = IntArray(styleTags.size) { it }.toTypedArray() - ) - ) - - InputHandler.updateControllerData() - - val autoMappingSetting = object : AbstractIntSetting { - override val key = "auto_mapping_device" - - override fun getInt(needsGlobal: Boolean): Int = -1 - - override fun setInt(value: Int) { - val registeredController = InputHandler.registeredControllers[value + 1] - val displayName = registeredController.get( - "display", - context.getString(R.string.unknown) - ) - NativeInput.updateMappingsWithDefault( - playerIndex, - registeredController, - displayName - ) - Toast.makeText( - context, - context.getString(R.string.attempted_auto_map, displayName), - Toast.LENGTH_SHORT - ).show() - settingsViewModel.setReloadListAndNotifyDataset(true) - } - - override val defaultValue = -1 - - override fun getValueAsString(needsGlobal: Boolean) = getInt().toString() - - override fun reset() = setInt(defaultValue) - - override val isRuntimeModifiable: Boolean = true - } - - val unknownString = context.getString(R.string.unknown) - val prettyAutoMappingControllerList = InputHandler.registeredControllers.mapNotNull { - val port = it.get("port", -1) - return@mapNotNull if (port == 100 || port == -1) { - null - } else { - it.get("display", unknownString) - } - }.toTypedArray() - add( - IntSingleChoiceSetting( - autoMappingSetting, - titleId = R.string.auto_map, - descriptionId = R.string.auto_map_description, - choices = prettyAutoMappingControllerList, - values = IntArray(prettyAutoMappingControllerList.size) { it }.toTypedArray() - ) - ) - - val mappingFilterSetting = object : AbstractIntSetting { - override val key = "mapping_filter" - - override fun getInt(needsGlobal: Boolean): Int = settingsViewModel.currentDevice - - override fun setInt(value: Int) { - settingsViewModel.currentDevice = value - } - - override val defaultValue = 0 - - override fun getValueAsString(needsGlobal: Boolean) = getInt().toString() - - override fun reset() = setInt(defaultValue) - - override val isRuntimeModifiable: Boolean = true - } - - val prettyControllerList = InputHandler.registeredControllers.mapNotNull { - return@mapNotNull if (it.get("port", 0) == 100) { - null - } else { - it.get("display", unknownString) - } - }.toTypedArray() - add( - IntSingleChoiceSetting( - mappingFilterSetting, - titleId = R.string.input_mapping_filter, - descriptionId = R.string.input_mapping_filter_description, - choices = prettyControllerList, - values = IntArray(prettyControllerList.size) { it }.toTypedArray() - ) - ) - - add(InputProfileSetting(playerIndex)) - add( - RunnableSetting(titleId = R.string.reset_to_default, isRunnable = true) { - settingsViewModel.setShouldShowResetInputDialog(true) - } - ) - - val styleIndex = NativeInput.getStyleIndex(playerIndex) - - // Buttons - when (styleIndex) { - NpadStyleIndex.Fullkey, - NpadStyleIndex.Handheld, - NpadStyleIndex.JoyconDual -> { - add(HeaderSetting(R.string.buttons)) - add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) - add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) - add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) - add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) - add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus)) - add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus)) - add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home)) - add( - ButtonInputSetting( - playerIndex, - NativeButton.Capture, - R.string.button_capture - ) - ) - } - - NpadStyleIndex.JoyconLeft -> { - add(HeaderSetting(R.string.buttons)) - add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus)) - add( - ButtonInputSetting( - playerIndex, - NativeButton.Capture, - R.string.button_capture - ) - ) - } - - NpadStyleIndex.JoyconRight -> { - add(HeaderSetting(R.string.buttons)) - add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) - add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) - add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) - add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) - add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus)) - add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home)) - } - - NpadStyleIndex.GameCube -> { - add(HeaderSetting(R.string.buttons)) - add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) - add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) - add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) - add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) - add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.start_pause)) - } - - else -> { - // No-op - } - } - - when (styleIndex) { - NpadStyleIndex.Fullkey, - NpadStyleIndex.Handheld, - NpadStyleIndex.JoyconDual, - NpadStyleIndex.JoyconLeft -> { - add(HeaderSetting(R.string.dpad)) - add(ButtonInputSetting(playerIndex, NativeButton.DUp, R.string.up)) - add(ButtonInputSetting(playerIndex, NativeButton.DDown, R.string.down)) - add(ButtonInputSetting(playerIndex, NativeButton.DLeft, R.string.left)) - add(ButtonInputSetting(playerIndex, NativeButton.DRight, R.string.right)) - } - - else -> { - // No-op - } - } - - // Left stick - when (styleIndex) { - NpadStyleIndex.Fullkey, - NpadStyleIndex.Handheld, - NpadStyleIndex.JoyconDual, - NpadStyleIndex.JoyconLeft -> { - add(HeaderSetting(R.string.left_stick)) - addAll(getStickDirections(playerIndex, NativeAnalog.LStick)) - add(ButtonInputSetting(playerIndex, NativeButton.LStick, R.string.pressed)) - addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick)) - } - - NpadStyleIndex.GameCube -> { - add(HeaderSetting(R.string.control_stick)) - addAll(getStickDirections(playerIndex, NativeAnalog.LStick)) - addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick)) - } - - else -> { - // No-op - } - } - - // Right stick - when (styleIndex) { - NpadStyleIndex.Fullkey, - NpadStyleIndex.Handheld, - NpadStyleIndex.JoyconDual, - NpadStyleIndex.JoyconRight -> { - add(HeaderSetting(R.string.right_stick)) - addAll(getStickDirections(playerIndex, NativeAnalog.RStick)) - add(ButtonInputSetting(playerIndex, NativeButton.RStick, R.string.pressed)) - addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick)) - } - - NpadStyleIndex.GameCube -> { - add(HeaderSetting(R.string.c_stick)) - addAll(getStickDirections(playerIndex, NativeAnalog.RStick)) - addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick)) - } - - else -> { - // No-op - } - } - - // L/R, ZL/ZR, and SL/SR - when (styleIndex) { - NpadStyleIndex.Fullkey, - NpadStyleIndex.Handheld -> { - add(HeaderSetting(R.string.triggers)) - add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) - add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) - add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) - add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) - } - - NpadStyleIndex.JoyconDual -> { - add(HeaderSetting(R.string.triggers)) - add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) - add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) - add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) - add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SLLeft, - R.string.button_sl_left - ) - ) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SRLeft, - R.string.button_sr_left - ) - ) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SLRight, - R.string.button_sl_right - ) - ) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SRRight, - R.string.button_sr_right - ) - ) - } - - NpadStyleIndex.JoyconLeft -> { - add(HeaderSetting(R.string.triggers)) - add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) - add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SLLeft, - R.string.button_sl_left - ) - ) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SRLeft, - R.string.button_sr_left - ) - ) - } - - NpadStyleIndex.JoyconRight -> { - add(HeaderSetting(R.string.triggers)) - add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) - add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SLRight, - R.string.button_sl_right - ) - ) - add( - ButtonInputSetting( - playerIndex, - NativeButton.SRRight, - R.string.button_sr_right - ) - ) - } - - NpadStyleIndex.GameCube -> { - add(HeaderSetting(R.string.triggers)) - add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_z)) - add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_l)) - add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_r)) - } - - else -> { - // No-op - } - } - - add(HeaderSetting(R.string.vibration)) - val vibrationEnabledSetting = object : AbstractBooleanSetting { - override val key = "vibration" - - override fun getBoolean(needsGlobal: Boolean): Boolean = - NativeConfig.getInputSettings(true)[playerIndex].vibrationEnabled - - override fun setBoolean(value: Boolean) { - val settings = NativeConfig.getInputSettings(true) - settings[playerIndex].vibrationEnabled = value - NativeConfig.setInputSettings(settings, true) - } - - override val defaultValue = true - - override fun getValueAsString(needsGlobal: Boolean): String = - getBoolean(needsGlobal).toString() - - override fun reset() = setBoolean(defaultValue) - } - add(SwitchSetting(vibrationEnabledSetting, R.string.vibration)) - - val useSystemVibratorSetting = object : AbstractBooleanSetting { - override val key = "" - - override fun getBoolean(needsGlobal: Boolean): Boolean = - NativeConfig.getInputSettings(true)[playerIndex].useSystemVibrator - - override fun setBoolean(value: Boolean) { - val settings = NativeConfig.getInputSettings(true) - settings[playerIndex].useSystemVibrator = value - NativeConfig.setInputSettings(settings, true) - } - - override val defaultValue = playerIndex == 0 - - override fun getValueAsString(needsGlobal: Boolean): String = - getBoolean(needsGlobal).toString() - - override fun reset() = setBoolean(defaultValue) - - override val pairedSettingKey: String = "vibration" - } - addAbstract(SwitchSetting(useSystemVibratorSetting, R.string.use_system_vibrator)) - - val vibrationStrengthSetting = object : AbstractIntSetting { - override val key = "" - - override fun getInt(needsGlobal: Boolean): Int = - NativeConfig.getInputSettings(true)[playerIndex].vibrationStrength - - override fun setInt(value: Int) { - val settings = NativeConfig.getInputSettings(true) - settings[playerIndex].vibrationStrength = value - NativeConfig.setInputSettings(settings, true) - } - - override val defaultValue = 100 - - override fun getValueAsString(needsGlobal: Boolean): String = - getInt(needsGlobal).toString() - - override fun reset() = setInt(defaultValue) - - override val pairedSettingKey: String = "vibration" - } - addAbstract( - SliderSetting(vibrationStrengthSetting, R.string.vibration_strength, units = "%") - ) - } - } - - // Convenience function for creating AbstractIntSettings for modifier range/stick range/stick deadzones - private fun getStickIntSettingFromParam( - playerIndex: Int, - paramName: String, - stick: NativeAnalog, - defaultValue: Float - ): AbstractIntSetting = - object : AbstractIntSetting { - val params get() = NativeInput.getStickParam(playerIndex, stick) - - override val key = "" - - override fun getInt(needsGlobal: Boolean): Int = - (params.get(paramName, defaultValue) * 100).toInt() - - override fun setInt(value: Int) { - val tempParams = params - tempParams.set(paramName, value.toFloat() / 100) - NativeInput.setStickParam(playerIndex, stick, tempParams) - } - - override val defaultValue = (defaultValue * 100).toInt() - - override fun getValueAsString(needsGlobal: Boolean): String = - getInt(needsGlobal).toString() - - override fun reset() = setInt(this.defaultValue) - } - - private fun getExtraStickSettings( - playerIndex: Int, - nativeAnalog: NativeAnalog - ): List { - val stickIsController = - NativeInput.isController(NativeInput.getStickParam(playerIndex, nativeAnalog)) - val modifierRangeSetting = - getStickIntSettingFromParam(playerIndex, "modifier_scale", nativeAnalog, 0.5f) - val stickRangeSetting = - getStickIntSettingFromParam(playerIndex, "range", nativeAnalog, 0.95f) - val stickDeadzoneSetting = - getStickIntSettingFromParam(playerIndex, "deadzone", nativeAnalog, 0.15f) - - val out = mutableListOf().apply { - if (stickIsController) { - add(SliderSetting(stickRangeSetting, titleId = R.string.range, min = 25, max = 150)) - add(SliderSetting(stickDeadzoneSetting, R.string.deadzone)) - } else { - add(ModifierInputSetting(playerIndex, NativeAnalog.LStick, R.string.modifier)) - add(SliderSetting(modifierRangeSetting, R.string.modifier_range)) - } - } - return out - } - - private fun getStickDirections(player: Int, stick: NativeAnalog): List = - listOf( - AnalogInputSetting( - player, - stick, - AnalogDirection.Up, - R.string.up - ), - AnalogInputSetting( - player, - stick, - AnalogDirection.Down, - R.string.down - ), - AnalogInputSetting( - player, - stick, - AnalogDirection.Left, - R.string.left - ), - AnalogInputSetting( - player, - stick, - AnalogDirection.Right, - R.string.right - ) - ) - - private fun addThemeSettings(sl: ArrayList) { - sl.apply { - val theme: AbstractIntSetting = object : AbstractIntSetting { - override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt() - override fun setInt(value: Int) { - IntSetting.THEME.setInt(value) - settingsViewModel.setShouldRecreate(true) - } - - override val key: String = IntSetting.THEME.key - override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable - override fun getValueAsString(needsGlobal: Boolean): String = - IntSetting.THEME.getValueAsString() - - override val defaultValue: Int = IntSetting.THEME.defaultValue - override fun reset() = IntSetting.THEME.setInt(defaultValue) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add( - SingleChoiceSetting( - theme, - titleId = R.string.change_app_theme, - choicesId = R.array.themeEntriesA12, - valuesId = R.array.themeValuesA12 - ) - ) - } else { - add( - SingleChoiceSetting( - theme, - titleId = R.string.change_app_theme, - choicesId = R.array.themeEntries, - valuesId = R.array.themeValues - ) - ) - } - - val themeMode: AbstractIntSetting = object : AbstractIntSetting { - override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt() - override fun setInt(value: Int) { - IntSetting.THEME_MODE.setInt(value) - settingsViewModel.setShouldRecreate(true) - } - - override val key: String = IntSetting.THEME_MODE.key - override val isRuntimeModifiable: Boolean = - IntSetting.THEME_MODE.isRuntimeModifiable - - override fun getValueAsString(needsGlobal: Boolean): String = - IntSetting.THEME_MODE.getValueAsString() - - override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue - override fun reset() { - IntSetting.THEME_MODE.setInt(defaultValue) - settingsViewModel.setShouldRecreate(true) - } - } - - add( - SingleChoiceSetting( - themeMode, - titleId = R.string.change_theme_mode, - choicesId = R.array.themeModeEntries, - valuesId = R.array.themeModeValues - ) - ) - - val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { - override fun getBoolean(needsGlobal: Boolean): Boolean = - BooleanSetting.BLACK_BACKGROUNDS.getBoolean() - - override fun setBoolean(value: Boolean) { - BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value) - settingsViewModel.setShouldRecreate(true) - } - - override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key - override val isRuntimeModifiable: Boolean = - BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable - - override fun getValueAsString(needsGlobal: Boolean): String = - BooleanSetting.BLACK_BACKGROUNDS.getValueAsString() - - override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue - override fun reset() { - BooleanSetting.BLACK_BACKGROUNDS - .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue) - settingsViewModel.setShouldRecreate(true) - } - } - - add( - SwitchSetting( - blackBackgrounds, - titleId = R.string.use_black_backgrounds, - descriptionId = R.string.use_black_backgrounds_description - ) - ) - } - } - - private fun addDebugSettings(sl: ArrayList) { - sl.apply { - add(HeaderSetting(R.string.gpu)) - add(IntSetting.RENDERER_BACKEND.key) - add(BooleanSetting.RENDERER_DEBUG.key) - - add(HeaderSetting(R.string.cpu)) - add(IntSetting.CPU_BACKEND.key) - add(IntSetting.CPU_ACCURACY.key) - add(BooleanSetting.CPU_DEBUG_MODE.key) - add(SettingsItem.FASTMEM_COMBINED) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsViewModel.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsViewModel.kt deleted file mode 100644 index 9f037299..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/SettingsViewModel.kt +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui - -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.utils.InputHandler -import org.sudachi.sudachi_emu.utils.ParamPackage - -class SettingsViewModel : ViewModel() { - var game: Game? = null - - var clickedItem: SettingsItem? = null - - var currentDevice = 0 - - val shouldRecreate: StateFlow get() = _shouldRecreate - private val _shouldRecreate = MutableStateFlow(false) - - val shouldNavigateBack: StateFlow get() = _shouldNavigateBack - private val _shouldNavigateBack = MutableStateFlow(false) - - val shouldShowResetSettingsDialog: StateFlow get() = _shouldShowResetSettingsDialog - private val _shouldShowResetSettingsDialog = MutableStateFlow(false) - - val shouldReloadSettingsList: StateFlow get() = _shouldReloadSettingsList - private val _shouldReloadSettingsList = MutableStateFlow(false) - - val sliderProgress: StateFlow get() = _sliderProgress - private val _sliderProgress = MutableStateFlow(-1) - - val sliderTextValue: StateFlow get() = _sliderTextValue - private val _sliderTextValue = MutableStateFlow("") - - val adapterItemChanged: StateFlow get() = _adapterItemChanged - private val _adapterItemChanged = MutableStateFlow(-1) - - private val _datasetChanged = MutableStateFlow(false) - val datasetChanged = _datasetChanged.asStateFlow() - - private val _reloadListAndNotifyDataset = MutableStateFlow(false) - val reloadListAndNotifyDataset = _reloadListAndNotifyDataset.asStateFlow() - - private val _shouldShowDeleteProfileDialog = MutableStateFlow("") - val shouldShowDeleteProfileDialog = _shouldShowDeleteProfileDialog.asStateFlow() - - private val _shouldShowResetInputDialog = MutableStateFlow(false) - val shouldShowResetInputDialog = _shouldShowResetInputDialog.asStateFlow() - - fun setShouldRecreate(value: Boolean) { - _shouldRecreate.value = value - } - - fun setShouldNavigateBack(value: Boolean) { - _shouldNavigateBack.value = value - } - - fun setShouldShowResetSettingsDialog(value: Boolean) { - _shouldShowResetSettingsDialog.value = value - } - - fun setShouldReloadSettingsList(value: Boolean) { - _shouldReloadSettingsList.value = value - } - - fun setSliderTextValue(value: Float, units: String) { - _sliderProgress.value = value.toInt() - _sliderTextValue.value = String.format( - SudachiApplication.appContext.getString(R.string.value_with_units), - value.toInt().toString(), - units - ) - } - - fun setSliderProgress(value: Float) { - _sliderProgress.value = value.toInt() - } - - fun setAdapterItemChanged(value: Int) { - _adapterItemChanged.value = value - } - - fun setDatasetChanged(value: Boolean) { - _datasetChanged.value = value - } - - fun setReloadListAndNotifyDataset(value: Boolean) { - _reloadListAndNotifyDataset.value = value - } - - fun setShouldShowDeleteProfileDialog(profile: String) { - _shouldShowDeleteProfileDialog.value = profile - } - - fun setShouldShowResetInputDialog(value: Boolean) { - _shouldShowResetInputDialog.value = value - } - - fun getCurrentDeviceParams(defaultParams: ParamPackage): ParamPackage = - try { - InputHandler.registeredControllers[currentDevice] - } catch (e: IndexOutOfBoundsException) { - defaultParams - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt deleted file mode 100644 index 6305e705..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui.viewholder - -import android.view.View -import java.time.Instant -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import org.sudachi.sudachi_emu.databinding.ListItemSettingBinding -import org.sudachi.sudachi_emu.features.settings.model.view.DateTimeSetting -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.features.settings.ui.SettingsAdapter -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible - -class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: DateTimeSetting - - override fun bind(item: SettingsItem) { - setting = item as DateTimeSetting - binding.textSettingName.text = item.title - binding.textSettingDescription.setVisible(item.description.isNotEmpty()) - binding.textSettingDescription.text = item.description - binding.textSettingValue.setVisible(true) - val epochTime = setting.getValue() - val instant = Instant.ofEpochMilli(epochTime * 1000) - val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) - val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) - binding.textSettingValue.text = dateFormatter.format(zonedTime) - - binding.buttonClear.setVisible(setting.clearable) - binding.buttonClear.setOnClickListener { - adapter.onClearClick(setting, bindingAdapterPosition) - } - - setStyle(setting.isEditable, binding) - } - - override fun onClick(clicked: View) { - if (setting.isEditable) { - adapter.onDateTimeClick(setting, bindingAdapterPosition) - } - } - - override fun onLongClick(clicked: View): Boolean { - if (setting.isEditable) { - return adapter.onLongClick(setting, bindingAdapterPosition) - } - return false - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/HeaderViewHolder.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/HeaderViewHolder.kt deleted file mode 100644 index b8a46294..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/HeaderViewHolder.kt +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui.viewholder - -import android.view.View -import org.sudachi.sudachi_emu.databinding.ListItemSettingsHeaderBinding -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.features.settings.ui.SettingsAdapter - -class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - - init { - itemView.setOnClickListener(null) - } - - override fun bind(item: SettingsItem) { - binding.textHeaderName.text = item.title - } - - override fun onClick(clicked: View) { - // no-op - } - - override fun onLongClick(clicked: View): Boolean { - // no-op - return true - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt deleted file mode 100644 index 693b8d56..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui.viewholder - -import android.view.View -import org.sudachi.sudachi_emu.databinding.ListItemSettingBinding -import org.sudachi.sudachi_emu.features.settings.model.view.InputProfileSetting -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.features.settings.ui.SettingsAdapter -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible - -class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: InputProfileSetting - - override fun bind(item: SettingsItem) { - setting = item as InputProfileSetting - binding.textSettingName.text = setting.title - binding.textSettingValue.text = - setting.getCurrentProfile().ifEmpty { binding.root.context.getString(R.string.not_set) } - - binding.textSettingDescription.setVisible(false) - binding.buttonClear.setVisible(false) - binding.icon.setVisible(false) - binding.buttonClear.setVisible(false) - } - - override fun onClick(clicked: View) = - adapter.onInputProfileClick(setting, bindingAdapterPosition) - - override fun onLongClick(clicked: View): Boolean = false -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt deleted file mode 100644 index 4e60b9e3..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui.viewholder - -import android.view.View -import androidx.core.content.res.ResourcesCompat -import org.sudachi.sudachi_emu.databinding.ListItemSettingBinding -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.features.settings.model.view.SubmenuSetting -import org.sudachi.sudachi_emu.features.settings.ui.SettingsAdapter -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible - -class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SubmenuSetting - - override fun bind(item: SettingsItem) { - setting = item as SubmenuSetting - binding.icon.setVisible(setting.iconId != 0) - if (setting.iconId != 0) { - binding.icon.setImageDrawable( - ResourcesCompat.getDrawable( - binding.icon.resources, - setting.iconId, - binding.icon.context.theme - ) - ) - } - - binding.textSettingName.text = setting.title - binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) - binding.textSettingDescription.text = setting.description - binding.textSettingValue.setVisible(false) - binding.buttonClear.setVisible(false) - } - - override fun onClick(clicked: View) { - adapter.onSubmenuClick(setting) - } - - override fun onLongClick(clicked: View): Boolean { - // no-op - return true - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt deleted file mode 100644 index f2002ce0..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.ui.viewholder - -import android.view.View -import android.widget.CompoundButton -import org.sudachi.sudachi_emu.databinding.ListItemSettingSwitchBinding -import org.sudachi.sudachi_emu.features.settings.model.view.SettingsItem -import org.sudachi.sudachi_emu.features.settings.model.view.SwitchSetting -import org.sudachi.sudachi_emu.features.settings.ui.SettingsAdapter -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible - -class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - - private lateinit var setting: SwitchSetting - - override fun bind(item: SettingsItem) { - setting = item as SwitchSetting - binding.textSettingName.text = setting.title - binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) - binding.textSettingDescription.text = setting.description - - binding.switchWidget.setOnCheckedChangeListener(null) - binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal) - binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> - adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition) - } - - binding.buttonClear.setVisible(setting.clearable) - binding.buttonClear.setOnClickListener { - adapter.onClearClick(setting, bindingAdapterPosition) - } - - setStyle(setting.isEditable, binding) - } - - override fun onClick(clicked: View) { - if (setting.isEditable) { - binding.switchWidget.toggle() - } - } - - override fun onLongClick(clicked: View): Boolean { - if (setting.isEditable) { - return adapter.onLongClick(setting, bindingAdapterPosition) - } - return false - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/utils/SettingsFile.kt deleted file mode 100644 index a9b9d844..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/features/settings/utils/SettingsFile.kt +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.features.settings.utils - -import android.net.Uri -import org.sudachi.sudachi_emu.model.Game -import java.io.* -import org.sudachi.sudachi_emu.utils.DirectoryInitialization -import org.sudachi.sudachi_emu.utils.FileUtil -import org.sudachi.sudachi_emu.utils.NativeConfig - -/** - * Contains static methods for interacting with .ini files in which settings are stored. - */ -object SettingsFile { - const val FILE_NAME_CONFIG = "config.ini" - - fun getSettingsFile(fileName: String): File = - File(DirectoryInitialization.userDirectory + "/config/" + fileName) - - fun getCustomSettingsFile(game: Game): File = - File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini") - - fun loadCustomConfig(game: Game) { - val fileName = FileUtil.getFilename(Uri.parse(game.path)) - NativeConfig.initializePerGameConfig(game.programId, fileName) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddGameFolderDialogFragment.kt deleted file mode 100644 index e39bdd56..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddGameFolderDialogFragment.kt +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.content.DialogInterface -import android.net.Uri -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.DialogAddFolderBinding -import org.sudachi.sudachi_emu.model.GameDir -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.model.HomeViewModel - -class AddGameFolderDialogFragment : DialogFragment() { - private val homeViewModel: HomeViewModel by activityViewModels() - private val gamesViewModel: GamesViewModel by activityViewModels() - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val binding = DialogAddFolderBinding.inflate(layoutInflater) - val folderUriString = requireArguments().getString(FOLDER_URI_STRING) - if (folderUriString == null) { - dismiss() - } - binding.path.text = Uri.parse(folderUriString).path - - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.add_game_folder) - .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked) - homeViewModel.setGamesDirSelected(true) - gamesViewModel.addFolder(newGameDir) - } - .setNegativeButton(android.R.string.cancel, null) - .setView(binding.root) - .show() - } - - companion object { - const val TAG = "AddGameFolderDialogFragment" - - private const val FOLDER_URI_STRING = "FolderUriString" - - fun newInstance(folderUriString: String): AddGameFolderDialogFragment { - val args = Bundle() - args.putString(FOLDER_URI_STRING, folderUriString) - val fragment = AddGameFolderDialogFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddonsFragment.kt deleted file mode 100644 index 07d58825..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/AddonsFragment.kt +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.content.Intent -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.documentfile.provider.DocumentFile -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.transition.MaterialSharedAxis -import kotlinx.coroutines.launch -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.adapters.AddonAdapter -import org.sudachi.sudachi_emu.databinding.FragmentAddonsBinding -import org.sudachi.sudachi_emu.model.AddonViewModel -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.utils.AddonUtil -import org.sudachi.sudachi_emu.utils.FileUtil.copyFilesTo -import org.sudachi.sudachi_emu.utils.ViewUtils.updateMargins -import org.sudachi.sudachi_emu.utils.collect -import java.io.File - -class AddonsFragment : Fragment() { - private var _binding: FragmentAddonsBinding? = null - private val binding get() = _binding!! - - private val homeViewModel: HomeViewModel by activityViewModels() - private val addonViewModel: AddonViewModel by activityViewModels() - - private val args by navArgs() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - addonViewModel.onOpenAddons(args.game) - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentAddonsBinding.inflate(inflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - homeViewModel.setNavigationVisibility(visible = false, animated = false) - homeViewModel.setStatusBarShadeVisibility(false) - - binding.toolbarAddons.setNavigationOnClickListener { - binding.root.findNavController().popBackStack() - } - - binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title) - - binding.listAddons.apply { - layoutManager = LinearLayoutManager(requireContext()) - adapter = AddonAdapter(addonViewModel) - } - - addonViewModel.addonList.collect(viewLifecycleOwner) { - (binding.listAddons.adapter as AddonAdapter).submitList(it) - } - addonViewModel.showModInstallPicker.collect( - viewLifecycleOwner, - resetState = { addonViewModel.showModInstallPicker(false) } - ) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) } - addonViewModel.showModNoticeDialog.collect( - viewLifecycleOwner, - resetState = { addonViewModel.showModNoticeDialog(false) } - ) { - if (it) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.addon_notice, - descriptionId = R.string.addon_notice_description, - dismissible = false, - positiveAction = { addonViewModel.showModInstallPicker(true) }, - negativeAction = {}, - negativeButtonTitleId = R.string.close - ).show(parentFragmentManager, MessageDialogFragment.TAG) - } - } - addonViewModel.addonToDelete.collect( - viewLifecycleOwner, - resetState = { addonViewModel.setAddonToDelete(null) } - ) { - if (it != null) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.confirm_uninstall, - descriptionId = R.string.confirm_uninstall_description, - positiveAction = { addonViewModel.onDeleteAddon(it) }, - negativeAction = {} - ).show(parentFragmentManager, MessageDialogFragment.TAG) - } - } - - binding.buttonInstall.setOnClickListener { - ContentTypeSelectionDialogFragment().show( - parentFragmentManager, - ContentTypeSelectionDialogFragment.TAG - ) - } - - setInsets() - } - - override fun onResume() { - super.onResume() - addonViewModel.refreshAddons() - } - - override fun onDestroy() { - super.onDestroy() - addonViewModel.onCloseAddons() - } - - val installAddon = - registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> - if (result == null) { - return@registerForActivityResult - } - - val externalAddonDirectory = DocumentFile.fromTreeUri(requireContext(), result) - if (externalAddonDirectory == null) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.invalid_directory, - descriptionId = R.string.invalid_directory_description - ).show(parentFragmentManager, MessageDialogFragment.TAG) - return@registerForActivityResult - } - - val isValid = externalAddonDirectory.listFiles() - .any { AddonUtil.validAddonDirectories.contains(it.name?.lowercase()) } - val errorMessage = MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.invalid_directory, - descriptionId = R.string.invalid_directory_description - ) - if (isValid) { - ProgressDialogFragment.newInstance( - requireActivity(), - R.string.installing_game_content, - false - ) { progressCallback, _ -> - val parentDirectoryName = externalAddonDirectory.name - val internalAddonDirectory = - File(args.game.addonDir + parentDirectoryName) - try { - externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback) - } catch (_: Exception) { - return@newInstance errorMessage - } - addonViewModel.refreshAddons() - return@newInstance getString(R.string.addon_installed_successfully) - }.show(parentFragmentManager, ProgressDialogFragment.TAG) - } else { - errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG) - } - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - - val leftInsets = barInsets.left + cutoutInsets.left - val rightInsets = barInsets.right + cutoutInsets.right - - binding.toolbarAddons.updateMargins(left = leftInsets, right = rightInsets) - binding.listAddons.updateMargins(left = leftInsets, right = rightInsets) - binding.listAddons.updatePadding( - bottom = barInsets.bottom + - resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) - ) - - val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) - binding.buttonInstall.updateMargins( - left = leftInsets + fabSpacing, - right = rightInsets + fabSpacing, - bottom = barInsets.bottom + fabSpacing - ) - - windowInsets - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ContentTypeSelectionDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ContentTypeSelectionDialogFragment.kt deleted file mode 100644 index 384daf0c..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ContentTypeSelectionDialogFragment.kt +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.content.DialogInterface -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels -import androidx.preference.PreferenceManager -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.model.AddonViewModel -import org.sudachi.sudachi_emu.ui.main.MainActivity - -class ContentTypeSelectionDialogFragment : DialogFragment() { - private val addonViewModel: AddonViewModel by activityViewModels() - - private val preferences get() = - PreferenceManager.getDefaultSharedPreferences(SudachiApplication.appContext) - - private var selectedItem = 0 - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val launchOptions = - arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats)) - - if (savedInstanceState != null) { - selectedItem = savedInstanceState.getInt(SELECTED_ITEM) - } - - val mainActivity = requireActivity() as MainActivity - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.select_content_type) - .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - when (selectedItem) { - 0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*")) - else -> { - if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) { - preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply() - addonViewModel.showModNoticeDialog(true) - return@setPositiveButton - } - addonViewModel.showModInstallPicker(true) - } - } - } - .setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int -> - selectedItem = i - } - .setNegativeButton(android.R.string.cancel, null) - .show() - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putInt(SELECTED_ITEM, selectedItem) - } - - companion object { - const val TAG = "ContentTypeSelectionDialogFragment" - - private const val SELECTED_ITEM = "SelectedItem" - private const val MOD_NOTICE_SEEN = "ModNoticeSeen" - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/CoreErrorDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/CoreErrorDialogFragment.kt deleted file mode 100644 index c750e9eb..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/CoreErrorDialogFragment.kt +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.content.DialogInterface -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R - -class CoreErrorDialogFragment : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(requireArguments().getString(TITLE)) - .setMessage(requireArguments().getString(MESSAGE)) - .setPositiveButton(R.string.continue_button, null) - .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> - NativeLibrary.coreErrorAlertResult = false - synchronized(NativeLibrary.coreErrorAlertLock) { - NativeLibrary.coreErrorAlertLock.notify() - } - } - .create() - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - NativeLibrary.coreErrorAlertResult = true - synchronized(NativeLibrary.coreErrorAlertLock) { NativeLibrary.coreErrorAlertLock.notify() } - } - - companion object { - const val TITLE = "Title" - const val MESSAGE = "Message" - - fun newInstance(title: String, message: String): CoreErrorDialogFragment { - val frag = CoreErrorDialogFragment() - val args = Bundle() - args.putString(TITLE, title) - args.putString(MESSAGE, message) - frag.arguments = args - return frag - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/DriversLoadingDialogFragment.kt deleted file mode 100644 index bb34bd3f..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/DriversLoadingDialogFragment.kt +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.DialogProgressBarBinding -import org.sudachi.sudachi_emu.model.DriverViewModel -import org.sudachi.sudachi_emu.utils.collect - -class DriversLoadingDialogFragment : DialogFragment() { - private val driverViewModel: DriverViewModel by activityViewModels() - - private lateinit var binding: DialogProgressBarBinding - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - binding = DialogProgressBarBinding.inflate(layoutInflater) - binding.progressBar.isIndeterminate = true - - isCancelable = false - - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.loading) - .setView(binding.root) - .create() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View = binding.root - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() } - } - - companion object { - const val TAG = "DriversLoadingDialogFragment" - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EarlyAccessFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EarlyAccessFragment.kt deleted file mode 100644 index 07125213..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EarlyAccessFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.fragment.findNavController -import com.google.android.material.transition.MaterialSharedAxis -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.FragmentEarlyAccessBinding -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.utils.ViewUtils.updateMargins - -class EarlyAccessFragment : Fragment() { - private var _binding: FragmentEarlyAccessBinding? = null - private val binding get() = _binding!! - - private val homeViewModel: HomeViewModel by activityViewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentEarlyAccessBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - homeViewModel.setNavigationVisibility(visible = false, animated = true) - homeViewModel.setStatusBarShadeVisibility(visible = false) - - binding.toolbarAbout.setNavigationOnClickListener { - parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() - } - - binding.getEarlyAccessButton.setOnClickListener { - openLink( - getString(R.string.play_store_link) - ) - } - - setInsets() - } - - private fun openLink(link: String) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) - startActivity(intent) - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - - val leftInsets = barInsets.left + cutoutInsets.left - val rightInsets = barInsets.right + cutoutInsets.right - - binding.appbarEa.updateMargins(left = leftInsets, right = rightInsets) - - binding.scrollEa.updatePadding( - left = leftInsets, - right = rightInsets, - bottom = barInsets.bottom - ) - - windowInsets - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EmulationFragment.kt deleted file mode 100644 index d5ca95de..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/EmulationFragment.kt +++ /dev/null @@ -1,1048 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.annotation.SuppressLint -import android.app.AlertDialog -import android.content.Context -import android.content.DialogInterface -import android.content.pm.ActivityInfo -import android.content.res.Configuration -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.os.PowerManager -import android.os.SystemClock -import android.util.Rational -import android.view.* -import android.widget.FrameLayout -import android.widget.TextView -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.appcompat.widget.PopupMenu -import androidx.core.content.res.ResourcesCompat -import androidx.core.graphics.Insets -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding -import androidx.drawerlayout.widget.DrawerLayout -import androidx.drawerlayout.widget.DrawerLayout.DrawerListener -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.navArgs -import androidx.window.layout.FoldingFeature -import androidx.window.layout.WindowInfoTracker -import androidx.window.layout.WindowLayoutInfo -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.slider.Slider -import org.sudachi.sudachi_emu.HomeNavigationDirections -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.activities.EmulationActivity -import org.sudachi.sudachi_emu.databinding.DialogOverlayAdjustBinding -import org.sudachi.sudachi_emu.databinding.FragmentEmulationBinding -import org.sudachi.sudachi_emu.features.settings.model.BooleanSetting -import org.sudachi.sudachi_emu.features.settings.model.IntSetting -import org.sudachi.sudachi_emu.features.settings.model.Settings -import org.sudachi.sudachi_emu.features.settings.model.Settings.EmulationOrientation -import org.sudachi.sudachi_emu.features.settings.model.Settings.EmulationVerticalAlignment -import org.sudachi.sudachi_emu.features.settings.utils.SettingsFile -import org.sudachi.sudachi_emu.model.DriverViewModel -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.model.EmulationViewModel -import org.sudachi.sudachi_emu.overlay.model.OverlayControl -import org.sudachi.sudachi_emu.overlay.model.OverlayLayout -import org.sudachi.sudachi_emu.utils.* -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import java.lang.NullPointerException - -class EmulationFragment : Fragment(), SurfaceHolder.Callback { - private lateinit var emulationState: EmulationState - private var emulationActivity: EmulationActivity? = null - private var perfStatsUpdater: (() -> Unit)? = null - private var thermalStatsUpdater: (() -> Unit)? = null - - private var _binding: FragmentEmulationBinding? = null - private val binding get() = _binding!! - - private val args by navArgs() - - private lateinit var game: Game - - private val emulationViewModel: EmulationViewModel by activityViewModels() - private val driverViewModel: DriverViewModel by activityViewModels() - - private var isInFoldableLayout = false - - private lateinit var powerManager: PowerManager - - override fun onAttach(context: Context) { - super.onAttach(context) - if (context is EmulationActivity) { - emulationActivity = context - NativeLibrary.setEmulationActivity(context) - } else { - throw IllegalStateException("EmulationFragment must have EmulationActivity parent") - } - } - - /** - * Initialize anything that doesn't depend on the layout / views in here. - */ - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - updateOrientation() - - powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager - - val intentUri: Uri? = requireActivity().intent.data - var intentGame: Game? = null - if (intentUri != null) { - intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) { - GameHelper.getGame(requireActivity().intent.data!!, false) - } else { - null - } - } - - try { - game = if (args.game != null) { - args.game!! - } else { - intentGame!! - } - } catch (e: NullPointerException) { - Toast.makeText( - requireContext(), - R.string.no_game_present, - Toast.LENGTH_SHORT - ).show() - requireActivity().finish() - return - } - - // Always load custom settings when launching a game from an intent - if (args.custom || intentGame != null) { - SettingsFile.loadCustomConfig(game) - NativeConfig.unloadPerGameConfig() - } else { - NativeConfig.reloadGlobalConfig() - } - - // Install the selected driver asynchronously as the game starts - driverViewModel.onLaunchGame() - - // So this fragment doesn't restart on configuration changes; i.e. rotation. - retainInstance = true - emulationState = EmulationState(game.path) { - return@EmulationState driverViewModel.isInteractionAllowed.value - } - } - - /** - * Initialize the UI and start emulation in here. - */ - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentEmulationBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - if (requireActivity().isFinishing) { - return - } - - binding.surfaceEmulation.holder.addCallback(this) - binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } - - binding.drawerLayout.addDrawerListener(object : DrawerListener { - override fun onDrawerSlide(drawerView: View, slideOffset: Float) { - binding.surfaceInputOverlay.dispatchTouchEvent( - MotionEvent.obtain( - SystemClock.uptimeMillis(), - SystemClock.uptimeMillis() + 100, - MotionEvent.ACTION_UP, - 0f, - 0f, - 0 - ) - ) - } - - override fun onDrawerOpened(drawerView: View) { - binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) - binding.inGameMenu.requestFocus() - emulationViewModel.setDrawerOpen(true) - } - - override fun onDrawerClosed(drawerView: View) { - binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) - emulationViewModel.setDrawerOpen(false) - } - - override fun onDrawerStateChanged(newState: Int) { - // No op - } - }) - binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - binding.inGameMenu.getHeaderView(0).findViewById(R.id.text_game_title).text = - game.title - - binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { - val lockMode = IntSetting.LOCK_DRAWER.getInt() - val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { - R.string.unlock_drawer - } else { - R.string.lock_drawer - } - val iconId = if (lockMode == DrawerLayout.LOCK_MODE_UNLOCKED) { - R.drawable.ic_unlock - } else { - R.drawable.ic_lock - } - - title = getString(titleId) - icon = ResourcesCompat.getDrawable( - resources, - iconId, - requireContext().theme - ) - } - - binding.inGameMenu.setNavigationItemSelectedListener { - when (it.itemId) { - R.id.menu_pause_emulation -> { - if (emulationState.isPaused) { - emulationState.run(false) - it.title = resources.getString(R.string.emulation_pause) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_pause, - requireContext().theme - ) - } else { - emulationState.pause() - it.title = resources.getString(R.string.emulation_unpause) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_play, - requireContext().theme - ) - } - binding.inGameMenu.requestFocus() - true - } - - R.id.menu_settings -> { - val action = HomeNavigationDirections.actionGlobalSettingsActivity( - null, - Settings.MenuTag.SECTION_ROOT - ) - binding.inGameMenu.requestFocus() - binding.root.findNavController().navigate(action) - true - } - - R.id.menu_settings_per_game -> { - val action = HomeNavigationDirections.actionGlobalSettingsActivity( - args.game, - Settings.MenuTag.SECTION_ROOT - ) - binding.inGameMenu.requestFocus() - binding.root.findNavController().navigate(action) - true - } - - R.id.menu_controls -> { - val action = HomeNavigationDirections.actionGlobalSettingsActivity( - null, - Settings.MenuTag.SECTION_INPUT - ) - binding.root.findNavController().navigate(action) - true - } - - R.id.menu_overlay_controls -> { - showOverlayOptions() - true - } - - R.id.menu_lock_drawer -> { - when (IntSetting.LOCK_DRAWER.getInt()) { - DrawerLayout.LOCK_MODE_UNLOCKED -> { - IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - it.title = resources.getString(R.string.unlock_drawer) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_lock, - requireContext().theme - ) - } - - DrawerLayout.LOCK_MODE_LOCKED_CLOSED -> { - IntSetting.LOCK_DRAWER.setInt(DrawerLayout.LOCK_MODE_UNLOCKED) - it.title = resources.getString(R.string.lock_drawer) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_unlock, - requireContext().theme - ) - } - } - binding.inGameMenu.requestFocus() - NativeConfig.saveGlobalConfig() - true - } - - R.id.menu_exit -> { - emulationState.stop() - NativeConfig.reloadGlobalConfig() - emulationViewModel.setIsEmulationStopping(true) - binding.drawerLayout.close() - binding.inGameMenu.requestFocus() - true - } - - else -> true - } - } - - setInsets() - - requireActivity().onBackPressedDispatcher.addCallback( - requireActivity(), - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (!NativeLibrary.isRunning()) { - return - } - emulationViewModel.setDrawerOpen(!binding.drawerLayout.isOpen) - } - } - ) - - GameIconUtils.loadGameIcon(game, binding.loadingImage) - binding.loadingTitle.text = game.title - binding.loadingTitle.isSelected = true - binding.loadingText.isSelected = true - - WindowInfoTracker.getOrCreate(requireContext()) - .windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) { - updateFoldableLayout(requireActivity() as EmulationActivity, it) - } - emulationViewModel.shaderProgress.collect(viewLifecycleOwner) { - if (it > 0 && it != emulationViewModel.totalShaders.value) { - binding.loadingProgressIndicator.isIndeterminate = false - - if (it < binding.loadingProgressIndicator.max) { - binding.loadingProgressIndicator.progress = it - } - } - - if (it == emulationViewModel.totalShaders.value) { - binding.loadingText.setText(R.string.loading) - binding.loadingProgressIndicator.isIndeterminate = true - } - } - emulationViewModel.totalShaders.collect(viewLifecycleOwner) { - binding.loadingProgressIndicator.max = it - } - emulationViewModel.shaderMessage.collect(viewLifecycleOwner) { - if (it.isNotEmpty()) { - binding.loadingText.text = it - } - } - - emulationViewModel.emulationStarted.collect(viewLifecycleOwner) { - if (it) { - binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) - ViewUtils.showView(binding.surfaceInputOverlay) - ViewUtils.hideView(binding.loadingIndicator) - - emulationState.updateSurface() - - // Setup overlays - updateShowFpsOverlay() - updateThermalOverlay() - } - } - emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) { - if (it) { - binding.loadingText.setText(R.string.shutting_down) - ViewUtils.showView(binding.loadingIndicator) - ViewUtils.hideView(binding.inputContainer) - ViewUtils.hideView(binding.showFpsText) - } - } - emulationViewModel.drawerOpen.collect(viewLifecycleOwner) { - if (it) { - binding.drawerLayout.open() - binding.inGameMenu.requestFocus() - } else { - binding.drawerLayout.close() - } - } - emulationViewModel.programChanged.collect(viewLifecycleOwner) { - if (it != 0) { - emulationViewModel.setEmulationStarted(false) - binding.drawerLayout.close() - binding.drawerLayout - .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - ViewUtils.hideView(binding.surfaceInputOverlay) - ViewUtils.showView(binding.loadingIndicator) - } - } - emulationViewModel.emulationStopped.collect(viewLifecycleOwner) { - if (it && emulationViewModel.programChanged.value != -1) { - if (perfStatsUpdater != null) { - perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) - } - emulationState.changeProgram(emulationViewModel.programChanged.value) - emulationViewModel.setProgramChanged(-1) - emulationViewModel.setEmulationStopped(false) - } - } - - driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { - if (it) startEmulation() - } - } - - private fun startEmulation(programIndex: Int = 0) { - if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { - if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start() - } - - updateScreenLayout() - - emulationState.run(emulationActivity!!.isActivityRecreated, programIndex) - } - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - if (_binding == null) { - return - } - - updateScreenLayout() - val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() - if (emulationActivity?.isInPictureInPictureMode == true) { - if (binding.drawerLayout.isOpen) { - binding.drawerLayout.close() - } - if (showInputOverlay) { - binding.surfaceInputOverlay.setVisible(visible = false, gone = false) - } - } else { - binding.surfaceInputOverlay.setVisible( - showInputOverlay && emulationViewModel.emulationStarted.value - ) - if (!isInFoldableLayout) { - if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - binding.surfaceInputOverlay.layout = OverlayLayout.Portrait - } else { - binding.surfaceInputOverlay.layout = OverlayLayout.Landscape - } - } - } - } - - override fun onPause() { - if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { - emulationState.pause() - } - super.onPause() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onDetach() { - NativeLibrary.clearEmulationActivity() - super.onDetach() - } - - private fun resetInputOverlay() { - IntSetting.OVERLAY_SCALE.reset() - IntSetting.OVERLAY_OPACITY.reset() - binding.surfaceInputOverlay.post { - binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() - } - } - - private fun updateShowFpsOverlay() { - val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() - binding.showFpsText.setVisible(showOverlay) - if (showOverlay) { - val SYSTEM_FPS = 0 - val FPS = 1 - val FRAMETIME = 2 - val SPEED = 3 - perfStatsUpdater = { - if (emulationViewModel.emulationStarted.value && - !emulationViewModel.isEmulationStopping.value - ) { - val perfStats = NativeLibrary.getPerfStats() - val cpuBackend = NativeLibrary.getCpuBackend() - val gpuDriver = NativeLibrary.getGpuDriver() - if (_binding != null) { - binding.showFpsText.text = - String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver) - } - perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) - } - } - perfStatsUpdateHandler.post(perfStatsUpdater!!) - } else { - if (perfStatsUpdater != null) { - perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) - } - } - } - - private fun updateThermalOverlay() { - val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean() - binding.showThermalsText.setVisible(showOverlay) - if (showOverlay) { - thermalStatsUpdater = { - if (emulationViewModel.emulationStarted.value && - !emulationViewModel.isEmulationStopping.value - ) { - val thermalStatus = when (powerManager.currentThermalStatus) { - PowerManager.THERMAL_STATUS_LIGHT -> "😥" - PowerManager.THERMAL_STATUS_MODERATE -> "🥵" - PowerManager.THERMAL_STATUS_SEVERE -> "🔥" - PowerManager.THERMAL_STATUS_CRITICAL, - PowerManager.THERMAL_STATUS_EMERGENCY, - PowerManager.THERMAL_STATUS_SHUTDOWN -> "☢️" - - else -> "🙂" - } - if (_binding != null) { - binding.showThermalsText.text = thermalStatus - } - thermalStatsUpdateHandler.postDelayed(thermalStatsUpdater!!, 1000) - } - } - thermalStatsUpdateHandler.post(thermalStatsUpdater!!) - } else { - if (thermalStatsUpdater != null) { - thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!) - } - } - } - - @SuppressLint("SourceLockedOrientationActivity") - private fun updateOrientation() { - emulationActivity?.let { - val orientationSetting = - EmulationOrientation.from(IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) - it.requestedOrientation = when (orientationSetting) { - EmulationOrientation.Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - EmulationOrientation.SensorLandscape -> - ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - - EmulationOrientation.Landscape -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - EmulationOrientation.ReverseLandscape -> - ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - - EmulationOrientation.SensorPortrait -> - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - - EmulationOrientation.Portrait -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - EmulationOrientation.ReversePortrait -> - ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT - } - } - } - - private fun updateScreenLayout() { - val verticalAlignment = - EmulationVerticalAlignment.from(IntSetting.VERTICAL_ALIGNMENT.getInt()) - val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) { - 0 -> Rational(16, 9) - 1 -> Rational(4, 3) - 2 -> Rational(21, 9) - 3 -> Rational(16, 10) - else -> null // Best fit - } - when (verticalAlignment) { - EmulationVerticalAlignment.Top -> { - binding.surfaceEmulation.setAspectRatio(aspectRatio) - val params = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL - binding.surfaceEmulation.layoutParams = params - } - - EmulationVerticalAlignment.Center -> { - binding.surfaceEmulation.setAspectRatio(null) - binding.surfaceEmulation.updateLayoutParams { - width = ViewGroup.LayoutParams.MATCH_PARENT - height = ViewGroup.LayoutParams.MATCH_PARENT - } - } - - EmulationVerticalAlignment.Bottom -> { - binding.surfaceEmulation.setAspectRatio(aspectRatio) - val params = - FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - binding.surfaceEmulation.layoutParams = params - } - } - emulationState.updateSurface() - emulationActivity?.buildPictureInPictureParams() - updateOrientation() - } - - private fun updateFoldableLayout( - emulationActivity: EmulationActivity, - newLayoutInfo: WindowLayoutInfo - ) { - val isFolding = - (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { - if (it.isSeparating) { - emulationActivity.requestedOrientation = - ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { - // Restrict emulation and overlays to the top of the screen - binding.emulationContainer.layoutParams.height = it.bounds.top - // Restrict input and menu drawer to the bottom of the screen - binding.inputContainer.layoutParams.height = it.bounds.bottom - binding.inGameMenu.layoutParams.height = it.bounds.bottom - - isInFoldableLayout = true - binding.surfaceInputOverlay.layout = OverlayLayout.Foldable - } - } - it.isSeparating - } ?: false - if (!isFolding) { - binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - isInFoldableLayout = false - updateOrientation() - onConfigurationChanged(resources.configuration) - } - binding.emulationContainer.requestLayout() - binding.inputContainer.requestLayout() - binding.inGameMenu.requestLayout() - } - - override fun surfaceCreated(holder: SurfaceHolder) { - // We purposely don't do anything here. - // All work is done in surfaceChanged, which we are guaranteed to get even for surface creation. - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) - emulationState.newSurface(holder.surface) - } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - emulationState.clearSurface() - } - - private fun showOverlayOptions() { - val anchor = binding.inGameMenu.findViewById(R.id.menu_overlay_controls) - val popup = PopupMenu(requireContext(), anchor) - - popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu) - - popup.menu.apply { - findItem(R.id.menu_toggle_fps).isChecked = - BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() - findItem(R.id.thermal_indicator).isChecked = - BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean() - findItem(R.id.menu_rel_stick_center).isChecked = - BooleanSetting.JOYSTICK_REL_CENTER.getBoolean() - findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean() - findItem(R.id.menu_show_overlay).isChecked = - BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() - findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean() - findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean() - } - - popup.setOnDismissListener { NativeConfig.saveGlobalConfig() } - popup.setOnMenuItemClickListener { - when (it.itemId) { - R.id.menu_toggle_fps -> { - it.isChecked = !it.isChecked - BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked) - updateShowFpsOverlay() - true - } - - R.id.thermal_indicator -> { - it.isChecked = !it.isChecked - BooleanSetting.SHOW_THERMAL_OVERLAY.setBoolean(it.isChecked) - updateThermalOverlay() - true - } - - R.id.menu_edit_overlay -> { - binding.drawerLayout.close() - binding.surfaceInputOverlay.requestFocus() - startConfiguringControls() - true - } - - R.id.menu_adjust_overlay -> { - adjustOverlay() - true - } - - R.id.menu_toggle_controls -> { - val overlayControlData = NativeConfig.getOverlayControlData() - val optionsArray = BooleanArray(overlayControlData.size) - overlayControlData.forEachIndexed { i, _ -> - optionsArray[i] = overlayControlData.firstOrNull { data -> - OverlayControl.entries[i].id == data.id - }?.enabled == true - } - - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.emulation_toggle_controls) - .setMultiChoiceItems( - R.array.gamepadButtons, - optionsArray - ) { _, indexSelected, isChecked -> - overlayControlData.firstOrNull { data -> - OverlayControl.entries[indexSelected].id == data.id - }?.enabled = isChecked - } - .setPositiveButton(android.R.string.ok) { _, _ -> - NativeConfig.setOverlayControlData(overlayControlData) - NativeConfig.saveGlobalConfig() - binding.surfaceInputOverlay.refreshControls() - } - .setNegativeButton(android.R.string.cancel, null) - .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } - .show() - - // Override normal behaviour so the dialog doesn't close - dialog.getButton(AlertDialog.BUTTON_NEUTRAL) - .setOnClickListener { - val isChecked = !optionsArray[0] - overlayControlData.forEachIndexed { i, _ -> - optionsArray[i] = isChecked - dialog.listView.setItemChecked(i, isChecked) - overlayControlData[i].enabled = isChecked - } - } - true - } - - R.id.menu_show_overlay -> { - it.isChecked = !it.isChecked - BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked) - binding.surfaceInputOverlay.refreshControls() - true - } - - R.id.menu_rel_stick_center -> { - it.isChecked = !it.isChecked - BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked) - true - } - - R.id.menu_dpad_slide -> { - it.isChecked = !it.isChecked - BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked) - true - } - - R.id.menu_haptics -> { - it.isChecked = !it.isChecked - BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked) - true - } - - R.id.menu_touchscreen -> { - it.isChecked = !it.isChecked - BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked) - true - } - - R.id.menu_reset_overlay -> { - binding.drawerLayout.close() - resetInputOverlay() - true - } - - else -> true - } - } - - popup.show() - } - - @SuppressLint("SourceLockedOrientationActivity") - private fun startConfiguringControls() { - // Lock the current orientation to prevent editing inconsistencies - if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { - emulationActivity?.let { - it.requestedOrientation = - if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT - } else { - ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE - } - } - } - binding.doneControlConfig.setVisible(true) - binding.surfaceInputOverlay.setIsInEditMode(true) - } - - private fun stopConfiguringControls() { - binding.doneControlConfig.setVisible(false) - binding.surfaceInputOverlay.setIsInEditMode(false) - // Unlock the orientation if it was locked for editing - if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { - emulationActivity?.let { - it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } - } - NativeConfig.saveGlobalConfig() - } - - @SuppressLint("SetTextI18n") - private fun adjustOverlay() { - val adjustBinding = DialogOverlayAdjustBinding.inflate(layoutInflater) - adjustBinding.apply { - inputScaleSlider.apply { - valueTo = 150F - value = IntSetting.OVERLAY_SCALE.getInt().toFloat() - addOnChangeListener( - Slider.OnChangeListener { _, value, _ -> - inputScaleValue.text = "${value.toInt()}%" - setControlScale(value.toInt()) - } - ) - } - inputOpacitySlider.apply { - valueTo = 100F - value = IntSetting.OVERLAY_OPACITY.getInt().toFloat() - addOnChangeListener( - Slider.OnChangeListener { _, value, _ -> - inputOpacityValue.text = "${value.toInt()}%" - setControlOpacity(value.toInt()) - } - ) - } - inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" - inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" - } - - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.emulation_control_adjust) - .setView(adjustBinding.root) - .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - NativeConfig.saveGlobalConfig() - } - .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int -> - setControlScale(50) - setControlOpacity(100) - } - .show() - } - - private fun setControlScale(scale: Int) { - IntSetting.OVERLAY_SCALE.setInt(scale) - binding.surfaceInputOverlay.refreshControls() - } - - private fun setControlOpacity(opacity: Int) { - IntSetting.OVERLAY_OPACITY.setInt(opacity) - binding.surfaceInputOverlay.refreshControls() - } - - private fun setInsets() { - ViewCompat.setOnApplyWindowInsetsListener( - binding.inGameMenu - ) { v: View, windowInsets: WindowInsetsCompat -> - val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - var left = 0 - var right = 0 - if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { - left = cutInsets.left - } else { - right = cutInsets.right - } - - v.updatePadding(left = left, top = cutInsets.top, right = right) - windowInsets - } - } - - private class EmulationState( - private val gamePath: String, - private val emulationCanStart: () -> Boolean - ) { - private var state: State - private var surface: Surface? = null - lateinit var emulationThread: Thread - - init { - // Starting state is stopped. - state = State.STOPPED - } - - @get:Synchronized - val isStopped: Boolean - get() = state == State.STOPPED - - // Getters for the current state - @get:Synchronized - val isPaused: Boolean - get() = state == State.PAUSED - - @get:Synchronized - val isRunning: Boolean - get() = state == State.RUNNING - - @Synchronized - fun stop() { - if (state != State.STOPPED) { - Log.debug("[EmulationFragment] Stopping emulation.") - NativeLibrary.stopEmulation() - state = State.STOPPED - } else { - Log.warning("[EmulationFragment] Stop called while already stopped.") - } - } - - // State changing methods - @Synchronized - fun pause() { - if (state != State.PAUSED) { - Log.debug("[EmulationFragment] Pausing emulation.") - - NativeLibrary.pauseEmulation() - - state = State.PAUSED - } else { - Log.warning("[EmulationFragment] Pause called while already paused.") - } - } - - @Synchronized - fun run(isActivityRecreated: Boolean, programIndex: Int = 0) { - if (isActivityRecreated) { - if (NativeLibrary.isRunning()) { - state = State.PAUSED - } - } else { - Log.debug("[EmulationFragment] activity resumed or fresh start") - } - - // If the surface is set, run now. Otherwise, wait for it to get set. - if (surface != null) { - runWithValidSurface(programIndex) - } - } - - @Synchronized - fun changeProgram(programIndex: Int) { - emulationThread.join() - emulationThread = Thread({ - Log.debug("[EmulationFragment] Starting emulation thread.") - NativeLibrary.run(gamePath, programIndex, false) - }, "NativeEmulation") - emulationThread.start() - } - - // Surface callbacks - @Synchronized - fun newSurface(surface: Surface?) { - this.surface = surface - if (this.surface != null) { - runWithValidSurface() - } - } - - @Synchronized - fun updateSurface() { - if (surface != null) { - NativeLibrary.surfaceChanged(surface) - } - } - - @Synchronized - fun clearSurface() { - if (surface == null) { - Log.warning("[EmulationFragment] clearSurface called, but surface already null.") - } else { - surface = null - Log.debug("[EmulationFragment] Surface destroyed.") - when (state) { - State.RUNNING -> { - state = State.PAUSED - } - - State.PAUSED -> Log.warning( - "[EmulationFragment] Surface cleared while emulation paused." - ) - - else -> Log.warning( - "[EmulationFragment] Surface cleared while emulation stopped." - ) - } - } - } - - private fun runWithValidSurface(programIndex: Int = 0) { - NativeLibrary.surfaceChanged(surface) - if (!emulationCanStart.invoke()) { - return - } - - when (state) { - State.STOPPED -> { - emulationThread = Thread({ - Log.debug("[EmulationFragment] Starting emulation thread.") - NativeLibrary.run(gamePath, programIndex, true) - }, "NativeEmulation") - emulationThread.start() - } - - State.PAUSED -> { - Log.debug("[EmulationFragment] Resuming emulation.") - NativeLibrary.unpauseEmulation() - } - - else -> Log.debug("[EmulationFragment] Bug, run called while already running.") - } - state = State.RUNNING - } - - private enum class State { - STOPPED, RUNNING, PAUSED - } - } - - companion object { - private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) - private val thermalStatsUpdateHandler = Handler(Looper.myLooper()!!) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameFolderPropertiesDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameFolderPropertiesDialogFragment.kt deleted file mode 100644 index b75bd538..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameFolderPropertiesDialogFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.content.DialogInterface -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.DialogFolderPropertiesBinding -import org.sudachi.sudachi_emu.model.GameDir -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.utils.NativeConfig -import org.sudachi.sudachi_emu.utils.SerializableHelper.parcelable - -class GameFolderPropertiesDialogFragment : DialogFragment() { - private val gamesViewModel: GamesViewModel by activityViewModels() - - private var deepScan = false - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val binding = DialogFolderPropertiesBinding.inflate(layoutInflater) - val gameDir = requireArguments().parcelable(GAME_DIR)!! - - // Restore checkbox state - binding.deepScanSwitch.isChecked = - savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan - - // Ensure that we can get the checkbox state even if the view is destroyed - deepScan = binding.deepScanSwitch.isChecked - binding.deepScanSwitch.setOnClickListener { - deepScan = binding.deepScanSwitch.isChecked - } - - return MaterialAlertDialogBuilder(requireContext()) - .setView(binding.root) - .setTitle(R.string.game_folder_properties) - .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - val folderIndex = gamesViewModel.folders.value.indexOf(gameDir) - if (folderIndex != -1) { - gamesViewModel.folders.value[folderIndex].deepScan = - binding.deepScanSwitch.isChecked - gamesViewModel.updateGameDirs() - } - } - .setNegativeButton(android.R.string.cancel, null) - .show() - } - - override fun onStop() { - super.onStop() - NativeConfig.saveGlobalConfig() - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putBoolean(DEEP_SCAN, deepScan) - } - - companion object { - const val TAG = "GameFolderPropertiesDialogFragment" - - private const val GAME_DIR = "GameDir" - - private const val DEEP_SCAN = "DeepScan" - - fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment { - val args = Bundle() - args.putParcelable(GAME_DIR, gameDir) - val fragment = GameFolderPropertiesDialogFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameInfoFragment.kt deleted file mode 100644 index 9c13afeb..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/GameInfoFragment.kt +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.navArgs -import com.google.android.material.transition.MaterialSharedAxis -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.FragmentGameInfoBinding -import org.sudachi.sudachi_emu.model.GameVerificationResult -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.utils.GameMetadata -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import org.sudachi.sudachi_emu.utils.ViewUtils.updateMargins - -class GameInfoFragment : Fragment() { - private var _binding: FragmentGameInfoBinding? = null - private val binding get() = _binding!! - - private val homeViewModel: HomeViewModel by activityViewModels() - - private val args by navArgs() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - - // Check for an up-to-date version string - args.game.version = GameMetadata.getVersion(args.game.path, true) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentGameInfoBinding.inflate(inflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - homeViewModel.setNavigationVisibility(visible = false, animated = false) - homeViewModel.setStatusBarShadeVisibility(false) - - binding.apply { - toolbarInfo.title = args.game.title - toolbarInfo.setNavigationOnClickListener { - view.findNavController().popBackStack() - } - - val pathString = Uri.parse(args.game.path).path ?: "" - path.setHint(R.string.path) - pathField.setText(pathString) - pathField.setOnClickListener { copyToClipboard(getString(R.string.path), pathString) } - - programId.setHint(R.string.program_id) - programIdField.setText(args.game.programIdHex) - programIdField.setOnClickListener { - copyToClipboard(getString(R.string.program_id), args.game.programIdHex) - } - - if (args.game.developer.isNotEmpty()) { - developer.setHint(R.string.developer) - developerField.setText(args.game.developer) - developerField.setOnClickListener { - copyToClipboard(getString(R.string.developer), args.game.developer) - } - } else { - developer.setVisible(false) - } - - version.setHint(R.string.version) - versionField.setText(args.game.version) - versionField.setOnClickListener { - copyToClipboard(getString(R.string.version), args.game.version) - } - - buttonCopy.setOnClickListener { - val details = """ - ${args.game.title} - ${getString(R.string.path)} - $pathString - ${getString(R.string.program_id)} - ${args.game.programIdHex} - ${getString(R.string.developer)} - ${args.game.developer} - ${getString(R.string.version)} - ${args.game.version} - """.trimIndent() - copyToClipboard(args.game.title, details) - } - - buttonVerifyIntegrity.setOnClickListener { - ProgressDialogFragment.newInstance( - requireActivity(), - R.string.verifying, - true - ) { progressCallback, _ -> - val result = GameVerificationResult.from( - NativeLibrary.verifyGameContents( - args.game.path, - progressCallback - ) - ) - return@newInstance when (result) { - GameVerificationResult.Success -> - MessageDialogFragment.newInstance( - titleId = R.string.verify_success, - descriptionId = R.string.operation_completed_successfully - ) - - GameVerificationResult.Failed -> - MessageDialogFragment.newInstance( - titleId = R.string.verify_failure, - descriptionId = R.string.verify_failure_description - ) - - GameVerificationResult.NotImplemented -> - MessageDialogFragment.newInstance( - titleId = R.string.verify_no_result, - descriptionId = R.string.verify_no_result_description - ) - } - }.show(parentFragmentManager, ProgressDialogFragment.TAG) - } - } - - setInsets() - } - - private fun copyToClipboard(label: String, body: String) { - val clipBoard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText(label, body) - clipBoard.setPrimaryClip(clip) - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - Toast.makeText( - requireContext(), - R.string.copied_to_clipboard, - Toast.LENGTH_SHORT - ).show() - } - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - - val leftInsets = barInsets.left + cutoutInsets.left - val rightInsets = barInsets.right + cutoutInsets.right - - binding.toolbarInfo.updateMargins(left = leftInsets, right = rightInsets) - binding.scrollInfo.updateMargins(left = leftInsets, right = rightInsets) - - binding.contentInfo.updatePadding(bottom = barInsets.bottom) - - windowInsets - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicenseBottomSheetDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicenseBottomSheetDialogFragment.kt deleted file mode 100644 index a525bd3d..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicenseBottomSheetDialogFragment.kt +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.sudachi.sudachi_emu.databinding.DialogLicenseBinding -import org.sudachi.sudachi_emu.model.License -import org.sudachi.sudachi_emu.utils.SerializableHelper.parcelable - -class LicenseBottomSheetDialogFragment : BottomSheetDialogFragment() { - private var _binding: DialogLicenseBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = DialogLicenseBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - BottomSheetBehavior.from(view.parent as View).state = - BottomSheetBehavior.STATE_HALF_EXPANDED - - val license = requireArguments().parcelable(LICENSE)!! - - binding.apply { - textTitle.setText(license.titleId) - textLink.setText(license.linkId) - textCopyright.setText(license.copyrightId) - textLicense.setText(license.licenseId) - } - } - - companion object { - const val TAG = "LicenseBottomSheetDialogFragment" - - const val LICENSE = "License" - - fun newInstance( - license: License - ): LicenseBottomSheetDialogFragment { - val dialog = LicenseBottomSheetDialogFragment() - val bundle = Bundle() - bundle.putParcelable(LICENSE, license) - dialog.arguments = bundle - return dialog - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicensesFragment.kt deleted file mode 100644 index 76dec479..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/LicensesFragment.kt +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.transition.MaterialSharedAxis -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.adapters.LicenseAdapter -import org.sudachi.sudachi_emu.databinding.FragmentLicensesBinding -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.model.License -import org.sudachi.sudachi_emu.utils.ViewUtils.updateMargins - -class LicensesFragment : Fragment() { - private var _binding: FragmentLicensesBinding? = null - private val binding get() = _binding!! - - private val homeViewModel: HomeViewModel by activityViewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentLicensesBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - homeViewModel.setNavigationVisibility(visible = false, animated = true) - homeViewModel.setStatusBarShadeVisibility(visible = false) - - binding.toolbarLicenses.setNavigationOnClickListener { - binding.root.findNavController().popBackStack() - } - - val licenses = listOf( - License( - R.string.license_fidelityfx_fsr, - R.string.license_fidelityfx_fsr_description, - R.string.license_fidelityfx_fsr_link, - R.string.license_fidelityfx_fsr_copyright, - R.string.license_fidelityfx_fsr_text - ), - License( - R.string.license_cubeb, - R.string.license_cubeb_description, - R.string.license_cubeb_link, - R.string.license_cubeb_copyright, - R.string.license_cubeb_text - ), - License( - R.string.license_dynarmic, - R.string.license_dynarmic_description, - R.string.license_dynarmic_link, - R.string.license_dynarmic_copyright, - R.string.license_dynarmic_text - ), - License( - R.string.license_ffmpeg, - R.string.license_ffmpeg_description, - R.string.license_ffmpeg_link, - R.string.license_ffmpeg_copyright, - R.string.license_ffmpeg_text - ), - License( - R.string.license_opus, - R.string.license_opus_description, - R.string.license_opus_link, - R.string.license_opus_copyright, - R.string.license_opus_text - ), - License( - R.string.license_sirit, - R.string.license_sirit_description, - R.string.license_sirit_link, - R.string.license_sirit_copyright, - R.string.license_sirit_text - ), - License( - R.string.license_adreno_tools, - R.string.license_adreno_tools_description, - R.string.license_adreno_tools_link, - R.string.license_adreno_tools_copyright, - R.string.license_adreno_tools_text - ) - ) - - binding.listLicenses.apply { - layoutManager = LinearLayoutManager(requireContext()) - adapter = LicenseAdapter(requireActivity() as AppCompatActivity, licenses) - } - - setInsets() - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - - val leftInsets = barInsets.left + cutoutInsets.left - val rightInsets = barInsets.right + cutoutInsets.right - - binding.appbarLicenses.updateMargins(left = leftInsets, right = rightInsets) - binding.listLicenses.updateMargins(left = leftInsets, right = rightInsets) - - binding.listLicenses.updatePadding(bottom = barInsets.bottom) - - windowInsets - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/PermissionDeniedDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/PermissionDeniedDialogFragment.kt deleted file mode 100644 index 33ccdfb9..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/PermissionDeniedDialogFragment.kt +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.provider.Settings -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R - -class PermissionDeniedDialogFragment : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return MaterialAlertDialogBuilder(requireContext()) - .setPositiveButton(R.string.home_settings) { _: DialogInterface?, _: Int -> - openSettings() - } - .setNegativeButton(android.R.string.cancel, null) - .setTitle(R.string.permission_denied) - .setMessage(R.string.permission_denied_description) - .show() - } - - private fun openSettings() { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - val uri = Uri.fromParts("package", requireActivity().packageName, null) - intent.data = uri - startActivity(intent) - } - - companion object { - const val TAG = "PermissionDeniedDialogFragment" - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ResetSettingsDialogFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ResetSettingsDialogFragment.kt deleted file mode 100644 index 8a824752..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/ResetSettingsDialogFragment.kt +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.features.settings.ui.SettingsActivity - -class ResetSettingsDialogFragment : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val settingsActivity = requireActivity() as SettingsActivity - - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.reset_all_settings) - .setMessage(R.string.reset_all_settings_description) - .setPositiveButton(android.R.string.ok) { _, _ -> - settingsActivity.onSettingsReset() - } - .setNegativeButton(android.R.string.cancel, null) - .show() - } - - companion object { - const val TAG = "ResetSettingsDialogFragment" - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/SearchFragment.kt deleted file mode 100644 index 803e00c0..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/fragments/SearchFragment.kt +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.fragments - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.core.widget.doOnTextChanged -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.preference.PreferenceManager -import info.debatty.java.stringsimilarity.Jaccard -import info.debatty.java.stringsimilarity.JaroWinkler -import java.util.Locale -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.adapters.GameAdapter -import org.sudachi.sudachi_emu.databinding.FragmentSearchBinding -import org.sudachi.sudachi_emu.layout.AutofitGridLayoutManager -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import org.sudachi.sudachi_emu.utils.collect - -class SearchFragment : Fragment() { - private var _binding: FragmentSearchBinding? = null - private val binding get() = _binding!! - - private val gamesViewModel: GamesViewModel by activityViewModels() - private val homeViewModel: HomeViewModel by activityViewModels() - - private lateinit var preferences: SharedPreferences - - companion object { - private const val SEARCH_TEXT = "SearchText" - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentSearchBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - homeViewModel.setNavigationVisibility(visible = true, animated = true) - homeViewModel.setStatusBarShadeVisibility(true) - preferences = PreferenceManager.getDefaultSharedPreferences(SudachiApplication.appContext) - - if (savedInstanceState != null) { - binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT)) - } - - binding.gridGamesSearch.apply { - layoutManager = AutofitGridLayoutManager( - requireContext(), - requireContext().resources.getDimensionPixelSize(R.dimen.card_width) - ) - adapter = GameAdapter(requireActivity() as AppCompatActivity) - } - - binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() } - - binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int -> - binding.clearButton.setVisible(text.toString().isNotEmpty()) - filterAndSearch() - } - - gamesViewModel.searchFocused.collect( - viewLifecycleOwner, - resetState = { gamesViewModel.setSearchFocused(false) } - ) { if (it) focusSearch() } - gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() } - gamesViewModel.searchedGames.collect(viewLifecycleOwner) { - (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) - binding.noResultsView.setVisible(it.isNotEmpty()) - } - - binding.clearButton.setOnClickListener { binding.searchText.setText("") } - - binding.searchBackground.setOnClickListener { focusSearch() } - - setInsets() - filterAndSearch() - } - - private inner class ScoredGame(val score: Double, val item: Game) - - private fun filterAndSearch() { - val baseList = gamesViewModel.games.value - val filteredList: List = when (binding.chipGroup.checkedChipId) { - R.id.chip_recently_played -> { - baseList.filter { - val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L) - lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) - }.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) } - } - - R.id.chip_recently_added -> { - baseList.filter { - val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L) - addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) - }.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) } - } - - R.id.chip_homebrew -> baseList.filter { it.isHomebrew } - - R.id.chip_retail -> baseList.filter { !it.isHomebrew } - - else -> baseList - } - - if (binding.searchText.text.toString().isEmpty() && - binding.chipGroup.checkedChipId != View.NO_ID - ) { - gamesViewModel.setSearchedGames(filteredList) - return - } - - val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault()) - val searchAlgorithm = if (searchTerm.length > 1) Jaccard(2) else JaroWinkler() - val sortedList: List = filteredList.mapNotNull { game -> - val title = game.title.lowercase(Locale.getDefault()) - val score = searchAlgorithm.similarity(searchTerm, title) - if (score > 0.03) { - ScoredGame(score, game) - } else { - null - } - }.sortedByDescending { it.score }.map { it.item } - gamesViewModel.setSearchedGames(sortedList) - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - if (_binding != null) { - outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) - } - } - - private fun focusSearch() { - if (_binding != null) { - binding.searchText.requestFocus() - val imm = requireActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? - imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) - } - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { view: View, windowInsets: WindowInsetsCompat -> - val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) - val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) - val spacingNavigationRail = - resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail) - val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip) - - binding.constraintSearch.updatePadding( - left = barInsets.left + cutoutInsets.left, - top = barInsets.top, - right = barInsets.right + cutoutInsets.right - ) - - binding.gridGamesSearch.updatePadding( - top = extraListSpacing, - bottom = barInsets.bottom + spacingNavigation + extraListSpacing - ) - binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom) - - val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams - if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) { - binding.frameSearch.updatePadding(left = spacingNavigationRail) - binding.gridGamesSearch.updatePadding(left = spacingNavigationRail) - binding.noResultsView.updatePadding(left = spacingNavigationRail) - binding.chipGroup.updatePadding( - left = chipSpacing + spacingNavigationRail, - right = chipSpacing - ) - mlpDivider.leftMargin = chipSpacing + spacingNavigationRail - mlpDivider.rightMargin = chipSpacing - } else { - binding.frameSearch.updatePadding(right = spacingNavigationRail) - binding.gridGamesSearch.updatePadding(right = spacingNavigationRail) - binding.noResultsView.updatePadding(right = spacingNavigationRail) - binding.chipGroup.updatePadding( - left = chipSpacing, - right = chipSpacing + spacingNavigationRail - ) - mlpDivider.leftMargin = chipSpacing - mlpDivider.rightMargin = chipSpacing + spacingNavigationRail - } - binding.divider.layoutParams = mlpDivider - - windowInsets - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/layout/AutofitGridLayoutManager.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/layout/AutofitGridLayoutManager.kt deleted file mode 100644 index aedda0e2..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/layout/AutofitGridLayoutManager.kt +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.layout - -import android.content.Context -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.Recycler -import org.sudachi.sudachi_emu.R - -/** - * Cut down version of the solution provided here - * https://stackoverflow.com/questions/26666143/recyclerview-gridlayoutmanager-how-to-auto-detect-span-count - */ -class AutofitGridLayoutManager( - context: Context, - columnWidth: Int -) : GridLayoutManager(context, 1) { - private var columnWidth = 0 - private var isColumnWidthChanged = true - private var lastWidth = 0 - private var lastHeight = 0 - - init { - setColumnWidth(checkedColumnWidth(context, columnWidth)) - } - - private fun checkedColumnWidth(context: Context, columnWidth: Int): Int { - var newColumnWidth = columnWidth - if (newColumnWidth <= 0) { - newColumnWidth = context.resources.getDimensionPixelSize(R.dimen.spacing_xtralarge) - } - return newColumnWidth - } - - private fun setColumnWidth(newColumnWidth: Int) { - if (newColumnWidth > 0 && newColumnWidth != columnWidth) { - columnWidth = newColumnWidth - isColumnWidthChanged = true - } - } - - override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { - val width = width - val height = height - if (columnWidth > 0 && width > 0 && height > 0 && - (isColumnWidthChanged || lastWidth != width || lastHeight != height) - ) { - val totalSpace: Int = if (orientation == VERTICAL) { - width - paddingRight - paddingLeft - } else { - height - paddingTop - paddingBottom - } - val spanCount = 1.coerceAtLeast(totalSpace / columnWidth) - setSpanCount(spanCount) - isColumnWidthChanged = false - } - lastWidth = width - lastHeight = height - super.onLayoutChildren(recycler, state) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/AddonViewModel.kt deleted file mode 100644 index b83ad2fa..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/AddonViewModel.kt +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.utils.NativeConfig -import java.util.concurrent.atomic.AtomicBoolean - -class AddonViewModel : ViewModel() { - private val _patchList = MutableStateFlow(mutableListOf()) - val addonList get() = _patchList.asStateFlow() - - private val _showModInstallPicker = MutableStateFlow(false) - val showModInstallPicker get() = _showModInstallPicker.asStateFlow() - - private val _showModNoticeDialog = MutableStateFlow(false) - val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow() - - private val _addonToDelete = MutableStateFlow(null) - val addonToDelete = _addonToDelete.asStateFlow() - - var game: Game? = null - - private val isRefreshing = AtomicBoolean(false) - - fun onOpenAddons(game: Game) { - this.game = game - refreshAddons() - } - - fun refreshAddons() { - if (isRefreshing.get() || game == null) { - return - } - isRefreshing.set(true) - viewModelScope.launch { - withContext(Dispatchers.IO) { - val patchList = ( - NativeLibrary.getPatchesForFile(game!!.path, game!!.programId) - ?: emptyArray() - ).toMutableList() - patchList.sortBy { it.name } - _patchList.value = patchList - isRefreshing.set(false) - } - } - } - - fun setAddonToDelete(patch: Patch?) { - _addonToDelete.value = patch - } - - fun onDeleteAddon(patch: Patch) { - when (PatchType.from(patch.type)) { - PatchType.Update -> NativeLibrary.removeUpdate(patch.programId) - PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) - PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) - } - refreshAddons() - } - - fun onCloseAddons() { - if (_patchList.value.isEmpty()) { - return - } - - NativeConfig.setDisabledAddons( - game!!.programId, - _patchList.value.mapNotNull { - if (it.enabled) { - null - } else { - it.name - } - }.toTypedArray() - ) - NativeConfig.saveGlobalConfig() - _patchList.value.clear() - game = null - } - - fun showModInstallPicker(install: Boolean) { - _showModInstallPicker.value = install - } - - fun showModNoticeDialog(show: Boolean) { - _showModNoticeDialog.value = show - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Applet.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Applet.kt deleted file mode 100644 index a7c9aef5..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Applet.kt +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import org.sudachi.sudachi_emu.R - -data class Applet( - @StringRes val titleId: Int, - @StringRes val descriptionId: Int, - @DrawableRes val iconId: Int, - val appletInfo: AppletInfo, - val cabinetMode: CabinetMode = CabinetMode.None -) - -// Combination of Common::AM::Applets::AppletId enum and the entry id -enum class AppletInfo(val appletId: Int, val entryId: Long = 0) { - None(0x00), - Application(0x01), - OverlayDisplay(0x02), - QLaunch(0x03), - Starter(0x04), - Auth(0x0A), - Cabinet(0x0B, 0x0100000000001002), - Controller(0x0C), - DataErase(0x0D), - Error(0x0E), - NetConnect(0x0F), - ProfileSelect(0x10), - SoftwareKeyboard(0x11), - MiiEdit(0x12, 0x0100000000001009), - Web(0x13), - Shop(0x14), - PhotoViewer(0x015, 0x010000000000100D), - Settings(0x16), - OfflineWeb(0x17), - LoginShare(0x18), - WebAuth(0x19), - MyPage(0x1A) -} - -// Matches enum in Service::NFP::CabinetMode with extra metadata -enum class CabinetMode( - val id: Int, - @StringRes val titleId: Int = 0, - @DrawableRes val iconId: Int = 0 -) { - None(-1), - StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit), - StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh), - StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore), - StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Driver.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Driver.kt deleted file mode 100644 index 756fa254..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Driver.kt +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import org.sudachi.sudachi_emu.utils.GpuDriverMetadata - -data class Driver( - override var selected: Boolean, - val title: String, - val version: String = "", - val description: String = "" -) : SelectableItem { - override fun onSelectionStateChanged(selected: Boolean) { - this.selected = selected - } - - companion object { - fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver = - Driver( - selected, - this.name ?: "", - this.version ?: "", - this.description ?: "" - ) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/GamesViewModel.kt deleted file mode 100644 index eb3c5322..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/GamesViewModel.kt +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import android.net.Uri -import androidx.documentfile.provider.DocumentFile -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.preference.PreferenceManager -import java.util.Locale -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.utils.GameHelper -import org.sudachi.sudachi_emu.utils.NativeConfig -import java.util.concurrent.atomic.AtomicBoolean - -class GamesViewModel : ViewModel() { - val games: StateFlow> get() = _games - private val _games = MutableStateFlow(emptyList()) - - val searchedGames: StateFlow> get() = _searchedGames - private val _searchedGames = MutableStateFlow(emptyList()) - - val isReloading: StateFlow get() = _isReloading - private val _isReloading = MutableStateFlow(false) - - private val reloading = AtomicBoolean(false) - - val shouldSwapData: StateFlow get() = _shouldSwapData - private val _shouldSwapData = MutableStateFlow(false) - - val shouldScrollToTop: StateFlow get() = _shouldScrollToTop - private val _shouldScrollToTop = MutableStateFlow(false) - - val searchFocused: StateFlow get() = _searchFocused - private val _searchFocused = MutableStateFlow(false) - - private val _folders = MutableStateFlow(mutableListOf()) - val folders = _folders.asStateFlow() - - init { - // Ensure keys are loaded so that ROM metadata can be decrypted. - NativeLibrary.reloadKeys() - - getGameDirs() - reloadGames(directoriesChanged = false, firstStartup = true) - } - - fun setGames(games: List) { - val sortedList = games.sortedWith( - compareBy( - { it.title.lowercase(Locale.getDefault()) }, - { it.path } - ) - ) - - _games.value = sortedList - } - - fun setSearchedGames(games: List) { - _searchedGames.value = games - } - - fun setShouldSwapData(shouldSwap: Boolean) { - _shouldSwapData.value = shouldSwap - } - - fun setShouldScrollToTop(shouldScroll: Boolean) { - _shouldScrollToTop.value = shouldScroll - } - - fun setSearchFocused(searchFocused: Boolean) { - _searchFocused.value = searchFocused - } - - fun reloadGames(directoriesChanged: Boolean, firstStartup: Boolean = false) { - if (reloading.get()) { - return - } - reloading.set(true) - _isReloading.value = true - - viewModelScope.launch { - withContext(Dispatchers.IO) { - if (firstStartup) { - // Retrieve list of cached games - val storedGames = - PreferenceManager.getDefaultSharedPreferences(SudachiApplication.appContext) - .getStringSet(GameHelper.KEY_GAMES, emptySet()) - if (storedGames!!.isNotEmpty()) { - val deserializedGames = mutableSetOf() - storedGames.forEach { - val game: Game - try { - game = Json.decodeFromString(it) - } catch (e: Exception) { - // We don't care about any errors related to parsing the game cache - return@forEach - } - - val gameExists = - DocumentFile.fromSingleUri( - SudachiApplication.appContext, - Uri.parse(game.path) - )?.exists() - if (gameExists == true) { - deserializedGames.add(game) - } - } - setGames(deserializedGames.toList()) - } - } - - setGames(GameHelper.getGames()) - reloading.set(false) - _isReloading.value = false - - if (directoriesChanged) { - setShouldSwapData(true) - } - } - } - } - - fun addFolder(gameDir: GameDir) = - viewModelScope.launch { - withContext(Dispatchers.IO) { - NativeConfig.addGameDir(gameDir) - getGameDirs(true) - } - } - - fun removeFolder(gameDir: GameDir) = - viewModelScope.launch { - withContext(Dispatchers.IO) { - val gameDirs = _folders.value.toMutableList() - val removedDirIndex = gameDirs.indexOf(gameDir) - if (removedDirIndex != -1) { - gameDirs.removeAt(removedDirIndex) - NativeConfig.setGameDirs(gameDirs.toTypedArray()) - getGameDirs() - } - } - } - - fun updateGameDirs() = - viewModelScope.launch { - withContext(Dispatchers.IO) { - NativeConfig.setGameDirs(_folders.value.toTypedArray()) - getGameDirs() - } - } - - fun onOpenGameFoldersFragment() = - viewModelScope.launch { - withContext(Dispatchers.IO) { - getGameDirs() - } - } - - fun onCloseGameFoldersFragment() { - NativeConfig.saveGlobalConfig() - viewModelScope.launch { - withContext(Dispatchers.IO) { - getGameDirs(true) - } - } - } - - private fun getGameDirs(reloadList: Boolean = false) { - val gameDirs = NativeConfig.getGameDirs() - _folders.value = gameDirs.toMutableList() - if (reloadList) { - reloadGames(true) - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeSetting.kt deleted file mode 100644 index dc9aa5e3..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeSetting.kt +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow - -data class HomeSetting( - val titleId: Int, - val descriptionId: Int, - val iconId: Int, - val onClick: () -> Unit, - val isEnabled: () -> Boolean = { true }, - val disabledTitleId: Int = 0, - val disabledMessageId: Int = 0, - val details: StateFlow = MutableStateFlow("") -) diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeViewModel.kt deleted file mode 100644 index d003e36a..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/HomeViewModel.kt +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -package org.sudachi.sudachi_emu.model - -import android.net.Uri -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -class HomeViewModel : ViewModel() { - val navigationVisible: StateFlow> get() = _navigationVisible - private val _navigationVisible = MutableStateFlow(Pair(false, false)) - - val statusBarShadeVisible: StateFlow get() = _statusBarShadeVisible - private val _statusBarShadeVisible = MutableStateFlow(true) - - val shouldPageForward: StateFlow get() = _shouldPageForward - private val _shouldPageForward = MutableStateFlow(false) - - private val _gamesDirSelected = MutableStateFlow(false) - val gamesDirSelected get() = _gamesDirSelected.asStateFlow() - - private val _openImportSaves = MutableStateFlow(false) - val openImportSaves get() = _openImportSaves.asStateFlow() - - private val _contentToInstall = MutableStateFlow?>(null) - val contentToInstall get() = _contentToInstall.asStateFlow() - - private val _reloadPropertiesList = MutableStateFlow(false) - val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow() - - private val _checkKeys = MutableStateFlow(false) - val checkKeys = _checkKeys.asStateFlow() - - var navigatedToSetup = false - - fun setNavigationVisibility(visible: Boolean, animated: Boolean) { - if (navigationVisible.value.first == visible) { - return - } - _navigationVisible.value = Pair(visible, animated) - } - - fun setStatusBarShadeVisibility(visible: Boolean) { - if (statusBarShadeVisible.value == visible) { - return - } - _statusBarShadeVisible.value = visible - } - - fun setShouldPageForward(pageForward: Boolean) { - _shouldPageForward.value = pageForward - } - - fun setGamesDirSelected(selected: Boolean) { - _gamesDirSelected.value = selected - } - - fun setOpenImportSaves(import: Boolean) { - _openImportSaves.value = import - } - - fun setContentToInstall(documents: List?) { - _contentToInstall.value = documents - } - - fun reloadPropertiesList(reload: Boolean) { - _reloadPropertiesList.value = reload - } - - fun setCheckKeys(value: Boolean) { - _checkKeys.value = value - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/InstallResult.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/InstallResult.kt deleted file mode 100644 index c736305f..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/InstallResult.kt +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -enum class InstallResult(val int: Int) { - Success(0), - Overwrite(1), - Failure(2), - BaseInstallAttempted(3); - - companion object { - fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/License.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/License.kt deleted file mode 100644 index 24f28007..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/License.kt +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class License( - val titleId: Int, - val descriptionId: Int, - val linkId: Int, - val copyrightId: Int, - val licenseId: Int -) : Parcelable diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Patch.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Patch.kt deleted file mode 100644 index b556efbd..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/Patch.kt +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -import androidx.annotation.Keep - -@Keep -data class Patch( - var enabled: Boolean, - val name: String, - val version: String, - val type: Int, - val programId: String, - val titleId: String -) diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/PatchType.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/PatchType.kt deleted file mode 100644 index 305d75d0..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/PatchType.kt +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -enum class PatchType(val int: Int) { - Update(0), - DLC(1), - Mod(2); - - companion object { - fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/SelectableItem.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/SelectableItem.kt deleted file mode 100644 index c24819a5..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/model/SelectableItem.kt +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.model - -interface SelectableItem { - var selected: Boolean - fun onSelectionStateChanged(selected: Boolean) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableButton.kt deleted file mode 100644 index 4c5bc1a9..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableButton.kt +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.overlay - -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.BitmapDrawable -import android.view.MotionEvent -import org.sudachi.sudachi_emu.features.input.NativeInput.ButtonState -import org.sudachi.sudachi_emu.features.input.model.NativeButton -import org.sudachi.sudachi_emu.overlay.model.OverlayControlData - -/** - * Custom [BitmapDrawable] that is capable - * of storing it's own ID. - * - * @param res [Resources] instance. - * @param defaultStateBitmap [Bitmap] to use with the default state Drawable. - * @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable. - * @param button [NativeButton] for this type of button. - */ -class InputOverlayDrawableButton( - res: Resources, - defaultStateBitmap: Bitmap, - pressedStateBitmap: Bitmap, - val button: NativeButton, - val overlayControlData: OverlayControlData -) { - // The ID value what motion event is tracking - var trackId: Int - - // The drawable position on the screen - private var buttonPositionX = 0 - private var buttonPositionY = 0 - - val width: Int - val height: Int - - private val defaultStateBitmap: BitmapDrawable - private val pressedStateBitmap: BitmapDrawable - private var pressedState = false - - private var previousTouchX = 0 - private var previousTouchY = 0 - var controlPositionX = 0 - var controlPositionY = 0 - - init { - this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap) - this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap) - trackId = -1 - width = this.defaultStateBitmap.intrinsicWidth - height = this.defaultStateBitmap.intrinsicHeight - } - - /** - * Updates button status based on the motion event. - * - * @return true if value was changed - */ - fun updateStatus(event: MotionEvent): Boolean { - val pointerIndex = event.actionIndex - val xPosition = event.getX(pointerIndex).toInt() - val yPosition = event.getY(pointerIndex).toInt() - val pointerId = event.getPointerId(pointerIndex) - val motionEvent = event.action and MotionEvent.ACTION_MASK - val isActionDown = - motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN - val isActionUp = - motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP - - if (isActionDown) { - if (!bounds.contains(xPosition, yPosition)) { - return false - } - pressedState = true - trackId = pointerId - return true - } - - if (isActionUp) { - if (trackId != pointerId) { - return false - } - pressedState = false - trackId = -1 - return true - } - - return false - } - - fun setPosition(x: Int, y: Int) { - buttonPositionX = x - buttonPositionY = y - } - - fun draw(canvas: Canvas?) { - currentStateBitmapDrawable.draw(canvas!!) - } - - private val currentStateBitmapDrawable: BitmapDrawable - get() = if (pressedState) pressedStateBitmap else defaultStateBitmap - - fun onConfigureTouch(event: MotionEvent): Boolean { - val pointerIndex = event.actionIndex - val fingerPositionX = event.getX(pointerIndex).toInt() - val fingerPositionY = event.getY(pointerIndex).toInt() - - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previousTouchX = fingerPositionX - previousTouchY = fingerPositionY - controlPositionX = fingerPositionX - (width / 2) - controlPositionY = fingerPositionY - (height / 2) - } - - MotionEvent.ACTION_MOVE -> { - controlPositionX += fingerPositionX - previousTouchX - controlPositionY += fingerPositionY - previousTouchY - setBounds( - controlPositionX, - controlPositionY, - width + controlPositionX, - height + controlPositionY - ) - previousTouchX = fingerPositionX - previousTouchY = fingerPositionY - } - } - return true - } - - fun setBounds(left: Int, top: Int, right: Int, bottom: Int) { - defaultStateBitmap.setBounds(left, top, right, bottom) - pressedStateBitmap.setBounds(left, top, right, bottom) - } - - fun setOpacity(value: Int) { - defaultStateBitmap.alpha = value - pressedStateBitmap.alpha = value - } - - val status: Int - get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED - val bounds: Rect - get() = defaultStateBitmap.bounds -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableDpad.kt deleted file mode 100644 index 82ba2811..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/InputOverlayDrawableDpad.kt +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.overlay - -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.BitmapDrawable -import android.view.MotionEvent -import org.sudachi.sudachi_emu.features.input.NativeInput.ButtonState -import org.sudachi.sudachi_emu.features.input.model.NativeButton - -/** - * Custom [BitmapDrawable] that is capable - * of storing it's own ID. - * - * @param res [Resources] instance. - * @param defaultStateBitmap [Bitmap] of the default state. - * @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction. - * @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction. - */ -class InputOverlayDrawableDpad( - res: Resources, - defaultStateBitmap: Bitmap, - pressedOneDirectionStateBitmap: Bitmap, - pressedTwoDirectionsStateBitmap: Bitmap -) { - /** - * Gets one of the InputOverlayDrawableDpad's button IDs. - * - * @return the requested InputOverlayDrawableDpad's button ID. - */ - // The ID identifying what type of button this Drawable represents. - val up = NativeButton.DUp - val down = NativeButton.DDown - val left = NativeButton.DLeft - val right = NativeButton.DRight - var trackId: Int - - val width: Int - val height: Int - - private val defaultStateBitmap: BitmapDrawable - private val pressedOneDirectionStateBitmap: BitmapDrawable - private val pressedTwoDirectionsStateBitmap: BitmapDrawable - - private var previousTouchX = 0 - private var previousTouchY = 0 - private var controlPositionX = 0 - private var controlPositionY = 0 - - private var upButtonState = false - private var downButtonState = false - private var leftButtonState = false - private var rightButtonState = false - - init { - this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap) - this.pressedOneDirectionStateBitmap = BitmapDrawable(res, pressedOneDirectionStateBitmap) - this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap) - width = this.defaultStateBitmap.intrinsicWidth - height = this.defaultStateBitmap.intrinsicHeight - trackId = -1 - } - - fun updateStatus(event: MotionEvent, dpad_slide: Boolean): Boolean { - val pointerIndex = event.actionIndex - val xPosition = event.getX(pointerIndex).toInt() - val yPosition = event.getY(pointerIndex).toInt() - val pointerId = event.getPointerId(pointerIndex) - val motionEvent = event.action and MotionEvent.ACTION_MASK - val isActionDown = - motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN - val isActionUp = - motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP - if (isActionDown) { - if (!bounds.contains(xPosition, yPosition)) { - return false - } - trackId = pointerId - } - if (isActionUp) { - if (trackId != pointerId) { - return false - } - trackId = -1 - upButtonState = false - downButtonState = false - leftButtonState = false - rightButtonState = false - return true - } - if (trackId == -1) { - return false - } - if (!dpad_slide && !isActionDown) { - return false - } - for (i in 0 until event.pointerCount) { - if (trackId != event.getPointerId(i)) { - continue - } - - var touchX = event.getX(i) - var touchY = event.getY(i) - var maxY = bounds.bottom.toFloat() - var maxX = bounds.right.toFloat() - touchX -= bounds.centerX().toFloat() - maxX -= bounds.centerX().toFloat() - touchY -= bounds.centerY().toFloat() - maxY -= bounds.centerY().toFloat() - val axisX = touchX / maxX - val axisY = touchY / maxY - val oldUpState = upButtonState - val oldDownState = downButtonState - val oldLeftState = leftButtonState - val oldRightState = rightButtonState - - upButtonState = axisY < -VIRT_AXIS_DEADZONE - downButtonState = axisY > VIRT_AXIS_DEADZONE - leftButtonState = axisX < -VIRT_AXIS_DEADZONE - rightButtonState = axisX > VIRT_AXIS_DEADZONE - return oldUpState != upButtonState || - oldDownState != downButtonState || - oldLeftState != leftButtonState || - oldRightState != rightButtonState - } - return false - } - - fun draw(canvas: Canvas) { - val px = controlPositionX + width / 2 - val py = controlPositionY + height / 2 - - // Pressed up - if (upButtonState && !leftButtonState && !rightButtonState) { - pressedOneDirectionStateBitmap.draw(canvas) - return - } - - // Pressed down - if (downButtonState && !leftButtonState && !rightButtonState) { - canvas.save() - canvas.rotate(180f, px.toFloat(), py.toFloat()) - pressedOneDirectionStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Pressed left - if (leftButtonState && !upButtonState && !downButtonState) { - canvas.save() - canvas.rotate(270f, px.toFloat(), py.toFloat()) - pressedOneDirectionStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Pressed right - if (rightButtonState && !upButtonState && !downButtonState) { - canvas.save() - canvas.rotate(90f, px.toFloat(), py.toFloat()) - pressedOneDirectionStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Pressed up left - if (upButtonState && leftButtonState && !rightButtonState) { - pressedTwoDirectionsStateBitmap.draw(canvas) - return - } - - // Pressed up right - if (upButtonState && !leftButtonState && rightButtonState) { - canvas.save() - canvas.rotate(90f, px.toFloat(), py.toFloat()) - pressedTwoDirectionsStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Pressed down right - if (downButtonState && !leftButtonState && rightButtonState) { - canvas.save() - canvas.rotate(180f, px.toFloat(), py.toFloat()) - pressedTwoDirectionsStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Pressed down left - if (downButtonState && leftButtonState && !rightButtonState) { - canvas.save() - canvas.rotate(270f, px.toFloat(), py.toFloat()) - pressedTwoDirectionsStateBitmap.draw(canvas) - canvas.restore() - return - } - - // Not pressed - defaultStateBitmap.draw(canvas) - } - - val upStatus: Int - get() = if (upButtonState) ButtonState.PRESSED else ButtonState.RELEASED - val downStatus: Int - get() = if (downButtonState) ButtonState.PRESSED else ButtonState.RELEASED - val leftStatus: Int - get() = if (leftButtonState) ButtonState.PRESSED else ButtonState.RELEASED - val rightStatus: Int - get() = if (rightButtonState) ButtonState.PRESSED else ButtonState.RELEASED - - fun onConfigureTouch(event: MotionEvent): Boolean { - val pointerIndex = event.actionIndex - val fingerPositionX = event.getX(pointerIndex).toInt() - val fingerPositionY = event.getY(pointerIndex).toInt() - - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previousTouchX = fingerPositionX - previousTouchY = fingerPositionY - } - - MotionEvent.ACTION_MOVE -> { - controlPositionX += fingerPositionX - previousTouchX - controlPositionY += fingerPositionY - previousTouchY - setBounds( - controlPositionX, - controlPositionY, - width + controlPositionX, - height + controlPositionY - ) - previousTouchX = fingerPositionX - previousTouchY = fingerPositionY - } - } - return true - } - - fun setPosition(x: Int, y: Int) { - controlPositionX = x - controlPositionY = y - } - - fun setBounds(left: Int, top: Int, right: Int, bottom: Int) { - defaultStateBitmap.setBounds(left, top, right, bottom) - pressedOneDirectionStateBitmap.setBounds(left, top, right, bottom) - pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom) - } - - fun setOpacity(value: Int) { - defaultStateBitmap.alpha = value - pressedOneDirectionStateBitmap.alpha = value - pressedTwoDirectionsStateBitmap.alpha = value - } - - val bounds: Rect - get() = defaultStateBitmap.bounds - - companion object { - const val VIRT_AXIS_DEADZONE = 0.5f - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControl.kt deleted file mode 100644 index d0d16300..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControl.kt +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.overlay.model - -import androidx.annotation.IntegerRes -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication - -enum class OverlayControl( - val id: String, - val defaultVisibility: Boolean, - @IntegerRes val defaultLandscapePositionResources: Pair, - @IntegerRes val defaultPortraitPositionResources: Pair, - @IntegerRes val defaultFoldablePositionResources: Pair -) { - BUTTON_A( - "button_a", - true, - Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y), - Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT), - Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE) - ), - BUTTON_B( - "button_b", - true, - Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y), - Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT), - Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE) - ), - BUTTON_X( - "button_x", - true, - Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y), - Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT), - Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE) - ), - BUTTON_Y( - "button_y", - true, - Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y), - Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT), - Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE) - ), - BUTTON_PLUS( - "button_plus", - true, - Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y), - Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT), - Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE) - ), - BUTTON_MINUS( - "button_minus", - true, - Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y), - Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT), - Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE) - ), - BUTTON_HOME( - "button_home", - false, - Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y), - Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT), - Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE) - ), - BUTTON_CAPTURE( - "button_capture", - false, - Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y), - Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT), - Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE) - ), - BUTTON_L( - "button_l", - true, - Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y), - Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT), - Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE) - ), - BUTTON_R( - "button_r", - true, - Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y), - Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT), - Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE) - ), - BUTTON_ZL( - "button_zl", - true, - Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y), - Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT), - Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE) - ), - BUTTON_ZR( - "button_zr", - true, - Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y), - Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT), - Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE) - ), - BUTTON_STICK_L( - "button_stick_l", - true, - Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y), - Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT), - Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE) - ), - BUTTON_STICK_R( - "button_stick_r", - true, - Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y), - Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT), - Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE) - ), - STICK_L( - "stick_l", - true, - Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y), - Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT), - Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE) - ), - STICK_R( - "stick_r", - true, - Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y), - Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT), - Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE) - ), - COMBINED_DPAD( - "combined_dpad", - true, - Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y), - Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT), - Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE) - ); - - fun getDefaultPositionForLayout(layout: OverlayLayout): Pair { - val rawResourcePair: Pair - SudachiApplication.appContext.resources.apply { - rawResourcePair = when (layout) { - OverlayLayout.Landscape -> { - Pair( - getInteger(this@OverlayControl.defaultLandscapePositionResources.first), - getInteger(this@OverlayControl.defaultLandscapePositionResources.second) - ) - } - - OverlayLayout.Portrait -> { - Pair( - getInteger(this@OverlayControl.defaultPortraitPositionResources.first), - getInteger(this@OverlayControl.defaultPortraitPositionResources.second) - ) - } - - OverlayLayout.Foldable -> { - Pair( - getInteger(this@OverlayControl.defaultFoldablePositionResources.first), - getInteger(this@OverlayControl.defaultFoldablePositionResources.second) - ) - } - } - } - - return Pair( - rawResourcePair.first.toDouble() / 1000, - rawResourcePair.second.toDouble() / 1000 - ) - } - - fun toOverlayControlData(): OverlayControlData = - OverlayControlData( - id, - defaultVisibility, - getDefaultPositionForLayout(OverlayLayout.Landscape), - getDefaultPositionForLayout(OverlayLayout.Portrait), - getDefaultPositionForLayout(OverlayLayout.Foldable) - ) - - companion object { - val map: HashMap by lazy { - val hashMap = hashMapOf() - entries.forEach { hashMap[it.id] = it } - hashMap - } - - fun from(id: String): OverlayControl? = map[id] - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControlData.kt deleted file mode 100644 index 901187cf..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/overlay/model/OverlayControlData.kt +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.overlay.model - -data class OverlayControlData( - val id: String, - var enabled: Boolean, - var landscapePosition: Pair, - var portraitPosition: Pair, - var foldablePosition: Pair -) { - fun positionFromLayout(layout: OverlayLayout): Pair = - when (layout) { - OverlayLayout.Landscape -> landscapePosition - OverlayLayout.Portrait -> portraitPosition - OverlayLayout.Foldable -> foldablePosition - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/MainActivity.kt deleted file mode 100644 index 1a849928..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/MainActivity.kt +++ /dev/null @@ -1,692 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.ui.main - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.View -import android.view.ViewGroup.MarginLayoutParams -import android.view.WindowManager -import android.view.animation.PathInterpolator -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.core.view.ViewCompat -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.navigation.NavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.setupWithNavController -import androidx.preference.PreferenceManager -import com.google.android.material.color.MaterialColors -import com.google.android.material.navigation.NavigationBarView -import java.io.File -import java.io.FilenameFilter -import org.sudachi.sudachi_emu.HomeNavigationDirections -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.databinding.ActivityMainBinding -import org.sudachi.sudachi_emu.features.settings.model.Settings -import org.sudachi.sudachi_emu.fragments.AddGameFolderDialogFragment -import org.sudachi.sudachi_emu.fragments.ProgressDialogFragment -import org.sudachi.sudachi_emu.fragments.MessageDialogFragment -import org.sudachi.sudachi_emu.model.AddonViewModel -import org.sudachi.sudachi_emu.model.DriverViewModel -import org.sudachi.sudachi_emu.model.GamesViewModel -import org.sudachi.sudachi_emu.model.HomeViewModel -import org.sudachi.sudachi_emu.model.InstallResult -import org.sudachi.sudachi_emu.model.TaskState -import org.sudachi.sudachi_emu.model.TaskViewModel -import org.sudachi.sudachi_emu.utils.* -import org.sudachi.sudachi_emu.utils.ViewUtils.setVisible -import java.io.BufferedInputStream -import java.io.BufferedOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream - -class MainActivity : AppCompatActivity(), ThemeProvider { - private lateinit var binding: ActivityMainBinding - - private val homeViewModel: HomeViewModel by viewModels() - private val gamesViewModel: GamesViewModel by viewModels() - private val taskViewModel: TaskViewModel by viewModels() - private val addonViewModel: AddonViewModel by viewModels() - private val driverViewModel: DriverViewModel by viewModels() - - override var themeId: Int = 0 - - private val CHECKED_DECRYPTION = "CheckedDecryption" - private var checkedDecryption = false - - override fun onCreate(savedInstanceState: Bundle?) { - val splashScreen = installSplashScreen() - splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } - - ThemeHelper.setTheme(this) - - super.onCreate(savedInstanceState) - - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - if (savedInstanceState != null) { - checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION) - } - if (!checkedDecryption) { - val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) - if (!firstTimeSetup) { - checkKeys() - } - checkedDecryption = true - } - - WindowCompat.setDecorFitsSystemWindows(window, false) - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) - - window.statusBarColor = - ContextCompat.getColor(applicationContext, android.R.color.transparent) - window.navigationBarColor = - ContextCompat.getColor(applicationContext, android.R.color.transparent) - - binding.statusBarShade.setBackgroundColor( - ThemeHelper.getColorWithOpacity( - MaterialColors.getColor( - binding.root, - com.google.android.material.R.attr.colorSurface - ), - ThemeHelper.SYSTEM_BAR_ALPHA - ) - ) - if (InsetsHelper.getSystemGestureType(applicationContext) != - InsetsHelper.GESTURE_NAVIGATION - ) { - binding.navigationBarShade.setBackgroundColor( - ThemeHelper.getColorWithOpacity( - MaterialColors.getColor( - binding.root, - com.google.android.material.R.attr.colorSurface - ), - ThemeHelper.SYSTEM_BAR_ALPHA - ) - ) - } - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment - setUpNavigation(navHostFragment.navController) - (binding.navigationView as NavigationBarView).setOnItemReselectedListener { - when (it.itemId) { - R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) - R.id.searchFragment -> gamesViewModel.setSearchFocused(true) - R.id.homeSettingsFragment -> { - val action = HomeNavigationDirections.actionGlobalSettingsActivity( - null, - Settings.MenuTag.SECTION_ROOT - ) - navHostFragment.navController.navigate(action) - } - } - } - - // Prevents navigation from being drawn for a short time on recreation if set to hidden - if (!homeViewModel.navigationVisible.value.first) { - binding.navigationView.setVisible(visible = false, gone = false) - binding.statusBarShade.setVisible(visible = false, gone = false) - } - - homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) } - homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) } - homeViewModel.contentToInstall.collect( - this, - resetState = { homeViewModel.setContentToInstall(null) } - ) { - if (it != null) { - installContent(it) - } - } - homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) { - if (it) checkKeys() - } - - setInsets() - } - - private fun checkKeys() { - if (!NativeLibrary.areKeysPresent()) { - MessageDialogFragment.newInstance( - titleId = R.string.keys_missing, - descriptionId = R.string.keys_missing_description, - helpLinkId = R.string.keys_missing_help - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption) - } - - fun finishSetup(navController: NavController) { - navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment) - (binding.navigationView as NavigationBarView).setupWithNavController(navController) - showNavigation(visible = true, animated = true) - } - - private fun setUpNavigation(navController: NavController) { - val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) - - if (firstTimeSetup && !homeViewModel.navigatedToSetup) { - navController.navigate(R.id.firstTimeSetupFragment) - homeViewModel.navigatedToSetup = true - } else { - (binding.navigationView as NavigationBarView).setupWithNavController(navController) - } - } - - private fun showNavigation(visible: Boolean, animated: Boolean) { - if (!animated) { - binding.navigationView.setVisible(visible) - return - } - - val smallLayout = resources.getBoolean(R.bool.small_layout) - binding.navigationView.animate().apply { - if (visible) { - binding.navigationView.setVisible(true) - duration = 300 - interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f) - - if (smallLayout) { - binding.navigationView.translationY = - binding.navigationView.height.toFloat() * 2 - translationY(0f) - } else { - if (ViewCompat.getLayoutDirection(binding.navigationView) == - ViewCompat.LAYOUT_DIRECTION_LTR - ) { - binding.navigationView.translationX = - binding.navigationView.width.toFloat() * -2 - translationX(0f) - } else { - binding.navigationView.translationX = - binding.navigationView.width.toFloat() * 2 - translationX(0f) - } - } - } else { - duration = 300 - interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f) - - if (smallLayout) { - translationY(binding.navigationView.height.toFloat() * 2) - } else { - if (ViewCompat.getLayoutDirection(binding.navigationView) == - ViewCompat.LAYOUT_DIRECTION_LTR - ) { - translationX(binding.navigationView.width.toFloat() * -2) - } else { - translationX(binding.navigationView.width.toFloat() * 2) - } - } - } - }.withEndAction { - if (!visible) { - binding.navigationView.setVisible(visible = false, gone = false) - } - }.start() - } - - private fun showStatusBarShade(visible: Boolean) { - binding.statusBarShade.animate().apply { - if (visible) { - binding.statusBarShade.setVisible(true) - binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2 - duration = 300 - translationY(0f) - interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f) - } else { - duration = 300 - translationY(binding.navigationView.height.toFloat() * -2) - interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f) - } - }.withEndAction { - if (!visible) { - binding.statusBarShade.setVisible(visible = false, gone = false) - } - }.start() - } - - override fun onResume() { - ThemeHelper.setCorrectTheme(this) - super.onResume() - } - - private fun setInsets() = - ViewCompat.setOnApplyWindowInsetsListener( - binding.root - ) { _: View, windowInsets: WindowInsetsCompat -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams - mlpStatusShade.height = insets.top - binding.statusBarShade.layoutParams = mlpStatusShade - - // The only situation where we care to have a nav bar shade is when it's at the bottom - // of the screen where scrolling list elements can go behind it. - val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams - mlpNavShade.height = insets.bottom - binding.navigationBarShade.layoutParams = mlpNavShade - - windowInsets - } - - override fun setTheme(resId: Int) { - super.setTheme(resId) - themeId = resId - } - - val getGamesDirectory = - registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> - if (result != null) { - processGamesDir(result) - } - } - - fun processGamesDir(result: Uri) { - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - - val uriString = result.toString() - val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } - if (folder != null) { - Toast.makeText( - applicationContext, - R.string.folder_already_added, - Toast.LENGTH_SHORT - ).show() - return - } - - AddGameFolderDialogFragment.newInstance(uriString) - .show(supportFragmentManager, AddGameFolderDialogFragment.TAG) - } - - val getProdKey = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result != null) { - processKey(result) - } - } - - fun processKey(result: Uri): Boolean { - if (FileUtil.getExtension(result) != "keys") { - MessageDialogFragment.newInstance( - this, - titleId = R.string.reading_keys_failure, - descriptionId = R.string.install_prod_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return false - } - - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( - result, - dstPath, - "prod.keys" - ) != null - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - homeViewModel.setCheckKeys(true) - gamesViewModel.reloadGames(true) - return true - } else { - MessageDialogFragment.newInstance( - this, - titleId = R.string.invalid_keys_error, - descriptionId = R.string.install_keys_failure_description, - helpLinkId = R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return false - } - } - return false - } - - val getFirmware = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } - - val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } - - val firmwarePath = - File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") - val cacheFirmwareDir = File("${cacheDir.path}/registered/") - - ProgressDialogFragment.newInstance( - this, - R.string.firmware_installing - ) { progressCallback, _ -> - var messageToShow: Any - try { - FileUtil.unzipToInternalStorage( - result.toString(), - cacheFirmwareDir, - progressCallback - ) - val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 - val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 - messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { - MessageDialogFragment.newInstance( - this, - titleId = R.string.firmware_installed_failure, - descriptionId = R.string.firmware_installed_failure_description - ) - } else { - firmwarePath.deleteRecursively() - cacheFirmwareDir.copyRecursively(firmwarePath, true) - NativeLibrary.initializeSystem(true) - homeViewModel.setCheckKeys(true) - getString(R.string.save_file_imported_success) - } - } catch (e: Exception) { - Log.error("[MainActivity] Firmware install failed - ${e.message}") - messageToShow = getString(R.string.fatal_error) - } finally { - cacheFirmwareDir.deleteRecursively() - } - messageToShow - }.show(supportFragmentManager, ProgressDialogFragment.TAG) - } - - val getAmiiboKey = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } - - if (FileUtil.getExtension(result) != "bin") { - MessageDialogFragment.newInstance( - this, - titleId = R.string.reading_keys_failure, - descriptionId = R.string.install_amiibo_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return@registerForActivityResult - } - - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( - result, - dstPath, - "key_retail.bin" - ) != null - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - } else { - MessageDialogFragment.newInstance( - this, - titleId = R.string.invalid_keys_error, - descriptionId = R.string.install_keys_failure_description, - helpLinkId = R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } - } - } - - val installGameUpdate = registerForActivityResult( - ActivityResultContracts.OpenMultipleDocuments() - ) { documents: List -> - if (documents.isEmpty()) { - return@registerForActivityResult - } - - if (addonViewModel.game == null) { - installContent(documents) - return@registerForActivityResult - } - - ProgressDialogFragment.newInstance( - this@MainActivity, - R.string.verifying_content, - false - ) { _, _ -> - var updatesMatchProgram = true - for (document in documents) { - val valid = NativeLibrary.doesUpdateMatchProgram( - addonViewModel.game!!.programId, - document.toString() - ) - if (!valid) { - updatesMatchProgram = false - break - } - } - - if (updatesMatchProgram) { - homeViewModel.setContentToInstall(documents) - } else { - MessageDialogFragment.newInstance( - this@MainActivity, - titleId = R.string.content_install_notice, - descriptionId = R.string.content_install_notice_description, - positiveAction = { homeViewModel.setContentToInstall(documents) }, - negativeAction = {} - ) - } - }.show(supportFragmentManager, ProgressDialogFragment.TAG) - } - - private fun installContent(documents: List) { - ProgressDialogFragment.newInstance( - this@MainActivity, - R.string.installing_game_content - ) { progressCallback, messageCallback -> - var installSuccess = 0 - var installOverwrite = 0 - var errorBaseGame = 0 - var error = 0 - documents.forEach { - messageCallback.invoke(FileUtil.getFilename(it)) - when ( - InstallResult.from( - NativeLibrary.installFileToNand( - it.toString(), - progressCallback - ) - ) - ) { - InstallResult.Success -> { - installSuccess += 1 - } - - InstallResult.Overwrite -> { - installOverwrite += 1 - } - - InstallResult.BaseInstallAttempted -> { - errorBaseGame += 1 - } - - InstallResult.Failure -> { - error += 1 - } - } - } - - addonViewModel.refreshAddons() - - 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) - } - val errorTotal: Int = errorBaseGame + error - 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 (error > 0) { - installResult.append( - getString(R.string.install_game_content_failure_description) - ) - installResult.append(separator) - } - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.install_game_content_failure, - descriptionString = installResult.toString().trim(), - helpLinkId = R.string.install_game_content_help_link - ) - } else { - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.install_game_content_success, - descriptionString = installResult.toString().trim() - ) - } - }.show(supportFragmentManager, ProgressDialogFragment.TAG) - } - - val exportUserData = registerForActivityResult( - ActivityResultContracts.CreateDocument("application/zip") - ) { result -> - if (result == null) { - return@registerForActivityResult - } - - ProgressDialogFragment.newInstance( - this, - R.string.exporting_user_data, - true - ) { progressCallback, _ -> - val zipResult = FileUtil.zipFromInternalStorage( - File(DirectoryInitialization.userDirectory!!), - DirectoryInitialization.userDirectory!!, - BufferedOutputStream(contentResolver.openOutputStream(result)), - progressCallback, - compression = false - ) - return@newInstance when (zipResult) { - TaskState.Completed -> getString(R.string.user_data_export_success) - TaskState.Failed -> R.string.export_failed - TaskState.Cancelled -> R.string.user_data_export_cancelled - } - }.show(supportFragmentManager, ProgressDialogFragment.TAG) - } - - val importUserData = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } - - ProgressDialogFragment.newInstance( - this, - R.string.importing_user_data - ) { progressCallback, _ -> - val checkStream = - ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) - var isSudachiBackup = false - checkStream.use { stream -> - var ze: ZipEntry? = null - while (stream.nextEntry?.also { ze = it } != null) { - val itemName = ze!!.name.trim() - if (itemName == "/config/config.ini" || itemName == "config/config.ini") { - isSudachiBackup = true - return@use - } - } - } - if (!isSudachiBackup) { - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.invalid_sudachi_backup, - descriptionId = R.string.user_data_import_failed_description - ) - } - - // Clear existing user data - NativeConfig.unloadGlobalConfig() - File(DirectoryInitialization.userDirectory!!).deleteRecursively() - - // Copy archive to internal storage - try { - FileUtil.unzipToInternalStorage( - result.toString(), - File(DirectoryInitialization.userDirectory!!), - progressCallback - ) - } catch (e: Exception) { - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.import_failed, - descriptionId = R.string.user_data_import_failed_description - ) - } - - // Reinitialize relevant data - NativeLibrary.initializeSystem(true) - NativeConfig.initializeGlobalConfig() - gamesViewModel.reloadGames(false) - driverViewModel.reloadDriverData() - - return@newInstance getString(R.string.user_data_import_success) - }.show(supportFragmentManager, ProgressDialogFragment.TAG) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/ThemeProvider.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/ThemeProvider.kt deleted file mode 100644 index baf19942..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/ui/main/ThemeProvider.kt +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.ui.main - -interface ThemeProvider { - /** - * Provides theme ID by overriding an activity's 'setTheme' method and returning that result - */ - var themeId: Int -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/FileUtil.kt deleted file mode 100644 index 0c56d8dd..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/FileUtil.kt +++ /dev/null @@ -1,503 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.database.Cursor -import android.net.Uri -import android.provider.DocumentsContract -import androidx.documentfile.provider.DocumentFile -import java.io.BufferedInputStream -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.net.URLDecoder -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.model.MinimalDocumentFile -import org.sudachi.sudachi_emu.model.TaskState -import java.io.BufferedOutputStream -import java.io.OutputStream -import java.lang.NullPointerException -import java.nio.charset.StandardCharsets -import java.util.zip.Deflater -import java.util.zip.ZipOutputStream -import kotlin.IllegalStateException - -object FileUtil { - const val PATH_TREE = "tree" - const val DECODE_METHOD = "UTF-8" - const val APPLICATION_OCTET_STREAM = "application/octet-stream" - const val TEXT_PLAIN = "text/plain" - - private val context get() = SudachiApplication.appContext - - /** - * Create a file from directory with filename. - * @param context Application context - * @param directory parent path for file. - * @param filename file display name. - * @return boolean - */ - fun createFile(directory: String?, filename: String): DocumentFile? { - var decodedFilename = filename - try { - val directoryUri = Uri.parse(directory) - val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null - decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD) - var mimeType = APPLICATION_OCTET_STREAM - if (decodedFilename.endsWith(".txt")) { - mimeType = TEXT_PLAIN - } - val exists = parent.findFile(decodedFilename) - return exists ?: parent.createFile(mimeType, decodedFilename) - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot create file, error: " + e.message) - } - return null - } - - /** - * Create a directory from directory with filename. - * @param directory parent path for directory. - * @param directoryName directory display name. - * @return boolean - */ - fun createDir(directory: String?, directoryName: String?): DocumentFile? { - var decodedDirectoryName = directoryName - try { - val directoryUri = Uri.parse(directory) - val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null - decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD) - val isExist = parent.findFile(decodedDirectoryName) - return isExist ?: parent.createDirectory(decodedDirectoryName) - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot create file, error: " + e.message) - } - return null - } - - /** - * Open content uri and return file descriptor to JNI. - * @param path Native content uri path - * @param openMode will be one of "r", "r", "rw", "wa", "rwa" - * @return file descriptor - */ - @JvmStatic - fun openContentUri(path: String, openMode: String?): Int { - try { - val uri = Uri.parse(path) - val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) - if (parcelFileDescriptor == null) { - Log.error("[FileUtil]: Cannot get the file descriptor from uri: $path") - return -1 - } - val fileDescriptor = parcelFileDescriptor.detachFd() - parcelFileDescriptor.close() - return fileDescriptor - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot open content uri, error: " + e.message) - } - return -1 - } - - /** - * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow - * This function will be faster than DocumentFile.listFiles - * @param uri Directory uri. - * @return CheapDocument lists. - */ - fun listFiles(uri: Uri): Array { - val resolver = context.contentResolver - val columns = arrayOf( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE - ) - var c: Cursor? = null - val results: MutableList = ArrayList() - try { - val docId: String = if (isRootTreeUri(uri)) { - DocumentsContract.getTreeDocumentId(uri) - } else { - DocumentsContract.getDocumentId(uri) - } - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId) - c = resolver.query(childrenUri, columns, null, null, null) - while (c!!.moveToNext()) { - val documentId = c.getString(0) - val documentName = c.getString(1) - val documentMimeType = c.getString(2) - val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId) - val document = MinimalDocumentFile(documentName, documentMimeType, documentUri) - results.add(document) - } - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot list file error: " + e.message) - } finally { - closeQuietly(c) - } - return results.toTypedArray() - } - - /** - * Check whether given path exists. - * @param path Native content uri path - * @return bool - */ - fun exists(path: String?, suppressLog: Boolean = false): Boolean { - var c: Cursor? = null - try { - val mUri = Uri.parse(path) - val columns = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID) - c = context.contentResolver.query(mUri, columns, null, null, null) - return c!!.count > 0 - } catch (e: Exception) { - if (!suppressLog) { - Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) - } - } finally { - closeQuietly(c) - } - return false - } - - /** - * Check whether given path is a directory - * @param path content uri path - * @return bool - */ - fun isDirectory(path: String): Boolean { - val resolver = context.contentResolver - val columns = arrayOf( - DocumentsContract.Document.COLUMN_MIME_TYPE - ) - var isDirectory = false - var c: Cursor? = null - try { - val mUri = Uri.parse(path) - c = resolver.query(mUri, columns, null, null, null) - c!!.moveToNext() - val mimeType = c.getString(0) - isDirectory = mimeType == DocumentsContract.Document.MIME_TYPE_DIR - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot list files, error: " + e.message) - } finally { - closeQuietly(c) - } - return isDirectory - } - - /** - * Get file display name from given path - * @param uri content uri - * @return String display name - */ - fun getFilename(uri: Uri): String { - val resolver = SudachiApplication.appContext.contentResolver - val columns = arrayOf( - DocumentsContract.Document.COLUMN_DISPLAY_NAME - ) - var filename = "" - var c: Cursor? = null - try { - c = resolver.query(uri, columns, null, null, null) - c!!.moveToNext() - filename = c.getString(0) - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot get file size, error: " + e.message) - } finally { - closeQuietly(c) - } - return filename - } - - fun getFilesName(path: String): Array { - val uri = Uri.parse(path) - val files: MutableList = ArrayList() - for (file in listFiles(uri)) { - files.add(file.filename) - } - return files.toTypedArray() - } - - /** - * Get file size from given path. - * @param path content uri path - * @return long file size - */ - @JvmStatic - fun getFileSize(path: String): Long { - val resolver = context.contentResolver - val columns = arrayOf( - DocumentsContract.Document.COLUMN_SIZE - ) - var size: Long = 0 - var c: Cursor? = null - try { - val mUri = Uri.parse(path) - c = resolver.query(mUri, columns, null, null, null) - c!!.moveToNext() - size = c.getLong(0) - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot get file size, error: " + e.message) - } finally { - closeQuietly(c) - } - return size - } - - /** - * Creates an input stream with a given [Uri] and copies its data to the given path. This will - * overwrite any pre-existing files. - * - * @param sourceUri The [Uri] to copy data from - * @param destinationParentPath Destination directory - * @param destinationFilename Optionally renames the file once copied - */ - fun copyUriToInternalStorage( - sourceUri: Uri, - destinationParentPath: String, - destinationFilename: String = "" - ): File? = - try { - val fileName = - if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename" - val inputStream = context.contentResolver.openInputStream(sourceUri)!! - - val destinationFile = File("$destinationParentPath$fileName") - if (destinationFile.exists()) { - destinationFile.delete() - } - - destinationFile.outputStream().use { fos -> - inputStream.use { it.copyTo(fos) } - } - destinationFile - } catch (e: IOException) { - null - } catch (e: NullPointerException) { - null - } - - /** - * Extracts the given zip file into the given directory. - * @param path String representation of a [Uri] or a typical path delimited by '/' - * @param destDir Location to unzip the contents of [path] into - * @param progressCallback Lambda that is called with the total number of files and the current - * progress through the process. Stops execution as soon as possible if this returns true. - */ - @Throws(SecurityException::class) - fun unzipToInternalStorage( - path: String, - destDir: File, - progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false } - ) { - var totalEntries = 0L - ZipInputStream(getInputStream(path)).use { zis -> - var tempEntry = zis.nextEntry - while (tempEntry != null) { - tempEntry = zis.nextEntry - totalEntries++ - } - } - - var progress = 0L - ZipInputStream(getInputStream(path)).use { zis -> - var entry: ZipEntry? = zis.nextEntry - while (entry != null) { - if (progressCallback.invoke(totalEntries, progress)) { - return@use - } - - val newFile = File(destDir, entry.name) - val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile - - if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { - throw SecurityException("Zip file attempted path traversal! ${entry.name}") - } - - if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) { - throw IOException("Failed to create directory $destinationDirectory") - } - - if (!entry.isDirectory) { - newFile.outputStream().use { fos -> zis.copyTo(fos) } - } - entry = zis.nextEntry - progress++ - } - } - } - - /** - * Creates a zip file from a directory within internal storage - * @param inputFile File representation of the item that will be zipped - * @param rootDir Directory containing the inputFile - * @param outputStream Stream where the zip file will be output - * @param progressCallback Lambda that is called with the total number of files and the current - * progress through the process. Stops execution as soon as possible if this returns true. - * @param compression Disables compression if true - */ - fun zipFromInternalStorage( - inputFile: File, - rootDir: String, - outputStream: BufferedOutputStream, - progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }, - compression: Boolean = true - ): TaskState { - try { - ZipOutputStream(outputStream).use { zos -> - if (!compression) { - zos.setMethod(ZipOutputStream.DEFLATED) - zos.setLevel(Deflater.NO_COMPRESSION) - } - - var count = 0L - val totalFiles = inputFile.walkTopDown().count().toLong() - inputFile.walkTopDown().forEach { file -> - if (progressCallback.invoke(totalFiles, count)) { - return TaskState.Cancelled - } - - if (!file.isDirectory) { - val entryName = - file.absolutePath.removePrefix(rootDir).removePrefix("/") - val entry = ZipEntry(entryName) - zos.putNextEntry(entry) - if (file.isFile) { - file.inputStream().use { fis -> fis.copyTo(zos) } - } - count++ - } - } - } - } catch (e: Exception) { - Log.error("[FileUtil] Failed creating zip file - ${e.message}") - return TaskState.Failed - } - return TaskState.Completed - } - - /** - * Helper function that copies the contents of a DocumentFile folder into a [File] - * @param file [File] representation of the folder to copy into - * @param progressCallback Lambda that is called with the total number of files and the current - * progress through the process. Stops execution as soon as possible if this returns true. - * @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa - */ - fun DocumentFile.copyFilesTo( - file: File, - progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false } - ) { - file.mkdirs() - if (!this.isDirectory || !file.isDirectory) { - throw IllegalStateException( - "[FileUtil] Tried to copy a folder into a file or vice versa" - ) - } - - var count = 0L - val totalFiles = this.listFiles().size.toLong() - this.listFiles().forEach { - if (progressCallback.invoke(totalFiles, count)) { - return - } - - val newFile = File(file, it.name!!) - if (it.isDirectory) { - newFile.mkdirs() - DocumentFile.fromTreeUri(SudachiApplication.appContext, it.uri)?.copyFilesTo(newFile) - } else { - val inputStream = - SudachiApplication.appContext.contentResolver.openInputStream(it.uri) - BufferedInputStream(inputStream).use { bos -> - if (!newFile.exists()) { - newFile.createNewFile() - } - newFile.outputStream().use { os -> bos.copyTo(os) } - } - } - count++ - } - } - - fun isRootTreeUri(uri: Uri): Boolean { - val paths = uri.pathSegments - return paths.size == 2 && PATH_TREE == paths[0] - } - - fun closeQuietly(closeable: AutoCloseable?) { - if (closeable != null) { - try { - closeable.close() - } catch (rethrown: RuntimeException) { - throw rethrown - } catch (ignored: Exception) { - } - } - } - - fun getExtension(uri: Uri): String { - val fileName = getFilename(uri) - return fileName.substring(fileName.lastIndexOf(".") + 1) - .lowercase() - } - - fun isTreeUriValid(uri: Uri): Boolean { - val resolver = context.contentResolver - val columns = arrayOf( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE - ) - return try { - val docId: String = if (isRootTreeUri(uri)) { - DocumentsContract.getTreeDocumentId(uri) - } else { - DocumentsContract.getDocumentId(uri) - } - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId) - resolver.query(childrenUri, columns, null, null, null) - true - } catch (_: Exception) { - false - } - } - - fun getInputStream(path: String) = if (path.contains("content://")) { - Uri.parse(path).inputStream() - } else { - File(path).inputStream() - } - - fun getOutputStream(path: String) = if (path.contains("content://")) { - Uri.parse(path).outputStream() - } else { - File(path).outputStream() - } - - @Throws(IOException::class) - fun getStringFromFile(file: File): String = - String(file.readBytes(), StandardCharsets.UTF_8) - - @Throws(IOException::class) - fun getStringFromInputStream(stream: InputStream): String = - String(stream.readBytes(), StandardCharsets.UTF_8) - - fun DocumentFile.inputStream(): InputStream = - SudachiApplication.appContext.contentResolver.openInputStream(uri)!! - - fun DocumentFile.outputStream(): OutputStream = - SudachiApplication.appContext.contentResolver.openOutputStream(uri)!! - - fun Uri.inputStream(): InputStream = - SudachiApplication.appContext.contentResolver.openInputStream(this)!! - - fun Uri.outputStream(): OutputStream = - SudachiApplication.appContext.contentResolver.openOutputStream(this)!! - - fun Uri.asDocumentFile(): DocumentFile? = - DocumentFile.fromSingleUri(SudachiApplication.appContext, this) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GameHelper.kt deleted file mode 100644 index 9dce3577..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GameHelper.kt +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.content.SharedPreferences -import android.net.Uri -import androidx.preference.PreferenceManager -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.model.Game -import org.sudachi.sudachi_emu.model.GameDir -import org.sudachi.sudachi_emu.model.MinimalDocumentFile - -object GameHelper { - private const val KEY_OLD_GAME_PATH = "game_path" - const val KEY_GAMES = "Games" - - private lateinit var preferences: SharedPreferences - - fun getGames(): List { - val games = mutableListOf() - val context = SudachiApplication.appContext - preferences = PreferenceManager.getDefaultSharedPreferences(context) - - val gameDirs = mutableListOf() - val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: "" - if (oldGamesDir.isNotEmpty()) { - gameDirs.add(GameDir(oldGamesDir, true)) - preferences.edit().remove(KEY_OLD_GAME_PATH).apply() - } - gameDirs.addAll(NativeConfig.getGameDirs()) - - // Ensure keys are loaded so that ROM metadata can be decrypted. - NativeLibrary.reloadKeys() - - // Reset metadata so we don't use stale information - GameMetadata.resetMetadata() - - // Remove previous filesystem provider information so we can get up to date version info - NativeLibrary.clearFilesystemProvider() - - val badDirs = mutableListOf() - gameDirs.forEachIndexed { index: Int, gameDir: GameDir -> - val gameDirUri = Uri.parse(gameDir.uriString) - val isValid = FileUtil.isTreeUriValid(gameDirUri) - if (isValid) { - addGamesRecursive( - games, - FileUtil.listFiles(gameDirUri), - if (gameDir.deepScan) 3 else 1 - ) - } else { - badDirs.add(index) - } - } - - // Remove all game dirs with insufficient permissions from config - if (badDirs.isNotEmpty()) { - var offset = 0 - badDirs.forEach { - gameDirs.removeAt(it - offset) - offset++ - } - } - NativeConfig.setGameDirs(gameDirs.toTypedArray()) - - // Cache list of games found on disk - val serializedGames = mutableSetOf() - games.forEach { - serializedGames.add(Json.encodeToString(it)) - } - preferences.edit() - .remove(KEY_GAMES) - .putStringSet(KEY_GAMES, serializedGames) - .apply() - - return games.toList() - } - - private fun addGamesRecursive( - games: MutableList, - files: Array, - depth: Int - ) { - if (depth <= 0) { - return - } - - files.forEach { - if (it.isDirectory) { - addGamesRecursive( - games, - FileUtil.listFiles(it.uri), - depth - 1 - ) - } else { - if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { - val game = getGame(it.uri, true) - if (game != null) { - games.add(game) - } - } - } - } - } - - fun getGame(uri: Uri, addedToLibrary: Boolean): Game? { - val filePath = uri.toString() - if (!GameMetadata.getIsValid(filePath)) { - return null - } - - // Needed to update installed content information - NativeLibrary.addFileToFilesystemProvider(filePath) - - var name = GameMetadata.getTitle(filePath) - - // If the game's title field is empty, use the filename. - if (name.isEmpty()) { - name = FileUtil.getFilename(uri) - } - var programId = GameMetadata.getProgramId(filePath) - - // If the game's ID field is empty, use the filename without extension. - if (programId.isEmpty()) { - programId = name.substring(0, name.lastIndexOf(".")) - } - - val newGame = Game( - name, - filePath, - programId, - GameMetadata.getDeveloper(filePath), - GameMetadata.getVersion(filePath, false), - GameMetadata.getIsHomebrew(filePath) - ) - - if (addedToLibrary) { - val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) - if (addedTime == 0L) { - preferences.edit() - .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) - .apply() - } - } - - return newGame - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GpuDriverHelper.kt deleted file mode 100644 index 94ef6495..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/GpuDriverHelper.kt +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.graphics.SurfaceTexture -import android.net.Uri -import android.os.Build -import android.view.Surface -import java.io.File -import java.io.IOException -import org.sudachi.sudachi_emu.NativeLibrary -import org.sudachi.sudachi_emu.SudachiApplication -import org.sudachi.sudachi_emu.features.settings.model.StringSetting -import java.io.FileNotFoundException -import java.util.zip.ZipException -import java.util.zip.ZipFile - -object GpuDriverHelper { - private const val META_JSON_FILENAME = "meta.json" - private var fileRedirectionPath: String? = null - var driverInstallationPath: String? = null - private var hookLibPath: String? = null - - val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/" - - fun initializeDriverParameters() { - try { - // Initialize the file redirection directory. - fileRedirectionPath = SudachiApplication.appContext - .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/" - - // Initialize the driver installation directory. - driverInstallationPath = SudachiApplication.appContext - .filesDir.canonicalPath + "/gpu_driver/" - } catch (e: IOException) { - throw RuntimeException(e) - } - - // Initialize directories. - initializeDirectories() - - // Initialize hook libraries directory. - hookLibPath = SudachiApplication.appContext.applicationInfo.nativeLibraryDir + "/" - - // Initialize GPU driver. - NativeLibrary.initializeGpuDriver( - hookLibPath, - driverInstallationPath, - installedCustomDriverData.libraryName, - fileRedirectionPath - ) - } - - fun getDrivers(): MutableList> { - val driverZips = File(driverStoragePath).listFiles() - val drivers: MutableList> = - driverZips - ?.mapNotNull { - val metadata = getMetadataFromZip(it) - metadata.name?.let { _ -> Pair(it.path, metadata) } - } - ?.sortedByDescending { it: Pair -> it.second.name } - ?.distinct() - ?.toMutableList() ?: mutableListOf() - return drivers - } - - fun installDefaultDriver() { - // Removing the installed driver will result in the backend using the default system driver. - File(driverInstallationPath!!).deleteRecursively() - initializeDriverParameters() - } - - fun copyDriverToInternalStorage(driverUri: Uri): Boolean { - // Ensure we have directories. - initializeDirectories() - - // Copy the zip file URI to user data - val copiedFile = - FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false - - // Validate driver - val metadata = getMetadataFromZip(copiedFile) - if (metadata.name == null) { - copiedFile.delete() - return false - } - - if (metadata.minApi > Build.VERSION.SDK_INT) { - copiedFile.delete() - return false - } - return true - } - - /** - * Copies driver zip into user data directory so that it can be exported along with - * other user data and also unzipped into the installation directory - */ - fun installCustomDriver(driverUri: Uri): Boolean { - // Revert to system default in the event the specified driver is bad. - installDefaultDriver() - - // Ensure we have directories. - initializeDirectories() - - // Copy the zip file URI to user data - val copiedFile = - FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false - - // Validate driver - val metadata = getMetadataFromZip(copiedFile) - if (metadata.name == null) { - copiedFile.delete() - return false - } - - if (metadata.minApi > Build.VERSION.SDK_INT) { - copiedFile.delete() - return false - } - - // Unzip the driver. - try { - FileUtil.unzipToInternalStorage( - copiedFile.path, - File(driverInstallationPath!!) - ) - } catch (e: SecurityException) { - return false - } - - // Initialize the driver parameters. - initializeDriverParameters() - - return true - } - - /** - * Unzips driver into installation directory - */ - fun installCustomDriver(driver: File): Boolean { - // Revert to system default in the event the specified driver is bad. - installDefaultDriver() - - // Ensure we have directories. - initializeDirectories() - - // Validate driver - val metadata = getMetadataFromZip(driver) - if (metadata.name == null) { - driver.delete() - return false - } - - // Unzip the driver to the private installation directory - try { - FileUtil.unzipToInternalStorage( - driver.path, - File(driverInstallationPath!!) - ) - } catch (e: SecurityException) { - return false - } - - // Initialize the driver parameters. - initializeDriverParameters() - - return true - } - - /** - * Takes in a zip file and reads the meta.json file for presentation to the UI - * - * @param driver Zip containing driver and meta.json file - * @return A non-null [GpuDriverMetadata] instance that may have null members - */ - fun getMetadataFromZip(driver: File): GpuDriverMetadata { - try { - ZipFile(driver).use { zf -> - val entries = zf.entries() - while (entries.hasMoreElements()) { - val entry = entries.nextElement() - if (!entry.isDirectory && entry.name.lowercase().contains(".json")) { - zf.getInputStream(entry).use { - return GpuDriverMetadata(it, entry.size) - } - } - } - } - } catch (_: ZipException) { - } catch (_: FileNotFoundException) { - } - return GpuDriverMetadata() - } - - external fun supportsCustomDriverLoading(): Boolean - - external fun getSystemDriverInfo( - surface: Surface = Surface(SurfaceTexture(true)), - hookLibPath: String = GpuDriverHelper.hookLibPath!! - ): Array? - - // Parse the custom driver metadata to retrieve the name. - val installedCustomDriverData: GpuDriverMetadata - get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) - - val customDriverSettingData: GpuDriverMetadata - get() = getMetadataFromZip(File(StringSetting.DRIVER_PATH.getString())) - - fun initializeDirectories() { - // Ensure the file redirection directory exists. - val fileRedirectionDir = File(fileRedirectionPath!!) - if (!fileRedirectionDir.exists()) { - fileRedirectionDir.mkdirs() - } - // Ensure the driver installation directory exists. - val driverInstallationDir = File(driverInstallationPath!!) - if (!driverInstallationDir.exists()) { - driverInstallationDir.mkdirs() - } - // Ensure the driver storage directory exists - val driverStorageDirectory = File(driverStoragePath) - if (!driverStorageDirectory.exists()) { - driverStorageDirectory.mkdirs() - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/InputHandler.kt deleted file mode 100644 index 3aa83be5..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/InputHandler.kt +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.view.InputDevice -import android.view.KeyEvent -import android.view.MotionEvent -import org.sudachi.sudachi_emu.features.input.NativeInput -import org.sudachi.sudachi_emu.features.input.SudachiInputOverlayDevice -import org.sudachi.sudachi_emu.features.input.SudachiPhysicalDevice - -object InputHandler { - var androidControllers = mapOf() - var registeredControllers = mutableListOf() - - fun dispatchKeyEvent(event: KeyEvent): Boolean { - val action = when (event.action) { - KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED - KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED - else -> return false - } - - var controllerData = androidControllers[event.device.controllerNumber] - if (controllerData == null) { - updateControllerData() - controllerData = androidControllers[event.device.controllerNumber] ?: return false - } - - NativeInput.onGamePadButtonEvent( - controllerData.getGUID(), - controllerData.getPort(), - event.keyCode, - action - ) - return true - } - - fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { - val controllerData = - androidControllers[event.device.controllerNumber] ?: return false - event.device.motionRanges.forEach { - NativeInput.onGamePadAxisEvent( - controllerData.getGUID(), - controllerData.getPort(), - it.axis, - event.getAxisValue(it.axis) - ) - } - return true - } - - fun getDevices(): Map { - val gameControllerDeviceIds = mutableMapOf() - val deviceIds = InputDevice.getDeviceIds() - var port = 0 - val inputSettings = NativeConfig.getInputSettings(true) - deviceIds.forEach { deviceId -> - InputDevice.getDevice(deviceId)?.apply { - // Verify that the device has gamepad buttons, control sticks, or both. - if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || - sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK - ) { - if (!gameControllerDeviceIds.contains(controllerNumber)) { - gameControllerDeviceIds[controllerNumber] = SudachiPhysicalDevice( - this, - port, - inputSettings[port].useSystemVibrator - ) - } - port++ - } - } - } - return gameControllerDeviceIds - } - - fun updateControllerData() { - androidControllers = getDevices() - androidControllers.forEach { - NativeInput.registerController(it.value) - } - - // Register the input overlay on a dedicated port for all player 1 vibrations - NativeInput.registerController(SudachiInputOverlayDevice(androidControllers.isEmpty(), 100)) - registeredControllers.clear() - NativeInput.getInputDevices().forEach { - registeredControllers.add(ParamPackage(it)) - } - registeredControllers.sortBy { it.get("port", 0) } - } - - fun InputDevice.getGUID(): String = String.format("%016x%016x", productId, vendorId) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/LifecycleUtils.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/LifecycleUtils.kt deleted file mode 100644 index 2158a490..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/LifecycleUtils.kt +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch - -/** - * Collects this [Flow] with a given [LifecycleOwner]. - * @param scope [LifecycleOwner] that this [Flow] will be collected with. - * @param repeatState When to repeat collection on this [Flow]. - * @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after - * [stateCollector] has been run. - * @param stateCollector Lambda that receives new state. - */ -inline fun Flow.collect( - scope: LifecycleOwner, - repeatState: Lifecycle.State = Lifecycle.State.CREATED, - crossinline resetState: () -> Unit = {}, - crossinline stateCollector: (state: T) -> Unit -) { - scope.apply { - lifecycleScope.launch { - repeatOnLifecycle(repeatState) { - this@collect.collect { - stateCollector(it) - resetState() - } - } - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/MemoryUtil.kt deleted file mode 100644 index 884ab834..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/MemoryUtil.kt +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.app.ActivityManager -import android.content.Context -import android.os.Build -import org.sudachi.sudachi_emu.R -import org.sudachi.sudachi_emu.SudachiApplication -import java.util.Locale -import kotlin.math.ceil - -object MemoryUtil { - private val context get() = SudachiApplication.appContext - - private val Float.hundredths: String - get() = String.format(Locale.ROOT, "%.2f", this) - - // Required total system memory - const val REQUIRED_MEMORY = 8 - - const val Kb: Float = 1024F - 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 - - fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String = - when { - size < Kb -> { - context.getString( - R.string.memory_formatted, - size.hundredths, - context.getString(R.string.memory_byte_shorthand) - ) - } - size < Mb -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Kb) else (size / Kb).hundredths, - context.getString(R.string.memory_kilobyte) - ) - } - size < Gb -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Mb) else (size / Mb).hundredths, - context.getString(R.string.memory_megabyte) - ) - } - size < Tb -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Gb) else (size / Gb).hundredths, - context.getString(R.string.memory_gigabyte) - ) - } - size < Pb -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Tb) else (size / Tb).hundredths, - context.getString(R.string.memory_terabyte) - ) - } - size < Eb -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Pb) else (size / Pb).hundredths, - context.getString(R.string.memory_petabyte) - ) - } - else -> { - context.getString( - R.string.memory_formatted, - if (roundUp) ceil(size / Eb) else (size / Eb).hundredths, - context.getString(R.string.memory_exabyte) - ) - } - } - - val totalMemory: Float - get() { - val memInfo = ActivityManager.MemoryInfo() - with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) { - getMemoryInfo(memInfo) - } - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - memInfo.advertisedMem.toFloat() - } else { - memInfo.totalMem.toFloat() - } - } - - fun isLessThan(minimum: Int, size: Float): Boolean = - 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 - } - - // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for - // the potential error created by memInfo.totalMem - fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true) -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/NativeConfig.kt deleted file mode 100644 index a8d36943..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/NativeConfig.kt +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import org.sudachi.sudachi_emu.model.GameDir -import org.sudachi.sudachi_emu.overlay.model.OverlayControlData - -import org.sudachi.sudachi_emu.features.input.model.PlayerInput - -object NativeConfig { - /** - * Loads global config. - */ - @Synchronized - external fun initializeGlobalConfig() - - /** - * Destroys the stored global config object. This does not save the existing config. - */ - @Synchronized - external fun unloadGlobalConfig() - - /** - * Reads values in the global config file and saves them. - */ - @Synchronized - external fun reloadGlobalConfig() - - /** - * Saves global settings values in memory to disk. - */ - @Synchronized - external fun saveGlobalConfig() - - /** - * Creates per-game config for the specified parameters. Must be unloaded once per-game config - * is closed with [unloadPerGameConfig]. All switchable values that [NativeConfig] gets/sets - * will follow the per-game config until the global config is reloaded. - * - * @param programId String representation of the u64 programId - * @param fileName Filename of the game, including its extension - */ - @Synchronized - external fun initializePerGameConfig(programId: String, fileName: String) - - @Synchronized - external fun isPerGameConfigLoaded(): Boolean - - /** - * Saves per-game settings values in memory to disk. - */ - @Synchronized - external fun savePerGameConfig() - - /** - * Destroys the stored per-game config object. This does not save the config. - */ - @Synchronized - external fun unloadPerGameConfig() - - @Synchronized - external fun getBoolean(key: String, needsGlobal: Boolean): Boolean - - @Synchronized - external fun setBoolean(key: String, value: Boolean) - - @Synchronized - external fun getByte(key: String, needsGlobal: Boolean): Byte - - @Synchronized - external fun setByte(key: String, value: Byte) - - @Synchronized - external fun getShort(key: String, needsGlobal: Boolean): Short - - @Synchronized - external fun setShort(key: String, value: Short) - - @Synchronized - external fun getInt(key: String, needsGlobal: Boolean): Int - - @Synchronized - external fun setInt(key: String, value: Int) - - @Synchronized - external fun getFloat(key: String, needsGlobal: Boolean): Float - - @Synchronized - external fun setFloat(key: String, value: Float) - - @Synchronized - external fun getLong(key: String, needsGlobal: Boolean): Long - - @Synchronized - external fun setLong(key: String, value: Long) - - @Synchronized - external fun getString(key: String, needsGlobal: Boolean): String - - @Synchronized - external fun setString(key: String, value: String) - - external fun getIsRuntimeModifiable(key: String): Boolean - - external fun getPairedSettingKey(key: String): String - - external fun getIsSwitchable(key: String): Boolean - - @Synchronized - external fun usingGlobal(key: String): Boolean - - @Synchronized - external fun setGlobal(key: String, global: Boolean) - - external fun getIsSaveable(key: String): Boolean - - external fun getDefaultToString(key: String): String - - /** - * Gets every [GameDir] in AndroidSettings::values.game_dirs - */ - @Synchronized - external fun getGameDirs(): Array - - /** - * Clears the AndroidSettings::values.game_dirs array and replaces them with the provided array - */ - @Synchronized - external fun setGameDirs(dirs: Array) - - /** - * Adds a single [GameDir] to the AndroidSettings::values.game_dirs array - */ - @Synchronized - external fun addGameDir(dir: GameDir) - - /** - * Gets an array of the addons that are disabled for a given game - * - * @param programId String representation of a game's program ID - * @return An array of disabled addons - */ - @Synchronized - external fun getDisabledAddons(programId: String): Array - - /** - * Clears the disabled addons array corresponding to [programId] and replaces them - * with [disabledAddons] - * - * @param programId String representation of a game's program ID - * @param disabledAddons Replacement array of disabled addons - */ - @Synchronized - external fun setDisabledAddons(programId: String, disabledAddons: Array) - - /** - * Gets an array of [OverlayControlData] from settings - * - * @return An array of [OverlayControlData] - */ - @Synchronized - external fun getOverlayControlData(): Array - - /** - * Clears the AndroidSettings::values.overlay_control_data array and replaces its values - * with [overlayControlData] - * - * @param overlayControlData Replacement array of [OverlayControlData] - */ - @Synchronized - external fun setOverlayControlData(overlayControlData: Array) - - @Synchronized - external fun getInputSettings(global: Boolean): Array - - @Synchronized - external fun setInputSettings(value: Array, global: Boolean) - - /** - * Saves control values for a specific player - * Must be used when per game config is loaded - */ - @Synchronized - external fun saveControlPlayerValues() -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ParamPackage.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ParamPackage.kt deleted file mode 100644 index 8d9ccc42..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ParamPackage.kt +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -// Kotlin version of src/common/param_package.h -class ParamPackage(serialized: String = "") { - private val KEY_VALUE_SEPARATOR = ":" - private val PARAM_SEPARATOR = "," - - private val ESCAPE_CHARACTER = "$" - private val KEY_VALUE_SEPARATOR_ESCAPE = "$0" - private val PARAM_SEPARATOR_ESCAPE = "$1" - private val ESCAPE_CHARACTER_ESCAPE = "$2" - - private val EMPTY_PLACEHOLDER = "[empty]" - - val data = mutableMapOf() - - init { - val pairs = serialized.split(PARAM_SEPARATOR) - for (pair in pairs) { - val keyValue = pair.split(KEY_VALUE_SEPARATOR).toMutableList() - if (keyValue.size != 2) { - Log.error("[ParamPackage] Invalid key pair $keyValue") - continue - } - - keyValue.forEachIndexed { i: Int, _: String -> - keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR_ESCAPE, KEY_VALUE_SEPARATOR) - keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR_ESCAPE, PARAM_SEPARATOR) - keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER_ESCAPE, ESCAPE_CHARACTER) - } - - set(keyValue[0], keyValue[1]) - } - } - - constructor(params: List>) : this() { - params.forEach { - data[it.first] = it.second - } - } - - fun serialize(): String { - if (data.isEmpty()) { - return EMPTY_PLACEHOLDER - } - - val result = StringBuilder() - data.forEach { - val keyValue = mutableListOf(it.key, it.value) - keyValue.forEachIndexed { i, _ -> - keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER, ESCAPE_CHARACTER_ESCAPE) - keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR, PARAM_SEPARATOR_ESCAPE) - keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR_ESCAPE) - } - result.append("${keyValue[0]}$KEY_VALUE_SEPARATOR${keyValue[1]}$PARAM_SEPARATOR") - } - return result.removeSuffix(PARAM_SEPARATOR).toString() - } - - fun get(key: String, defaultValue: String): String = - if (has(key)) { - data[key]!! - } else { - Log.debug("[ParamPackage] key $key not found") - defaultValue - } - - fun get(key: String, defaultValue: Int): Int = - if (has(key)) { - try { - data[key]!!.toInt() - } catch (e: NumberFormatException) { - Log.debug("[ParamPackage] failed to convert ${data[key]!!} to int") - defaultValue - } - } else { - Log.debug("[ParamPackage] key $key not found") - defaultValue - } - - private fun Int.toBoolean(): Boolean = - if (this == 1) { - true - } else if (this == 0) { - false - } else { - throw Exception("Tried to convert a value to a boolean that was not 0 or 1!") - } - - fun get(key: String, defaultValue: Boolean): Boolean = - if (has(key)) { - try { - get(key, if (defaultValue) 1 else 0).toBoolean() - } catch (e: Exception) { - Log.debug("[ParamPackage] failed to convert ${data[key]!!} to boolean") - defaultValue - } - } else { - Log.debug("[ParamPackage] key $key not found") - defaultValue - } - - fun get(key: String, defaultValue: Float): Float = - if (has(key)) { - try { - data[key]!!.toFloat() - } catch (e: NumberFormatException) { - Log.debug("[ParamPackage] failed to convert ${data[key]!!} to float") - defaultValue - } - } else { - Log.debug("[ParamPackage] key $key not found") - defaultValue - } - - fun set(key: String, value: String) { - data[key] = value - } - - fun set(key: String, value: Int) { - data[key] = value.toString() - } - - fun Boolean.toInt(): Int = if (this) 1 else 0 - fun set(key: String, value: Boolean) { - data[key] = value.toInt().toString() - } - - fun set(key: String, value: Float) { - data[key] = value.toString() - } - - fun has(key: String): Boolean = data.containsKey(key) - - fun erase(key: String) = data.remove(key) - - fun clear() = data.clear() -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/PreferenceUtil.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/PreferenceUtil.kt deleted file mode 100644 index a3daa275..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/PreferenceUtil.kt +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.content.SharedPreferences - -object PreferenceUtil { - /** - * Retrieves a shared preference value and then deletes the value in storage. - * @param key Associated key for the value in this preferences instance - * @return Typed value associated with [key]. Null if no such key exists. - */ - inline fun SharedPreferences.migratePreference(key: String): T? { - if (!this.contains(key)) { - return null - } - - val value: Any = when (T::class) { - String::class -> this.getString(key, "")!! - - Boolean::class -> this.getBoolean(key, false) - - Int::class -> this.getInt(key, 0) - - Float::class -> this.getFloat(key, 0f) - - Long::class -> this.getLong(key, 0) - - else -> throw IllegalStateException("Tried to migrate preference with invalid type!") - } - deletePreference(key) - return value as T - } - - fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply() -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/SerializableHelper.kt deleted file mode 100644 index a8182ab7..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/SerializableHelper.kt +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.os.Parcelable -import java.io.Serializable - -object SerializableHelper { - inline fun Bundle.serializable(key: String): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - getSerializable(key, T::class.java) - } else { - getSerializable(key) as? T - } - } - - inline fun Intent.serializable(key: String): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - getSerializableExtra(key, T::class.java) - } else { - getSerializableExtra(key) as? T - } - } - - inline fun Bundle.parcelable(key: String): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - getParcelable(key, T::class.java) - } else { - getParcelable(key) as? T - } - } - - inline fun Intent.parcelable(key: String): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - getParcelableExtra(key, T::class.java) - } else { - getParcelableExtra(key) as? T - } - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ViewUtils.kt deleted file mode 100644 index 4509cdc8..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/utils/ViewUtils.kt +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.utils - -import android.text.TextUtils -import android.view.View -import android.view.ViewGroup -import android.widget.TextView - -object ViewUtils { - fun showView(view: View, length: Long = 300) { - view.apply { - alpha = 0f - visibility = View.VISIBLE - isClickable = true - }.animate().apply { - duration = length - alpha(1f) - }.start() - } - - fun hideView(view: View, length: Long = 300) { - if (view.visibility == View.INVISIBLE) { - return - } - - view.apply { - alpha = 1f - isClickable = false - }.animate().apply { - duration = length - alpha(0f) - }.withEndAction { - view.visibility = View.INVISIBLE - }.start() - } - - fun View.updateMargins( - left: Int = -1, - top: Int = -1, - right: Int = -1, - bottom: Int = -1 - ) { - val layoutParams = this.layoutParams as ViewGroup.MarginLayoutParams - layoutParams.apply { - if (left != -1) { - leftMargin = left - } - if (top != -1) { - topMargin = top - } - if (right != -1) { - rightMargin = right - } - if (bottom != -1) { - bottomMargin = bottom - } - } - this.layoutParams = layoutParams - } - - /** - * Shows or hides a view. - * @param visible Whether a view will be made View.VISIBLE or View.INVISIBLE/GONE. - * @param gone Optional parameter for hiding a view. Uses View.GONE if true and View.INVISIBLE otherwise. - */ - fun View.setVisible(visible: Boolean, gone: Boolean = true) { - visibility = if (visible) { - View.VISIBLE - } else { - if (gone) { - View.GONE - } else { - View.INVISIBLE - } - } - } - - /** - * Starts a marquee on some text. - * @param delay Optional parameter for changing the start delay. 3 seconds of delay by default. - */ - fun TextView.marquee(delay: Long = 3000) { - ellipsize = null - marqueeRepeatLimit = -1 - isSingleLine = true - postDelayed({ - ellipsize = TextUtils.TruncateAt.MARQUEE - isSelected = true - }, delay) - } -} diff --git a/src/android/app/src/main/java/org/sudachi/sudachi_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/sudachi/sudachi_emu/views/FixedRatioSurfaceView.kt deleted file mode 100644 index 1a2874c2..00000000 --- a/src/android/app/src/main/java/org/sudachi/sudachi_emu/views/FixedRatioSurfaceView.kt +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.sudachi.sudachi_emu.views - -import android.content.Context -import android.util.AttributeSet -import android.util.Rational -import android.view.SurfaceView - -class FixedRatioSurfaceView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : SurfaceView(context, attrs, defStyleAttr) { - private var aspectRatio: Float = 0f // (width / height), 0f is a special value for stretch - - /** - * Sets the desired aspect ratio for this view - * @param ratio the ratio to force the view to, or null to stretch to fit - */ - fun setAspectRatio(ratio: Rational?) { - aspectRatio = ratio?.toFloat() ?: 0f - requestLayout() - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat() - val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat() - if (aspectRatio != 0f) { - val displayAspect = displayWidth / displayHeight - if (displayAspect < aspectRatio) { - // Max out width - val halfHeight = displayHeight / 2 - val surfaceHeight = displayWidth / aspectRatio - val newTop: Float = halfHeight - (surfaceHeight / 2) - val newBottom: Float = halfHeight + (surfaceHeight / 2) - super.onMeasure( - widthMeasureSpec, - MeasureSpec.makeMeasureSpec( - newBottom.toInt() - newTop.toInt(), - MeasureSpec.EXACTLY - ) - ) - return - } else { - // Max out height - val halfWidth = displayWidth / 2 - val surfaceWidth = displayHeight * aspectRatio - val newLeft: Float = halfWidth - (surfaceWidth / 2) - val newRight: Float = halfWidth + (surfaceWidth / 2) - super.onMeasure( - MeasureSpec.makeMeasureSpec( - newRight.toInt() - newLeft.toInt(), - MeasureSpec.EXACTLY - ), - heightMeasureSpec - ) - return - } - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - } -} diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt deleted file mode 100644 index b0a750cc..00000000 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-FileCopyrightText: 2023 sudachi Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -add_library(sudachi-android SHARED - emu_window/emu_window.cpp - emu_window/emu_window.h - native.cpp - native.h - native_config.cpp - android_settings.cpp - game_metadata.cpp - native_log.cpp - android_config.cpp - android_config.h - native_input.cpp -) - -set_property(TARGET sudachi-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) - -target_link_libraries(sudachi-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers) -target_link_libraries(sudachi-android PRIVATE android camera2ndk EGL glad jnigraphics log) -if (ARCHITECTURE_arm64) - target_link_libraries(sudachi-android PRIVATE adrenotools) -endif() - -set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} sudachi-android) diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp deleted file mode 100644 index b06d73cf..00000000 --- a/src/android/app/src/main/jni/android_common/android_common.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "jni/android_common/android_common.h" - -#include -#include - -#include - -#include "common/string_util.h" -#include "jni/id_cache.h" - -std::string GetJString(JNIEnv* env, jstring jstr) { - if (!jstr) { - return {}; - } - - const jchar* jchars = env->GetStringChars(jstr, nullptr); - const jsize length = env->GetStringLength(jstr); - const std::u16string_view string_view(reinterpret_cast(jchars), length); - const std::string converted_string = Common::UTF16ToUTF8(string_view); - env->ReleaseStringChars(jstr, jchars); - - return converted_string; -} - -jstring ToJString(JNIEnv* env, std::string_view str) { - const std::u16string converted_string = Common::UTF8ToUTF16(str); - return env->NewString(reinterpret_cast(converted_string.data()), - static_cast(converted_string.size())); -} - -jstring ToJString(JNIEnv* env, std::u16string_view str) { - return ToJString(env, Common::UTF16ToUTF8(str)); -} - -double GetJDouble(JNIEnv* env, jobject jdouble) { - return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField()); -} - -jobject ToJDouble(JNIEnv* env, double value) { - return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); -} - -s32 GetJInteger(JNIEnv* env, jobject jinteger) { - return env->GetIntField(jinteger, IDCache::GetIntegerValueField()); -} - -jobject ToJInteger(JNIEnv* env, s32 value) { - return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value); -} - -bool GetJBoolean(JNIEnv* env, jobject jboolean) { - return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField()); -} - -jobject ToJBoolean(JNIEnv* env, bool value) { - return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value); -} diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h deleted file mode 100644 index 5fd1c050..00000000 --- a/src/android/app/src/main/jni/android_common/android_common.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include -#include "common/common_types.h" - -std::string GetJString(JNIEnv* env, jstring jstr); -jstring ToJString(JNIEnv* env, std::string_view str); -jstring ToJString(JNIEnv* env, std::u16string_view str); - -double GetJDouble(JNIEnv* env, jobject jdouble); -jobject ToJDouble(JNIEnv* env, double value); - -s32 GetJInteger(JNIEnv* env, jobject jinteger); -jobject ToJInteger(JNIEnv* env, s32 value); - -bool GetJBoolean(JNIEnv* env, jobject jboolean); -jobject ToJBoolean(JNIEnv* env, bool value); diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp deleted file mode 100644 index ef8a8506..00000000 --- a/src/android/app/src/main/jni/android_config.cpp +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "android_config.h" -#include "android_settings.h" -#include "common/settings_setting.h" - -AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_type) - : Config(config_type) { - Initialize(config_name); - if (config_type != ConfigType::InputProfile) { - ReadAndroidValues(); - SaveAndroidValues(); - } -} - -void AndroidConfig::ReloadAllValues() { - Reload(); - ReadAndroidValues(); - SaveAndroidValues(); -} - -void AndroidConfig::SaveAllValues() { - SaveValues(); - SaveAndroidValues(); -} - -void AndroidConfig::ReadAndroidValues() { - if (global) { - ReadAndroidUIValues(); - ReadUIValues(); - ReadOverlayValues(); - } - ReadDriverValues(); - ReadAndroidControlValues(); -} - -void AndroidConfig::ReadAndroidUIValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Android)); - - ReadCategory(Settings::Category::Android); - - EndGroup(); -} - -void AndroidConfig::ReadUIValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); - - ReadPathValues(); - - EndGroup(); -} - -void AndroidConfig::ReadPathValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); - - AndroidSettings::values.game_dirs.clear(); - const int gamedirs_size = BeginArray(std::string("gamedirs")); - for (int i = 0; i < gamedirs_size; ++i) { - SetArrayIndex(i); - AndroidSettings::GameDir game_dir; - game_dir.path = ReadStringSetting(std::string("path")); - game_dir.deep_scan = - ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false)); - AndroidSettings::values.game_dirs.push_back(game_dir); - } - EndArray(); - - EndGroup(); -} - -void AndroidConfig::ReadDriverValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); - - ReadCategory(Settings::Category::GpuDriver); - - EndGroup(); -} - -void AndroidConfig::ReadOverlayValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay)); - - ReadCategory(Settings::Category::Overlay); - - AndroidSettings::values.overlay_control_data.clear(); - const int control_data_size = BeginArray("control_data"); - for (int i = 0; i < control_data_size; ++i) { - SetArrayIndex(i); - AndroidSettings::OverlayControlData control_data; - control_data.id = ReadStringSetting(std::string("id")); - control_data.enabled = ReadBooleanSetting(std::string("enabled")); - control_data.landscape_position.first = - ReadDoubleSetting(std::string("landscape\\x_position")); - control_data.landscape_position.second = - ReadDoubleSetting(std::string("landscape\\y_position")); - control_data.portrait_position.first = - ReadDoubleSetting(std::string("portrait\\x_position")); - control_data.portrait_position.second = - ReadDoubleSetting(std::string("portrait\\y_position")); - control_data.foldable_position.first = - ReadDoubleSetting(std::string("foldable\\x_position")); - control_data.foldable_position.second = - ReadDoubleSetting(std::string("foldable\\y_position")); - AndroidSettings::values.overlay_control_data.push_back(control_data); - } - EndArray(); - - EndGroup(); -} - -void AndroidConfig::ReadAndroidPlayerValues(std::size_t player_index) { - std::string player_prefix; - if (type != ConfigType::InputProfile) { - player_prefix.append("player_").append(ToString(player_index)).append("_"); - } - - auto& player = Settings::values.players.GetValue()[player_index]; - if (IsCustomConfig()) { - const auto profile_name = - ReadStringSetting(std::string(player_prefix).append("profile_name")); - if (profile_name.empty()) { - // Use the global input config - player = Settings::values.players.GetValue(true)[player_index]; - player.profile_name = ""; - return; - } - } - - // Android doesn't have default options for controllers. We have the input overlay for that. - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param; - auto& player_buttons = player.buttons[i]; - - player_buttons = ReadStringSetting( - std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param); - if (player_buttons.empty()) { - player_buttons = default_param; - } - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param; - auto& player_analogs = player.analogs[i]; - - player_analogs = ReadStringSetting( - std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param); - if (player_analogs.empty()) { - player_analogs = default_param; - } - } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param; - auto& player_motions = player.motions[i]; - - player_motions = ReadStringSetting( - std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param); - if (player_motions.empty()) { - player_motions = default_param; - } - } - player.use_system_vibrator = ReadBooleanSetting( - std::string(player_prefix).append("use_system_vibrator"), player_index == 0); -} - -void AndroidConfig::ReadAndroidControlValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - - Settings::values.players.SetGlobal(!IsCustomConfig()); - for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { - ReadAndroidPlayerValues(p); - } - if (IsCustomConfig()) { - EndGroup(); - return; - } - // ReadDebugControlValues(); - // ReadHidbusValues(); - - EndGroup(); -} - -void AndroidConfig::SaveAndroidValues() { - if (global) { - SaveAndroidUIValues(); - SaveUIValues(); - SaveOverlayValues(); - } - SaveDriverValues(); - SaveAndroidControlValues(); - - WriteToIni(); -} - -void AndroidConfig::SaveAndroidUIValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Android)); - - WriteCategory(Settings::Category::Android); - - EndGroup(); -} - -void AndroidConfig::SaveUIValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); - - SavePathValues(); - - EndGroup(); -} - -void AndroidConfig::SavePathValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); - - BeginArray(std::string("gamedirs")); - for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) { - SetArrayIndex(i); - const auto& game_dir = AndroidSettings::values.game_dirs[i]; - WriteStringSetting(std::string("path"), game_dir.path); - WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan, - std::make_optional(false)); - } - EndArray(); - - EndGroup(); -} - -void AndroidConfig::SaveDriverValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); - - WriteCategory(Settings::Category::GpuDriver); - - EndGroup(); -} - -void AndroidConfig::SaveOverlayValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay)); - - WriteCategory(Settings::Category::Overlay); - - BeginArray("control_data"); - for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) { - SetArrayIndex(i); - const auto& control_data = AndroidSettings::values.overlay_control_data[i]; - WriteStringSetting(std::string("id"), control_data.id); - WriteBooleanSetting(std::string("enabled"), control_data.enabled); - WriteDoubleSetting(std::string("landscape\\x_position"), - control_data.landscape_position.first); - WriteDoubleSetting(std::string("landscape\\y_position"), - control_data.landscape_position.second); - WriteDoubleSetting(std::string("portrait\\x_position"), - control_data.portrait_position.first); - WriteDoubleSetting(std::string("portrait\\y_position"), - control_data.portrait_position.second); - WriteDoubleSetting(std::string("foldable\\x_position"), - control_data.foldable_position.first); - WriteDoubleSetting(std::string("foldable\\y_position"), - control_data.foldable_position.second); - } - EndArray(); - - EndGroup(); -} - -void AndroidConfig::SaveAndroidPlayerValues(std::size_t player_index) { - std::string player_prefix; - if (type != ConfigType::InputProfile) { - player_prefix = std::string("player_").append(ToString(player_index)).append("_"); - } - - const auto& player = Settings::values.players.GetValue()[player_index]; - if (IsCustomConfig() && player.profile_name.empty()) { - // No custom profile selected - return; - } - - const std::string default_param; - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), - player.buttons[i], std::make_optional(default_param)); - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), - player.analogs[i], std::make_optional(default_param)); - } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), - player.motions[i], std::make_optional(default_param)); - } - WriteBooleanSetting(std::string(player_prefix).append("use_system_vibrator"), - player.use_system_vibrator, std::make_optional(player_index == 0)); -} - -void AndroidConfig::SaveAndroidControlValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - - Settings::values.players.SetGlobal(!IsCustomConfig()); - for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { - SaveAndroidPlayerValues(p); - } - if (IsCustomConfig()) { - EndGroup(); - return; - } - // SaveDebugControlValues(); - // SaveHidbusValues(); - - EndGroup(); -} - -std::vector& AndroidConfig::FindRelevantList(Settings::Category category) { - auto& map = Settings::values.linkage.by_category; - if (map.contains(category)) { - return Settings::values.linkage.by_category[category]; - } - return AndroidSettings::values.linkage.by_category[category]; -} - -void AndroidConfig::ReadAndroidControlPlayerValues(std::size_t player_index) { - BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - - ReadPlayerValues(player_index); - ReadAndroidPlayerValues(player_index); - - EndGroup(); -} - -void AndroidConfig::SaveAndroidControlPlayerValues(std::size_t player_index) { - BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - - LOG_DEBUG(Config, "Saving players control configuration values"); - SavePlayerValues(player_index); - SaveAndroidPlayerValues(player_index); - - EndGroup(); - - WriteToIni(); -} diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h deleted file mode 100644 index 246c4c4b..00000000 --- a/src/android/app/src/main/jni/android_config.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "frontend_common/config.h" - -class AndroidConfig final : public Config { -public: - explicit AndroidConfig(const std::string& config_name = "config", - ConfigType config_type = ConfigType::GlobalConfig); - - void ReloadAllValues() override; - void SaveAllValues() override; - - void ReadAndroidControlPlayerValues(std::size_t player_index); - void SaveAndroidControlPlayerValues(std::size_t player_index); - -protected: - void ReadAndroidPlayerValues(std::size_t player_index); - void ReadAndroidControlValues(); - void ReadAndroidValues(); - void ReadAndroidUIValues(); - void ReadDriverValues(); - void ReadOverlayValues(); - void ReadHidbusValues() override {} - void ReadDebugControlValues() override {} - void ReadPathValues() override; - void ReadShortcutValues() override {} - void ReadUIValues() override; - void ReadUIGamelistValues() override {} - void ReadUILayoutValues() override {} - void ReadMultiplayerValues() override {} - - void SaveAndroidPlayerValues(std::size_t player_index); - void SaveAndroidControlValues(); - void SaveAndroidValues(); - void SaveAndroidUIValues(); - void SaveDriverValues(); - void SaveOverlayValues(); - void SaveHidbusValues() override {} - void SaveDebugControlValues() override {} - void SavePathValues() override; - void SaveShortcutValues() override {} - void SaveUIValues() override; - void SaveUIGamelistValues() override {} - void SaveUILayoutValues() override {} - void SaveMultiplayerValues() override {} - - std::vector& FindRelevantList(Settings::Category category) override; -}; diff --git a/src/android/app/src/main/jni/android_settings.cpp b/src/android/app/src/main/jni/android_settings.cpp deleted file mode 100644 index ee998276..00000000 --- a/src/android/app/src/main/jni/android_settings.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "android_settings.h" - -namespace AndroidSettings { - -Values values; - -} // namespace AndroidSettings diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp deleted file mode 100644 index 74827a08..00000000 --- a/src/android/app/src/main/jni/config.cpp +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include -#include "common/fs/file.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/settings_enums.h" -#include "core/hle/service/acc/profile_manager.h" -#include "input_common/main.h" -#include "jni/config.h" -#include "jni/default_ini.h" -#include "uisettings.h" - -namespace FS = Common::FS; - -Config::Config(const std::string& config_name, ConfigType config_type) - : type(config_type), global{config_type == ConfigType::GlobalConfig} { - Initialize(config_name); -} - -Config::~Config() = default; - -bool Config::LoadINI(const std::string& default_contents, bool retry) { - void(FS::CreateParentDir(config_loc)); - config = std::make_unique(FS::PathToUTF8String(config_loc)); - const auto config_loc_str = FS::PathToUTF8String(config_loc); - if (config->ParseError() < 0) { - if (retry) { - LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", - config_loc_str); - - void(FS::CreateParentDir(config_loc)); - void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents)); - - config = std::make_unique(config_loc_str); - - return LoadINI(default_contents, false); - } - LOG_ERROR(Config, "Failed."); - return false; - } - LOG_INFO(Config, "Successfully loaded {}", config_loc_str); - return true; -} - -template <> -void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault()); - if (setting_value.empty()) { - setting_value = setting.GetDefault(); - } - setting = std::move(setting_value); -} - -template <> -void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); -} - -template -void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - setting = static_cast( - config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); -} - -void Config::ReadValues() { - ReadSetting("ControlsGeneral", Settings::values.mouse_enabled); - ReadSetting("ControlsGeneral", Settings::values.touch_device); - ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled); - ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled); - ReadSetting("ControlsGeneral", Settings::values.vibration_enabled); - ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations); - ReadSetting("ControlsGeneral", Settings::values.motion_enabled); - Settings::values.touchscreen.enabled = - config->GetBoolean("ControlsGeneral", "touch_enabled", true); - Settings::values.touchscreen.rotation_angle = - config->GetInteger("ControlsGeneral", "touch_angle", 0); - Settings::values.touchscreen.diameter_x = - config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); - Settings::values.touchscreen.diameter_y = - config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); - - int num_touch_from_button_maps = - config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); - if (num_touch_from_button_maps > 0) { - for (int i = 0; i < num_touch_from_button_maps; ++i) { - Settings::TouchFromButtonMap map; - map.name = config->Get("ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + - std::string("_name"), - "default"); - const int num_touch_maps = config->GetInteger( - "ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), - 0); - map.buttons.reserve(num_touch_maps); - - for (int j = 0; j < num_touch_maps; ++j) { - std::string touch_mapping = - config->Get("ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + - std::string("_bind_") + std::to_string(j), - ""); - map.buttons.emplace_back(std::move(touch_mapping)); - } - - Settings::values.touch_from_button_maps.emplace_back(std::move(map)); - } - } else { - Settings::values.touch_from_button_maps.emplace_back( - Settings::TouchFromButtonMap{"default", {}}); - num_touch_from_button_maps = 1; - } - Settings::values.touch_from_button_map_index = std::clamp( - Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); - - ReadSetting("ControlsGeneral", Settings::values.udp_input_servers); - - // Data Storage - ReadSetting("Data Storage", Settings::values.use_virtual_sd); - FS::SetSudachiPath(FS::SudachiPath::NANDDir, - config->Get("Data Storage", "nand_directory", - FS::GetSudachiPathString(FS::SudachiPath::NANDDir))); - FS::SetSudachiPath(FS::SudachiPath::SDMCDir, - config->Get("Data Storage", "sdmc_directory", - FS::GetSudachiPathString(FS::SudachiPath::SDMCDir))); - FS::SetSudachiPath(FS::SudachiPath::LoadDir, - config->Get("Data Storage", "load_directory", - FS::GetSudachiPathString(FS::SudachiPath::LoadDir))); - FS::SetSudachiPath(FS::SudachiPath::DumpDir, - config->Get("Data Storage", "dump_directory", - FS::GetSudachiPathString(FS::SudachiPath::DumpDir))); - ReadSetting("Data Storage", Settings::values.gamecard_inserted); - ReadSetting("Data Storage", Settings::values.gamecard_current_game); - ReadSetting("Data Storage", Settings::values.gamecard_path); - - // System - ReadSetting("System", Settings::values.current_user); - Settings::values.current_user = std::clamp(Settings::values.current_user.GetValue(), 0, - Service::Account::MAX_USERS - 1); - - // Disable docked mode by default on Android - Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) - ? Settings::ConsoleMode::Docked - : Settings::ConsoleMode::Handheld); - - const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); - if (rng_seed_enabled) { - Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0)); - } else { - Settings::values.rng_seed.SetValue(0); - } - Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled); - - const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false); - if (custom_rtc_enabled) { - Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0); - } else { - Settings::values.custom_rtc = 0; - } - Settings::values.custom_rtc_enabled = custom_rtc_enabled; - - ReadSetting("System", Settings::values.language_index); - ReadSetting("System", Settings::values.region_index); - ReadSetting("System", Settings::values.time_zone_index); - ReadSetting("System", Settings::values.sound_index); - - // Core - ReadSetting("Core", Settings::values.use_multi_core); - ReadSetting("Core", Settings::values.memory_layout_mode); - - // Cpu - ReadSetting("Cpu", Settings::values.cpu_accuracy); - ReadSetting("Cpu", Settings::values.cpu_debug_mode); - ReadSetting("Cpu", Settings::values.cpuopt_page_tables); - ReadSetting("Cpu", Settings::values.cpuopt_block_linking); - ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer); - ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher); - ReadSetting("Cpu", Settings::values.cpuopt_context_elimination); - ReadSetting("Cpu", Settings::values.cpuopt_const_prop); - ReadSetting("Cpu", Settings::values.cpuopt_misc_ir); - ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks); - ReadSetting("Cpu", Settings::values.cpuopt_fastmem); - ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives); - ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives); - ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check); - ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor); - - // Renderer - ReadSetting("Renderer", Settings::values.renderer_backend); - ReadSetting("Renderer", Settings::values.renderer_debug); - ReadSetting("Renderer", Settings::values.renderer_shader_feedback); - ReadSetting("Renderer", Settings::values.enable_nsight_aftermath); - ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks); - ReadSetting("Renderer", Settings::values.vulkan_device); - - ReadSetting("Renderer", Settings::values.resolution_setup); - ReadSetting("Renderer", Settings::values.scaling_filter); - ReadSetting("Renderer", Settings::values.fsr_sharpening_slider); - ReadSetting("Renderer", Settings::values.anti_aliasing); - ReadSetting("Renderer", Settings::values.fullscreen_mode); - ReadSetting("Renderer", Settings::values.aspect_ratio); - ReadSetting("Renderer", Settings::values.max_anisotropy); - ReadSetting("Renderer", Settings::values.use_speed_limit); - ReadSetting("Renderer", Settings::values.speed_limit); - ReadSetting("Renderer", Settings::values.use_disk_shader_cache); - ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation); - ReadSetting("Renderer", Settings::values.vsync_mode); - ReadSetting("Renderer", Settings::values.shader_backend); - ReadSetting("Renderer", Settings::values.use_asynchronous_shaders); - ReadSetting("Renderer", Settings::values.nvdec_emulation); - ReadSetting("Renderer", Settings::values.use_fast_gpu_time); - ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache); - - ReadSetting("Renderer", Settings::values.bg_red); - ReadSetting("Renderer", Settings::values.bg_green); - ReadSetting("Renderer", Settings::values.bg_blue); - - // Use GPU accuracy normal by default on Android - Settings::values.gpu_accuracy = static_cast(config->GetInteger( - "Renderer", "gpu_accuracy", static_cast(Settings::GpuAccuracy::Normal))); - - // Use GPU default anisotropic filtering on Android - Settings::values.max_anisotropy = - static_cast(config->GetInteger("Renderer", "max_anisotropy", 1)); - - // Disable ASTC compute by default on Android - Settings::values.accelerate_astc.SetValue( - config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu - : Settings::AstcDecodeMode::Cpu); - - // Enable asynchronous presentation by default on Android - Settings::values.async_presentation = - config->GetBoolean("Renderer", "async_presentation", true); - - // Disable force_max_clock by default on Android - Settings::values.renderer_force_max_clock = - config->GetBoolean("Renderer", "force_max_clock", false); - - // Disable use_reactive_flushing by default on Android - Settings::values.use_reactive_flushing = - config->GetBoolean("Renderer", "use_reactive_flushing", false); - - // Audio - ReadSetting("Audio", Settings::values.sink_id); - ReadSetting("Audio", Settings::values.audio_output_device_id); - ReadSetting("Audio", Settings::values.volume); - - // Miscellaneous - // log_filter has a different default here than from common - Settings::values.log_filter = "*:Info"; - ReadSetting("Miscellaneous", Settings::values.use_dev_keys); - - // Debugging - Settings::values.record_frame_times = - config->GetBoolean("Debugging", "record_frame_times", false); - ReadSetting("Debugging", Settings::values.dump_exefs); - ReadSetting("Debugging", Settings::values.dump_nso); - ReadSetting("Debugging", Settings::values.enable_fs_access_log); - ReadSetting("Debugging", Settings::values.reporting_services); - ReadSetting("Debugging", Settings::values.quest_flag); - ReadSetting("Debugging", Settings::values.use_debug_asserts); - ReadSetting("Debugging", Settings::values.use_auto_stub); - ReadSetting("Debugging", Settings::values.disable_macro_jit); - ReadSetting("Debugging", Settings::values.disable_macro_hle); - ReadSetting("Debugging", Settings::values.use_gdbstub); - ReadSetting("Debugging", Settings::values.gdbstub_port); - - const auto title_list = config->Get("AddOns", "title_ids", ""); - std::stringstream ss(title_list); - std::string line; - while (std::getline(ss, line, '|')) { - const auto title_id = std::strtoul(line.c_str(), nullptr, 16); - const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); - - std::stringstream inner_ss(disabled_list); - std::string inner_line; - std::vector out; - while (std::getline(inner_ss, inner_line, '|')) { - out.push_back(inner_line); - } - - Settings::values.disabled_addons.insert_or_assign(title_id, out); - } - - // Web Service - ReadSetting("WebService", Settings::values.enable_telemetry); - ReadSetting("WebService", Settings::values.web_api_url); - ReadSetting("WebService", Settings::values.sudachi_username); - ReadSetting("WebService", Settings::values.sudachi_token); - - // Network - ReadSetting("Network", Settings::values.network_interface); - - // Android - ReadSetting("Android", AndroidSettings::values.picture_in_picture); - ReadSetting("Android", AndroidSettings::values.screen_layout); -} - -void Config::Initialize(const std::string& config_name) { - const auto fs_config_loc = FS::GetSudachiPath(FS::SudachiPath::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(); -} diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h deleted file mode 100644 index 6c13cd09..00000000 --- a/src/android/app/src/main/jni/config.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "common/settings.h" - -class INIReader; - -class Config { - bool LoadINI(const std::string& default_contents = "", bool retry = true); - -public: - enum class ConfigType { - GlobalConfig, - PerGameConfig, - InputProfile, - }; - - explicit Config(const std::string& config_name = "config", - ConfigType config_type = ConfigType::GlobalConfig); - ~Config(); - - void Initialize(const std::string& config_name); - -private: - /** - * Applies a value read from the config to a Setting. - * - * @param group The name of the INI group - * @param setting The sudachi setting to modify - */ - template - void ReadSetting(const std::string& group, Settings::Setting& setting); - - void ReadValues(); - - const ConfigType type; - std::unique_ptr config; - std::string config_loc; - const bool global; -}; diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h deleted file mode 100644 index 23f6436c..00000000 --- a/src/android/app/src/main/jni/default_ini.h +++ /dev/null @@ -1,511 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace DefaultINI { - -const char* android_config_file = R"( - -[ControlsP0] -# The input devices and parameters for each Switch native input -# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... -# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." -# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values - -# Indicates if this player should be connected at boot -connected= - -# for button input, the following devices are available: -# - "keyboard" (default) for keyboard input. Required parameters: -# - "code": the code of the key to bind -# - "sdl" for joystick input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "button"(optional): the index of the button to bind -# - "hat"(optional): the index of the hat to bind as direction buttons -# - "axis"(optional): the index of the axis to bind -# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" -# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is -# triggered if the axis value crosses -# - "direction"(only used for axis): "+" means the button is triggered when the axis value -# is greater than the threshold; "-" means the button is triggered when the axis value -# is smaller than the threshold -button_a= -button_b= -button_x= -button_y= -button_lstick= -button_rstick= -button_l= -button_r= -button_zl= -button_zr= -button_plus= -button_minus= -button_dleft= -button_dup= -button_dright= -button_ddown= -button_lstick_left= -button_lstick_up= -button_lstick_right= -button_lstick_down= -button_sl= -button_sr= -button_home= -button_screenshot= - -# for analog input, the following devices are available: -# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: -# - "up", "down", "left", "right": sub-devices for each direction. -# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" -# - "modifier": sub-devices as a modifier. -# - "modifier_scale": a float number representing the applied modifier scale to the analog input. -# Must be in range of 0.0-1.0. Defaults to 0.5 -# - "sdl" for joystick input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "axis_x": the index of the axis to bind as x-axis (default to 0) -# - "axis_y": the index of the axis to bind as y-axis (default to 1) -lstick= -rstick= - -# for motion input, the following devices are available: -# - "keyboard" (default) for emulating random motion input from buttons. Required parameters: -# - "code": the code of the key to bind -# - "sdl" for motion input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "motion": the index of the motion sensor to bind -# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters: -# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001" -# - "port": the port of the cemu hook server -# - "pad": the index of the joystick -# - "motion": the index of the motion sensor of the joystick to bind -motionleft= -motionright= - -[ControlsGeneral] -# To use the debug_pad, prepend `debug_pad_` before each button setting above. -# i.e. debug_pad_button_a= - -# Enable debug pad inputs to the guest -# 0 (default): Disabled, 1: Enabled -debug_pad_enabled = - -# Whether to enable or disable vibration -# 0: Disabled, 1 (default): Enabled -vibration_enabled= - -# Whether to enable or disable accurate vibrations -# 0 (default): Disabled, 1: Enabled -enable_accurate_vibrations= - -# Enables controller motion inputs -# 0: Disabled, 1 (default): Enabled -motion_enabled = - -# Defines the udp device's touch screen coordinate system for cemuhookudp devices -# - "min_x", "min_y", "max_x", "max_y" -touch_device= - -# for mapping buttons to touch inputs. -#touch_from_button_map=1 -#touch_from_button_maps_0_name=default -#touch_from_button_maps_0_count=2 -#touch_from_button_maps_0_bind_0=foo -#touch_from_button_maps_0_bind_1=bar -# etc. - -# List of Cemuhook UDP servers, delimited by ','. -# Default: 127.0.0.1:26760 -# Example: 127.0.0.1:26760,123.4.5.67:26761 -udp_input_servers = - -# Enable controlling an axis via a mouse input. -# 0 (default): Off, 1: On -mouse_panning = - -# Set mouse sensitivity. -# Default: 1.0 -mouse_panning_sensitivity = - -# Emulate an analog control stick from keyboard inputs. -# 0 (default): Disabled, 1: Enabled -emulate_analog_keyboard = - -# Enable mouse inputs to the guest -# 0 (default): Disabled, 1: Enabled -mouse_enabled = - -# Enable keyboard inputs to the guest -# 0 (default): Disabled, 1: Enabled -keyboard_enabled = - -[Core] -# Whether to use multi-core for CPU emulation -# 0: Disabled, 1 (default): Enabled -use_multi_core = - -# Enable unsafe extended guest system memory layout (8GB DRAM) -# 0 (default): Disabled, 1: Enabled -use_unsafe_extended_memory_layout = - -[Cpu] -# Adjusts various optimizations. -# Auto-select mode enables choice unsafe optimizations. -# Accurate enables only safe optimizations. -# Unsafe allows any unsafe optimizations. -# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations -cpu_accuracy = - -# Allow disabling safe optimizations. -# 0 (default): Disabled, 1: Enabled -cpu_debug_mode = - -# Enable inline page tables optimization (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_page_tables = - -# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps) -# 0: Disabled, 1 (default): Enabled -cpuopt_block_linking = - -# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns) -# 0: Disabled, 1 (default): Enabled -cpuopt_return_stack_buffer = - -# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture) -# 0: Disabled, 1 (default): Enabled -cpuopt_fast_dispatcher = - -# Enable context elimination CPU Optimization (reduce host memory use for guest context) -# 0: Disabled, 1 (default): Enabled -cpuopt_context_elimination = - -# Enable constant propagation CPU optimization (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_const_prop = - -# Enable miscellaneous CPU optimizations (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_misc_ir = - -# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access) -# 0: Disabled, 1 (default): Enabled -cpuopt_reduce_misalign_checks = - -# Enable Host MMU Emulation (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_fastmem = - -# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_fastmem_exclusives = - -# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_recompile_exclusives = - -# Enable optimization to ignore invalid memory accesses (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_ignore_memory_aborts = - -# Enable unfuse FMA (improve performance on CPUs without FMA) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_unfuse_fma = - -# Enable faster FRSQRTE and FRECPE -# Only enabled if cpu_accuracy is set to Unsafe. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_reduce_fp_error = - -# Enable faster ASIMD instructions (32 bits only) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_ignore_standard_fpcr = - -# Enable inaccurate NaN handling -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_inaccurate_nan = - -# Disable address space checks (64 bits only) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_fastmem_check = - -# Enable faster exclusive instructions -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_ignore_global_monitor = - -[Renderer] -# Which backend API to use. -# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null -backend = - -# Whether to enable asynchronous presentation (Vulkan only) -# 0: Off, 1 (default): On -async_presentation = - -# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). -# 0 (default): Disabled, 1: Enabled -force_max_clock = - -# Enable graphics API debugging mode. -# 0 (default): Disabled, 1: Enabled -debug = - -# Enable shader feedback. -# 0 (default): Disabled, 1: Enabled -renderer_shader_feedback = - -# Enable Nsight Aftermath crash dumps -# 0 (default): Disabled, 1: Enabled -nsight_aftermath = - -# Disable shader loop safety checks, executing the shader without loop logic changes -# 0 (default): Disabled, 1: Enabled -disable_shader_loop_safety_checks = - -# Which Vulkan physical device to use (defaults to 0) -vulkan_device = - -# 0: 0.5x (360p/540p) [EXPERIMENTAL] -# 1: 0.75x (540p/810p) [EXPERIMENTAL] -# 2 (default): 1x (720p/1080p) -# 3: 2x (1440p/2160p) -# 4: 3x (2160p/3240p) -# 5: 4x (2880p/4320p) -# 6: 5x (3600p/5400p) -# 7: 6x (4320p/6480p) -resolution_setup = - -# Pixel filter to use when up- or down-sampling rendered frames. -# 0: Nearest Neighbor -# 1 (default): Bilinear -# 2: Bicubic -# 3: Gaussian -# 4: ScaleForce -# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only] -scaling_filter = - -# Anti-Aliasing (AA) -# 0 (default): None, 1: FXAA -anti_aliasing = - -# Whether to use fullscreen or borderless window mode -# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen -fullscreen_mode = - -# Aspect ratio -# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window -aspect_ratio = - -# Anisotropic filtering -# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x -max_anisotropy = - -# Whether to enable VSync or not. -# OpenGL: Values other than 0 enable VSync -# Vulkan: FIFO is selected if the requested mode is not supported by the driver. -# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. -# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. -# Mailbox can have lower latency than FIFO and does not tear but may drop frames. -# Immediate (no synchronization) just presents whatever is available and can exhibit tearing. -# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed -use_vsync = - -# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is -# not available and GLASM is selected, GLSL will be used. -# 0: GLSL, 1 (default): GLASM, 2: SPIR-V -shader_backend = - -# Whether to allow asynchronous shader building. -# 0 (default): Off, 1: On -use_asynchronous_shaders = - -# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory. -# 0 (default): Off, 1: On -use_reactive_flushing = - -# NVDEC emulation. -# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding -nvdec_emulation = - -# Accelerate ASTC texture decoding. -# 0 (default): Off, 1: On -accelerate_astc = - -# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value -# 0: Off, 1: On (default) -use_speed_limit = - -# Limits the speed of the game to run no faster than this value as a percentage of target speed -# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) -speed_limit = - -# Whether to use disk based shader cache -# 0: Off, 1 (default): On -use_disk_shader_cache = - -# Which gpu accuracy level to use -# 0 (default): Normal, 1: High, 2: Extreme (Very slow) -gpu_accuracy = - -# Whether to use asynchronous GPU emulation -# 0 : Off (slow), 1 (default): On (fast) -use_asynchronous_gpu_emulation = - -# Inform the guest that GPU operations completed more quickly than they did. -# 0: Off, 1 (default): On -use_fast_gpu_time = - -# Force unmodified buffers to be flushed, which can cost performance. -# 0: Off (default), 1: On -use_pessimistic_flushes = - -# Whether to use garbage collection or not for GPU caches. -# 0 (default): Off, 1: On -use_caches_gc = - -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0-255. Defaults to 0 for all. -bg_red = -bg_blue = -bg_green = - -[Audio] -# Which audio output engine to use. -# auto (default): Auto-select -# cubeb: Cubeb audio engine (if available) -# sdl2: SDL2 audio engine (if available) -# null: No audio output -output_engine = - -# Which audio device to use. -# auto (default): Auto-select -output_device = - -# Output volume. -# 100 (default): 100%, 0; mute -volume = - -[Data Storage] -# Whether to create a virtual SD card. -# 1: Yes, 0 (default): No -use_virtual_sd = - -# Whether or not to enable gamecard emulation -# 1: Yes, 0 (default): No -gamecard_inserted = - -# Whether or not the gamecard should be emulated as the current game -# If 'gamecard_inserted' is 0 this setting is irrelevant -# 1: Yes, 0 (default): No -gamecard_current_game = - -# Path to an XCI file to use as the gamecard -# If 'gamecard_inserted' is 0 this setting is irrelevant -# If 'gamecard_current_game' is 1 this setting is irrelevant -gamecard_path = - -[System] -# Whether the system is docked -# 1 (default): Yes, 0: No -use_docked_mode = - -# Sets the seed for the RNG generator built into the switch -# rng_seed will be ignored and randomly generated if rng_seed_enabled is false -rng_seed_enabled = -rng_seed = - -# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service -# This will auto-increment, with the time set being the time the game is started -# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used -custom_rtc_enabled = -custom_rtc = - -# Sets the systems language index -# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, -# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, -# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese -language_index = - -# The system region that sudachi will use during emulation -# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan -region_index = - -# The system time zone that sudachi will use during emulation -# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone -time_zone_index = - -# Sets the sound output mode. -# 0: Mono, 1 (default): Stereo, 2: Surround -sound_index = - -[Miscellaneous] -# A filter which removes logs below a certain logging level. -# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical -log_filter = *:Trace - -# Use developer keys -# 0 (default): Disabled, 1: Enabled -use_dev_keys = - -[Debugging] -# Record frame time data, can be found in the log directory. Boolean value -record_frame_times = -# Determines whether or not sudachi will dump the ExeFS of all games it attempts to load while loading them -dump_exefs=false -# Determines whether or not sudachi will dump all NSOs it attempts to load while loading them -dump_nso=false -# Determines whether or not sudachi will save the filesystem access log. -enable_fs_access_log=false -# Enables verbose reporting services -reporting_services = -# Determines whether or not sudachi will report to the game that the emulated console is in Kiosk Mode -# false: Retail/Normal Mode (default), true: Kiosk Mode -quest_flag = -# Determines whether debug asserts should be enabled, which will throw an exception on asserts. -# false: Disabled (default), true: Enabled -use_debug_asserts = -# Determines whether unimplemented HLE service calls should be automatically stubbed. -# false: Disabled (default), true: Enabled -use_auto_stub = -# Enables/Disables the macro JIT compiler -disable_macro_jit=false -# Determines whether to enable the GDB stub and wait for the debugger to attach before running. -# false: Disabled (default), true: Enabled -use_gdbstub=false -# The port to use for the GDB server, if it is enabled. -gdbstub_port=6543 - -[WebService] -# Whether or not to enable telemetry -# 0: No, 1 (default): Yes -enable_telemetry = -# URL for Web API -web_api_url = https://api.sudachi-emu.org -# Username and token for sudachi Web Service -# See https://profile.sudachi-emu.org/ for more info -sudachi_username = -sudachi_token = - -[Network] -# Name of the network interface device to use with sudachi LAN play. -# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo' -# e.g. On Windows: 'Ethernet', 'Wi-Fi' -network_interface = - -[AddOns] -# Used to disable add-ons -# List of title IDs of games that will have add-ons disabled (separated by '|'): -title_ids = -# For each title ID, have a key/value pair called `disabled_` equal to the names of the add-ons to disable (sep. by '|') -# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey -)"; -} // namespace DefaultINI diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp deleted file mode 100644 index dae83bf1..00000000 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include - -#include "common/android/id_cache.h" -#include "common/logging/log.h" -#include "input_common/drivers/android.h" -#include "input_common/drivers/touch_screen.h" -#include "input_common/drivers/virtual_amiibo.h" -#include "input_common/drivers/virtual_gamepad.h" -#include "input_common/main.h" -#include "jni/emu_window/emu_window.h" -#include "jni/native.h" - -void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { - m_window_width = ANativeWindow_getWidth(surface); - m_window_height = ANativeWindow_getHeight(surface); - - // Ensures that we emulate with the correct aspect ratio. - UpdateCurrentFramebufferLayout(m_window_width, m_window_height); - - window_info.render_surface = reinterpret_cast(surface); -} - -void EmuWindow_Android::OnTouchPressed(int id, float x, float y) { - const auto [touch_x, touch_y] = MapToTouchScreen(x, y); - EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x, - touch_y, id); -} - -void EmuWindow_Android::OnTouchMoved(int id, float x, float y) { - const auto [touch_x, touch_y] = MapToTouchScreen(x, y); - EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x, - touch_y, id); -} - -void EmuWindow_Android::OnTouchReleased(int id) { - EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id); -} - -void EmuWindow_Android::OnFrameDisplayed() { - if (!m_first_frame) { - Common::Android::RunJNIOnFiber( - [&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); }); - m_first_frame = true; - } -} - -EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, - std::shared_ptr driver_library) - : m_driver_library{driver_library} { - LOG_INFO(Frontend, "initializing"); - - if (!surface) { - LOG_CRITICAL(Frontend, "surface is nullptr"); - return; - } - - OnSurfaceChanged(surface); - window_info.type = Core::Frontend::WindowSystemType::Android; -} diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp deleted file mode 100644 index f703fa8b..00000000 --- a/src/android/app/src/main/jni/id_cache.cpp +++ /dev/null @@ -1,428 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/assert.h" -#include "common/fs/fs_android.h" -#include "jni/applets/software_keyboard.h" -#include "jni/id_cache.h" -#include "video_core/rasterizer_interface.h" - -static JavaVM* s_java_vm; -static jclass s_native_library_class; -static jclass s_disk_cache_progress_class; -static jclass s_load_callback_stage_class; -static jclass s_game_dir_class; -static jmethodID s_game_dir_constructor; -static jmethodID s_exit_emulation_activity; -static jmethodID s_disk_cache_load_progress; -static jmethodID s_on_emulation_started; -static jmethodID s_on_emulation_stopped; -static jmethodID s_on_program_changed; - -static jclass s_game_class; -static jmethodID s_game_constructor; -static jfieldID s_game_title_field; -static jfieldID s_game_path_field; -static jfieldID s_game_program_id_field; -static jfieldID s_game_developer_field; -static jfieldID s_game_version_field; -static jfieldID s_game_is_homebrew_field; - -static jclass s_string_class; -static jclass s_pair_class; -static jmethodID s_pair_constructor; -static jfieldID s_pair_first_field; -static jfieldID s_pair_second_field; - -static jclass s_overlay_control_data_class; -static jmethodID s_overlay_control_data_constructor; -static jfieldID s_overlay_control_data_id_field; -static jfieldID s_overlay_control_data_enabled_field; -static jfieldID s_overlay_control_data_landscape_position_field; -static jfieldID s_overlay_control_data_portrait_position_field; -static jfieldID s_overlay_control_data_foldable_position_field; - -static jclass s_patch_class; -static jmethodID s_patch_constructor; -static jfieldID s_patch_enabled_field; -static jfieldID s_patch_name_field; -static jfieldID s_patch_version_field; -static jfieldID s_patch_type_field; -static jfieldID s_patch_program_id_field; -static jfieldID s_patch_title_id_field; - -static jclass s_double_class; -static jmethodID s_double_constructor; -static jfieldID s_double_value_field; - -static jclass s_integer_class; -static jmethodID s_integer_constructor; -static jfieldID s_integer_value_field; - -static jclass s_boolean_class; -static jmethodID s_boolean_constructor; -static jfieldID s_boolean_value_field; - -static constexpr jint JNI_VERSION = JNI_VERSION_1_6; - -namespace IDCache { - -JNIEnv* GetEnvForThread() { - thread_local static struct OwnedEnv { - OwnedEnv() { - status = s_java_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (status == JNI_EDETACHED) - s_java_vm->AttachCurrentThread(&env, nullptr); - } - - ~OwnedEnv() { - if (status == JNI_EDETACHED) - s_java_vm->DetachCurrentThread(); - } - - int status; - JNIEnv* env = nullptr; - } owned; - return owned.env; -} - -jclass GetNativeLibraryClass() { - return s_native_library_class; -} - -jclass GetDiskCacheProgressClass() { - return s_disk_cache_progress_class; -} - -jclass GetDiskCacheLoadCallbackStageClass() { - return s_load_callback_stage_class; -} - -jclass GetGameDirClass() { - return s_game_dir_class; -} - -jmethodID GetGameDirConstructor() { - return s_game_dir_constructor; -} - -jmethodID GetExitEmulationActivity() { - return s_exit_emulation_activity; -} - -jmethodID GetDiskCacheLoadProgress() { - return s_disk_cache_load_progress; -} - -jmethodID GetOnEmulationStarted() { - return s_on_emulation_started; -} - -jmethodID GetOnEmulationStopped() { - return s_on_emulation_stopped; -} - -jmethodID GetOnProgramChanged() { - return s_on_program_changed; -} - -jclass GetGameClass() { - return s_game_class; -} - -jmethodID GetGameConstructor() { - return s_game_constructor; -} - -jfieldID GetGameTitleField() { - return s_game_title_field; -} - -jfieldID GetGamePathField() { - return s_game_path_field; -} - -jfieldID GetGameProgramIdField() { - return s_game_program_id_field; -} - -jfieldID GetGameDeveloperField() { - return s_game_developer_field; -} - -jfieldID GetGameVersionField() { - return s_game_version_field; -} - -jfieldID GetGameIsHomebrewField() { - return s_game_is_homebrew_field; -} - -jclass GetStringClass() { - return s_string_class; -} - -jclass GetPairClass() { - return s_pair_class; -} - -jmethodID GetPairConstructor() { - return s_pair_constructor; -} - -jfieldID GetPairFirstField() { - return s_pair_first_field; -} - -jfieldID GetPairSecondField() { - return s_pair_second_field; -} - -jclass GetOverlayControlDataClass() { - return s_overlay_control_data_class; -} - -jmethodID GetOverlayControlDataConstructor() { - return s_overlay_control_data_constructor; -} - -jfieldID GetOverlayControlDataIdField() { - return s_overlay_control_data_id_field; -} - -jfieldID GetOverlayControlDataEnabledField() { - return s_overlay_control_data_enabled_field; -} - -jfieldID GetOverlayControlDataLandscapePositionField() { - return s_overlay_control_data_landscape_position_field; -} - -jfieldID GetOverlayControlDataPortraitPositionField() { - return s_overlay_control_data_portrait_position_field; -} - -jfieldID GetOverlayControlDataFoldablePositionField() { - return s_overlay_control_data_foldable_position_field; -} - -jclass GetPatchClass() { - return s_patch_class; -} - -jmethodID GetPatchConstructor() { - return s_patch_constructor; -} - -jfieldID GetPatchEnabledField() { - return s_patch_enabled_field; -} - -jfieldID GetPatchNameField() { - return s_patch_name_field; -} - -jfieldID GetPatchVersionField() { - return s_patch_version_field; -} - -jfieldID GetPatchTypeField() { - return s_patch_type_field; -} - -jfieldID GetPatchProgramIdField() { - return s_patch_program_id_field; -} - -jfieldID GetPatchTitleIdField() { - return s_patch_title_id_field; -} - -jclass GetDoubleClass() { - return s_double_class; -} - -jmethodID GetDoubleConstructor() { - return s_double_constructor; -} - -jfieldID GetDoubleValueField() { - return s_double_value_field; -} - -jclass GetIntegerClass() { - return s_integer_class; -} - -jmethodID GetIntegerConstructor() { - return s_integer_constructor; -} - -jfieldID GetIntegerValueField() { - return s_integer_value_field; -} - -jclass GetBooleanClass() { - return s_boolean_class; -} - -jmethodID GetBooleanConstructor() { - return s_boolean_constructor; -} - -jfieldID GetBooleanValueField() { - return s_boolean_value_field; -} - -} // namespace IDCache - -#ifdef __cplusplus -extern "C" { -#endif - -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - s_java_vm = vm; - - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) - return JNI_ERR; - - // Initialize Java classes - const jclass native_library_class = env->FindClass("org/sudachi/sudachi_emu/NativeLibrary"); - s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); - s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( - env->FindClass("org/sudachi/sudachi_emu/disk_shader_cache/DiskShaderCacheProgress"))); - s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( - "org/sudachi/sudachi_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); - - const jclass game_dir_class = env->FindClass("org/sudachi/sudachi_emu/model/GameDir"); - s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); - s_game_dir_constructor = env->GetMethodID(game_dir_class, "", "(Ljava/lang/String;Z)V"); - env->DeleteLocalRef(game_dir_class); - - // Initialize methods - s_exit_emulation_activity = - env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); - s_disk_cache_load_progress = - env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); - s_on_emulation_started = - env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); - s_on_emulation_stopped = - env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); - s_on_program_changed = - env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); - - const jclass game_class = env->FindClass("org/sudachi/sudachi_emu/model/Game"); - s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); - s_game_constructor = env->GetMethodID(game_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;Z)V"); - s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); - s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); - s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); - s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); - s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); - s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); - env->DeleteLocalRef(game_class); - - const jclass string_class = env->FindClass("java/lang/String"); - s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); - env->DeleteLocalRef(string_class); - - const jclass pair_class = env->FindClass("kotlin/Pair"); - s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); - s_pair_constructor = - env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); - s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); - env->DeleteLocalRef(pair_class); - - const jclass overlay_control_data_class = - env->FindClass("org/sudachi/sudachi_emu/overlay/model/OverlayControlData"); - s_overlay_control_data_class = - reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); - s_overlay_control_data_constructor = - env->GetMethodID(overlay_control_data_class, "", - "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); - s_overlay_control_data_id_field = - env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); - s_overlay_control_data_enabled_field = - env->GetFieldID(overlay_control_data_class, "enabled", "Z"); - s_overlay_control_data_landscape_position_field = - env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); - s_overlay_control_data_portrait_position_field = - env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); - s_overlay_control_data_foldable_position_field = - env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); - env->DeleteLocalRef(overlay_control_data_class); - - const jclass patch_class = env->FindClass("org/sudachi/sudachi_emu/model/Patch"); - s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); - s_patch_constructor = env->GetMethodID( - patch_class, "", - "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); - s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); - s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); - s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); - s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); - s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); - s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); - env->DeleteLocalRef(patch_class); - - const jclass double_class = env->FindClass("java/lang/Double"); - s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); - s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); - s_double_value_field = env->GetFieldID(double_class, "value", "D"); - env->DeleteLocalRef(double_class); - - const jclass int_class = env->FindClass("java/lang/Integer"); - s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); - s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); - s_integer_value_field = env->GetFieldID(int_class, "value", "I"); - env->DeleteLocalRef(int_class); - - const jclass boolean_class = env->FindClass("java/lang/Boolean"); - s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); - s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); - s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); - env->DeleteLocalRef(boolean_class); - - // Initialize Android Storage - Common::FS::Android::RegisterCallbacks(env, s_native_library_class); - - // Initialize applets - SoftwareKeyboard::InitJNI(env); - - return JNI_VERSION; -} - -void JNI_OnUnload(JavaVM* vm, void* reserved) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { - return; - } - - // UnInitialize Android Storage - Common::FS::Android::UnRegisterCallbacks(); - env->DeleteGlobalRef(s_native_library_class); - env->DeleteGlobalRef(s_disk_cache_progress_class); - env->DeleteGlobalRef(s_load_callback_stage_class); - env->DeleteGlobalRef(s_game_dir_class); - env->DeleteGlobalRef(s_game_class); - env->DeleteGlobalRef(s_string_class); - env->DeleteGlobalRef(s_pair_class); - env->DeleteGlobalRef(s_overlay_control_data_class); - env->DeleteGlobalRef(s_patch_class); - env->DeleteGlobalRef(s_double_class); - env->DeleteGlobalRef(s_integer_class); - env->DeleteGlobalRef(s_boolean_class); - - // UnInitialize applets - SoftwareKeyboard::CleanupJNI(env); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp deleted file mode 100644 index 30f8dabd..00000000 --- a/src/android/app/src/main/jni/native_config.cpp +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-FileCopyrightText: 2023 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include - -#include "android_config.h" -#include "android_settings.h" -#include "common/android/android_common.h" -#include "common/android/id_cache.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "frontend_common/config.h" -#include "native.h" - -std::unique_ptr global_config; -std::unique_ptr per_game_config; - -template -Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { - auto key = Common::Android::GetJString(env, jkey); - auto basic_setting = Settings::values.linkage.by_key[key]; - if (basic_setting != 0) { - return static_cast*>(basic_setting); - } - auto basic_android_setting = AndroidSettings::values.linkage.by_key[key]; - if (basic_android_setting != 0) { - return static_cast*>(basic_android_setting); - } - LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); - return nullptr; -} - -extern "C" { - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) { - global_config = std::make_unique(); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) { - global_config.reset(); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) { - global_config->AndroidConfig::ReloadAllValues(); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) { - global_config->AndroidConfig::SaveAllValues(); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj, - jstring jprogramId, - jstring jfileName) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); - auto file_name = Common::Android::GetJString(env, jfileName); - const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id); - per_game_config = - std::make_unique(config_file_name, Config::ConfigType::PerGameConfig); -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_isPerGameConfigLoaded(JNIEnv* env, - jobject obj) { - return per_game_config != nullptr; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_savePerGameConfig(JNIEnv* env, jobject obj) { - per_game_config->AndroidConfig::SaveAllValues(); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_unloadPerGameConfig(JNIEnv* env, jobject obj) { - per_game_config.reset(); -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, - jstring jkey, jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return false; - } - return setting->GetValue(static_cast(needGlobal)); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey, - jboolean value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(static_cast(value)); -} - -jbyte Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return -1; - } - return setting->GetValue(static_cast(needGlobal)); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey, - jbyte value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(value); -} - -jshort Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return -1; - } - return setting->GetValue(static_cast(needGlobal)); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey, - jshort value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(value); -} - -jint Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return -1; - } - return setting->GetValue(needGlobal); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey, - jint value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(value); -} - -jfloat Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return -1; - } - return setting->GetValue(static_cast(needGlobal)); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey, - jfloat value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(value); -} - -jlong Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return -1; - } - return setting->GetValue(static_cast(needGlobal)); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey, - jlong value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - setting->SetValue(value); -} - -jstring Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey, - jboolean needGlobal) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return Common::Android::ToJString(env, ""); - } - return Common::Android::ToJString(env, setting->GetValue(static_cast(needGlobal))); -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey, - jstring value) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return; - } - - setting->SetValue(Common::Android::GetJString(env, value)); -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - return setting->RuntimeModifiable(); - } - return true; -} - -jstring Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting == nullptr) { - return Common::Android::ToJString(env, ""); - } - if (setting->PairedSetting() == nullptr) { - return Common::Android::ToJString(env, ""); - } - - return Common::Android::ToJString(env, setting->PairedSetting()->GetLabel()); -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getIsSwitchable(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - return setting->Switchable(); - } - return false; -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_usingGlobal(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - return setting->UsingGlobal(); - } - return true; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setGlobal(JNIEnv* env, jobject obj, jstring jkey, - jboolean global) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - setting->SetGlobal(static_cast(global)); - } -} - -jboolean Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getIsSaveable(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - return setting->Save(); - } - return false; -} - -jstring Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getDefaultToString(JNIEnv* env, jobject obj, - jstring jkey) { - auto setting = getSetting(env, jkey); - if (setting != nullptr) { - return Common::Android::ToJString(env, setting->DefaultToString()); - } - return Common::Android::ToJString(env, ""); -} - -jobjectArray Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) { - jclass gameDirClass = Common::Android::GetGameDirClass(); - jmethodID gameDirConstructor = Common::Android::GetGameDirConstructor(); - jobjectArray jgameDirArray = - env->NewObjectArray(AndroidSettings::values.game_dirs.size(), gameDirClass, nullptr); - for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) { - jobject jgameDir = env->NewObject( - gameDirClass, gameDirConstructor, - Common::Android::ToJString(env, AndroidSettings::values.game_dirs[i].path), - static_cast(AndroidSettings::values.game_dirs[i].deep_scan)); - env->SetObjectArrayElement(jgameDirArray, i, jgameDir); - } - return jgameDirArray; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setGameDirs(JNIEnv* env, jobject obj, - jobjectArray gameDirs) { - AndroidSettings::values.game_dirs.clear(); - int size = env->GetArrayLength(gameDirs); - - if (size == 0) { - return; - } - - jobject dir = env->GetObjectArrayElement(gameDirs, 0); - jclass gameDirClass = Common::Android::GetGameDirClass(); - jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;"); - jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z"); - for (int i = 0; i < size; ++i) { - dir = env->GetObjectArrayElement(gameDirs, i); - jstring juriString = static_cast(env->GetObjectField(dir, uriStringField)); - jboolean jdeepScanBoolean = env->GetBooleanField(dir, deepScanBooleanField); - std::string uriString = Common::Android::GetJString(env, juriString); - AndroidSettings::values.game_dirs.push_back( - AndroidSettings::GameDir{uriString, static_cast(jdeepScanBoolean)}); - } -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject obj, - jobject gameDir) { - jclass gameDirClass = Common::Android::GetGameDirClass(); - jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;"); - jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z"); - - jstring juriString = static_cast(env->GetObjectField(gameDir, uriStringField)); - jboolean jdeepScanBoolean = env->GetBooleanField(gameDir, deepScanBooleanField); - std::string uriString = Common::Android::GetJString(env, juriString); - AndroidSettings::values.game_dirs.push_back( - AndroidSettings::GameDir{uriString, static_cast(jdeepScanBoolean)}); -} - -jobjectArray Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj, - jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); - auto& disabledAddons = Settings::values.disabled_addons[program_id]; - jobjectArray jdisabledAddonsArray = - env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(), - Common::Android::ToJString(env, "")); - for (size_t i = 0; i < disabledAddons.size(); ++i) { - env->SetObjectArrayElement(jdisabledAddonsArray, i, - Common::Android::ToJString(env, disabledAddons[i])); - } - return jdisabledAddonsArray; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj, - jstring jprogramId, - jobjectArray jdisabledAddons) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); - Settings::values.disabled_addons[program_id].clear(); - std::vector disabled_addons; - const int size = env->GetArrayLength(jdisabledAddons); - for (int i = 0; i < size; ++i) { - auto jaddon = static_cast(env->GetObjectArrayElement(jdisabledAddons, i)); - disabled_addons.push_back(Common::Android::GetJString(env, jaddon)); - } - Settings::values.disabled_addons[program_id] = disabled_addons; -} - -jobjectArray Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env, - jobject obj) { - jobjectArray joverlayControlDataArray = - env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(), - Common::Android::GetOverlayControlDataClass(), nullptr); - for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) { - const auto& control_data = AndroidSettings::values.overlay_control_data[i]; - jobject jlandscapePosition = - env->NewObject(Common::Android::GetPairClass(), Common::Android::GetPairConstructor(), - Common::Android::ToJDouble(env, control_data.landscape_position.first), - Common::Android::ToJDouble(env, control_data.landscape_position.second)); - jobject jportraitPosition = - env->NewObject(Common::Android::GetPairClass(), Common::Android::GetPairConstructor(), - Common::Android::ToJDouble(env, control_data.portrait_position.first), - Common::Android::ToJDouble(env, control_data.portrait_position.second)); - jobject jfoldablePosition = - env->NewObject(Common::Android::GetPairClass(), Common::Android::GetPairConstructor(), - Common::Android::ToJDouble(env, control_data.foldable_position.first), - Common::Android::ToJDouble(env, control_data.foldable_position.second)); - - jobject jcontrolData = - env->NewObject(Common::Android::GetOverlayControlDataClass(), - Common::Android::GetOverlayControlDataConstructor(), - Common::Android::ToJString(env, control_data.id), control_data.enabled, - jlandscapePosition, jportraitPosition, jfoldablePosition); - env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData); - } - return joverlayControlDataArray; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setOverlayControlData( - JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) { - AndroidSettings::values.overlay_control_data.clear(); - int size = env->GetArrayLength(joverlayControlDataArray); - - if (size == 0) { - return; - } - - for (int i = 0; i < size; ++i) { - jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i); - jstring jidString = static_cast(env->GetObjectField( - joverlayControlData, Common::Android::GetOverlayControlDataIdField())); - bool enabled = static_cast(env->GetBooleanField( - joverlayControlData, Common::Android::GetOverlayControlDataEnabledField())); - - jobject jlandscapePosition = env->GetObjectField( - joverlayControlData, Common::Android::GetOverlayControlDataLandscapePositionField()); - std::pair landscape_position = std::make_pair( - Common::Android::GetJDouble( - env, env->GetObjectField(jlandscapePosition, Common::Android::GetPairFirstField())), - Common::Android::GetJDouble( - env, - env->GetObjectField(jlandscapePosition, Common::Android::GetPairSecondField()))); - - jobject jportraitPosition = env->GetObjectField( - joverlayControlData, Common::Android::GetOverlayControlDataPortraitPositionField()); - std::pair portrait_position = std::make_pair( - Common::Android::GetJDouble( - env, env->GetObjectField(jportraitPosition, Common::Android::GetPairFirstField())), - Common::Android::GetJDouble( - env, - env->GetObjectField(jportraitPosition, Common::Android::GetPairSecondField()))); - - jobject jfoldablePosition = env->GetObjectField( - joverlayControlData, Common::Android::GetOverlayControlDataFoldablePositionField()); - std::pair foldable_position = std::make_pair( - Common::Android::GetJDouble( - env, env->GetObjectField(jfoldablePosition, Common::Android::GetPairFirstField())), - Common::Android::GetJDouble( - env, - env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField()))); - - AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{ - Common::Android::GetJString(env, jidString), enabled, landscape_position, - portrait_position, foldable_position}); - } -} - -jobjectArray Java_org_sudachi_sudachi_1emu_utils_NativeConfig_getInputSettings(JNIEnv* env, jobject obj, - jboolean j_global) { - Settings::values.players.SetGlobal(static_cast(j_global)); - auto& players = Settings::values.players.GetValue(); - jobjectArray j_input_settings = - env->NewObjectArray(players.size(), Common::Android::GetPlayerInputClass(), nullptr); - for (size_t i = 0; i < players.size(); ++i) { - auto j_connected = static_cast(players[i].connected); - - jobjectArray j_buttons = env->NewObjectArray( - players[i].buttons.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); - for (size_t j = 0; j < players[i].buttons.size(); ++j) { - env->SetObjectArrayElement(j_buttons, j, - Common::Android::ToJString(env, players[i].buttons[j])); - } - jobjectArray j_analogs = env->NewObjectArray( - players[i].analogs.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); - for (size_t j = 0; j < players[i].analogs.size(); ++j) { - env->SetObjectArrayElement(j_analogs, j, - Common::Android::ToJString(env, players[i].analogs[j])); - } - jobjectArray j_motions = env->NewObjectArray( - players[i].motions.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); - for (size_t j = 0; j < players[i].motions.size(); ++j) { - env->SetObjectArrayElement(j_motions, j, - Common::Android::ToJString(env, players[i].motions[j])); - } - - auto j_vibration_enabled = static_cast(players[i].vibration_enabled); - auto j_vibration_strength = static_cast(players[i].vibration_strength); - - auto j_body_color_left = static_cast(players[i].body_color_left); - auto j_body_color_right = static_cast(players[i].body_color_right); - auto j_button_color_left = static_cast(players[i].button_color_left); - auto j_button_color_right = static_cast(players[i].button_color_right); - - auto j_profile_name = Common::Android::ToJString(env, players[i].profile_name); - - auto j_use_system_vibrator = players[i].use_system_vibrator; - - jobject playerInput = env->NewObject( - Common::Android::GetPlayerInputClass(), Common::Android::GetPlayerInputConstructor(), - j_connected, j_buttons, j_analogs, j_motions, j_vibration_enabled, j_vibration_strength, - j_body_color_left, j_body_color_right, j_button_color_left, j_button_color_right, - j_profile_name, j_use_system_vibrator); - env->SetObjectArrayElement(j_input_settings, i, playerInput); - } - return j_input_settings; -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_setInputSettings(JNIEnv* env, jobject obj, - jobjectArray j_value, - jboolean j_global) { - auto& players = Settings::values.players.GetValue(static_cast(j_global)); - int playersSize = env->GetArrayLength(j_value); - for (int i = 0; i < playersSize; ++i) { - jobject jplayer = env->GetObjectArrayElement(j_value, i); - - players[i].connected = static_cast( - env->GetBooleanField(jplayer, Common::Android::GetPlayerInputConnectedField())); - - auto j_buttons_array = static_cast( - env->GetObjectField(jplayer, Common::Android::GetPlayerInputButtonsField())); - int buttons_size = env->GetArrayLength(j_buttons_array); - for (int j = 0; j < buttons_size; ++j) { - auto button = static_cast(env->GetObjectArrayElement(j_buttons_array, j)); - players[i].buttons[j] = Common::Android::GetJString(env, button); - } - auto j_analogs_array = static_cast( - env->GetObjectField(jplayer, Common::Android::GetPlayerInputAnalogsField())); - int analogs_size = env->GetArrayLength(j_analogs_array); - for (int j = 0; j < analogs_size; ++j) { - auto analog = static_cast(env->GetObjectArrayElement(j_analogs_array, j)); - players[i].analogs[j] = Common::Android::GetJString(env, analog); - } - auto j_motions_array = static_cast( - env->GetObjectField(jplayer, Common::Android::GetPlayerInputMotionsField())); - int motions_size = env->GetArrayLength(j_motions_array); - for (int j = 0; j < motions_size; ++j) { - auto motion = static_cast(env->GetObjectArrayElement(j_motions_array, j)); - players[i].motions[j] = Common::Android::GetJString(env, motion); - } - - players[i].vibration_enabled = static_cast( - env->GetBooleanField(jplayer, Common::Android::GetPlayerInputVibrationEnabledField())); - players[i].vibration_strength = static_cast( - env->GetIntField(jplayer, Common::Android::GetPlayerInputVibrationStrengthField())); - - players[i].body_color_left = static_cast( - env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorLeftField())); - players[i].body_color_right = static_cast( - env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorRightField())); - players[i].button_color_left = static_cast( - env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorLeftField())); - players[i].button_color_right = static_cast( - env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorRightField())); - - auto profileName = static_cast( - env->GetObjectField(jplayer, Common::Android::GetPlayerInputProfileNameField())); - players[i].profile_name = Common::Android::GetJString(env, profileName); - - players[i].use_system_vibrator = - env->GetBooleanField(jplayer, Common::Android::GetPlayerInputUseSystemVibratorField()); - } -} - -void Java_org_sudachi_sudachi_1emu_utils_NativeConfig_saveControlPlayerValues(JNIEnv* env, jobject obj) { - Settings::values.players.SetGlobal(false); - - // Clear all controls from the config in case the user reverted back to globals - per_game_config->ClearControlPlayerValues(); - for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) { - per_game_config->SaveAndroidControlPlayerValues(index); - } -} - -} // extern "C" diff --git a/src/android/app/src/main/jni/native_input.cpp b/src/android/app/src/main/jni/native_input.cpp deleted file mode 100644 index 0ff784c0..00000000 --- a/src/android/app/src/main/jni/native_input.cpp +++ /dev/null @@ -1,638 +0,0 @@ -// SPDX-FileCopyrightText: 2024 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include - -#include "android_config.h" -#include "common/android/android_common.h" -#include "common/android/id_cache.h" -#include "hid_core/frontend/emulated_controller.h" -#include "hid_core/hid_core.h" -#include "input_common/drivers/android.h" -#include "input_common/drivers/touch_screen.h" -#include "input_common/drivers/virtual_amiibo.h" -#include "input_common/drivers/virtual_gamepad.h" -#include "native.h" - -std::unordered_map> map_profiles; - -bool IsHandheldOnly() { - const auto npad_style_set = - EmulationSession::GetInstance().System().HIDCore().GetSupportedStyleTag(); - - if (npad_style_set.fullkey == 1) { - return false; - } - - if (npad_style_set.handheld == 0) { - return false; - } - - return !Settings::IsDockedMode(); -} - -std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { - return filename.replace_extension(); -} - -bool IsProfileNameValid(std::string_view profile_name) { - return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; -} - -bool ProfileExistsInFilesystem(std::string_view profile_name) { - return Common::FS::Exists(Common::FS::GetSudachiPath(Common::FS::SudachiPath::ConfigDir) / - "input" / fmt::format("{}.ini", profile_name)); -} - -bool ProfileExistsInMap(const std::string& profile_name) { - return map_profiles.find(profile_name) != map_profiles.end(); -} - -bool SaveProfile(const std::string& profile_name, std::size_t player_index) { - if (!ProfileExistsInMap(profile_name)) { - return false; - } - - Settings::values.players.GetValue()[player_index].profile_name = profile_name; - map_profiles[profile_name]->SaveAndroidControlPlayerValues(player_index); - return true; -} - -bool LoadProfile(std::string& profile_name, std::size_t player_index) { - if (!ProfileExistsInMap(profile_name)) { - return false; - } - - if (!ProfileExistsInFilesystem(profile_name)) { - map_profiles.erase(profile_name); - return false; - } - - LOG_INFO(Config, "Loading input profile `{}`", profile_name); - - Settings::values.players.GetValue()[player_index].profile_name = profile_name; - map_profiles[profile_name]->ReadAndroidControlPlayerValues(player_index); - return true; -} - -void ApplyControllerConfig(size_t player_index, - const std::function& apply) { - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - if (player_index == 0) { - auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); - handheld->EnableConfiguration(); - player_one->EnableConfiguration(); - apply(handheld); - apply(player_one); - handheld->DisableConfiguration(); - player_one->DisableConfiguration(); - handheld->SaveCurrentConfig(); - player_one->SaveCurrentConfig(); - } else { - auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); - controller->EnableConfiguration(); - apply(controller); - controller->DisableConfiguration(); - controller->SaveCurrentConfig(); - } -} - -std::vector GetSupportedStyles(int player_index) { - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - const auto npad_style_set = hid_core.GetSupportedStyleTag(); - std::vector supported_indexes; - if (npad_style_set.fullkey == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::Fullkey)); - } - - if (npad_style_set.joycon_dual == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconDual)); - } - - if (npad_style_set.joycon_left == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconLeft)); - } - - if (npad_style_set.joycon_right == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconRight)); - } - - if (player_index == 0 && npad_style_set.handheld == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::Handheld)); - } - - if (npad_style_set.gamecube == 1) { - supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::GameCube)); - } - - return supported_indexes; -} - -void ConnectController(size_t player_index, bool connected) { - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - ApplyControllerConfig(player_index, [&](Core::HID::EmulatedController* controller) { - auto supported_styles = GetSupportedStyles(player_index); - auto controller_style = controller->GetNpadStyleIndex(true); - auto style = std::find(supported_styles.begin(), supported_styles.end(), - static_cast(controller_style)); - if (style == supported_styles.end() && !supported_styles.empty()) { - controller->SetNpadStyleIndex( - static_cast(supported_styles[0])); - } - }); - - if (player_index == 0) { - auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); - handheld->EnableConfiguration(); - player_one->EnableConfiguration(); - if (player_one->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { - if (connected) { - handheld->Connect(); - } else { - handheld->Disconnect(); - } - player_one->Disconnect(); - } else { - if (connected) { - player_one->Connect(); - } else { - player_one->Disconnect(); - } - handheld->Disconnect(); - } - handheld->DisableConfiguration(); - player_one->DisableConfiguration(); - handheld->SaveCurrentConfig(); - player_one->SaveCurrentConfig(); - } else { - auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); - controller->EnableConfiguration(); - if (connected) { - controller->Connect(); - } else { - controller->Disconnect(); - } - controller->DisableConfiguration(); - controller->SaveCurrentConfig(); - } -} - -extern "C" { - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_isHandheldOnly(JNIEnv* env, - jobject j_obj) { - return IsHandheldOnly(); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onGamePadButtonEvent( - JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_button_id, jint j_action) { - EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetButtonState( - Common::Android::GetJString(env, j_guid), j_port, j_button_id, j_action != 0); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onGamePadAxisEvent( - JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_stick_id, jfloat j_value) { - EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetAxisPosition( - Common::Android::GetJString(env, j_guid), j_port, j_stick_id, j_value); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onGamePadMotionEvent( - JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jlong j_delta_timestamp, - jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, - jfloat j_z_accel) { - EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetMotionState( - Common::Android::GetJString(env, j_guid), j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, - j_z_gyro, j_x_accel, j_y_accel, j_z_accel); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onReadNfcTag(JNIEnv* env, - jobject j_obj, - jbyteArray j_data) { - jboolean isCopy{false}; - std::span data(reinterpret_cast(env->GetByteArrayElements(j_data, &isCopy)), - static_cast(env->GetArrayLength(j_data))); - - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->LoadAmiibo(data); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onRemoveNfcTag(JNIEnv* env, - jobject j_obj) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->CloseAmiibo(); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onTouchPressed( - JNIEnv* env, jobject j_obj, jint j_id, jfloat j_x_axis, jfloat j_y_axis) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().Window().OnTouchPressed(j_id, j_x_axis, j_y_axis); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env, - jobject j_obj, jint j_id, - jfloat j_x_axis, - jfloat j_y_axis) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().Window().OnTouchMoved(j_id, j_x_axis, j_y_axis); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, - jobject j_obj, - jint j_id) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().Window().OnTouchReleased(j_id); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onOverlayButtonEventImpl( - JNIEnv* env, jobject j_obj, jint j_port, jint j_button_id, jint j_action) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetButtonState( - j_port, j_button_id, j_action == 1); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onOverlayJoystickEventImpl( - JNIEnv* env, jobject j_obj, jint j_port, jint j_stick_id, jfloat j_x_axis, jfloat j_y_axis) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetStickPosition( - j_port, j_stick_id, j_x_axis, j_y_axis); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_onDeviceMotionEvent( - JNIEnv* env, jobject j_obj, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro, - jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) { - if (EmulationSession::GetInstance().IsRunning()) { - EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetMotionState( - j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel, - j_z_accel); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_reloadInputDevices(JNIEnv* env, - jobject j_obj) { - EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices(); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_registerController(JNIEnv* env, - jobject j_obj, - jobject j_device) { - EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->RegisterController(j_device); -} - -jobjectArray Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getInputDevices( - JNIEnv* env, jobject j_obj) { - auto devices = EmulationSession::GetInstance().GetInputSubsystem().GetInputDevices(); - jobjectArray jdevices = env->NewObjectArray(devices.size(), Common::Android::GetStringClass(), - Common::Android::ToJString(env, "")); - for (size_t i = 0; i < devices.size(); ++i) { - env->SetObjectArrayElement(jdevices, i, - Common::Android::ToJString(env, devices[i].Serialize())); - } - return jdevices; -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env, - jobject j_obj) { - map_profiles.clear(); - const auto input_profile_loc = - Common::FS::GetSudachiPath(Common::FS::SudachiPath::ConfigDir) / "input"; - - if (Common::FS::IsDir(input_profile_loc)) { - Common::FS::IterateDirEntries( - input_profile_loc, - [&](const std::filesystem::path& full_path) { - const auto filename = full_path.filename(); - const auto name_without_ext = - Common::FS::PathToUTF8String(GetNameWithoutExtension(filename)); - - if (filename.extension() == ".ini" && IsProfileNameValid(name_without_ext)) { - map_profiles.insert_or_assign( - name_without_ext, std::make_unique( - name_without_ext, Config::ConfigType::InputProfile)); - } - - return true; - }, - Common::FS::DirEntryFilter::File); - } -} - -jobjectArray Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getInputProfileNames( - JNIEnv* env, jobject j_obj) { - std::vector profile_names; - profile_names.reserve(map_profiles.size()); - - auto it = map_profiles.cbegin(); - while (it != map_profiles.cend()) { - const auto& [profile_name, config] = *it; - if (!ProfileExistsInFilesystem(profile_name)) { - it = map_profiles.erase(it); - continue; - } - - profile_names.push_back(profile_name); - ++it; - } - - std::stable_sort(profile_names.begin(), profile_names.end()); - - jobjectArray j_profile_names = - env->NewObjectArray(profile_names.size(), Common::Android::GetStringClass(), - Common::Android::ToJString(env, "")); - for (size_t i = 0; i < profile_names.size(); ++i) { - env->SetObjectArrayElement(j_profile_names, i, - Common::Android::ToJString(env, profile_names[i])); - } - - return j_profile_names; -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_isProfileNameValid( - JNIEnv* env, jobject j_obj, jstring j_name) { - return Common::Android::GetJString(env, j_name).find_first_of("<>:;\"/\\|,.!?*") == - std::string::npos; -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_createProfile( - JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { - auto profile_name = Common::Android::GetJString(env, j_name); - if (ProfileExistsInMap(profile_name)) { - return false; - } - - map_profiles.insert_or_assign( - profile_name, - std::make_unique(profile_name, Config::ConfigType::InputProfile)); - - return SaveProfile(profile_name, j_player_index); -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_deleteProfile( - JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { - auto profile_name = Common::Android::GetJString(env, j_name); - if (!ProfileExistsInMap(profile_name)) { - return false; - } - - if (!ProfileExistsInFilesystem(profile_name) || - Common::FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) { - map_profiles.erase(profile_name); - } - - Settings::values.players.GetValue()[j_player_index].profile_name = ""; - return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_loadProfile(JNIEnv* env, - jobject j_obj, - jstring j_name, - jint j_player_index) { - auto profile_name = Common::Android::GetJString(env, j_name); - return LoadProfile(profile_name, j_player_index); -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_saveProfile(JNIEnv* env, - jobject j_obj, - jstring j_name, - jint j_player_index) { - auto profile_name = Common::Android::GetJString(env, j_name); - return SaveProfile(profile_name, j_player_index); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_loadPerGameConfiguration( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_selected_index, - jstring j_selected_profile_name) { - static constexpr size_t HANDHELD_INDEX = 8; - - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - Settings::values.players.SetGlobal(false); - - auto profile_name = Common::Android::GetJString(env, j_selected_profile_name); - auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(j_player_index); - - if (j_selected_index == 0) { - Settings::values.players.GetValue()[j_player_index].profile_name = ""; - if (j_player_index == 0) { - Settings::values.players.GetValue()[HANDHELD_INDEX] = {}; - } - Settings::values.players.SetGlobal(true); - emulated_controller->ReloadFromSettings(); - return; - } - if (profile_name.empty()) { - return; - } - auto& player = Settings::values.players.GetValue()[j_player_index]; - auto& global_player = Settings::values.players.GetValue(true)[j_player_index]; - player.profile_name = profile_name; - global_player.profile_name = profile_name; - // Read from the profile into the custom player settings - LoadProfile(profile_name, j_player_index); - // Make sure the controller is connected - player.connected = true; - - emulated_controller->ReloadFromSettings(); - - if (j_player_index > 0) { - return; - } - // Handle Handheld cases - auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX]; - auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - if (player.controller_type == Settings::ControllerType::Handheld) { - handheld_player = player; - } else { - handheld_player = {}; - } - handheld_controller->ReloadFromSettings(); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_beginMapping(JNIEnv* env, - jobject j_obj, - jint jtype) { - EmulationSession::GetInstance().GetInputSubsystem().BeginMapping( - static_cast(jtype)); -} - -jstring Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getNextInput(JNIEnv* env, - jobject j_obj) { - return Common::Android::ToJString( - env, EmulationSession::GetInstance().GetInputSubsystem().GetNextInput().Serialize()); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_stopMapping(JNIEnv* env, - jobject j_obj) { - EmulationSession::GetInstance().GetInputSubsystem().StopMapping(); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params, - jstring j_display_name) { - auto& input_subsystem = EmulationSession::GetInstance().GetInputSubsystem(); - - // Clear all previous mappings - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetButtonParam(button_id, {}); - }); - } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetStickParam(analog_id, {}); - }); - } - - // Apply new mappings - auto device = Common::ParamPackage(Common::Android::GetJString(env, j_device_params)); - auto button_mappings = input_subsystem.GetButtonMappingForDevice(device); - auto analog_mappings = input_subsystem.GetAnalogMappingForDevice(device); - auto display_name = Common::Android::GetJString(env, j_display_name); - for (const auto& button_mapping : button_mappings) { - const std::size_t index = button_mapping.first; - auto named_mapping = button_mapping.second; - named_mapping.Set("display", display_name); - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetButtonParam(index, named_mapping); - }); - } - for (const auto& analog_mapping : analog_mappings) { - const std::size_t index = analog_mapping.first; - auto named_mapping = analog_mapping.second; - named_mapping.Set("display", display_name); - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetStickParam(index, named_mapping); - }); - } -} - -jstring Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getButtonParamImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button) { - return Common::Android::ToJString(env, EmulationSession::GetInstance() - .System() - .HIDCore() - .GetEmulatedControllerByIndex(j_player_index) - ->GetButtonParam(j_button) - .Serialize()); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_setButtonParamImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button_id, jstring j_param) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetButtonParam(j_button_id, - Common::ParamPackage(Common::Android::GetJString(env, j_param))); - }); -} - -jstring Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getStickParamImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick) { - return Common::Android::ToJString(env, EmulationSession::GetInstance() - .System() - .HIDCore() - .GetEmulatedControllerByIndex(j_player_index) - ->GetStickParam(j_stick) - .Serialize()); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_setStickParamImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick_id, jstring j_param) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetStickParam(j_stick_id, - Common::ParamPackage(Common::Android::GetJString(env, j_param))); - }); -} - -jint Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getButtonNameImpl(JNIEnv* env, - jobject j_obj, - jstring j_param) { - return static_cast(EmulationSession::GetInstance().GetInputSubsystem().GetButtonName( - Common::ParamPackage(Common::Android::GetJString(env, j_param)))); -} - -jintArray Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getSupportedStyleTagsImpl( - JNIEnv* env, jobject j_obj, jint j_player_index) { - auto supported_styles = GetSupportedStyles(j_player_index); - jintArray j_supported_indexes = env->NewIntArray(supported_styles.size()); - env->SetIntArrayRegion(j_supported_indexes, 0, supported_styles.size(), - supported_styles.data()); - return j_supported_indexes; -} - -jint Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getStyleIndexImpl( - JNIEnv* env, jobject j_obj, jint j_player_index) { - return static_cast(EmulationSession::GetInstance() - .System() - .HIDCore() - .GetEmulatedControllerByIndex(j_player_index) - ->GetNpadStyleIndex(true)); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_setStyleIndexImpl( - JNIEnv* env, jobject j_obj, jint j_player_index, jint j_style_index) { - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - auto type = static_cast(j_style_index); - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetNpadStyleIndex(type); - }); - if (j_player_index == 0) { - auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); - ConnectController(j_player_index, - player_one->IsConnected(true) || handheld->IsConnected(true)); - } -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_isControllerImpl( - JNIEnv* env, jobject j_obj, jstring jparams) { - return static_cast(EmulationSession::GetInstance().GetInputSubsystem().IsController( - Common::ParamPackage(Common::Android::GetJString(env, jparams)))); -} - -jboolean Java_org_sudachi_sudachi_1emu_features_input_NativeInput_getIsConnected( - JNIEnv* env, jobject j_obj, jint j_player_index) { - auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); - auto* controller = hid_core.GetEmulatedControllerByIndex(static_cast(j_player_index)); - if (j_player_index == 0 && - controller->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { - return hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->IsConnected(true); - } - return controller->IsConnected(true); -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_connectControllersImpl( - JNIEnv* env, jobject j_obj, jbooleanArray j_connected) { - jboolean isCopy = false; - auto j_connected_array_size = env->GetArrayLength(j_connected); - jboolean* j_connected_array = env->GetBooleanArrayElements(j_connected, &isCopy); - for (int i = 0; i < j_connected_array_size; ++i) { - ConnectController(i, j_connected_array[i]); - } -} - -void Java_org_sudachi_sudachi_1emu_features_input_NativeInput_resetControllerMappings( - JNIEnv* env, jobject j_obj, jint j_player_index) { - // Clear all previous mappings - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetButtonParam(button_id, {}); - }); - } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { - controller->SetStickParam(analog_id, {}); - }); - } -} - -} // extern "C" diff --git a/src/android/app/src/main/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so b/src/android/app/src/main/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so deleted file mode 100644 index 9bbff1c7..00000000 Binary files a/src/android/app/src/main/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so and /dev/null differ diff --git a/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png deleted file mode 100644 index 66ebfa85..00000000 Binary files a/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png and /dev/null differ diff --git a/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png deleted file mode 100644 index 71068f45..00000000 Binary files a/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png and /dev/null differ diff --git a/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png b/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png deleted file mode 100644 index 20c77059..00000000 Binary files a/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png and /dev/null differ diff --git a/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png deleted file mode 100644 index d73fad15..00000000 Binary files a/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png and /dev/null differ diff --git a/src/android/app/src/main/res/drawable/button_l3_depressed.xml b/src/android/app/src/main/res/drawable/button_l3_depressed.xml deleted file mode 100644 index b078dedc..00000000 --- a/src/android/app/src/main/res/drawable/button_l3_depressed.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/button_r3.xml b/src/android/app/src/main/res/drawable/button_r3.xml deleted file mode 100644 index 5c6864e2..00000000 --- a/src/android/app/src/main/res/drawable/button_r3.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/dpad_standard.xml b/src/android/app/src/main/res/drawable/dpad_standard.xml deleted file mode 100644 index 28aba657..00000000 --- a/src/android/app/src/main/res/drawable/dpad_standard.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_a.xml b/src/android/app/src/main/res/drawable/facebutton_a.xml deleted file mode 100644 index 668652ed..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_a.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml deleted file mode 100644 index 4fbe0696..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml deleted file mode 100644 index cde7c6a9..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_minus.xml b/src/android/app/src/main/res/drawable/facebutton_minus.xml deleted file mode 100644 index 4296b4fc..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_minus.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml deleted file mode 100644 index 62802784..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_plus.xml b/src/android/app/src/main/res/drawable/facebutton_plus.xml deleted file mode 100644 index 43ae1436..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_plus.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml deleted file mode 100644 index c510e136..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml deleted file mode 100644 index fd2e4429..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_x.xml b/src/android/app/src/main/res/drawable/facebutton_x.xml deleted file mode 100644 index 43fdd14c..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_x.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/facebutton_y.xml b/src/android/app/src/main/res/drawable/facebutton_y.xml deleted file mode 100644 index 980be3b2..00000000 --- a/src/android/app/src/main/res/drawable/facebutton_y.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/ic_arrow_forward.xml b/src/android/app/src/main/res/drawable/ic_arrow_forward.xml deleted file mode 100644 index 3b85a3e2..00000000 --- a/src/android/app/src/main/res/drawable/ic_arrow_forward.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_check_circle.xml b/src/android/app/src/main/res/drawable/ic_check_circle.xml deleted file mode 100644 index 49e6ecd7..00000000 --- a/src/android/app/src/main/res/drawable/ic_check_circle.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_clear.xml b/src/android/app/src/main/res/drawable/ic_clear.xml deleted file mode 100644 index b6edb1d3..00000000 --- a/src/android/app/src/main/res/drawable/ic_clear.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_code.xml b/src/android/app/src/main/res/drawable/ic_code.xml deleted file mode 100644 index 26f83b39..00000000 --- a/src/android/app/src/main/res/drawable/ic_code.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_controller.xml b/src/android/app/src/main/res/drawable/ic_controller.xml deleted file mode 100644 index 060cd9ae..00000000 --- a/src/android/app/src/main/res/drawable/ic_controller.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml b/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml deleted file mode 100644 index 8e3c66f7..00000000 --- a/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml deleted file mode 100644 index d26a7971..00000000 --- a/src/android/app/src/main/res/drawable/ic_delete.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_diamond.xml b/src/android/app/src/main/res/drawable/ic_diamond.xml deleted file mode 100644 index 3896e12e..00000000 --- a/src/android/app/src/main/res/drawable/ic_diamond.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_exit.xml b/src/android/app/src/main/res/drawable/ic_exit.xml deleted file mode 100644 index a55a1d38..00000000 --- a/src/android/app/src/main/res/drawable/ic_exit.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_export.xml b/src/android/app/src/main/res/drawable/ic_export.xml deleted file mode 100644 index 463d2f41..00000000 --- a/src/android/app/src/main/res/drawable/ic_export.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_folder_open.xml b/src/android/app/src/main/res/drawable/ic_folder_open.xml deleted file mode 100644 index 7958fdae..00000000 --- a/src/android/app/src/main/res/drawable/ic_folder_open.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_github.xml b/src/android/app/src/main/res/drawable/ic_github.xml deleted file mode 100644 index c2ee4380..00000000 --- a/src/android/app/src/main/res/drawable/ic_github.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_graphics.xml b/src/android/app/src/main/res/drawable/ic_graphics.xml deleted file mode 100644 index 2fdb5a4d..00000000 --- a/src/android/app/src/main/res/drawable/ic_graphics.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_icon_bg.xml b/src/android/app/src/main/res/drawable/ic_icon_bg.xml deleted file mode 100644 index df62dde9..00000000 --- a/src/android/app/src/main/res/drawable/ic_icon_bg.xml +++ /dev/null @@ -1,751 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/ic_install.xml b/src/android/app/src/main/res/drawable/ic_install.xml deleted file mode 100644 index 01f2de3d..00000000 --- a/src/android/app/src/main/res/drawable/ic_install.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_key.xml b/src/android/app/src/main/res/drawable/ic_key.xml deleted file mode 100644 index a3943634..00000000 --- a/src/android/app/src/main/res/drawable/ic_key.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_launcher.xml b/src/android/app/src/main/res/drawable/ic_launcher.xml deleted file mode 100644 index af009538..00000000 --- a/src/android/app/src/main/res/drawable/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/android/app/src/main/res/drawable/ic_lock.xml b/src/android/app/src/main/res/drawable/ic_lock.xml deleted file mode 100644 index ef97b193..00000000 --- a/src/android/app/src/main/res/drawable/ic_lock.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_nfc.xml b/src/android/app/src/main/res/drawable/ic_nfc.xml deleted file mode 100644 index 3dacf798..00000000 --- a/src/android/app/src/main/res/drawable/ic_nfc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_overlay.xml b/src/android/app/src/main/res/drawable/ic_overlay.xml deleted file mode 100644 index c7986c5a..00000000 --- a/src/android/app/src/main/res/drawable/ic_overlay.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/android/app/src/main/res/drawable/ic_pause.xml b/src/android/app/src/main/res/drawable/ic_pause.xml deleted file mode 100644 index adb3abab..00000000 --- a/src/android/app/src/main/res/drawable/ic_pause.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_pip_play.xml b/src/android/app/src/main/res/drawable/ic_pip_play.xml deleted file mode 100644 index 2303a462..00000000 --- a/src/android/app/src/main/res/drawable/ic_pip_play.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_play.xml b/src/android/app/src/main/res/drawable/ic_play.xml deleted file mode 100644 index 7f01dc59..00000000 --- a/src/android/app/src/main/res/drawable/ic_play.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_restore.xml b/src/android/app/src/main/res/drawable/ic_restore.xml deleted file mode 100644 index d6d9d401..00000000 --- a/src/android/app/src/main/res/drawable/ic_restore.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_settings.xml b/src/android/app/src/main/res/drawable/ic_settings.xml deleted file mode 100644 index e527f85f..00000000 --- a/src/android/app/src/main/res/drawable/ic_settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_sudachi_title.xml b/src/android/app/src/main/res/drawable/ic_sudachi_title.xml deleted file mode 100644 index b733e524..00000000 --- a/src/android/app/src/main/res/drawable/ic_sudachi_title.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/ic_system_settings.xml b/src/android/app/src/main/res/drawable/ic_system_settings.xml deleted file mode 100644 index 7701a2ba..00000000 --- a/src/android/app/src/main/res/drawable/ic_system_settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/ic_website.xml b/src/android/app/src/main/res/drawable/ic_website.xml deleted file mode 100644 index f35b84a7..00000000 --- a/src/android/app/src/main/res/drawable/ic_website.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/android/app/src/main/res/drawable/joystick.xml b/src/android/app/src/main/res/drawable/joystick.xml deleted file mode 100644 index bdd07121..00000000 --- a/src/android/app/src/main/res/drawable/joystick.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/joystick_range.xml b/src/android/app/src/main/res/drawable/joystick_range.xml deleted file mode 100644 index f6282b5c..00000000 --- a/src/android/app/src/main/res/drawable/joystick_range.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/l_shoulder.xml b/src/android/app/src/main/res/drawable/l_shoulder.xml deleted file mode 100644 index 28f9a995..00000000 --- a/src/android/app/src/main/res/drawable/l_shoulder.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml b/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml deleted file mode 100644 index 2f9a1fd7..00000000 --- a/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/android/app/src/main/res/drawable/premium_background.xml b/src/android/app/src/main/res/drawable/premium_background.xml deleted file mode 100644 index 8595e6d9..00000000 --- a/src/android/app/src/main/res/drawable/premium_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml b/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml deleted file mode 100644 index 00393c04..00000000 --- a/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_game_info.xml b/src/android/app/src/main/res/layout-w600dp/fragment_game_info.xml deleted file mode 100644 index 90d95dbb..00000000 --- a/src/android/app/src/main/res/layout-w600dp/fragment_game_info.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/layout/card_applet_option.xml b/src/android/app/src/main/res/layout/card_applet_option.xml deleted file mode 100644 index 19fbec9f..00000000 --- a/src/android/app/src/main/res/layout/card_applet_option.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml deleted file mode 100644 index 09e26990..00000000 --- a/src/android/app/src/main/res/layout/card_driver_option.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - -