Compare commits
12 Commits
android-25
...
android-25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c5e43502a | ||
|
|
2d4ae8dce6 | ||
|
|
6a1bb4430c | ||
|
|
a1ff7c92bf | ||
|
|
b6121c8c97 | ||
|
|
ae9140a715 | ||
|
|
7a10fdd2f2 | ||
|
|
3007032b12 | ||
|
|
fb34d40a1c | ||
|
|
abcb021211 | ||
|
|
807fbad23c | ||
|
|
7a1e249314 |
@@ -6,9 +6,11 @@
|
||||
| [13000](https://github.com/yuzu-emu/yuzu//pull/13000) | [`461eaca7e`](https://github.com/yuzu-emu/yuzu//pull/13000/files) | device_memory_manager: skip unregistered interfaces on invalidate | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13006](https://github.com/yuzu-emu/yuzu//pull/13006) | [`3067bfd12`](https://github.com/yuzu-emu/yuzu//pull/13006/files) | buffer_cache: use mapped range with large vertex buffer size | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13026](https://github.com/yuzu-emu/yuzu//pull/13026) | [`462ea921e`](https://github.com/yuzu-emu/yuzu//pull/13026/files) | shader_recompiler: fix non-const offset for arrayed image types | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13031](https://github.com/yuzu-emu/yuzu//pull/13031) | [`110969e20`](https://github.com/yuzu-emu/yuzu//pull/13031/files) | service: btm: Migrate service to new IPC | [german77](https://github.com/german77/) | Yes |
|
||||
| [13035](https://github.com/yuzu-emu/yuzu//pull/13035) | [`940a71422`](https://github.com/yuzu-emu/yuzu//pull/13035/files) | vi: manage resources independently of nvnflinger and refactor | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13048](https://github.com/yuzu-emu/yuzu//pull/13048) | [`d45a12826`](https://github.com/yuzu-emu/yuzu//pull/13048/files) | ns: rewrite for new IPC | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13030](https://github.com/yuzu-emu/yuzu//pull/13030) | [`4cbafc1ef`](https://github.com/yuzu-emu/yuzu//pull/13030/files) | service: audio: Rewrite IAudioController to new IPC | [german77](https://github.com/german77/) | Yes |
|
||||
| [13032](https://github.com/yuzu-emu/yuzu//pull/13032) | [`ec02a1cfe`](https://github.com/yuzu-emu/yuzu//pull/13032/files) | service: Implement functions needed by Qlaunch | [german77](https://github.com/german77/) | Yes |
|
||||
| [13034](https://github.com/yuzu-emu/yuzu//pull/13034) | [`50ecad547`](https://github.com/yuzu-emu/yuzu//pull/13034/files) | android: Input mapping | [t895](https://github.com/t895/) | Yes |
|
||||
| [13035](https://github.com/yuzu-emu/yuzu//pull/13035) | [`a07f0883b`](https://github.com/yuzu-emu/yuzu//pull/13035/files) | vi: manage resources independently of nvnflinger and refactor | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13048](https://github.com/yuzu-emu/yuzu//pull/13048) | [`72d806db0`](https://github.com/yuzu-emu/yuzu//pull/13048/files) | ns: rewrite for new IPC | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
|
||||
|
||||
End of merge log. You can find the original README.md below the break.
|
||||
|
||||
@@ -3,21 +3,24 @@
|
||||
|
||||
package org.yuzu.yuzu_emu
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||
import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import org.yuzu.yuzu_emu.model.Patch
|
||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
@@ -181,13 +184,46 @@ object NativeLibrary {
|
||||
ErrorUnknown
|
||||
}
|
||||
|
||||
var coreErrorAlertResult = false
|
||||
val coreErrorAlertLock = Object()
|
||||
private var coreErrorAlertResult = false
|
||||
private val coreErrorAlertLock = Object()
|
||||
|
||||
class CoreErrorDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val title = requireArguments().serializable<String>("title")
|
||||
val message = requireArguments().serializable<String>("message")
|
||||
|
||||
return MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.continue_button, null)
|
||||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||
coreErrorAlertResult = false
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
coreErrorAlertResult = true
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCoreErrorImpl(title: String, message: String) {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,7 +239,7 @@ object NativeLibrary {
|
||||
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -234,7 +270,7 @@ object NativeLibrary {
|
||||
}
|
||||
|
||||
// Show the AlertDialog on the main thread.
|
||||
emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
|
||||
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||
|
||||
// Wait for the lock to notify that it is complete.
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
||||
|
||||
@@ -80,14 +80,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
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()) {
|
||||
val playerOne = NativeConfig.getInputSettings(true)[0]
|
||||
if (!playerOne.hasMapping() && InputHandler.androidControllers.isNotEmpty()) {
|
||||
var params: ParamPackage? = null
|
||||
for (controller in InputHandler.registeredControllers) {
|
||||
if (controller.get("port", -1) == 0) {
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.model.Driver
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
@@ -44,15 +44,25 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
}
|
||||
|
||||
// Delay marquee by 3s
|
||||
title.marquee()
|
||||
version.marquee()
|
||||
description.marquee()
|
||||
title.postDelayed(
|
||||
{
|
||||
title.isSelected = true
|
||||
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
version.isSelected = true
|
||||
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
description.isSelected = true
|
||||
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
},
|
||||
3000
|
||||
)
|
||||
title.text = model.title
|
||||
version.text = model.version
|
||||
description.text = model.description
|
||||
buttonDelete.setVisible(
|
||||
model.title != binding.root.context.getString(R.string.system_gpu_driver)
|
||||
)
|
||||
if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) {
|
||||
buttonDelete.visibility = View.VISIBLE
|
||||
} else {
|
||||
buttonDelete.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
@@ -11,7 +12,6 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
||||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
||||
@@ -29,7 +29,13 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
||||
override fun bind(model: GameDir) {
|
||||
binding.apply {
|
||||
path.text = Uri.parse(model.uriString).path
|
||||
path.marquee()
|
||||
path.postDelayed(
|
||||
{
|
||||
path.isSelected = true
|
||||
path.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
buttonEdit.setOnClickListener {
|
||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
@@ -26,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity) :
|
||||
@@ -44,7 +44,14 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
|
||||
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||
|
||||
binding.textGameTitle.marquee()
|
||||
binding.textGameTitle.postDelayed(
|
||||
{
|
||||
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.textGameTitle.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.cardGame.setOnClickListener { onClick(model) }
|
||||
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
||||
}
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||
import org.yuzu.yuzu_emu.model.GameProperty
|
||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class GamePropertiesAdapter(
|
||||
@@ -73,15 +76,23 @@ class GamePropertiesAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
binding.details.marquee()
|
||||
binding.details.postDelayed({
|
||||
binding.details.isSelected = true
|
||||
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
}, 3000)
|
||||
|
||||
if (submenuProperty.details != null) {
|
||||
binding.details.setVisible(true)
|
||||
binding.details.visibility = View.VISIBLE
|
||||
binding.details.text = submenuProperty.details.invoke()
|
||||
} else if (submenuProperty.detailsFlow != null) {
|
||||
binding.details.setVisible(true)
|
||||
submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
|
||||
binding.details.visibility = View.VISIBLE
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
submenuProperty.detailsFlow.collect { binding.details.text = it }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.details.setVisible(false)
|
||||
binding.details.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,10 +112,14 @@ class GamePropertiesAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
binding.buttonInstall.setVisible(installableProperty.install != null)
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
|
||||
binding.buttonExport.setVisible(installableProperty.export != null)
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
|
||||
if (installableProperty.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
|
||||
}
|
||||
if (installableProperty.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class HomeSettingAdapter(
|
||||
@@ -56,8 +59,18 @@ class HomeSettingAdapter(
|
||||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
|
||||
model.details.collect(viewLifecycle) { updateOptionDetails(it) }
|
||||
binding.optionDetail.marquee()
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
model.details.collect { updateOptionDetails(it) }
|
||||
}
|
||||
}
|
||||
binding.optionDetail.postDelayed(
|
||||
{
|
||||
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.optionDetail.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
@@ -77,7 +90,7 @@ class HomeSettingAdapter(
|
||||
private fun updateOptionDetails(detailString: String) {
|
||||
if (detailString.isNotEmpty()) {
|
||||
binding.optionDetail.text = detailString
|
||||
binding.optionDetail.setVisible(true)
|
||||
binding.optionDetail.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||
import org.yuzu.yuzu_emu.model.Installable
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class InstallableAdapter(installables: List<Installable>) :
|
||||
@@ -26,10 +26,14 @@ class InstallableAdapter(installables: List<Installable>) :
|
||||
binding.title.setText(model.titleId)
|
||||
binding.description.setText(model.descriptionId)
|
||||
|
||||
binding.buttonInstall.setVisible(model.install != null)
|
||||
binding.buttonInstall.setOnClickListener { model.install?.invoke() }
|
||||
binding.buttonExport.setVisible(model.export != null)
|
||||
binding.buttonExport.setOnClickListener { model.export?.invoke() }
|
||||
if (model.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { model.install.invoke() }
|
||||
}
|
||||
if (model.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { model.export.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.License
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
||||
@@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic
|
||||
binding.apply {
|
||||
textSettingName.text = root.context.getString(model.titleId)
|
||||
textSettingDescription.text = root.context.getString(model.descriptionId)
|
||||
textSettingValue.setVisible(false)
|
||||
textSettingValue.visibility = View.GONE
|
||||
|
||||
root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@@ -16,7 +17,6 @@ import org.yuzu.yuzu_emu.model.SetupCallback
|
||||
import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.model.StepState
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||
@@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||
AbstractViewHolder<SetupPage>(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.buttonAction.visibility = View.INVISIBLE
|
||||
binding.textConfirmation.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
binding.icon.setImageDrawable(
|
||||
|
||||
@@ -11,13 +11,16 @@ import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class InputProfileDialogFragment : DialogFragment() {
|
||||
private var position = 0
|
||||
@@ -107,21 +110,25 @@ class InputProfileDialogFragment : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.delete_input_profile,
|
||||
descriptionId = R.string.delete_input_profile_description,
|
||||
positiveAction = {
|
||||
setting.deleteProfile(it)
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = android.R.string.cancel
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowDeleteProfileDialog("")
|
||||
dismiss()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowDeleteProfileDialog.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.delete_input_profile,
|
||||
descriptionId = R.string.delete_input_profile_description,
|
||||
positiveAction = {
|
||||
setting.deleteProfile(it)
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = android.R.string.cancel
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowDeleteProfileDialog("")
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@@ -65,23 +70,39 @@ class SettingsActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldRecreate.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldNavigateBack.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
@@ -25,7 +29,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
||||
private var type = 0
|
||||
@@ -166,11 +169,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
when (type) {
|
||||
SettingsItem.TYPE_SLIDER -> {
|
||||
settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderTextValue.collect {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderProgress.collect {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,21 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
private lateinit var presenter: SettingsFragmentPresenter
|
||||
@@ -59,7 +63,8 @@ class SettingsFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector", "NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||
@@ -95,37 +100,65 @@ class SettingsFragment : Fragment() {
|
||||
settingsViewModel.setShouldNavigateBack(true)
|
||||
}
|
||||
|
||||
settingsViewModel.shouldReloadSettingsList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||
) { if (it) presenter.loadSettingsList() }
|
||||
settingsViewModel.adapterItemChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
||||
) { if (it != -1) settingsAdapter?.notifyItemChanged(it) }
|
||||
settingsViewModel.datasetChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setDatasetChanged(false) }
|
||||
) { if (it) settingsAdapter?.notifyDataSetChanged() }
|
||||
settingsViewModel.reloadListAndNotifyDataset.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) }
|
||||
) { if (it) presenter.loadSettingsList(true) }
|
||||
settingsViewModel.shouldShowResetInputDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowResetInputDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.reset_mapping,
|
||||
descriptionId = R.string.reset_mapping_description,
|
||||
positiveAction = {
|
||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
settingsViewModel.adapterItemChanged.collect {
|
||||
if (it != -1) {
|
||||
settingsAdapter?.notifyItemChanged(it)
|
||||
settingsViewModel.setAdapterItemChanged(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
settingsViewModel.datasetChanged.collect {
|
||||
if (it) {
|
||||
settingsAdapter?.notifyDataSetChanged()
|
||||
settingsViewModel.setDatasetChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.reloadListAndNotifyDataset.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setReloadListAndNotifyDataset(false)
|
||||
presenter.loadSettingsList(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetInputDialog.collectLatest {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.reset_mapping,
|
||||
descriptionId = R.string.reset_mapping_description,
|
||||
positiveAction = {
|
||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowResetInputDialog(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,17 +15,19 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import info.debatty.java.stringsimilarity.Cosine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsSearchFragment : Fragment() {
|
||||
private var _binding: FragmentSettingsSearchBinding? = null
|
||||
@@ -81,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
|
||||
search()
|
||||
binding.settingsList.smoothScrollToPosition(0)
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collect {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +106,10 @@ class SettingsSearchFragment : Fragment() {
|
||||
|
||||
private fun search() {
|
||||
val searchTerm = binding.searchText.text.toString().lowercase()
|
||||
binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false)
|
||||
binding.clearButton.visibility =
|
||||
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
||||
if (searchTerm.isEmpty()) {
|
||||
binding.noResultsView.setVisible(visible = false, gone = false)
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
settingsAdapter?.submitList(emptyList())
|
||||
return
|
||||
}
|
||||
@@ -129,7 +136,8 @@ class SettingsSearchFragment : Fragment() {
|
||||
optionalSetting
|
||||
}
|
||||
settingsAdapter?.submitList(sortedList)
|
||||
binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false)
|
||||
binding.noResultsView.visibility =
|
||||
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
private fun focusSearch() {
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -23,18 +22,27 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
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)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
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.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -21,10 +20,10 @@ class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
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)
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
binding.icon.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) =
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -23,26 +22,38 @@ class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: Setting
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingValue.text = setting.getSelectedValue()
|
||||
|
||||
when (item) {
|
||||
binding.buttonOptions.visibility = when (item) {
|
||||
is AnalogInputSetting -> {
|
||||
val param = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
||||
binding.buttonOptions.setVisible(
|
||||
if (
|
||||
param.get("engine", "") == "analog_from_button" ||
|
||||
param.has("axis_x") || param.has("axis_y")
|
||||
)
|
||||
param.has("axis_x") || param.has("axis_y")
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
is ButtonInputSetting -> {
|
||||
val param = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
|
||||
binding.buttonOptions.setVisible(
|
||||
if (
|
||||
param.has("code") || param.has("button") || param.has("hat") ||
|
||||
param.has("axis")
|
||||
)
|
||||
param.has("axis")
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
is ModifierInputSetting -> {
|
||||
val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
||||
binding.buttonOptions.setVisible(params.has("modifier"))
|
||||
if (params.has("modifier")) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -17,8 +16,8 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as RunnableSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
@@ -26,13 +25,19 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -20,10 +19,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
if (item.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
when (item) {
|
||||
is SingleChoiceSetting -> {
|
||||
val resMgr = binding.textSettingValue.context.resources
|
||||
@@ -45,12 +48,16 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
}
|
||||
}
|
||||
if (binding.textSettingValue.text.isEmpty()) {
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -19,18 +18,26 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SliderSetting
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(true)
|
||||
if (item.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = String.format(
|
||||
binding.textSettingValue.context.getString(R.string.value_with_units),
|
||||
setting.getSelectedValue(),
|
||||
setting.units
|
||||
)
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -17,8 +16,8 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SubmenuSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
@@ -26,13 +25,19 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@@ -20,8 +19,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
||||
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
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||
@@ -29,9 +32,13 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
||||
adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -15,6 +16,9 @@ import androidx.core.view.updatePadding
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -28,7 +32,6 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.AddonUtil
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
|
||||
class AddonsFragment : Fragment() {
|
||||
@@ -57,6 +60,8 @@ class AddonsFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
||||
@@ -73,41 +78,57 @@ class AddonsFragment : Fragment() {
|
||||
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)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonList.collect {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModInstallPicker.collect {
|
||||
if (it) {
|
||||
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
addonViewModel.showModInstallPicker(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModNoticeDialog.collect {
|
||||
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.showModNoticeDialog(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonToDelete.collect {
|
||||
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)
|
||||
addonViewModel.setAddonToDelete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -13,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
@@ -31,7 +35,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -60,6 +63,8 @@ class DriverManagerFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
@@ -84,8 +89,15 @@ class DriverManagerFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
driverViewModel.showClearButton.collect(viewLifecycleOwner) {
|
||||
binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
driverViewModel.showClearButton.collect {
|
||||
binding.toolbarDrivers.menu
|
||||
.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class DriversLoadingDialogFragment : DialogFragment() {
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
@@ -41,7 +44,13 @@ class DriversLoadingDialogFragment : DialogFragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -32,6 +32,9 @@ import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.window.layout.FoldingFeature
|
||||
@@ -39,6 +42,9 @@ 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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@@ -57,7 +63,6 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.lang.NullPointerException
|
||||
|
||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
@@ -85,6 +90,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
.collect { updateFoldableLayout(context, it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
@@ -155,6 +168,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (requireActivity().isFinishing) {
|
||||
@@ -335,85 +350,128 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
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
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderProgress.collectLatest {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = 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!!)
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.totalShaders.collectLatest {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderMessage.collectLatest {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect {
|
||||
if (it) {
|
||||
startEmulation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStarted.collectLatest {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
||||
if (it) startEmulation()
|
||||
emulationState.updateSurface()
|
||||
|
||||
// Setup overlays
|
||||
updateShowFpsOverlay()
|
||||
updateThermalOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.isEmulationStopping.collectLatest {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.drawerOpen.collect {
|
||||
if (it) {
|
||||
binding.drawerLayout.open()
|
||||
binding.inGameMenu.requestFocus()
|
||||
} else {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.programChanged.collect {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStopped.collect {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,12 +500,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
if (showInputOverlay) {
|
||||
binding.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
binding.surfaceInputOverlay.setVisible(
|
||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
||||
)
|
||||
if (showInputOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
@@ -484,9 +544,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun updateShowFpsOverlay() {
|
||||
val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
||||
binding.showFpsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val FRAMETIME = 2
|
||||
@@ -506,17 +564,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
||||
binding.showFpsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
binding.showFpsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateThermalOverlay() {
|
||||
val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
||||
binding.showThermalsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) {
|
||||
thermalStatsUpdater = {
|
||||
if (emulationViewModel.emulationStarted.value &&
|
||||
!emulationViewModel.isEmulationStopping.value
|
||||
@@ -538,10 +596,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
|
||||
binding.showThermalsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (thermalStatsUpdater != null) {
|
||||
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
|
||||
}
|
||||
binding.showThermalsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,12 +870,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.doneControlConfig.setVisible(false)
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
}
|
||||
|
||||
private fun stopConfiguringControls() {
|
||||
binding.doneControlConfig.setVisible(false)
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
// Unlock the orientation if it was locked for editing
|
||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
|
||||
|
||||
@@ -13,6 +13,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
@@ -24,7 +27,6 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class GameFoldersFragment : Fragment() {
|
||||
private var _binding: FragmentFoldersBinding? = null
|
||||
@@ -68,8 +70,12 @@ class GameFoldersFragment : Fragment() {
|
||||
adapter = FolderAdapter(requireActivity(), gamesViewModel)
|
||||
}
|
||||
|
||||
gamesViewModel.folders.collect(viewLifecycleOwner) {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.folders.collect {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
|
||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameMetadata
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
|
||||
class GameInfoFragment : Fragment() {
|
||||
@@ -86,7 +85,7 @@ class GameInfoFragment : Fragment() {
|
||||
copyToClipboard(getString(R.string.developer), args.game.developer)
|
||||
}
|
||||
} else {
|
||||
developer.setVisible(false)
|
||||
developer.visibility = View.GONE
|
||||
}
|
||||
|
||||
version.setHint(R.string.version)
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -16,7 +18,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
@@ -42,9 +46,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
|
||||
@@ -74,6 +76,8 @@ class GamePropertiesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
@@ -103,7 +107,13 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
|
||||
binding.title.text = args.game.title
|
||||
binding.title.marquee()
|
||||
binding.title.postDelayed(
|
||||
{
|
||||
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.title.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.buttonStart.setOnClickListener {
|
||||
LaunchGameDialogFragment.newInstance(args.game)
|
||||
@@ -112,14 +122,28 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
reloadList()
|
||||
|
||||
homeViewModel.openImportSaves.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setOpenImportSaves(false) }
|
||||
) { if (it) importSaves.launch(arrayOf("application/zip")) }
|
||||
homeViewModel.reloadPropertiesList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.reloadPropertiesList(false) }
|
||||
) { if (it) reloadList() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.reloadPropertiesList.collect {
|
||||
if (it) {
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
@@ -32,7 +35,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
@@ -73,10 +75,14 @@ class InstallableFragment : Fragment() {
|
||||
binding.root.findNavController().popBackStack()
|
||||
}
|
||||
|
||||
homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,15 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class ProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
@@ -62,49 +64,71 @@ class ProgressDialogFragment : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.message.isSelected = true
|
||||
taskViewModel.isComplete.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(
|
||||
requireContext(),
|
||||
result,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.isComplete.collect {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(
|
||||
requireContext(),
|
||||
result,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
|
||||
else -> {
|
||||
// Do nothing
|
||||
else -> {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
taskViewModel.cancelled.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
dialog?.setTitle(R.string.cancelling)
|
||||
}
|
||||
}
|
||||
taskViewModel.progress.collect(viewLifecycleOwner) {
|
||||
if (it != 0.0) {
|
||||
binding.progressBar.apply {
|
||||
isIndeterminate = false
|
||||
progress = (
|
||||
(it / taskViewModel.maxProgress.value) *
|
||||
PROGRESS_BAR_RESOLUTION
|
||||
).toInt()
|
||||
min = 0
|
||||
max = PROGRESS_BAR_RESOLUTION
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.cancelled.collect {
|
||||
if (it) {
|
||||
dialog?.setTitle(R.string.cancelling)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.progress.collect {
|
||||
if (it != 0.0) {
|
||||
binding.progressBar.apply {
|
||||
isIndeterminate = false
|
||||
progress = (
|
||||
(it / taskViewModel.maxProgress.value) *
|
||||
PROGRESS_BAR_RESOLUTION
|
||||
).toInt()
|
||||
min = 0
|
||||
max = PROGRESS_BAR_RESOLUTION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.message.collect {
|
||||
if (it.isEmpty()) {
|
||||
binding.message.visibility = View.GONE
|
||||
} else {
|
||||
binding.message.visibility = View.VISIBLE
|
||||
binding.message.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
taskViewModel.message.collect(viewLifecycleOwner) {
|
||||
binding.message.setVisible(it.isNotEmpty())
|
||||
binding.message.text = it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
@@ -17,9 +18,14 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@@ -29,8 +35,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
@@ -54,6 +58,8 @@ class SearchFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||
@@ -75,18 +81,42 @@ class SearchFragment : Fragment() {
|
||||
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
|
||||
|
||||
binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
|
||||
binding.clearButton.setVisible(text.toString().isNotEmpty())
|
||||
if (text.toString().isNotEmpty()) {
|
||||
binding.clearButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.clearButton.visibility = View.INVISIBLE
|
||||
}
|
||||
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())
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchFocused.collect {
|
||||
if (it) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.games.collectLatest { filterAndSearch() }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchedGames.collect {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -22,6 +23,9 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
@@ -42,8 +46,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SetupFragment : Fragment() {
|
||||
private var _binding: FragmentSetupBinding? = null
|
||||
@@ -75,6 +77,8 @@ class SetupFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
@@ -206,14 +210,28 @@ class SetupFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
homeViewModel.shouldPageForward.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setShouldPageForward(false) }
|
||||
) { if (it) pageForward() }
|
||||
homeViewModel.gamesDirSelected.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setGamesDirSelected(false) }
|
||||
) { if (it) gamesDirCallback.onStepCompleted() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.shouldPageForward.collect {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.gamesDirSelected.collect {
|
||||
if (it) {
|
||||
gamesDirCallback.onStepCompleted()
|
||||
homeViewModel.setGamesDirSelected(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.viewPager2.apply {
|
||||
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
|
||||
@@ -274,8 +292,12 @@ class SetupFragment : Fragment() {
|
||||
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
||||
|
||||
binding.buttonNext.setVisible(nextIsVisible)
|
||||
binding.buttonBack.setVisible(backIsVisible)
|
||||
if (nextIsVisible) {
|
||||
binding.buttonNext.visibility = View.VISIBLE
|
||||
}
|
||||
if (backIsVisible) {
|
||||
binding.buttonBack.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
hasBeenWarned = BooleanArray(pages.size)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -13,16 +14,19 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
||||
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
@@ -40,6 +44,8 @@ class GamesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||
@@ -82,28 +88,49 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
gamesViewModel.isReloading.collect(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
binding.noticeText.setVisible(
|
||||
visible = gamesViewModel.games.value.isEmpty() && !it,
|
||||
gone = false
|
||||
)
|
||||
}
|
||||
gamesViewModel.games.collect(viewLifecycleOwner) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
gamesViewModel.shouldSwapData.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { gamesViewModel.setShouldSwapData(false) }
|
||||
) {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.isReloading.collect {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
if (gamesViewModel.games.value.isEmpty() && !it) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.games.collectLatest {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldSwapData.collect {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldScrollToTop.collect {
|
||||
if (it) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gamesViewModel.shouldScrollToTop.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { gamesViewModel.setShouldScrollToTop(false) }
|
||||
) { if (it) scrollToTop() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
@@ -27,6 +30,7 @@ import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@@ -43,7 +47,6 @@ import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
@@ -136,22 +139,41 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
// 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)
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
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)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.contentToInstall.collect {
|
||||
if (it != null) {
|
||||
installContent(it)
|
||||
homeViewModel.setContentToInstall(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.checkKeys.collect {
|
||||
if (it) {
|
||||
checkKeys()
|
||||
homeViewModel.setCheckKeys(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) {
|
||||
if (it) checkKeys()
|
||||
}
|
||||
|
||||
setInsets()
|
||||
@@ -192,14 +214,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
private fun showNavigation(visible: Boolean, animated: Boolean) {
|
||||
if (!animated) {
|
||||
binding.navigationView.setVisible(visible)
|
||||
if (visible) {
|
||||
binding.navigationView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val smallLayout = resources.getBoolean(R.bool.small_layout)
|
||||
binding.navigationView.animate().apply {
|
||||
if (visible) {
|
||||
binding.navigationView.setVisible(true)
|
||||
binding.navigationView.visibility = View.VISIBLE
|
||||
duration = 300
|
||||
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
|
||||
|
||||
@@ -238,7 +264,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
}.withEndAction {
|
||||
if (!visible) {
|
||||
binding.navigationView.setVisible(visible = false, gone = false)
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
@@ -246,7 +272,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
private fun showStatusBarShade(visible: Boolean) {
|
||||
binding.statusBarShade.animate().apply {
|
||||
if (visible) {
|
||||
binding.statusBarShade.setVisible(true)
|
||||
binding.statusBarShade.visibility = View.VISIBLE
|
||||
binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
|
||||
duration = 300
|
||||
translationY(0f)
|
||||
@@ -258,7 +284,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
}.withEndAction {
|
||||
if (!visible) {
|
||||
binding.statusBarShade.setVisible(visible = false, gone = false)
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
@@ -498,8 +524,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
this@MainActivity,
|
||||
titleId = R.string.content_install_notice,
|
||||
descriptionId = R.string.content_install_notice_description,
|
||||
positiveAction = { homeViewModel.setContentToInstall(documents) },
|
||||
negativeAction = {}
|
||||
positiveAction = { homeViewModel.setContentToInstall(documents) }
|
||||
)
|
||||
}
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_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 <reified T> Flow<T>.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_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) {
|
||||
@@ -59,35 +57,4 @@ object ViewUtils {
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,22 +23,6 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
window_info.render_surface = reinterpret_cast<void*>(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<void>(
|
||||
|
||||
@@ -38,10 +38,6 @@ public:
|
||||
void OnSurfaceChanged(ANativeWindow* surface);
|
||||
void OnFrameDisplayed() override;
|
||||
|
||||
void OnTouchPressed(int id, float x, float y);
|
||||
void OnTouchMoved(int id, float x, float y);
|
||||
void OnTouchReleased(int id);
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
|
||||
}
|
||||
|
||||
@@ -190,7 +190,8 @@ void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* e
|
||||
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);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(
|
||||
j_id, j_x_axis, j_y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,14 +199,15 @@ void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env
|
||||
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);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(
|
||||
j_id, j_x_axis, j_y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj,
|
||||
jint j_id) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased(j_id);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(j_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/select_gpu_driver_default" />
|
||||
|
||||
@@ -49,7 +52,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_gpu_driver_description" />
|
||||
|
||||
@@ -59,7 +65,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_gpu_driver_description" />
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button_layout"
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:singleLine="true"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:ellipsize="none"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:layout_marginTop="6dp"
|
||||
android:visibility="gone"
|
||||
|
||||
@@ -76,7 +76,10 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
tools:text="deko_basic" />
|
||||
|
||||
|
||||
@@ -543,16 +543,6 @@ add_library(core STATIC
|
||||
hle/service/btdrv/btdrv.h
|
||||
hle/service/btm/btm.cpp
|
||||
hle/service/btm/btm.h
|
||||
hle/service/btm/btm_debug.cpp
|
||||
hle/service/btm/btm_debug.h
|
||||
hle/service/btm/btm_system.cpp
|
||||
hle/service/btm/btm_system.h
|
||||
hle/service/btm/btm_system_core.cpp
|
||||
hle/service/btm/btm_system_core.h
|
||||
hle/service/btm/btm_user.cpp
|
||||
hle/service/btm/btm_user.h
|
||||
hle/service/btm/btm_user_core.cpp
|
||||
hle/service/btm/btm_user_core.h
|
||||
hle/service/caps/caps.cpp
|
||||
hle/service/caps/caps.h
|
||||
hle/service/caps/caps_a.cpp
|
||||
|
||||
@@ -64,8 +64,8 @@ struct RawNACP {
|
||||
u64_le cache_storage_size;
|
||||
u64_le cache_storage_journal_size;
|
||||
u64_le cache_storage_data_and_journal_max_size;
|
||||
u16_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE76);
|
||||
u64_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE70);
|
||||
};
|
||||
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
Result Read(s64 offset, void* buffer, size_t size) override {
|
||||
R_TRY(ValidateOffset(offset, size, m_size));
|
||||
|
||||
m_memory.ReadBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
|
||||
m_memory.ReadBlock(m_trmem->GetSourceAddress(), buffer, size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
R_UNLESS(m_is_writable, ResultUnknown);
|
||||
R_TRY(ValidateOffset(offset, size, m_size));
|
||||
|
||||
m_memory.WriteBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
|
||||
m_memory.WriteBlock(m_trmem->GetSourceAddress(), buffer, size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/save_data_controller.h"
|
||||
#include "core/hle/service/glue/glue_manager.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
#include "core/hle/service/ns/service_getter_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
@@ -42,7 +41,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_
|
||||
{26, D<&IApplicationFunctions::GetSaveDataSize>, "GetSaveDataSize"},
|
||||
{27, D<&IApplicationFunctions::CreateCacheStorage>, "CreateCacheStorage"},
|
||||
{28, D<&IApplicationFunctions::GetSaveDataSizeMax>, "GetSaveDataSizeMax"},
|
||||
{29, D<&IApplicationFunctions::GetCacheStorageMax>, "GetCacheStorageMax"},
|
||||
{29, nullptr, "GetCacheStorageMax"},
|
||||
{30, D<&IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed>, "BeginBlockingHomeButtonShortAndLongPressed"},
|
||||
{31, D<&IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed>, "EndBlockingHomeButtonShortAndLongPressed"},
|
||||
{32, D<&IApplicationFunctions::BeginBlockingHomeButton>, "BeginBlockingHomeButton"},
|
||||
@@ -271,22 +270,6 @@ Result IApplicationFunctions::GetSaveDataSizeMax(Out<u64> out_max_normal_size,
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetCacheStorageMax(Out<u32> out_cache_storage_index_max,
|
||||
Out<u64> out_max_journal_size) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
std::vector<u8> nacp;
|
||||
R_TRY(system.GetARPManager().GetControlProperty(&nacp, m_applet->program_id));
|
||||
|
||||
auto raw_nacp = std::make_unique<FileSys::RawNACP>();
|
||||
std::memcpy(raw_nacp.get(), nacp.data(), std::min(sizeof(*raw_nacp), nacp.size()));
|
||||
|
||||
*out_cache_storage_index_max = static_cast<u32>(raw_nacp->cache_storage_max_index);
|
||||
*out_max_journal_size = static_cast<u64>(raw_nacp->cache_storage_data_and_journal_max_size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed(s64 unused) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ private:
|
||||
Result CreateCacheStorage(Out<u32> out_target_media, Out<u64> out_required_size, u16 index,
|
||||
u64 normal_size, u64 journal_size);
|
||||
Result GetSaveDataSizeMax(Out<u64> out_max_normal_size, Out<u64> out_max_journal_size);
|
||||
Result GetCacheStorageMax(Out<u32> out_cache_storage_index_max, Out<u64> out_max_journal_size);
|
||||
Result BeginBlockingHomeButtonShortAndLongPressed(s64 unused);
|
||||
Result EndBlockingHomeButtonShortAndLongPressed();
|
||||
Result BeginBlockingHomeButton(s64 timeout_ns);
|
||||
|
||||
@@ -3,18 +3,141 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/btm/btm.h"
|
||||
#include "core/hle/service/btm/btm_debug.h"
|
||||
#include "core/hle/service/btm/btm_system.h"
|
||||
#include "core/hle/service/btm/btm_user.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
class IBtm final : public ServiceFramework<IBtm> {
|
||||
class IBtmUserCore final : public ServiceFramework<IBtmUserCore> {
|
||||
public:
|
||||
explicit IBtm(Core::System& system_) : ServiceFramework{system_, "btm"} {
|
||||
explicit IBtmUserCore(Core::System& system_)
|
||||
: ServiceFramework{system_, "IBtmUserCore"}, service_context{system_, "IBtmUserCore"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"},
|
||||
{1, nullptr, "GetBleScanFilterParameter"},
|
||||
{2, nullptr, "GetBleScanFilterParameter2"},
|
||||
{3, nullptr, "StartBleScanForGeneral"},
|
||||
{4, nullptr, "StopBleScanForGeneral"},
|
||||
{5, nullptr, "GetBleScanResultsForGeneral"},
|
||||
{6, nullptr, "StartBleScanForPaired"},
|
||||
{7, nullptr, "StopBleScanForPaired"},
|
||||
{8, nullptr, "StartBleScanForSmartDevice"},
|
||||
{9, nullptr, "StopBleScanForSmartDevice"},
|
||||
{10, nullptr, "GetBleScanResultsForSmartDevice"},
|
||||
{17, &IBtmUserCore::AcquireBleConnectionEvent, "AcquireBleConnectionEvent"},
|
||||
{18, nullptr, "BleConnect"},
|
||||
{19, nullptr, "BleDisconnect"},
|
||||
{20, nullptr, "BleGetConnectionState"},
|
||||
{21, nullptr, "AcquireBlePairingEvent"},
|
||||
{22, nullptr, "BlePairDevice"},
|
||||
{23, nullptr, "BleUnPairDevice"},
|
||||
{24, nullptr, "BleUnPairDevice2"},
|
||||
{25, nullptr, "BleGetPairedDevices"},
|
||||
{26, &IBtmUserCore::AcquireBleServiceDiscoveryEvent, "AcquireBleServiceDiscoveryEvent"},
|
||||
{27, nullptr, "GetGattServices"},
|
||||
{28, nullptr, "GetGattService"},
|
||||
{29, nullptr, "GetGattIncludedServices"},
|
||||
{30, nullptr, "GetBelongingGattService"},
|
||||
{31, nullptr, "GetGattCharacteristics"},
|
||||
{32, nullptr, "GetGattDescriptors"},
|
||||
{33, &IBtmUserCore::AcquireBleMtuConfigEvent, "AcquireBleMtuConfigEvent"},
|
||||
{34, nullptr, "ConfigureBleMtu"},
|
||||
{35, nullptr, "GetBleMtu"},
|
||||
{36, nullptr, "RegisterBleGattDataPath"},
|
||||
{37, nullptr, "UnregisterBleGattDataPath"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
|
||||
scan_event = service_context.CreateEvent("IBtmUserCore:ScanEvent");
|
||||
connection_event = service_context.CreateEvent("IBtmUserCore:ConnectionEvent");
|
||||
service_discovery_event = service_context.CreateEvent("IBtmUserCore:DiscoveryEvent");
|
||||
config_event = service_context.CreateEvent("IBtmUserCore:ConfigEvent");
|
||||
}
|
||||
|
||||
~IBtmUserCore() override {
|
||||
service_context.CloseEvent(scan_event);
|
||||
service_context.CloseEvent(connection_event);
|
||||
service_context.CloseEvent(service_discovery_event);
|
||||
service_context.CloseEvent(config_event);
|
||||
}
|
||||
|
||||
private:
|
||||
void AcquireBleScanEvent(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
rb.PushCopyObjects(scan_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
void AcquireBleConnectionEvent(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
rb.PushCopyObjects(connection_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
void AcquireBleServiceDiscoveryEvent(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
rb.PushCopyObjects(service_discovery_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
void AcquireBleMtuConfigEvent(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
rb.PushCopyObjects(config_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* scan_event;
|
||||
Kernel::KEvent* connection_event;
|
||||
Kernel::KEvent* service_discovery_event;
|
||||
Kernel::KEvent* config_event;
|
||||
};
|
||||
|
||||
class BTM_USR final : public ServiceFramework<BTM_USR> {
|
||||
public:
|
||||
explicit BTM_USR(Core::System& system_) : ServiceFramework{system_, "btm:u"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BTM_USR::GetCore, "GetCore"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetCore(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IBtmUserCore>(system);
|
||||
}
|
||||
};
|
||||
|
||||
class BTM final : public ServiceFramework<BTM> {
|
||||
public:
|
||||
explicit BTM(Core::System& system_) : ServiceFramework{system_, "btm"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetState"},
|
||||
@@ -109,13 +232,144 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class BTM_DBG final : public ServiceFramework<BTM_DBG> {
|
||||
public:
|
||||
explicit BTM_DBG(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "AcquireDiscoveryEvent"},
|
||||
{1, nullptr, "StartDiscovery"},
|
||||
{2, nullptr, "CancelDiscovery"},
|
||||
{3, nullptr, "GetDeviceProperty"},
|
||||
{4, nullptr, "CreateBond"},
|
||||
{5, nullptr, "CancelBond"},
|
||||
{6, nullptr, "SetTsiMode"},
|
||||
{7, nullptr, "GeneralTest"},
|
||||
{8, nullptr, "HidConnect"},
|
||||
{9, nullptr, "GeneralGet"},
|
||||
{10, nullptr, "GetGattClientDisconnectionReason"},
|
||||
{11, nullptr, "GetBleConnectionParameter"},
|
||||
{12, nullptr, "GetBleConnectionParameterRequest"},
|
||||
{13, nullptr, "Unknown13"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class IBtmSystemCore final : public ServiceFramework<IBtmSystemCore> {
|
||||
public:
|
||||
explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IBtmSystemCore::StartGamepadPairing, "StartGamepadPairing"},
|
||||
{1, &IBtmSystemCore::CancelGamepadPairing, "CancelGamepadPairing"},
|
||||
{2, nullptr, "ClearGamepadPairingDatabase"},
|
||||
{3, nullptr, "GetPairedGamepadCount"},
|
||||
{4, nullptr, "EnableRadio"},
|
||||
{5, nullptr, "DisableRadio"},
|
||||
{6, &IBtmSystemCore::IsRadioEnabled, "IsRadioEnabled"},
|
||||
{7, nullptr, "AcquireRadioEvent"},
|
||||
{8, nullptr, "AcquireGamepadPairingEvent"},
|
||||
{9, nullptr, "IsGamepadPairingStarted"},
|
||||
{10, nullptr, "StartAudioDeviceDiscovery"},
|
||||
{11, nullptr, "StopAudioDeviceDiscovery"},
|
||||
{12, nullptr, "IsDiscoveryingAudioDevice"},
|
||||
{13, nullptr, "GetDiscoveredAudioDevice"},
|
||||
{14, nullptr, "AcquireAudioDeviceConnectionEvent"},
|
||||
{15, nullptr, "ConnectAudioDevice"},
|
||||
{16, nullptr, "IsConnectingAudioDevice"},
|
||||
{17, &IBtmSystemCore::GetConnectedAudioDevices, "GetConnectedAudioDevices"},
|
||||
{18, nullptr, "DisconnectAudioDevice"},
|
||||
{19, nullptr, "AcquirePairedAudioDeviceInfoChangedEvent"},
|
||||
{20, &IBtmSystemCore::GetPairedAudioDevices, "GetPairedAudioDevices"},
|
||||
{21, nullptr, "RemoveAudioDevicePairing"},
|
||||
{22, &IBtmSystemCore::RequestAudioDeviceConnectionRejection, "RequestAudioDeviceConnectionRejection"},
|
||||
{23, &IBtmSystemCore::CancelAudioDeviceConnectionRejection, "CancelAudioDeviceConnectionRejection"}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void IsRadioEnabled(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BTM, "(STUBBED) called"); // Spams a lot when controller applet is running
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
}
|
||||
|
||||
void StartGamepadPairing(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void CancelGamepadPairing(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void CancelAudioDeviceConnectionRejection(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void GetConnectedAudioDevices(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0);
|
||||
}
|
||||
|
||||
void GetPairedAudioDevices(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0);
|
||||
}
|
||||
|
||||
void RequestAudioDeviceConnectionRejection(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
class BTM_SYS final : public ServiceFramework<BTM_SYS> {
|
||||
public:
|
||||
explicit BTM_SYS(Core::System& system_) : ServiceFramework{system_, "btm:sys"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BTM_SYS::GetCore, "GetCore"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetCore(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_BTM, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IBtmSystemCore>(system);
|
||||
}
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("btm", std::make_shared<IBtm>(system));
|
||||
server_manager->RegisterNamedService("btm:dbg", std::make_shared<IBtmDebug>(system));
|
||||
server_manager->RegisterNamedService("btm:sys", std::make_shared<IBtmSystem>(system));
|
||||
server_manager->RegisterNamedService("btm:u", std::make_shared<IBtmUser>(system));
|
||||
server_manager->RegisterNamedService("btm", std::make_shared<BTM>(system));
|
||||
server_manager->RegisterNamedService("btm:dbg", std::make_shared<BTM_DBG>(system));
|
||||
server_manager->RegisterNamedService("btm:sys", std::make_shared<BTM_SYS>(system));
|
||||
server_manager->RegisterNamedService("btm:u", std::make_shared<BTM_USR>(system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
};
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/hle/service/btm/btm_debug.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
IBtmDebug::IBtmDebug(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "AcquireDiscoveryEvent"},
|
||||
{1, nullptr, "StartDiscovery"},
|
||||
{2, nullptr, "CancelDiscovery"},
|
||||
{3, nullptr, "GetDeviceProperty"},
|
||||
{4, nullptr, "CreateBond"},
|
||||
{5, nullptr, "CancelBond"},
|
||||
{6, nullptr, "SetTsiMode"},
|
||||
{7, nullptr, "GeneralTest"},
|
||||
{8, nullptr, "HidConnect"},
|
||||
{9, nullptr, "GeneralGet"},
|
||||
{10, nullptr, "GetGattClientDisconnectionReason"},
|
||||
{11, nullptr, "GetBleConnectionParameter"},
|
||||
{12, nullptr, "GetBleConnectionParameterRequest"},
|
||||
{13, nullptr, "Unknown13"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IBtmDebug::~IBtmDebug() = default;
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,21 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
class IBtmDebug final : public ServiceFramework<IBtmDebug> {
|
||||
public:
|
||||
explicit IBtmDebug(Core::System& system_);
|
||||
~IBtmDebug() override;
|
||||
};
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/btm/btm_system.h"
|
||||
#include "core/hle/service/btm/btm_system_core.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
IBtmSystem::IBtmSystem(Core::System& system_) : ServiceFramework{system_, "btm:sys"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, C<&IBtmSystem::GetCore>, "GetCore"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IBtmSystem::~IBtmSystem() = default;
|
||||
|
||||
Result IBtmSystem::GetCore(OutInterface<IBtmSystemCore> out_interface) {
|
||||
LOG_WARNING(Service_BTM, "called");
|
||||
|
||||
*out_interface = std::make_shared<IBtmSystemCore>(system);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::BTM {
|
||||
class IBtmSystemCore;
|
||||
|
||||
class IBtmSystem final : public ServiceFramework<IBtmSystem> {
|
||||
public:
|
||||
explicit IBtmSystem(Core::System& system_);
|
||||
~IBtmSystem() override;
|
||||
|
||||
private:
|
||||
Result GetCore(OutInterface<IBtmSystemCore> out_interface);
|
||||
};
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,127 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/btm/btm_system_core.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
IBtmSystemCore::IBtmSystemCore(Core::System& system_)
|
||||
: ServiceFramework{system_, "IBtmSystemCore"}, service_context{system_, "IBtmSystemCore"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, C<&IBtmSystemCore::StartGamepadPairing>, "StartGamepadPairing"},
|
||||
{1, C<&IBtmSystemCore::CancelGamepadPairing>, "CancelGamepadPairing"},
|
||||
{2, nullptr, "ClearGamepadPairingDatabase"},
|
||||
{3, nullptr, "GetPairedGamepadCount"},
|
||||
{4, C<&IBtmSystemCore::EnableRadio>, "EnableRadio"},
|
||||
{5, C<&IBtmSystemCore::DisableRadio>, "DisableRadio"},
|
||||
{6, C<&IBtmSystemCore::IsRadioEnabled>, "IsRadioEnabled"},
|
||||
{7, C<&IBtmSystemCore::AcquireRadioEvent>, "AcquireRadioEvent"},
|
||||
{8, nullptr, "AcquireGamepadPairingEvent"},
|
||||
{9, nullptr, "IsGamepadPairingStarted"},
|
||||
{10, nullptr, "StartAudioDeviceDiscovery"},
|
||||
{11, nullptr, "StopAudioDeviceDiscovery"},
|
||||
{12, nullptr, "IsDiscoveryingAudioDevice"},
|
||||
{13, nullptr, "GetDiscoveredAudioDevice"},
|
||||
{14, C<&IBtmSystemCore::AcquireAudioDeviceConnectionEvent>, "AcquireAudioDeviceConnectionEvent"},
|
||||
{15, nullptr, "ConnectAudioDevice"},
|
||||
{16, nullptr, "IsConnectingAudioDevice"},
|
||||
{17, C<&IBtmSystemCore::GetConnectedAudioDevices>, "GetConnectedAudioDevices"},
|
||||
{18, nullptr, "DisconnectAudioDevice"},
|
||||
{19, nullptr, "AcquirePairedAudioDeviceInfoChangedEvent"},
|
||||
{20, C<&IBtmSystemCore::GetPairedAudioDevices>, "GetPairedAudioDevices"},
|
||||
{21, nullptr, "RemoveAudioDevicePairing"},
|
||||
{22, C<&IBtmSystemCore::RequestAudioDeviceConnectionRejection>, "RequestAudioDeviceConnectionRejection"},
|
||||
{23, C<&IBtmSystemCore::CancelAudioDeviceConnectionRejection>, "CancelAudioDeviceConnectionRejection"}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
radio_event = service_context.CreateEvent("IBtmSystemCore::RadioEvent");
|
||||
audio_device_connection_event =
|
||||
service_context.CreateEvent("IBtmSystemCore::AudioDeviceConnectionEvent");
|
||||
|
||||
m_set_sys =
|
||||
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
|
||||
}
|
||||
|
||||
IBtmSystemCore::~IBtmSystemCore() {
|
||||
service_context.CloseEvent(radio_event);
|
||||
service_context.CloseEvent(audio_device_connection_event);
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::StartGamepadPairing() {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::CancelGamepadPairing() {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::EnableRadio() {
|
||||
LOG_DEBUG(Service_BTM, "called");
|
||||
|
||||
R_RETURN(m_set_sys->SetBluetoothEnableFlag(true));
|
||||
}
|
||||
Result IBtmSystemCore::DisableRadio() {
|
||||
LOG_DEBUG(Service_BTM, "called");
|
||||
|
||||
R_RETURN(m_set_sys->SetBluetoothEnableFlag(false));
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::IsRadioEnabled(Out<bool> out_is_enabled) {
|
||||
LOG_DEBUG(Service_BTM, "called");
|
||||
|
||||
R_RETURN(m_set_sys->GetBluetoothEnableFlag(out_is_enabled));
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::AcquireRadioEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_is_valid = true;
|
||||
*out_event = &radio_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::AcquireAudioDeviceConnectionEvent(
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_event = &audio_device_connection_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::GetConnectedAudioDevices(
|
||||
Out<s32> out_count, OutArray<std::array<u8, 0xFF>, BufferAttr_HipcPointer> out_audio_devices) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::GetPairedAudioDevices(
|
||||
Out<s32> out_count, OutArray<std::array<u8, 0xFF>, BufferAttr_HipcPointer> out_audio_devices) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::RequestAudioDeviceConnectionRejection(ClientAppletResourceUserId aruid) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called, applet_resource_user_id={}", aruid.pid);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmSystemCore::CancelAudioDeviceConnectionRejection(ClientAppletResourceUserId aruid) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called, applet_resource_user_id={}", aruid.pid);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Set {
|
||||
class ISystemSettingsServer;
|
||||
}
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
class IBtmSystemCore final : public ServiceFramework<IBtmSystemCore> {
|
||||
public:
|
||||
explicit IBtmSystemCore(Core::System& system_);
|
||||
~IBtmSystemCore() override;
|
||||
|
||||
private:
|
||||
Result StartGamepadPairing();
|
||||
Result CancelGamepadPairing();
|
||||
Result EnableRadio();
|
||||
Result DisableRadio();
|
||||
Result IsRadioEnabled(Out<bool> out_is_enabled);
|
||||
|
||||
Result AcquireRadioEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
Result AcquireAudioDeviceConnectionEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
Result GetConnectedAudioDevices(
|
||||
Out<s32> out_count,
|
||||
OutArray<std::array<u8, 0xFF>, BufferAttr_HipcPointer> out_audio_devices);
|
||||
|
||||
Result GetPairedAudioDevices(
|
||||
Out<s32> out_count,
|
||||
OutArray<std::array<u8, 0xFF>, BufferAttr_HipcPointer> out_audio_devices);
|
||||
|
||||
Result RequestAudioDeviceConnectionRejection(ClientAppletResourceUserId aruid);
|
||||
Result CancelAudioDeviceConnectionRejection(ClientAppletResourceUserId aruid);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* radio_event;
|
||||
Kernel::KEvent* audio_device_connection_event;
|
||||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
||||
};
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,30 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/btm/btm_user.h"
|
||||
#include "core/hle/service/btm/btm_user_core.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
IBtmUser::IBtmUser(Core::System& system_) : ServiceFramework{system_, "btm:u"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, C<&IBtmUser::GetCore>, "GetCore"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IBtmUser::~IBtmUser() = default;
|
||||
|
||||
Result IBtmUser::GetCore(OutInterface<IBtmUserCore> out_interface) {
|
||||
LOG_WARNING(Service_BTM, "called");
|
||||
|
||||
*out_interface = std::make_shared<IBtmUserCore>(system);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::BTM {
|
||||
class IBtmUserCore;
|
||||
|
||||
class IBtmUser final : public ServiceFramework<IBtmUser> {
|
||||
public:
|
||||
explicit IBtmUser(Core::System& system_);
|
||||
~IBtmUser() override;
|
||||
|
||||
private:
|
||||
Result GetCore(OutInterface<IBtmUserCore> out_interface);
|
||||
};
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,103 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/btm/btm_user_core.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
IBtmUserCore::IBtmUserCore(Core::System& system_)
|
||||
: ServiceFramework{system_, "IBtmUserCore"}, service_context{system_, "IBtmUserCore"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, C<&IBtmUserCore::AcquireBleScanEvent>, "AcquireBleScanEvent"},
|
||||
{1, nullptr, "GetBleScanFilterParameter"},
|
||||
{2, nullptr, "GetBleScanFilterParameter2"},
|
||||
{3, nullptr, "StartBleScanForGeneral"},
|
||||
{4, nullptr, "StopBleScanForGeneral"},
|
||||
{5, nullptr, "GetBleScanResultsForGeneral"},
|
||||
{6, nullptr, "StartBleScanForPaired"},
|
||||
{7, nullptr, "StopBleScanForPaired"},
|
||||
{8, nullptr, "StartBleScanForSmartDevice"},
|
||||
{9, nullptr, "StopBleScanForSmartDevice"},
|
||||
{10, nullptr, "GetBleScanResultsForSmartDevice"},
|
||||
{17, C<&IBtmUserCore::AcquireBleConnectionEvent>, "AcquireBleConnectionEvent"},
|
||||
{18, nullptr, "BleConnect"},
|
||||
{19, nullptr, "BleDisconnect"},
|
||||
{20, nullptr, "BleGetConnectionState"},
|
||||
{21, nullptr, "AcquireBlePairingEvent"},
|
||||
{22, nullptr, "BlePairDevice"},
|
||||
{23, nullptr, "BleUnPairDevice"},
|
||||
{24, nullptr, "BleUnPairDevice2"},
|
||||
{25, nullptr, "BleGetPairedDevices"},
|
||||
{26, C<&IBtmUserCore::AcquireBleServiceDiscoveryEvent>, "AcquireBleServiceDiscoveryEvent"},
|
||||
{27, nullptr, "GetGattServices"},
|
||||
{28, nullptr, "GetGattService"},
|
||||
{29, nullptr, "GetGattIncludedServices"},
|
||||
{30, nullptr, "GetBelongingGattService"},
|
||||
{31, nullptr, "GetGattCharacteristics"},
|
||||
{32, nullptr, "GetGattDescriptors"},
|
||||
{33, C<&IBtmUserCore::AcquireBleMtuConfigEvent>, "AcquireBleMtuConfigEvent"},
|
||||
{34, nullptr, "ConfigureBleMtu"},
|
||||
{35, nullptr, "GetBleMtu"},
|
||||
{36, nullptr, "RegisterBleGattDataPath"},
|
||||
{37, nullptr, "UnregisterBleGattDataPath"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
|
||||
scan_event = service_context.CreateEvent("IBtmUserCore:ScanEvent");
|
||||
connection_event = service_context.CreateEvent("IBtmUserCore:ConnectionEvent");
|
||||
service_discovery_event = service_context.CreateEvent("IBtmUserCore:DiscoveryEvent");
|
||||
config_event = service_context.CreateEvent("IBtmUserCore:ConfigEvent");
|
||||
}
|
||||
|
||||
IBtmUserCore::~IBtmUserCore() {
|
||||
service_context.CloseEvent(scan_event);
|
||||
service_context.CloseEvent(connection_event);
|
||||
service_context.CloseEvent(service_discovery_event);
|
||||
service_context.CloseEvent(config_event);
|
||||
}
|
||||
|
||||
Result IBtmUserCore::AcquireBleScanEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_is_valid = true;
|
||||
*out_event = &scan_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmUserCore::AcquireBleConnectionEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_is_valid = true;
|
||||
*out_event = &connection_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmUserCore::AcquireBleServiceDiscoveryEvent(
|
||||
Out<bool> out_is_valid, OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_is_valid = true;
|
||||
*out_event = &service_discovery_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IBtmUserCore::AcquireBleMtuConfigEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_BTM, "(STUBBED) called");
|
||||
|
||||
*out_is_valid = true;
|
||||
*out_event = &config_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::BTM {
|
||||
|
||||
class IBtmUserCore final : public ServiceFramework<IBtmUserCore> {
|
||||
public:
|
||||
explicit IBtmUserCore(Core::System& system_);
|
||||
~IBtmUserCore() override;
|
||||
|
||||
private:
|
||||
Result AcquireBleScanEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
Result AcquireBleConnectionEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
Result AcquireBleServiceDiscoveryEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
Result AcquireBleMtuConfigEvent(Out<bool> out_is_valid,
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* scan_event;
|
||||
Kernel::KEvent* connection_event;
|
||||
Kernel::KEvent* service_discovery_event;
|
||||
Kernel::KEvent* config_event;
|
||||
};
|
||||
|
||||
} // namespace Service::BTM
|
||||
@@ -336,7 +336,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{1012, nullptr, "GetFsStackUsage"},
|
||||
{1013, nullptr, "UnsetSaveDataRootPath"},
|
||||
{1014, nullptr, "OutputMultiProgramTagAccessLog"},
|
||||
{1016, &FSP_SRV::FlushAccessLogOnSdCard, "FlushAccessLogOnSdCard"},
|
||||
{1016, nullptr, "FlushAccessLogOnSdCard"},
|
||||
{1017, nullptr, "OutputApplicationInfoAccessLog"},
|
||||
{1018, nullptr, "SetDebugOption"},
|
||||
{1019, nullptr, "UnsetDebugOption"},
|
||||
@@ -706,13 +706,6 @@ void FSP_SRV::GetProgramIndexForAccessLog(HLERequestContext& ctx) {
|
||||
rb.Push(access_log_program_index);
|
||||
}
|
||||
|
||||
void FSP_SRV::FlushAccessLogOnSdCard(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void FSP_SRV::GetCacheStorageSize(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto index{rp.Pop<s32>()};
|
||||
|
||||
@@ -58,7 +58,6 @@ private:
|
||||
void SetGlobalAccessLogMode(HLERequestContext& ctx);
|
||||
void GetGlobalAccessLogMode(HLERequestContext& ctx);
|
||||
void OutputAccessLogToSdCard(HLERequestContext& ctx);
|
||||
void FlushAccessLogOnSdCard(HLERequestContext& ctx);
|
||||
void GetProgramIndexForAccessLog(HLERequestContext& ctx);
|
||||
void OpenMultiCommitManager(HLERequestContext& ctx);
|
||||
void GetCacheStorageSize(HLERequestContext& ctx);
|
||||
|
||||
@@ -441,13 +441,12 @@ Result IApplicationManagerInterface::GetApplicationRightsOnClient(
|
||||
flags, application_id, account_id.FormattedString());
|
||||
|
||||
if (!out_rights.empty()) {
|
||||
ApplicationRightsOnClient rights{};
|
||||
rights.application_id = application_id;
|
||||
rights.uid = account_id;
|
||||
rights.flags = 0;
|
||||
rights.flags2 = 0;
|
||||
|
||||
out_rights[0] = rights;
|
||||
out_rights[0] = {
|
||||
.application_id = application_id,
|
||||
.uid = account_id,
|
||||
.flags = 0,
|
||||
.flags2 = 0,
|
||||
};
|
||||
*out_count = 1;
|
||||
} else {
|
||||
*out_count = 0;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/ns/develop_interface.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
@@ -13,8 +13,8 @@ enum class ApplicationRecordType : u8 {
|
||||
Installing = 2,
|
||||
Installed = 3,
|
||||
GameCardNotInserted = 5,
|
||||
Archived = 11,
|
||||
GameCard = 16,
|
||||
Archived = 0xB,
|
||||
GameCard = 0x10,
|
||||
};
|
||||
|
||||
enum class ApplicationControlSource : u8 {
|
||||
@@ -37,34 +37,31 @@ struct ApplicationRecord {
|
||||
u8 unknown2;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x7);
|
||||
};
|
||||
static_assert(sizeof(ApplicationRecord) == 0x18, "ApplicationRecord has incorrect size.");
|
||||
static_assert(sizeof(ApplicationRecord) == 0x18, "ApplicationRecord is an invalid size");
|
||||
|
||||
/// ApplicationView
|
||||
struct ApplicationView {
|
||||
u64 application_id; ///< ApplicationId.
|
||||
u32 unk; ///< Unknown.
|
||||
u32 flags; ///< Flags.
|
||||
std::array<u8, 0x10> unk_x10; ///< Unknown.
|
||||
u32 unk_x20; ///< Unknown.
|
||||
u16 unk_x24; ///< Unknown.
|
||||
std::array<u8, 0x2> unk_x26; ///< Unknown.
|
||||
std::array<u8, 0x8> unk_x28; ///< Unknown.
|
||||
std::array<u8, 0x10> unk_x30; ///< Unknown.
|
||||
u32 unk_x40; ///< Unknown.
|
||||
u8 unk_x44; ///< Unknown.
|
||||
std::array<u8, 0xb> unk_x45; ///< Unknown.
|
||||
u64 application_id; ///< ApplicationId.
|
||||
u32 unk; ///< Unknown.
|
||||
u32 flags; ///< Flags.
|
||||
u8 unk_x10[0x10]; ///< Unknown.
|
||||
u32 unk_x20; ///< Unknown.
|
||||
u16 unk_x24; ///< Unknown.
|
||||
u8 unk_x26[0x2]; ///< Unknown.
|
||||
u8 unk_x28[0x8]; ///< Unknown.
|
||||
u8 unk_x30[0x10]; ///< Unknown.
|
||||
u32 unk_x40; ///< Unknown.
|
||||
u8 unk_x44; ///< Unknown.
|
||||
u8 unk_x45[0xb]; ///< Unknown.
|
||||
};
|
||||
static_assert(sizeof(ApplicationView) == 0x50, "ApplicationView has incorrect size.");
|
||||
|
||||
struct ApplicationRightsOnClient {
|
||||
u64 application_id;
|
||||
Common::UUID uid;
|
||||
u8 flags;
|
||||
u8 flags2;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x6);
|
||||
INSERT_PADDING_BYTES(0x6);
|
||||
};
|
||||
static_assert(sizeof(ApplicationRightsOnClient) == 0x20,
|
||||
"ApplicationRightsOnClient has incorrect size.");
|
||||
|
||||
/// NsPromotionInfo
|
||||
struct PromotionInfo {
|
||||
@@ -77,15 +74,12 @@ struct PromotionInfo {
|
||||
///< remaining_time is set.
|
||||
INSERT_PADDING_BYTES_NOINIT(0x3);
|
||||
};
|
||||
static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size.");
|
||||
|
||||
/// NsApplicationViewWithPromotionInfo
|
||||
struct ApplicationViewWithPromotionInfo {
|
||||
ApplicationView view; ///< \ref NsApplicationView
|
||||
PromotionInfo promotion; ///< \ref NsPromotionInfo
|
||||
};
|
||||
static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70,
|
||||
"ApplicationViewWithPromotionInfo has incorrect size.");
|
||||
|
||||
struct ApplicationOccupiedSizeEntity {
|
||||
FileSys::StorageId storage_id;
|
||||
@@ -99,13 +93,10 @@ static_assert(sizeof(ApplicationOccupiedSizeEntity) == 0x20,
|
||||
struct ApplicationOccupiedSize {
|
||||
std::array<ApplicationOccupiedSizeEntity, 4> entities;
|
||||
};
|
||||
static_assert(sizeof(ApplicationOccupiedSize) == 0x80,
|
||||
"ApplicationOccupiedSize has incorrect size.");
|
||||
|
||||
struct ContentPath {
|
||||
u8 file_system_proxy_type;
|
||||
u64 program_id;
|
||||
};
|
||||
static_assert(sizeof(ContentPath) == 0x10, "ContentPath has incorrect size.");
|
||||
|
||||
} // namespace Service::NS
|
||||
|
||||
@@ -43,7 +43,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
|
||||
const auto size = out_buffer.size();
|
||||
|
||||
const auto icon_size = control.second ? control.second->GetSize() : 0;
|
||||
const auto total_size = sizeof(FileSys::RawNACP) + icon_size;
|
||||
const auto total_size = 0x4000 + icon_size;
|
||||
|
||||
if (size < total_size) {
|
||||
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)",
|
||||
@@ -57,11 +57,11 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero",
|
||||
application_id);
|
||||
std::memset(out_buffer.data(), 0, sizeof(FileSys::RawNACP));
|
||||
std::memset(out_buffer.data(), 0, 0x4000);
|
||||
}
|
||||
|
||||
if (control.second != nullptr) {
|
||||
control.second->Read(out_buffer.data() + sizeof(FileSys::RawNACP), icon_size);
|
||||
control.second->Read(out_buffer.data() + 0x4000, icon_size);
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing icon data for application_id={:016X}", application_id);
|
||||
}
|
||||
|
||||
@@ -44,10 +44,6 @@ struct Display {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasLayers() {
|
||||
return !stack.layers.empty();
|
||||
}
|
||||
|
||||
u64 id;
|
||||
LayerStack stack;
|
||||
};
|
||||
|
||||
@@ -33,17 +33,16 @@ void SurfaceFlinger::RemoveDisplay(u64 display_id) {
|
||||
std::erase_if(m_displays, [&](auto& display) { return display.id == display_id; });
|
||||
}
|
||||
|
||||
bool SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
void SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
u64 display_id) {
|
||||
auto* const display = this->FindDisplay(display_id);
|
||||
if (!display || !display->HasLayers()) {
|
||||
return false;
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
*out_swap_interval =
|
||||
m_composer.ComposeLocked(out_compose_speed_scale, *display,
|
||||
*nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SurfaceFlinger::AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id) {
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
|
||||
void AddDisplay(u64 display_id);
|
||||
void RemoveDisplay(u64 display_id);
|
||||
bool ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
void ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
|
||||
void AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id);
|
||||
void RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id);
|
||||
|
||||
@@ -218,11 +218,10 @@ void Container::DestroyBufferQueueLocked(Layer* layer) {
|
||||
layer->GetProducerBinderId());
|
||||
}
|
||||
|
||||
bool Container::ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
void Container::ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
u64 display_id) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
return m_surface_flinger->ComposeDisplay(out_swap_interval, out_compose_speed_scale,
|
||||
display_id);
|
||||
m_surface_flinger->ComposeDisplay(out_swap_interval, out_compose_speed_scale, display_id);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -76,7 +76,7 @@ private:
|
||||
void DestroyBufferQueueLocked(Layer* layer);
|
||||
|
||||
public:
|
||||
bool ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
void ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
|
||||
private:
|
||||
std::mutex m_lock{};
|
||||
|
||||
@@ -117,9 +117,9 @@ bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
||||
(in < metadata.alias_extents.base ||
|
||||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
|
||||
in >= metadata.heap_extents.base + metadata.alias_extents.size) &&
|
||||
(in < metadata.aslr_extents.base ||
|
||||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
|
||||
in >= metadata.heap_extents.base + metadata.aslr_extents.size)) {
|
||||
LOG_DEBUG(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
|
||||
@@ -251,12 +251,11 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string&
|
||||
* \param callback Callback to report the progress of the installation. The first size_t
|
||||
* parameter is the total size of the installed contents and the second is the current progress. If
|
||||
* you return true to the callback, it will cancel the installation as soon as possible.
|
||||
* \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install.
|
||||
* \return A list of entries that failed to install. Returns an empty vector if successful.
|
||||
*/
|
||||
inline std::vector<std::string> VerifyInstalledContents(
|
||||
Core::System& system, FileSys::ManualContentProvider& provider,
|
||||
const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) {
|
||||
const std::function<bool(size_t, size_t)>& callback) {
|
||||
// Get content registries.
|
||||
auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
|
||||
auto user_contents = system.GetFileSystemController().GetUserNANDContents();
|
||||
@@ -265,7 +264,7 @@ inline std::vector<std::string> VerifyInstalledContents(
|
||||
if (bis_contents) {
|
||||
content_providers.push_back(bis_contents);
|
||||
}
|
||||
if (user_contents && !firmware_only) {
|
||||
if (user_contents) {
|
||||
content_providers.push_back(user_contents);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,13 +174,9 @@ void EmulatedController::LoadDevices() {
|
||||
// Only map virtual devices to the first controller
|
||||
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
#ifdef HAVE_LIBUSB
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
android_params = Common::ParamPackage{"engine:android,port:100"};
|
||||
#endif
|
||||
}
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
|
||||
@@ -128,11 +128,10 @@ private:
|
||||
const std::string razer_vid{"1532"};
|
||||
const std::string redmagic_vid{"3537"};
|
||||
const std::string backbone_labs_vid{"358a"};
|
||||
const std::string xbox_vid{"045e"};
|
||||
const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid,
|
||||
redmagic_vid, backbone_labs_vid, xbox_vid};
|
||||
const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid, redmagic_vid,
|
||||
backbone_labs_vid};
|
||||
const std::vector<std::string> flipped_xy_vids{sony_vid, razer_vid, redmagic_vid,
|
||||
backbone_labs_vid, xbox_vid};
|
||||
backbone_labs_vid};
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
||||
|
||||
@@ -1603,7 +1603,6 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
// Help
|
||||
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
|
||||
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
|
||||
connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware);
|
||||
connect_menu(ui->action_About, &GMainWindow::OnAbout);
|
||||
}
|
||||
|
||||
@@ -1632,8 +1631,6 @@ void GMainWindow::UpdateMenuState() {
|
||||
action->setEnabled(emulation_running);
|
||||
}
|
||||
|
||||
ui->action_Install_Firmware->setEnabled(!emulation_running);
|
||||
|
||||
for (QAction* action : applet_actions) {
|
||||
action->setEnabled(is_firmware_available && !emulation_running);
|
||||
}
|
||||
@@ -4153,146 +4150,6 @@ void GMainWindow::OnVerifyInstalledContents() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnInstallFirmware() {
|
||||
// Don't do this while emulation is running, that'd probably be a bad idea.
|
||||
if (emu_thread != nullptr && emu_thread->IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for installed keys, error out, suggest restart?
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::information(
|
||||
this, tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart yuzu before attempting to install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
QString firmware_source_location =
|
||||
QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"),
|
||||
QString::fromStdString(""), QFileDialog::ShowDirsOnly);
|
||||
if (firmware_source_location.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
progress.show();
|
||||
|
||||
// Declare progress callback.
|
||||
auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString());
|
||||
|
||||
// Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
|
||||
// there.)
|
||||
std::filesystem::path firmware_source_path = firmware_source_location.toStdString();
|
||||
if (!Common::FS::IsDir(firmware_source_path)) {
|
||||
progress.close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> out;
|
||||
const Common::FS::DirEntryCallable callback =
|
||||
[&out](const std::filesystem::directory_entry& entry) {
|
||||
if (entry.path().has_extension() && entry.path().extension() == ".nca")
|
||||
out.emplace_back(entry.path());
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
QtProgressCallback(100, 10);
|
||||
|
||||
Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
|
||||
if (out.size() <= 0) {
|
||||
progress.close();
|
||||
QMessageBox::warning(this, tr("Firmware install failed"),
|
||||
tr("Unable to locate potential firmware NCA files"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Locate and erase the content of nand/system/Content/registered/*.nca, if any.
|
||||
auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
|
||||
if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
|
||||
progress.close();
|
||||
QMessageBox::critical(this, tr("Firmware install failed"),
|
||||
tr("Failed to delete one or more firmware file."));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend,
|
||||
"Cleaned nand/system/Content/registered folder in preparation for new firmware.");
|
||||
|
||||
QtProgressCallback(100, 20);
|
||||
|
||||
auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
|
||||
|
||||
bool success = true;
|
||||
bool cancelled = false;
|
||||
int i = 0;
|
||||
for (const auto& firmware_src_path : out) {
|
||||
i++;
|
||||
auto firmware_src_vfile =
|
||||
vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile =
|
||||
firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
|
||||
|
||||
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
|
||||
LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
|
||||
firmware_src_path.generic_string(), firmware_src_path.filename().string());
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) {
|
||||
success = false;
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && !cancelled) {
|
||||
progress.close();
|
||||
QMessageBox::critical(this, tr("Firmware install failed"),
|
||||
tr("One or more firmware files failed to copy into NAND."));
|
||||
return;
|
||||
} else if (cancelled) {
|
||||
progress.close();
|
||||
QMessageBox::warning(this, tr("Firmware install failed"),
|
||||
tr("Firmware installation cancelled, firmware may be in bad state, "
|
||||
"restart yuzu or re-install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-scan VFS for the newly placed firmware files.
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
auto result =
|
||||
ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true);
|
||||
|
||||
if (result.size() > 0) {
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
progress.close();
|
||||
QMessageBox::critical(
|
||||
this, tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
return;
|
||||
}
|
||||
|
||||
progress.close();
|
||||
OnCheckFirmwareDecryption();
|
||||
}
|
||||
|
||||
void GMainWindow::OnAbout() {
|
||||
AboutDialog aboutDialog(this);
|
||||
aboutDialog.exec();
|
||||
|
||||
@@ -380,7 +380,6 @@ private slots:
|
||||
void OnLoadAmiibo();
|
||||
void OnOpenYuzuFolder();
|
||||
void OnVerifyInstalledContents();
|
||||
void OnInstallFirmware();
|
||||
void OnAbout();
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleStatusBar();
|
||||
|
||||
@@ -25,16 +25,7 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin" stdset="0">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
@@ -165,8 +156,8 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Configure_Tas"/>
|
||||
</widget>
|
||||
<addaction name="action_Rederive"/>
|
||||
<addaction name="action_Verify_installed_contents"/>
|
||||
<addaction name="action_Install_Firmware"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_cabinet_applet"/>
|
||||
<addaction name="action_Load_Album"/>
|
||||
@@ -464,11 +455,6 @@
|
||||
<string>Open &Controller Menu</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Install_Firmware">
|
||||
<property name="text">
|
||||
<string>Install Firmware</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="yuzu.qrc"/>
|
||||
|
||||
Reference in New Issue
Block a user